mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 12:33:35 +00:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31fdbdf8c9 | ||
|
|
2741d0ab2a | ||
|
|
79b0187b58 | ||
|
|
bf5659d0e2 | ||
|
|
f6314cab69 | ||
|
|
4f5fe3d383 | ||
|
|
1c720d587c | ||
|
|
c46dc99224 | ||
|
|
5e43d4f20d | ||
|
|
359434bfd3 | ||
|
|
e755a2d4ec | ||
|
|
0b416cd03e | ||
|
|
857e0f251b | ||
|
|
f040c7c742 | ||
|
|
2e82c9d312 | ||
|
|
126923c33e | ||
|
|
26528d8bec | ||
|
|
f99da111f7 | ||
|
|
8c30472472 | ||
|
|
11131ebe06 | ||
|
|
8dd80589d6 | ||
|
|
51e27146f3 | ||
|
|
70717dcbe5 | ||
|
|
51cba32d8d | ||
|
|
b9076714cf | ||
|
|
b76caabd32 | ||
|
|
0922fd66a4 | ||
|
|
4e7e9b2cfc | ||
|
|
0c24134ac2 | ||
|
|
f96e418dd6 | ||
|
|
1627415cca | ||
|
|
d047c91399 | ||
|
|
8bec5550cf | ||
|
|
7a61ade4a0 | ||
|
|
0239be69e6 | ||
|
|
883bdc2879 | ||
|
|
c3457a4c8a | ||
|
|
d93506a18c | ||
|
|
0bb77a671b | ||
|
|
028f883499 | ||
|
|
727133e28b | ||
|
|
1bd08cb2db | ||
|
|
2962aa6166 | ||
|
|
bac55cd90d | ||
|
|
9b51936131 | ||
|
|
692665d0da | ||
|
|
d80f760c92 | ||
|
|
ce2c887469 | ||
|
|
4908463722 | ||
|
|
26d0ef9ac9 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
.pnpm-store
|
||||||
build
|
build
|
||||||
.svelte-kit
|
.svelte-kit
|
||||||
package
|
package
|
||||||
|
|||||||
4
.gitpod.Dockerfile
vendored
4
.gitpod.Dockerfile
vendored
@@ -1,2 +1,2 @@
|
|||||||
FROM gitpod/workspace-node:2022-06-20-19-54-55
|
FROM gitpod/workspace-full:2022-08-17-18-37-55
|
||||||
RUN (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack)
|
RUN brew install buildpacks/tap/pack
|
||||||
10
.gitpod.yml
10
.gitpod.yml
@@ -1,11 +1,11 @@
|
|||||||
# This configuration file was automatically generated by Gitpod.
|
# This configuration file was automatically generated by Gitpod.
|
||||||
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
||||||
# and commit this file to your remote git repository to share the goodness with others.
|
# and commit this file to your remote git repository to share the goodness with others.
|
||||||
image:
|
#image:
|
||||||
file: .gitpod.Dockerfile
|
# file: .gitpod.Dockerfile
|
||||||
tasks:
|
#tasks:
|
||||||
- init: pnpm install && pnpm db:push && pnpm db:seed
|
# - init: pnpm install && pnpm db:push && pnpm db:seed
|
||||||
command: pnpm dev
|
# command: pnpm dev
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- port: 3001
|
- port: 3001
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ RUN mkdir -p ~/.docker/cli-plugins/
|
|||||||
# https://download.docker.com/linux/static/stable/
|
# https://download.docker.com/linux/static/stable/
|
||||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-20.10.9 -o /usr/bin/docker
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-20.10.9 -o /usr/bin/docker
|
||||||
# https://github.com/docker/compose/releases
|
# https://github.com/docker/compose/releases
|
||||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.7.0 -o ~/.docker/cli-plugins/docker-compose
|
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.6.1 -o ~/.docker/cli-plugins/docker-compose
|
||||||
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
|
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
|
||||||
|
|
||||||
RUN (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack)
|
RUN (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack)
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ Deploy your resource to:
|
|||||||
- [Umami](https://github.com/mikecao/umami)
|
- [Umami](https://github.com/mikecao/umami)
|
||||||
- [Fider](https://fider.io)
|
- [Fider](https://fider.io)
|
||||||
- [Hasura](https://hasura.io)
|
- [Hasura](https://hasura.io)
|
||||||
|
- [GlitchTip](https://glitchtip.com)
|
||||||
|
|
||||||
## Migration from v1
|
## Migration from v1
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@breejs/ts-worker": "2.0.0",
|
"@breejs/ts-worker": "2.0.0",
|
||||||
"@fastify/autoload": "5.2.0",
|
"@fastify/autoload": "5.2.0",
|
||||||
"@fastify/cookie": "7.3.1",
|
"@fastify/cookie": "8.0.0",
|
||||||
"@fastify/cors": "8.1.0",
|
"@fastify/cors": "8.1.0",
|
||||||
"@fastify/env": "4.1.0",
|
"@fastify/env": "4.1.0",
|
||||||
"@fastify/jwt": "6.3.2",
|
"@fastify/jwt": "6.3.2",
|
||||||
@@ -29,13 +29,13 @@
|
|||||||
"cabin": "9.1.2",
|
"cabin": "9.1.2",
|
||||||
"compare-versions": "4.1.3",
|
"compare-versions": "4.1.3",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
"dayjs": "1.11.4",
|
"dayjs": "1.11.5",
|
||||||
"dockerode": "3.3.3",
|
"dockerode": "3.3.4",
|
||||||
"dotenv-extended": "2.9.0",
|
"dotenv-extended": "2.9.0",
|
||||||
"fastify": "4.4.0",
|
"execa": "6.1.0",
|
||||||
"fastify-plugin": "4.1.0",
|
"fastify": "4.5.2",
|
||||||
|
"fastify-plugin": "4.2.0",
|
||||||
"generate-password": "1.7.0",
|
"generate-password": "1.7.0",
|
||||||
"get-port": "6.1.2",
|
|
||||||
"got": "12.3.1",
|
"got": "12.3.1",
|
||||||
"is-ip": "5.0.0",
|
"is-ip": "5.0.0",
|
||||||
"is-port-reachable": "4.0.0",
|
"is-port-reachable": "4.0.0",
|
||||||
@@ -50,12 +50,12 @@
|
|||||||
"unique-names-generator": "4.7.1"
|
"unique-names-generator": "4.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "18.6.5",
|
"@types/node": "18.7.11",
|
||||||
"@types/node-os-utils": "1.3.0",
|
"@types/node-os-utils": "1.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "5.33.0",
|
"@typescript-eslint/eslint-plugin": "5.34.0",
|
||||||
"@typescript-eslint/parser": "5.33.0",
|
"@typescript-eslint/parser": "5.34.0",
|
||||||
"esbuild": "0.15.0",
|
"esbuild": "0.15.5",
|
||||||
"eslint": "8.21.0",
|
"eslint": "8.22.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"nodemon": "2.0.19",
|
"nodemon": "2.0.19",
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "GlitchTip" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"postgresqlUser" TEXT NOT NULL,
|
||||||
|
"postgresqlPassword" TEXT NOT NULL,
|
||||||
|
"postgresqlDatabase" TEXT NOT NULL,
|
||||||
|
"postgresqlPublicPort" INTEGER,
|
||||||
|
"secretKeyBase" TEXT,
|
||||||
|
"defaultEmail" TEXT NOT NULL,
|
||||||
|
"defaultUsername" TEXT NOT NULL,
|
||||||
|
"defaultPassword" TEXT NOT NULL,
|
||||||
|
"defaultEmailFrom" TEXT NOT NULL DEFAULT 'glitchtip@domain.tdl',
|
||||||
|
"emailSmtpHost" TEXT DEFAULT 'domain.tdl',
|
||||||
|
"emailSmtpPort" INTEGER DEFAULT 25,
|
||||||
|
"emailSmtpUser" TEXT,
|
||||||
|
"emailSmtpPassword" TEXT,
|
||||||
|
"emailSmtpUseTls" BOOLEAN DEFAULT false,
|
||||||
|
"emailSmtpUseSsl" BOOLEAN DEFAULT false,
|
||||||
|
"emailBackend" TEXT,
|
||||||
|
"mailgunApiKey" TEXT,
|
||||||
|
"sendgridApiKey" TEXT,
|
||||||
|
"enableOpenUserRegistration" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "GlitchTip_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "GlitchTip_serviceId_key" ON "GlitchTip"("serviceId");
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
-- 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,
|
||||||
|
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"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", "autodeploy", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "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 "Setting" ADD COLUMN "DNSServers" TEXT;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_GitSource" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"forPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"type" TEXT,
|
||||||
|
"apiUrl" TEXT,
|
||||||
|
"htmlUrl" TEXT,
|
||||||
|
"customPort" INTEGER NOT NULL DEFAULT 22,
|
||||||
|
"organization" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"githubAppId" TEXT,
|
||||||
|
"gitlabAppId" TEXT,
|
||||||
|
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt" FROM "GitSource";
|
||||||
|
DROP TABLE "GitSource";
|
||||||
|
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
|
||||||
|
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
|
||||||
|
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
|
||||||
|
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,
|
||||||
|
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"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", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "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,13 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Searxng" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"secretKey" TEXT NOT NULL,
|
||||||
|
"redisPassword" TEXT NOT NULL,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Searxng_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Searxng_serviceId_key" ON "Searxng"("serviceId");
|
||||||
@@ -20,6 +20,7 @@ model Setting {
|
|||||||
proxyHash String?
|
proxyHash String?
|
||||||
isAutoUpdateEnabled Boolean @default(false)
|
isAutoUpdateEnabled Boolean @default(false)
|
||||||
isDNSCheckEnabled Boolean @default(true)
|
isDNSCheckEnabled Boolean @default(true)
|
||||||
|
DNSServers String?
|
||||||
isTraefikUsed Boolean @default(true)
|
isTraefikUsed Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@@ -118,15 +119,17 @@ model Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model ApplicationSettings {
|
model ApplicationSettings {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
applicationId String @unique
|
applicationId String @unique
|
||||||
dualCerts Boolean @default(false)
|
dualCerts Boolean @default(false)
|
||||||
debug Boolean @default(false)
|
debug Boolean @default(false)
|
||||||
previews Boolean @default(false)
|
previews Boolean @default(false)
|
||||||
autodeploy Boolean @default(true)
|
autodeploy Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
isBot Boolean @default(false)
|
||||||
updatedAt DateTime @updatedAt
|
isPublicRepository Boolean @default(false)
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model ApplicationPersistentStorage {
|
model ApplicationPersistentStorage {
|
||||||
@@ -236,6 +239,7 @@ model SshKey {
|
|||||||
model GitSource {
|
model GitSource {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
|
forPublic Boolean @default(false)
|
||||||
type String?
|
type String?
|
||||||
apiUrl String?
|
apiUrl String?
|
||||||
htmlUrl String?
|
htmlUrl String?
|
||||||
@@ -312,33 +316,34 @@ model DatabaseSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Service {
|
model Service {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
fqdn String?
|
fqdn String?
|
||||||
exposePort Int?
|
exposePort Int?
|
||||||
dualCerts Boolean @default(false)
|
dualCerts Boolean @default(false)
|
||||||
type String?
|
type String?
|
||||||
version String?
|
version String?
|
||||||
destinationDockerId String?
|
destinationDockerId String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
|
persistentStorage ServicePersistentStorage[]
|
||||||
|
serviceSecret ServiceSecret[]
|
||||||
|
teams Team[]
|
||||||
|
|
||||||
fider Fider?
|
fider Fider?
|
||||||
ghost Ghost?
|
ghost Ghost?
|
||||||
|
glitchTip GlitchTip?
|
||||||
hasura Hasura?
|
hasura Hasura?
|
||||||
meiliSearch MeiliSearch?
|
meiliSearch MeiliSearch?
|
||||||
minio Minio?
|
minio Minio?
|
||||||
moodle Moodle?
|
moodle Moodle?
|
||||||
plausibleAnalytics PlausibleAnalytics?
|
plausibleAnalytics PlausibleAnalytics?
|
||||||
persistentStorage ServicePersistentStorage[]
|
|
||||||
serviceSecret ServiceSecret[]
|
|
||||||
umami Umami?
|
umami Umami?
|
||||||
vscodeserver Vscodeserver?
|
vscodeserver Vscodeserver?
|
||||||
wordpress Wordpress?
|
wordpress Wordpress?
|
||||||
appwrite Appwrite?
|
appwrite Appwrite?
|
||||||
|
searxng Searxng?
|
||||||
teams Team[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model PlausibleAnalytics {
|
model PlausibleAnalytics {
|
||||||
@@ -513,3 +518,40 @@ model Appwrite {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model GlitchTip {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
postgresqlUser String
|
||||||
|
postgresqlPassword String
|
||||||
|
postgresqlDatabase String
|
||||||
|
postgresqlPublicPort Int?
|
||||||
|
secretKeyBase String?
|
||||||
|
defaultEmail String
|
||||||
|
defaultUsername String
|
||||||
|
defaultPassword String
|
||||||
|
defaultEmailFrom String @default("glitchtip@domain.tdl")
|
||||||
|
emailSmtpHost String? @default("domain.tdl")
|
||||||
|
emailSmtpPort Int? @default(25)
|
||||||
|
emailSmtpUser String?
|
||||||
|
emailSmtpPassword String?
|
||||||
|
emailSmtpUseTls Boolean? @default(false)
|
||||||
|
emailSmtpUseSsl Boolean? @default(false)
|
||||||
|
emailBackend String?
|
||||||
|
mailgunApiKey String?
|
||||||
|
sendgridApiKey String?
|
||||||
|
enableOpenUserRegistration Boolean @default(true)
|
||||||
|
serviceId String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Searxng {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
secretKey String
|
||||||
|
redisPassword String
|
||||||
|
serviceId String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,6 +66,34 @@ async function main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const github = await prisma.gitSource.findFirst({
|
||||||
|
where: { htmlUrl: 'https://github.com', forPublic: true }
|
||||||
|
});
|
||||||
|
const gitlab = await prisma.gitSource.findFirst({
|
||||||
|
where: { htmlUrl: 'https://gitlab.com', forPublic: true }
|
||||||
|
});
|
||||||
|
if (!github) {
|
||||||
|
await prisma.gitSource.create({
|
||||||
|
data: {
|
||||||
|
apiUrl: 'https://api.github.com',
|
||||||
|
htmlUrl: 'https://github.com',
|
||||||
|
forPublic: true,
|
||||||
|
name: 'Github Public',
|
||||||
|
type: 'github'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!gitlab) {
|
||||||
|
await prisma.gitSource.create({
|
||||||
|
data: {
|
||||||
|
apiUrl: 'https://gitlab.com/api/v4',
|
||||||
|
htmlUrl: 'https://gitlab.com',
|
||||||
|
forPublic: true,
|
||||||
|
name: 'Gitlab Public',
|
||||||
|
type: 'gitlab'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
main()
|
main()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import env from '@fastify/env';
|
|||||||
import cookie from '@fastify/cookie';
|
import cookie from '@fastify/cookie';
|
||||||
import path, { join } from 'path';
|
import path, { join } from 'path';
|
||||||
import autoLoad from '@fastify/autoload';
|
import autoLoad from '@fastify/autoload';
|
||||||
import { asyncExecShell, isDev, listSettings, prisma } from './lib/common';
|
import { asyncExecShell, isDev, listSettings, prisma, version } from './lib/common';
|
||||||
import { scheduler } from './lib/scheduler';
|
import { scheduler } from './lib/scheduler';
|
||||||
|
import axios from 'axios';
|
||||||
|
import compareVersions from 'compare-versions';
|
||||||
|
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
@@ -113,8 +115,22 @@ fastify.listen({ port, host }, async (err: any, address: any) => {
|
|||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||||
if (isAutoUpdateEnabled) {
|
if (isAutoUpdateEnabled) {
|
||||||
if (scheduler.workers.has('deployApplication')) {
|
const currentVersion = version;
|
||||||
scheduler.workers.get('deployApplication').postMessage("status:autoUpdater");
|
const { data: versions } = await axios
|
||||||
|
.get(
|
||||||
|
`https://get.coollabs.io/versions.json`
|
||||||
|
, {
|
||||||
|
params: {
|
||||||
|
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||||
|
version: currentVersion
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const latestVersion = versions['coolify'].main.version;
|
||||||
|
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||||
|
if (isUpdateAvailable === 1) {
|
||||||
|
if (scheduler.workers.has('deployApplication')) {
|
||||||
|
scheduler.workers.get('deployApplication').postMessage("status:autoUpdater");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, isDev ? 5000 : 60000 * 15)
|
}, isDev ? 5000 : 60000 * 15)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import fs from 'fs/promises';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
|
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
|
||||||
import { createDirectories, decrypt, executeDockerCmd, getDomain, prisma } from '../lib/common';
|
import { createDirectories, decrypt, defaultComposeConfiguration, executeDockerCmd, getDomain, prisma } from '../lib/common';
|
||||||
import * as importers from '../lib/importers';
|
import * as importers from '../lib/importers';
|
||||||
import * as buildpacks from '../lib/buildPacks';
|
import * as buildpacks from '../lib/buildPacks';
|
||||||
|
|
||||||
@@ -56,6 +56,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
baseImage,
|
baseImage,
|
||||||
baseBuildImage,
|
baseBuildImage,
|
||||||
deploymentType,
|
deploymentType,
|
||||||
|
forceRebuild
|
||||||
} = message
|
} = message
|
||||||
let {
|
let {
|
||||||
branch,
|
branch,
|
||||||
@@ -69,6 +70,30 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
dockerFileLocation,
|
dockerFileLocation,
|
||||||
denoMainFile
|
denoMainFile
|
||||||
} = message
|
} = message
|
||||||
|
const currentHash = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(
|
||||||
|
JSON.stringify({
|
||||||
|
pythonWSGI,
|
||||||
|
pythonModule,
|
||||||
|
pythonVariable,
|
||||||
|
deploymentType,
|
||||||
|
denoOptions,
|
||||||
|
baseImage,
|
||||||
|
baseBuildImage,
|
||||||
|
buildPack,
|
||||||
|
port,
|
||||||
|
exposePort,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
secrets,
|
||||||
|
branch,
|
||||||
|
repository,
|
||||||
|
fqdn
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.digest('hex');
|
||||||
try {
|
try {
|
||||||
const { debug } = settings;
|
const { debug } = settings;
|
||||||
if (concurrency === 1) {
|
if (concurrency === 1) {
|
||||||
@@ -131,7 +156,8 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
htmlUrl: gitSource.htmlUrl,
|
htmlUrl: gitSource.htmlUrl,
|
||||||
projectId,
|
projectId,
|
||||||
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
|
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
|
||||||
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null
|
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null,
|
||||||
|
forPublic: gitSource.forPublic
|
||||||
});
|
});
|
||||||
if (!commit) {
|
if (!commit) {
|
||||||
throw new Error('No commit found?');
|
throw new Error('No commit found?');
|
||||||
@@ -146,38 +172,10 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pullmergeRequestId) {
|
if (!pullmergeRequestId) {
|
||||||
const currentHash = crypto
|
|
||||||
//@ts-ignore
|
|
||||||
.createHash('sha256')
|
|
||||||
.update(
|
|
||||||
JSON.stringify({
|
|
||||||
pythonWSGI,
|
|
||||||
pythonModule,
|
|
||||||
pythonVariable,
|
|
||||||
deploymentType,
|
|
||||||
denoOptions,
|
|
||||||
baseImage,
|
|
||||||
baseBuildImage,
|
|
||||||
buildPack,
|
|
||||||
port,
|
|
||||||
exposePort,
|
|
||||||
installCommand,
|
|
||||||
buildCommand,
|
|
||||||
startCommand,
|
|
||||||
secrets,
|
|
||||||
branch,
|
|
||||||
repository,
|
|
||||||
fqdn
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.digest('hex');
|
|
||||||
|
|
||||||
if (configHash !== currentHash) {
|
if (configHash !== currentHash) {
|
||||||
await prisma.application.update({
|
|
||||||
where: { id: applicationId },
|
|
||||||
data: { configHash: currentHash }
|
|
||||||
});
|
|
||||||
deployNeeded = true;
|
deployNeeded = true;
|
||||||
if (configHash) {
|
if (configHash) {
|
||||||
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
|
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
|
||||||
@@ -200,8 +198,10 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
||||||
|
|
||||||
|
if (forceRebuild) deployNeeded = true
|
||||||
if (!imageFound || deployNeeded) {
|
if (!imageFound || deployNeeded) {
|
||||||
// if (true) {
|
// if (true) {
|
||||||
if (buildpacks[buildPack])
|
if (buildpacks[buildPack])
|
||||||
await buildpacks[buildPack]({
|
await buildpacks[buildPack]({
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
@@ -250,16 +250,18 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
const envs = [];
|
const envs = [
|
||||||
|
`PORT=${port}`
|
||||||
|
];
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
if (secret.isPRMRSecret) {
|
if (secret.isPRMRSecret) {
|
||||||
envs.push(`${secret.name}='${secret.value}'`);
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
envs.push(`${secret.name}='${secret.value}'`);
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -298,7 +300,6 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
console.log({port})
|
|
||||||
const composeFile = {
|
const composeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
@@ -307,23 +308,14 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
container_name: imageId,
|
container_name: imageId,
|
||||||
volumes,
|
volumes,
|
||||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||||
networks: [destinationDocker.network],
|
|
||||||
labels,
|
labels,
|
||||||
depends_on: [],
|
depends_on: [],
|
||||||
restart: 'always',
|
|
||||||
expose: [port],
|
expose: [port],
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
// logging: {
|
// logging: {
|
||||||
// driver: 'fluentd',
|
// driver: 'fluentd',
|
||||||
// },
|
// },
|
||||||
deploy: {
|
...defaultComposeConfiguration(destinationDocker.network),
|
||||||
restart_policy: {
|
|
||||||
condition: 'on-failure',
|
|
||||||
delay: '5s',
|
|
||||||
max_attempts: 3,
|
|
||||||
window: '120s'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -346,6 +338,10 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
}
|
}
|
||||||
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||||
await prisma.build.update({ where: { id: message.build_id }, data: { status: 'success' } });
|
await prisma.build.update({ where: { id: message.build_id }, data: { status: 'success' } });
|
||||||
|
if (!pullmergeRequestId) await prisma.application.update({
|
||||||
|
where: { id: applicationId },
|
||||||
|
data: { configHash: currentHash }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -541,9 +541,6 @@ export async function buildImage({
|
|||||||
} else {
|
} else {
|
||||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
||||||
}
|
}
|
||||||
if (debug) {
|
|
||||||
await saveBuildLog({ line: `\n###############\nIMPORTANT: Due to some issues during implementing Remote Docker Engine, the builds logs are not streamed at the moment - but will be soon! You will see the full build log when the build is finished!\n###############`, buildId, applicationId });
|
|
||||||
}
|
|
||||||
if (!debug && isCache) {
|
if (!debug && isCache) {
|
||||||
await saveBuildLog({
|
await saveBuildLog({
|
||||||
line: `Debug turned off. To see more details, allow it in the configuration.`,
|
line: `Debug turned off. To see more details, allow it in the configuration.`,
|
||||||
@@ -553,54 +550,7 @@ export async function buildImage({
|
|||||||
}
|
}
|
||||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
||||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||||
const { stderr } = await executeDockerCmd({ dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` })
|
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` })
|
||||||
if (debug) {
|
|
||||||
const array = stderr.split('\n')
|
|
||||||
for (const line of array) {
|
|
||||||
if (line !== '\n') {
|
|
||||||
await saveBuildLog({
|
|
||||||
line: `${line.replace('\n', '')}`,
|
|
||||||
buildId,
|
|
||||||
applicationId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// await new Promise((resolve, reject) => {
|
|
||||||
// const command = spawn(`docker`, ['build', '-f', `${workdir}${dockerFile}`, '-t', `${cache}`,`${workdir}`], {
|
|
||||||
// env: {
|
|
||||||
// DOCKER_HOST: 'ssh://root@95.217.178.202',
|
|
||||||
// DOCKER_BUILDKIT: '1'
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// command.stdout.on('data', function (data) {
|
|
||||||
// console.log('stdout: ' + data);
|
|
||||||
// });
|
|
||||||
// command.stderr.on('data', function (data) {
|
|
||||||
// console.log('stderr: ' + data);
|
|
||||||
// });
|
|
||||||
// command.on('error', function (error) {
|
|
||||||
// console.log(error)
|
|
||||||
// reject(error)
|
|
||||||
// })
|
|
||||||
// command.on('exit', function (code) {
|
|
||||||
// console.log('exit code: ' + code);
|
|
||||||
// resolve(code)
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
|
||||||
// console.log({ stdout, stderr })
|
|
||||||
// const stream = await docker.engine.buildImage(
|
|
||||||
// { src: ['.'], context: workdir },
|
|
||||||
// {
|
|
||||||
// dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation,
|
|
||||||
// t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// await streamEvents({ stream, docker, buildId, applicationId, debug });
|
|
||||||
if (isCache) {
|
if (isCache) {
|
||||||
await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import child from 'child_process';
|
import { exec } from 'node:child_process'
|
||||||
import util from 'util';
|
import util from 'util';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
@@ -16,8 +16,9 @@ import sshConfig from 'ssh-config'
|
|||||||
import { checkContainer, removeContainer } from './docker';
|
import { checkContainer, removeContainer } from './docker';
|
||||||
import { day } from './dayjs';
|
import { day } from './dayjs';
|
||||||
import * as serviceFields from './serviceFields'
|
import * as serviceFields from './serviceFields'
|
||||||
|
import { saveBuildLog } from './buildPacks/common';
|
||||||
|
|
||||||
export const version = '3.4.0';
|
export const version = '3.8.0';
|
||||||
export const isDev = process.env.NODE_ENV === 'development';
|
export const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
const algorithm = 'aes-256-ctr';
|
const algorithm = 'aes-256-ctr';
|
||||||
@@ -79,11 +80,58 @@ export const include: any = {
|
|||||||
hasura: true,
|
hasura: true,
|
||||||
fider: true,
|
fider: true,
|
||||||
moodle: true,
|
moodle: true,
|
||||||
appwrite: true
|
appwrite: true,
|
||||||
|
glitchTip: true,
|
||||||
|
searxng: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
|
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
|
||||||
export const asyncExecShell = util.promisify(child.exec);
|
export const asyncExecShell = util.promisify(exec);
|
||||||
|
export const asyncExecShellStream = async ({ debug, buildId, applicationId, command, engine }: { debug: boolean, buildId: string, applicationId: string, command: string, engine: string }) => {
|
||||||
|
return await new Promise(async (resolve, reject) => {
|
||||||
|
const { execaCommand } = await import('execa')
|
||||||
|
const subprocess = execaCommand(command, { env: { DOCKER_BUILDKIT: "1", DOCKER_HOST: engine } })
|
||||||
|
if (debug) {
|
||||||
|
await saveBuildLog({ line: `=========================`, buildId, applicationId });
|
||||||
|
subprocess.stdout.on('data', async (data) => {
|
||||||
|
const stdout = data.toString();
|
||||||
|
const array = stdout.split('\n')
|
||||||
|
for (const line of array) {
|
||||||
|
if (line !== '\n' && line !== '') {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `${line.replace('\n', '')}`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
subprocess.stderr.on('data', async (data) => {
|
||||||
|
const stderr = data.toString();
|
||||||
|
const array = stderr.split('\n')
|
||||||
|
for (const line of array) {
|
||||||
|
if (line !== '\n' && line !== '') {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `${line.replace('\n', '')}`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
subprocess.on('exit', async (code) => {
|
||||||
|
await asyncSleep(1000);
|
||||||
|
await saveBuildLog({ line: `=========================`, buildId, applicationId });
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(code)
|
||||||
|
} else {
|
||||||
|
reject(code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const asyncSleep = (delay: number): Promise<unknown> =>
|
export const asyncSleep = (delay: number): Promise<unknown> =>
|
||||||
new Promise((resolve) => setTimeout(resolve, delay));
|
new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
export const prisma = new PrismaClient({
|
export const prisma = new PrismaClient({
|
||||||
@@ -287,7 +335,7 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
ports: {
|
ports: {
|
||||||
main: 80
|
main: 80
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
// {
|
// {
|
||||||
// name: 'moodle',
|
// name: 'moodle',
|
||||||
// fancyName: 'Moodle',
|
// fancyName: 'Moodle',
|
||||||
@@ -299,6 +347,28 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
// main: 8080
|
// main: 8080
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
{
|
||||||
|
name: 'glitchTip',
|
||||||
|
fancyName: 'GlitchTip',
|
||||||
|
baseImage: 'glitchtip/glitchtip',
|
||||||
|
images: ['postgres:14-alpine', 'redis:7-alpine'],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'searxng',
|
||||||
|
fancyName: 'SearXNG',
|
||||||
|
baseImage: 'searxng/searxng',
|
||||||
|
images: [],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
|
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
|
||||||
@@ -307,6 +377,10 @@ export async function checkDoubleBranch(branch: string, projectId: number): Prom
|
|||||||
}
|
}
|
||||||
export async function isDNSValid(hostname: any, domain: string): Promise<any> {
|
export async function isDNSValid(hostname: any, domain: string): Promise<any> {
|
||||||
const { isIP } = await import('is-ip');
|
const { isIP } = await import('is-ip');
|
||||||
|
const { DNSServers } = await listSettings();
|
||||||
|
if (DNSServers) {
|
||||||
|
dns.setServers([DNSServers]);
|
||||||
|
}
|
||||||
let resolves = [];
|
let resolves = [];
|
||||||
try {
|
try {
|
||||||
if (isIP(hostname)) {
|
if (isIP(hostname)) {
|
||||||
@@ -320,7 +394,6 @@ export async function isDNSValid(hostname: any, domain: string): Promise<any> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
let ipDomainFound = false;
|
let ipDomainFound = false;
|
||||||
dns.setServers(['1.1.1.1', '8.8.8.8']);
|
|
||||||
const dnsResolve = await dns.resolve4(domain);
|
const dnsResolve = await dns.resolve4(domain);
|
||||||
if (dnsResolve.length > 0) {
|
if (dnsResolve.length > 0) {
|
||||||
for (const ip of dnsResolve) {
|
for (const ip of dnsResolve) {
|
||||||
@@ -412,7 +485,12 @@ export async function checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts }): P
|
|||||||
const { isIP } = await import('is-ip');
|
const { isIP } = await import('is-ip');
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const domainDualCert = domain.includes('www.') ? domain.replace('www.', '') : `www.${domain}`;
|
const domainDualCert = domain.includes('www.') ? domain.replace('www.', '') : `www.${domain}`;
|
||||||
dns.setServers(['1.1.1.1', '8.8.8.8']);
|
|
||||||
|
const { DNSServers } = await listSettings();
|
||||||
|
if (DNSServers) {
|
||||||
|
dns.setServers([DNSServers]);
|
||||||
|
}
|
||||||
|
|
||||||
let resolves = [];
|
let resolves = [];
|
||||||
try {
|
try {
|
||||||
if (isIP(hostname)) {
|
if (isIP(hostname)) {
|
||||||
@@ -525,21 +603,38 @@ export const supportedDatabaseTypesAndVersions = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function getFreeSSHLocalPort(id: string): Promise<number> {
|
export async function getFreeSSHLocalPort(id: string): Promise<number | boolean> {
|
||||||
const { default: getPort, portNumbers } = await import('get-port');
|
const { default: isReachable } = await import('is-port-reachable');
|
||||||
const { remoteIpAddress, sshLocalPort } = await prisma.destinationDocker.findUnique({ where: { id } })
|
const { remoteIpAddress, sshLocalPort } = await prisma.destinationDocker.findUnique({ where: { id } })
|
||||||
if (sshLocalPort) {
|
if (sshLocalPort) {
|
||||||
return Number(sshLocalPort)
|
return Number(sshLocalPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = await prisma.setting.findFirst();
|
||||||
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
const ports = await prisma.destinationDocker.findMany({ where: { sshLocalPort: { not: null }, remoteIpAddress: { not: remoteIpAddress } } })
|
const ports = await prisma.destinationDocker.findMany({ where: { sshLocalPort: { not: null }, remoteIpAddress: { not: remoteIpAddress } } })
|
||||||
const alreadyConfigured = await prisma.destinationDocker.findFirst({ where: { remoteIpAddress, id: { not: id }, sshLocalPort: { not: null } } })
|
|
||||||
|
const alreadyConfigured = await prisma.destinationDocker.findFirst({
|
||||||
|
where: {
|
||||||
|
remoteIpAddress, id: { not: id }, sshLocalPort: { not: null }
|
||||||
|
}
|
||||||
|
})
|
||||||
if (alreadyConfigured?.sshLocalPort) {
|
if (alreadyConfigured?.sshLocalPort) {
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: alreadyConfigured.sshLocalPort } })
|
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: alreadyConfigured.sshLocalPort } })
|
||||||
return Number(alreadyConfigured.sshLocalPort)
|
return Number(alreadyConfigured.sshLocalPort)
|
||||||
}
|
}
|
||||||
const availablePort = await getPort({ port: portNumbers(10000, 10100), exclude: ports.map(p => p.sshLocalPort) })
|
const range = generateRangeArray(minPort, maxPort)
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: Number(availablePort) } })
|
console.log({ ports })
|
||||||
return Number(availablePort)
|
const availablePorts = range.filter(port => !ports.map(p => p.sshLocalPort).includes(port))
|
||||||
|
for (const port of availablePorts) {
|
||||||
|
const found = await isReachable(port, { host: 'localhost' })
|
||||||
|
if (!found) {
|
||||||
|
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: Number(port) } })
|
||||||
|
return Number(port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createRemoteEngineConfiguration(id: string) {
|
export async function createRemoteEngineConfiguration(id: string) {
|
||||||
@@ -571,7 +666,7 @@ export async function createRemoteEngineConfiguration(id: string) {
|
|||||||
config.append({
|
config.append({
|
||||||
Host: remoteIpAddress,
|
Host: remoteIpAddress,
|
||||||
Hostname: 'localhost',
|
Hostname: 'localhost',
|
||||||
Port: Number(localPort),
|
Port: localPort.toString(),
|
||||||
User: remoteUser,
|
User: remoteUser,
|
||||||
IdentityFile: sshKeyFile,
|
IdentityFile: sshKeyFile,
|
||||||
StrictHostKeyChecking: 'no'
|
StrictHostKeyChecking: 'no'
|
||||||
@@ -584,7 +679,7 @@ export async function createRemoteEngineConfiguration(id: string) {
|
|||||||
}
|
}
|
||||||
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config))
|
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config))
|
||||||
}
|
}
|
||||||
export async function executeDockerCmd({ dockerId, command }: { dockerId: string, command: string }) {
|
export async function executeDockerCmd({ debug, buildId, applicationId, dockerId, command }: { debug?: boolean, buildId?: string, applicationId?: string, dockerId: string, command: string }): Promise<any> {
|
||||||
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
||||||
if (remoteEngine) {
|
if (remoteEngine) {
|
||||||
await createRemoteEngineConfiguration(dockerId)
|
await createRemoteEngineConfiguration(dockerId)
|
||||||
@@ -597,6 +692,9 @@ export async function executeDockerCmd({ dockerId, command }: { dockerId: string
|
|||||||
command = command.replace(/docker compose/gi, 'docker-compose')
|
command = command.replace(/docker compose/gi, 'docker-compose')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (command.startsWith(`docker build --progress plain`)) {
|
||||||
|
return await asyncExecShellStream({ debug, buildId, applicationId, command, engine });
|
||||||
|
}
|
||||||
return await asyncExecShell(
|
return await asyncExecShell(
|
||||||
`DOCKER_BUILDKIT=1 DOCKER_HOST="${engine}" ${command}`
|
`DOCKER_BUILDKIT=1 DOCKER_HOST="${engine}" ${command}`
|
||||||
);
|
);
|
||||||
@@ -716,13 +814,18 @@ export async function listSettings(): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function generatePassword(length = 24, symbols = false): string {
|
export function generatePassword({ length = 24, symbols = false, isHex = false }: { length?: number, symbols?: boolean, isHex?: boolean } | null): string {
|
||||||
return generator.generate({
|
if (isHex) {
|
||||||
|
return crypto.randomBytes(length).toString("hex");
|
||||||
|
}
|
||||||
|
const password = generator.generate({
|
||||||
length,
|
length,
|
||||||
numbers: true,
|
numbers: true,
|
||||||
strict: true,
|
strict: true,
|
||||||
symbols
|
symbols
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateDatabaseConfiguration(database: any, arch: string):
|
export function generateDatabaseConfiguration(database: any, arch: string):
|
||||||
@@ -1168,8 +1271,27 @@ export async function updatePasswordInDb(database, user, newPassword, isRoot) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }: { id: string, configuredPort?: number, exposePort: number, dockerId: string, remoteIpAddress?: string }) {
|
||||||
|
if (exposePort < 1024 || exposePort > 65535) {
|
||||||
|
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuredPort) {
|
||||||
|
if (configuredPort !== exposePort) {
|
||||||
|
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
||||||
|
if (availablePort.toString() !== exposePort.toString()) {
|
||||||
|
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
||||||
|
if (availablePort.toString() !== exposePort.toString()) {
|
||||||
|
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress) {
|
export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress) {
|
||||||
const { default: getPort } = await import('get-port');
|
const { default: checkPort } = await import('is-port-reachable');
|
||||||
const applicationUsed = await (
|
const applicationUsed = await (
|
||||||
await prisma.application.findMany({
|
await prisma.application.findMany({
|
||||||
where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
||||||
@@ -1183,22 +1305,23 @@ export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddre
|
|||||||
})
|
})
|
||||||
).map((a) => a.exposePort);
|
).map((a) => a.exposePort);
|
||||||
const usedPorts = [...applicationUsed, ...serviceUsed];
|
const usedPorts = [...applicationUsed, ...serviceUsed];
|
||||||
if (remoteIpAddress) {
|
if (usedPorts.includes(exposePort)) {
|
||||||
const { default: checkPort } = await import('is-port-reachable');
|
|
||||||
const found = await checkPort(exposePort, { host: remoteIpAddress });
|
|
||||||
if (!found) {
|
|
||||||
return exposePort
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return await getPort({ port: Number(exposePort), exclude: usedPorts });
|
const found = await checkPort(exposePort, { host: remoteIpAddress || 'localhost' });
|
||||||
|
if (!found) {
|
||||||
|
return exposePort
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
export function generateRangeArray(start, end) {
|
||||||
|
return Array.from({ length: (end - start) }, (v, k) => k + start);
|
||||||
|
}
|
||||||
export async function getFreePublicPort(id, dockerId) {
|
export async function getFreePublicPort(id, dockerId) {
|
||||||
const { default: getPort, portNumbers } = await import('get-port');
|
const { default: isReachable } = await import('is-port-reachable');
|
||||||
const data = await prisma.setting.findFirst();
|
const data = await prisma.setting.findFirst();
|
||||||
const { minPort, maxPort } = data;
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
const dbUsed = await (
|
const dbUsed = await (
|
||||||
await prisma.database.findMany({
|
await prisma.database.findMany({
|
||||||
where: { publicPort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
where: { publicPort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
||||||
@@ -1224,7 +1347,15 @@ export async function getFreePublicPort(id, dockerId) {
|
|||||||
})
|
})
|
||||||
).map((a) => a.publicPort);
|
).map((a) => a.publicPort);
|
||||||
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed];
|
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed];
|
||||||
return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
|
const range = generateRangeArray(minPort, maxPort)
|
||||||
|
const availablePorts = range.filter(port => !usedPorts.includes(port))
|
||||||
|
for (const port of availablePorts) {
|
||||||
|
const found = await isReachable(port, { host: 'localhost' })
|
||||||
|
if (!found) {
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startTraefikTCPProxy(
|
export async function startTraefikTCPProxy(
|
||||||
@@ -1353,11 +1484,11 @@ export async function configureServiceType({
|
|||||||
type: string;
|
type: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
if (type === 'plausibleanalytics') {
|
if (type === 'plausibleanalytics') {
|
||||||
const password = encrypt(generatePassword());
|
const password = encrypt(generatePassword({}));
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'plausibleanalytics';
|
const postgresqlDatabase = 'plausibleanalytics';
|
||||||
const secretKeyBase = encrypt(generatePassword(64));
|
const secretKeyBase = encrypt(generatePassword({ length: 64 }));
|
||||||
|
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -1381,22 +1512,22 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'minio') {
|
} else if (type === 'minio') {
|
||||||
const rootUser = cuid();
|
const rootUser = cuid();
|
||||||
const rootUserPassword = encrypt(generatePassword());
|
const rootUserPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { type, minio: { create: { rootUser, rootUserPassword } } }
|
data: { type, minio: { create: { rootUser, rootUserPassword } } }
|
||||||
});
|
});
|
||||||
} else if (type === 'vscodeserver') {
|
} else if (type === 'vscodeserver') {
|
||||||
const password = encrypt(generatePassword());
|
const password = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { type, vscodeserver: { create: { password } } }
|
data: { type, vscodeserver: { create: { password } } }
|
||||||
});
|
});
|
||||||
} else if (type === 'wordpress') {
|
} else if (type === 'wordpress') {
|
||||||
const mysqlUser = cuid();
|
const mysqlUser = cuid();
|
||||||
const mysqlPassword = encrypt(generatePassword());
|
const mysqlPassword = encrypt(generatePassword({}));
|
||||||
const mysqlRootUser = cuid();
|
const mysqlRootUser = cuid();
|
||||||
const mysqlRootUserPassword = encrypt(generatePassword());
|
const mysqlRootUserPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1434,11 +1565,11 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'ghost') {
|
} else if (type === 'ghost') {
|
||||||
const defaultEmail = `${cuid()}@example.com`;
|
const defaultEmail = `${cuid()}@example.com`;
|
||||||
const defaultPassword = encrypt(generatePassword());
|
const defaultPassword = encrypt(generatePassword({}));
|
||||||
const mariadbUser = cuid();
|
const mariadbUser = cuid();
|
||||||
const mariadbPassword = encrypt(generatePassword());
|
const mariadbPassword = encrypt(generatePassword({}));
|
||||||
const mariadbRootUser = cuid();
|
const mariadbRootUser = cuid();
|
||||||
const mariadbRootUserPassword = encrypt(generatePassword());
|
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||||
|
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -1457,7 +1588,7 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'meilisearch') {
|
} else if (type === 'meilisearch') {
|
||||||
const masterKey = encrypt(generatePassword(32));
|
const masterKey = encrypt(generatePassword({ length: 32 }));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1466,11 +1597,11 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'umami') {
|
} else if (type === 'umami') {
|
||||||
const umamiAdminPassword = encrypt(generatePassword());
|
const umamiAdminPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'umami';
|
const postgresqlDatabase = 'umami';
|
||||||
const hashSalt = encrypt(generatePassword(64));
|
const hashSalt = encrypt(generatePassword({ length: 64 }));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1488,9 +1619,9 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'hasura') {
|
} else if (type === 'hasura') {
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'hasura';
|
const postgresqlDatabase = 'hasura';
|
||||||
const graphQLAdminPassword = encrypt(generatePassword());
|
const graphQLAdminPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1507,9 +1638,9 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'fider') {
|
} else if (type === 'fider') {
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'fider';
|
const postgresqlDatabase = 'fider';
|
||||||
const jwtSecret = encrypt(generatePassword(64, true));
|
const jwtSecret = encrypt(generatePassword({ length: 64, symbols: true }));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1526,13 +1657,13 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'moodle') {
|
} else if (type === 'moodle') {
|
||||||
const defaultUsername = cuid();
|
const defaultUsername = cuid();
|
||||||
const defaultPassword = encrypt(generatePassword());
|
const defaultPassword = encrypt(generatePassword({}));
|
||||||
const defaultEmail = `${cuid()} @example.com`;
|
const defaultEmail = `${cuid()} @example.com`;
|
||||||
const mariadbUser = cuid();
|
const mariadbUser = cuid();
|
||||||
const mariadbPassword = encrypt(generatePassword());
|
const mariadbPassword = encrypt(generatePassword({}));
|
||||||
const mariadbDatabase = 'moodle_db';
|
const mariadbDatabase = 'moodle_db';
|
||||||
const mariadbRootUser = cuid();
|
const mariadbRootUser = cuid();
|
||||||
const mariadbRootUserPassword = encrypt(generatePassword());
|
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1552,15 +1683,15 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'appwrite') {
|
} else if (type === 'appwrite') {
|
||||||
const opensslKeyV1 = encrypt(generatePassword());
|
const opensslKeyV1 = encrypt(generatePassword({}));
|
||||||
const executorSecret = encrypt(generatePassword());
|
const executorSecret = encrypt(generatePassword({}));
|
||||||
const redisPassword = encrypt(generatePassword());
|
const redisPassword = encrypt(generatePassword({}));
|
||||||
const mariadbHost = `${id}-mariadb`
|
const mariadbHost = `${id}-mariadb`
|
||||||
const mariadbUser = cuid();
|
const mariadbUser = cuid();
|
||||||
const mariadbPassword = encrypt(generatePassword());
|
const mariadbPassword = encrypt(generatePassword({}));
|
||||||
const mariadbDatabase = 'appwrite';
|
const mariadbDatabase = 'appwrite';
|
||||||
const mariadbRootUser = cuid();
|
const mariadbRootUser = cuid();
|
||||||
const mariadbRootUserPassword = encrypt(generatePassword());
|
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1580,6 +1711,47 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (type === 'glitchTip') {
|
||||||
|
const defaultUsername = cuid();
|
||||||
|
const defaultEmail = `${defaultUsername}@example.com`;
|
||||||
|
const defaultPassword = encrypt(generatePassword({}));
|
||||||
|
const postgresqlUser = cuid();
|
||||||
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
|
const postgresqlDatabase = 'glitchTip';
|
||||||
|
const secretKeyBase = encrypt(generatePassword({ length: 64 }));
|
||||||
|
|
||||||
|
await prisma.service.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
type,
|
||||||
|
glitchTip: {
|
||||||
|
create: {
|
||||||
|
postgresqlDatabase,
|
||||||
|
postgresqlUser,
|
||||||
|
postgresqlPassword,
|
||||||
|
secretKeyBase,
|
||||||
|
defaultEmail,
|
||||||
|
defaultUsername,
|
||||||
|
defaultPassword,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (type === 'searxng') {
|
||||||
|
const secretKey = encrypt(generatePassword({ length: 32, isHex: true }))
|
||||||
|
const redisPassword = encrypt(generatePassword({}));
|
||||||
|
await prisma.service.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
type,
|
||||||
|
searxng: {
|
||||||
|
create: {
|
||||||
|
secretKey,
|
||||||
|
redisPassword,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -1602,8 +1774,10 @@ export async function removeService({ id }: { id: string }): Promise<void> {
|
|||||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
|
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
|
||||||
|
await prisma.glitchTip.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.moodle.deleteMany({ where: { serviceId: id } });
|
await prisma.moodle.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
||||||
|
await prisma.searxng.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.service.delete({ where: { id } });
|
await prisma.service.delete({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1703,11 +1877,11 @@ export async function stopBuild(buildId, applicationId) {
|
|||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
if (count > 100) {
|
if (count > 50) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
return reject(new Error('Build canceled'));
|
return reject(new Error('Build canceled'));
|
||||||
}
|
}
|
||||||
const { stdout: buildContainers } = await executeDockerCmd({ dockerId, command: `docker container ls--filter "label=coolify.buildId=${buildId}" --format '{{json .}}'` })
|
const { stdout: buildContainers } = await executeDockerCmd({ dockerId, command: `docker container ls --filter "label=coolify.buildId=${buildId}" --format '{{json .}}'` })
|
||||||
if (buildContainers) {
|
if (buildContainers) {
|
||||||
const containersArray = buildContainers.trim().split('\n');
|
const containersArray = buildContainers.trim().split('\n');
|
||||||
for (const container of containersArray) {
|
for (const container of containersArray) {
|
||||||
@@ -1715,14 +1889,15 @@ export async function stopBuild(buildId, applicationId) {
|
|||||||
const id = containerObj.ID;
|
const id = containerObj.ID;
|
||||||
if (!containerObj.Names.startsWith(`${applicationId} `)) {
|
if (!containerObj.Names.startsWith(`${applicationId} `)) {
|
||||||
await removeContainer({ id, dockerId });
|
await removeContainer({ id, dockerId });
|
||||||
await cleanupDB(buildId);
|
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
} catch (error) { }
|
} catch (error) { } finally {
|
||||||
|
await cleanupDB(buildId);
|
||||||
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1806,3 +1981,17 @@ export function persistentVolumes(id, persistentStorage, config) {
|
|||||||
) || {}
|
) || {}
|
||||||
return { volumes, volumeMounts }
|
return { volumes, volumeMounts }
|
||||||
}
|
}
|
||||||
|
export function defaultComposeConfiguration(network: string): any {
|
||||||
|
return {
|
||||||
|
networks: [network],
|
||||||
|
restart: 'on-failure',
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 10,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ export async function removeContainer({
|
|||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
|
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
|
||||||
console.log(id)
|
|
||||||
if (JSON.parse(stdout).Running) {
|
if (JSON.parse(stdout).Running) {
|
||||||
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
||||||
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export default async function ({
|
|||||||
htmlUrl,
|
htmlUrl,
|
||||||
branch,
|
branch,
|
||||||
buildId,
|
buildId,
|
||||||
customPort
|
customPort,
|
||||||
|
forPublic
|
||||||
}: {
|
}: {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
workdir: string;
|
workdir: string;
|
||||||
@@ -23,41 +24,55 @@ export default async function ({
|
|||||||
branch: string;
|
branch: string;
|
||||||
buildId: string;
|
buildId: string;
|
||||||
customPort: number;
|
customPort: number;
|
||||||
|
forPublic?: boolean;
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got')
|
||||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
||||||
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
||||||
|
if (forPublic) {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `Cloning ${repository}:${branch} branch.`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
await asyncExecShell(
|
||||||
|
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
|
);
|
||||||
|
|
||||||
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
} else {
|
||||||
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
|
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
||||||
const { privateKey, appId, installationId } = body
|
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
|
||||||
|
const { privateKey, appId, installationId } = body
|
||||||
|
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
||||||
|
|
||||||
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
const payload = {
|
||||||
|
iat: Math.round(new Date().getTime() / 1000),
|
||||||
const payload = {
|
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||||
iat: Math.round(new Date().getTime() / 1000),
|
iss: appId
|
||||||
exp: Math.round(new Date().getTime() / 1000 + 60),
|
};
|
||||||
iss: appId
|
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
||||||
};
|
algorithm: 'RS256'
|
||||||
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
});
|
||||||
algorithm: 'RS256'
|
const { token } = await got
|
||||||
});
|
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
|
||||||
const { token } = await got
|
headers: {
|
||||||
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
|
Authorization: `Bearer ${jwtToken}`,
|
||||||
headers: {
|
Accept: 'application/vnd.github.machine-man-preview+json'
|
||||||
Authorization: `Bearer ${jwtToken}`,
|
}
|
||||||
Accept: 'application/vnd.github.machine-man-preview+json'
|
})
|
||||||
}
|
.json();
|
||||||
})
|
await saveBuildLog({
|
||||||
.json();
|
line: `Cloning ${repository}:${branch} branch.`,
|
||||||
await saveBuildLog({
|
buildId,
|
||||||
line: `Cloning ${repository}:${branch} branch.`,
|
applicationId
|
||||||
buildId,
|
});
|
||||||
applicationId
|
await asyncExecShell(
|
||||||
});
|
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
await asyncExecShell(
|
);
|
||||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
}
|
||||||
);
|
|
||||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||||
|
|
||||||
return commit.replace('\n', '');
|
return commit.replace('\n', '');
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ const options: any = {
|
|||||||
}
|
}
|
||||||
if (message.caller === 'cleanupStorage') {
|
if (message.caller === 'cleanupStorage') {
|
||||||
if (!scheduler.workers.has('cleanupStorage')) {
|
if (!scheduler.workers.has('cleanupStorage')) {
|
||||||
await scheduler.stop('deployApplication');
|
|
||||||
await scheduler.run('cleanupStorage')
|
await scheduler.run('cleanupStorage')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -558,3 +558,133 @@ export const appwrite = [{
|
|||||||
isBoolean: false,
|
isBoolean: false,
|
||||||
isEncrypted: false
|
isEncrypted: false
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
export const glitchTip = [{
|
||||||
|
name: 'postgresqlUser',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlDatabase',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlPublicPort',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: true,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'secretKeyBase',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'defaultEmail',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'defaultUsername',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'defaultPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'defaultFromEmail',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailUrl',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailBackend',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mailgunApiKey',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sendgridApiKey',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableOpenUserRegistration',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: true,
|
||||||
|
isEncrypted: false
|
||||||
|
}]
|
||||||
|
|
||||||
|
export const searxng = [{
|
||||||
|
name: 'secretKey',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redisPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
}]
|
||||||
@@ -18,18 +18,3 @@ export async function defaultServiceConfigurations({ id, teamId }) {
|
|||||||
}
|
}
|
||||||
return { ...service, network, port, workdir, image, secrets }
|
return { ...service, network, port, workdir, image, secrets }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function defaultServiceComposeConfiguration(network: string): any {
|
|
||||||
return {
|
|
||||||
networks: [network],
|
|
||||||
restart: 'always',
|
|
||||||
deploy: {
|
|
||||||
restart_policy: {
|
|
||||||
condition: 'on-failure',
|
|
||||||
delay: '10s',
|
|
||||||
max_attempts: 10,
|
|
||||||
window: '120s'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ import axios from 'axios';
|
|||||||
import { FastifyReply } from 'fastify';
|
import { FastifyReply } from 'fastify';
|
||||||
import { day } from '../../../../lib/dayjs';
|
import { day } from '../../../../lib/dayjs';
|
||||||
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
||||||
import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
import { checkDomainsIsValidInDNS, checkDoubleBranch, checkExposedPort, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
||||||
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
|
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
|
||||||
import { scheduler } from '../../../../lib/scheduler';
|
import { scheduler } from '../../../../lib/scheduler';
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ export async function listApplications(request: FastifyRequest) {
|
|||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
const applications = await prisma.application.findMany({
|
const applications = await prisma.application.findMany({
|
||||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { teams: true, destinationDocker: true }
|
include: { teams: true, destinationDocker: true, settings: true }
|
||||||
});
|
});
|
||||||
const settings = await prisma.setting.findFirst()
|
const settings = await prisma.setting.findFirst()
|
||||||
return {
|
return {
|
||||||
@@ -90,10 +90,11 @@ export async function getApplication(request: FastifyRequest<OnlyId>) {
|
|||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
const appId = process.env['COOLIFY_APP_ID'];
|
const appId = process.env['COOLIFY_APP_ID'];
|
||||||
const application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
|
const settings = await listSettings();
|
||||||
return {
|
return {
|
||||||
application,
|
application,
|
||||||
appId
|
appId,
|
||||||
|
settings
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -237,6 +238,9 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
if (exposePort) {
|
if (exposePort) {
|
||||||
exposePort = Number(exposePort);
|
exposePort = Number(exposePort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { destinationDocker: { id: dockerId, remoteIpAddress } } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
|
if (exposePort) await checkExposedPort({ id, exposePort, dockerId, remoteIpAddress })
|
||||||
if (denoOptions) denoOptions = denoOptions.trim();
|
if (denoOptions) denoOptions = denoOptions.trim();
|
||||||
const defaultConfiguration = await setDefaultConfiguration({
|
const defaultConfiguration = await setDefaultConfiguration({
|
||||||
buildPack,
|
buildPack,
|
||||||
@@ -275,7 +279,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
export async function saveApplicationSettings(request: FastifyRequest<SaveApplicationSettings>, reply: FastifyReply) {
|
export async function saveApplicationSettings(request: FastifyRequest<SaveApplicationSettings>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { debug, previews, dualCerts, autodeploy, branch, projectId } = request.body
|
const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot } = request.body
|
||||||
const isDouble = await checkDoubleBranch(branch, projectId);
|
const isDouble = await checkDoubleBranch(branch, projectId);
|
||||||
if (isDouble && autodeploy) {
|
if (isDouble && autodeploy) {
|
||||||
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
|
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
|
||||||
@@ -283,7 +287,7 @@ export async function saveApplicationSettings(request: FastifyRequest<SaveApplic
|
|||||||
}
|
}
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
|
data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot } } },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
return reply.code(201).send();
|
return reply.code(201).send();
|
||||||
@@ -391,18 +395,7 @@ export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
|||||||
if (found) {
|
if (found) {
|
||||||
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
||||||
}
|
}
|
||||||
if (exposePort) {
|
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
|
||||||
if (exposePort < 1024 || exposePort > 65535) {
|
|
||||||
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuredPort !== exposePort) {
|
|
||||||
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
|
||||||
if (availablePort.toString() !== exposePort.toString()) {
|
|
||||||
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||||
let hostname = request.hostname.split(':')[0];
|
let hostname = request.hostname.split(':')[0];
|
||||||
if (remoteEngine) hostname = remoteIpAddress;
|
if (remoteEngine) hostname = remoteIpAddress;
|
||||||
@@ -435,7 +428,7 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
|||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const teamId = request.user?.teamId;
|
const teamId = request.user?.teamId;
|
||||||
const { pullmergeRequestId = null, branch } = request.body
|
const { pullmergeRequestId = null, branch, forceRebuild } = request.body
|
||||||
const buildId = cuid();
|
const buildId = cuid();
|
||||||
const application = await getApplicationFromDB(id, teamId);
|
const application = await getApplicationFromDB(id, teamId);
|
||||||
if (application) {
|
if (application) {
|
||||||
@@ -474,13 +467,15 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
|||||||
type: 'manual',
|
type: 'manual',
|
||||||
...application,
|
...application,
|
||||||
sourceBranch: branch,
|
sourceBranch: branch,
|
||||||
pullmergeRequestId
|
pullmergeRequestId,
|
||||||
|
forceRebuild
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
scheduler.workers.get('deployApplication').postMessage({
|
scheduler.workers.get('deployApplication').postMessage({
|
||||||
build_id: buildId,
|
build_id: buildId,
|
||||||
type: 'manual',
|
type: 'manual',
|
||||||
...application
|
...application,
|
||||||
|
forceRebuild
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -498,11 +493,20 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
|||||||
export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) {
|
export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { gitSourceId } = request.body
|
const { gitSourceId, forPublic, type } = request.body
|
||||||
await prisma.application.update({
|
if (forPublic) {
|
||||||
where: { id },
|
const publicGit = await prisma.gitSource.findFirst({ where: { type, forPublic } });
|
||||||
data: { gitSource: { connect: { id: gitSourceId } } }
|
await prisma.application.update({
|
||||||
});
|
where: { id },
|
||||||
|
data: { gitSource: { connect: { id: publicGit.id } } }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await prisma.application.update({
|
||||||
|
where: { id },
|
||||||
|
data: { gitSource: { connect: { id: gitSourceId } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -556,7 +560,7 @@ export async function checkRepository(request: FastifyRequest<CheckRepository>)
|
|||||||
export async function saveRepository(request, reply) {
|
export async function saveRepository(request, reply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
let { repository, branch, projectId, autodeploy, webhookToken } = request.body
|
let { repository, branch, projectId, autodeploy, webhookToken, isPublicRepository = false } = request.body
|
||||||
|
|
||||||
repository = repository.toLowerCase();
|
repository = repository.toLowerCase();
|
||||||
branch = branch.toLowerCase();
|
branch = branch.toLowerCase();
|
||||||
@@ -564,17 +568,19 @@ export async function saveRepository(request, reply) {
|
|||||||
if (webhookToken) {
|
if (webhookToken) {
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy } } }
|
data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy, isPublicRepository } } }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { repository, branch, projectId, settings: { update: { autodeploy } } }
|
data: { repository, branch, projectId, settings: { update: { autodeploy, isPublicRepository } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const isDouble = await checkDoubleBranch(branch, projectId);
|
if (!isPublicRepository) {
|
||||||
if (isDouble) {
|
const isDouble = await checkDoubleBranch(branch, projectId);
|
||||||
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
|
if (isDouble) {
|
||||||
|
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false, isPublicRepository } })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -606,7 +612,8 @@ export async function getBuildPack(request) {
|
|||||||
projectId: application.projectId,
|
projectId: application.projectId,
|
||||||
repository: application.repository,
|
repository: application.repository,
|
||||||
branch: application.branch,
|
branch: application.branch,
|
||||||
apiUrl: application.gitSource.apiUrl
|
apiUrl: application.gitSource.apiUrl,
|
||||||
|
isPublicRepository: application.settings.isPublicRepository
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -656,13 +663,13 @@ export async function saveSecret(request: FastifyRequest<SaveSecret>, reply: Fas
|
|||||||
if (found) {
|
if (found) {
|
||||||
throw { status: 500, message: `Secret ${name} already exists.` }
|
throw { status: 500, message: `Secret ${name} already exists.` }
|
||||||
} else {
|
} else {
|
||||||
value = encrypt(value);
|
value = encrypt(value.trim());
|
||||||
await prisma.secret.create({
|
await prisma.secret.create({
|
||||||
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
value = encrypt(value);
|
value = encrypt(value.trim());
|
||||||
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export interface SaveApplication extends OnlyId {
|
|||||||
}
|
}
|
||||||
export interface SaveApplicationSettings extends OnlyId {
|
export interface SaveApplicationSettings extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string; };
|
||||||
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; };
|
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; };
|
||||||
}
|
}
|
||||||
export interface DeleteApplication extends OnlyId {
|
export interface DeleteApplication extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string; };
|
||||||
@@ -44,13 +44,13 @@ export interface CheckDNS extends OnlyId {
|
|||||||
}
|
}
|
||||||
export interface DeployApplication {
|
export interface DeployApplication {
|
||||||
Querystring: { domain: string }
|
Querystring: { domain: string }
|
||||||
Body: { pullmergeRequestId: string | null, branch: string }
|
Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean }
|
||||||
}
|
}
|
||||||
export interface GetImages {
|
export interface GetImages {
|
||||||
Body: { buildPack: string, deploymentType: string }
|
Body: { buildPack: string, deploymentType: string }
|
||||||
}
|
}
|
||||||
export interface SaveApplicationSource extends OnlyId {
|
export interface SaveApplicationSource extends OnlyId {
|
||||||
Body: { gitSourceId: string }
|
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string }
|
||||||
}
|
}
|
||||||
export interface CheckRepository extends OnlyId {
|
export interface CheckRepository extends OnlyId {
|
||||||
Querystring: { repository: string, branch: string }
|
Querystring: { repository: string, branch: string }
|
||||||
@@ -115,7 +115,8 @@ export interface CancelDeployment {
|
|||||||
export interface DeployApplication extends OnlyId {
|
export interface DeployApplication extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
pullmergeRequestId: string | null,
|
pullmergeRequestId: string | null,
|
||||||
branch: string
|
branch: string,
|
||||||
|
forceRebuild?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ export async function newDatabase(request: FastifyRequest, reply: FastifyReply)
|
|||||||
|
|
||||||
const name = uniqueName();
|
const name = uniqueName();
|
||||||
const dbUser = cuid();
|
const dbUser = cuid();
|
||||||
const dbUserPassword = encrypt(generatePassword());
|
const dbUserPassword = encrypt(generatePassword({}));
|
||||||
const rootUser = cuid();
|
const rootUser = cuid();
|
||||||
const rootUserPassword = encrypt(generatePassword());
|
const rootUserPassword = encrypt(generatePassword({}));
|
||||||
const defaultDatabase = cuid();
|
const defaultDatabase = cuid();
|
||||||
|
|
||||||
const { id } = await prisma.database.create({
|
const { id } = await prisma.database.create({
|
||||||
@@ -433,9 +433,13 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { isPublic, appendOnly = true } = request.body;
|
const { isPublic, appendOnly = true } = request.body;
|
||||||
|
|
||||||
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
|
let publicPort = null
|
||||||
const publicPort = await getFreePublicPort(id, dockerId);
|
|
||||||
|
|
||||||
|
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
|
|
||||||
|
if (isPublic) {
|
||||||
|
publicPort = await getFreePublicPort(id, dockerId);
|
||||||
|
}
|
||||||
await prisma.database.update({
|
await prisma.database.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
|
|||||||
|
|
||||||
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
|
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
|
||||||
if (id === 'new') {
|
if (id === 'new') {
|
||||||
console.log(engine)
|
|
||||||
if (engine) {
|
if (engine) {
|
||||||
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`);
|
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`);
|
||||||
if (stdout === '') {
|
if (stdout === '') {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import axios from 'axios';
|
|||||||
import compare from 'compare-versions';
|
import compare from 'compare-versions';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, prisma, uniqueName, version } from '../../../lib/common';
|
import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, listSettings, prisma, uniqueName, version } from '../../../lib/common';
|
||||||
|
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import type { Login, Update } from '.';
|
import type { Login, Update } from '.';
|
||||||
@@ -97,7 +97,8 @@ export async function showDashboard(request: FastifyRequest) {
|
|||||||
const userId = request.user.userId;
|
const userId = request.user.userId;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const applications = await prisma.application.findMany({
|
const applications = await prisma.application.findMany({
|
||||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
|
include: { settings: true }
|
||||||
});
|
});
|
||||||
const databases = await prisma.database.findMany({
|
const databases = await prisma.database.findMany({
|
||||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
@@ -105,10 +106,12 @@ export async function showDashboard(request: FastifyRequest) {
|
|||||||
const services = await prisma.service.findMany({
|
const services = await prisma.service.findMany({
|
||||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
});
|
});
|
||||||
|
const settings = await listSettings();
|
||||||
return {
|
return {
|
||||||
applications,
|
applications,
|
||||||
databases,
|
databases,
|
||||||
services,
|
services,
|
||||||
|
settings,
|
||||||
};
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { prisma, uniqueName, asyncExecShell, getServiceImage, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions, executeDockerCmd, listSettings, getFreeExposedPort, checkDomainsIsValidInDNS, persistentVolumes, asyncSleep, isARM } from '../../../../lib/common';
|
import { prisma, uniqueName, asyncExecShell, getServiceImage, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions, executeDockerCmd, listSettings, getFreeExposedPort, checkDomainsIsValidInDNS, persistentVolumes, asyncSleep, isARM, defaultComposeConfiguration, checkExposedPort } from '../../../../lib/common';
|
||||||
import { day } from '../../../../lib/dayjs';
|
import { day } from '../../../../lib/dayjs';
|
||||||
import { checkContainer, isContainerExited, removeContainer } from '../../../../lib/docker';
|
import { checkContainer, isContainerExited, removeContainer } from '../../../../lib/docker';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
|
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
|
||||||
import { defaultServiceComposeConfiguration, defaultServiceConfigurations } from '../../../../lib/services';
|
import { defaultServiceConfigurations } from '../../../../lib/services';
|
||||||
|
|
||||||
// async function startServiceNew(request: FastifyRequest<OnlyId>) {
|
// async function startServiceNew(request: FastifyRequest<OnlyId>) {
|
||||||
// try {
|
// try {
|
||||||
@@ -378,18 +378,7 @@ export async function checkService(request: FastifyRequest<CheckService>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (exposePort) {
|
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
|
||||||
if (exposePort < 1024 || exposePort > 65535) {
|
|
||||||
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuredPort !== exposePort) {
|
|
||||||
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
|
||||||
if (availablePort.toString() !== exposePort.toString()) {
|
|
||||||
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||||
let hostname = request.hostname.split(':')[0];
|
let hostname = request.hostname.split(':')[0];
|
||||||
if (remoteEngine) hostname = remoteIpAddress;
|
if (remoteEngine) hostname = remoteIpAddress;
|
||||||
@@ -458,13 +447,13 @@ export async function saveServiceSecret(request: FastifyRequest<SaveServiceSecre
|
|||||||
if (found) {
|
if (found) {
|
||||||
throw `Secret ${name} already exists.`
|
throw `Secret ${name} already exists.`
|
||||||
} else {
|
} else {
|
||||||
value = encrypt(value);
|
value = encrypt(value.trim());
|
||||||
await prisma.serviceSecret.create({
|
await prisma.serviceSecret.create({
|
||||||
data: { name, value, service: { connect: { id } } }
|
data: { name, value, service: { connect: { id } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
value = encrypt(value);
|
value = encrypt(value.trim());
|
||||||
const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
|
const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
@@ -591,6 +580,12 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
if (type === 'appwrite') {
|
if (type === 'appwrite') {
|
||||||
return await startAppWriteService(request)
|
return await startAppWriteService(request)
|
||||||
}
|
}
|
||||||
|
if (type === 'glitchTip') {
|
||||||
|
return await startGlitchTipService(request)
|
||||||
|
}
|
||||||
|
if (type === 'searxng') {
|
||||||
|
return await startSearXNGService(request)
|
||||||
|
}
|
||||||
throw `Service type ${type} not supported.`
|
throw `Service type ${type} not supported.`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw { status: 500, message: error?.message || error }
|
throw { status: 500, message: error?.message || error }
|
||||||
@@ -599,53 +594,6 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
export async function stopService(request: FastifyRequest<ServiceStartStop>) {
|
export async function stopService(request: FastifyRequest<ServiceStartStop>) {
|
||||||
try {
|
try {
|
||||||
return await stopServiceContainers(request)
|
return await stopServiceContainers(request)
|
||||||
// const { type } = request.params
|
|
||||||
// if (type === 'plausibleanalytics') {
|
|
||||||
// return await stopPlausibleAnalyticsService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'nocodb') {
|
|
||||||
// return await stopNocodbService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'minio') {
|
|
||||||
// return await stopMinioService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'vscodeserver') {
|
|
||||||
// return await stopVscodeService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'wordpress') {
|
|
||||||
// return await stopWordpressService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'vaultwarden') {
|
|
||||||
// return await stopVaultwardenService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'languagetool') {
|
|
||||||
// return await stopLanguageToolService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'n8n') {
|
|
||||||
// return await stopN8nService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'uptimekuma') {
|
|
||||||
// return await stopUptimekumaService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'ghost') {
|
|
||||||
// return await stopGhostService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'meilisearch') {
|
|
||||||
// return await stopMeilisearchService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'umami') {
|
|
||||||
// return await stopUmamiService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'hasura') {
|
|
||||||
// return await stopHasuraService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'fider') {
|
|
||||||
// return await stopFiderService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'moodle') {
|
|
||||||
// return await stopMoodleService(request)
|
|
||||||
// }
|
|
||||||
// throw `Service type ${type} not supported.`
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw { status: 500, message: error?.message || error }
|
throw { status: 500, message: error?.message || error }
|
||||||
}
|
}
|
||||||
@@ -806,21 +754,21 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
|||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
depends_on: [`${id}-postgresql`, `${id}-clickhouse`],
|
depends_on: [`${id}-postgresql`, `${id}-clickhouse`],
|
||||||
labels: makeLabelForServices('plausibleAnalytics'),
|
labels: makeLabelForServices('plausibleAnalytics'),
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-postgresql`]: {
|
[`${id}-postgresql`]: {
|
||||||
container_name: `${id}-postgresql`,
|
container_name: `${id}-postgresql`,
|
||||||
image: config.postgresql.image,
|
image: config.postgresql.image,
|
||||||
environment: config.postgresql.environmentVariables,
|
environment: config.postgresql.environmentVariables,
|
||||||
volumes: [config.postgresql.volume],
|
volumes: [config.postgresql.volume],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-clickhouse`]: {
|
[`${id}-clickhouse`]: {
|
||||||
build: workdir,
|
build: workdir,
|
||||||
container_name: `${id}-clickhouse`,
|
container_name: `${id}-clickhouse`,
|
||||||
environment: config.clickhouse.environmentVariables,
|
environment: config.clickhouse.environmentVariables,
|
||||||
volumes: [config.clickhouse.volume],
|
volumes: [config.clickhouse.volume],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -881,7 +829,7 @@ async function startNocodbService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
environment: config.environmentVariables,
|
environment: config.environmentVariables,
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
labels: makeLabelForServices('nocodb'),
|
labels: makeLabelForServices('nocodb'),
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -953,7 +901,7 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
volumes,
|
volumes,
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
labels: makeLabelForServices('minio'),
|
labels: makeLabelForServices('minio'),
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1019,7 +967,7 @@ async function startVscodeService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
volumes,
|
volumes,
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
labels: makeLabelForServices('vscodeServer'),
|
labels: makeLabelForServices('vscodeServer'),
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1116,7 +1064,7 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
|
|
||||||
const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config.wordpress)
|
const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config.wordpress)
|
||||||
|
|
||||||
let composeFile: ComposeFile = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
[id]: {
|
[id]: {
|
||||||
@@ -1126,7 +1074,7 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
volumes,
|
volumes,
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
labels: makeLabelForServices('wordpress'),
|
labels: makeLabelForServices('wordpress'),
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1143,7 +1091,7 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
image: config.mysql.image,
|
image: config.mysql.image,
|
||||||
volumes: [config.mysql.volume],
|
volumes: [config.mysql.volume],
|
||||||
environment: config.mysql.environmentVariables,
|
environment: config.mysql.environmentVariables,
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
};
|
};
|
||||||
|
|
||||||
composeFile.volumes[config.mysql.volume.split(':')[0]] = {
|
composeFile.volumes[config.mysql.volume.split(':')[0]] = {
|
||||||
@@ -1196,7 +1144,7 @@ async function startVaultwardenService(request: FastifyRequest<ServiceStartStop>
|
|||||||
volumes,
|
volumes,
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
labels: makeLabelForServices('vaultWarden'),
|
labels: makeLabelForServices('vaultWarden'),
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1252,7 +1200,7 @@ async function startLanguageToolService(request: FastifyRequest<ServiceStartStop
|
|||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
volumes,
|
volumes,
|
||||||
labels: makeLabelForServices('languagetool'),
|
labels: makeLabelForServices('languagetool'),
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1309,7 +1257,7 @@ async function startN8nService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
environment: config.environmentVariables,
|
environment: config.environmentVariables,
|
||||||
labels: makeLabelForServices('n8n'),
|
labels: makeLabelForServices('n8n'),
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1364,7 +1312,7 @@ async function startUptimekumaService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
environment: config.environmentVariables,
|
environment: config.environmentVariables,
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
labels: makeLabelForServices('uptimekuma'),
|
labels: makeLabelForServices('uptimekuma'),
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1463,14 +1411,14 @@ async function startGhostService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
labels: makeLabelForServices('ghost'),
|
labels: makeLabelForServices('ghost'),
|
||||||
depends_on: [`${id}-mariadb`],
|
depends_on: [`${id}-mariadb`],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-mariadb`]: {
|
[`${id}-mariadb`]: {
|
||||||
container_name: `${id}-mariadb`,
|
container_name: `${id}-mariadb`,
|
||||||
image: config.mariadb.image,
|
image: config.mariadb.image,
|
||||||
volumes: [config.mariadb.volume],
|
volumes: [config.mariadb.volume],
|
||||||
environment: config.mariadb.environmentVariables,
|
environment: config.mariadb.environmentVariables,
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1536,7 +1484,7 @@ async function startMeilisearchService(request: FastifyRequest<ServiceStartStop>
|
|||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
volumes,
|
volumes,
|
||||||
labels: makeLabelForServices('meilisearch'),
|
labels: makeLabelForServices('meilisearch'),
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1702,14 +1650,14 @@ async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
labels: makeLabelForServices('umami'),
|
labels: makeLabelForServices('umami'),
|
||||||
depends_on: [`${id}-postgresql`],
|
depends_on: [`${id}-postgresql`],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-postgresql`]: {
|
[`${id}-postgresql`]: {
|
||||||
build: workdir,
|
build: workdir,
|
||||||
container_name: `${id}-postgresql`,
|
container_name: `${id}-postgresql`,
|
||||||
environment: config.postgresql.environmentVariables,
|
environment: config.postgresql.environmentVariables,
|
||||||
volumes: [config.postgresql.volume],
|
volumes: [config.postgresql.volume],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1789,14 +1737,14 @@ async function startHasuraService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
labels: makeLabelForServices('hasura'),
|
labels: makeLabelForServices('hasura'),
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
depends_on: [`${id}-postgresql`],
|
depends_on: [`${id}-postgresql`],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-postgresql`]: {
|
[`${id}-postgresql`]: {
|
||||||
image: config.postgresql.image,
|
image: config.postgresql.image,
|
||||||
container_name: `${id}-postgresql`,
|
container_name: `${id}-postgresql`,
|
||||||
environment: config.postgresql.environmentVariables,
|
environment: config.postgresql.environmentVariables,
|
||||||
volumes: [config.postgresql.volume],
|
volumes: [config.postgresql.volume],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1902,14 +1850,14 @@ async function startFiderService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
labels: makeLabelForServices('fider'),
|
labels: makeLabelForServices('fider'),
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
depends_on: [`${id}-postgresql`],
|
depends_on: [`${id}-postgresql`],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-postgresql`]: {
|
[`${id}-postgresql`]: {
|
||||||
image: config.postgresql.image,
|
image: config.postgresql.image,
|
||||||
container_name: `${id}-postgresql`,
|
container_name: `${id}-postgresql`,
|
||||||
environment: config.postgresql.environmentVariables,
|
environment: config.postgresql.environmentVariables,
|
||||||
volumes: [config.postgresql.volume],
|
volumes: [config.postgresql.volume],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -1995,7 +1943,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
"_APP_STATSD_PORT=8125",
|
"_APP_STATSD_PORT=8125",
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-realtime`]: {
|
[`${id}-realtime`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2018,7 +1966,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
`_APP_DB_PASS=${mariadbPassword}`,
|
`_APP_DB_PASS=${mariadbPassword}`,
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-worker-audits`]: {
|
[`${id}-worker-audits`]: {
|
||||||
|
|
||||||
@@ -2042,7 +1990,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
`_APP_DB_PASS=${mariadbPassword}`,
|
`_APP_DB_PASS=${mariadbPassword}`,
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-worker-webhooks`]: {
|
[`${id}-worker-webhooks`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2060,7 +2008,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
"_APP_REDIS_PORT=6379",
|
"_APP_REDIS_PORT=6379",
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-worker-deletes`]: {
|
[`${id}-worker-deletes`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2093,7 +2041,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
`_APP_EXECUTOR_HOST=http://${id}-executor/v1`,
|
`_APP_EXECUTOR_HOST=http://${id}-executor/v1`,
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-worker-databases`]: {
|
[`${id}-worker-databases`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2116,7 +2064,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
`_APP_DB_PASS=${mariadbPassword}`,
|
`_APP_DB_PASS=${mariadbPassword}`,
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-worker-builds`]: {
|
[`${id}-worker-builds`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2141,7 +2089,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
`_APP_DB_PASS=${mariadbPassword}`,
|
`_APP_DB_PASS=${mariadbPassword}`,
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-worker-certificates`]: {
|
[`${id}-worker-certificates`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2170,7 +2118,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
`_APP_DB_PASS=${mariadbPassword}`,
|
`_APP_DB_PASS=${mariadbPassword}`,
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-worker-functions`]: {
|
[`${id}-worker-functions`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2196,7 +2144,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
`_APP_EXECUTOR_HOST=http://${id}-executor/v1`,
|
`_APP_EXECUTOR_HOST=http://${id}-executor/v1`,
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-executor`]: {
|
[`${id}-executor`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2220,7 +2168,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
`_APP_EXECUTOR_SECRET=${executorSecret}`,
|
`_APP_EXECUTOR_SECRET=${executorSecret}`,
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-worker-mails`]: {
|
[`${id}-worker-mails`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2237,7 +2185,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
"_APP_REDIS_PORT=6379",
|
"_APP_REDIS_PORT=6379",
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-worker-messaging`]: {
|
[`${id}-worker-messaging`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2253,7 +2201,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
"_APP_REDIS_PORT=6379",
|
"_APP_REDIS_PORT=6379",
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-maintenance`]: {
|
[`${id}-maintenance`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2277,7 +2225,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
`_APP_DB_PASS=${mariadbPassword}`,
|
`_APP_DB_PASS=${mariadbPassword}`,
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-schedule`]: {
|
[`${id}-schedule`]: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@@ -2293,7 +2241,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
"_APP_REDIS_PORT=6379",
|
"_APP_REDIS_PORT=6379",
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-mariadb`]: {
|
[`${id}-mariadb`]: {
|
||||||
"image": "mariadb:10.7",
|
"image": "mariadb:10.7",
|
||||||
@@ -2310,7 +2258,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
`MYSQL_DATABASE=${mariadbDatabase}`
|
`MYSQL_DATABASE=${mariadbDatabase}`
|
||||||
],
|
],
|
||||||
"command": "mysqld --innodb-flush-method=fsync",
|
"command": "mysqld --innodb-flush-method=fsync",
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
[`${id}-redis`]: {
|
[`${id}-redis`]: {
|
||||||
"image": "redis:6.2-alpine",
|
"image": "redis:6.2-alpine",
|
||||||
@@ -2319,7 +2267,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
"volumes": [
|
"volumes": [
|
||||||
`${id}-redis:/data:rw`
|
`${id}-redis:/data:rw`
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -2348,7 +2296,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
"_APP_REDIS_PORT=6379",
|
"_APP_REDIS_PORT=6379",
|
||||||
...secrets
|
...secrets
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
dockerCompose[`${id}-influxdb`] = {
|
dockerCompose[`${id}-influxdb`] = {
|
||||||
"image": "appwrite/influxdb:1.5.0",
|
"image": "appwrite/influxdb:1.5.0",
|
||||||
@@ -2356,7 +2304,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
"volumes": [
|
"volumes": [
|
||||||
`${id}-influxdb:/var/lib/influxdb:rw`
|
`${id}-influxdb:/var/lib/influxdb:rw`
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
dockerCompose[`${id}-telegraf`] = {
|
dockerCompose[`${id}-telegraf`] = {
|
||||||
"image": "appwrite/telegraf:1.4.0",
|
"image": "appwrite/telegraf:1.4.0",
|
||||||
@@ -2365,7 +2313,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
`_APP_INFLUXDB_HOST=${id}-influxdb`,
|
`_APP_INFLUXDB_HOST=${id}-influxdb`,
|
||||||
"_APP_INFLUXDB_PORT=8086",
|
"_APP_INFLUXDB_PORT=8086",
|
||||||
],
|
],
|
||||||
...defaultServiceComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2420,6 +2368,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
async function startServiceContainers(dockerId, composeFileDestination) {
|
async function startServiceContainers(dockerId, composeFileDestination) {
|
||||||
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} pull` })
|
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
|
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} build --no-cache` })
|
||||||
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} create` })
|
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} create` })
|
||||||
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} start` })
|
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} start` })
|
||||||
await asyncSleep(1000);
|
await asyncSleep(1000);
|
||||||
@@ -2574,6 +2523,255 @@ async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function startGlitchTipService(request: FastifyRequest<ServiceStartStop>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
version,
|
||||||
|
fqdn,
|
||||||
|
destinationDockerId,
|
||||||
|
destinationDocker,
|
||||||
|
serviceSecret,
|
||||||
|
persistentStorage,
|
||||||
|
exposePort,
|
||||||
|
glitchTip: {
|
||||||
|
postgresqlDatabase,
|
||||||
|
postgresqlPassword,
|
||||||
|
postgresqlUser,
|
||||||
|
secretKeyBase,
|
||||||
|
defaultEmail,
|
||||||
|
defaultUsername,
|
||||||
|
defaultPassword,
|
||||||
|
defaultFromEmail,
|
||||||
|
emailSmtpHost,
|
||||||
|
emailSmtpPort,
|
||||||
|
emailSmtpUser,
|
||||||
|
emailSmtpPassword,
|
||||||
|
emailSmtpUseTls,
|
||||||
|
emailSmtpUseSsl,
|
||||||
|
emailBackend,
|
||||||
|
mailgunApiKey,
|
||||||
|
sendgridApiKey,
|
||||||
|
enableOpenUserRegistration,
|
||||||
|
}
|
||||||
|
} = service;
|
||||||
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
|
const port = getServiceMainPort('glitchTip');
|
||||||
|
|
||||||
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
|
const image = getServiceImage(type);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
glitchTip: {
|
||||||
|
image: `${image}:${version}`,
|
||||||
|
environmentVariables: {
|
||||||
|
PORT: port,
|
||||||
|
GLITCHTIP_DOMAIN: fqdn,
|
||||||
|
SECRET_KEY: secretKeyBase,
|
||||||
|
DATABASE_URL: `postgresql://${postgresqlUser}:${postgresqlPassword}@${id}-postgresql:5432/${postgresqlDatabase}`,
|
||||||
|
REDIS_URL: `redis://${id}-redis:6379/0`,
|
||||||
|
DEFAULT_FROM_EMAIL: defaultFromEmail,
|
||||||
|
EMAIL_HOST: emailSmtpHost,
|
||||||
|
EMAIL_PORT: emailSmtpPort,
|
||||||
|
EMAIL_HOST_USER: emailSmtpUser,
|
||||||
|
EMAIL_HOST_PASSWORD: emailSmtpPassword,
|
||||||
|
EMAIL_USE_TLS: emailSmtpUseTls,
|
||||||
|
EMAIL_USE_SSL: emailSmtpUseSsl,
|
||||||
|
EMAIL_BACKEND: emailBackend,
|
||||||
|
MAILGUN_API_KEY: mailgunApiKey,
|
||||||
|
SENDGRID_API_KEY: sendgridApiKey,
|
||||||
|
ENABLE_OPEN_USER_REGISTRATION: enableOpenUserRegistration,
|
||||||
|
DJANGO_SUPERUSER_EMAIL: defaultEmail,
|
||||||
|
DJANGO_SUPERUSER_USERNAME: defaultUsername,
|
||||||
|
DJANGO_SUPERUSER_PASSWORD: defaultPassword,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
postgresql: {
|
||||||
|
image: 'postgres:14-alpine',
|
||||||
|
volume: `${id}-postgresql-data:/var/lib/postgresql/data`,
|
||||||
|
environmentVariables: {
|
||||||
|
POSTGRES_USER: postgresqlUser,
|
||||||
|
POSTGRES_PASSWORD: postgresqlPassword,
|
||||||
|
POSTGRES_DB: postgresqlDatabase
|
||||||
|
}
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
image: 'redis:7-alpine',
|
||||||
|
volume: `${id}-redis-data:/data`,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (serviceSecret.length > 0) {
|
||||||
|
serviceSecret.forEach((secret) => {
|
||||||
|
config.glitchTip.environmentVariables[secret.name] = secret.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config.glitchTip)
|
||||||
|
const composeFile: ComposeFile = {
|
||||||
|
version: '3.8',
|
||||||
|
services: {
|
||||||
|
[id]: {
|
||||||
|
container_name: id,
|
||||||
|
image: config.glitchTip.image,
|
||||||
|
environment: config.glitchTip.environmentVariables,
|
||||||
|
volumes,
|
||||||
|
labels: makeLabelForServices('glitchTip'),
|
||||||
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
|
depends_on: [`${id}-postgresql`, `${id}-redis`],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
[`${id}-worker`]: {
|
||||||
|
container_name: `${id}-worker`,
|
||||||
|
image: config.glitchTip.image,
|
||||||
|
command: './bin/run-celery-with-beat.sh',
|
||||||
|
environment: config.glitchTip.environmentVariables,
|
||||||
|
depends_on: [`${id}-postgresql`, `${id}-redis`],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
[`${id}-setup`]: {
|
||||||
|
container_name: `${id}-setup`,
|
||||||
|
image: config.glitchTip.image,
|
||||||
|
command: 'sh -c "(./manage.py migrate || true) && (./manage.py createsuperuser --noinput || true)"',
|
||||||
|
environment: config.glitchTip.environmentVariables,
|
||||||
|
networks: [network],
|
||||||
|
restart: "no",
|
||||||
|
depends_on: [`${id}-postgresql`, `${id}-redis`]
|
||||||
|
},
|
||||||
|
[`${id}-postgresql`]: {
|
||||||
|
image: config.postgresql.image,
|
||||||
|
container_name: `${id}-postgresql`,
|
||||||
|
environment: config.postgresql.environmentVariables,
|
||||||
|
volumes: [config.postgresql.volume],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
[`${id}-redis`]: {
|
||||||
|
image: config.redis.image,
|
||||||
|
container_name: `${id}-redis`,
|
||||||
|
volumes: [config.redis.volume],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
networks: {
|
||||||
|
[network]: {
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
volumes: {
|
||||||
|
...volumeMounts,
|
||||||
|
[config.postgresql.volume.split(':')[0]]: {
|
||||||
|
name: config.postgresql.volume.split(':')[0]
|
||||||
|
},
|
||||||
|
[config.redis.volume.split(':')[0]]: {
|
||||||
|
name: config.redis.volume.split(':')[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||||
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
|
return {}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startSearXNGService(request: FastifyRequest<ServiceStartStop>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
|
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort, persistentStorage, fqdn, searxng: { secretKey, redisPassword } } =
|
||||||
|
service;
|
||||||
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
|
const port = getServiceMainPort('searxng');
|
||||||
|
|
||||||
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
|
const image = getServiceImage(type);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
searxng: {
|
||||||
|
image: `${image}:${version}`,
|
||||||
|
volume: `${id}-searxng:/etc/searxng`,
|
||||||
|
environmentVariables: {
|
||||||
|
SEARXNG_BASE_URL: `${fqdn}`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
image: 'redis:7-alpine',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const settingsYml = `
|
||||||
|
# see https://docs.searxng.org/admin/engines/settings.html#use-default-settings
|
||||||
|
use_default_settings: true
|
||||||
|
server:
|
||||||
|
secret_key: ${secretKey}
|
||||||
|
limiter: true
|
||||||
|
image_proxy: true
|
||||||
|
ui:
|
||||||
|
static_use_hash: true
|
||||||
|
redis:
|
||||||
|
url: redis://:${redisPassword}@${id}-redis:6379/0`
|
||||||
|
|
||||||
|
const Dockerfile = `
|
||||||
|
FROM ${config.searxng.image}
|
||||||
|
COPY ./settings.yml /etc/searxng/settings.yml`;
|
||||||
|
|
||||||
|
if (serviceSecret.length > 0) {
|
||||||
|
serviceSecret.forEach((secret) => {
|
||||||
|
config.searxng.environmentVariables[secret.name] = secret.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
|
const composeFile: ComposeFile = {
|
||||||
|
version: '3.8',
|
||||||
|
services: {
|
||||||
|
[id]: {
|
||||||
|
build: workdir,
|
||||||
|
container_name: id,
|
||||||
|
volumes,
|
||||||
|
environment: config.searxng.environmentVariables,
|
||||||
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
|
labels: makeLabelForServices('searxng'),
|
||||||
|
cap_drop: ['ALL'],
|
||||||
|
cap_add: ['CHOWN', 'SETGID', 'SETUID', 'DAC_OVERRIDE'],
|
||||||
|
depends_on: [`${id}-redis`],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
[`${id}-redis`]: {
|
||||||
|
container_name: `${id}-redis`,
|
||||||
|
image: config.redis.image,
|
||||||
|
command: `redis-server --requirepass ${redisPassword} --save "" --appendonly "no"`,
|
||||||
|
labels: makeLabelForServices('searxng'),
|
||||||
|
cap_drop: ['ALL'],
|
||||||
|
cap_add: ['SETGID', 'SETUID', 'DAC_OVERRIDE'],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
networks: {
|
||||||
|
[network]: {
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
volumes: volumeMounts
|
||||||
|
};
|
||||||
|
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||||
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile);
|
||||||
|
await fs.writeFile(`${workdir}/settings.yml`, settingsYml);
|
||||||
|
|
||||||
|
await startServiceContainers(destinationDocker.id, composeFileDestination)
|
||||||
|
return {}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
@@ -2624,7 +2822,7 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
const publicPort = await getFreePublicPort(id, dockerId);
|
const publicPort = await getFreePublicPort(id, dockerId);
|
||||||
|
|
||||||
let ftpUser = cuid();
|
let ftpUser = cuid();
|
||||||
let ftpPassword = generatePassword();
|
let ftpPassword = generatePassword({});
|
||||||
|
|
||||||
const hostkeyDir = isDev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
|
const hostkeyDir = isDev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -33,12 +33,13 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
|||||||
minPort,
|
minPort,
|
||||||
maxPort,
|
maxPort,
|
||||||
isAutoUpdateEnabled,
|
isAutoUpdateEnabled,
|
||||||
isDNSCheckEnabled
|
isDNSCheckEnabled,
|
||||||
|
DNSServers
|
||||||
} = request.body
|
} = request.body
|
||||||
const { id } = await listSettings();
|
const { id } = await listSettings();
|
||||||
await prisma.setting.update({
|
await prisma.setting.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled }
|
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers }
|
||||||
});
|
});
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||||
@@ -54,6 +55,10 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
|||||||
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
|
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { fqdn } = request.body
|
const { fqdn } = request.body
|
||||||
|
const { DNSServers } = await listSettings();
|
||||||
|
if (DNSServers) {
|
||||||
|
dns.setServers([DNSServers]);
|
||||||
|
}
|
||||||
let ip;
|
let ip;
|
||||||
try {
|
try {
|
||||||
ip = await dns.resolve(fqdn);
|
ip = await dns.resolve(fqdn);
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ export interface SaveSettings {
|
|||||||
minPort: number,
|
minPort: number,
|
||||||
maxPort: number,
|
maxPort: number,
|
||||||
isAutoUpdateEnabled: boolean,
|
isAutoUpdateEnabled: boolean,
|
||||||
isDNSCheckEnabled: boolean
|
isDNSCheckEnabled: boolean,
|
||||||
|
DNSServers: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface DeleteDomain {
|
export interface DeleteDomain {
|
||||||
|
|||||||
@@ -484,7 +484,6 @@ export async function traefikOtherConfiguration(request: FastifyRequest<TraefikO
|
|||||||
}
|
}
|
||||||
throw { status: 500 }
|
throw { status: 500 }
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
console.log(status, message);
|
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
ports: {
|
ports: {
|
||||||
main: 80
|
main: 80
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
// {
|
// {
|
||||||
// name: 'moodle',
|
// name: 'moodle',
|
||||||
// fancyName: 'Moodle',
|
// fancyName: 'Moodle',
|
||||||
@@ -170,6 +170,28 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
// main: 8080
|
// main: 8080
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
{
|
||||||
|
name: 'glitchTip',
|
||||||
|
fancyName: 'GlitchTip',
|
||||||
|
baseImage: 'glitchtip/glitchtip',
|
||||||
|
images: ['postgres:14-alpine', 'redis:7-alpine'],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'searxng',
|
||||||
|
fancyName: 'SearXNG',
|
||||||
|
baseImage: 'searxng/searxng',
|
||||||
|
images: ['redis:6.2-alpine'],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const asyncSleep = (delay: number) =>
|
export const asyncSleep = (delay: number) =>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
disabled={updateStatus.success === false}
|
disabled={updateStatus.success === false}
|
||||||
on:click={update}
|
on:click={update}
|
||||||
class="icons tooltip tooltip-right tooltip-primary bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
|
class="icons tooltip tooltip-right tooltip-primary bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
|
||||||
data-tip="Update available!"
|
data-tip="Update Available!"
|
||||||
>
|
>
|
||||||
{#if updateStatus.loading}
|
{#if updateStatus.loading}
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
51
apps/ui/src/lib/components/svg/services/GlitchTip.svelte
Normal file
51
apps/ui/src/lib/components/svg/services/GlitchTip.svelte
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
style="isolation:isolate"
|
||||||
|
viewBox="0 0 400 400"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="_clipPath_5kOQy2sGcuF9aeG3NHWmCAGgMEPQrnNW">
|
||||||
|
<rect width="400" height="400" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#_clipPath_5kOQy2sGcuF9aeG3NHWmCAGgMEPQrnNW)">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d=" M 276.155 367.684 L 337.655 367.684 L 337.655 180.781 L 205.525 180.781 L 205.525 241.801 L 267.987 241.801 L 267.987 258.617 C 267.987 291.29 238.678 308.586 202.162 308.586 C 156.998 308.586 127.689 282.641 127.689 226.906 L 127.689 173.094 C 127.689 117.359 156.998 91.414 202.162 91.414 C 241.08 91.414 261.74 112.554 271.83 138.5 L 331.409 104.386 C 306.424 52.976 261.74 26.55 202.162 26.55 C 111.353 26.55 50.333 88.531 50.333 201.441 C 50.333 313.872 110.873 373.45 187.748 373.45 C 238.197 373.45 268.947 347.985 273.752 314.352 L 276.155 314.352 L 276.155 367.684 Z "
|
||||||
|
fill="rgb(132,24,128)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g opacity="0.5">
|
||||||
|
<path
|
||||||
|
d=" M 139.701 175.78 L 139.701 173.094 C 139.701 117.359 169.01 91.414 214.174 91.414 C 253.092 91.414 273.752 112.554 283.842 138.5 L 343.421 104.386 C 318.436 52.976 273.752 26.55 214.174 26.55 C 128.962 26.55 69.981 81.125 63.033 181.145 L 139.701 175.78 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g opacity="0.5">
|
||||||
|
<path
|
||||||
|
d=" M 349.667 305.194 L 349.667 247.137 L 279.998 252.019 L 279.998 258.617 C 279.998 291.29 250.69 308.586 214.174 308.586 C 179.697 308.586 154.459 293.467 144.446 261.518 L 70.341 266.711 C 76.285 288.796 85.348 307.563 96.86 322.909 L 349.667 305.194 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
d=" M 337.655 247.03 L 337.655 180.781 L 205.525 180.781 L 205.525 241.801 L 267.987 241.801 L 267.987 251.912 L 337.655 247.03 Z M 132.401 261.413 C 129.319 251.534 127.689 240.048 127.689 226.906 L 127.689 175.099 L 51.069 180.468 C 50.581 187.25 50.333 194.242 50.333 201.441 C 50.333 225.632 53.136 247.376 58.301 266.606 L 132.401 261.413 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d=" M 337.655 305.862 L 337.655 367.684 L 276.155 367.684 L 276.155 314.352 L 273.752 314.352 C 268.947 347.985 238.197 373.45 187.748 373.45 C 146.712 373.45 110.33 356.473 85.327 323.543 L 337.655 305.862 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(255,63,42)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
57
apps/ui/src/lib/components/svg/services/Searxng.svelte
Normal file
57
apps/ui/src/lib/components/svg/services/Searxng.svelte
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
id="svg8"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 92 92"
|
||||||
|
class={isAbsolute ? 'w-12 absolute top-0 left-0 -m-3 -mt-5' : 'w-8 mx-auto'}
|
||||||
|
>
|
||||||
|
<defs id="defs2" />
|
||||||
|
<metadata id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(-40.921303,-17.416526)" id="layer1">
|
||||||
|
<circle
|
||||||
|
r="0"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cy="92"
|
||||||
|
cx="75"
|
||||||
|
id="path3713"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
r="30"
|
||||||
|
cy="53.902557"
|
||||||
|
cx="75.921303"
|
||||||
|
id="path834"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#3050ff;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m 67.514849,37.91524 a 18,18 0 0 1 21.051475,3.312407 18,18 0 0 1 3.137312,21.078282"
|
||||||
|
id="path852"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#3050ff;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
transform="rotate(-46.234709)"
|
||||||
|
ry="1.8669105e-13"
|
||||||
|
y="122.08995"
|
||||||
|
x="3.7063529"
|
||||||
|
height="39.963303"
|
||||||
|
width="18.846331"
|
||||||
|
id="rect912"
|
||||||
|
style="opacity:1;fill:#3050ff;fill-opacity:1;stroke:none;stroke-width:8;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
@@ -36,4 +36,8 @@
|
|||||||
<Icons.Appwrite {isAbsolute} />
|
<Icons.Appwrite {isAbsolute} />
|
||||||
{:else if type === 'moodle'}
|
{:else if type === 'moodle'}
|
||||||
<Icons.Moodle {isAbsolute} />
|
<Icons.Moodle {isAbsolute} />
|
||||||
|
{:else if type === 'glitchTip'}
|
||||||
|
<Icons.GlitchTip {isAbsolute} />
|
||||||
|
{:else if type === 'searxng'}
|
||||||
|
<Icons.Searxng {isAbsolute} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ export { default as Hasura } from './Hasura.svelte';
|
|||||||
export { default as Fider } from './Fider.svelte';
|
export { default as Fider } from './Fider.svelte';
|
||||||
export { default as Appwrite } from './Appwrite.svelte';
|
export { default as Appwrite } from './Appwrite.svelte';
|
||||||
export { default as Moodle } from './Moodle.svelte';
|
export { default as Moodle } from './Moodle.svelte';
|
||||||
|
export { default as GlitchTip } from './GlitchTip.svelte';
|
||||||
|
export { default as Searxng } from './Searxng.svelte';
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"wait_new_version_startup": "Waiting for the new version to start...",
|
"wait_new_version_startup": "Waiting for the new version to start...",
|
||||||
"new_version": "New version reachable. Reloading...",
|
"new_version": "New version reachable. Reloading...",
|
||||||
"switch_to_a_different_team": "Switch to a different team...",
|
"switch_to_a_different_team": "Switch to a different team...",
|
||||||
"update_available": "Update available"
|
"update_available": "Update Available"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"you_can_find_your_way_back": "You can find your way back",
|
"you_can_find_your_way_back": "You can find your way back",
|
||||||
@@ -144,8 +144,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"need_during_buildtime": "Need during buildtime?",
|
"need_during_buildtime": "Need during buildtime?",
|
||||||
"setup_secret_app_first": "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-applications font-bold'>staging</span> environments.",
|
"setup_secret_app_first": "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments.",
|
||||||
"values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-applications font-bold'>staging</span> environments.",
|
"values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments.",
|
||||||
"redeploy": "Redeploy",
|
"redeploy": "Redeploy",
|
||||||
"no_previews_available": "No previews available"
|
"no_previews_available": "No previews available"
|
||||||
},
|
},
|
||||||
@@ -159,13 +159,13 @@
|
|||||||
"storage_saved": "Storage saved.",
|
"storage_saved": "Storage saved.",
|
||||||
"storage_updated": "Storage updated.",
|
"storage_updated": "Storage updated.",
|
||||||
"storage_deleted": "Storage deleted.",
|
"storage_deleted": "Storage deleted.",
|
||||||
"persistent_storage_explainer": "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."
|
"persistent_storage_explainer": "You can specify any folder that you want to be persistent across deployments.<br><span class='text-green-500 font-bold'>/example</span> means it will preserve <span class='text-green-500 font-bold'>/app/example</span> in the container as <span class='text-green-500 font-bold'>/app</span> is <span class='text-green-500 font-bold'>the root directory</span> for your application.<br><br>This is useful for storing data such as a <span class='text-green-500 font-bold'>database (SQLite)</span> or a <span class='text-green-500 font-bold'>cache</span>."
|
||||||
},
|
},
|
||||||
"deployment_queued": "Deployment queued.",
|
"deployment_queued": "Deployment queued.",
|
||||||
"confirm_to_delete": "Are you sure you would like to delete '{{name}}'?",
|
"confirm_to_delete": "Are you sure you would like to delete '{{name}}'?",
|
||||||
"stop_application": "Stop application",
|
"stop_application": "Stop Application",
|
||||||
"permission_denied_stop_application": "You do not have permission to stop the application.",
|
"permission_denied_stop_application": "You do not have permission to stop the application.",
|
||||||
"rebuild_application": "Rebuild application",
|
"rebuild_application": "Rebuild Application",
|
||||||
"permission_denied_rebuild_application": "You do not have permission to rebuild application.",
|
"permission_denied_rebuild_application": "You do not have permission to rebuild application.",
|
||||||
"build_and_start_application": "Deploy",
|
"build_and_start_application": "Deploy",
|
||||||
"permission_denied_build_and_start_application": "You do not have permission to deploy application.",
|
"permission_denied_build_and_start_application": "You do not have permission to deploy application.",
|
||||||
@@ -194,14 +194,14 @@
|
|||||||
"application": "Application",
|
"application": "Application",
|
||||||
"url_fqdn": "URL (FQDN)",
|
"url_fqdn": "URL (FQDN)",
|
||||||
"domain_fqdn": "Domain (FQDN)",
|
"domain_fqdn": "Domain (FQDN)",
|
||||||
"https_explainer": "If you specify <span class='text-applications font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-applications font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white font-bold'>You must set your DNS to point to the server IP in advance.</span>",
|
"https_explainer": "If you specify <span class='text-green-500 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-500 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white font-bold'>You must set your DNS to point to the server IP in advance.</span>",
|
||||||
"ssl_www_and_non_www": "Generate SSL for www and non-www?",
|
"ssl_www_and_non_www": "Generate SSL for www and non-www?",
|
||||||
"ssl_explainer": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-applications'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both.",
|
"ssl_explainer": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-green-500'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both.",
|
||||||
"install_command": "Install Command",
|
"install_command": "Install Command",
|
||||||
"build_command": "Build Command",
|
"build_command": "Build Command",
|
||||||
"start_command": "Start Command",
|
"start_command": "Start Command",
|
||||||
"directory_to_use_explainer": "Directory to use as the base for all commands.<br>Could be useful with <span class='text-applications font-bold'>monorepos</span>.",
|
"directory_to_use_explainer": "Directory to use as the base for all commands.<br>Could be useful with <span class='text-green-500 font-bold'>monorepos</span>.",
|
||||||
"publish_directory_explainer": "Directory containing all the assets for deployment. <br> For example: <span class='text-applications font-bold'>dist</span>,<span class='text-applications font-bold'>_site</span> or <span class='text-applications font-bold'>public</span>.",
|
"publish_directory_explainer": "Directory containing all the assets for deployment. <br> For example: <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> or <span class='text-green-500 font-bold'>public</span>.",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"enable_automatic_deployment": "Enable Automatic Deployment",
|
"enable_automatic_deployment": "Enable Automatic Deployment",
|
||||||
"enable_auto_deploy_webhooks": "Enable automatic deployment through webhooks.",
|
"enable_auto_deploy_webhooks": "Enable automatic deployment through webhooks.",
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
"features": "Caractéristiques",
|
"features": "Caractéristiques",
|
||||||
"git_repository": "Dépôt Git",
|
"git_repository": "Dépôt Git",
|
||||||
"git_source": "Source Git",
|
"git_source": "Source Git",
|
||||||
"https_explainer": "Si vous spécifiez <span class='text-applications font-bold'>https</span>, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.<br>Si vous spécifiez <span class='text-applications font-bold'>www</span>, l'application sera redirigée (302) à partir de non-www et vice versa \n.<br><br>Pour modifier le domaine, vous devez d'abord arrêter l'application.<br><br><span class='text-white font-bold'>Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.</span>",
|
"https_explainer": "Si vous spécifiez <span class='text-green-500 font-bold'>https</span>, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.<br>Si vous spécifiez <span class='text-green-500 font-bold'>www</span>, l'application sera redirigée (302) à partir de non-www et vice versa \n.<br><br>Pour modifier le domaine, vous devez d'abord arrêter l'application.<br><br><span class='text-white font-bold'>Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.</span>",
|
||||||
"install_command": "Commande d'installation",
|
"install_command": "Commande d'installation",
|
||||||
"logs": "Journaux des applications",
|
"logs": "Journaux des applications",
|
||||||
"no_applications_found": "Aucune application trouvée",
|
"no_applications_found": "Aucune application trouvée",
|
||||||
@@ -78,11 +78,11 @@
|
|||||||
"need_during_buildtime": "Besoin pendant la build ?",
|
"need_during_buildtime": "Besoin pendant la build ?",
|
||||||
"no_previews_available": "Aucun aperçu disponible",
|
"no_previews_available": "Aucun aperçu disponible",
|
||||||
"redeploy": "Redéployer",
|
"redeploy": "Redéployer",
|
||||||
"setup_secret_app_first": "Vous pouvez ajouter des secrets aux déploiements PR/MR. \nVeuillez d'abord ajouter des secrets à l'application. \n<br>Utile pour créer des environnements <span class='text-applications font-bold'>de mise en scène</span>.",
|
"setup_secret_app_first": "Vous pouvez ajouter des secrets aux déploiements PR/MR. \nVeuillez d'abord ajouter des secrets à l'application. \n<br>Utile pour créer des environnements <span class='text-green-500 font-bold'>de mise en scène</span>.",
|
||||||
"values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements <span class='text-applications font-bold'>de mise en scène</span>."
|
"values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements <span class='text-green-500 font-bold'>de mise en scène</span>."
|
||||||
},
|
},
|
||||||
"previews": "Aperçus",
|
"previews": "Aperçus",
|
||||||
"publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n<br> Par exemple : <span class='text-applications font-bold'>dist</span>,<span class='text-applications font-bold'>_site</span> ou <span \nclass='text-applications font-bold'>public</span>.",
|
"publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n<br> Par exemple : <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> ou <span \nclass='text-green-500 font-bold'>public</span>.",
|
||||||
"rebuild_application": "Re-build l'application",
|
"rebuild_application": "Re-build l'application",
|
||||||
"secret": "secrets",
|
"secret": "secrets",
|
||||||
"secrets": {
|
"secrets": {
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
"use_isbuildsecret": "Utiliser isBuildSecret"
|
"use_isbuildsecret": "Utiliser isBuildSecret"
|
||||||
},
|
},
|
||||||
"settings_saved": "Paramètres sauvegardés.",
|
"settings_saved": "Paramètres sauvegardés.",
|
||||||
"ssl_explainer": "Il générera des certificats pour www et non-www. \n<br>Vous devez avoir <span class='font-bold text-applications'>les deux entrées DNS</span> définies à l'avance.<br><br>Utile si vous prévoyez d'avoir des visiteurs sur les deux.",
|
"ssl_explainer": "Il générera des certificats pour www et non-www. \n<br>Vous devez avoir <span class='font-bold text-green-500'>les deux entrées DNS</span> définies à l'avance.<br><br>Utile si vous prévoyez d'avoir des visiteurs sur les deux.",
|
||||||
"ssl_www_and_non_www": "Générer SSL pour www et non-www ?",
|
"ssl_www_and_non_www": "Générer SSL pour www et non-www ?",
|
||||||
"start_command": "Démarrer la commande",
|
"start_command": "Démarrer la commande",
|
||||||
"stop_application": "Arrêter l'application",
|
"stop_application": "Arrêter l'application",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { dev } from '$app/env';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import { writable, readable, type Writable } from 'svelte/store';
|
import { writable, readable, type Writable } from 'svelte/store';
|
||||||
|
|
||||||
@@ -70,7 +71,11 @@ export const features = readable({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const location: Writable<null | string> = writable(null)
|
export const location: Writable<null | string> = writable(null)
|
||||||
export const setLocation = (resource: any) => {
|
export const setLocation = (resource: any, settings?: any) => {
|
||||||
|
if (resource.settings.isBot && resource.exposePort) {
|
||||||
|
disabledButton.set(false);
|
||||||
|
return location.set(`http://${dev ? 'localhost' : settings.ipv4}:${resource.exposePort}`)
|
||||||
|
}
|
||||||
if (GITPOD_WORKSPACE_URL && resource.exposePort) {
|
if (GITPOD_WORKSPACE_URL && resource.exposePort) {
|
||||||
const { href } = new URL(GITPOD_WORKSPACE_URL);
|
const { href } = new URL(GITPOD_WORKSPACE_URL);
|
||||||
const newURL = href
|
const newURL = href
|
||||||
@@ -81,7 +86,12 @@ export const setLocation = (resource: any) => {
|
|||||||
const newURL = `https://${CODESANDBOX_HOST.replace(/\$PORT/, resource.exposePort)}`
|
const newURL = `https://${CODESANDBOX_HOST.replace(/\$PORT/, resource.exposePort)}`
|
||||||
return location.set(newURL)
|
return location.set(newURL)
|
||||||
}
|
}
|
||||||
return location.set(resource.fqdn)
|
if (resource.fqdn) {
|
||||||
|
return location.set(resource.fqdn)
|
||||||
|
} else {
|
||||||
|
location.set(null);
|
||||||
|
disabledButton.set(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toasts: any = writable([])
|
export const toasts: any = writable([])
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
export const load: Load = async ({ fetch, url, params }) => {
|
export const load: Load = async ({ fetch, url, params }) => {
|
||||||
try {
|
try {
|
||||||
const response = await get(`/applications/${params.id}`);
|
const response = await get(`/applications/${params.id}`);
|
||||||
let { application, appId, settings, isQueueActive } = response;
|
let { application, appId, settings } = response;
|
||||||
if (!application || Object.entries(application).length === 0) {
|
if (!application || Object.entries(application).length === 0) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
@@ -36,7 +36,8 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
application
|
application,
|
||||||
|
settings
|
||||||
},
|
},
|
||||||
stuff: {
|
stuff: {
|
||||||
application,
|
application,
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let application: any;
|
export let application: any;
|
||||||
|
export let settings: any;
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||||
import { del, get, post } from '$lib/api';
|
import { del, get, post } from '$lib/api';
|
||||||
@@ -65,10 +66,10 @@
|
|||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let statusInterval: any;
|
let statusInterval: any;
|
||||||
let isQueueActive= false;
|
let isQueueActive = false;
|
||||||
$disabledButton =
|
$disabledButton =
|
||||||
!$appSession.isAdmin ||
|
!$appSession.isAdmin ||
|
||||||
!application.fqdn ||
|
(!application.fqdn && !application.settings.isBot) ||
|
||||||
!application.gitSource ||
|
!application.gitSource ||
|
||||||
!application.repository ||
|
!application.repository ||
|
||||||
!application.destinationDocker ||
|
!application.destinationDocker ||
|
||||||
@@ -76,13 +77,13 @@
|
|||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
async function handleDeploySubmit() {
|
async function handleDeploySubmit(forceRebuild = false) {
|
||||||
try {
|
try {
|
||||||
const { buildId } = await post(`/applications/${id}/deploy`, { ...application });
|
const { buildId } = await post(`/applications/${id}/deploy`, { ...application, forceRebuild });
|
||||||
addToast({
|
addToast({
|
||||||
message: $t('application.deployment_queued'),
|
message: $t('application.deployment_queued'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
});
|
});
|
||||||
if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) {
|
if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) {
|
||||||
return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`);
|
return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||||
} else {
|
} else {
|
||||||
@@ -126,18 +127,22 @@
|
|||||||
$status.application.loading = false;
|
$status.application.loading = false;
|
||||||
$status.application.initialLoading = false;
|
$status.application.initialLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
$status.application.initialLoading = true;
|
$status.application.initialLoading = true;
|
||||||
$location = null;
|
$location = null;
|
||||||
clearInterval(statusInterval);
|
clearInterval(statusInterval);
|
||||||
});
|
});
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
setLocation(application);
|
setLocation(application, settings);
|
||||||
|
|
||||||
$status.application.isRunning = false;
|
$status.application.isRunning = false;
|
||||||
$status.application.isExited = false;
|
$status.application.isExited = false;
|
||||||
$status.application.loading = false;
|
$status.application.loading = false;
|
||||||
if (application.gitSourceId && application.destinationDockerId && application.fqdn) {
|
if (
|
||||||
|
application.gitSourceId &&
|
||||||
|
application.destinationDockerId &&
|
||||||
|
(application.fqdn || application.settings.isBot)
|
||||||
|
) {
|
||||||
await getStatus();
|
await getStatus();
|
||||||
statusInterval = setInterval(async () => {
|
statusInterval = setInterval(async () => {
|
||||||
await getStatus();
|
await getStatus();
|
||||||
@@ -173,9 +178,10 @@
|
|||||||
<polyline points="15 4 20 4 20 9" />
|
<polyline points="15 4 20 4 20 9" />
|
||||||
</svg></a
|
</svg></a
|
||||||
>
|
>
|
||||||
|
<div class="border border-coolgray-500 h-8" />
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="border border-coolgray-500 h-8" />
|
|
||||||
{#if $status.application.isExited}
|
{#if $status.application.isExited}
|
||||||
<a
|
<a
|
||||||
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
||||||
@@ -250,7 +256,7 @@
|
|||||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<form on:submit|preventDefault={handleDeploySubmit}>
|
<form on:submit|preventDefault={() => handleDeploySubmit(true)}>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={$disabledButton || !isQueueActive}
|
disabled={$disabledButton || !isQueueActive}
|
||||||
@@ -258,7 +264,7 @@
|
|||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2"
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2"
|
||||||
data-tip={$appSession.isAdmin
|
data-tip={$appSession.isAdmin
|
||||||
? isQueueActive
|
? isQueueActive
|
||||||
? 'Rebuild application'
|
? 'Force Rebuild Application'
|
||||||
: 'Autoupdate inprogress. Cannot rebuild application.'
|
: 'Autoupdate inprogress. Cannot rebuild application.'
|
||||||
: 'You do not have permission to rebuild application.'}
|
: 'You do not have permission to rebuild application.'}
|
||||||
>
|
>
|
||||||
@@ -403,37 +409,39 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button></a
|
</button></a
|
||||||
>
|
>
|
||||||
<a
|
{#if !application.settings.isBot}
|
||||||
href={!$disabledButton ? `/applications/${id}/previews` : null}
|
<a
|
||||||
sveltekit:prefetch
|
href={!$disabledButton ? `/applications/${id}/previews` : null}
|
||||||
class="hover:text-orange-500 rounded"
|
sveltekit:prefetch
|
||||||
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
class="hover:text-orange-500 rounded"
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||||
>
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||||
<button
|
|
||||||
disabled={$disabledButton}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
|
||||||
data-tip="Previews"
|
|
||||||
>
|
>
|
||||||
<svg
|
<button
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
disabled={$disabledButton}
|
||||||
class="w-6 h-6"
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||||
viewBox="0 0 24 24"
|
data-tip="Previews"
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<svg
|
||||||
<circle cx="7" cy="18" r="2" />
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<circle cx="7" cy="6" r="2" />
|
class="w-6 h-6"
|
||||||
<circle cx="17" cy="12" r="2" />
|
viewBox="0 0 24 24"
|
||||||
<line x1="7" y1="8" x2="7" y2="16" />
|
stroke-width="1.5"
|
||||||
<path d="M7 8a4 4 0 0 0 4 4h4" />
|
stroke="currentColor"
|
||||||
</svg></button
|
fill="none"
|
||||||
></a
|
stroke-linecap="round"
|
||||||
>
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<circle cx="7" cy="18" r="2" />
|
||||||
|
<circle cx="7" cy="6" r="2" />
|
||||||
|
<circle cx="17" cy="12" r="2" />
|
||||||
|
<line x1="7" y1="8" x2="7" y2="16" />
|
||||||
|
<path d="M7 8a4 4 0 0 0 4 4h4" />
|
||||||
|
</svg></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
<div class="border border-coolgray-500 h-8" />
|
<div class="border border-coolgray-500 h-8" />
|
||||||
<a
|
<a
|
||||||
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
|
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
delete tempBuildPack.color;
|
delete tempBuildPack.color;
|
||||||
delete tempBuildPack.hoverColor;
|
delete tempBuildPack.hoverColor;
|
||||||
|
|
||||||
if (foundConfig.buildPack !== name) {
|
if (foundConfig?.buildPack !== name) {
|
||||||
await post(`/applications/${id}`, { ...tempBuildPack, buildPack: name });
|
await post(`/applications/${id}`, { ...tempBuildPack, buildPack: name });
|
||||||
}
|
}
|
||||||
await post(`/applications/${id}/configuration/buildpack`, { buildPack: name });
|
await post(`/applications/${id}/configuration/buildpack`, { buildPack: name });
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { get, post } from '$lib/api';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
import Select from 'svelte-select';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { errorNotification } from '$lib/common';
|
||||||
|
|
||||||
|
const { id } = $page.params;
|
||||||
|
|
||||||
|
let publicRepositoryLink: string;
|
||||||
|
let projectId: number;
|
||||||
|
let repositoryName: string;
|
||||||
|
let branchName: string;
|
||||||
|
let ownerName: string;
|
||||||
|
let type: string;
|
||||||
|
let branchSelectOptions: any = [];
|
||||||
|
let loading = {
|
||||||
|
branches: false
|
||||||
|
};
|
||||||
|
async function loadBranches() {
|
||||||
|
try {
|
||||||
|
loading.branches = true;
|
||||||
|
|
||||||
|
const protocol = publicRepositoryLink.split(':')[0];
|
||||||
|
const gitUrl = publicRepositoryLink.replace('http://', '').replace('https://', '');
|
||||||
|
|
||||||
|
let [host, ...path] = gitUrl.split('/');
|
||||||
|
const [owner, repository, ...branch] = path;
|
||||||
|
|
||||||
|
ownerName = owner;
|
||||||
|
repositoryName = repository;
|
||||||
|
|
||||||
|
if (host === 'github.com') {
|
||||||
|
host = 'api.github.com';
|
||||||
|
type = 'github';
|
||||||
|
if (branch[0] === 'tree' && branch[1]) {
|
||||||
|
branchName = branch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (host === 'gitlab.com') {
|
||||||
|
host = 'gitlab.com/api/v4';
|
||||||
|
type = 'gitlab';
|
||||||
|
if (branch[1] === 'tree' && branch[2]) {
|
||||||
|
branchName = branch[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const apiUrl = `${protocol}://${host}`;
|
||||||
|
if (type === 'github') {
|
||||||
|
const repositoryDetails = await get(`${apiUrl}/repos/${ownerName}/${repositoryName}`);
|
||||||
|
projectId = repositoryDetails.id.toString();
|
||||||
|
}
|
||||||
|
if (type === 'gitlab') {
|
||||||
|
const repositoryDetails = await get(`${apiUrl}/projects/${ownerName}%2F${repositoryName}`);
|
||||||
|
projectId = repositoryDetails.id.toString();
|
||||||
|
}
|
||||||
|
if (type === 'github' && branchName) {
|
||||||
|
try {
|
||||||
|
await get(`${apiUrl}/repos/${ownerName}/${repositoryName}/branches/${branchName}`);
|
||||||
|
await saveRepository();
|
||||||
|
loading.branches = false;
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === 'gitlab' && branchName) {
|
||||||
|
try {
|
||||||
|
await get(
|
||||||
|
`${apiUrl}/projects/${ownerName}%2F${repositoryName}/repository/branches/${branchName}`
|
||||||
|
);
|
||||||
|
await saveRepository();
|
||||||
|
loading.branches = false;
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let branches: any[] = [];
|
||||||
|
let page = 1;
|
||||||
|
let branchCount = 0;
|
||||||
|
const loadedBranches = await loadBranchesByPage(
|
||||||
|
apiUrl,
|
||||||
|
ownerName,
|
||||||
|
repositoryName,
|
||||||
|
page,
|
||||||
|
type
|
||||||
|
);
|
||||||
|
branches = branches.concat(loadedBranches);
|
||||||
|
branchCount = branches.length;
|
||||||
|
if (branchCount === 100) {
|
||||||
|
while (branchCount === 100) {
|
||||||
|
page = page + 1;
|
||||||
|
const nextBranches = await loadBranchesByPage(
|
||||||
|
apiUrl,
|
||||||
|
ownerName,
|
||||||
|
repositoryName,
|
||||||
|
page,
|
||||||
|
type
|
||||||
|
);
|
||||||
|
branches = branches.concat(nextBranches);
|
||||||
|
branchCount = nextBranches.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loading.branches = false;
|
||||||
|
branchSelectOptions = branches.map((branch: any) => ({
|
||||||
|
value: branch.name,
|
||||||
|
label: branch.name
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading.branches = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function loadBranchesByPage(
|
||||||
|
apiUrl: string,
|
||||||
|
owner: string,
|
||||||
|
repository: string,
|
||||||
|
page = 1,
|
||||||
|
type: string
|
||||||
|
) {
|
||||||
|
if (type === 'github') {
|
||||||
|
return await get(`${apiUrl}/repos/${owner}/${repository}/branches?per_page=100&page=${page}`);
|
||||||
|
}
|
||||||
|
if (type === 'gitlab') {
|
||||||
|
return await get(
|
||||||
|
`${apiUrl}/projects/${ownerName}%2F${repositoryName}/repository/branches?page=${page}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function saveRepository(event?: any) {
|
||||||
|
try {
|
||||||
|
if (event?.detail?.value) {
|
||||||
|
branchName = event.detail.value;
|
||||||
|
}
|
||||||
|
await post(`/applications/${id}/configuration/source`, {
|
||||||
|
gitSourceId: null,
|
||||||
|
forPublic: true,
|
||||||
|
type
|
||||||
|
});
|
||||||
|
await post(`/applications/${id}/configuration/repository`, {
|
||||||
|
repository: `${ownerName}/${repositoryName}`,
|
||||||
|
branch: branchName,
|
||||||
|
projectId,
|
||||||
|
autodeploy: false,
|
||||||
|
webhookToken: null,
|
||||||
|
isPublicRepository: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return await goto(`/applications/${id}/configuration/destination`);
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-5xl">
|
||||||
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
|
<div class="flex">
|
||||||
|
<form class="flex" on:submit|preventDefault={loadBranches}>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<input
|
||||||
|
placeholder="eg: https://github.com/coollabsio/nodejs-example/tree/main"
|
||||||
|
class="text-xs"
|
||||||
|
bind:value={publicRepositoryLink}
|
||||||
|
/>
|
||||||
|
{#if branchSelectOptions.length > 0}
|
||||||
|
<div class="custom-select-wrapper">
|
||||||
|
<Select
|
||||||
|
placeholder={loading.branches
|
||||||
|
? $t('application.configuration.loading_branches')
|
||||||
|
: !publicRepositoryLink
|
||||||
|
? $t('application.configuration.select_a_repository_first')
|
||||||
|
: $t('application.configuration.select_a_branch')}
|
||||||
|
isWaiting={loading.branches}
|
||||||
|
showIndicator={!!publicRepositoryLink && !loading.branches}
|
||||||
|
id="branches"
|
||||||
|
on:select={saveRepository}
|
||||||
|
items={branchSelectOptions}
|
||||||
|
isDisabled={loading.branches || !!!publicRepositoryLink}
|
||||||
|
isClearable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn mx-4 bg-orange-600" class:loading={loading.branches} type="submit"
|
||||||
|
>Load Repository</button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Explainer
|
||||||
|
text="Examples:<br><br>https://github.com/coollabsio/nodejs-example<br>https://github.com/coollabsio/nodejs-example/tree/main<br>https://gitlab.com/aleveha/fastify-example<br>https://gitlab.com/aleveha/fastify-example/-/tree/master<br><br>Only works with Github.com and Gitlab.com."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
export let branch: any;
|
export let branch: any;
|
||||||
export let type: any;
|
export let type: any;
|
||||||
export let application: any;
|
export let application: any;
|
||||||
|
export let isPublicRepository: boolean;
|
||||||
|
|
||||||
function checkPackageJSONContents({ key, json }: { key: any; json: any }) {
|
function checkPackageJSONContents({ key, json }: { key: any; json: any }) {
|
||||||
return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key);
|
return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key);
|
||||||
@@ -236,7 +237,7 @@
|
|||||||
if (error.message === 'Bad credentials') {
|
if (error.message === 'Bad credentials') {
|
||||||
const { token } = await get(`/applications/${id}/configuration/githubToken`);
|
const { token } = await get(`/applications/${id}/configuration/githubToken`);
|
||||||
$appSession.tokens.github = token;
|
$appSession.tokens.github = token;
|
||||||
return await scanRepository()
|
return await scanRepository();
|
||||||
}
|
}
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -245,7 +246,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await scanRepository();
|
if (!isPublicRepository) {
|
||||||
|
await scanRepository();
|
||||||
|
} else {
|
||||||
|
scanning = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -262,27 +267,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
<div class="max-w-5xl mx-auto ">
|
||||||
|
<div class="title pb-2">Coolify</div>
|
||||||
<div class="max-w-7xl mx-auto ">
|
|
||||||
<div class="title pb-2">Coolify Buildpacks</div>
|
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-wrap justify-center">
|
||||||
{#each buildPacks.filter(bp => bp.isCoolifyBuildPack === true) as buildPack}
|
{#each buildPacks.filter((bp) => bp.isCoolifyBuildPack === true) as buildPack}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
|
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto ">
|
<div class="max-w-5xl mx-auto ">
|
||||||
<div class="title pb-2">Heroku</div>
|
<div class="title pb-2">Other</div>
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-wrap justify-center">
|
||||||
{#each buildPacks.filter(bp => bp.isHerokuBuildPack === true) as buildPack}
|
{#each buildPacks.filter((bp) => bp.isHerokuBuildPack === true) as buildPack}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
|
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -48,3 +48,4 @@
|
|||||||
<GitlabRepositories {application} {appId} {settings} />
|
<GitlabRepositories {application} {appId} {settings} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { appSession } from '$lib/store';
|
import { appSession } from '$lib/store';
|
||||||
|
import PublicRepository from './_PublicRepository.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
const from = $page.url.searchParams.get('from');
|
const from = $page.url.searchParams.get('from');
|
||||||
@@ -71,120 +73,126 @@
|
|||||||
{$t('application.configuration.select_a_git_source')}
|
{$t('application.configuration.select_a_git_source')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center">
|
<div class="max-w-5xl mx-auto ">
|
||||||
{#if !filteredSources || ownSources.length === 0}
|
<div class="title pb-8">Git App</div>
|
||||||
<div class="flex-col">
|
<div class="flex flex-wrap justify-center">
|
||||||
<div class="pb-2 text-center font-bold">
|
{#if !filteredSources || ownSources.length === 0}
|
||||||
{$t('application.configuration.no_configurable_git')}
|
<div class="flex-col">
|
||||||
</div>
|
<div class="pb-2 text-center font-bold">
|
||||||
<div class="flex justify-center">
|
{$t('application.configuration.no_configurable_git')}
|
||||||
<a
|
</div>
|
||||||
href="/sources/new?from={$page.url.pathname}"
|
<div class="flex justify-center">
|
||||||
class="add-icon bg-orange-600 hover:bg-orange-500"
|
<a
|
||||||
>
|
href="/sources/new?from={$page.url.pathname}"
|
||||||
<svg
|
class="add-icon bg-orange-600 hover:bg-orange-500"
|
||||||
class="w-6"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
><path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
|
||||||
/></svg
|
|
||||||
>
|
>
|
||||||
</a>
|
<svg
|
||||||
|
class="w-6"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
><path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||||
|
/></svg
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{:else}
|
||||||
{:else}
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row ">
|
||||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row ">
|
{#each ownSources as source}
|
||||||
{#each ownSources as source}
|
<div class="p-2 relative">
|
||||||
<div class="p-2 relative">
|
<div class="absolute -m-4">
|
||||||
<div class="absolute -m-4">
|
{#if source?.type === 'gitlab'}
|
||||||
{#if source?.type === 'gitlab'}
|
<svg viewBox="0 0 128 128" class="w-8">
|
||||||
<svg viewBox="0 0 128 128" class="w-8">
|
<path
|
||||||
<path
|
fill="#FC6D26"
|
||||||
fill="#FC6D26"
|
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
|
||||||
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
|
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
|
||||||
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
|
fill="#FC6D26"
|
||||||
fill="#FC6D26"
|
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
|
||||||
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
|
|
||||||
/><path
|
|
||||||
fill="#FCA326"
|
|
||||||
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
|
|
||||||
/><path
|
|
||||||
fill="#E24329"
|
|
||||||
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
|
|
||||||
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
|
|
||||||
fill="#FCA326"
|
|
||||||
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
|
|
||||||
/><path
|
|
||||||
fill="#E24329"
|
|
||||||
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{:else if source?.type === 'github'}
|
|
||||||
<svg viewBox="0 0 128 128" class="w-8">
|
|
||||||
<g fill="#ffffff"
|
|
||||||
><path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
|
|
||||||
/><path
|
/><path
|
||||||
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
|
fill="#FCA326"
|
||||||
/></g
|
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
|
||||||
>
|
/><path
|
||||||
</svg>
|
fill="#E24329"
|
||||||
{/if}
|
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
|
||||||
|
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
|
||||||
|
fill="#FCA326"
|
||||||
|
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
|
||||||
|
/><path
|
||||||
|
fill="#E24329"
|
||||||
|
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{:else if source?.type === 'github'}
|
||||||
|
<svg viewBox="0 0 128 128" class="w-8">
|
||||||
|
<g fill="#ffffff"
|
||||||
|
><path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
|
||||||
|
/><path
|
||||||
|
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
|
||||||
|
/></g
|
||||||
|
>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
||||||
|
<button
|
||||||
|
disabled={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
type="submit"
|
||||||
|
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
|
||||||
|
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
>
|
||||||
|
<div class="font-bold text-xl text-center truncate">{source.name}</div>
|
||||||
|
{#if source.gitlabApp && !source.gitlabAppId}
|
||||||
|
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
{/each}
|
||||||
<button
|
</div>
|
||||||
disabled={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
type="submit"
|
|
||||||
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
|
|
||||||
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
>
|
|
||||||
<div class="font-bold text-xl text-center truncate">{source.name}</div>
|
|
||||||
{#if source.gitlabApp && !source.gitlabAppId}
|
|
||||||
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
|
||||||
Configuration missing
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if otherSources.length > 0 && $appSession.teamId === '0'}
|
{#if otherSources.length > 0 && $appSession.teamId === '0'}
|
||||||
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Sources</div>
|
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Sources</div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherSources as source}
|
||||||
|
<div class="p-2">
|
||||||
|
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
||||||
|
<button
|
||||||
|
disabled={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
type="submit"
|
||||||
|
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
|
||||||
|
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
>
|
||||||
|
<div class="font-bold text-xl text-center truncate">{source.name}</div>
|
||||||
|
{#if source.gitlabApp && !source.gitlabAppId}
|
||||||
|
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
||||||
|
{$t('application.configuration.configuration_missing')}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
</div>
|
||||||
{#each otherSources as source}
|
<div class="title py-4">Public Repository</div>
|
||||||
<div class="p-2">
|
|
||||||
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
<PublicRepository />
|
||||||
<button
|
|
||||||
disabled={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
type="submit"
|
|
||||||
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
|
|
||||||
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
>
|
|
||||||
<div class="font-bold text-xl text-center truncate">{source.name}</div>
|
|
||||||
{#if source.gitlabApp && !source.gitlabAppId}
|
|
||||||
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
|
||||||
{$t('application.configuration.configuration_missing')}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
if (stuff?.application?.id) {
|
if (stuff?.application?.id) {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
application: stuff.application
|
application: stuff.application,
|
||||||
|
settings: stuff.settings
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let application: any;
|
export let application: any;
|
||||||
|
export let settings: any;
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import Select from 'svelte-select';
|
import Select from 'svelte-select';
|
||||||
@@ -60,6 +62,7 @@
|
|||||||
let previews = application.settings.previews;
|
let previews = application.settings.previews;
|
||||||
let dualCerts = application.settings.dualCerts;
|
let dualCerts = application.settings.dualCerts;
|
||||||
let autodeploy = application.settings.autodeploy;
|
let autodeploy = application.settings.autodeploy;
|
||||||
|
let isBot = application.settings.isBot;
|
||||||
|
|
||||||
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||||
let isNonWWWDomainOK = false;
|
let isNonWWWDomainOK = false;
|
||||||
@@ -99,7 +102,7 @@
|
|||||||
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||||
await handleSubmit();
|
await handleSubmit();
|
||||||
}
|
}
|
||||||
domainEl.focus();
|
// !isBot && domainEl.focus();
|
||||||
await getUsage();
|
await getUsage();
|
||||||
usageInterval = setInterval(async () => {
|
usageInterval = setInterval(async () => {
|
||||||
await getUsage();
|
await getUsage();
|
||||||
@@ -129,11 +132,18 @@
|
|||||||
if (name === 'autodeploy') {
|
if (name === 'autodeploy') {
|
||||||
autodeploy = !autodeploy;
|
autodeploy = !autodeploy;
|
||||||
}
|
}
|
||||||
|
if (name === 'isBot') {
|
||||||
|
if ($status.application.isRunning) return;
|
||||||
|
isBot = !isBot;
|
||||||
|
application.settings.isBot = isBot;
|
||||||
|
setLocation(application, settings);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/settings`, {
|
await post(`/applications/${id}/settings`, {
|
||||||
previews,
|
previews,
|
||||||
debug,
|
debug,
|
||||||
dualCerts,
|
dualCerts,
|
||||||
|
isBot,
|
||||||
autodeploy,
|
autodeploy,
|
||||||
branch: application.branch,
|
branch: application.branch,
|
||||||
projectId: application.projectId
|
projectId: application.projectId
|
||||||
@@ -155,24 +165,28 @@
|
|||||||
if (name === 'autodeploy') {
|
if (name === 'autodeploy') {
|
||||||
autodeploy = !autodeploy;
|
autodeploy = !autodeploy;
|
||||||
}
|
}
|
||||||
|
if (name === 'isBot') {
|
||||||
|
isBot = !isBot;
|
||||||
|
}
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
if (loading || !application.fqdn) return;
|
if (loading || (!application.fqdn && !isBot)) return;
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||||
if (application.deploymentType)
|
if (application.deploymentType)
|
||||||
application.deploymentType = application.deploymentType.toLowerCase();
|
application.deploymentType = application.deploymentType.toLowerCase();
|
||||||
await post(`/applications/${id}/check`, {
|
!isBot &&
|
||||||
fqdn: application.fqdn,
|
(await post(`/applications/${id}/check`, {
|
||||||
forceSave,
|
fqdn: application.fqdn,
|
||||||
dualCerts,
|
forceSave,
|
||||||
exposePort: application.exposePort
|
dualCerts,
|
||||||
});
|
exposePort: application.exposePort
|
||||||
|
}));
|
||||||
await post(`/applications/${id}`, { ...application });
|
await post(`/applications/${id}`, { ...application });
|
||||||
setLocation(application);
|
setLocation(application, settings);
|
||||||
$disabledButton = false;
|
$disabledButton = false;
|
||||||
forceSave = false;
|
forceSave = false;
|
||||||
addToast({
|
addToast({
|
||||||
@@ -332,8 +346,11 @@
|
|||||||
<label for="gitSource" class="text-base font-bold text-stone-100"
|
<label for="gitSource" class="text-base font-bold text-stone-100"
|
||||||
>{$t('application.git_source')}</label
|
>{$t('application.git_source')}</label
|
||||||
>
|
>
|
||||||
{#if isDisabled}
|
{#if isDisabled || application.settings.isPublicRepository}
|
||||||
<input disabled={isDisabled} value={application.gitSource.name} />
|
<input
|
||||||
|
disabled={isDisabled || application.settings.isPublicRepository}
|
||||||
|
value={application.gitSource.name}
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<a
|
<a
|
||||||
href={`/applications/${id}/configuration/source?from=/applications/${id}`}
|
href={`/applications/${id}/configuration/source?from=/applications/${id}`}
|
||||||
@@ -350,8 +367,11 @@
|
|||||||
<label for="repository" class="text-base font-bold text-stone-100"
|
<label for="repository" class="text-base font-bold text-stone-100"
|
||||||
>{$t('application.git_repository')}</label
|
>{$t('application.git_repository')}</label
|
||||||
>
|
>
|
||||||
{#if isDisabled}
|
{#if isDisabled || application.settings.isPublicRepository}
|
||||||
<input disabled={isDisabled} value="{application.repository}/{application.branch}" />
|
<input
|
||||||
|
disabled={isDisabled || application.settings.isPublicRepository}
|
||||||
|
value="{application.repository}/{application.branch}"
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<a
|
<a
|
||||||
href={`/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`}
|
href={`/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`}
|
||||||
@@ -371,16 +391,16 @@
|
|||||||
{#if isDisabled}
|
{#if isDisabled}
|
||||||
<input class="capitalize" disabled={isDisabled} value={application.buildPack} />
|
<input class="capitalize" disabled={isDisabled} value={application.buildPack} />
|
||||||
{:else}
|
{:else}
|
||||||
<a
|
<a
|
||||||
href={`/applications/${id}/configuration/buildpack?from=/applications/${id}`}
|
href={`/applications/${id}/configuration/buildpack?from=/applications/${id}`}
|
||||||
class="no-underline "
|
class="no-underline "
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
value={application.buildPack}
|
value={application.buildPack}
|
||||||
id="buildPack"
|
id="buildPack"
|
||||||
class="cursor-pointer hover:bg-coolgray-500 capitalize"
|
class="cursor-pointer hover:bg-coolgray-500 capitalize"
|
||||||
/></a
|
/></a
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center pb-8">
|
<div class="grid grid-cols-2 items-center pb-8">
|
||||||
@@ -468,77 +488,89 @@
|
|||||||
<div class="title">{$t('application.application')}</div>
|
<div class="title">{$t('application.application')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
<div class="grid grid-cols-2">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="flex-col">
|
<Setting
|
||||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
isCenter={false}
|
||||||
>{$t('application.url_fqdn')}</label
|
bind:setting={isBot}
|
||||||
>
|
on:click={() => changeSettings('isBot')}
|
||||||
{#if browser && window.location.hostname === 'demo.coolify.io'}
|
title="Is your application a bot?"
|
||||||
<Explainer
|
description="You can deploy applications without domains. <br>You can also make them to listen on <span class='text-green-500 font-bold'>IP:EXPOSEDPORT</span> as well.<br></Setting><br>Useful to host <span class='text-green-500 font-bold'>Twitch bots, regular jobs, or anything that does not require an incoming connection.</span>"
|
||||||
text="<span class='text-white font-bold'>You can use the predefined random url name or enter your own domain name.</span>"
|
disabled={$status.application.isRunning}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if !isBot}
|
||||||
|
<div class="grid grid-cols-2">
|
||||||
|
<div class="flex-col">
|
||||||
|
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
||||||
|
>{$t('application.url_fqdn')}</label
|
||||||
|
>
|
||||||
|
{#if browser && window.location.hostname === 'demo.coolify.io'}
|
||||||
|
<Explainer
|
||||||
|
text="<span class='text-white font-bold'>You can use the predefined random url name or enter your own domain name.</span>"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<Explainer text={$t('application.https_explainer')} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
readonly={isDisabled}
|
||||||
|
disabled={isDisabled}
|
||||||
|
bind:this={domainEl}
|
||||||
|
name="fqdn"
|
||||||
|
id="fqdn"
|
||||||
|
required
|
||||||
|
bind:value={application.fqdn}
|
||||||
|
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||||
|
placeholder="eg: https://coollabs.io"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{#if forceSave}
|
||||||
<Explainer text={$t('application.https_explainer')} />
|
<div class="flex-col space-y-2 pt-4 text-center">
|
||||||
</div>
|
{#if isNonWWWDomainOK}
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
readonly={isDisabled}
|
|
||||||
disabled={isDisabled}
|
|
||||||
bind:this={domainEl}
|
|
||||||
name="fqdn"
|
|
||||||
id="fqdn"
|
|
||||||
required
|
|
||||||
bind:value={application.fqdn}
|
|
||||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
|
||||||
placeholder="eg: https://coollabs.io"
|
|
||||||
/>
|
|
||||||
{#if forceSave}
|
|
||||||
<div class="flex-col space-y-2 pt-4 text-center">
|
|
||||||
{#if isNonWWWDomainOK}
|
|
||||||
<button
|
|
||||||
class="bg-green-600 hover:bg-green-500"
|
|
||||||
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
|
||||||
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
class="bg-red-600 hover:bg-red-500"
|
|
||||||
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
|
||||||
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
{#if dualCerts}
|
|
||||||
{#if isWWWDomainOK}
|
|
||||||
<button
|
<button
|
||||||
class="bg-green-600 hover:bg-green-500"
|
class="btn btn-sm bg-green-600 hover:bg-green-500"
|
||||||
on:click|preventDefault={() =>
|
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
||||||
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
|
||||||
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
|
|
||||||
>
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
class="bg-red-600 hover:bg-red-500"
|
class="btn btn-sm bg-red-600 hover:bg-red-500"
|
||||||
on:click|preventDefault={() =>
|
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
||||||
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
|
||||||
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
|
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{#if dualCerts}
|
||||||
</div>
|
{#if isWWWDomainOK}
|
||||||
{/if}
|
<button
|
||||||
|
class="btn btn-sm bg-green-600 hover:bg-green-500"
|
||||||
|
on:click|preventDefault={() =>
|
||||||
|
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
||||||
|
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
class="btn btn-sm bg-red-600 hover:bg-red-500"
|
||||||
|
on:click|preventDefault={() =>
|
||||||
|
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
||||||
|
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-2 items-center pb-8">
|
||||||
<div class="grid grid-cols-2 items-center pb-8">
|
<Setting
|
||||||
<Setting
|
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
disabled={$status.application.isRunning}
|
||||||
disabled={$status.application.isRunning}
|
isCenter={false}
|
||||||
isCenter={false}
|
bind:setting={dualCerts}
|
||||||
bind:setting={dualCerts}
|
title={$t('application.ssl_www_and_non_www')}
|
||||||
title={$t('application.ssl_www_and_non_www')}
|
description={$t('application.ssl_explainer')}
|
||||||
description={$t('application.ssl_explainer')}
|
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
|
||||||
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{#if application.buildPack === 'python'}
|
{#if application.buildPack === 'python'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="pythonModule" class="text-base font-bold text-stone-100">WSGI / ASGI</label>
|
<label for="pythonModule" class="text-base font-bold text-stone-100">WSGI / ASGI</label>
|
||||||
@@ -599,6 +631,9 @@
|
|||||||
bind:value={application.port}
|
bind:value={application.port}
|
||||||
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
|
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
|
||||||
/>
|
/>
|
||||||
|
<Explainer
|
||||||
|
text={'The port your application listens on.'}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
@@ -745,24 +780,28 @@
|
|||||||
<div class="title">{$t('application.features')}</div>
|
<div class="title">{$t('application.features')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-10 pb-10">
|
<div class="px-10 pb-10">
|
||||||
<div class="grid grid-cols-2 items-center">
|
{#if !application.settings.isPublicRepository}
|
||||||
<Setting
|
<div class="grid grid-cols-2 items-center">
|
||||||
isCenter={false}
|
<Setting
|
||||||
bind:setting={autodeploy}
|
isCenter={false}
|
||||||
on:click={() => changeSettings('autodeploy')}
|
bind:setting={autodeploy}
|
||||||
title={$t('application.enable_automatic_deployment')}
|
on:click={() => changeSettings('autodeploy')}
|
||||||
description={$t('application.enable_auto_deploy_webhooks')}
|
title={$t('application.enable_automatic_deployment')}
|
||||||
/>
|
description={$t('application.enable_auto_deploy_webhooks')}
|
||||||
</div>
|
/>
|
||||||
<div class="grid grid-cols-2 items-center">
|
</div>
|
||||||
<Setting
|
{/if}
|
||||||
isCenter={false}
|
{#if !application.settings.isBot}
|
||||||
bind:setting={previews}
|
<div class="grid grid-cols-2 items-center">
|
||||||
on:click={() => changeSettings('previews')}
|
<Setting
|
||||||
title={$t('application.enable_mr_pr_previews')}
|
isCenter={false}
|
||||||
description={$t('application.enable_preview_deploy_mr_pr_requests')}
|
bind:setting={previews}
|
||||||
/>
|
on:click={() => changeSettings('previews')}
|
||||||
</div>
|
title={$t('application.enable_mr_pr_previews')}
|
||||||
|
description={$t('application.enable_preview_deploy_mr_pr_requests')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
isCenter={false}
|
isCenter={false}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
<div class="flex justify-end sticky top-0 p-2 mx-1">
|
<div class="flex justify-end sticky top-0 p-2 mx-1">
|
||||||
<button
|
<button
|
||||||
on:click={followBuild}
|
on:click={followBuild}
|
||||||
class="bg-transparent btn btn-sm tooltip tooltip-primary tooltip-bottom hover:text-green-500 hover:bg-coolgray-500"
|
class="bg-transparent btn btn-sm btn-link tooltip tooltip-primary tooltip-bottom hover:text-green-500 hover:bg-coolgray-500"
|
||||||
data-tip="Follow logs"
|
data-tip="Follow logs"
|
||||||
class:text-green-500={followingBuild}
|
class:text-green-500={followingBuild}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -147,7 +147,7 @@
|
|||||||
<div class="flex justify-end sticky top-0 p-1 mx-1">
|
<div class="flex justify-end sticky top-0 p-1 mx-1">
|
||||||
<button
|
<button
|
||||||
on:click={followBuild}
|
on:click={followBuild}
|
||||||
class="bg-transparent btn btn-sm tooltip tooltip-primary tooltip-bottom"
|
class="bg-transparent btn btn-sm btn-link tooltip tooltip-primary tooltip-bottom"
|
||||||
data-tip="Follow logs"
|
data-tip="Follow logs"
|
||||||
class:text-green-500={followingLogs}
|
class:text-green-500={followingLogs}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -43,7 +43,8 @@
|
|||||||
const batchSecretsPairs = eachValuePair
|
const batchSecretsPairs = eachValuePair
|
||||||
.filter((secret) => !secret.startsWith('#') && secret)
|
.filter((secret) => !secret.startsWith('#') && secret)
|
||||||
.map((secret) => {
|
.map((secret) => {
|
||||||
const [name, value] = secret.split('=');
|
const [name, ...rest] = secret.split('=');
|
||||||
|
const value = rest.join('=');
|
||||||
const cleanValue = value?.replaceAll('"', '') || '';
|
const cleanValue = value?.replaceAll('"', '') || '';
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -87,9 +87,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
<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={$t('application.storage.persistent_storage_explainer')} />
|
|
||||||
</div>
|
|
||||||
<table class="mx-auto border-separate text-left">
|
<table class="mx-auto border-separate text-left">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="h-12">
|
<tr class="h-12">
|
||||||
@@ -109,4 +107,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<div class="flex justify-center py-4 text-center">
|
||||||
|
<Explainer customClass="w-full" text={$t('application.storage.persistent_storage_explainer')} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col justify-center">
|
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
|
||||||
{#if !applications || ownApplications.length === 0}
|
{#if !applications || ownApplications.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
|
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
|
||||||
@@ -87,6 +87,9 @@
|
|||||||
{#if application.fqdn}
|
{#if application.fqdn}
|
||||||
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
|
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if application.settings.isBot}
|
||||||
|
<div class="truncate text-center">BOT</div>
|
||||||
|
{/if}
|
||||||
{#if application.destinationDocker?.name}
|
{#if application.destinationDocker?.name}
|
||||||
<div class="truncate text-center">{application.destinationDocker.name}</div>
|
<div class="truncate text-center">{application.destinationDocker.name}</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -98,7 +101,7 @@
|
|||||||
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
Destination Missing
|
Destination Missing
|
||||||
</div>
|
</div>
|
||||||
{:else if !application.fqdn}
|
{:else if !application.fqdn && !application.settings.isBot}
|
||||||
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
URL Missing
|
URL Missing
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-col justify-center">
|
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
|
||||||
{#if !databases || ownDatabases.length === 0}
|
{#if !databases || ownDatabases.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>
|
<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col justify-center">
|
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
|
||||||
{#if !destinations || ownDestinations.length === 0}
|
{#if !destinations || ownDestinations.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">{$t('destination.no_destination_found')}</div>
|
<div class="text-center text-xl font-bold">{$t('destination.no_destination_found')}</div>
|
||||||
|
|||||||
@@ -212,6 +212,7 @@
|
|||||||
<div class="flex items-center justify-center pt-3">
|
<div class="flex items-center justify-center pt-3">
|
||||||
<button
|
<button
|
||||||
on:click|preventDefault={() => switchTeam(team.id)}
|
on:click|preventDefault={() => switchTeam(team.id)}
|
||||||
|
class="btn btn-sm"
|
||||||
class:bg-fuchsia-600={$appSession.teamId !== team.id}
|
class:bg-fuchsia-600={$appSession.teamId !== team.id}
|
||||||
class:hover:bg-fuchsia-500={$appSession.teamId !== team.id}
|
class:hover:bg-fuchsia-500={$appSession.teamId !== team.id}
|
||||||
class:bg-transparent={$appSession.teamId === team.id}
|
class:bg-transparent={$appSession.teamId === team.id}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
export let applications: any;
|
||||||
|
export let databases: any;
|
||||||
|
export let services: any;
|
||||||
|
export let settings: any;
|
||||||
|
|
||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import Usage from '$lib/components/Usage.svelte';
|
import Usage from '$lib/components/Usage.svelte';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
@@ -29,26 +34,24 @@
|
|||||||
import ApplicationsIcons from '$lib/components/svg/applications/ApplicationIcons.svelte';
|
import ApplicationsIcons from '$lib/components/svg/applications/ApplicationIcons.svelte';
|
||||||
import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte';
|
import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte';
|
||||||
import ServiceIcons from '$lib/components/svg/services/ServiceIcons.svelte';
|
import ServiceIcons from '$lib/components/svg/services/ServiceIcons.svelte';
|
||||||
|
import { dev } from '$app/env';
|
||||||
|
|
||||||
let loading = {
|
let loading = {
|
||||||
cleanup: false
|
cleanup: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export let applications: any;
|
let numberOfGetStatus = 0;
|
||||||
export let databases: any;
|
|
||||||
export let services: any;
|
|
||||||
let numberOfGetStatus = 0;
|
|
||||||
|
|
||||||
function getRndInteger(min: number, max: number) {
|
function getRndInteger(min: number, max: number) {
|
||||||
return Math.floor(Math.random() * (max - min + 1) ) + min;
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getStatus(resources: any) {
|
async function getStatus(resources: any) {
|
||||||
while (numberOfGetStatus > 1){
|
while (numberOfGetStatus > 1) {
|
||||||
await asyncSleep(getRndInteger(100,200));
|
await asyncSleep(getRndInteger(100, 200));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
numberOfGetStatus++;
|
numberOfGetStatus++;
|
||||||
const { id, buildPack, dualCerts } = resources;
|
const { id, buildPack, dualCerts } = resources;
|
||||||
let isRunning = false;
|
let isRunning = false;
|
||||||
if (buildPack) {
|
if (buildPack) {
|
||||||
@@ -69,8 +72,8 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return 'Error';
|
return 'Error';
|
||||||
} finally {
|
} finally {
|
||||||
numberOfGetStatus--;
|
numberOfGetStatus--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function manuallyCleanupStorage() {
|
async function manuallyCleanupStorage() {
|
||||||
try {
|
try {
|
||||||
@@ -90,48 +93,100 @@
|
|||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
<div class="mr-4 text-2xl tracking-tight">{$t('index.dashboard')}</div>
|
<div class="mr-4 text-2xl tracking-tight">{$t('index.dashboard')}</div>
|
||||||
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
|
{#if $appSession.teamId === '0'}
|
||||||
>Cleanup Storage</button
|
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
|
||||||
>
|
>Cleanup Storage</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-10 pb-12 tracking-tight sm:pb-16">
|
<div class="mt-10 pb-12 sm:pb-16">
|
||||||
<div class="mx-auto px-10">
|
<div class="mx-auto px-10">
|
||||||
<div class="flex flex-col justify-center xl:flex-row">
|
<div class="flex flex-col justify-center xl:flex-row">
|
||||||
{#if applications.length > 0}
|
{#if applications.length > 0}
|
||||||
<div>
|
<div>
|
||||||
<div class="title">Resources</div>
|
<div class="title">Resources</div>
|
||||||
<div class="flex items-start justify-center p-8">
|
<div class="flex items-start justify-center p-8">
|
||||||
<table class="rounded-none text-base">
|
<table class="rounded-none text-base">
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each applications as application}
|
{#each applications as application}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="space-x-2 items-center tracking-tight font-bold">
|
<td class="space-x-2 items-center tracking-tight font-bold">
|
||||||
{#await getStatus(application)}
|
{#await getStatus(application)}
|
||||||
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
|
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
|
||||||
{:then status}
|
{:then status}
|
||||||
{#if status === 'Running'}
|
{#if status === 'Running'}
|
||||||
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
|
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
|
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
<div class="inline-flex">{application.name}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-10 inline-flex">
|
||||||
|
<ApplicationsIcons {application} isAbsolute={false} />
|
||||||
|
</td>
|
||||||
|
<td class="px-10">
|
||||||
|
<div
|
||||||
|
class="badge badge-outline text-xs border-applications rounded text-white"
|
||||||
|
>
|
||||||
|
Application
|
||||||
|
{#if application.settings.isBot}
|
||||||
|
| BOT
|
||||||
|
{/if}
|
||||||
|
</div></td
|
||||||
|
>
|
||||||
|
<td class="flex justify-end">
|
||||||
|
{#if application.fqdn}
|
||||||
|
<a
|
||||||
|
href={application.fqdn}
|
||||||
|
target="_blank"
|
||||||
|
class="icons bg-transparent text-sm inline-flex"
|
||||||
|
><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" />
|
||||||
|
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||||
|
<line x1="10" y1="14" x2="20" y2="4" />
|
||||||
|
<polyline points="15 4 20 4 20 9" />
|
||||||
|
</svg></a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{#if application.settings.isBot && application.exposePort}
|
||||||
|
<a
|
||||||
|
href={`http://${dev ? 'localhost' : settings.ipv4}:${
|
||||||
|
application.exposePort
|
||||||
|
}`}
|
||||||
|
target="_blank"
|
||||||
|
class="icons bg-transparent text-sm inline-flex"
|
||||||
|
><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" />
|
||||||
|
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||||
|
<line x1="10" y1="14" x2="20" y2="4" />
|
||||||
|
<polyline points="15 4 20 4 20 9" />
|
||||||
|
</svg></a
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{/await}
|
|
||||||
<div class="inline-flex">{application.name}</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-10 inline-flex">
|
|
||||||
<ApplicationsIcons {application} isAbsolute={false} />
|
|
||||||
</td>
|
|
||||||
<td class="px-10">
|
|
||||||
<div class="badge badge-outline text-xs border-applications rounded text-white">
|
|
||||||
Application
|
|
||||||
</div></td
|
|
||||||
>
|
|
||||||
<td class="flex justify-end">
|
|
||||||
{#if application.fqdn}
|
|
||||||
<a
|
<a
|
||||||
href={application.fqdn}
|
href={`/applications/${application.id}`}
|
||||||
target="_blank"
|
|
||||||
class="icons bg-transparent text-sm inline-flex"
|
class="icons bg-transparent text-sm inline-flex"
|
||||||
><svg
|
>
|
||||||
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="h-6 w-6"
|
class="h-6 w-6"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -142,72 +197,72 @@
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
<rect x="4" y="8" width="4" height="4" />
|
||||||
<line x1="10" y1="14" x2="20" y2="4" />
|
<line x1="6" y1="4" x2="6" y2="8" />
|
||||||
<polyline points="15 4 20 4 20 9" />
|
<line x1="6" y1="12" x2="6" y2="20" />
|
||||||
</svg></a
|
<rect x="10" y="14" width="4" height="4" />
|
||||||
>
|
<line x1="12" y1="4" x2="12" y2="14" />
|
||||||
{/if}
|
<line x1="12" y1="18" x2="12" y2="20" />
|
||||||
<a
|
<rect x="16" y="5" width="4" height="4" />
|
||||||
href={`/applications/${application.id}`}
|
<line x1="18" y1="4" x2="18" y2="5" />
|
||||||
class="icons bg-transparent text-sm inline-flex"
|
<line x1="18" y1="9" x2="18" y2="20" />
|
||||||
>
|
</svg>
|
||||||
<svg
|
</a>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
</td>
|
||||||
class="h-6 w-6"
|
</tr>
|
||||||
viewBox="0 0 24 24"
|
{/each}
|
||||||
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>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
{#each services as service}
|
{#each services as service}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="space-x-2 items-center tracking-tight font-bold">
|
<td class="space-x-2 items-center tracking-tight font-bold">
|
||||||
{#await getStatus(service)}
|
{#await getStatus(service)}
|
||||||
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
|
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
|
||||||
{:then status}
|
{:then status}
|
||||||
{#if status === 'Running'}
|
{#if status === 'Running'}
|
||||||
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
|
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
|
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
<div class="inline-flex">{service.name}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-10 inline-flex">
|
||||||
|
<ServiceIcons type={service.type} isAbsolute={false} />
|
||||||
|
</td>
|
||||||
|
<td class="px-10"
|
||||||
|
><div class="badge badge-outline text-xs border-services rounded text-white">
|
||||||
|
Service
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="flex justify-end">
|
||||||
|
{#if service.fqdn}
|
||||||
|
<a
|
||||||
|
href={service.fqdn}
|
||||||
|
target="_blank"
|
||||||
|
class="icons bg-transparent text-sm inline-flex"
|
||||||
|
><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" />
|
||||||
|
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||||
|
<line x1="10" y1="14" x2="20" y2="4" />
|
||||||
|
<polyline points="15 4 20 4 20 9" />
|
||||||
|
</svg></a
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{/await}
|
|
||||||
<div class="inline-flex">{service.name}</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-10 inline-flex">
|
|
||||||
<ServiceIcons type={service.type} isAbsolute={false} />
|
|
||||||
</td>
|
|
||||||
<td class="px-10"
|
|
||||||
><div class="badge badge-outline text-xs border-services rounded text-white">
|
|
||||||
Service
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="flex justify-end">
|
|
||||||
{#if service.fqdn}
|
|
||||||
<a
|
<a
|
||||||
href={service.fqdn}
|
href={`/services/${service.id}`}
|
||||||
target="_blank"
|
|
||||||
class="icons bg-transparent text-sm inline-flex"
|
class="icons bg-transparent text-sm inline-flex"
|
||||||
><svg
|
>
|
||||||
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="h-6 w-6"
|
class="h-6 w-6"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -218,97 +273,78 @@
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
<rect x="4" y="8" width="4" height="4" />
|
||||||
<line x1="10" y1="14" x2="20" y2="4" />
|
<line x1="6" y1="4" x2="6" y2="8" />
|
||||||
<polyline points="15 4 20 4 20 9" />
|
<line x1="6" y1="12" x2="6" y2="20" />
|
||||||
</svg></a
|
<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>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
{#each databases as database}
|
||||||
|
<tr>
|
||||||
|
<td class="space-x-2 items-center tracking-tight font-bold">
|
||||||
|
{#await getStatus(database)}
|
||||||
|
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
|
||||||
|
{:then status}
|
||||||
|
{#if status === 'Running'}
|
||||||
|
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
|
||||||
|
{:else}
|
||||||
|
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
<div class="inline-flex">{database.name}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-10 inline-flex">
|
||||||
|
<DatabaseIcons type={database.type} />
|
||||||
|
</td>
|
||||||
|
<td class="px-10">
|
||||||
|
<div class="badge badge-outline text-xs border-databases rounded text-white">
|
||||||
|
Database
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="flex justify-end">
|
||||||
|
<a
|
||||||
|
href={`/databases/${database.id}`}
|
||||||
|
class="icons bg-transparent text-sm inline-flex ml-11"
|
||||||
>
|
>
|
||||||
{/if}
|
<svg
|
||||||
<a
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
href={`/services/${service.id}`}
|
class="h-6 w-6"
|
||||||
class="icons bg-transparent text-sm inline-flex"
|
viewBox="0 0 24 24"
|
||||||
>
|
stroke-width="1.5"
|
||||||
<svg
|
stroke="currentColor"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
fill="none"
|
||||||
class="h-6 w-6"
|
stroke-linecap="round"
|
||||||
viewBox="0 0 24 24"
|
stroke-linejoin="round"
|
||||||
stroke-width="1.5"
|
>
|
||||||
stroke="currentColor"
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
fill="none"
|
<rect x="4" y="8" width="4" height="4" />
|
||||||
stroke-linecap="round"
|
<line x1="6" y1="4" x2="6" y2="8" />
|
||||||
stroke-linejoin="round"
|
<line x1="6" y1="12" x2="6" y2="20" />
|
||||||
>
|
<rect x="10" y="14" width="4" height="4" />
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<line x1="12" y1="4" x2="12" y2="14" />
|
||||||
<rect x="4" y="8" width="4" height="4" />
|
<line x1="12" y1="18" x2="12" y2="20" />
|
||||||
<line x1="6" y1="4" x2="6" y2="8" />
|
<rect x="16" y="5" width="4" height="4" />
|
||||||
<line x1="6" y1="12" x2="6" y2="20" />
|
<line x1="18" y1="4" x2="18" y2="5" />
|
||||||
<rect x="10" y="14" width="4" height="4" />
|
<line x1="18" y1="9" x2="18" y2="20" />
|
||||||
<line x1="12" y1="4" x2="12" y2="14" />
|
</svg>
|
||||||
<line x1="12" y1="18" x2="12" y2="20" />
|
</a>
|
||||||
<rect x="16" y="5" width="4" height="4" />
|
</td>
|
||||||
<line x1="18" y1="4" x2="18" y2="5" />
|
</tr>
|
||||||
<line x1="18" y1="9" x2="18" y2="20" />
|
{/each}
|
||||||
</svg>
|
</tbody>
|
||||||
</a>
|
</table>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
{#each databases as database}
|
|
||||||
<tr>
|
|
||||||
<td class="space-x-2 items-center tracking-tight font-bold">
|
|
||||||
{#await getStatus(database)}
|
|
||||||
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
|
|
||||||
{:then status}
|
|
||||||
{#if status === 'Running'}
|
|
||||||
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
|
|
||||||
{:else}
|
|
||||||
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
|
|
||||||
{/if}
|
|
||||||
{/await}
|
|
||||||
<div class="inline-flex">{database.name}</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-10 inline-flex">
|
|
||||||
<DatabaseIcons type={database.type} />
|
|
||||||
</td>
|
|
||||||
<td class="px-10">
|
|
||||||
<div class="badge badge-outline text-xs border-databases rounded text-white">
|
|
||||||
Database
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="flex justify-end">
|
|
||||||
<a
|
|
||||||
href={`/databases/${database.id}`}
|
|
||||||
class="icons bg-transparent text-sm inline-flex ml-11"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{:else}
|
||||||
|
<div class="text-center text-xl font-bold">Nothing is configured yet.</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $appSession.teamId === '0'}
|
{#if $appSession.teamId === '0'}
|
||||||
<Usage />
|
<Usage />
|
||||||
|
|||||||
@@ -57,10 +57,18 @@
|
|||||||
</a>
|
</a>
|
||||||
{:else if service.type === 'appwrite'}
|
{:else if service.type === 'appwrite'}
|
||||||
<a href="https://appwrite.io" target="_blank">
|
<a href="https://appwrite.io" target="_blank">
|
||||||
<Icons.Appwrite/>
|
<Icons.Appwrite />
|
||||||
</a>
|
</a>
|
||||||
{:else if service.type === 'moodle'}
|
{:else if service.type === 'moodle'}
|
||||||
<a href="https://moodle.org" target="_blank">
|
<a href="https://moodle.org" target="_blank">
|
||||||
<Icons.Moodle />
|
<Icons.Moodle />
|
||||||
</a>
|
</a>
|
||||||
|
{:else if service.type === 'glitchTip'}
|
||||||
|
<a href="https://glitchtip.com" target="_blank">
|
||||||
|
<Icons.GlitchTip />
|
||||||
|
</a>
|
||||||
|
{:else if service.type === 'searxng'}
|
||||||
|
<a href="https://searxng.org" target="_blank">
|
||||||
|
<Icons.Searxng />
|
||||||
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
export let readOnly: any;
|
export let readOnly: any;
|
||||||
export let service: any;
|
export let service: any;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5">
|
||||||
<div class="title">Ghost</div>
|
<div class="title">Ghost</div>
|
||||||
|
<Explainer text={'You can change these values in the Ghost admin panel.'} />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="email">{$t('forms.default_email_address')}</label>
|
<label for="email">{$t('forms.default_email_address')}</label>
|
||||||
|
|||||||
208
apps/ui/src/routes/services/[id]/_Services/_GlitchTip.svelte
Normal file
208
apps/ui/src/routes/services/[id]/_Services/_GlitchTip.svelte
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
export let service: any;
|
||||||
|
function toggleEmailSmtpUseTls() {
|
||||||
|
service.glitchTip.emailSmtpUseTls = !service.glitchTip.emailSmtpUseTls;
|
||||||
|
}
|
||||||
|
function toggleEmailSmtpUseSsl() {
|
||||||
|
service.glitchTip.emailSmtpUseSsl = !service.glitchTip.emailSmtpUseSsl;
|
||||||
|
}
|
||||||
|
function toggleEnableOpenUserRegistration() {
|
||||||
|
service.glitchTip.enableOpenUserRegistration = !service.glitchTip.enableOpenUserRegistration;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">GlitchTip</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-2 font-bold">
|
||||||
|
<div class="subtitle">Settings</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<Setting
|
||||||
|
bind:setting={service.glitchTip.enableOpenUserRegistration}
|
||||||
|
on:click={toggleEnableOpenUserRegistration}
|
||||||
|
title={'Enable Open User Registration'}
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-2 font-bold">
|
||||||
|
<div class="subtitle">Email settings</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultEmailFrom" class="text-base font-bold text-stone-100">Default Email From</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
required
|
||||||
|
name="defaultEmailFrom"
|
||||||
|
id="defaultEmailFrom"
|
||||||
|
value={service.glitchTip.defaultEmailFrom}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpHost" class="text-base font-bold text-stone-100">SMTP Host</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpHost"
|
||||||
|
id="emailSmtpHost"
|
||||||
|
value={service.glitchTip.emailSmtpHost}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpPort" class="text-base font-bold text-stone-100">SMTP Port</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpPort"
|
||||||
|
id="emailSmtpPort"
|
||||||
|
value={service.glitchTip.emailSmtpPort}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpUser" class="text-base font-bold text-stone-100">SMTP User</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpUser"
|
||||||
|
id="emailSmtpUser"
|
||||||
|
value={service.glitchTip.emailSmtpUser}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpPassword" class="text-base font-bold text-stone-100">SMTP Password</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpPassword"
|
||||||
|
id="emailSmtpPassword"
|
||||||
|
value={service.glitchTip.emailSmtpPassword}
|
||||||
|
isPasswordField
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<Setting
|
||||||
|
bind:setting={service.glitchTip.emailSmtpUseTls}
|
||||||
|
on:click={toggleEmailSmtpUseTls}
|
||||||
|
title={'SMTP Use TLS'}
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<Setting
|
||||||
|
bind:setting={service.glitchTip.emailSmtpUseSsl}
|
||||||
|
on:click={toggleEmailSmtpUseSsl}
|
||||||
|
title={'SMTP Use SSL'}
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailBackend" class="text-base font-bold text-stone-100">Email Backend</label>
|
||||||
|
<CopyPasswordField name="emailBackend" id="emailBackend" value={service.glitchTip.emailBackend} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="mailgunApiKey" class="text-base font-bold text-stone-100">Mailgun API Key</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="mailgunApiKey"
|
||||||
|
id="mailgunApiKey"
|
||||||
|
value={service.glitchTip.mailgunApiKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="sendgridApiKey" class="text-base font-bold text-stone-100">SendGrid API Key</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="sendgridApiKey"
|
||||||
|
id="sendgridApiKey"
|
||||||
|
value={service.glitchTip.sendgridApiKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-2 font-bold">
|
||||||
|
<div class="subtitle">Default User & Superuser</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultEmail" class="text-base font-bold text-stone-100">{$t('forms.email')}</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="defaultEmail"
|
||||||
|
id="defaultEmail"
|
||||||
|
value={service.glitchTip.defaultEmail}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultUsername" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.username')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="defaultUsername"
|
||||||
|
id="defaultUsername"
|
||||||
|
value={service.glitchTip.defaultUsername}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultPassword" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.password')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="defaultPassword"
|
||||||
|
id="defaultPassword"
|
||||||
|
value={service.glitchTip.defaultPassword}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
isPasswordField
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">PostgreSQL</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlUser" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.username')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlUser"
|
||||||
|
id="postgresqlUser"
|
||||||
|
value={service.glitchTip.postgresqlUser}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlPassword" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.password')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
id="postgresqlPassword"
|
||||||
|
isPasswordField
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
name="postgresqlPassword"
|
||||||
|
value={service.glitchTip.postgresqlPassword}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlDatabase" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('index.database')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlDatabase"
|
||||||
|
id="postgresqlDatabase"
|
||||||
|
value={service.glitchTip.postgresqlDatabase}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
36
apps/ui/src/routes/services/[id]/_Services/_Searxng.svelte
Normal file
36
apps/ui/src/routes/services/[id]/_Services/_Searxng.svelte
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
export let service: any;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">SearXNG</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="secretKey">Secret Key</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="secretKey"
|
||||||
|
id="secretKey"
|
||||||
|
isPasswordField
|
||||||
|
value={service.searxng.secretKey}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">Redis</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="redisPassword">{$t('forms.password')}</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="redisPassword"
|
||||||
|
id="redisPassword"
|
||||||
|
isPasswordField
|
||||||
|
value={service.searxng.redisPassword}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import Fider from './_Fider.svelte';
|
import Fider from './_Fider.svelte';
|
||||||
import Ghost from './_Ghost.svelte';
|
import Ghost from './_Ghost.svelte';
|
||||||
|
import GlitchTip from './_GlitchTip.svelte';
|
||||||
import Hasura from './_Hasura.svelte';
|
import Hasura from './_Hasura.svelte';
|
||||||
import MeiliSearch from './_MeiliSearch.svelte';
|
import MeiliSearch from './_MeiliSearch.svelte';
|
||||||
import MinIo from './_MinIO.svelte';
|
import MinIo from './_MinIO.svelte';
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
import Wordpress from './_Wordpress.svelte';
|
import Wordpress from './_Wordpress.svelte';
|
||||||
import Appwrite from './_Appwrite.svelte';
|
import Appwrite from './_Appwrite.svelte';
|
||||||
import Moodle from './_Moodle.svelte';
|
import Moodle from './_Moodle.svelte';
|
||||||
|
import Searxng from './_Searxng.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
$: isDisabled =
|
$: isDisabled =
|
||||||
@@ -322,13 +324,13 @@
|
|||||||
<div class="flex-col space-y-2 pt-4 text-center">
|
<div class="flex-col space-y-2 pt-4 text-center">
|
||||||
{#if isNonWWWDomainOK}
|
{#if isNonWWWDomainOK}
|
||||||
<button
|
<button
|
||||||
class="bg-green-600 hover:bg-green-500"
|
class="btn btn-sm bg-green-600 hover:bg-green-500"
|
||||||
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
||||||
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
|
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
|
||||||
>
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
class="bg-red-600 hover:bg-red-500"
|
class="btn btn-sm bg-red-600 hover:bg-red-500"
|
||||||
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
||||||
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
|
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
|
||||||
>
|
>
|
||||||
@@ -336,13 +338,13 @@
|
|||||||
{#if dualCerts}
|
{#if dualCerts}
|
||||||
{#if isWWWDomainOK}
|
{#if isWWWDomainOK}
|
||||||
<button
|
<button
|
||||||
class="bg-green-600 hover:bg-green-500"
|
class="btn btn-sm bg-green-600 hover:bg-green-500"
|
||||||
on:click|preventDefault={() => isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
on:click|preventDefault={() => isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
||||||
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
|
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
|
||||||
>
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
class="bg-red-600 hover:bg-red-500"
|
class="btn btn-sm bg-red-600 hover:bg-red-500"
|
||||||
on:click|preventDefault={() => isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
on:click|preventDefault={() => isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
||||||
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
|
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
|
||||||
>
|
>
|
||||||
@@ -399,6 +401,10 @@
|
|||||||
<Appwrite bind:service {readOnly} />
|
<Appwrite bind:service {readOnly} />
|
||||||
{:else if service.type === 'moodle'}
|
{:else if service.type === 'moodle'}
|
||||||
<Moodle bind:service {readOnly} />
|
<Moodle bind:service {readOnly} />
|
||||||
|
{:else if service.type === 'glitchTip'}
|
||||||
|
<GlitchTip bind:service />
|
||||||
|
{:else if service.type === 'searxng'}
|
||||||
|
<Searxng bind:service />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-col justify-center">
|
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
|
||||||
{#if !services || ownServices.length === 0}
|
{#if !services || ownServices.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">{$t('service.no_service')}</div>
|
<div class="text-center text-xl font-bold">{$t('service.no_service')}</div>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
let dualCerts = settings.dualCerts;
|
let dualCerts = settings.dualCerts;
|
||||||
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
|
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
|
||||||
let isDNSCheckEnabled = settings.isDNSCheckEnabled;
|
let isDNSCheckEnabled = settings.isDNSCheckEnabled;
|
||||||
|
let DNSServers = settings.DNSServers;
|
||||||
let minPort = settings.minPort;
|
let minPort = settings.minPort;
|
||||||
let maxPort = settings.maxPort;
|
let maxPort = settings.maxPort;
|
||||||
|
|
||||||
@@ -105,6 +105,10 @@
|
|||||||
settings.minPort = minPort;
|
settings.minPort = minPort;
|
||||||
settings.maxPort = maxPort;
|
settings.maxPort = maxPort;
|
||||||
}
|
}
|
||||||
|
if (DNSServers !== settings.DNSServers) {
|
||||||
|
await post(`/settings`, { DNSServers });
|
||||||
|
settings.DNSServers = DNSServers;
|
||||||
|
}
|
||||||
forceSave = false;
|
forceSave = false;
|
||||||
return addToast({
|
return addToast({
|
||||||
message: 'Configuration saved.',
|
message: 'Configuration saved.',
|
||||||
@@ -275,6 +279,17 @@
|
|||||||
on:click={() => changeSettings('isDNSCheckEnabled')}
|
on:click={() => changeSettings('isDNSCheckEnabled')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<div class="flex-col">
|
||||||
|
<div class="pt-2 text-base font-bold text-stone-100">
|
||||||
|
Custom DNS servers
|
||||||
|
</div>
|
||||||
|
<Explainer text="You can specify a custom DNS server to verify your domains all over Coolify.<br><br>By default, the OS defined DNS servers are used." />
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto flex-row items-center justify-center space-y-2">
|
||||||
|
<input placeholder="1.1.1.1,8.8.8.8" bind:value={DNSServers} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
dataTooltip={$t('setting.must_remove_domain_before_changing')}
|
dataTooltip={$t('setting.must_remove_domain_before_changing')}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col justify-center">
|
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
|
||||||
{#if !sources || ownSources.length === 0}
|
{#if !sources || ownSources.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">{$t('source.no_git_sources_found')}</div>
|
<div class="text-center text-xl font-bold">{$t('source.no_git_sources_found')}</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "3.4.0",
|
"version": "3.8.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": "github:coollabsio/coolify",
|
"repository": "github:coollabsio/coolify",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
524
pnpm-lock.yaml
generated
524
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user