commit
ad3044dce1
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "2.1.1",
|
||||
"version": "2.2.0",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev",
|
||||
|
19
prisma/migrations/20220327180323_ghost/migration.sql
Normal file
19
prisma/migrations/20220327180323_ghost/migration.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Ghost" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"defaultEmail" TEXT NOT NULL,
|
||||
"defaultPassword" TEXT NOT NULL,
|
||||
"mariadbUser" TEXT NOT NULL,
|
||||
"mariadbPassword" TEXT NOT NULL,
|
||||
"mariadbRootUser" TEXT NOT NULL,
|
||||
"mariadbRootUserPassword" TEXT NOT NULL,
|
||||
"mariadbDatabase" TEXT,
|
||||
"mariadbPublicPort" INTEGER,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Ghost_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Ghost_serviceId_key" ON "Ghost"("serviceId");
|
@ -278,6 +278,7 @@ model Service {
|
||||
minio Minio?
|
||||
vscodeserver Vscodeserver?
|
||||
wordpress Wordpress?
|
||||
ghost Ghost?
|
||||
serviceSecret ServiceSecret[]
|
||||
}
|
||||
|
||||
@ -332,3 +333,19 @@ model Wordpress {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Ghost {
|
||||
id String @id @default(cuid())
|
||||
defaultEmail String
|
||||
defaultPassword String
|
||||
mariadbUser String
|
||||
mariadbPassword String
|
||||
mariadbRootUser String
|
||||
mariadbRootUserPassword String
|
||||
mariadbDatabase String?
|
||||
mariadbPublicPort Int?
|
||||
serviceId String @unique
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
2
src/app.d.ts
vendored
2
src/app.d.ts
vendored
@ -8,9 +8,11 @@ declare namespace App {
|
||||
interface Platform {}
|
||||
interface Session extends SessionData {}
|
||||
interface Stuff {
|
||||
service: any;
|
||||
application: any;
|
||||
isRunning: boolean;
|
||||
appId: string;
|
||||
readOnly: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
9
src/lib/components/svg/services/Ghost.svelte
Normal file
9
src/lib/components/svg/services/Ghost.svelte
Normal file
@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = false;
|
||||
</script>
|
||||
|
||||
<img
|
||||
alt="ghost logo"
|
||||
class={isAbsolute ? 'w-12 absolute top-0 left-0 -m-3 -mt-5' : 'w-8 mx-auto'}
|
||||
src="/ghost.png"
|
||||
/>
|
24
src/lib/components/svg/services/N8n.svelte
Normal file
24
src/lib/components/svg/services/N8n.svelte
Normal file
@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = false;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'w-12 h-12 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
|
||||
viewBox="0 0 220 105"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
fill="#FF6D5A"
|
||||
d="M183.9,0.2c-9.8,0-18,6.7-20.3,15.8h-29.2c-11.5,0-20.8,9.3-20.8,20.8c0,5.7-4.7,10.4-10.4,10.4H99
|
||||
c-2.3-9.1-10.5-15.8-20.3-15.8c-9.8,0-18,6.7-20.3,15.8H41.7c-2.3-9.1-10.5-15.8-20.3-15.8c-11.6,0-21,9.4-21,21
|
||||
c0,11.6,9.4,21,21,21c9.8,0,18-6.7,20.3-15.8h16.7c2.3,9.1,10.5,15.8,20.3,15.8c9.7,0,17.9-6.6,20.3-15.6h4.2
|
||||
c5.7,0,10.4,4.7,10.4,10.4c0,11.5,9.3,20.8,20.8,20.8h6.8c2.3,9.1,10.5,15.8,20.3,15.8c11.6,0,21-9.4,21-21c0-11.6-9.4-21-21-21
|
||||
c-9.8,0-18,6.7-20.3,15.8h-6.8c-5.7,0-10.4-4.7-10.4-10.4c0-6.3-2.8-11.9-7.2-15.7c4.4-3.8,7.2-9.4,7.2-15.7
|
||||
c0-5.7,4.7-10.4,10.4-10.4h29.2c2.3,9.1,10.5,15.8,20.3,15.8c11.6,0,21-9.4,21-21C204.9,9.6,195.5,0.2,183.9,0.2z M21.4,63
|
||||
c-5.8,0-10.6-4.8-10.6-10.6s4.8-10.6,10.6-10.6S32,46.6,32,52.4S27.3,63,21.4,63z M78.7,63c-5.8,0-10.6-4.8-10.6-10.6
|
||||
s4.8-10.6,10.6-10.6s10.6,4.8,10.6,10.6S84.6,63,78.7,63z M161.5,73.2c5.8,0,10.6,4.8,10.6,10.6s-4.8,10.6-10.6,10.6
|
||||
s-10.6-4.8-10.6-10.6C150.9,77.9,155.7,73.2,161.5,73.2z M183.9,31.8c-5.8,0-10.6-4.8-10.6-10.6s4.8-10.6,10.6-10.6
|
||||
s10.6,4.8,10.6,10.6C194.5,27,189.8,31.8,183.9,31.8z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
159
src/lib/components/svg/services/UptimeKuma.svelte
Normal file
159
src/lib/components/svg/services/UptimeKuma.svelte
Normal file
@ -0,0 +1,159 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = false;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-10 h-10 mx-auto'}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 640 640"
|
||||
width="640"
|
||||
height="640"
|
||||
><defs
|
||||
><path
|
||||
d="M407.55 916.24C471.25 916.24 522.89 967.88 522.89 1031.57C522.89 1113.88 522.89 1245.44 522.89 1327.74C522.89 1391.44 471.25 1443.08 407.55 1443.08C325.25 1443.08 193.68 1443.08 111.38 1443.08C47.69 1443.08 -3.95 1391.44 -3.95 1327.74C-3.95 1245.44 -3.95 1113.88 -3.95 1031.57C-3.95 967.88 47.69 916.24 111.38 916.24C193.68 916.24 325.25 916.24 407.55 916.24Z"
|
||||
id="a1LdTs1gvU"
|
||||
/><linearGradient
|
||||
id="gradientcoH7TNh19"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="256.07"
|
||||
y1="1132.14"
|
||||
x2="609.11"
|
||||
y2="1480.42"
|
||||
><stop style="stop-color: #c2efd2;stop-opacity: 1" offset="0%" /><stop
|
||||
style="stop-color: #8ff0e5;stop-opacity: 1"
|
||||
offset="100%"
|
||||
/></linearGradient
|
||||
><path
|
||||
d="M-467.41 394.63C-467.41 554.76 -597.42 684.76 -757.55 684.76C-917.68 684.76 -1047.69 554.76 -1047.69 394.63C-1047.69 234.5 -917.68 104.49 -757.55 104.49C-597.42 104.49 -467.41 234.5 -467.41 394.63Z"
|
||||
id="a1uaEBd4xM"
|
||||
/><path
|
||||
d="M-96.99 -586.14C-57.24 -619.85 -5.79 -604.75 19.26 -580.46C31.43 -568.66 56.57 -546.36 40.97 -491.67C32.76 -462.87 10.41 -436.4 -26.05 -412.27C-15.07 -377.85 -5.6 -344.76 2.36 -313C14.29 -265.36 13.55 -189.67 -26.05 -155.4C-67.27 -119.73 -166.91 -104.09 -234.24 -103.09C-301.57 -102.1 -406.19 -113.09 -461.6 -155.4C-517.01 -197.7 -512.24 -257.07 -498.04 -313C-488.58 -350.28 -476.43 -383.38 -461.6 -412.27C-505.54 -441.3 -530.54 -467.76 -536.6 -491.67C-545.68 -527.54 -530.93 -565.61 -501.12 -586.14C-471.31 -606.67 -435.18 -606.9 -400.45 -586.14C-377.3 -572.3 -354.79 -542.13 -332.92 -495.62C-287.85 -505.25 -254.96 -509.57 -234.24 -508.6C-214.74 -507.68 -186.57 -503.36 -149.72 -495.62C-135.81 -537.95 -118.23 -568.12 -96.99 -586.14Z"
|
||||
id="f8p7QlEjN3"
|
||||
/><linearGradient
|
||||
id="gradienta4Tg99ZOOp"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-440.25"
|
||||
y1="-388.59"
|
||||
x2="-100.49"
|
||||
y2="-147.33"
|
||||
><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
|
||||
style="stop-color: #7ae6a1;stop-opacity: 1"
|
||||
offset="100%"
|
||||
/></linearGradient
|
||||
><path
|
||||
d="M-86.03 -10.69C-61.35 -10.69 -41.34 9.32 -41.34 34.01C-41.34 119.07 -41.34 329.58 -41.34 414.65C-41.34 439.33 -61.35 459.34 -86.03 459.34C-136.01 459.34 -241.25 459.34 -291.23 459.34C-315.92 459.34 -335.93 439.33 -335.93 414.65C-335.93 329.58 -335.93 119.07 -335.93 34.01C-335.93 9.32 -315.92 -10.69 -291.23 -10.69C-241.25 -10.69 -136.01 -10.69 -86.03 -10.69Z"
|
||||
id="d32ZZRxd1S"
|
||||
/><linearGradient
|
||||
id="gradientb1JxIe4xUm"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-791.65"
|
||||
y1="-33.27"
|
||||
x2="892.1"
|
||||
y2="418.94"
|
||||
><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
|
||||
style="stop-color: #5ae98f;stop-opacity: 1"
|
||||
offset="100%"
|
||||
/></linearGradient
|
||||
><path
|
||||
d="M-257.95 458.12C-247.92 449.62 -234.93 453.43 -228.61 459.56C-225.54 462.54 -219.19 468.17 -223.13 481.97C-225.2 489.24 -230.84 495.92 -240.05 502.01C-237.27 510.7 -234.88 519.06 -232.88 527.07C-229.86 539.1 -230.05 558.21 -240.05 566.86C-250.45 575.86 -275.6 579.81 -292.6 580.06C-309.6 580.31 -336.01 577.54 -349.99 566.86C-363.98 556.18 -362.77 541.19 -359.19 527.07C-356.8 517.66 -353.73 509.31 -349.99 502.01C-361.08 494.69 -367.39 488.01 -368.92 481.97C-371.22 472.92 -367.49 463.31 -359.97 458.12C-352.44 452.94 -343.32 452.88 -334.56 458.12C-328.71 461.62 -323.03 469.23 -317.51 480.97C-306.13 478.54 -297.83 477.45 -292.6 477.7C-287.68 477.93 -280.56 479.02 -271.26 480.97C-267.75 470.29 -263.32 462.67 -257.95 458.12Z"
|
||||
id="b19LRRbPrG"
|
||||
/><path
|
||||
d="M490.4 235.64C544.09 358.38 544.09 435.34 490.4 466.5C409.85 513.24 199.96 527.49 139.54 455.64C99.26 407.74 99.26 334.4 139.54 235.64C180.5 168.18 238.71 134.45 314.17 134.45C389.64 134.45 448.38 168.18 490.4 235.64Z"
|
||||
id="bN5StdyPU"
|
||||
/><linearGradient
|
||||
id="gradientb1HT15TsY0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="259.78"
|
||||
y1="261.15"
|
||||
x2="463.85"
|
||||
y2="456.49"
|
||||
><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
|
||||
style="stop-color: #86e6a9;stop-opacity: 1"
|
||||
offset="100%"
|
||||
/></linearGradient
|
||||
><path
|
||||
d="M393.81 -775.89C428.26 -748.09 439.99 -725.54 429 -708.22C412.51 -682.24 353.16 -646.07 324.5 -657.93C305.39 -665.83 294.22 -687.32 290.97 -722.41C292.69 -748.43 304.61 -767.19 326.73 -778.69C348.85 -790.19 371.21 -789.26 393.81 -775.89Z"
|
||||
id="arh6miPP2"
|
||||
/><linearGradient
|
||||
id="gradientc2g6rBSAiq"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="330.1"
|
||||
y1="-733.26"
|
||||
x2="419.69"
|
||||
y2="-707.1"
|
||||
><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
|
||||
style="stop-color: #86e6a9;stop-opacity: 1"
|
||||
offset="100%"
|
||||
/></linearGradient
|
||||
><path
|
||||
d="M675.36 -369.24C669.97 -325.31 657.02 -303.43 636.51 -303.61C605.74 -303.87 543.67 -335.15 538.59 -365.74C535.2 -386.14 547.54 -406.99 575.61 -428.29C598.61 -440.58 620.83 -440.37 642.29 -427.67C663.74 -414.97 674.77 -395.49 675.36 -369.24Z"
|
||||
id="a2VENFzCvL"
|
||||
/><linearGradient
|
||||
id="gradientc18GuJy4sZ"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="605.5"
|
||||
y1="-400.8"
|
||||
x2="630.64"
|
||||
y2="-310.92"
|
||||
><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
|
||||
style="stop-color: #86e6a9;stop-opacity: 1"
|
||||
offset="100%"
|
||||
/></linearGradient
|
||||
></defs
|
||||
><g
|
||||
><g
|
||||
><g><use xlink:href="#a1LdTs1gvU" opacity="1" fill="url(#gradientcoH7TNh19)" /></g><g
|
||||
><use xlink:href="#a1uaEBd4xM" opacity="1" fill="#ebf0ed" fill-opacity="1" /></g
|
||||
><g
|
||||
><use xlink:href="#f8p7QlEjN3" opacity="1" fill="url(#gradienta4Tg99ZOOp)" /><g
|
||||
><use
|
||||
xlink:href="#f8p7QlEjN3"
|
||||
opacity="1"
|
||||
fill-opacity="0"
|
||||
stroke="#ffffff"
|
||||
stroke-width="98"
|
||||
stroke-opacity="0.57"
|
||||
/></g
|
||||
></g
|
||||
><g
|
||||
><use xlink:href="#d32ZZRxd1S" opacity="1" fill="url(#gradientb1JxIe4xUm)" /><g
|
||||
><use
|
||||
xlink:href="#d32ZZRxd1S"
|
||||
opacity="1"
|
||||
fill-opacity="0"
|
||||
stroke="#f2f2f2"
|
||||
stroke-width="60"
|
||||
stroke-opacity="0.51"
|
||||
/></g
|
||||
></g
|
||||
><g
|
||||
><use xlink:href="#b19LRRbPrG" opacity="1" fill="#d8ad9a" fill-opacity="1" /><g
|
||||
><use
|
||||
xlink:href="#b19LRRbPrG"
|
||||
opacity="1"
|
||||
fill-opacity="0"
|
||||
stroke="#ffffff"
|
||||
stroke-width="17"
|
||||
stroke-opacity="1"
|
||||
/></g
|
||||
></g
|
||||
><g
|
||||
><use xlink:href="#bN5StdyPU" opacity="1" fill="url(#gradientb1HT15TsY0)" /><g
|
||||
><use
|
||||
xlink:href="#bN5StdyPU"
|
||||
opacity="1"
|
||||
fill-opacity="0"
|
||||
stroke="#f2f2f2"
|
||||
stroke-width="200"
|
||||
stroke-opacity="0.51"
|
||||
/></g
|
||||
></g
|
||||
><g><use xlink:href="#arh6miPP2" opacity="1" fill="url(#gradientc2g6rBSAiq)" /></g><g
|
||||
><use xlink:href="#a2VENFzCvL" opacity="1" fill="url(#gradientc18GuJy4sZ)" /></g
|
||||
></g
|
||||
></g
|
||||
></svg
|
||||
>
|
@ -107,6 +107,7 @@ export const supportedServiceTypesAndVersions = [
|
||||
name: 'plausibleanalytics',
|
||||
fancyName: 'Plausible Analytics',
|
||||
baseImage: 'plausible/analytics',
|
||||
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
|
||||
versions: ['latest'],
|
||||
ports: {
|
||||
main: 8000
|
||||
@ -143,6 +144,7 @@ export const supportedServiceTypesAndVersions = [
|
||||
name: 'wordpress',
|
||||
fancyName: 'Wordpress',
|
||||
baseImage: 'wordpress',
|
||||
images: ['bitnami/mysql:5.7'],
|
||||
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
|
||||
ports: {
|
||||
main: 80
|
||||
@ -165,6 +167,34 @@ export const supportedServiceTypesAndVersions = [
|
||||
ports: {
|
||||
main: 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n',
|
||||
fancyName: 'n8n',
|
||||
baseImage: 'n8nio/n8n',
|
||||
versions: ['latest'],
|
||||
ports: {
|
||||
main: 5678
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'uptimekuma',
|
||||
fancyName: 'Uptime Kuma',
|
||||
baseImage: 'louislam/uptime-kuma',
|
||||
versions: ['latest'],
|
||||
ports: {
|
||||
main: 3001
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ghost',
|
||||
fancyName: 'Ghost',
|
||||
baseImage: 'bitnami/ghost',
|
||||
images: ['bitnami/mariadb'],
|
||||
versions: ['latest'],
|
||||
ports: {
|
||||
main: 2368
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@ -189,6 +219,13 @@ export function getServiceImage(type) {
|
||||
}
|
||||
return '';
|
||||
}
|
||||
export function getServiceImages(type) {
|
||||
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
|
||||
if (found) {
|
||||
return found.images;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
export function generateDatabaseConfiguration(database) {
|
||||
const {
|
||||
id,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import cuid from 'cuid';
|
||||
import { generatePassword } from '.';
|
||||
@ -20,6 +21,7 @@ export async function getService({ id, teamId }) {
|
||||
minio: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true,
|
||||
ghost: true,
|
||||
serviceSecret: true
|
||||
}
|
||||
});
|
||||
@ -43,12 +45,18 @@ export async function getService({ id, teamId }) {
|
||||
if (body.wordpress?.mysqlRootUserPassword)
|
||||
body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword);
|
||||
|
||||
if (body.ghost?.mariadbPassword) body.ghost.mariadbPassword = decrypt(body.ghost.mariadbPassword);
|
||||
if (body.ghost?.mariadbRootUserPassword)
|
||||
body.ghost.mariadbRootUserPassword = decrypt(body.ghost.mariadbRootUserPassword);
|
||||
if (body.ghost?.defaultPassword) body.ghost.defaultPassword = decrypt(body.ghost.defaultPassword);
|
||||
|
||||
if (body?.serviceSecret.length > 0) {
|
||||
body.serviceSecret = body.serviceSecret.map((s) => {
|
||||
s.value = decrypt(s.value);
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
return { ...body };
|
||||
}
|
||||
|
||||
@ -119,6 +127,44 @@ export async function configureServiceType({ id, type }) {
|
||||
type
|
||||
}
|
||||
});
|
||||
} else if (type === 'n8n') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type
|
||||
}
|
||||
});
|
||||
} else if (type === 'uptimekuma') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type
|
||||
}
|
||||
});
|
||||
} else if (type === 'ghost') {
|
||||
const defaultEmail = `${cuid()}@coolify.io`;
|
||||
const defaultPassword = encrypt(generatePassword());
|
||||
const mariadbUser = cuid();
|
||||
const mariadbPassword = encrypt(generatePassword());
|
||||
const mariadbRootUser = cuid();
|
||||
const mariadbRootUserPassword = encrypt(generatePassword());
|
||||
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
ghost: {
|
||||
create: {
|
||||
defaultEmail,
|
||||
defaultPassword,
|
||||
mariadbUser,
|
||||
mariadbPassword,
|
||||
mariadbRootUser,
|
||||
mariadbRootUserPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
export async function setServiceVersion({ id, version }) {
|
||||
@ -139,7 +185,7 @@ export async function updatePlausibleAnalyticsService({ id, fqdn, email, usernam
|
||||
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
|
||||
await prisma.service.update({ where: { id }, data: { name, fqdn } });
|
||||
}
|
||||
export async function updateNocoDbOrMinioService({ id, fqdn, name }) {
|
||||
export async function updateService({ id, fqdn, name }) {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
}
|
||||
export async function updateLanguageToolService({ id, fqdn, name }) {
|
||||
@ -160,8 +206,15 @@ export async function updateWordpress({ id, fqdn, name, mysqlDatabase, extraConf
|
||||
export async function updateMinioService({ id, publicPort }) {
|
||||
return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
|
||||
}
|
||||
export async function updateGhostService({ id, fqdn, name, mariadbDatabase }) {
|
||||
return await prisma.service.update({
|
||||
where: { id },
|
||||
data: { fqdn, name, ghost: { update: { mariadbDatabase } } }
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeService({ id }) {
|
||||
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
||||
|
@ -104,32 +104,34 @@ export async function generateSSLCerts() {
|
||||
});
|
||||
for (const application of applications) {
|
||||
try {
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
destinationDocker: { engine, network },
|
||||
settings: { previews }
|
||||
} = application;
|
||||
const isRunning = await checkContainer(engine, id);
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
if (isRunning) {
|
||||
if (isHttps) ssls.push({ domain, id, isCoolify: false });
|
||||
}
|
||||
if (previews) {
|
||||
const host = getEngine(engine);
|
||||
const { stdout } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
||||
);
|
||||
const containers = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((a) => a)
|
||||
.map((c) => c.replace(/"/g, ''));
|
||||
if (containers.length > 0) {
|
||||
for (const container of containers) {
|
||||
let previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
|
||||
if (application.fqdn && application.destinationDockerId) {
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
destinationDocker: { engine, network },
|
||||
settings: { previews }
|
||||
} = application;
|
||||
const isRunning = await checkContainer(engine, id);
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
if (isRunning) {
|
||||
if (isHttps) ssls.push({ domain, id, isCoolify: false });
|
||||
}
|
||||
if (previews) {
|
||||
const host = getEngine(engine);
|
||||
const { stdout } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
||||
);
|
||||
const containers = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((a) => a)
|
||||
.map((c) => c.replace(/"/g, ''));
|
||||
if (containers.length > 0) {
|
||||
for (const container of containers) {
|
||||
let previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,26 +145,29 @@ export async function generateSSLCerts() {
|
||||
minio: true,
|
||||
plausibleAnalytics: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true
|
||||
wordpress: true,
|
||||
ghost: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
|
||||
for (const service of services) {
|
||||
try {
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
type,
|
||||
destinationDocker: { engine }
|
||||
} = service;
|
||||
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||
if (found) {
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isRunning = await checkContainer(engine, id);
|
||||
if (isRunning) {
|
||||
if (isHttps) ssls.push({ domain, id, isCoolify: false });
|
||||
if (service.fqdn && service.destinationDockerId) {
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
type,
|
||||
destinationDocker: { engine }
|
||||
} = service;
|
||||
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||
if (found) {
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isRunning = await checkContainer(engine, id);
|
||||
if (isRunning) {
|
||||
if (isHttps) ssls.push({ domain, id, isCoolify: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
|
||||
import Select from 'svelte-select';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { get, post } from '$lib/api';
|
||||
@ -35,6 +35,9 @@
|
||||
Authorization: `token ${$gitTokens.githubToken}`
|
||||
});
|
||||
}
|
||||
|
||||
let reposSelectOptions;
|
||||
let branchSelectOptions;
|
||||
async function loadRepositories() {
|
||||
let page = 1;
|
||||
let reposCount = 0;
|
||||
@ -49,8 +52,13 @@
|
||||
}
|
||||
}
|
||||
loading.repositories = false;
|
||||
reposSelectOptions = repositories.map((repo) => ({
|
||||
value: repo.full_name,
|
||||
label: repo.name
|
||||
}));
|
||||
}
|
||||
async function loadBranches() {
|
||||
async function loadBranches(event) {
|
||||
selected.repository = event.detail.value;
|
||||
loading.branches = true;
|
||||
selected.branch = undefined;
|
||||
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
|
||||
@ -58,6 +66,10 @@
|
||||
branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
|
||||
Authorization: `token ${$gitTokens.githubToken}`
|
||||
});
|
||||
branchSelectOptions = branches.map((branch) => ({
|
||||
value: branch.name,
|
||||
label: branch.name
|
||||
}));
|
||||
return;
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
@ -65,7 +77,8 @@
|
||||
loading.branches = false;
|
||||
}
|
||||
}
|
||||
async function isBranchAlreadyUsed() {
|
||||
async function isBranchAlreadyUsed(event) {
|
||||
selected.branch = event.detail.value;
|
||||
try {
|
||||
const data = await get(
|
||||
`/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}`
|
||||
@ -153,47 +166,33 @@
|
||||
{:else}
|
||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
|
||||
<div class="flex-col space-y-3 md:space-y-0 space-x-1">
|
||||
{#if loading.repositories}
|
||||
<select name="repository" disabled class="w-96">
|
||||
<option selected value="">Loading repositories...</option>
|
||||
</select>
|
||||
{:else}
|
||||
<select
|
||||
name="repository"
|
||||
class="w-96"
|
||||
bind:value={selected.repository}
|
||||
on:change={loadBranches}
|
||||
>
|
||||
<option value="" disabled selected>Please select a repository</option>
|
||||
{#each repositories as repository}
|
||||
<option value={repository.full_name}>{repository.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
||||
{#if loading.branches}
|
||||
<select name="branch" disabled class="w-96">
|
||||
<option selected value="">Loading branches...</option>
|
||||
</select>
|
||||
{:else}
|
||||
<select
|
||||
name="branch"
|
||||
class="w-96"
|
||||
disabled={!selected.repository}
|
||||
bind:value={selected.branch}
|
||||
on:change={isBranchAlreadyUsed}
|
||||
>
|
||||
{#if !selected.repository}
|
||||
<option value="" disabled selected>Select a repository first</option>
|
||||
{:else}
|
||||
<option value="" disabled selected>Please select a branch</option>
|
||||
{/if}
|
||||
|
||||
{#each branches as branch}
|
||||
<option value={branch.name}>{branch.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
<div class="flex gap-4">
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
placeholder={loading.repositories
|
||||
? 'Loading repositories ...'
|
||||
: 'Please select a repository'}
|
||||
id="repository"
|
||||
on:select={loadBranches}
|
||||
items={reposSelectOptions}
|
||||
isDisabled={loading.repositories}
|
||||
/>
|
||||
</div>
|
||||
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
placeholder={loading.branches
|
||||
? 'Loading branches ...'
|
||||
: !selected.repository
|
||||
? 'Please select a repository first'
|
||||
: 'Please select a branch'}
|
||||
id="repository"
|
||||
on:select={isBranchAlreadyUsed}
|
||||
items={branchSelectOptions}
|
||||
isDisabled={loading.branches || !selected.repository}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-5 flex-col flex justify-center items-center space-y-4">
|
||||
<button
|
||||
|
@ -103,7 +103,7 @@
|
||||
}
|
||||
async function forceRestartProxy() {
|
||||
const sure = confirm(
|
||||
'Are you sure you want to restart the proxy? Everyting will be reconfigured in ~10 sec.'
|
||||
'Are you sure you want to restart the proxy? Everything will be reconfigured in ~10 secs.'
|
||||
);
|
||||
if (sure) {
|
||||
try {
|
||||
|
@ -106,7 +106,7 @@
|
||||
}
|
||||
async function forceRestartProxy() {
|
||||
const sure = confirm(
|
||||
'Are you sure you want to restart the proxy? Everyting will be reconfigured in ~10 sec.'
|
||||
'Are you sure you want to restart the proxy? Everything will be reconfigured in ~10 secs.'
|
||||
);
|
||||
if (sure) {
|
||||
try {
|
||||
|
90
src/routes/services/[id]/_Services/_Ghost.svelte
Normal file
90
src/routes/services/[id]/_Services/_Ghost.svelte
Normal file
@ -0,0 +1,90 @@
|
||||
<script lang="ts">
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
export let readOnly;
|
||||
export let service;
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">Ghost</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="email">Default Email Address</label>
|
||||
<input
|
||||
name="email"
|
||||
id="email"
|
||||
disabled
|
||||
readonly
|
||||
placeholder="Email address"
|
||||
value={service.ghost.defaultEmail}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="defaultPassword">Default Password</label>
|
||||
<CopyPasswordField
|
||||
id="defaultPassword"
|
||||
isPasswordField
|
||||
readonly
|
||||
disabled
|
||||
name="defaultPassword"
|
||||
value={service.ghost.defaultPassword}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">MariaDB</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mariadbUser">Username</label>
|
||||
<CopyPasswordField
|
||||
name="mariadbUser"
|
||||
id="mariadbUser"
|
||||
value={service.ghost.mariadbUser}
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mariadbPassword">Password</label>
|
||||
<CopyPasswordField
|
||||
id="mariadbPassword"
|
||||
isPasswordField
|
||||
readonly
|
||||
disabled
|
||||
name="mariadbPassword"
|
||||
value={service.ghost.mariadbPassword}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mariadbDatabase">Database</label>
|
||||
<input
|
||||
name="mariadbDatabase"
|
||||
id="mariadbDatabase"
|
||||
required
|
||||
readonly={readOnly}
|
||||
disabled={readOnly}
|
||||
bind:value={service.ghost.mariadbDatabase}
|
||||
placeholder="eg: ghost_db"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mariadbRootUser">Root DB User</label>
|
||||
<CopyPasswordField
|
||||
id="mariadbRootUser"
|
||||
isPasswordField
|
||||
readonly
|
||||
disabled
|
||||
name="mariadbRootUser"
|
||||
value={service.ghost.mariadbRootUser}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mariadbRootUserPassword">Root DB Password</label>
|
||||
<CopyPasswordField
|
||||
id="mariadbRootUserPassword"
|
||||
isPasswordField
|
||||
readonly
|
||||
disabled
|
||||
name="mariadbRootUserPassword"
|
||||
value={service.ghost.mariadbRootUserPassword}
|
||||
/>
|
||||
</div>
|
@ -10,6 +10,7 @@
|
||||
import Setting from '$lib/components/Setting.svelte';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import Ghost from './_Ghost.svelte';
|
||||
import MinIo from './_MinIO.svelte';
|
||||
import PlausibleAnalytics from './_PlausibleAnalytics.svelte';
|
||||
import VsCodeServer from './_VSCodeServer.svelte';
|
||||
@ -142,6 +143,8 @@
|
||||
<VsCodeServer {service} />
|
||||
{:else if service.type === 'wordpress'}
|
||||
<Wordpress bind:service {isRunning} {readOnly} />
|
||||
{:else if service.type === 'ghost'}
|
||||
<Ghost bind:service {readOnly} />
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
|
@ -35,6 +35,7 @@
|
||||
}
|
||||
if (service.plausibleAnalytics?.email && service.plausibleAnalytics.username) readOnly = true;
|
||||
if (service.wordpress?.mysqlDatabase) readOnly = true;
|
||||
if (service.ghost?.mariadbDatabase && service.ghost.mariadbDatabase) readOnly = true;
|
||||
|
||||
return {
|
||||
props: {
|
||||
|
@ -38,6 +38,9 @@
|
||||
import { post } from '$lib/api';
|
||||
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
|
||||
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
|
||||
import N8n from '$lib/components/svg/services/N8n.svelte';
|
||||
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
|
||||
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from');
|
||||
@ -77,6 +80,12 @@
|
||||
<VaultWarden isAbsolute />
|
||||
{:else if type.name === 'languagetool'}
|
||||
<LanguageTool isAbsolute />
|
||||
{:else if type.name === 'n8n'}
|
||||
<N8n isAbsolute />
|
||||
{:else if type.name === 'uptimekuma'}
|
||||
<UptimeKuma isAbsolute />
|
||||
{:else if type.name === 'ghost'}
|
||||
<Ghost isAbsolute />
|
||||
{/if}{type.fancyName}
|
||||
</button>
|
||||
</form>
|
||||
|
23
src/routes/services/[id]/ghost/index.json.ts
Normal file
23
src/routes/services/[id]/ghost/index.json.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
const { id } = event.params;
|
||||
|
||||
let {
|
||||
name,
|
||||
fqdn,
|
||||
ghost: { mariadbDatabase }
|
||||
} = await event.request.json();
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
try {
|
||||
await db.updateGhostService({ id, fqdn, name, mariadbDatabase });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
133
src/routes/services/[id]/ghost/start.json.ts
Normal file
133
src/routes/services/[id]/ghost/start.json.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import {
|
||||
asyncExecShell,
|
||||
createDirectories,
|
||||
getDomain,
|
||||
getEngine,
|
||||
getUserDetails
|
||||
} from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { promises as fs } from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const {
|
||||
type,
|
||||
version,
|
||||
destinationDockerId,
|
||||
destinationDocker,
|
||||
serviceSecret,
|
||||
fqdn,
|
||||
ghost: {
|
||||
defaultEmail,
|
||||
defaultPassword,
|
||||
mariadbRootUser,
|
||||
mariadbRootUserPassword,
|
||||
mariadbDatabase,
|
||||
mariadbPassword,
|
||||
mariadbUser
|
||||
}
|
||||
} = service;
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
|
||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||
const image = getServiceImage(type);
|
||||
const domain = getDomain(fqdn);
|
||||
const config = {
|
||||
ghost: {
|
||||
image: `${image}:${version}`,
|
||||
volume: `${id}-ghost:/bitnami/ghost`,
|
||||
environmentVariables: {
|
||||
GHOST_HOST: domain,
|
||||
GHOST_EMAIL: defaultEmail,
|
||||
GHOST_PASSWORD: defaultPassword,
|
||||
GHOST_DATABASE_HOST: `${id}-mariadb`,
|
||||
GHOST_DATABASE_USER: mariadbUser,
|
||||
GHOST_DATABASE_PASSWORD: mariadbPassword,
|
||||
GHOST_DATABASE_NAME: mariadbDatabase,
|
||||
GHOST_DATABASE_PORT_NUMBER: 3306
|
||||
}
|
||||
},
|
||||
mariadb: {
|
||||
image: `bitnami/mariadb:latest`,
|
||||
volume: `${id}-mariadb:/bitnami/mariadb`,
|
||||
environmentVariables: {
|
||||
MARIADB_USER: mariadbUser,
|
||||
MARIADB_PASSWORD: mariadbPassword,
|
||||
MARIADB_DATABASE: mariadbDatabase,
|
||||
MARIADB_ROOT_USER: mariadbRootUser,
|
||||
MARIADB_ROOT_PASSWORD: mariadbRootUserPassword
|
||||
}
|
||||
}
|
||||
};
|
||||
if (serviceSecret.length > 0) {
|
||||
serviceSecret.forEach((secret) => {
|
||||
config.ghost.environmentVariables[secret.name] = secret.value;
|
||||
});
|
||||
}
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[id]: {
|
||||
container_name: id,
|
||||
image: config.ghost.image,
|
||||
networks: [network],
|
||||
volumes: [config.ghost.volume],
|
||||
environment: config.ghost.environmentVariables,
|
||||
restart: 'always',
|
||||
labels: makeLabelForServices('ghost'),
|
||||
depends_on: [`${id}-mariadb`]
|
||||
},
|
||||
[`${id}-mariadb`]: {
|
||||
container_name: `${id}-mariadb`,
|
||||
image: config.mariadb.image,
|
||||
networks: [network],
|
||||
volumes: [config.mariadb.volume],
|
||||
environment: config.mariadb.environmentVariables,
|
||||
restart: 'always'
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[config.ghost.volume.split(':')[0]]: {
|
||||
name: config.ghost.volume.split(':')[0]
|
||||
},
|
||||
[config.mariadb.volume.split(':')[0]]: {
|
||||
name: config.mariadb.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
39
src/routes/services/[id]/ghost/stop.json.ts
Normal file
39
src/routes/services/[id]/ghost/stop.json.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { getUserDetails, removeDestinationDocker } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
||||
if (destinationDockerId) {
|
||||
const engine = destinationDocker.engine;
|
||||
|
||||
try {
|
||||
let found = await checkContainer(engine, id);
|
||||
if (found) {
|
||||
await removeDestinationDocker({ id, engine });
|
||||
}
|
||||
found = await checkContainer(engine, `${id}-mariadb`);
|
||||
if (found) {
|
||||
await removeDestinationDocker({ id: `${id}-mariadb`, engine });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -4,7 +4,8 @@ import {
|
||||
generateDatabaseConfiguration,
|
||||
getServiceImage,
|
||||
getVersions,
|
||||
ErrorHandler
|
||||
ErrorHandler,
|
||||
getServiceImages
|
||||
} from '$lib/database';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
@ -23,7 +24,13 @@ export const get: RequestHandler = async (event) => {
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
const docker = dockerInstance({ destinationDocker });
|
||||
const baseImage = getServiceImage(type);
|
||||
const images = getServiceImages(type);
|
||||
docker.engine.pull(`${baseImage}:${version}`);
|
||||
if (images?.length > 0) {
|
||||
for (const image of images) {
|
||||
docker.engine.pull(`${image}:latest`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
const { stdout } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
|
||||
|
@ -39,6 +39,9 @@
|
||||
import cuid from 'cuid';
|
||||
import { browser } from '$app/env';
|
||||
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
|
||||
import N8n from '$lib/components/svg/services/N8n.svelte';
|
||||
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
|
||||
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
||||
|
||||
export let service;
|
||||
export let isRunning;
|
||||
@ -109,6 +112,18 @@
|
||||
<a href="https://languagetool.org/dev" target="_blank">
|
||||
<LanguageTool />
|
||||
</a>
|
||||
{:else if service.type === 'n8n'}
|
||||
<a href="https://n8n.io" target="_blank">
|
||||
<N8n />
|
||||
</a>
|
||||
{:else if service.type === 'uptimekuma'}
|
||||
<a href="https://github.com/louislam/uptime-kuma" target="_blank">
|
||||
<UptimeKuma />
|
||||
</a>
|
||||
{:else if service.type === 'ghost'}
|
||||
<a href="https://ghost.org" target="_blank">
|
||||
<Ghost />
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -41,7 +41,7 @@ export const post: RequestHandler = async (event) => {
|
||||
networks: [network],
|
||||
environment: config.environmentVariables,
|
||||
restart: 'always',
|
||||
volumes: [`${id}-ngrams:/ngrams`],
|
||||
volumes: [config.volume],
|
||||
labels: makeLabelForServices('languagetool')
|
||||
}
|
||||
},
|
||||
@ -51,20 +51,20 @@ export const post: RequestHandler = async (event) => {
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[`${id}-ngrams`]: {
|
||||
external: true
|
||||
[config.volume.split(':')[0]]: {
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}-ngrams`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
@ -13,7 +13,7 @@ export const post: RequestHandler = async (event) => {
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
|
||||
try {
|
||||
await db.updateNocoDbOrMinioService({ id, fqdn, name });
|
||||
await db.updateService({ id, fqdn, name });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
|
@ -76,19 +76,13 @@ export const post: RequestHandler = async (event) => {
|
||||
},
|
||||
volumes: {
|
||||
[config.volume.split(':')[0]]: {
|
||||
external: true
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
await db.updateMinioService({ id, publicPort });
|
||||
|
20
src/routes/services/[id]/n8n/index.json.ts
Normal file
20
src/routes/services/[id]/n8n/index.json.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
const { id } = event.params;
|
||||
|
||||
let { name, fqdn } = await event.request.json();
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
|
||||
try {
|
||||
await db.updateService({ id, fqdn, name });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
77
src/routes/services/[id]/n8n/start.json.ts
Normal file
77
src/routes/services/[id]/n8n/start.json.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { promises as fs } from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
|
||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||
const image = getServiceImage(type);
|
||||
|
||||
const config = {
|
||||
image: `${image}:${version}`,
|
||||
volume: `${id}-n8n:/root/.n8n`,
|
||||
environmentVariables: {}
|
||||
};
|
||||
if (serviceSecret.length > 0) {
|
||||
serviceSecret.forEach((secret) => {
|
||||
config.environmentVariables[secret.name] = secret.value;
|
||||
});
|
||||
}
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[id]: {
|
||||
container_name: id,
|
||||
image: config.image,
|
||||
networks: [network],
|
||||
volumes: [config.volume],
|
||||
environment: config.environmentVariables,
|
||||
restart: 'always',
|
||||
labels: makeLabelForServices('n8n')
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[config.volume.split(':')[0]]: {
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
35
src/routes/services/[id]/n8n/stop.json.ts
Normal file
35
src/routes/services/[id]/n8n/stop.json.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { getUserDetails, removeDestinationDocker } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
||||
if (destinationDockerId) {
|
||||
const engine = destinationDocker.engine;
|
||||
|
||||
try {
|
||||
const found = await checkContainer(engine, id);
|
||||
if (found) {
|
||||
await removeDestinationDocker({ id, engine });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -12,7 +12,7 @@ export const post: RequestHandler = async (event) => {
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
|
||||
try {
|
||||
await db.updateNocoDbOrMinioService({ id, fqdn, name });
|
||||
await db.updateService({ id, fqdn, name });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
|
@ -52,6 +52,11 @@ export const post: RequestHandler = async (event) => {
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
@ -158,29 +158,21 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
||||
},
|
||||
volumes: {
|
||||
[config.postgresql.volume.split(':')[0]]: {
|
||||
external: true
|
||||
name: config.postgresql.volume.split(':')[0]
|
||||
},
|
||||
[config.clickhouse.volume.split(':')[0]]: {
|
||||
external: true
|
||||
name: config.clickhouse.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker volume create ${config.postgresql.volume.split(':')[0]}`
|
||||
);
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker volume create ${config.clickhouse.volume.split(':')[0]}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
}
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
|
||||
);
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
|
20
src/routes/services/[id]/uptimekuma/index.json.ts
Normal file
20
src/routes/services/[id]/uptimekuma/index.json.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
const { id } = event.params;
|
||||
|
||||
let { name, fqdn } = await event.request.json();
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
|
||||
try {
|
||||
await db.updateService({ id, fqdn, name });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
77
src/routes/services/[id]/uptimekuma/start.json.ts
Normal file
77
src/routes/services/[id]/uptimekuma/start.json.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { promises as fs } from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
|
||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||
const image = getServiceImage(type);
|
||||
|
||||
const config = {
|
||||
image: `${image}:${version}`,
|
||||
volume: `${id}-uptimekuma:/app/data`,
|
||||
environmentVariables: {}
|
||||
};
|
||||
if (serviceSecret.length > 0) {
|
||||
serviceSecret.forEach((secret) => {
|
||||
config.environmentVariables[secret.name] = secret.value;
|
||||
});
|
||||
}
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[id]: {
|
||||
container_name: id,
|
||||
image: config.image,
|
||||
networks: [network],
|
||||
volumes: [config.volume],
|
||||
environment: config.environmentVariables,
|
||||
restart: 'always',
|
||||
labels: makeLabelForServices('uptimekuma')
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[config.volume.split(':')[0]]: {
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
35
src/routes/services/[id]/uptimekuma/stop.json.ts
Normal file
35
src/routes/services/[id]/uptimekuma/stop.json.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { getUserDetails, removeDestinationDocker } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
||||
if (destinationDockerId) {
|
||||
const engine = destinationDocker.engine;
|
||||
|
||||
try {
|
||||
const found = await checkContainer(engine, id);
|
||||
if (found) {
|
||||
await removeDestinationDocker({ id, engine });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -52,20 +52,18 @@ export const post: RequestHandler = async (event) => {
|
||||
},
|
||||
volumes: {
|
||||
[config.volume.split(':')[0]]: {
|
||||
external: true
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
@ -61,29 +61,20 @@ export const post: RequestHandler = async (event) => {
|
||||
},
|
||||
volumes: {
|
||||
[config.volume.split(':')[0]]: {
|
||||
external: true
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ export const post: RequestHandler = async (event) => {
|
||||
container_name: id,
|
||||
image: config.wordpress.image,
|
||||
environment: config.wordpress.environmentVariables,
|
||||
volumes: [config.wordpress.volume],
|
||||
networks: [network],
|
||||
restart: 'always',
|
||||
depends_on: [`${id}-mysql`],
|
||||
@ -80,6 +81,7 @@ export const post: RequestHandler = async (event) => {
|
||||
[`${id}-mysql`]: {
|
||||
container_name: `${id}-mysql`,
|
||||
image: config.mysql.image,
|
||||
volumes: [config.mysql.volume],
|
||||
environment: config.mysql.environmentVariables,
|
||||
networks: [network],
|
||||
restart: 'always'
|
||||
@ -91,29 +93,22 @@ export const post: RequestHandler = async (event) => {
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[config.mysql.volume.split(':')[0]]: {
|
||||
external: true
|
||||
},
|
||||
[config.wordpress.volume.split(':')[0]]: {
|
||||
external: true
|
||||
name: config.wordpress.volume.split(':')[0]
|
||||
},
|
||||
[config.mysql.volume.split(':')[0]]: {
|
||||
name: config.mysql.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker volume create ${config.mysql.volume.split(':')[0]}`
|
||||
);
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker volume create ${config.wordpress.volume.split(':')[0]}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
@ -8,6 +8,9 @@
|
||||
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
|
||||
import { post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
import N8n from '$lib/components/svg/services/N8n.svelte';
|
||||
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
|
||||
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
||||
|
||||
export let services;
|
||||
async function newService() {
|
||||
@ -58,6 +61,12 @@
|
||||
<VaultWarden isAbsolute />
|
||||
{:else if service.type === 'languagetool'}
|
||||
<LanguageTool isAbsolute />
|
||||
{:else if service.type === 'n8n'}
|
||||
<N8n isAbsolute />
|
||||
{:else if service.type === 'uptimekuma'}
|
||||
<UptimeKuma isAbsolute />
|
||||
{:else if service.type === 'ghost'}
|
||||
<Ghost isAbsolute />
|
||||
{/if}
|
||||
<div class="font-bold text-xl text-center truncate">
|
||||
{service.name}
|
||||
|
@ -37,6 +37,29 @@ textarea {
|
||||
@apply min-w-[24rem] 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;
|
||||
}
|
||||
|
||||
#svelte .custom-select-wrapper .selectContainer.disabled input {
|
||||
@apply placeholder:text-stone-600;
|
||||
}
|
||||
|
||||
#svelte .custom-select-wrapper .selectContainer input {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
#svelte .custom-select-wrapper .selectContainer {
|
||||
@apply h-12 w-96 rounded border-none bg-coolgray-200 p-2 text-xs font-bold tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm;
|
||||
}
|
||||
|
||||
#svelte .listContainer {
|
||||
@apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-coollabs scrollbar-track-coolgray-200;
|
||||
}
|
||||
|
||||
#svelte .item.hover {
|
||||
@apply bg-coolgray-100 text-white;
|
||||
}
|
||||
#svelte .item.active {
|
||||
@apply bg-coollabs text-white;
|
||||
}
|
||||
|
||||
select {
|
||||
@apply h-12 w-96 rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm;
|
||||
}
|
||||
|
BIN
static/ghost.png
Normal file
BIN
static/ghost.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
Loading…
x
Reference in New Issue
Block a user