fix: unique secret by application

css: redesign secret page
This commit is contained in:
Andras Bacsai 2022-02-12 15:23:58 +01:00
parent e7a6ecf95b
commit 45f920f802
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,101 +42,85 @@
} }
</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"> <input
<div class="grid grid-flow-row"> id="secretName"
<label for="secretName">Name</label> bind:value={name}
<input placeholder="EXAMPLE_VARIABLE"
id="secretName" class="-mx-2 w-64 border-2 border-transparent"
bind:value={name} readonly={!isNewSecret}
placeholder="EXAMPLE_VARIABLE" class:bg-transparent={!isNewSecret}
class="w-64 border-2 border-transparent" class:cursor-not-allowed={!isNewSecret}
readonly={!isNewSecret} />
class:hover:bg-coolgray-200={!isNewSecret} </td>
class:cursor-not-allowed={!isNewSecret} <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
/> <input
</div> id="secretValue"
<div class="grid grid-flow-row"> bind:value
<label for="secretValue">Value (will be encrypted)</label> placeholder="J$#@UIO%HO#$U%H"
<input class="-mx-2 w-64 border-2 border-transparent"
id="secretValue" class:bg-transparent={!isNewSecret}
bind:value class:cursor-not-allowed={!isNewSecret}
placeholder="J$#@UIO%HO#$U%H" readonly={!isNewSecret}
class="w-64 border-2 border-transparent" />
class:hover:bg-coolgray-200={!isNewSecret} </td>
class:cursor-not-allowed={!isNewSecret} <td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
readonly={!isNewSecret} <div
/> type="button"
</div> on:click={setSecretValue}
aria-pressed="false"
<div class="w-32 px-2 text-center"> class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
<div class="text-xs">Is build variable?</div> class:bg-green-600={isBuildSecret}
class:bg-stone-700={!isBuildSecret}
<div class="mt-2"> class:opacity-50={!isNewSecret}
<ul class="divide-y divide-stone-800"> class:cursor-not-allowed={!isNewSecret}
<li> class:cursor-pointer={isNewSecret}
<div >
type="button" <span class="sr-only">Use isBuildSecret</span>
on:click={setSecretValue} <span
aria-pressed="false" class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition 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:translate-x-5={isBuildSecret}
class:bg-green-600={isBuildSecret} class:translate-x-0={!isBuildSecret}
class:bg-stone-700={!isBuildSecret} >
class:cursor-not-allowed={!isNewSecret} <span
class:cursor-pointer={isNewSecret} class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
> class:opacity-0={isBuildSecret}
<span class="sr-only">Use isBuildSecret</span> class:opacity-100={!isBuildSecret}
<span aria-hidden="true"
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out" >
class:translate-x-5={isBuildSecret} <svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
class:translate-x-0={!isBuildSecret} <path
> d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
<span stroke="currentColor"
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in" stroke-width="2"
class:opacity-0={isBuildSecret} stroke-linecap="round"
class:opacity-100={!isBuildSecret} stroke-linejoin="round"
aria-hidden="true" />
> </svg>
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12"> </span>
<path <span
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out"
stroke="currentColor" aria-hidden="true"
stroke-width="2" class:opacity-100={isBuildSecret}
stroke-linecap="round" class:opacity-0={!isBuildSecret}
stroke-linejoin="round" >
/> <svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
</svg> <path
</span> 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"
<span />
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out" </svg>
aria-hidden="true" </span>
class:opacity-100={isBuildSecret} </span>
class:opacity-0={!isBuildSecret}
>
<svg
class="h-3 w-3 bg-white text-green-600"
fill="currentColor"
viewBox="0 0 12 12"
>
<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"
/>
</svg>
</span>
</span>
</div>
</li>
</ul>
</div>
</div>
{#if isNewSecret}
<div class="mt-6">
<button class="w-20 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
</div>
{:else}
<div class="mt-6">
<button class="w-20 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
{/if}
</div> </div>
</div> </td>
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
{#if isNewSecret}
<div class="flex items-center justify-center">
<button class="w-24 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
</div>
{:else}
<div class="flex justify-center items-end">
<button class="w-24 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
{/if}
</td>

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">
{#each secrets as secret} <thead class=" rounded-xl border-b border-coolgray-500">
<Secret name={secret.name} value={secret.value} isBuildSecret={secret.isBuildSecret} /> <tr>
{/each} <th
<Secret isNewSecret /> scope="col"
</div> 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}
{#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}
<tr>
<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 {