Compare commits

...

7 Commits

Author SHA1 Message Date
Andras Bacsai
004724da55 Merge pull request #119 from coollabsio/next
Unique secrets by applications
2022-02-12 15:31:53 +01:00
Andras Bacsai
97e9b5ffe3 Merge branch 'main' into next 2022-02-12 15:31:45 +01:00
Andras Bacsai
45f920f802 fix: unique secret by application
css: redesign secret page
2022-02-12 15:23:58 +01:00
Andras Bacsai
2b31532d19 Update README.md 2022-02-11 23:04:50 +01:00
Andras Bacsai
e7a6ecf95b fix: Typo 2022-02-11 21:50:47 +01:00
Andras Bacsai
545c98cee0 Merge pull request #117 from coollabsio/next
Update README.md
2022-02-11 21:50:19 +01:00
Andras Bacsai
d29ccbfe37 Update README.md 2022-02-11 21:49:30 +01:00
9 changed files with 228 additions and 115 deletions

View File

@@ -2,6 +2,66 @@
An open-source & self-hostable Heroku / Netlify alternative. An open-source & self-hostable Heroku / Netlify alternative.
## Installation
Installation is automated with the following command:
```bash
/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"
```
## Migration from v1
A fresh installation is necessary. v2 is not compatible with v1.
## Features
### Git Sources
You can use the official ones or your self hosted version!
- Github
- GitLab
- Bitbucket (WIP)
### Destinations
- Local Docker Engine
- Remote Docker Engine (WIP)
- Kubernetes (WIP)
### Applications
- Static sites
- NodeJS
- VueJS
- NuxtJS
- NextJS
- React/Preact
- NextJS
- Gatsby
- Svelte
- PHP
- Rust
- Dockerfile (you can provide it)
### Databases
- MongoDB
- MySQL
- PostgreSQL
- CouchDB
- Redis
### One-click services
- [WordPress](https://wordpress.org)
- [Plausible Analytics](https://plausible.io)
- [NocoDB](https://nocodb.com)
- [VSCode Server](https://github.com/cdr/code-server)
- [MinIO](https://min.io)
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
## Support ## Support
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai) - Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
@@ -13,6 +73,8 @@ An open-source & self-hostable Heroku / Netlify alternative.
[See the Roadmap here](https://github.com/coollabsio/coolify/projects/1) [See the Roadmap here](https://github.com/coollabsio/coolify/projects/1)
(Will be updated soon!)
## License ## License
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Please see the [LICENSE](/LICENSE) file in our repository for the full text. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Please see the [LICENSE](/LICENSE) file in our repository for the full text.

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 {