mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad3044dce1 | ||
|
|
e40541d831 | ||
|
|
2786e7dbaf | ||
|
|
196d681a63 | ||
|
|
d2353e3c35 | ||
|
|
2475031f88 | ||
|
|
cd15e68adc | ||
|
|
27431f779d | ||
|
|
b9b5a2faeb | ||
|
|
e471b11d3b | ||
|
|
a742a3d2e3 | ||
|
|
c615f6c07e | ||
|
|
a6ebfb08f7 | ||
|
|
2b0d162226 | ||
|
|
2c5f09a8bb | ||
|
|
ef073e586b | ||
|
|
82bfdb87e3 | ||
|
|
767e7b80cb | ||
|
|
8d26ea9063 | ||
|
|
1a7c4310d0 | ||
|
|
4e8fe79e2b | ||
|
|
a8c5551292 | ||
|
|
2bf73109b2 | ||
|
|
f0ab3750bd | ||
|
|
58a11e37fe | ||
|
|
927bf46304 | ||
|
|
6b89857697 | ||
|
|
b72e5ccef6 | ||
|
|
6617b7811b | ||
|
|
e1c1988db4 | ||
|
|
af99ea4678 | ||
|
|
a6d5316090 | ||
|
|
f5e7a84fa6 | ||
|
|
c013764b61 | ||
|
|
2320ab0dfc | ||
|
|
1281a0f7e4 | ||
|
|
d8350cd4ee | ||
|
|
e3b7c23ed9 | ||
|
|
eae1ea21d6 | ||
|
|
541aa76b64 | ||
|
|
7b8555d524 | ||
|
|
fdf998c181 | ||
|
|
3d6b343adc | ||
|
|
e338cecc14 | ||
|
|
e5537a33fb | ||
|
|
35384deb68 | ||
|
|
547ca60c2a | ||
|
|
376f6f7455 | ||
|
|
abe92dedff | ||
|
|
4b521ceedc | ||
|
|
6dfcb9e52b | ||
|
|
335e3216e2 | ||
|
|
5b22bb4818 | ||
|
|
0097004882 | ||
|
|
1bc9e4c2d3 | ||
|
|
36c7e1a3c3 | ||
|
|
c6b4d04e26 | ||
|
|
fa6cf068c7 | ||
|
|
7c273a3a48 | ||
|
|
3de2ea1523 | ||
|
|
c5c9f84503 | ||
|
|
16ea9a3e07 | ||
|
|
48f952c798 | ||
|
|
f78ea5de07 | ||
|
|
5adbd5e784 | ||
|
|
5b2afa79d7 | ||
|
|
dc4e6d02b7 | ||
|
|
8ae61c8f78 | ||
|
|
684b8e0914 | ||
|
|
7c3314abae | ||
|
|
ab9f8ff356 | ||
|
|
892d8cd5c1 | ||
|
|
8b8b45778d | ||
|
|
6655fb182c | ||
|
|
0926d40247 | ||
|
|
ddc4d36688 | ||
|
|
53e1f22eb1 | ||
|
|
3d2a34737b | ||
|
|
ebde77008c | ||
|
|
3d27fd04ba | ||
|
|
d9fcaf3473 | ||
|
|
d266f761aa | ||
|
|
1d01405412 | ||
|
|
7c62eb5bd6 | ||
|
|
4dcc76d366 | ||
|
|
d2fad19a11 | ||
|
|
7c92c4c964 | ||
|
|
5a71d33236 | ||
|
|
1b4db4f793 | ||
|
|
c084b22815 | ||
|
|
acacef95cd | ||
|
|
5d722183d3 |
@@ -1,4 +1,5 @@
|
||||
FROM node:16.14.0-alpine
|
||||
RUN apk add --no-cache g++ cmake make python3
|
||||
WORKDIR /app
|
||||
COPY package*.json .
|
||||
RUN yarn install
|
||||
|
||||
54
package.json
54
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "2.0.24",
|
||||
"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 --host 0.0.0.0",
|
||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev",
|
||||
"dev:stop": "docker-compose -f docker-compose-dev.yaml down",
|
||||
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
|
||||
"studio": "npx prisma studio",
|
||||
@@ -25,57 +25,59 @@
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "1.0.0-next.69",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.28",
|
||||
"@sveltejs/kit": "1.0.0-next.283",
|
||||
"@sveltejs/adapter-node": "1.0.0-next.73",
|
||||
"@sveltejs/kit": "1.0.0-next.303",
|
||||
"@types/bcrypt": "5.0.0",
|
||||
"@types/js-cookie": "3.0.1",
|
||||
"@types/node": "17.0.20",
|
||||
"@types/node-forge": "1.0.0",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/node-forge": "1.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "4.31.1",
|
||||
"@typescript-eslint/parser": "4.31.1",
|
||||
"@zerodevx/svelte-toast": "0.7.0",
|
||||
"autoprefixer": "10.4.2",
|
||||
"@zerodevx/svelte-toast": "0.7.1",
|
||||
"autoprefixer": "10.4.4",
|
||||
"cross-var": "1.1.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "8.4.0",
|
||||
"eslint-plugin-svelte3": "3.4.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-svelte3": "3.4.1",
|
||||
"husky": "7.0.4",
|
||||
"lint-staged": "12.3.4",
|
||||
"postcss": "8.4.6",
|
||||
"prettier": "2.5.1",
|
||||
"lint-staged": "12.3.7",
|
||||
"postcss": "8.4.12",
|
||||
"prettier": "2.6.1",
|
||||
"prettier-plugin-svelte": "2.6.0",
|
||||
"prettier-plugin-tailwindcss": "0.1.7",
|
||||
"prisma": "3.10.0",
|
||||
"prettier-plugin-tailwindcss": "0.1.8",
|
||||
"prisma": "3.11.1",
|
||||
"svelte": "3.46.4",
|
||||
"svelte-check": "2.4.5",
|
||||
"svelte-check": "2.4.6",
|
||||
"svelte-preprocess": "4.10.4",
|
||||
"svelte-select": "^4.4.7",
|
||||
"tailwindcss": "3.0.23",
|
||||
"ts-node": "10.5.0",
|
||||
"ts-node": "10.7.0",
|
||||
"tslib": "2.3.1",
|
||||
"typescript": "4.5.5"
|
||||
"typescript": "4.6.3"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@prisma/client": "3.10.0",
|
||||
"@sentry/node": "6.17.9",
|
||||
"@prisma/client": "3.11.1",
|
||||
"@sentry/node": "6.19.2",
|
||||
"bcrypt": "5.0.1",
|
||||
"bullmq": "1.74.2",
|
||||
"bullmq": "1.78.1",
|
||||
"compare-versions": "4.1.3",
|
||||
"cookie": "0.4.2",
|
||||
"cooltipz-css": "^2.1.0",
|
||||
"cuid": "2.1.8",
|
||||
"dayjs": "1.10.7",
|
||||
"dayjs": "1.11.0",
|
||||
"dockerode": "3.3.1",
|
||||
"dotenv-extended": "2.9.0",
|
||||
"generate-password": "1.7.0",
|
||||
"get-port": "6.1.1",
|
||||
"got": "12.0.1",
|
||||
"get-port": "6.1.2",
|
||||
"got": "12.0.2",
|
||||
"js-cookie": "3.0.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"mustache": "^4.2.0",
|
||||
"node-forge": "1.2.1",
|
||||
"node-forge": "1.3.0",
|
||||
"svelte-kit-cookie-session": "2.1.2",
|
||||
"tailwindcss-scrollbar": "^0.1.0",
|
||||
"unique-names-generator": "4.7.1"
|
||||
|
||||
563
pnpm-lock.yaml
generated
563
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ServiceSecret" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
CONSTRAINT "ServiceSecret_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ServiceSecret_name_serviceId_key" ON "ServiceSecret"("name", "serviceId");
|
||||
19
prisma/migrations/20220311213422_autodeploy/migration.sql
Normal file
19
prisma/migrations/20220311213422_autodeploy/migration.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_ApplicationSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationSettings" ("applicationId", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt") SELECT "applicationId", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||
DROP TABLE "ApplicationSettings";
|
||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "phpModules" TEXT;
|
||||
@@ -0,0 +1,18 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ApplicationPersistentStorage" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationPersistentStorage_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ApplicationPersistentStorage_applicationId_key" ON "ApplicationPersistentStorage"("applicationId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ApplicationPersistentStorage_path_key" ON "ApplicationPersistentStorage"("path");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ApplicationPersistentStorage_applicationId_path_key" ON "ApplicationPersistentStorage"("applicationId", "path");
|
||||
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");
|
||||
@@ -86,6 +86,7 @@ model Application {
|
||||
startCommand String?
|
||||
baseDirectory String?
|
||||
publishDirectory String?
|
||||
phpModules String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
settings ApplicationSettings?
|
||||
@@ -95,6 +96,7 @@ model Application {
|
||||
gitSourceId String?
|
||||
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
||||
secrets Secret[]
|
||||
persistentStorage ApplicationPersistentStorage[]
|
||||
}
|
||||
|
||||
model ApplicationSettings {
|
||||
@@ -104,10 +106,22 @@ model ApplicationSettings {
|
||||
dualCerts Boolean @default(false)
|
||||
debug Boolean @default(false)
|
||||
previews Boolean @default(false)
|
||||
autodeploy Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model ApplicationPersistentStorage {
|
||||
id String @id @default(cuid())
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
applicationId String @unique
|
||||
path String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([applicationId, path])
|
||||
}
|
||||
|
||||
model Secret {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
@@ -122,6 +136,18 @@ model Secret {
|
||||
@@unique([name, applicationId, isPRMRSecret])
|
||||
}
|
||||
|
||||
model ServiceSecret {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
value String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
serviceId String
|
||||
|
||||
@@unique([name, serviceId])
|
||||
}
|
||||
|
||||
model BuildLog {
|
||||
id String @id @default(cuid())
|
||||
applicationId String?
|
||||
@@ -252,6 +278,8 @@ model Service {
|
||||
minio Minio?
|
||||
vscodeserver Vscodeserver?
|
||||
wordpress Wordpress?
|
||||
ghost Ghost?
|
||||
serviceSecret ServiceSecret[]
|
||||
}
|
||||
|
||||
model PlausibleAnalytics {
|
||||
@@ -305,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
|
||||
}
|
||||
|
||||
8
src/app.d.ts
vendored
8
src/app.d.ts
vendored
@@ -7,7 +7,13 @@ declare namespace App {
|
||||
}
|
||||
interface Platform {}
|
||||
interface Session extends SessionData {}
|
||||
interface Stuff {}
|
||||
interface Stuff {
|
||||
service: any;
|
||||
application: any;
|
||||
isRunning: boolean;
|
||||
appId: string;
|
||||
readOnly: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
interface SessionData {
|
||||
|
||||
@@ -29,10 +29,10 @@ export function makeLabelForStandaloneApplication({
|
||||
fqdn = `${protocol}://${pullmergeRequestId}.${domain}`;
|
||||
}
|
||||
return [
|
||||
'--label coolify.managed=true',
|
||||
`--label coolify.version=${version}`,
|
||||
`--label coolify.type=standalone-application`,
|
||||
`--label coolify.configuration=${base64Encode(
|
||||
'coolify.managed=true',
|
||||
`coolify.version=${version}`,
|
||||
`coolify.type=standalone-application`,
|
||||
`coolify.configuration=${base64Encode(
|
||||
JSON.stringify({
|
||||
applicationId,
|
||||
fqdn,
|
||||
@@ -84,7 +84,15 @@ export function makeLabelForServices(type) {
|
||||
}
|
||||
|
||||
export const setDefaultConfiguration = async (data) => {
|
||||
let { buildPack, port, installCommand, startCommand, buildCommand, publishDirectory } = data;
|
||||
let {
|
||||
buildPack,
|
||||
port,
|
||||
installCommand,
|
||||
startCommand,
|
||||
buildCommand,
|
||||
publishDirectory,
|
||||
baseDirectory
|
||||
} = data;
|
||||
const template = scanningTemplates[buildPack];
|
||||
if (!port) {
|
||||
port = template?.port || 3000;
|
||||
@@ -97,6 +105,10 @@ export const setDefaultConfiguration = async (data) => {
|
||||
if (!startCommand) startCommand = template?.startCommand || 'yarn start';
|
||||
if (!buildCommand) buildCommand = template?.buildCommand || null;
|
||||
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
||||
if (baseDirectory) {
|
||||
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
||||
if (!baseDirectory.endsWith('/')) baseDirectory = `${baseDirectory}/`;
|
||||
}
|
||||
|
||||
return {
|
||||
buildPack,
|
||||
@@ -104,7 +116,8 @@ export const setDefaultConfiguration = async (data) => {
|
||||
installCommand,
|
||||
startCommand,
|
||||
buildCommand,
|
||||
publishDirectory
|
||||
publishDirectory,
|
||||
baseDirectory
|
||||
};
|
||||
};
|
||||
|
||||
@@ -122,6 +135,7 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
|
||||
RewriteRule ^(.+)$ index.php [QSA,L]
|
||||
`
|
||||
);
|
||||
await fs.writeFile(`${workdir}/entrypoint.sh`, `chown -R 1000 /app`);
|
||||
saveBuildLog({ line: 'Copied default configuration file for PHP.', buildId, applicationId });
|
||||
} else if (staticDeployments.includes(buildPack)) {
|
||||
await fs.writeFile(
|
||||
@@ -129,27 +143,35 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
|
||||
`user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
error_log /docker.stdout;
|
||||
pid /run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /docker.stdout main;
|
||||
|
||||
access_log off;
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
root /app;
|
||||
index index.html;
|
||||
try_files $uri $uri/index.html $uri/ /index.html =404;
|
||||
}
|
||||
@@ -160,7 +182,7 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
root /app;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -175,3 +197,11 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export function checkPnpm(installCommand = null, buildCommand = null, startCommand = null) {
|
||||
return (
|
||||
installCommand?.includes('pnpm') ||
|
||||
buildCommand?.includes('pnpm') ||
|
||||
startCommand?.includes('pnpm')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export default async function ({
|
||||
let file = `${workdir}/Dockerfile`;
|
||||
if (baseDirectory) {
|
||||
file = `${workdir}/${baseDirectory}/Dockerfile`;
|
||||
workdir = `${workdir}/${baseDirectory}`;
|
||||
}
|
||||
|
||||
const Dockerfile: Array<string> = (await fs.readFile(`${file}`, 'utf8'))
|
||||
|
||||
@@ -6,17 +6,17 @@ const createDockerfile = async (data, imageforBuild): Promise<void> => {
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${imageforBuild}`);
|
||||
Dockerfile.push('WORKDIR /usr/share/nginx/html');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'nginx:stable-alpine';
|
||||
const image = 'webdevops/nginx:alpine';
|
||||
const imageForBuild = 'node:lts';
|
||||
|
||||
await buildCacheImageWithNode(data, imageForBuild);
|
||||
|
||||
@@ -4,13 +4,17 @@ import { promises as fs } from 'fs';
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { applicationId, tag, port, startCommand, workdir, baseDirectory } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
const isPnpm = startCommand.includes('pnpm');
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push(
|
||||
`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${baseDirectory || ''} ./`
|
||||
);
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
|
||||
Dockerfile.push('RUN pnpm add -g pnpm');
|
||||
}
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${baseDirectory || ''} ./`);
|
||||
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const {
|
||||
@@ -13,9 +14,9 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
pullmergeRequestId
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
const isPnpm = checkPnpm(installCommand, buildCommand, startCommand);
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
@@ -32,17 +33,13 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
}
|
||||
});
|
||||
}
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
||||
try {
|
||||
await fs.stat(`${workdir}/yarn.lock`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
|
||||
} catch (error) {}
|
||||
try {
|
||||
await fs.stat(`${workdir}/pnpm-lock.yaml`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
|
||||
} catch (error) {}
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
|
||||
Dockerfile.push('RUN pnpm add -g pnpm');
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
|
||||
|
||||
if (buildCommand) {
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const {
|
||||
@@ -13,9 +14,10 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
pullmergeRequestId
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
const isPnpm = checkPnpm(installCommand, buildCommand, startCommand);
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
@@ -32,17 +34,12 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
}
|
||||
});
|
||||
}
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
||||
try {
|
||||
await fs.stat(`${workdir}/yarn.lock`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
|
||||
} catch (error) {}
|
||||
try {
|
||||
await fs.stat(`${workdir}/pnpm-lock.yaml`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
|
||||
} catch (error) {}
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
|
||||
Dockerfile.push('RUN pnpm add -g pnpm');
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
|
||||
if (buildCommand) {
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const {
|
||||
@@ -13,9 +14,9 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
pullmergeRequestId
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
const isPnpm = checkPnpm(installCommand, buildCommand, startCommand);
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
@@ -32,17 +33,12 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
}
|
||||
});
|
||||
}
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
||||
try {
|
||||
await fs.stat(`${workdir}/yarn.lock`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
|
||||
} catch (error) {}
|
||||
try {
|
||||
await fs.stat(`${workdir}/pnpm-lock.yaml`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
|
||||
} catch (error) {}
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
|
||||
Dockerfile.push('RUN pnpm add -g pnpm');
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
|
||||
if (buildCommand) {
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
}
|
||||
|
||||
@@ -4,21 +4,19 @@ import { promises as fs } from 'fs';
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { workdir, baseDirectory } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push('RUN a2enmod rewrite');
|
||||
Dockerfile.push('WORKDIR /var/www/html');
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} /var/www/html`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} /app`);
|
||||
Dockerfile.push(`COPY /.htaccess .`);
|
||||
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["apache2-foreground"]');
|
||||
Dockerfile.push('RUN chown -R www-data /var/www/html');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'php:apache';
|
||||
const image = 'webdevops/php-nginx';
|
||||
await createDockerfile(data, image);
|
||||
await buildImage(data);
|
||||
} catch (error) {
|
||||
|
||||
@@ -7,16 +7,16 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push('WORKDIR /usr/share/nginx/html');
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'nginx:stable-alpine';
|
||||
const image = 'webdevops/nginx:alpine';
|
||||
const imageForBuild = 'node:lts';
|
||||
await buildCacheImageWithNode(data, imageForBuild);
|
||||
await createDockerfile(data, image);
|
||||
|
||||
@@ -7,23 +7,21 @@ const createDockerfile = async (data, image, name): Promise<void> => {
|
||||
const { workdir, port, applicationId, tag } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/target target`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target target`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/local/cargo /usr/local/cargo`);
|
||||
Dockerfile.push(`COPY . .`);
|
||||
Dockerfile.push(`RUN cargo build --release --bin ${name}`);
|
||||
Dockerfile.push('FROM debian:buster-slim');
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(
|
||||
`RUN apt-get update -y && apt-get install -y --no-install-recommends openssl libcurl4 ca-certificates && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*`
|
||||
);
|
||||
Dockerfile.push(`RUN update-ca-certificates`);
|
||||
Dockerfile.push(
|
||||
`COPY --from=${applicationId}:${tag}-cache /usr/src/app/target/release/${name} ${name}`
|
||||
);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ["/usr/src/app/${name}"]`);
|
||||
Dockerfile.push(`CMD ["/app/${name}"]`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/share/nginx/html');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
@@ -33,20 +33,18 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
});
|
||||
}
|
||||
if (buildCommand) {
|
||||
Dockerfile.push(
|
||||
`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`
|
||||
);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
} else {
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
}
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'nginx:stable-alpine';
|
||||
const image = 'webdevops/nginx:alpine';
|
||||
const imageForBuild = 'node:lts';
|
||||
if (data.buildCommand) await buildCacheImageWithNode(data, imageForBuild);
|
||||
await createDockerfile(data, image);
|
||||
|
||||
@@ -6,17 +6,17 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/share/nginx/html');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'nginx:stable-alpine';
|
||||
const image = 'webdevops/nginx:alpine';
|
||||
const imageForBuild = 'node:lts';
|
||||
|
||||
await buildCacheImageWithNode(data, imageForBuild);
|
||||
|
||||
@@ -6,17 +6,17 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/share/nginx/html');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'nginx:stable-alpine';
|
||||
const image = 'webdevops/nginx:alpine';
|
||||
const imageForBuild = 'node:lts';
|
||||
await buildCacheImageWithNode(data, imageForBuild);
|
||||
await createDockerfile(data, image);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { version as currentVersion } from '../../package.json';
|
||||
import dayjs from 'dayjs';
|
||||
import Cookie from 'cookie';
|
||||
import os from 'os';
|
||||
import cuid from 'cuid';
|
||||
|
||||
try {
|
||||
if (!dev) {
|
||||
@@ -68,9 +69,9 @@ export const isTeamIdTokenAvailable = (request) => {
|
||||
|
||||
export const getTeam = (event) => {
|
||||
const cookies = Cookie.parse(event.request.headers.get('cookie'));
|
||||
if (cookies.teamId) {
|
||||
if (cookies?.teamId) {
|
||||
return cookies.teamId;
|
||||
} else if (event.locals.session.data.teamId) {
|
||||
} else if (event.locals.session.data?.teamId) {
|
||||
return event.locals.session.data.teamId;
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<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-stone-600 hover:text-white">
|
||||
<div class="flex space-x-2">
|
||||
{#if isPasswordField}
|
||||
<div on:click={() => (showPassword = !showPassword)}>
|
||||
|
||||
@@ -9,7 +9,16 @@ export const dateOptions: DateTimeFormatOptions = {
|
||||
hour12: false
|
||||
};
|
||||
|
||||
export const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby', 'php'];
|
||||
export const staticDeployments = [
|
||||
'react',
|
||||
'vuejs',
|
||||
'static',
|
||||
'svelte',
|
||||
'gatsby',
|
||||
'php',
|
||||
'astro',
|
||||
'eleventy'
|
||||
];
|
||||
export const notNodeDeployments = ['php', 'docker', 'rust'];
|
||||
|
||||
export function getDomain(domain) {
|
||||
|
||||
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"
|
||||
/>
|
||||
27
src/lib/components/svg/services/LanguageTool.svelte
Normal file
27
src/lib/components/svg/services/LanguageTool.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = false;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
|
||||
fill="none"
|
||||
viewBox="0 0 140 140"
|
||||
data-lt-extension-installed="true"
|
||||
><g clip-path="url(#clip0)"
|
||||
><path
|
||||
fill="#fff"
|
||||
fill-rule="evenodd"
|
||||
d="M140 43.602c0-1.662.001-3.324-.01-4.987-.008-1.4-.024-2.8-.062-4.2-.082-3.05-.262-6.126-.805-9.142-.55-3.06-1.448-5.907-2.864-8.688A29.227 29.227 0 0 0 123.476 3.81c-2.783-1.416-5.634-2.314-8.697-2.864-3.016-.542-6.094-.722-9.144-.804-1.4-.038-2.801-.054-4.202-.063C99.77.068 98.107.07 96.444.07L77.135 0H62.694L43.726.07c-1.666 0-3.332-.002-4.998.008-1.404.01-2.807.025-4.21.063-3.058.082-6.142.262-9.166.805-3.067.55-5.922 1.447-8.709 2.862a29.293 29.293 0 0 0-7.419 5.377 29.223 29.223 0 0 0-5.389 7.4c-1.42 2.78-2.32 5.63-2.871 8.691-.543 3.016-.723 6.091-.806 9.14-.038 1.4-.054 2.8-.062 4.2C.086 40.277 0 42.342 0 44.004v33.3l.086 19.102c0 1.665 0 3.33.01 4.994a200.6 200.6 0 0 0 .062 4.205c.083 3.054.263 6.135.807 9.155.551 3.064 1.451 5.916 2.87 8.7a29.294 29.294 0 0 0 12.807 12.794c2.788 1.418 5.645 2.317 8.714 2.868 3.022.542 6.105.722 9.162.804 1.403.038 2.806.054 4.21.063 1.666.01 3.332.009 4.998.009l19.14.001h14.477l19.101-.001c1.663 0 3.326.001 4.989-.009a202.92 202.92 0 0 0 4.202-.063c3.052-.082 6.13-.262 9.148-.805 3.061-.551 5.911-1.45 8.692-2.867a29.215 29.215 0 0 0 7.405-5.384 29.22 29.22 0 0 0 5.378-7.409c1.417-2.785 2.315-5.639 2.866-8.704.542-3.02.722-6.099.804-9.152.038-1.402.054-2.804.062-4.205.011-1.665.01-3.33.01-4.993l-.001-19.103V62.694L140 43.602"
|
||||
clip-rule="evenodd"
|
||||
/><path
|
||||
fill="#000"
|
||||
fill-rule="evenodd"
|
||||
d="M39.375 40.188h8.313a6.25 6.25 0 0 1 6.25 6.25v24.25h16.25v8.75h-18.75a6.25 6.25 0 0 1-6.25-6.25v-24.25h-5.813v-8.75zm63.563 6.25v6.5h-8.75v-4h-6.876v30.5h-8.75v-30.5h-6.874v4h-8.75v-6.5a6.25 6.25 0 0 1 6.25-6.25h27.5a6.25 6.25 0 0 1 6.25 6.25z"
|
||||
clip-rule="evenodd"
|
||||
/><path
|
||||
fill="#239AFF"
|
||||
d="M35.319 102.906l-8.138-5.812c2.39-3.347 4.857-5.936 7.452-7.753 2.884-2.018 5.948-3.091 9.117-3.091 2.942 0 5.491.714 7.768 2.08a17.622 17.622 0 0 1 2.615 1.94c.589.518 1.009.926 1.903 1.82 1.355 1.354 1.917 1.851 2.591 2.255.731.439 1.503.655 2.623.655 1.121 0 1.896-.217 2.631-.657.677-.405 1.245-.905 2.6-2.257l.012-.012c.89-.888 1.314-1.299 1.902-1.817a17.643 17.643 0 0 1 2.61-1.933c2.273-1.362 4.814-2.074 7.745-2.074s5.472.712 7.745 2.074c.916.55 1.758 1.183 2.61 1.933.589.518 1.013.929 1.902 1.817l.013.012c1.354 1.352 1.922 1.852 2.599 2.257.735.44 1.51.657 2.631.657.998 0 2.1-.386 3.383-1.284 1.572-1.1 3.272-2.886 5.048-5.372l8.138 5.812c-2.391 3.347-4.857 5.936-7.452 7.753-2.884 2.018-5.948 3.091-9.117 3.091-2.941 0-5.49-.713-7.769-2.078a17.627 17.627 0 0 1-2.619-1.938c-.59-.519-1.015-.93-1.906-1.82l-.013-.013c-1.351-1.348-1.917-1.846-2.59-2.25-.728-.436-1.494-.651-2.603-.651-1.109 0-1.875.215-2.603.651-.673.404-1.239.902-2.59 2.25l-.012.013c-.892.89-1.317 1.301-1.907 1.82-.855.752-1.7 1.388-2.62 1.938C66.74 104.287 64.192 105 61.25 105c-2.942 0-5.49-.714-7.768-2.08a17.654 17.654 0 0 1-2.615-1.939c-.588-.519-1.009-.927-1.902-1.82-1.355-1.355-1.918-1.852-2.592-2.256-.731-.439-1.503-.655-2.623-.655-.998 0-2.1.386-3.383 1.284-1.572 1.1-3.272 2.886-5.048 5.372z"
|
||||
/></g
|
||||
><defs><clipPath id="clip0"><path fill="#fff" d="M0 0h140v140H0z" /></clipPath></defs></svg
|
||||
>
|
||||
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
|
||||
>
|
||||
@@ -13,7 +13,7 @@ export function findBuildPack(pack, packageManager = 'npm') {
|
||||
if (pack === 'node') {
|
||||
return {
|
||||
...metaData,
|
||||
installCommand: null,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
buildCommand: null,
|
||||
startCommand: null,
|
||||
publishDirectory: null,
|
||||
|
||||
@@ -58,29 +58,22 @@ export async function removeApplication({ id, teamId }) {
|
||||
const id = containerObj.ID;
|
||||
const preview = containerObj.Image.split('-')[1];
|
||||
await removeDestinationDocker({ id, engine: destinationDocker.engine });
|
||||
try {
|
||||
if (preview) {
|
||||
await removeProxyConfiguration({ domain: `${preview}.${domain}` });
|
||||
} else {
|
||||
await removeProxyConfiguration({ domain });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.applicationSettings.deleteMany({ where: { application: { id } } });
|
||||
await prisma.buildLog.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.build.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.secret.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
|
||||
}
|
||||
|
||||
export async function getApplicationWebhook({ projectId, branch }) {
|
||||
try {
|
||||
let body = await prisma.application.findFirst({
|
||||
where: { projectId, branch },
|
||||
let application = await prisma.application.findFirst({
|
||||
where: { projectId, branch, settings: { autodeploy: true } },
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
settings: true,
|
||||
@@ -88,30 +81,41 @@ export async function getApplicationWebhook({ projectId, branch }) {
|
||||
secrets: true
|
||||
}
|
||||
});
|
||||
|
||||
if (body.gitSource?.githubApp?.clientSecret) {
|
||||
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
|
||||
if (!application) {
|
||||
return null;
|
||||
}
|
||||
if (body.gitSource?.githubApp?.webhookSecret) {
|
||||
body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret);
|
||||
if (application?.gitSource?.githubApp?.clientSecret) {
|
||||
application.gitSource.githubApp.clientSecret = decrypt(
|
||||
application.gitSource.githubApp.clientSecret
|
||||
);
|
||||
}
|
||||
if (body.gitSource?.githubApp?.privateKey) {
|
||||
body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey);
|
||||
if (application?.gitSource?.githubApp?.webhookSecret) {
|
||||
application.gitSource.githubApp.webhookSecret = decrypt(
|
||||
application.gitSource.githubApp.webhookSecret
|
||||
);
|
||||
}
|
||||
if (body?.gitSource?.gitlabApp?.appSecret) {
|
||||
body.gitSource.gitlabApp.appSecret = decrypt(body.gitSource.gitlabApp.appSecret);
|
||||
if (application?.gitSource?.githubApp?.privateKey) {
|
||||
application.gitSource.githubApp.privateKey = decrypt(
|
||||
application.gitSource.githubApp.privateKey
|
||||
);
|
||||
}
|
||||
if (body?.gitSource?.gitlabApp?.webhookToken) {
|
||||
body.gitSource.gitlabApp.webhookToken = decrypt(body.gitSource.gitlabApp.webhookToken);
|
||||
if (application?.gitSource?.gitlabApp?.appSecret) {
|
||||
application.gitSource.gitlabApp.appSecret = decrypt(
|
||||
application.gitSource.gitlabApp.appSecret
|
||||
);
|
||||
}
|
||||
if (body?.secrets.length > 0) {
|
||||
body.secrets = body.secrets.map((s) => {
|
||||
if (application?.gitSource?.gitlabApp?.webhookToken) {
|
||||
application.gitSource.gitlabApp.webhookToken = decrypt(
|
||||
application.gitSource.gitlabApp.webhookToken
|
||||
);
|
||||
}
|
||||
if (application?.secrets.length > 0) {
|
||||
application.secrets = application.secrets.map((s) => {
|
||||
s.value = decrypt(s.value);
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
return { ...body };
|
||||
return { ...application };
|
||||
} catch (e) {
|
||||
throw { status: 404, body: { message: e.message } };
|
||||
}
|
||||
@@ -131,7 +135,8 @@ export async function getApplication({ id, teamId }) {
|
||||
destinationDocker: true,
|
||||
settings: true,
|
||||
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||
secrets: true
|
||||
secrets: true,
|
||||
persistentStorage: true
|
||||
}
|
||||
});
|
||||
|
||||
@@ -157,24 +162,41 @@ export async function getApplication({ id, teamId }) {
|
||||
return { ...body };
|
||||
}
|
||||
|
||||
export async function configureGitRepository({ id, repository, branch, projectId, webhookToken }) {
|
||||
export async function configureGitRepository({
|
||||
id,
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
webhookToken,
|
||||
autodeploy
|
||||
}) {
|
||||
if (webhookToken) {
|
||||
const encryptedWebhookToken = encrypt(webhookToken);
|
||||
return await prisma.application.update({
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: {
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } }
|
||||
gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } },
|
||||
settings: { update: { autodeploy } }
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return await prisma.application.update({
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: { repository, branch, projectId }
|
||||
data: { repository, branch, projectId, settings: { update: { autodeploy } } }
|
||||
});
|
||||
}
|
||||
if (!autodeploy) {
|
||||
const applications = await prisma.application.findMany({ where: { branch, projectId } });
|
||||
for (const application of applications) {
|
||||
await prisma.applicationSettings.updateMany({
|
||||
where: { applicationId: application.id },
|
||||
data: { autodeploy: false }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureBuildPack({ id, buildPack }) {
|
||||
@@ -209,10 +231,14 @@ export async function configureApplication({
|
||||
});
|
||||
}
|
||||
|
||||
export async function setApplicationSettings({ id, debug, previews, dualCerts }) {
|
||||
export async function checkDoubleBranch(branch, projectId) {
|
||||
const applications = await prisma.application.findMany({ where: { branch, projectId } });
|
||||
return applications.length > 1;
|
||||
}
|
||||
export async function setApplicationSettings({ id, debug, previews, dualCerts, autodeploy }) {
|
||||
return await prisma.application.update({
|
||||
where: { id },
|
||||
data: { settings: { update: { debug, previews, dualCerts } } },
|
||||
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
}
|
||||
@@ -239,3 +265,7 @@ export async function createBuild({
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function getPersistentStorage(id) {
|
||||
return await prisma.applicationPersistentStorage.findMany({ where: { applicationId: id } });
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ export async function isDockerNetworkExists({ network }) {
|
||||
return await prisma.destinationDocker.findFirst({ where: { network } });
|
||||
}
|
||||
|
||||
export async function isServiceSecretExists({ id, name }) {
|
||||
return await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
|
||||
}
|
||||
export async function isSecretExists({ id, name, isPRMRSecret }) {
|
||||
return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -156,6 +158,43 @@ export const supportedServiceTypesAndVersions = [
|
||||
ports: {
|
||||
main: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'languagetool',
|
||||
fancyName: 'LanguageTool',
|
||||
baseImage: 'silviof/docker-languagetool',
|
||||
versions: ['latest'],
|
||||
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
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -180,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,6 +1,19 @@
|
||||
import { encrypt, decrypt } from '$lib/crypto';
|
||||
import { prisma } from './common';
|
||||
|
||||
export async function listServiceSecrets(serviceId: string) {
|
||||
let secrets = await prisma.serviceSecret.findMany({
|
||||
where: { serviceId },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
secrets = secrets.map((secret) => {
|
||||
secret.value = decrypt(secret.value);
|
||||
return secret;
|
||||
});
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
export async function listSecrets(applicationId: string) {
|
||||
let secrets = await prisma.secret.findMany({
|
||||
where: { applicationId },
|
||||
@@ -14,6 +27,12 @@ export async function listSecrets(applicationId: string) {
|
||||
return secrets;
|
||||
}
|
||||
|
||||
export async function createServiceSecret({ id, name, value }) {
|
||||
value = encrypt(value);
|
||||
return await prisma.serviceSecret.create({
|
||||
data: { name, value, service: { connect: { id } } }
|
||||
});
|
||||
}
|
||||
export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
||||
value = encrypt(value);
|
||||
return await prisma.secret.create({
|
||||
@@ -21,10 +40,24 @@ export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecre
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateServiceSecret({ id, name, value }) {
|
||||
value = encrypt(value);
|
||||
const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
|
||||
|
||||
if (found) {
|
||||
return await prisma.serviceSecret.updateMany({
|
||||
where: { serviceId: id, name },
|
||||
data: { value }
|
||||
});
|
||||
} else {
|
||||
return await prisma.serviceSecret.create({
|
||||
data: { name, value, service: { connect: { id } } }
|
||||
});
|
||||
}
|
||||
}
|
||||
export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
||||
value = encrypt(value);
|
||||
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
||||
console.log(found);
|
||||
|
||||
if (found) {
|
||||
return await prisma.secret.updateMany({
|
||||
@@ -38,6 +71,10 @@ export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecre
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeServiceSecret({ id, name }) {
|
||||
return await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } });
|
||||
}
|
||||
|
||||
export async function removeSecret({ id, name }) {
|
||||
return await prisma.secret.deleteMany({ where: { applicationId: id, name } });
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import cuid from 'cuid';
|
||||
import { generatePassword } from '.';
|
||||
@@ -19,7 +20,9 @@ export async function getService({ id, teamId }) {
|
||||
plausibleAnalytics: true,
|
||||
minio: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true
|
||||
wordpress: true,
|
||||
ghost: true,
|
||||
serviceSecret: true
|
||||
}
|
||||
});
|
||||
|
||||
@@ -42,6 +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 };
|
||||
}
|
||||
|
||||
@@ -105,6 +120,51 @@ export async function configureServiceType({ id, type }) {
|
||||
type
|
||||
}
|
||||
});
|
||||
} else if (type === 'languagetool') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
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 }) {
|
||||
@@ -125,7 +185,10 @@ 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 }) {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
}
|
||||
export async function updateVaultWardenService({ id, fqdn, name }) {
|
||||
@@ -143,11 +206,20 @@ 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 } });
|
||||
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||
|
||||
await prisma.service.delete({ where: { id } });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Dockerode from 'dockerode';
|
||||
import { promises as fs } from 'fs';
|
||||
import { checkPnpm } from './buildPacks/common';
|
||||
import { saveBuildLog } from './common';
|
||||
|
||||
export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
@@ -16,9 +17,10 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
secrets,
|
||||
pullmergeRequestId
|
||||
} = data;
|
||||
const isPnpm = checkPnpm(installCommand, buildCommand);
|
||||
const Dockerfile: Array<string> = [];
|
||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
@@ -35,20 +37,14 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
}
|
||||
});
|
||||
}
|
||||
// TODO: If build command defined, install command should be the default yarn install
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
|
||||
Dockerfile.push('RUN pnpm add -g pnpm');
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
if (installCommand) {
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
||||
try {
|
||||
await fs.stat(`${workdir}/yarn.lock`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
|
||||
} catch (error) {}
|
||||
try {
|
||||
await fs.stat(`${workdir}/pnpm-lock.yaml`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
|
||||
} catch (error) {}
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
}
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug });
|
||||
@@ -69,14 +65,14 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push('RUN cargo install cargo-chef');
|
||||
Dockerfile.push('COPY . .');
|
||||
Dockerfile.push('RUN cargo chef prepare --recipe-path recipe.json');
|
||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push('RUN cargo install cargo-chef');
|
||||
Dockerfile.push(`COPY --from=planner-${applicationId} /usr/src/app/recipe.json recipe.json`);
|
||||
Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`);
|
||||
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug });
|
||||
|
||||
@@ -82,7 +82,7 @@ backend backend-certbot
|
||||
# updatedAt={{updatedAt}}
|
||||
backend {{domain}}
|
||||
option forwardfor
|
||||
server {{id}} {{id}}:{{port}} check
|
||||
server {{id}} {{id}}:{{port}}
|
||||
{{/isRunning}}
|
||||
{{/applications}}
|
||||
|
||||
@@ -91,7 +91,7 @@ backend {{domain}}
|
||||
# updatedAt={{updatedAt}}
|
||||
backend {{domain}}
|
||||
option forwardfor
|
||||
server {{id}} {{id}}:{{port}} check
|
||||
server {{id}} {{id}}:{{port}}
|
||||
{{/isRunning}}
|
||||
{{/services}}
|
||||
|
||||
@@ -112,9 +112,15 @@ export async function haproxyInstance() {
|
||||
}
|
||||
|
||||
export async function configureHAProxy() {
|
||||
try {
|
||||
const haproxy = await haproxyInstance();
|
||||
|
||||
try {
|
||||
await checkHAProxy(haproxy);
|
||||
} catch (error) {
|
||||
return 'Error: HAProxy is not running';
|
||||
}
|
||||
|
||||
try {
|
||||
const data = {
|
||||
applications: [],
|
||||
services: [],
|
||||
|
||||
@@ -49,7 +49,12 @@ export async function completeTransaction(transactionId) {
|
||||
}
|
||||
export async function deleteProxy({ id }) {
|
||||
const haproxy = await haproxyInstance();
|
||||
try {
|
||||
await checkHAProxy(haproxy);
|
||||
} catch (error) {
|
||||
return 'Error: HAProxy is not running';
|
||||
}
|
||||
|
||||
let transactionId;
|
||||
try {
|
||||
await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json();
|
||||
@@ -175,11 +180,9 @@ export async function checkContainer(engine, container) {
|
||||
const { stdout } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker inspect --format '{{json .State}}' ${container}`
|
||||
);
|
||||
|
||||
const parsedStdout = JSON.parse(stdout);
|
||||
const status = parsedStdout.Status;
|
||||
const isRunning = parsedStdout.Running;
|
||||
|
||||
const isRunning = status === 'running' ? true : false;
|
||||
if (status === 'exited' || status === 'created') {
|
||||
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ export default async function ({
|
||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||
return commit.replace('\n', '');
|
||||
} catch (error) {
|
||||
console.log({ error });
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||
import { checkContainer } from '$lib/haproxy';
|
||||
import { checkContainer, reloadHaproxy } from '$lib/haproxy';
|
||||
import * as db from '$lib/database';
|
||||
import { dev } from '$app/env';
|
||||
import cuid from 'cuid';
|
||||
@@ -48,6 +48,17 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
||||
}
|
||||
}
|
||||
if (dualCerts) {
|
||||
let found = false;
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "ls -1 /app/ssl/${wwwDomain}.pem"`
|
||||
);
|
||||
found = true;
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
if (found) return;
|
||||
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||
dev ? '--test-cert' : ''
|
||||
@@ -56,7 +67,18 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "test -d /etc/letsencrypt/live/${nakedDomain}/ && cat /etc/letsencrypt/live/${nakedDomain}/fullchain.pem /etc/letsencrypt/live/${nakedDomain}/privkey.pem > /app/ssl/${nakedDomain}.pem || cat /etc/letsencrypt/live/${wwwDomain}/fullchain.pem /etc/letsencrypt/live/${wwwDomain}/privkey.pem > /app/ssl/${wwwDomain}.pem"`
|
||||
);
|
||||
await reloadHaproxy(host);
|
||||
} else {
|
||||
let found = false;
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "ls -1 /app/ssl/${domain}.pem"`
|
||||
);
|
||||
found = true;
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
if (found) return;
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${domain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||
dev ? '--test-cert' : ''
|
||||
@@ -65,6 +87,7 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem"`
|
||||
);
|
||||
await reloadHaproxy(host);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== 0) {
|
||||
@@ -76,9 +99,12 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
||||
export async function generateSSLCerts() {
|
||||
const ssls = [];
|
||||
const applications = await db.prisma.application.findMany({
|
||||
include: { destinationDocker: true, settings: true }
|
||||
include: { destinationDocker: true, settings: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
for (const application of applications) {
|
||||
try {
|
||||
if (application.fqdn && application.destinationDockerId) {
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
@@ -109,17 +135,25 @@ export async function generateSSLCerts() {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error during generateSSLCerts with ${application.fqdn}: ${error}`);
|
||||
}
|
||||
}
|
||||
const services = await db.prisma.service.findMany({
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
minio: true,
|
||||
plausibleAnalytics: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true
|
||||
}
|
||||
wordpress: true,
|
||||
ghost: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
|
||||
for (const service of services) {
|
||||
try {
|
||||
if (service.fqdn && service.destinationDockerId) {
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
@@ -136,6 +170,10 @@ export async function generateSSLCerts() {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error during generateSSLCerts with ${service.fqdn}: ${error}`);
|
||||
}
|
||||
}
|
||||
const { fqdn } = await db.prisma.setting.findFirst();
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
@@ -145,9 +183,10 @@ export async function generateSSLCerts() {
|
||||
if (ssls.length > 0) {
|
||||
for (const ssl of ssls) {
|
||||
if (!dev) {
|
||||
console.log('Checking SSL for', ssl.domain);
|
||||
await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
||||
} else {
|
||||
console.log('Generate ssl for', ssl.domain);
|
||||
console.log('Checking SSL for', ssl.domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,14 @@ import fs from 'fs/promises';
|
||||
import * as buildpacks from '../buildPacks';
|
||||
import * as importers from '../importers';
|
||||
import { dockerInstance } from '../docker';
|
||||
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common';
|
||||
import {
|
||||
asyncExecShell,
|
||||
asyncSleep,
|
||||
createDirectories,
|
||||
getDomain,
|
||||
getEngine,
|
||||
saveBuildLog
|
||||
} from '../common';
|
||||
import * as db from '$lib/database';
|
||||
import { decrypt } from '$lib/crypto';
|
||||
import { sentry } from '$lib/common';
|
||||
@@ -12,7 +19,7 @@ import {
|
||||
makeLabelForStandaloneApplication,
|
||||
setDefaultConfiguration
|
||||
} from '$lib/buildPacks/common';
|
||||
import { letsEncrypt } from '$lib/letsencrypt';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
export default async function (job) {
|
||||
/*
|
||||
@@ -39,17 +46,33 @@ export default async function (job) {
|
||||
publishDirectory,
|
||||
projectId,
|
||||
secrets,
|
||||
phpModules,
|
||||
type,
|
||||
pullmergeRequestId = null,
|
||||
sourceBranch = null,
|
||||
settings
|
||||
settings,
|
||||
persistentStorage
|
||||
} = job.data;
|
||||
const { debug } = settings;
|
||||
|
||||
await asyncSleep(500);
|
||||
await db.prisma.build.updateMany({
|
||||
where: {
|
||||
status: 'queued',
|
||||
id: { not: buildId },
|
||||
applicationId,
|
||||
createdAt: { lt: new Date(new Date().getTime() - 60 * 60 * 1000) }
|
||||
},
|
||||
data: { status: 'failed' }
|
||||
});
|
||||
let imageId = applicationId;
|
||||
let domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
|
||||
let volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
|
||||
buildPack !== 'docker' ? '/app' : ''
|
||||
}${storage.path}`;
|
||||
}) || [];
|
||||
// Previews, we need to get the source branch and set subdomain
|
||||
if (pullmergeRequestId) {
|
||||
branch = sourceBranch;
|
||||
@@ -67,17 +90,8 @@ export default async function (job) {
|
||||
const docker = dockerInstance({ destinationDocker });
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
|
||||
const build = await db.createBuild({
|
||||
id: buildId,
|
||||
applicationId,
|
||||
destinationDockerId: destinationDocker.id,
|
||||
gitSourceId: gitSource.id,
|
||||
githubAppId: gitSource.githubApp?.id,
|
||||
gitlabAppId: gitSource.gitlabApp?.id,
|
||||
type
|
||||
});
|
||||
|
||||
const { workdir, repodir } = await createDirectories({ repository, buildId: build.id });
|
||||
await db.prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
||||
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
||||
|
||||
const configuration = await setDefaultConfiguration(job.data);
|
||||
|
||||
@@ -87,6 +101,7 @@ export default async function (job) {
|
||||
startCommand = configuration.startCommand;
|
||||
buildCommand = configuration.buildCommand;
|
||||
publishDirectory = configuration.publishDirectory;
|
||||
baseDirectory = configuration.baseDirectory;
|
||||
|
||||
let commit = await importers[gitSource.type]({
|
||||
applicationId,
|
||||
@@ -97,19 +112,22 @@ export default async function (job) {
|
||||
gitlabAppId: gitSource.gitlabApp?.id,
|
||||
repository,
|
||||
branch,
|
||||
buildId: build.id,
|
||||
buildId,
|
||||
apiUrl: gitSource.apiUrl,
|
||||
projectId,
|
||||
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
|
||||
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null
|
||||
});
|
||||
if (!commit) {
|
||||
throw new Error('No commit found?');
|
||||
}
|
||||
let tag = commit.slice(0, 7);
|
||||
if (pullmergeRequestId) {
|
||||
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
||||
}
|
||||
|
||||
try {
|
||||
await db.prisma.build.update({ where: { id: build.id }, data: { commit } });
|
||||
db.prisma.build.update({ where: { id: buildId }, data: { commit } });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
@@ -160,7 +178,7 @@ export default async function (job) {
|
||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId);
|
||||
if (buildpacks[buildPack])
|
||||
await buildpacks[buildPack]({
|
||||
buildId: build.id,
|
||||
buildId,
|
||||
applicationId,
|
||||
domain,
|
||||
name,
|
||||
@@ -181,7 +199,8 @@ export default async function (job) {
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
secrets
|
||||
secrets,
|
||||
phpModules
|
||||
});
|
||||
else {
|
||||
saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||
@@ -241,14 +260,53 @@ export default async function (job) {
|
||||
}
|
||||
try {
|
||||
saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
||||
const { stderr } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run ${envFound && `--env-file=${workdir}/.env`} ${labels.join(
|
||||
' '
|
||||
)} --name ${imageId} --network ${
|
||||
docker.network
|
||||
} --restart always -d ${applicationId}:${tag}`
|
||||
// for await (const volume of volumes) {
|
||||
// const id = volume.split(':')[0];
|
||||
// try {
|
||||
// await asyncExecShell(`DOCKER_HOST=${host} docker volume inspect ${id}`);
|
||||
// } catch (error) {
|
||||
// await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}`);
|
||||
// }
|
||||
// }
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
}
|
||||
};
|
||||
});
|
||||
const compose = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[imageId]: {
|
||||
image: `${applicationId}:${tag}`,
|
||||
container_name: imageId,
|
||||
volumes,
|
||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||
networks: [docker.network],
|
||||
labels: labels,
|
||||
depends_on: [],
|
||||
restart: 'always'
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[docker.network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: Object.assign({}, ...composeVolumes)
|
||||
};
|
||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(compose));
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
|
||||
);
|
||||
if (stderr) console.log(stderr);
|
||||
|
||||
// const { stderr } = await asyncExecShell(
|
||||
// `DOCKER_HOST=${host} docker run ${envFound && `--env-file=${workdir}/.env`} ${labels.join(
|
||||
// ' '
|
||||
// )} --name ${imageId} --network ${docker.network} --restart always ${volumes.length > 0 ? volumes : ''
|
||||
// } -d ${applicationId}:${tag}`
|
||||
// );
|
||||
saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||
} catch (error) {
|
||||
saveBuildLog({ line: error, buildId, applicationId });
|
||||
|
||||
@@ -1,12 +1,33 @@
|
||||
import { dev } from '$app/env';
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import { asyncExecShell, getEngine, version } from '$lib/common';
|
||||
import { prisma } from '$lib/database';
|
||||
import { defaultProxyImageHttp, defaultProxyImageTcp } from '$lib/haproxy';
|
||||
|
||||
export default async function () {
|
||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||
for (const destinationDocker of destinationDockers) {
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
// Cleanup old coolify images
|
||||
try {
|
||||
let { stdout: images } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs `
|
||||
);
|
||||
images = images.trim();
|
||||
if (images) {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
// Tagging images with labels
|
||||
// try {
|
||||
// const images = [
|
||||
@@ -30,16 +51,6 @@ export default async function () {
|
||||
// }
|
||||
// }
|
||||
// } catch (error) {}
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
// if (!dev) {
|
||||
// // Cleanup images that are not managed by coolify
|
||||
// try {
|
||||
|
||||
@@ -86,8 +86,8 @@ const cron = async () => {
|
||||
);
|
||||
|
||||
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
|
||||
await queue.ssl.add('ssl', {}, { repeat: { every: 60000 } });
|
||||
await queue.cleanup.add('cleanup', {}, { repeat: { every: 600000 } });
|
||||
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
|
||||
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
|
||||
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
||||
|
||||
const events = {
|
||||
@@ -110,18 +110,18 @@ cron().catch((error) => {
|
||||
const buildQueueName = 'build_queue';
|
||||
const buildQueue = new Queue(buildQueueName, connectionOptions);
|
||||
const buildWorker = new Worker(buildQueueName, async (job) => await builder(job), {
|
||||
concurrency: 2,
|
||||
concurrency: 1,
|
||||
...connectionOptions
|
||||
});
|
||||
|
||||
buildWorker.on('completed', async (job: Bullmq.Job) => {
|
||||
try {
|
||||
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
const workdir = `/tmp/build-sources/${job.data.repository}/`;
|
||||
await asyncExecShell(`rm -fr ${workdir}`);
|
||||
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
|
||||
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
||||
}
|
||||
return;
|
||||
});
|
||||
@@ -133,7 +133,7 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
|
||||
console.log(error);
|
||||
} finally {
|
||||
const workdir = `/tmp/build-sources/${job.data.repository}`;
|
||||
await asyncExecShell(`rm -fr ${workdir}`);
|
||||
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
||||
}
|
||||
saveBuildLog({
|
||||
line: 'Failed to deploy!',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { dev } from '$app/env';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { configureHAProxy } from '$lib/haproxy/configuration';
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ export default async function () {
|
||||
try {
|
||||
return await generateSSLCerts();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { asyncExecShell } from '$lib/common';
|
||||
import { reloadHaproxy } from '$lib/haproxy';
|
||||
|
||||
export default async function () {
|
||||
try {
|
||||
return await asyncExecShell(
|
||||
await asyncExecShell(
|
||||
`docker run --rm --name certbot-renewal -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs renew`
|
||||
);
|
||||
await reloadHaproxy('unix:///var/run/docker.sock');
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -63,13 +63,11 @@
|
||||
const data = await get(`/update.json`);
|
||||
if (overrideVersion || data?.isUpdateAvailable) {
|
||||
latestVersion = overrideVersion || data.latestVersion;
|
||||
console.log('checking update');
|
||||
const { exists } = await post(`/update.json`, {
|
||||
type: 'check',
|
||||
latestVersion,
|
||||
overrideVersion
|
||||
});
|
||||
isUpdateAvailable = exists;
|
||||
if (overrideVersion) {
|
||||
isUpdateAvailable = true;
|
||||
} else {
|
||||
isUpdateAvailable = data.isUpdateAvailable;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
import { del, post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
import { gitTokens } from '$lib/store';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
|
||||
if (githubToken) $gitTokens.githubToken = githubToken;
|
||||
if (gitlabToken) $gitTokens.gitlabToken = gitlabToken;
|
||||
@@ -86,7 +87,15 @@
|
||||
async function handleDeploySubmit() {
|
||||
try {
|
||||
const { buildId } = await post(`/applications/${id}/deploy.json`, { ...application });
|
||||
toast.push('Deployment queued.');
|
||||
console.log($page.url);
|
||||
if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) {
|
||||
return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||
} else {
|
||||
return await goto(`/applications/${id}/logs/build?buildId=${buildId}`, {
|
||||
replaceState: true
|
||||
});
|
||||
}
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
@@ -111,8 +120,6 @@
|
||||
return window.location.reload();
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -271,6 +278,35 @@
|
||||
</svg></button
|
||||
></a
|
||||
>
|
||||
<a
|
||||
href="/applications/{id}/storage"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-pink-500 rounded"
|
||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||
>
|
||||
<button
|
||||
title="Persistent Storage"
|
||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
||||
data-tooltip="Persistent Storage"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
||||
</svg>
|
||||
</button></a
|
||||
>
|
||||
<a
|
||||
href="/applications/{id}/previews"
|
||||
sveltekit:prefetch
|
||||
|
||||
@@ -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';
|
||||
@@ -25,14 +25,19 @@
|
||||
let selected = {
|
||||
projectId: undefined,
|
||||
repository: undefined,
|
||||
branch: undefined
|
||||
branch: undefined,
|
||||
autodeploy: application.settings.autodeploy || true
|
||||
};
|
||||
let showSave = false;
|
||||
|
||||
async function loadRepositoriesByPage(page = 0) {
|
||||
return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, {
|
||||
Authorization: `token ${$gitTokens.githubToken}`
|
||||
});
|
||||
}
|
||||
|
||||
let reposSelectOptions;
|
||||
let branchSelectOptions;
|
||||
async function loadRepositories() {
|
||||
let page = 1;
|
||||
let reposCount = 0;
|
||||
@@ -47,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;
|
||||
@@ -56,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);
|
||||
@@ -63,13 +77,21 @@
|
||||
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}`
|
||||
);
|
||||
if (data.used) {
|
||||
errorNotification('This branch is already used by another application.');
|
||||
const sure = confirm(
|
||||
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
|
||||
);
|
||||
if (sure) {
|
||||
selected.autodeploy = false;
|
||||
showSave = true;
|
||||
return true;
|
||||
}
|
||||
showSave = false;
|
||||
return true;
|
||||
}
|
||||
@@ -142,49 +164,35 @@
|
||||
<a href={`/sources/${application.gitSource.id}`}><button>Configure it now</button></a>
|
||||
</div>
|
||||
{:else}
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<div>
|
||||
{#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}
|
||||
<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">
|
||||
<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" />
|
||||
{#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="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
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
let projects = [];
|
||||
let branches = [];
|
||||
let showSave = false;
|
||||
let autodeploy = application.settings.autodeploy || true;
|
||||
|
||||
let selected = {
|
||||
group: undefined,
|
||||
@@ -138,7 +139,14 @@
|
||||
`/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
|
||||
);
|
||||
if (data.used) {
|
||||
errorNotification('This branch is already used by another application.');
|
||||
const sure = confirm(
|
||||
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
|
||||
);
|
||||
if (sure) {
|
||||
autodeploy = false;
|
||||
showSave = true;
|
||||
return true;
|
||||
}
|
||||
showSave = false;
|
||||
return true;
|
||||
}
|
||||
@@ -235,10 +243,14 @@
|
||||
|
||||
const url = `/applications/${id}/configuration/repository.json`;
|
||||
try {
|
||||
const repository = `${selected.group.full_path.replace('-personal', '')}/${
|
||||
selected.project.name
|
||||
}`;
|
||||
await post(url, {
|
||||
repository: `${selected.group.full_path}/${selected.project.name}`,
|
||||
repository,
|
||||
branch: selected.branch.name,
|
||||
projectId: selected.project.id,
|
||||
autodeploy,
|
||||
webhookToken
|
||||
});
|
||||
return await goto(from || `/applications/${id}/configuration/buildpack`);
|
||||
|
||||
@@ -30,14 +30,21 @@ export const post: RequestHandler = async (event) => {
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
let { repository, branch, projectId, webhookToken } = await event.request.json();
|
||||
let { repository, branch, projectId, webhookToken, autodeploy } = await event.request.json();
|
||||
|
||||
repository = repository.toLowerCase();
|
||||
branch = branch.toLowerCase();
|
||||
projectId = Number(projectId);
|
||||
|
||||
try {
|
||||
await db.configureGitRepository({ id, repository, branch, projectId, webhookToken });
|
||||
await db.configureGitRepository({
|
||||
id,
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
webhookToken,
|
||||
autodeploy
|
||||
});
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
gitlabApp: Prisma.GitlabApp;
|
||||
githubApp: Prisma.GithubApp;
|
||||
};
|
||||
sources = sources.filter(
|
||||
const filteredSources = sources.filter(
|
||||
(source) =>
|
||||
(source.type === 'github' && source.githubAppId && source.githubApp.installationId) ||
|
||||
(source.type === 'gitlab' && source.gitlabAppId)
|
||||
@@ -59,8 +59,8 @@
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">Select a Git Source</div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
{#if !sources || sources.length === 0}
|
||||
<div class="flex flex-col justify-center">
|
||||
{#if !filteredSources || filteredSources.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="pb-2">No configurable Git Source found</div>
|
||||
<div class="flex justify-center">
|
||||
@@ -83,7 +83,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-wrap justify-center">
|
||||
{#each sources as source}
|
||||
{#each filteredSources as source}
|
||||
<div class="p-2">
|
||||
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
||||
<button
|
||||
|
||||
@@ -11,6 +11,7 @@ export const post: RequestHandler = async (event) => {
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { pullmergeRequestId = null, branch } = await event.request.json();
|
||||
try {
|
||||
const buildId = cuid();
|
||||
const applicationFound = await db.getApplication({ id, teamId });
|
||||
@@ -30,7 +31,29 @@ export const post: RequestHandler = async (event) => {
|
||||
await db.prisma.application.update({ where: { id }, data: { configHash } });
|
||||
}
|
||||
await db.prisma.application.update({ where: { id }, data: { updatedAt: new Date() } });
|
||||
await db.prisma.build.create({
|
||||
data: {
|
||||
id: buildId,
|
||||
applicationId: id,
|
||||
destinationDockerId: applicationFound.destinationDocker.id,
|
||||
gitSourceId: applicationFound.gitSource.id,
|
||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
||||
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
|
||||
status: 'queued',
|
||||
type: 'manual'
|
||||
}
|
||||
});
|
||||
if (pullmergeRequestId) {
|
||||
await buildQueue.add(buildId, {
|
||||
build_id: buildId,
|
||||
type: 'manual',
|
||||
...applicationFound,
|
||||
sourceBranch: branch,
|
||||
pullmergeRequestId
|
||||
});
|
||||
} else {
|
||||
await buildQueue.add(buildId, { build_id: buildId, type: 'manual', ...applicationFound });
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
let debug = application.settings.debug;
|
||||
let previews = application.settings.previews;
|
||||
let dualCerts = application.settings.dualCerts;
|
||||
|
||||
let autodeploy = application.settings.autodeploy;
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
||||
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
}
|
||||
@@ -75,10 +75,32 @@
|
||||
if (name === 'dualCerts') {
|
||||
dualCerts = !dualCerts;
|
||||
}
|
||||
if (name === 'autodeploy') {
|
||||
autodeploy = !autodeploy;
|
||||
}
|
||||
try {
|
||||
await post(`/applications/${id}/settings.json`, { previews, debug, dualCerts });
|
||||
await post(`/applications/${id}/settings.json`, {
|
||||
previews,
|
||||
debug,
|
||||
dualCerts,
|
||||
autodeploy,
|
||||
branch: application.branch,
|
||||
projectId: application.projectId
|
||||
});
|
||||
return toast.push('Settings saved.');
|
||||
} catch ({ error }) {
|
||||
if (name === 'debug') {
|
||||
debug = !debug;
|
||||
}
|
||||
if (name === 'previews') {
|
||||
previews = !previews;
|
||||
}
|
||||
if (name === 'dualCerts') {
|
||||
dualCerts = !dualCerts;
|
||||
}
|
||||
if (name === 'autodeploy') {
|
||||
autodeploy = !autodeploy;
|
||||
}
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@@ -87,7 +109,7 @@
|
||||
try {
|
||||
await post(`/applications/${id}/check.json`, { fqdn: application.fqdn, forceSave });
|
||||
await post(`/applications/${id}.json`, { ...application });
|
||||
return window.location.reload();
|
||||
return toast.push('Configurations saved.');
|
||||
} catch ({ error }) {
|
||||
if (error.startsWith('DNS not set')) {
|
||||
forceSave = true;
|
||||
@@ -340,7 +362,6 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<div class="flex-col">
|
||||
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
|
||||
@@ -383,22 +404,23 @@
|
||||
<div class="flex space-x-1 pb-5 font-bold">
|
||||
<div class="title">Features</div>
|
||||
</div>
|
||||
<!-- <ul class="mt-2 divide-y divide-stone-800">
|
||||
<Setting
|
||||
bind:setting={forceSSL}
|
||||
on:click={() => changeSettings('forceSSL')}
|
||||
title="Force https"
|
||||
description="Creates a https redirect for all requests from http and also generates a https certificate for the domain through Let's Encrypt."
|
||||
/>
|
||||
</ul> -->
|
||||
<div class="px-10 pb-10">
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
isCenter={false}
|
||||
bind:setting={autodeploy}
|
||||
on:click={() => changeSettings('autodeploy')}
|
||||
title="Enable Automatic Deployment"
|
||||
description="Enable automatic deployment through webhooks."
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
isCenter={false}
|
||||
bind:setting={previews}
|
||||
on:click={() => changeSettings('previews')}
|
||||
title="Enable MR/PR Previews"
|
||||
description="Creates previews from pull and merge requests."
|
||||
description="Enable preview deployments from pull or merge requests."
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
logs = logs.concat(responseLogs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
|
||||
loading = false;
|
||||
streamInterval = setInterval(async () => {
|
||||
if (status !== 'running') {
|
||||
if (status !== 'running' && status !== 'queued') {
|
||||
clearInterval(streamInterval);
|
||||
return;
|
||||
}
|
||||
const nextSequence = logs[logs.length - 1].time;
|
||||
const nextSequence = logs[logs.length - 1]?.time || 0;
|
||||
try {
|
||||
const data = await get(
|
||||
`/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${nextSequence}`
|
||||
@@ -83,6 +83,9 @@
|
||||
{#if currentStatus === 'running'}
|
||||
<LoadingLogs />
|
||||
{/if}
|
||||
{#if currentStatus === 'queued'}
|
||||
<div class="text-center font-bold text-xl">Queued and waiting for execution.</div>
|
||||
{:else}
|
||||
<div class="flex justify-end sticky top-0 p-2">
|
||||
<button
|
||||
on:click={followBuild}
|
||||
@@ -116,5 +119,6 @@
|
||||
<div>{log.line + '\n'}</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const get: RequestHandler = async (event) => {
|
||||
return {
|
||||
body: {
|
||||
logs,
|
||||
status: data?.status || 'running'
|
||||
status: data?.status
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
build.took = data.builds[0].took;
|
||||
build.since = data.builds[0].since;
|
||||
}
|
||||
|
||||
return build;
|
||||
});
|
||||
return;
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
@@ -120,6 +120,8 @@
|
||||
<div class="w-48 text-center text-xs">
|
||||
{#if build.status === 'running'}
|
||||
<div class="font-bold">Running</div>
|
||||
{:else if build.status === 'queued'}
|
||||
<div class="font-bold">Queued</div>
|
||||
{:else}
|
||||
<div>{build.since}</div>
|
||||
<div>Finished in <span class="font-bold">{build.took}s</span></div>
|
||||
|
||||
@@ -26,15 +26,28 @@
|
||||
export let applicationSecrets;
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import Secret from '../secrets/_Secret.svelte';
|
||||
import { get } from '$lib/api';
|
||||
import { get, post } from '$lib/api';
|
||||
import { page } from '$app/stores';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
|
||||
const { id } = $page.params;
|
||||
async function refreshSecrets() {
|
||||
const data = await get(`/applications/${id}/secrets.json`);
|
||||
PRMRSecrets = [...data.secrets];
|
||||
}
|
||||
async function redeploy(container) {
|
||||
try {
|
||||
await post(`/applications/${id}/deploy.json`, {
|
||||
pullmergeRequestId: container.pullmergeRequestId,
|
||||
branch: container.branch
|
||||
});
|
||||
toast.push('Application redeployed queued.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
@@ -90,6 +103,11 @@
|
||||
<div class="truncate text-center text-xl font-bold">{getDomain(container.fqdn)}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="bg-coollabs hover:bg-coollabs-100" on:click={() => redeploy(container)}
|
||||
>Redeploy</button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="flex-col">
|
||||
|
||||
@@ -8,10 +8,17 @@ export const post: RequestHandler = async (event) => {
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { debug, previews, dualCerts } = await event.request.json();
|
||||
const { debug, previews, dualCerts, autodeploy, branch, projectId } = await event.request.json();
|
||||
|
||||
try {
|
||||
await db.setApplicationSettings({ id, debug, previews, dualCerts });
|
||||
const isDouble = await db.checkDoubleBranch(branch, projectId);
|
||||
if (isDouble && autodeploy) {
|
||||
throw {
|
||||
message:
|
||||
'Cannot activate automatic deployments until only one application is defined for this repository / branch.'
|
||||
};
|
||||
}
|
||||
await db.setApplicationSettings({ id, debug, previews, dualCerts, autodeploy });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
|
||||
73
src/routes/applications/[id]/storage/_Storage.svelte
Normal file
73
src/routes/applications/[id]/storage/_Storage.svelte
Normal file
@@ -0,0 +1,73 @@
|
||||
<script lang="ts">
|
||||
export let isNew = false;
|
||||
export let storage = {
|
||||
id: null,
|
||||
path: null
|
||||
};
|
||||
import { del, post } from '$lib/api';
|
||||
import { page } from '$app/stores';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
const { id } = $page.params;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
async function saveStorage(newStorage = false) {
|
||||
try {
|
||||
if (!storage.path) return errorNotification('Path is required.');
|
||||
storage.path = storage.path.startsWith('/') ? storage.path : `/${storage.path}`;
|
||||
storage.path = storage.path.endsWith('/') ? storage.path.slice(0, -1) : storage.path;
|
||||
storage.path.replace(/\/\//g, '/');
|
||||
await post(`/applications/${id}/storage.json`, {
|
||||
path: storage.path,
|
||||
storageId: storage.id,
|
||||
newStorage
|
||||
});
|
||||
dispatch('refresh');
|
||||
if (isNew) {
|
||||
storage.path = null;
|
||||
storage.id = null;
|
||||
}
|
||||
if (newStorage) toast.push('Storage saved.');
|
||||
else toast.push('Storage updated.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
async function removeStorage() {
|
||||
try {
|
||||
await del(`/applications/${id}/storage.json`, { path: storage.path });
|
||||
dispatch('refresh');
|
||||
toast.push('Storage deleted.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<td>
|
||||
<input
|
||||
bind:value={storage.path}
|
||||
required
|
||||
placeholder="eg: /sqlite.db"
|
||||
class=" border border-dashed border-coolgray-300"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{#if isNew}
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveStorage(true)}>Add</button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row justify-center space-x-2">
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="" on:click={() => saveStorage(false)}>Set</button>
|
||||
</div>
|
||||
<div class="flex justify-center items-end">
|
||||
<button class="bg-red-600 hover:bg-red-500" on:click={removeStorage}>Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
63
src/routes/applications/[id]/storage/index.json.ts
Normal file
63
src/routes/applications/[id]/storage/index.json.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { status, body, teamId } = await getUserDetails(event, false);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
try {
|
||||
const persistentStorages = await db.getPersistentStorage(id);
|
||||
return {
|
||||
body: {
|
||||
persistentStorages
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { path, newStorage, storageId } = await event.request.json();
|
||||
try {
|
||||
if (newStorage) {
|
||||
await db.prisma.applicationPersistentStorage.create({
|
||||
data: { path, application: { connect: { id } } }
|
||||
});
|
||||
} else {
|
||||
await db.prisma.applicationPersistentStorage.update({
|
||||
where: { id: storageId },
|
||||
data: { path }
|
||||
});
|
||||
}
|
||||
return {
|
||||
status: 201
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const del: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { path } = await event.request.json();
|
||||
|
||||
try {
|
||||
await db.prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id, path } });
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
73
src/routes/applications/[id]/storage/index.svelte
Normal file
73
src/routes/applications/[id]/storage/index.svelte
Normal file
@@ -0,0 +1,73 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, stuff }) => {
|
||||
let endpoint = `/applications/${params.id}/storage.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
application: stuff.application,
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${endpoint}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
|
||||
export let persistentStorages;
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { page } from '$app/stores';
|
||||
import Storage from './_Storage.svelte';
|
||||
import { get } from '$lib/api';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
|
||||
const { id } = $page.params;
|
||||
async function refreshStorage() {
|
||||
const data = await get(`/applications/${id}/storage.json`);
|
||||
persistentStorages = [...data.persistentStorages];
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
Persistent storage for <a href={application.fqdn} target="_blank"
|
||||
>{getDomain(application.fqdn)}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||
<div class="flex justify-center py-4 text-center">
|
||||
<Explainer
|
||||
customClass="w-full"
|
||||
text={'You can specify any folder that you want to be persistent across deployments. <br>This is useful for storing data such as a database (SQLite) or a cache.'}
|
||||
/>
|
||||
</div>
|
||||
<table class="mx-auto border-separate text-left">
|
||||
<thead>
|
||||
<tr class="h-12">
|
||||
<th scope="col">Path</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each persistentStorages as storage}
|
||||
{#key storage.id}
|
||||
<tr>
|
||||
<Storage on:refresh={refreshStorage} {storage} />
|
||||
</tr>
|
||||
{/key}
|
||||
{/each}
|
||||
<tr>
|
||||
<Storage on:refresh={refreshStorage} isNew />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1,34 +1,19 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch }) => {
|
||||
const endpoint = '/applications.json';
|
||||
const res = await fetch(endpoint);
|
||||
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${endpoint}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let applications: Array<Application>;
|
||||
import { session } from '$app/stores';
|
||||
import Application from './_Application.svelte';
|
||||
import { post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
async function newApplication() {
|
||||
const { id } = await post('/applications/new', {});
|
||||
return await goto(`/applications/${id}`, { replaceState: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl ">Applications</div>
|
||||
{#if $session.isAdmin}
|
||||
<a href="/new/application" class="add-icon bg-green-600 hover:bg-green-500">
|
||||
<div on:click={newApplication} class="add-icon cursor-pointer bg-green-600 hover:bg-green-500">
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -42,7 +27,7 @@
|
||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||
/></svg
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center">
|
||||
|
||||
@@ -6,10 +6,7 @@ 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 { name } = await event.request.json();
|
||||
if (!name) return { status: 400, body: { error: 'Missing name.' } };
|
||||
|
||||
const name = uniqueName();
|
||||
try {
|
||||
const { id } = await db.newApplication({ name, teamId });
|
||||
return { status: 201, body: { id } };
|
||||
@@ -8,12 +8,22 @@ export const get: RequestHandler = async (event) => {
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
try {
|
||||
const applicationsCount = await (await db.listApplications(teamId)).length;
|
||||
const sourcesCount = await (await db.listSources(teamId)).length;
|
||||
const destinationsCount = await (await db.listDestinations(teamId)).length;
|
||||
const teamsCount = await (await db.getMyTeams({ userId })).length;
|
||||
const databasesCount = await (await db.listDatabases(teamId)).length;
|
||||
const servicesCount = await (await db.listServices(teamId)).length;
|
||||
const applicationsCount = await db.prisma.application.count({
|
||||
where: { teams: { some: { id: teamId } } }
|
||||
});
|
||||
const sourcesCount = await db.prisma.gitSource.count({
|
||||
where: { teams: { some: { id: teamId } } }
|
||||
});
|
||||
const destinationsCount = await db.prisma.destinationDocker.count({
|
||||
where: { teams: { some: { id: teamId } } }
|
||||
});
|
||||
const teamsCount = await db.prisma.permission.count({ where: { userId } });
|
||||
const databasesCount = await db.prisma.database.count({
|
||||
where: { teams: { some: { id: teamId } } }
|
||||
});
|
||||
const servicesCount = await db.prisma.service.count({
|
||||
where: { teams: { some: { id: teamId } } }
|
||||
});
|
||||
return {
|
||||
body: {
|
||||
applicationsCount,
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch }) => {
|
||||
const url = `/databases.json`;
|
||||
const res = await fetch(url);
|
||||
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${url}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let databases;
|
||||
import Clickhouse from '$lib/components/svg/databases/Clickhouse.svelte';
|
||||
@@ -27,11 +6,18 @@
|
||||
import MySQL from '$lib/components/svg/databases/MySQL.svelte';
|
||||
import PostgreSQL from '$lib/components/svg/databases/PostgreSQL.svelte';
|
||||
import Redis from '$lib/components/svg/databases/Redis.svelte';
|
||||
import { post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
async function newDatabase() {
|
||||
const { id } = await post('/databases/new', {});
|
||||
return await goto(`/databases/${id}`, { replaceState: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">Databases</div>
|
||||
<a href="/new/database" class="add-icon bg-purple-600 hover:bg-purple-500">
|
||||
<div on:click={newDatabase} class="add-icon cursor-pointer bg-purple-600 hover:bg-purple-500">
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -45,7 +31,7 @@
|
||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||
/></svg
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap justify-center">
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { RequestHandler } from '@sveltejs/kit';
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
try {
|
||||
const databases = await db.listDatabases(teamId);
|
||||
return {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { getUserDetails, uniqueName } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
@@ -6,9 +6,7 @@ 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 { name } = await event.request.json();
|
||||
|
||||
const name = uniqueName();
|
||||
try {
|
||||
const { id } = await db.newDatabase({ name, teamId });
|
||||
return { status: 201, body: { id } };
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch }) => {
|
||||
const url = `/common/getUniqueName.json`;
|
||||
const res = await fetch(url);
|
||||
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${url}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let name;
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { post } from '$lib/api';
|
||||
import { errorNotification } from '$lib/form';
|
||||
|
||||
let nameEl: HTMLInputElement;
|
||||
onMount(() => {
|
||||
nameEl.focus();
|
||||
});
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const { id } = await post('/new/application.json', { name });
|
||||
return await goto(`/applications/${id}`);
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">Add New Application</div>
|
||||
</div>
|
||||
<div class="pt-10">
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<input name="name" placeholder="Application name" bind:this={nameEl} bind:value={name} />
|
||||
<button type="submit" class="bg-green-600 hover:bg-green-500">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,59 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, session }) => {
|
||||
const url = `/common/getUniqueName.json`;
|
||||
const res = await fetch(url);
|
||||
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${url}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let name;
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { post } from '$lib/api';
|
||||
let autofocus;
|
||||
|
||||
onMount(() => {
|
||||
autofocus.focus();
|
||||
});
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const { id } = await post('/new/database.json', { name });
|
||||
return await goto(`/databases/${id}`);
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">Add New Database</div>
|
||||
</div>
|
||||
<div class="pt-10">
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<input
|
||||
name="name"
|
||||
placeholder="Database name"
|
||||
required
|
||||
bind:this={autofocus}
|
||||
bind:value={name}
|
||||
/>
|
||||
<button type="submit" class="bg-purple-600 hover:bg-purple-500">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,59 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, session }) => {
|
||||
const url = `/common/getUniqueName.json`;
|
||||
const res = await fetch(url);
|
||||
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${url}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let name;
|
||||
import { enhance, errorNotification } from '$lib/form';
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { post } from '$lib/api';
|
||||
let autofocus;
|
||||
|
||||
onMount(() => {
|
||||
autofocus.focus();
|
||||
});
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const { id } = await post(`/new/service.json`, { name });
|
||||
return await goto(`/services/${id}`);
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">Add New Service</div>
|
||||
</div>
|
||||
<div class="pt-10">
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<input
|
||||
name="name"
|
||||
placeholder="Service name"
|
||||
required
|
||||
bind:this={autofocus}
|
||||
bind:value={name}
|
||||
/>
|
||||
<button type="submit" class="bg-pink-600 hover:bg-pink-500">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
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>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
define('WP_ALLOW_MULTISITE', true);
|
||||
define('MULTISITE', true);
|
||||
define('SUBDOMAIN_INSTALL', false);`
|
||||
: null}>{service.wordpress.extraConfig || 'N/A'}</textarea
|
||||
: 'N/A'}>{service.wordpress.extraConfig}</textarea
|
||||
>
|
||||
</div>
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
|
||||
@@ -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: {
|
||||
@@ -57,13 +58,13 @@
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { session } from '$app/stores';
|
||||
import { page, session } from '$app/stores';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
import { del, post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
const { id } = $page.params;
|
||||
|
||||
export let service;
|
||||
export let isRunning;
|
||||
@@ -110,23 +111,6 @@
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
// onMount(async () => {
|
||||
// if (
|
||||
// service.type &&
|
||||
// service.destinationDockerId &&
|
||||
// service.version &&
|
||||
// service.fqdn &&
|
||||
// !isRunning
|
||||
// ) {
|
||||
// try {
|
||||
// await post(`/services/${service.id}/${service.type}/stop.json`, {});
|
||||
// } catch ({ error }) {
|
||||
// return errorNotification(error);
|
||||
// } finally {
|
||||
// loading = false;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
</script>
|
||||
|
||||
<nav class="nav-side">
|
||||
@@ -185,6 +169,76 @@
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
<div class="border border-stone-700 h-8" />
|
||||
{/if}
|
||||
{#if service.type && service.destinationDockerId && service.version}
|
||||
<a
|
||||
href="/services/{id}"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-yellow-500 rounded"
|
||||
class:text-yellow-500={$page.url.pathname === `/services/${id}`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/services/${id}`}
|
||||
>
|
||||
<button
|
||||
title="Configurations"
|
||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
||||
data-tooltip="Configurations"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<rect x="4" y="8" width="4" height="4" />
|
||||
<line x1="6" y1="4" x2="6" y2="8" />
|
||||
<line x1="6" y1="12" x2="6" y2="20" />
|
||||
<rect x="10" y="14" width="4" height="4" />
|
||||
<line x1="12" y1="4" x2="12" y2="14" />
|
||||
<line x1="12" y1="18" x2="12" y2="20" />
|
||||
<rect x="16" y="5" width="4" height="4" />
|
||||
<line x1="18" y1="4" x2="18" y2="5" />
|
||||
<line x1="18" y1="9" x2="18" y2="20" />
|
||||
</svg></button
|
||||
></a
|
||||
>
|
||||
<a
|
||||
href="/services/{id}/secrets"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-pink-500 rounded"
|
||||
class:text-pink-500={$page.url.pathname === `/services/${id}/secrets`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/secrets`}
|
||||
>
|
||||
<button
|
||||
title="Secrets"
|
||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
||||
data-tooltip="Secrets"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
|
||||
/>
|
||||
<circle cx="12" cy="11" r="1" />
|
||||
<line x1="12" y1="12" x2="12" y2="14.5" />
|
||||
</svg></button
|
||||
></a
|
||||
>
|
||||
<div class="border border-stone-700 h-8" />
|
||||
{/if}
|
||||
<button
|
||||
on:click={deleteService}
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
import { goto } from '$app/navigation';
|
||||
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');
|
||||
@@ -74,6 +78,14 @@
|
||||
<Wordpress isAbsolute />
|
||||
{:else if type.name === 'vaultwarden'}
|
||||
<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}`
|
||||
|
||||
@@ -35,10 +35,13 @@
|
||||
import VsCodeServer from '$lib/components/svg/services/VSCodeServer.svelte';
|
||||
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
|
||||
import Services from './_Services/_Services.svelte';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
|
||||
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;
|
||||
@@ -105,6 +108,22 @@
|
||||
<a href="https://github.com/dani-garcia/vaultwarden" target="_blank">
|
||||
<VaultWarden />
|
||||
</a>
|
||||
{:else if service.type === 'languagetool'}
|
||||
<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>
|
||||
|
||||
@@ -4,25 +4,17 @@ import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
let { name, fqdn, port, buildCommand, startCommand, installCommand } = await event.request.json();
|
||||
const { id } = event.params;
|
||||
|
||||
let { name, fqdn } = await event.request.json();
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (port) port = Number(port);
|
||||
|
||||
try {
|
||||
const { id } = await db.importApplication({
|
||||
name,
|
||||
teamId,
|
||||
fqdn,
|
||||
port,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
installCommand
|
||||
});
|
||||
return { status: 201, body: { id } };
|
||||
await db.updateLanguageToolService({ id, fqdn, name });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
78
src/routes/services/[id]/languagetool/start.json.ts
Normal file
78
src/routes/services/[id]/languagetool/start.json.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
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}-ngrams:/ngrams`,
|
||||
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],
|
||||
environment: config.environmentVariables,
|
||||
restart: 'always',
|
||||
volumes: [config.volume],
|
||||
labels: makeLabelForServices('languagetool')
|
||||
}
|
||||
},
|
||||
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]/languagetool/stop.json.ts
Normal file
35
src/routes/services/[id]/languagetool/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);
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { startHttpProxy } from '$lib/haproxy';
|
||||
import getPort, { portNumbers } from 'get-port';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@@ -23,7 +23,8 @@ export const post: RequestHandler = async (event) => {
|
||||
fqdn,
|
||||
destinationDockerId,
|
||||
destinationDocker,
|
||||
minio: { rootUser, rootUserPassword }
|
||||
minio: { rootUser, rootUserPassword },
|
||||
serviceSecret
|
||||
} = service;
|
||||
|
||||
const data = await db.prisma.setting.findFirst();
|
||||
@@ -38,9 +39,10 @@ export const post: RequestHandler = async (event) => {
|
||||
const apiPort = 9000;
|
||||
|
||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||
const image = getServiceImage(type);
|
||||
|
||||
const config = {
|
||||
image: `minio/minio:${version}`,
|
||||
image: `${image}:${version}`,
|
||||
volume: `${id}-minio-data:/data`,
|
||||
environmentVariables: {
|
||||
MINIO_ROOT_USER: rootUser,
|
||||
@@ -48,12 +50,17 @@ export const post: RequestHandler = async (event) => {
|
||||
MINIO_BROWSER_REDIRECT_URL: fqdn
|
||||
}
|
||||
};
|
||||
if (serviceSecret.length > 0) {
|
||||
serviceSecret.forEach((secret) => {
|
||||
config.environmentVariables[secret.name] = secret.value;
|
||||
});
|
||||
}
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[id]: {
|
||||
container_name: id,
|
||||
image: `minio/minio:${version}`,
|
||||
image: config.image,
|
||||
command: `server /data --console-address ":${consolePort}"`,
|
||||
environment: config.environmentVariables,
|
||||
networks: [network],
|
||||
@@ -69,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);
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 } from '$lib/database';
|
||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@@ -14,19 +14,30 @@ export const post: RequestHandler = async (event) => {
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { type, version, destinationDockerId, destinationDocker } = service;
|
||||
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}`,
|
||||
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: `nocodb/nocodb:${version}`,
|
||||
image: config.image,
|
||||
networks: [network],
|
||||
environment: config.environmentVariables,
|
||||
restart: 'always',
|
||||
labels: makeLabelForServices('nocodb')
|
||||
}
|
||||
@@ -41,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
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 } from '$lib/database';
|
||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@@ -20,6 +20,7 @@ export const post: RequestHandler = async (event) => {
|
||||
fqdn,
|
||||
destinationDockerId,
|
||||
destinationDocker,
|
||||
serviceSecret,
|
||||
plausibleAnalytics: {
|
||||
id: plausibleDbId,
|
||||
username,
|
||||
@@ -31,10 +32,11 @@ export const post: RequestHandler = async (event) => {
|
||||
secretKeyBase
|
||||
}
|
||||
} = service;
|
||||
const image = getServiceImage(type);
|
||||
|
||||
const config = {
|
||||
plausibleAnalytics: {
|
||||
image: `plausible/analytics:${version}`,
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
ADMIN_USER_EMAIL: email,
|
||||
ADMIN_USER_NAME: username,
|
||||
@@ -68,6 +70,11 @@ export const post: RequestHandler = async (event) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
if (serviceSecret.length > 0) {
|
||||
serviceSecret.forEach((secret) => {
|
||||
config.plausibleAnalytics.environmentVariables[secret.name] = secret.value;
|
||||
});
|
||||
}
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
|
||||
@@ -151,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
|
||||
};
|
||||
|
||||
87
src/routes/services/[id]/secrets/_Secret.svelte
Normal file
87
src/routes/services/[id]/secrets/_Secret.svelte
Normal file
@@ -0,0 +1,87 @@
|
||||
<script>
|
||||
export let name = '';
|
||||
export let value = '';
|
||||
export let isNewSecret = false;
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { del, post } from '$lib/api';
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const { id } = $page.params;
|
||||
async function removeSecret() {
|
||||
try {
|
||||
await del(`/services/${id}/secrets.json`, { name });
|
||||
dispatch('refresh');
|
||||
if (isNewSecret) {
|
||||
name = '';
|
||||
value = '';
|
||||
}
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
async function saveSecret(isNew = false) {
|
||||
if (!name) return errorNotification('Name is required.');
|
||||
if (!value) return errorNotification('Value is required.');
|
||||
try {
|
||||
await post(`/services/${id}/secrets.json`, {
|
||||
name,
|
||||
value,
|
||||
|
||||
isNew
|
||||
});
|
||||
dispatch('refresh');
|
||||
if (isNewSecret) {
|
||||
name = '';
|
||||
value = '';
|
||||
}
|
||||
toast.push('Secret saved.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<td>
|
||||
<input
|
||||
id={isNewSecret ? 'secretName' : 'secretNameNew'}
|
||||
bind:value={name}
|
||||
required
|
||||
placeholder="EXAMPLE_VARIABLE"
|
||||
class=" border border-dashed border-coolgray-300"
|
||||
readonly={!isNewSecret}
|
||||
class:bg-transparent={!isNewSecret}
|
||||
class:cursor-not-allowed={!isNewSecret}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<CopyPasswordField
|
||||
id={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
||||
name={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
||||
isPasswordField={true}
|
||||
bind:value
|
||||
required
|
||||
placeholder="J$#@UIO%HO#$U%H"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{#if isNewSecret}
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveSecret(true)}>Add</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row justify-center space-x-2">
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="" on:click={() => saveSecret(false)}>Set</button>
|
||||
</div>
|
||||
<div class="flex justify-center items-end">
|
||||
<button class="bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
70
src/routes/services/[id]/secrets/index.json.ts
Normal file
70
src/routes/services/[id]/secrets/index.json.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { getTeam, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
try {
|
||||
const secrets = await db.listServiceSecrets(id);
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
secrets: secrets.sort((a, b) => {
|
||||
return ('' + a.name).localeCompare(b.name);
|
||||
})
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { name, value, isBuildSecret, isPRMRSecret, isNew } = await event.request.json();
|
||||
try {
|
||||
if (isNew) {
|
||||
const found = await db.isServiceSecretExists({ id, name });
|
||||
if (found) {
|
||||
throw {
|
||||
error: `Secret ${name} already exists.`
|
||||
};
|
||||
} else {
|
||||
await db.createServiceSecret({ id, name, value });
|
||||
return {
|
||||
status: 201
|
||||
};
|
||||
}
|
||||
} else {
|
||||
await db.updateServiceSecret({ id, name, value });
|
||||
return {
|
||||
status: 201
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
export const del: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { name } = await event.request.json();
|
||||
|
||||
try {
|
||||
await db.removeServiceSecret({ id, name });
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
67
src/routes/services/[id]/secrets/index.svelte
Normal file
67
src/routes/services/[id]/secrets/index.svelte
Normal file
@@ -0,0 +1,67 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, stuff }) => {
|
||||
let endpoint = `/services/${params.id}/secrets.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
service: stuff.service,
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${endpoint}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let secrets;
|
||||
export let service;
|
||||
import Secret from './_Secret.svelte';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { page } from '$app/stores';
|
||||
import { get } from '$lib/api';
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
async function refreshSecrets() {
|
||||
const data = await get(`/services/${id}/secrets.json`);
|
||||
secrets = [...data.secrets];
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
Secrets {#if service.fqdn}
|
||||
<a href={service.fqdn} target="_blank">{getDomain(service.fqdn)}</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||
<table class="mx-auto border-separate text-left">
|
||||
<thead>
|
||||
<tr class="h-12">
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Value</th>
|
||||
<th scope="col" class="w-96 text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each secrets as secret}
|
||||
{#key secret.id}
|
||||
<tr>
|
||||
<Secret name={secret.name} value={secret.value} on:refresh={refreshSecrets} />
|
||||
</tr>
|
||||
{/key}
|
||||
{/each}
|
||||
<tr>
|
||||
<Secret isNewSecret on:refresh={refreshSecrets} />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
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);
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user