Merge pull request #119 from coollabsio/next

Unique secrets by applications
This commit is contained in:
Andras Bacsai 2022-02-12 15:31:53 +01:00 committed by GitHub
commit 004724da55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 166 additions and 115 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "coolify", "name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.", "description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "2.0.5", "version": "2.0.6",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0", "dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",

View File

@ -0,0 +1,11 @@
/*
Warnings:
- A unique constraint covering the columns `[name,applicationId]` on the table `Secret` will be added. If there are existing duplicate values, this will fail.
*/
-- DropIndex
DROP INDEX "Secret_name_key";
-- CreateIndex
CREATE UNIQUE INDEX "Secret_name_applicationId_key" ON "Secret"("name", "applicationId");

View File

@ -105,13 +105,15 @@ model ApplicationSettings {
model Secret { model Secret {
id String @id @default(cuid()) id String @id @default(cuid())
name String @unique name String
value String value String
isBuildSecret Boolean @default(false) isBuildSecret Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id]) application Application @relation(fields: [applicationId], references: [id])
applicationId String applicationId String
@@unique([name, applicationId])
} }
model BuildLog { model BuildLog {

View File

@ -28,12 +28,12 @@ export async function login({ email, password }) {
console.log('Network created'); console.log('Network created');
}) })
.catch(() => { .catch(() => {
console.log('Network already exists'); console.log('Network already exists.');
}); });
startCoolifyProxy('/var/run/docker.sock') startCoolifyProxy('/var/run/docker.sock')
.then(() => { .then(() => {
console.log('Coolify Proxy Started'); console.log('Coolify Proxy started.');
}) })
.catch((err) => { .catch((err) => {
console.log(err); console.log(err);

View File

@ -6,14 +6,19 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { del, post } from '$lib/api'; import { del, post } from '$lib/api';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
if (name) value = 'ENCRYPTED';
const { id } = $page.params; const { id } = $page.params;
async function removeSecret() { async function removeSecret() {
try { try {
await del(`/applications/${id}/secrets.json`, { name }); await del(`/applications/${id}/secrets.json`, { name });
return window.location.reload(); dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
}
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
@ -21,7 +26,11 @@
async function saveSecret() { async function saveSecret() {
try { try {
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret }); await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret });
return window.location.reload(); dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
}
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
@ -33,39 +42,29 @@
} }
</script> </script>
<div class="mx-auto max-w-3xl pt-4"> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<div class="flex space-x-2">
<div class="grid grid-flow-row">
<label for="secretName">Name</label>
<input <input
id="secretName" id="secretName"
bind:value={name} bind:value={name}
placeholder="EXAMPLE_VARIABLE" placeholder="EXAMPLE_VARIABLE"
class="w-64 border-2 border-transparent" class="-mx-2 w-64 border-2 border-transparent"
readonly={!isNewSecret} readonly={!isNewSecret}
class:hover:bg-coolgray-200={!isNewSecret} class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret} class:cursor-not-allowed={!isNewSecret}
/> />
</div> </td>
<div class="grid grid-flow-row"> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<label for="secretValue">Value (will be encrypted)</label>
<input <input
id="secretValue" id="secretValue"
bind:value bind:value
placeholder="J$#@UIO%HO#$U%H" placeholder="J$#@UIO%HO#$U%H"
class="w-64 border-2 border-transparent" class="-mx-2 w-64 border-2 border-transparent"
class:hover:bg-coolgray-200={!isNewSecret} class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret} class:cursor-not-allowed={!isNewSecret}
readonly={!isNewSecret} readonly={!isNewSecret}
/> />
</div> </td>
<td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
<div class="w-32 px-2 text-center">
<div class="text-xs">Is build variable?</div>
<div class="mt-2">
<ul class="divide-y divide-stone-800">
<li>
<div <div
type="button" type="button"
on:click={setSecretValue} on:click={setSecretValue}
@ -73,6 +72,7 @@
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out" class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
class:bg-green-600={isBuildSecret} class:bg-green-600={isBuildSecret}
class:bg-stone-700={!isBuildSecret} class:bg-stone-700={!isBuildSecret}
class:opacity-50={!isNewSecret}
class:cursor-not-allowed={!isNewSecret} class:cursor-not-allowed={!isNewSecret}
class:cursor-pointer={isNewSecret} class:cursor-pointer={isNewSecret}
> >
@ -104,11 +104,7 @@
class:opacity-100={isBuildSecret} class:opacity-100={isBuildSecret}
class:opacity-0={!isBuildSecret} class:opacity-0={!isBuildSecret}
> >
<svg <svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
class="h-3 w-3 bg-white text-green-600"
fill="currentColor"
viewBox="0 0 12 12"
>
<path <path
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
/> />
@ -116,18 +112,15 @@
</span> </span>
</span> </span>
</div> </div>
</li> </td>
</ul> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
</div>
</div>
{#if isNewSecret} {#if isNewSecret}
<div class="mt-6"> <div class="flex items-center justify-center">
<button class="w-20 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button> <button class="w-24 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
</div> </div>
{:else} {:else}
<div class="mt-6"> <div class="flex justify-center items-end">
<button class="w-20 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button> <button class="w-24 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div> </div>
{/if} {/if}
</div> </td>
</div>

View File

@ -33,7 +33,7 @@ export const post: RequestHandler = async (event) => {
const found = await db.isSecretExists({ id, name }); const found = await db.isSecretExists({ id, name });
if (found) { if (found) {
throw { throw {
error: `Secret ${name} already exists` error: `Secret ${name} already exists.`
}; };
} else { } else {
await db.createSecret({ id, name, value, isBuildSecret }); await db.createSecret({ id, name, value, isBuildSecret });

View File

@ -24,6 +24,15 @@
export let application; export let application;
import Secret from './_Secret.svelte'; import Secret from './_Secret.svelte';
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { page } from '$app/stores';
import { get } from '$lib/api';
const { id } = $page.params;
async function refreshSecrets() {
const data = await get(`/applications/${id}/secrets.json`);
secrets = [...data.secrets];
}
</script> </script>
<div class="flex space-x-1 p-6 font-bold"> <div class="flex space-x-1 p-6 font-bold">
@ -31,11 +40,47 @@
Secrets for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a> Secrets for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
</div> </div>
</div> </div>
<div class="mx-auto max-w-4xl px-6"> <div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<div class="flex-col justify-start space-y-1"> <table class="mx-auto">
<thead class=" rounded-xl border-b border-coolgray-500">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
>Name</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
>Value</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
>Need during buildtime?</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
/>
</tr>
</thead>
<tbody class="">
{#each secrets as secret} {#each secrets as secret}
<Secret name={secret.name} value={secret.value} isBuildSecret={secret.isBuildSecret} /> {#key secret.id}
<tr class="hover:bg-coolgray-200">
<Secret
name={secret.name}
value={secret.value ? secret.value : 'ENCRYPTED'}
isBuildSecret={secret.isBuildSecret}
on:refresh={refreshSecrets}
/>
</tr>
{/key}
{/each} {/each}
<Secret isNewSecret /> <tr>
</div> <Secret isNewSecret on:refresh={refreshSecrets} />
</tr>
</tbody>
</table>
</div> </div>

View File

@ -35,7 +35,7 @@ main,
} }
input { input {
@apply w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm; @apply h-12 w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm;
} }
textarea { textarea {
@apply w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm; @apply w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm;
@ -50,7 +50,7 @@ label {
} }
button { button {
@apply rounded bg-coolgray-200 p-1 px-4 py-2 text-xs font-bold outline-none transition-all duration-100 hover:bg-coolgray-500 disabled:cursor-not-allowed disabled:bg-coolblack disabled:text-stone-600 md:text-sm; @apply rounded bg-coolgray-200 p-1 px-2 py-1 text-xs font-bold outline-none transition-all duration-100 hover:bg-coolgray-500 disabled:cursor-not-allowed disabled:bg-coolblack disabled:text-stone-600;
} }
a { a {