Merge pull request #154 from coollabsio/next

v2.0.16
This commit is contained in:
Andras Bacsai 2022-02-20 00:48:08 +01:00 committed by GitHub
commit 310b099ecf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 372 additions and 154 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.15", "version": "2.0.16",
"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,19 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Secret" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"value" TEXT NOT NULL,
"isPRMRSecret" BOOLEAN NOT NULL DEFAULT false,
"isBuildSecret" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"applicationId" TEXT NOT NULL,
CONSTRAINT "Secret_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Secret" ("applicationId", "createdAt", "id", "isBuildSecret", "name", "updatedAt", "value") SELECT "applicationId", "createdAt", "id", "isBuildSecret", "name", "updatedAt", "value" FROM "Secret";
DROP TABLE "Secret";
ALTER TABLE "new_Secret" RENAME TO "Secret";
CREATE UNIQUE INDEX "Secret_name_applicationId_isPRMRSecret_key" ON "Secret"("name", "applicationId", "isPRMRSecret");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@ -109,13 +109,14 @@ model Secret {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
value String value String
isPRMRSecret Boolean @default(false)
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]) @@unique([name, applicationId, isPRMRSecret])
} }
model BuildLog { model BuildLog {

View File

@ -9,7 +9,8 @@ export default async function ({
docker, docker,
buildId, buildId,
baseDirectory, baseDirectory,
secrets secrets,
pullmergeRequestId
}) { }) {
try { try {
let file = `${workdir}/Dockerfile`; let file = `${workdir}/Dockerfile`;
@ -24,8 +25,16 @@ export default async function ({
if (secrets.length > 0) { if (secrets.length > 0) {
secrets.forEach((secret) => { secrets.forEach((secret) => {
if (secret.isBuildSecret) { if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`); Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
} }
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
}
}
}
}); });
} }
await fs.writeFile(`${file}`, Dockerfile.join('\n')); await fs.writeFile(`${file}`, Dockerfile.join('\n'));

View File

@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => { const createDockerfile = async (data, image): Promise<void> => {
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } = const {
data; workdir,
port,
installCommand,
buildCommand,
startCommand,
baseDirectory,
secrets,
pullmergeRequestId
} = data;
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
@ -11,8 +19,16 @@ const createDockerfile = async (data, image): Promise<void> => {
if (secrets.length > 0) { if (secrets.length > 0) {
secrets.forEach((secret) => { secrets.forEach((secret) => {
if (secret.isBuildSecret) { if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`); Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
} }
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
}
}
}
}); });
} }
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);

View File

@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => { const createDockerfile = async (data, image): Promise<void> => {
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } = const {
data; workdir,
port,
installCommand,
buildCommand,
startCommand,
baseDirectory,
secrets,
pullmergeRequestId
} = data;
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
@ -11,8 +19,16 @@ const createDockerfile = async (data, image): Promise<void> => {
if (secrets.length > 0) { if (secrets.length > 0) {
secrets.forEach((secret) => { secrets.forEach((secret) => {
if (secret.isBuildSecret) { if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`); Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
} }
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
}
}
}
}); });
} }
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);

View File

@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => { const createDockerfile = async (data, image): Promise<void> => {
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } = const {
data; workdir,
port,
installCommand,
buildCommand,
startCommand,
baseDirectory,
secrets,
pullmergeRequestId
} = data;
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
@ -11,8 +19,16 @@ const createDockerfile = async (data, image): Promise<void> => {
if (secrets.length > 0) { if (secrets.length > 0) {
secrets.forEach((secret) => { secrets.forEach((secret) => {
if (secret.isBuildSecret) { if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`); Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
} }
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
}
}
}
}); });
} }
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);

View File

@ -2,8 +2,16 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => { const createDockerfile = async (data, image): Promise<void> => {
const { applicationId, tag, workdir, buildCommand, baseDirectory, publishDirectory, secrets } = const {
data; applicationId,
tag,
workdir,
buildCommand,
baseDirectory,
publishDirectory,
secrets,
pullmergeRequestId
} = data;
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
@ -11,8 +19,16 @@ const createDockerfile = async (data, image): Promise<void> => {
if (secrets.length > 0) { if (secrets.length > 0) {
secrets.forEach((secret) => { secrets.forEach((secret) => {
if (secret.isBuildSecret) { if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`); Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
} }
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
}
}
}
}); });
} }
if (buildCommand) { if (buildCommand) {

View File

@ -1,9 +1,9 @@
<script> <script>
import { browser } from '$app/env'; import { browser } from '$app/env';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
export let value;
let showPassword = false; let showPassword = false;
export let value;
export let disabled = false; export let disabled = false;
export let isPasswordField = false; export let isPasswordField = false;
export let readonly = false; export let readonly = false;
@ -14,7 +14,7 @@
export let name; export let name;
export let placeholder = ''; export let placeholder = '';
let disabledClass = 'bg-coolback disabled:bg-coolblack select-all'; let disabledClass = 'bg-coolback disabled:bg-coolblack';
let actionsShow = false; let actionsShow = false;
let isHttps = browser && window.location.protocol === 'https:'; let isHttps = browser && window.location.protocol === 'https:';
@ -29,11 +29,7 @@
} }
</script> </script>
<div <div class="relative">
class="relative"
on:mouseenter={() => showActions(true)}
on:mouseleave={() => showActions(false)}
>
{#if !isPasswordField || showPassword} {#if !isPasswordField || showPassword}
{#if textarea} {#if textarea}
<textarea <textarea
@ -77,7 +73,6 @@
/> />
{/if} {/if}
{#if actionsShow}
<div class="absolute top-0 right-0 m-3 cursor-pointer text-warmGray-600 hover:text-white"> <div class="absolute top-0 right-0 m-3 cursor-pointer text-warmGray-600 hover:text-white">
<div class="flex space-x-2"> <div class="flex space-x-2">
{#if isPasswordField} {#if isPasswordField}
@ -141,5 +136,4 @@
{/if} {/if}
</div> </div>
</div> </div>
{/if}
</div> </div>

View File

@ -18,7 +18,6 @@ export const buildPacks = [
{ {
name: 'static', name: 'static',
...defaultBuildAndDeploy,
publishDirectory: 'dist', publishDirectory: 'dist',
port: 80, port: 80,
fancyName: 'Static', fancyName: 'Static',

View File

@ -15,8 +15,8 @@ export async function isDockerNetworkExists({ network }) {
return await prisma.destinationDocker.findFirst({ where: { network } }); return await prisma.destinationDocker.findFirst({ where: { network } });
} }
export async function isSecretExists({ id, name }) { export async function isSecretExists({ id, name, isPRMRSecret }) {
return await prisma.secret.findFirst({ where: { name, applicationId: id } }); return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
} }
export async function isDomainConfigured({ id, fqdn }) { export async function isDomainConfigured({ id, fqdn }) {

View File

@ -1,19 +1,41 @@
import { encrypt } from '$lib/crypto'; import { encrypt, decrypt } from '$lib/crypto';
import { prisma } from './common'; import { prisma } from './common';
export async function listSecrets({ applicationId }) { export async function listSecrets(applicationId: string) {
return await prisma.secret.findMany({ let secrets = await prisma.secret.findMany({
where: { applicationId }, where: { applicationId },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' }
select: { id: true, createdAt: true, name: true, isBuildSecret: true } });
secrets = secrets.map((secret) => {
secret.value = decrypt(secret.value);
return secret;
});
return secrets;
}
export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
value = encrypt(value);
return await prisma.secret.create({
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
}); });
} }
export async function createSecret({ id, name, value, isBuildSecret }) { export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
value = encrypt(value); value = encrypt(value);
return await prisma.secret.create({ const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
data: { name, value, isBuildSecret, application: { connect: { id } } } console.log(found);
if (found) {
return await prisma.secret.updateMany({
where: { applicationId: id, name, isPRMRSecret },
data: { value, isBuildSecret, isPRMRSecret }
}); });
} else {
return await prisma.secret.create({
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
});
}
} }
export async function removeSecret({ id, name }) { export async function removeSecret({ id, name }) {

View File

@ -13,16 +13,25 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
installCommand, installCommand,
buildCommand, buildCommand,
debug, debug,
secrets secrets,
pullmergeRequestId
} = data; } = data;
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${imageForBuild}`); Dockerfile.push(`FROM ${imageForBuild}`);
Dockerfile.push('WORKDIR /usr/src/app'); Dockerfile.push('WORKDIR /usr/src/app');
if (secrets.length > 0) { if (secrets.length > 0) {
secrets.forEach((secret) => { secrets.forEach((secret) => {
if (!secret.isBuildSecret) { if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`); Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
} }
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
}
}
}
}); });
} }
// TODO: If build command defined, install command should be the default yarn install // TODO: If build command defined, install command should be the default yarn install

View File

@ -64,7 +64,6 @@ export default async function (job) {
if (destinationDockerId) { if (destinationDockerId) {
destinationType = 'docker'; destinationType = 'docker';
} }
if (destinationType === 'docker') { if (destinationType === 'docker') {
const docker = dockerInstance({ destinationDocker }); const docker = dockerInstance({ destinationDocker });
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
@ -205,7 +204,15 @@ export default async function (job) {
const envs = []; const envs = [];
if (secrets.length > 0) { if (secrets.length > 0) {
secrets.forEach((secret) => { secrets.forEach((secret) => {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
envs.push(`${secret.name}=${secret.value}`); envs.push(`${secret.name}=${secret.value}`);
}
} else {
if (!secret.isPRMRSecret) {
envs.push(`${secret.name}=${secret.value}`);
}
}
}); });
} }
await fs.writeFile(`${workdir}/.env`, envs.join('\n')); await fs.writeFile(`${workdir}/.env`, envs.join('\n'));

View File

@ -94,7 +94,7 @@
} }
} else if (type === 'github') { } else if (type === 'github') {
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, { const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
Authorization: `Bearer ${$session.ghToken || ghToken}`, Authorization: `Bearer ${$session.ghToken}`,
Accept: 'application/vnd.github.v2.json' Accept: 'application/vnd.github.v2.json'
}); });
const packageJson = files.find( const packageJson = files.find(

View File

@ -197,7 +197,7 @@
value={application.gitSource.name} value={application.gitSource.name}
id="gitSource" id="gitSource"
disabled disabled
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500" class="cursor-pointer hover:bg-coolgray-500"
/></a /></a
> >
</div> </div>
@ -214,7 +214,7 @@
value="{application.repository}/{application.branch}" value="{application.repository}/{application.branch}"
id="repository" id="repository"
disabled disabled
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500" class="cursor-pointer hover:bg-coolgray-500"
/></a /></a
> >
</div> </div>
@ -232,7 +232,7 @@
value={application.buildPack} value={application.buildPack}
id="buildPack" id="buildPack"
disabled disabled
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500" class="cursor-pointer hover:bg-coolgray-500"
/></a /></a
> >
</div> </div>

View File

@ -11,6 +11,9 @@ export const get: RequestHandler = async (event) => {
const { id } = event.params; const { id } = event.params;
try { try {
const secrets = await db.listSecrets(id);
const applicationSecrets = secrets.filter((secret) => !secret.isPRMRSecret);
const PRMRSecrets = secrets.filter((secret) => secret.isPRMRSecret);
const destinationDocker = await db.getDestinationByApplicationId({ id, teamId }); const destinationDocker = await db.getDestinationByApplicationId({ id, teamId });
const docker = dockerInstance({ destinationDocker }); const docker = dockerInstance({ destinationDocker });
const listContainers = await docker.engine.listContainers({ const listContainers = await docker.engine.listContainers({
@ -35,7 +38,13 @@ export const get: RequestHandler = async (event) => {
}); });
return { return {
body: { body: {
containers: jsonContainers containers: jsonContainers,
applicationSecrets: applicationSecrets.sort((a, b) => {
return ('' + a.name).localeCompare(b.name);
}),
PRMRSecrets: PRMRSecrets.sort((a, b) => {
return ('' + a.name).localeCompare(b.name);
})
} }
}; };
} catch (error) { } catch (error) {

View File

@ -22,8 +22,19 @@
<script lang="ts"> <script lang="ts">
export let containers; export let containers;
export let application; export let application;
export let PRMRSecrets;
export let applicationSecrets;
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import Secret from '../secrets/_Secret.svelte';
import { get } from '$lib/api';
import { page } from '$app/stores';
import Explainer from '$lib/components/Explainer.svelte';
const { id } = $page.params;
async function refreshSecrets() {
const data = await get(`/applications/${id}/secrets.json`);
PRMRSecrets = [...data.secrets];
}
</script> </script>
<div class="flex space-x-1 p-6 font-bold"> <div class="flex space-x-1 p-6 font-bold">
@ -32,7 +43,57 @@
</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">
<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-white">Name</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Value</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Need during buildtime?</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
/>
</tr>
</thead>
<tbody class="">
{#each applicationSecrets as secret}
{#key secret.id}
<tr class="h-20 transition duration-100 hover:bg-coolgray-400">
<Secret
PRMRSecret={PRMRSecrets.find((s) => s.name === secret.name)}
isPRMRSecret
name={secret.name}
value={secret.value}
isBuildSecret={secret.isBuildSecret}
on:refresh={refreshSecrets}
/>
</tr>
{/key}
{/each}
</tbody>
</table>
</div>
<div class="flex justify-center py-4 text-center">
<Explainer
customClass="w-full"
text={applicationSecrets.length === 0
? "<span class='font-bold text-white text-xl'>Please add secrets to the application first.</span> <br><br>These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
: "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."}
/>
</div>
<div class="mx-auto max-w-4xl py-10">
<div class="flex flex-wrap justify-center space-x-2"> <div class="flex flex-wrap justify-center space-x-2">
{#if containers.length > 0} {#if containers.length > 0}
{#each containers as container} {#each containers as container}

View File

@ -3,14 +3,19 @@
export let value = ''; export let value = '';
export let isBuildSecret = false; export let isBuildSecret = false;
export let isNewSecret = false; export let isNewSecret = false;
export let isPRMRSecret = false;
export let PRMRSecret = {};
if (isPRMRSecret) value = PRMRSecret.value;
import { page } from '$app/stores'; import { page } from '$app/stores';
import { del, post } from '$lib/api'; import { del, post } from '$lib/api';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let nameEl;
let valueEl;
const { id } = $page.params; const { id } = $page.params;
async function removeSecret() { async function removeSecret() {
try { try {
@ -25,24 +30,24 @@
return errorNotification(error); return errorNotification(error);
} }
} }
async function saveSecret() { async function saveSecret(isNew = false) {
const nameValid = nameEl.checkValidity(); if (!name) return errorNotification('Name is required.');
const valueValid = valueEl.checkValidity(); if (!value) return errorNotification('Value is required.');
if (!nameValid) {
return nameEl.reportValidity();
}
if (!valueValid) {
return valueEl.reportValidity();
}
try { try {
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret }); await post(`/applications/${id}/secrets.json`, {
name,
value,
isBuildSecret,
isPRMRSecret,
isNew
});
dispatch('refresh'); dispatch('refresh');
if (isNewSecret) { if (isNewSecret) {
name = ''; name = '';
value = ''; value = '';
isBuildSecret = false; isBuildSecret = false;
} }
toast.push('Secret saved.');
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
@ -56,8 +61,7 @@
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white"> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<input <input
id="secretName" id={isNewSecret ? 'secretName' : 'secretNameNew'}
bind:this={nameEl}
bind:value={name} bind:value={name}
required required
placeholder="EXAMPLE_VARIABLE" placeholder="EXAMPLE_VARIABLE"
@ -68,16 +72,13 @@
/> />
</td> </td>
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white"> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<input <CopyPasswordField
id="secretValue" id={isNewSecret ? 'secretValue' : 'secretValueNew'}
name={isNewSecret ? 'secretValue' : 'secretValueNew'}
isPasswordField={true}
bind:value bind:value
bind:this={valueEl}
required required
placeholder="J$#@UIO%HO#$U%H" placeholder="J$#@UIO%HO#$U%H"
class="-mx-2 w-64 border-2 border-transparent"
class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
readonly={!isNewSecret}
/> />
</td> </td>
<td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white"> <td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
@ -132,11 +133,20 @@
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white"> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
{#if isNewSecret} {#if isNewSecret}
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<button class="w-24 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button> <button class="bg-green-600 hover:bg-green-500" on:click={() => saveSecret(true)}>Add</button>
</div> </div>
{:else} {:else}
<div class="flex-col space-y-2">
<div class="flex items-center justify-center">
<button class="w-24 bg-green-600 hover:bg-green-500" on:click={() => saveSecret(false)}
>Set</button
>
</div>
{#if !isPRMRSecret}
<div class="flex justify-center items-end"> <div class="flex justify-center items-end">
<button class="w-24 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>
{/if}
</td> </td>

View File

@ -7,8 +7,9 @@ export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event); const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { id } = event.params;
try { try {
const secrets = await db.listSecrets({ applicationId: event.params.id }); const secrets = await (await db.listSecrets(id)).filter((secret) => !secret.isPRMRSecret);
return { return {
status: 200, status: 200,
body: { body: {
@ -27,16 +28,22 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { id } = event.params; const { id } = event.params;
const { name, value, isBuildSecret } = await event.request.json(); const { name, value, isBuildSecret, isPRMRSecret, isNew } = await event.request.json();
try { try {
const found = await db.isSecretExists({ id, name }); if (isNew) {
const found = await db.isSecretExists({ id, name, isPRMRSecret });
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, isPRMRSecret });
return {
status: 201
};
}
} else {
await db.updateSecret({ id, name, value, isBuildSecret, isPRMRSecret });
return { return {
status: 201 status: 201
}; };

View File

@ -67,10 +67,10 @@
<tbody class=""> <tbody class="">
{#each secrets as secret} {#each secrets as secret}
{#key secret.id} {#key secret.id}
<tr class="hover:bg-coolgray-200"> <tr class="h-20 transition duration-100 hover:bg-coolgray-400">
<Secret <Secret
name={secret.name} name={secret.name}
value={secret.value ? secret.value : 'ENCRYPTED'} value={secret.value}
isBuildSecret={secret.isBuildSecret} isBuildSecret={secret.isBuildSecret}
on:refresh={refreshSecrets} on:refresh={refreshSecrets}
/> />

View File

@ -6,7 +6,7 @@
<div class="flex space-x-1 py-5 font-bold"> <div class="flex space-x-1 py-5 font-bold">
<div class="title">CouchDB</div> <div class="title">CouchDB</div>
</div> </div>
<div class="px-10"> <div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="defaultDatabase">Default Database</label> <label for="defaultDatabase">Default Database</label>
<CopyPasswordField <CopyPasswordField

View File

@ -6,7 +6,7 @@
<div class="flex space-x-1 py-5 font-bold"> <div class="flex space-x-1 py-5 font-bold">
<div class="title">MongoDB</div> <div class="title">MongoDB</div>
</div> </div>
<div class="px-10"> <div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="rootUser">Root User</label> <label for="rootUser">Root User</label>
<CopyPasswordField <CopyPasswordField

View File

@ -6,7 +6,7 @@
<div class="flex space-x-1 py-5 font-bold"> <div class="flex space-x-1 py-5 font-bold">
<div class="title">MySQL</div> <div class="title">MySQL</div>
</div> </div>
<div class=" px-10"> <div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="defaultDatabase">Default Database</label> <label for="defaultDatabase">Default Database</label>
<CopyPasswordField <CopyPasswordField

View File

@ -6,7 +6,7 @@
<div class="flex space-x-1 py-5 font-bold"> <div class="flex space-x-1 py-5 font-bold">
<div class="title">PostgreSQL</div> <div class="title">PostgreSQL</div>
</div> </div>
<div class="px-10"> <div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="defaultDatabase">Default Database</label> <label for="defaultDatabase">Default Database</label>
<CopyPasswordField <CopyPasswordField

View File

@ -6,7 +6,7 @@
<div class="flex space-x-1 py-5 font-bold"> <div class="flex space-x-1 py-5 font-bold">
<div class="title">Redis</div> <div class="title">Redis</div>
</div> </div>
<div class="px-10"> <div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="dbUserPassword">Password</label> <label for="dbUserPassword">Password</label>
<CopyPasswordField <CopyPasswordField

View File

@ -7,7 +7,7 @@
<div class="flex space-x-1 py-5 font-bold"> <div class="flex space-x-1 py-5 font-bold">
<div class="title">MinIO Server</div> <div class="title">MinIO Server</div>
</div> </div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center px-10">
<label for="rootUser">Root User</label> <label for="rootUser">Root User</label>
<input <input
name="rootUser" name="rootUser"
@ -18,7 +18,7 @@
readonly readonly
/> />
</div> </div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center px-10">
<label for="rootUserPassword">Root's Password</label> <label for="rootUserPassword">Root's Password</label>
<CopyPasswordField <CopyPasswordField
id="rootUserPassword" id="rootUserPassword"
@ -29,7 +29,7 @@
value={service.minio.rootUserPassword} value={service.minio.rootUserPassword}
/> />
</div> </div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center px-10">
<label for="publicPort">API Port</label> <label for="publicPort">API Port</label>
<input <input
name="publicPort" name="publicPort"

View File

@ -1,3 +1,4 @@
import { dev } from '$app/env';
import { getDomain, getUserDetails } from '$lib/common'; import { getDomain, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { listSettings, ErrorHandler } from '$lib/database'; import { listSettings, ErrorHandler } from '$lib/database';
@ -43,7 +44,13 @@ export const del: RequestHandler = async (event) => {
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { fqdn } = await event.request.json(); const { fqdn } = await event.request.json();
const ip = await dns.resolve(event.url.hostname); let ip;
console.log(fqdn);
try {
ip = await dns.resolve(fqdn);
} catch (error) {
// Do not care.
}
try { try {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } }); await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
@ -53,7 +60,7 @@ export const del: RequestHandler = async (event) => {
status: 200, status: 200,
body: { body: {
message: 'Domain removed', message: 'Domain removed',
redirect: `http://${ip[0]}:3000/settings` redirect: ip ? `http://${ip[0]}:3000/settings` : undefined
} }
}; };
} catch (error) { } catch (error) {

View File

@ -159,7 +159,7 @@
: browser && 'http://' + window.location.hostname + ':8404' : browser && 'http://' + window.location.hostname + ':8404'
} target="_blank">stats</a> page.`} } target="_blank">stats</a> page.`}
/> />
<div class="px-10 py-5"> <div class="space-y-2 px-10 py-5">
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="proxyUser">User</label> <label for="proxyUser">User</label>
<CopyPasswordField <CopyPasswordField

View File

@ -35,10 +35,10 @@ .main {
} }
input { input {
@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; @apply h-12 w-96 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:border disabled:border-dashed disabled:border-coolgray-300 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 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:border disabled:border-dashed disabled:border-coolgray-300 disabled:bg-transparent md:text-sm;
} }
select { select {