Merge remote-tracking branch 'upstream/next' into next
This commit is contained in:
commit
68b220d06e
47
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
Normal file
47
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
name: 🐞 Bug report
|
||||
description: Create a bug report to help us improve coolify
|
||||
title: "[Bug]: "
|
||||
labels: [Bug]
|
||||
assignees:
|
||||
- andrasbacsai
|
||||
- vasani-arpit
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report! Please fill the form in English
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A concise description of what you're experiencing and what you expect.
|
||||
placeholder: |
|
||||
When I do <X>, <Y> happens and I see the error message attached below:
|
||||
```...```
|
||||
What I expect is <Z>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Add steps to reproduce this behaviour, include console / network logs & videos
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: "The version of your coolify Instance"
|
||||
placeholder: "2.5.2"
|
||||
validations:
|
||||
required: true
|
31
.github/ISSUE_TEMPLATE/--feature-request.yaml
vendored
Normal file
31
.github/ISSUE_TEMPLATE/--feature-request.yaml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
name: 🛠️ Feature request
|
||||
description: Suggest an idea to improve coolify
|
||||
title: '[Feature]: '
|
||||
labels: [Enhancement]
|
||||
assignees:
|
||||
- andrasbacsai
|
||||
- vasani-arpit
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to request a feature for coolify! Please also add your request here to get feedback from the community: https://feedback.coolify.io/!
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue related to this feature request already exists.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: One paragraph description of the feature.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Why should this be worked on?
|
||||
description: A concise description of the problems or use cases for this feature request.
|
||||
validations:
|
||||
required: true
|
20
.github/ISSUE_TEMPLATE/--task.yaml
vendored
Normal file
20
.github/ISSUE_TEMPLATE/--task.yaml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: 📝 Task
|
||||
description: Create a task for the team to work on
|
||||
title: "[Task]: "
|
||||
labels: [Task]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue related to this already exists.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: SubTasks
|
||||
placeholder: |
|
||||
- Sub Task 1
|
||||
- Sub Task 2
|
||||
validations:
|
||||
required: false
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 🤔 Questions and Help
|
||||
url: https://discord.com/invite/6rDM4fkymF
|
||||
about: Reach out to us on discord or our github discussions page.
|
39
.github/workflows/github-actions.yml
vendored
Normal file
39
.github/workflows/github-actions.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: release-coolify
|
||||
|
||||
on:
|
||||
release:
|
||||
types: published
|
||||
|
||||
jobs:
|
||||
make-it-coolifyed:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Get current package version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||
id: package-version
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: coollabsio/coolify:latest,coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache,mode=max
|
15
README.md
15
README.md
@ -8,6 +8,10 @@ https://demo.coolify.io/
|
||||
|
||||
(If it is unresponsive, that means someone overloaded the server. 🙃)
|
||||
|
||||
## Feedback
|
||||
|
||||
If you have a new service / build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
||||
|
||||
## How to install
|
||||
|
||||
Installation is automated with the following command:
|
||||
@ -52,18 +56,21 @@ These are the predefined build packs, but with the Docker build pack, you can ho
|
||||
- NuxtJS
|
||||
- NextJS
|
||||
- React/Preact
|
||||
- NextJS
|
||||
- Gatsby
|
||||
- Svelte
|
||||
- PHP
|
||||
- Laravel
|
||||
- Rust
|
||||
- Docker
|
||||
- Python
|
||||
- Deno
|
||||
|
||||
### Databases
|
||||
|
||||
One-click database is ready to be used internally or shared over the internet:
|
||||
|
||||
- MongoDB
|
||||
- MariaDB
|
||||
- MySQL
|
||||
- PostgreSQL
|
||||
- CouchDB
|
||||
@ -73,9 +80,9 @@ One-click database is ready to be used internally or shared over the internet:
|
||||
|
||||
You can host cool open-source services as well:
|
||||
|
||||
- [WordPress](https://wordpress.org)
|
||||
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
|
||||
- [Ghost](https://ghost.org)
|
||||
- [Plausible Analytics](https://plausible.io)
|
||||
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
|
||||
- [NocoDB](https://nocodb.com)
|
||||
- [VSCode Server](https://github.com/cdr/code-server)
|
||||
- [MinIO](https://min.io)
|
||||
@ -85,6 +92,8 @@ You can host cool open-source services as well:
|
||||
- [Uptime Kuma](https://github.com/louislam/uptime-kuma)
|
||||
- [MeiliSearch](https://github.com/meilisearch/meilisearch)
|
||||
- [Umami](https://github.com/mikecao/umami)
|
||||
- [Fider](https://fider.io)
|
||||
- [Hasura](https://hasura.io)
|
||||
|
||||
## Migration from v1
|
||||
|
||||
|
23
data/traefik/docker-compose-tcp.yaml
Normal file
23
data/traefik/docker-compose-tcp.yaml
Normal file
@ -0,0 +1,23 @@
|
||||
version: '3.5'
|
||||
|
||||
services:
|
||||
${ID}:
|
||||
container_name: proxy-for-${PORT}
|
||||
image: traefik:v2.6
|
||||
command:
|
||||
- --api.insecure=true
|
||||
- --entrypoints.web.address=:${PORT}
|
||||
- --providers.docker=false
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --providers.http.endpoint=http://host.docker.internal:3000/traefik.json?id=${ID}
|
||||
- --providers.http.pollTimeout=5s
|
||||
- --log.level=error
|
||||
ports:
|
||||
- '${PORT}:${PORT}'
|
||||
networks:
|
||||
- ${NETWORK}
|
||||
|
||||
networks:
|
||||
net:
|
||||
external: false
|
||||
name: ${NETWORK}
|
29
docker-compose-traefik.yaml
Normal file
29
docker-compose-traefik.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
proxy:
|
||||
image: traefik:v2.6
|
||||
command:
|
||||
- --api.insecure=true
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --providers.docker=false
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --providers.http.endpoint=http://host.docker.internal:3000/traefik.json
|
||||
- --providers.http.pollTimeout=5s
|
||||
- --log.level=error
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
- '8080:8080'
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
networks:
|
||||
- coolify-infra
|
||||
|
||||
networks:
|
||||
coolify-infra:
|
||||
attachable: true
|
||||
name: coolify-infra
|
@ -39,3 +39,5 @@ volumes:
|
||||
name: coolify-ssl-certs
|
||||
coolify-letsencrypt:
|
||||
name: coolify-letsencrypt
|
||||
coolify-traefik-letsencrypt:
|
||||
name: coolify-traefik-letsencrypt
|
||||
|
41
package.json
41
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "2.6.1",
|
||||
"version": "2.9.0",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0",
|
||||
@ -30,60 +30,63 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "1.0.0-next.73",
|
||||
"@sveltejs/kit": "1.0.0-next.316",
|
||||
"@types/js-cookie": "3.0.1",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.31",
|
||||
"@sveltejs/kit": "1.0.0-next.334",
|
||||
"@types/js-cookie": "3.0.2",
|
||||
"@types/js-yaml": "4.0.5",
|
||||
"@types/node": "17.0.25",
|
||||
"@types/node-forge": "1.0.1",
|
||||
"@types/node": "17.0.34",
|
||||
"@types/node-forge": "1.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "4.31.1",
|
||||
"@typescript-eslint/parser": "4.31.1",
|
||||
"@zerodevx/svelte-toast": "0.7.1",
|
||||
"autoprefixer": "10.4.4",
|
||||
"autoprefixer": "10.4.7",
|
||||
"cross-env": "7.0.3",
|
||||
"cross-var": "1.1.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-svelte3": "3.4.1",
|
||||
"husky": "7.0.4",
|
||||
"lint-staged": "12.4.0",
|
||||
"postcss": "8.4.12",
|
||||
"lint-staged": "12.4.1",
|
||||
"postcss": "8.4.13",
|
||||
"prettier": "2.6.2",
|
||||
"prettier-plugin-svelte": "2.7.0",
|
||||
"prettier-plugin-tailwindcss": "0.1.10",
|
||||
"prettier-plugin-tailwindcss": "0.1.11",
|
||||
"prisma": "3.11.1",
|
||||
"svelte": "3.47.0",
|
||||
"svelte-check": "2.7.0",
|
||||
"svelte": "3.48.0",
|
||||
"svelte-check": "2.7.1",
|
||||
"svelte-preprocess": "4.10.6",
|
||||
"svelte-select": "4.4.7",
|
||||
"sveltekit-i18n": "2.1.2",
|
||||
"sveltekit-i18n": "2.2.1",
|
||||
"tailwindcss": "3.0.24",
|
||||
"ts-node": "10.7.0",
|
||||
"tslib": "2.3.1",
|
||||
"typescript": "4.6.3"
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.6.4"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@prisma/client": "3.11.1",
|
||||
"@sentry/node": "6.19.6",
|
||||
"@sentry/node": "6.19.7",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bullmq": "1.80.4",
|
||||
"bullmq": "1.82.2",
|
||||
"compare-versions": "4.1.3",
|
||||
"cookie": "0.5.0",
|
||||
"cuid": "2.1.8",
|
||||
"dayjs": "1.11.1",
|
||||
"dayjs": "1.11.2",
|
||||
"dockerode": "3.3.1",
|
||||
"dotenv-extended": "2.9.0",
|
||||
"generate-password": "1.7.0",
|
||||
"get-port": "6.1.2",
|
||||
"got": "12.0.3",
|
||||
"got": "12.0.4",
|
||||
"is-ip": "4.0.0",
|
||||
"js-cookie": "3.0.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"mustache": "4.2.0",
|
||||
"node-forge": "1.3.1",
|
||||
"node-os-utils": "1.3.6",
|
||||
"p-limit": "4.0.0",
|
||||
"svelte-kit-cookie-session": "2.1.3",
|
||||
"svelte-kit-cookie-session": "2.1.4",
|
||||
"tailwindcss-scrollbar": "0.1.0",
|
||||
"unique-names-generator": "4.7.1"
|
||||
},
|
||||
|
434
pnpm-lock.yaml
generated
434
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "exposePort" INTEGER;
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Service" ADD COLUMN "exposePort" INTEGER;
|
@ -0,0 +1,24 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_PlausibleAnalytics" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"email" TEXT,
|
||||
"username" TEXT,
|
||||
"password" TEXT NOT NULL,
|
||||
"postgresqlUser" TEXT NOT NULL,
|
||||
"postgresqlPassword" TEXT NOT NULL,
|
||||
"postgresqlDatabase" TEXT NOT NULL,
|
||||
"postgresqlPublicPort" INTEGER,
|
||||
"secretKeyBase" TEXT,
|
||||
"scriptName" TEXT NOT NULL DEFAULT 'plausible.js',
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "PlausibleAnalytics_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_PlausibleAnalytics" ("createdAt", "email", "id", "password", "postgresqlDatabase", "postgresqlPassword", "postgresqlPublicPort", "postgresqlUser", "secretKeyBase", "serviceId", "updatedAt", "username") SELECT "createdAt", "email", "id", "password", "postgresqlDatabase", "postgresqlPassword", "postgresqlPublicPort", "postgresqlUser", "secretKeyBase", "serviceId", "updatedAt", "username" FROM "PlausibleAnalytics";
|
||||
DROP TABLE "PlausibleAnalytics";
|
||||
ALTER TABLE "new_PlausibleAnalytics" RENAME TO "PlausibleAnalytics";
|
||||
CREATE UNIQUE INDEX "PlausibleAnalytics_serviceId_key" ON "PlausibleAnalytics"("serviceId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
@ -0,0 +1,32 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Wordpress" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"extraConfig" TEXT,
|
||||
"tablePrefix" TEXT,
|
||||
"ownMysql" BOOLEAN NOT NULL DEFAULT false,
|
||||
"mysqlHost" TEXT,
|
||||
"mysqlPort" INTEGER,
|
||||
"mysqlUser" TEXT NOT NULL,
|
||||
"mysqlPassword" TEXT NOT NULL,
|
||||
"mysqlRootUser" TEXT NOT NULL,
|
||||
"mysqlRootUserPassword" TEXT NOT NULL,
|
||||
"mysqlDatabase" TEXT,
|
||||
"mysqlPublicPort" INTEGER,
|
||||
"ftpEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"ftpUser" TEXT,
|
||||
"ftpPassword" TEXT,
|
||||
"ftpPublicPort" INTEGER,
|
||||
"ftpHostKey" TEXT,
|
||||
"ftpHostKeyPrivate" TEXT,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Wordpress_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Wordpress" ("createdAt", "extraConfig", "ftpEnabled", "ftpHostKey", "ftpHostKeyPrivate", "ftpPassword", "ftpPublicPort", "ftpUser", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt") SELECT "createdAt", "extraConfig", "ftpEnabled", "ftpHostKey", "ftpHostKeyPrivate", "ftpPassword", "ftpPublicPort", "ftpUser", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt" FROM "Wordpress";
|
||||
DROP TABLE "Wordpress";
|
||||
ALTER TABLE "new_Wordpress" RENAME TO "Wordpress";
|
||||
CREATE UNIQUE INDEX "Wordpress_serviceId_key" ON "Wordpress"("serviceId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
24
prisma/migrations/20220517081328_traefik/migration.sql
Normal file
24
prisma/migrations/20220517081328_traefik/migration.sql
Normal file
@ -0,0 +1,24 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Setting" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"fqdn" TEXT,
|
||||
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||
"proxyPassword" TEXT NOT NULL,
|
||||
"proxyUser" TEXT NOT NULL,
|
||||
"proxyHash" TEXT,
|
||||
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Setting" ("createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting";
|
||||
DROP TABLE "Setting";
|
||||
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Minio" ADD COLUMN "apiFqdn" TEXT;
|
@ -20,6 +20,7 @@ model Setting {
|
||||
proxyHash String?
|
||||
isAutoUpdateEnabled Boolean @default(false)
|
||||
isDNSCheckEnabled Boolean @default(true)
|
||||
isTraefikUsed Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
@ -84,6 +85,7 @@ model Application {
|
||||
buildPack String?
|
||||
projectId Int?
|
||||
port Int?
|
||||
exposePort Int?
|
||||
installCommand String?
|
||||
buildCommand String?
|
||||
startCommand String?
|
||||
@ -289,6 +291,7 @@ model Service {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
fqdn String?
|
||||
exposePort Int?
|
||||
dualCerts Boolean @default(false)
|
||||
type String?
|
||||
version String?
|
||||
@ -320,6 +323,7 @@ model PlausibleAnalytics {
|
||||
postgresqlDatabase String
|
||||
postgresqlPublicPort Int?
|
||||
secretKeyBase String?
|
||||
scriptName String @default("plausible.js")
|
||||
serviceId String @unique
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
@ -331,6 +335,7 @@ model Minio {
|
||||
rootUser String
|
||||
rootUserPassword String
|
||||
publicPort Int?
|
||||
apiFqdn String?
|
||||
serviceId String @unique
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
@ -350,6 +355,9 @@ model Wordpress {
|
||||
id String @id @default(cuid())
|
||||
extraConfig String?
|
||||
tablePrefix String?
|
||||
ownMysql Boolean @default(false)
|
||||
mysqlHost String?
|
||||
mysqlPort Int?
|
||||
mysqlUser String
|
||||
mysqlPassword String
|
||||
mysqlRootUser String
|
||||
|
@ -3,7 +3,6 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Coolify</title>
|
||||
%svelte.head%
|
||||
</head>
|
||||
<body>
|
||||
|
@ -114,5 +114,5 @@ export const getSession: GetSession = function ({ locals }) {
|
||||
};
|
||||
|
||||
export async function handleError({ error, event }) {
|
||||
if (!dev) sentry.captureException(error, event);
|
||||
// if (!dev) sentry.captureException(error, event);
|
||||
}
|
||||
|
@ -8,14 +8,16 @@ import { staticDeployments } from '$lib/components/common';
|
||||
const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy'];
|
||||
const nodeBased = [
|
||||
'react',
|
||||
'preact',
|
||||
'vuejs',
|
||||
'svelte',
|
||||
'gatsby',
|
||||
'php',
|
||||
'astro',
|
||||
'eleventy',
|
||||
'node',
|
||||
'nestjs'
|
||||
'nestjs',
|
||||
'nuxtjs',
|
||||
'nextjs'
|
||||
];
|
||||
|
||||
export function makeLabelForStandaloneApplication({
|
||||
@ -403,7 +405,72 @@ export function setDefaultBaseImage(buildPack) {
|
||||
label: 'webdevops/php-nginx:7.1-alpine'
|
||||
}
|
||||
];
|
||||
|
||||
const pythonVersions = [
|
||||
{
|
||||
value: 'python:3.10-alpine',
|
||||
label: 'python:3.10-alpine'
|
||||
},
|
||||
{
|
||||
value: 'python:3.10-buster',
|
||||
label: 'python:3.10-buster'
|
||||
},
|
||||
{
|
||||
value: 'python:3.10-bullseye',
|
||||
label: 'python:3.10-bullseye'
|
||||
},
|
||||
{
|
||||
value: 'python:3.10-slim-bullseye',
|
||||
label: 'python:3.10-slim-bullseye'
|
||||
},
|
||||
{
|
||||
value: 'python:3.9-alpine',
|
||||
label: 'python:3.9-alpine'
|
||||
},
|
||||
{
|
||||
value: 'python:3.9-buster',
|
||||
label: 'python:3.9-buster'
|
||||
},
|
||||
{
|
||||
value: 'python:3.9-bullseye',
|
||||
label: 'python:3.9-bullseye'
|
||||
},
|
||||
{
|
||||
value: 'python:3.9-slim-bullseye',
|
||||
label: 'python:3.9-slim-bullseye'
|
||||
},
|
||||
{
|
||||
value: 'python:3.8-alpine',
|
||||
label: 'python:3.8-alpine'
|
||||
},
|
||||
{
|
||||
value: 'python:3.8-buster',
|
||||
label: 'python:3.8-buster'
|
||||
},
|
||||
{
|
||||
value: 'python:3.8-bullseye',
|
||||
label: 'python:3.8-bullseye'
|
||||
},
|
||||
{
|
||||
value: 'python:3.8-slim-bullseye',
|
||||
label: 'python:3.8-slim-bullseye'
|
||||
},
|
||||
{
|
||||
value: 'python:3.7-alpine',
|
||||
label: 'python:3.7-alpine'
|
||||
},
|
||||
{
|
||||
value: 'python:3.7-buster',
|
||||
label: 'python:3.7-buster'
|
||||
},
|
||||
{
|
||||
value: 'python:3.7-bullseye',
|
||||
label: 'python:3.7-bullseye'
|
||||
},
|
||||
{
|
||||
value: 'python:3.7-slim-bullseye',
|
||||
label: 'python:3.7-slim-bullseye'
|
||||
}
|
||||
];
|
||||
let payload = {
|
||||
baseImage: null,
|
||||
baseBuildImage: null,
|
||||
@ -423,7 +490,8 @@ export function setDefaultBaseImage(buildPack) {
|
||||
payload.baseBuildImages = nodeVersions;
|
||||
}
|
||||
if (buildPack === 'python') {
|
||||
payload.baseImage = 'python:3-alpine';
|
||||
payload.baseImage = 'python:3.10-alpine';
|
||||
payload.baseImages = pythonVersions;
|
||||
}
|
||||
if (buildPack === 'rust') {
|
||||
payload.baseImage = 'rust:latest';
|
||||
|
@ -2,7 +2,7 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const createDockerfile = async (data, imageforBuild): Promise<void> => {
|
||||
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data;
|
||||
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId, port } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${imageforBuild}`);
|
||||
@ -12,7 +12,7 @@ const createDockerfile = async (data, imageforBuild): Promise<void> => {
|
||||
if (baseImage.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { buildCacheImageForLaravel, buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { workdir, applicationId, tag, buildId } = data;
|
||||
const { workdir, applicationId, tag, buildId, port } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
@ -24,7 +24,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
`COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/mix-manifest.json /app/public/mix-manifest.json`
|
||||
);
|
||||
Dockerfile.push(`COPY --chown=application:application . ./`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
||||
const { workdir, baseDirectory, buildId } = data;
|
||||
const { workdir, baseDirectory, buildId, port } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
let composerFound = false;
|
||||
try {
|
||||
@ -22,7 +22,7 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
||||
}
|
||||
|
||||
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data;
|
||||
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId, port } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
@ -12,7 +12,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
if (baseImage.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
|
@ -12,7 +12,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
secrets,
|
||||
pullmergeRequestId,
|
||||
baseImage,
|
||||
buildId
|
||||
buildId,
|
||||
port
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
@ -42,7 +43,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
if (baseImage.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data;
|
||||
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId, port } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
@ -12,7 +12,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
if (baseImage.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data;
|
||||
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId, port } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
@ -12,7 +12,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
if (baseImage.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,8 @@ import { dev } from '$app/env';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
|
||||
import type { Config } from 'unique-names-generator';
|
||||
import { promises as dns } from 'dns';
|
||||
import { isIP } from 'is-ip';
|
||||
|
||||
import * as db from '$lib/database';
|
||||
import { buildLogQueue } from './queues';
|
||||
@ -14,24 +16,25 @@ import Cookie from 'cookie';
|
||||
import os from 'os';
|
||||
import type { RequestEvent } from '@sveltejs/kit/types/internal';
|
||||
import type { Job } from 'bullmq';
|
||||
import { t } from './translations';
|
||||
|
||||
try {
|
||||
if (!dev) {
|
||||
Sentry.init({
|
||||
dsn: process.env['COOLIFY_SENTRY_DSN'],
|
||||
tracesSampleRate: 0,
|
||||
environment: 'production',
|
||||
debug: true,
|
||||
release: currentVersion,
|
||||
initialScope: {
|
||||
tags: {
|
||||
appId: process.env['COOLIFY_APP_ID'],
|
||||
'os.arch': getOsArch(),
|
||||
'os.platform': os.platform(),
|
||||
'os.release': os.release()
|
||||
}
|
||||
}
|
||||
});
|
||||
// Sentry.init({
|
||||
// dsn: process.env['COOLIFY_SENTRY_DSN'],
|
||||
// tracesSampleRate: 0,
|
||||
// environment: 'production',
|
||||
// debug: true,
|
||||
// release: currentVersion,
|
||||
// initialScope: {
|
||||
// tags: {
|
||||
// appId: process.env['COOLIFY_APP_ID'],
|
||||
// 'os.arch': getOsArch(),
|
||||
// 'os.platform': os.platform(),
|
||||
// 'os.release': os.release()
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Could not initialize Sentry, no worries.');
|
||||
@ -93,12 +96,16 @@ export const getUserDetails = async (
|
||||
const userId = event?.locals?.session?.data?.userId || null;
|
||||
let permission = 'read';
|
||||
if (teamId && userId) {
|
||||
const data = await db.prisma.permission.findFirst({
|
||||
where: { teamId, userId },
|
||||
select: { permission: true },
|
||||
rejectOnNotFound: true
|
||||
});
|
||||
if (data.permission) permission = data.permission;
|
||||
try {
|
||||
const data = await db.prisma.permission.findFirst({
|
||||
where: { teamId, userId },
|
||||
select: { permission: true },
|
||||
rejectOnNotFound: true
|
||||
});
|
||||
if (data.permission) permission = data.permission;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
@ -179,3 +186,97 @@ export function getDomain(domain: string): string {
|
||||
export function getOsArch() {
|
||||
return os.arch();
|
||||
}
|
||||
|
||||
export async function isDNSValid(event: any, domain: string): Promise<any> {
|
||||
let resolves = [];
|
||||
try {
|
||||
if (isIP(event.url.hostname)) {
|
||||
resolves = [event.url.hostname];
|
||||
} else {
|
||||
resolves = await dns.resolve4(event.url.hostname);
|
||||
}
|
||||
} catch (error) {
|
||||
throw {
|
||||
message: t.get('application.dns_not_set_error', { domain })
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
let ipDomainFound = false;
|
||||
dns.setServers(['1.1.1.1', '8.8.8.8']);
|
||||
const dnsResolve = await dns.resolve4(domain);
|
||||
if (dnsResolve.length > 0) {
|
||||
for (const ip of dnsResolve) {
|
||||
if (resolves.includes(ip)) {
|
||||
ipDomainFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ipDomainFound) throw false;
|
||||
} catch (error) {
|
||||
throw {
|
||||
message: t.get('application.domain_not_valid')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkDomainsIsValidInDNS({ event, fqdn, dualCerts }): Promise<any> {
|
||||
const domain = getDomain(fqdn);
|
||||
const domainDualCert = domain.includes('www.') ? domain.replace('www.', '') : `www.${domain}`;
|
||||
dns.setServers(['1.1.1.1', '8.8.8.8']);
|
||||
let resolves = [];
|
||||
try {
|
||||
if (isIP(event.url.hostname)) {
|
||||
resolves = [event.url.hostname];
|
||||
} else {
|
||||
resolves = await dns.resolve4(event.url.hostname);
|
||||
}
|
||||
} catch (error) {
|
||||
throw {
|
||||
message: t.get('application.dns_not_set_error', { domain })
|
||||
};
|
||||
}
|
||||
|
||||
if (dualCerts) {
|
||||
try {
|
||||
const ipDomain = await dns.resolve4(domain);
|
||||
const ipDomainDualCert = await dns.resolve4(domainDualCert);
|
||||
|
||||
let ipDomainFound = false;
|
||||
let ipDomainDualCertFound = false;
|
||||
|
||||
for (const ip of ipDomain) {
|
||||
if (resolves.includes(ip)) {
|
||||
ipDomainFound = true;
|
||||
}
|
||||
}
|
||||
for (const ip of ipDomainDualCert) {
|
||||
if (resolves.includes(ip)) {
|
||||
ipDomainDualCertFound = true;
|
||||
}
|
||||
}
|
||||
if (ipDomainFound && ipDomainDualCertFound) return { status: 200 };
|
||||
throw false;
|
||||
} catch (error) {
|
||||
throw {
|
||||
message: t.get('application.dns_not_set_error', { domain })
|
||||
};
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const ipDomain = await dns.resolve4(domain);
|
||||
let ipDomainFound = false;
|
||||
for (const ip of ipDomain) {
|
||||
if (resolves.includes(ip)) {
|
||||
ipDomainFound = true;
|
||||
}
|
||||
}
|
||||
if (ipDomainFound) return { status: 200 };
|
||||
throw false;
|
||||
} catch (error) {
|
||||
throw {
|
||||
message: t.get('application.dns_not_set_error', { domain })
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
import Clickhouse from './svg/databases/Clickhouse.svelte';
|
||||
import CouchDb from './svg/databases/CouchDB.svelte';
|
||||
import MongoDb from './svg/databases/MongoDB.svelte';
|
||||
import MariaDb from './svg/databases/MariaDB.svelte';
|
||||
import MySql from './svg/databases/MySQL.svelte';
|
||||
import PostgreSql from './svg/databases/PostgreSQL.svelte';
|
||||
import Redis from './svg/databases/Redis.svelte';
|
||||
@ -17,6 +18,8 @@
|
||||
<MongoDb />
|
||||
{:else if database.type === 'mysql'}
|
||||
<MySql />
|
||||
{:else if database.type === 'mariadb'}
|
||||
<MariaDb />
|
||||
{:else if database.type === 'postgresql'}
|
||||
<PostgreSql />
|
||||
{:else if database.type === 'redis'}
|
||||
|
35
src/lib/components/PageLoader.svelte
Normal file
35
src/lib/components/PageLoader.svelte
Normal file
@ -0,0 +1,35 @@
|
||||
<script>
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { tweened } from 'svelte/motion';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
|
||||
let timeout;
|
||||
const progress = tweened(0, {
|
||||
duration: 2000,
|
||||
easing: cubicOut
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
timeout = setTimeout(() => {
|
||||
progress.set(0.7);
|
||||
}, 500);
|
||||
});
|
||||
onDestroy(() => {
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="progress-bar">
|
||||
<div class="progress-sliver" style={`--width: ${$progress * 100}%`} />
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.progress-bar {
|
||||
height: 0.2rem;
|
||||
@apply fixed top-0 left-0 right-0;
|
||||
}
|
||||
.progress-sliver {
|
||||
width: var(--width);
|
||||
@apply h-full bg-coollabs;
|
||||
}
|
||||
</style>
|
@ -52,6 +52,12 @@ export const supportedDatabaseTypesAndVersions = [
|
||||
versions: ['5.0', '4.4', '4.2']
|
||||
},
|
||||
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0', '5.7'] },
|
||||
{
|
||||
name: 'mariadb',
|
||||
fancyName: 'MariaDB',
|
||||
baseImage: 'bitnami/mariadb',
|
||||
versions: ['10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
|
||||
},
|
||||
{
|
||||
name: 'postgresql',
|
||||
fancyName: 'PostgreSQL',
|
||||
@ -213,5 +219,25 @@ export const supportedServiceTypesAndVersions = [
|
||||
ports: {
|
||||
main: 3000
|
||||
}
|
||||
// },
|
||||
// {
|
||||
// name: 'appwrite',
|
||||
// fancyName: 'AppWrite',
|
||||
// baseImage: 'appwrite/appwrite',
|
||||
// images: ['appwrite/influxdb', 'appwrite/telegraf', 'mariadb:10.7', 'redis:6.0-alpine3.12'],
|
||||
// versions: ['latest', '0.13.0'],
|
||||
// recommendedVersion: '0.13.0',
|
||||
// ports: {
|
||||
// main: 3000
|
||||
// }
|
||||
// }
|
||||
}
|
||||
];
|
||||
|
||||
export const getServiceMainPort = (service: string) => {
|
||||
const serviceType = supportedServiceTypesAndVersions.find((s) => s.name === service);
|
||||
if (serviceType) {
|
||||
return serviceType.ports.main;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
24
src/lib/components/svg/databases/MariaDB.svelte
Normal file
24
src/lib/components/svg/databases/MariaDB.svelte
Normal file
@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = false;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="Layer_1"
|
||||
data-name="Layer 1"
|
||||
viewBox="0 0 309.88 252.72"
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-12 w-12 ' : 'mx-auto w-8 h-8'}
|
||||
>
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
class="cls-1"
|
||||
d="M316,10.05a4.2,4.2,0,0,0-2.84-1c-2.84,0-6.5,1.92-8.46,3l-.79.4a26.81,26.81,0,0,1-10.57,2.66c-3.76.12-7,.34-11.22.77-25,2.58-36.15,21.74-46.89,40.27-5.84,10.08-11.88,20.5-20.16,28.57a55.71,55.71,0,0,1-5.46,4.63c-8.57,6.39-19.33,10.9-27.74,14.12-8.07,3.08-16.86,5.85-25.37,8.53-7.78,2.45-15.14,4.76-21.9,7.28-3.05,1.13-5.64,2-7.93,2.76-6.15,2-10.6,3.53-17.08,8-2.53,1.73-5.07,3.6-6.8,5a71.26,71.26,0,0,0-13.54,14.27A84.81,84.81,0,0,1,77.88,163c-1.36,1.34-3.8,2-7.43,2-4.27,0-9.43-.88-14.91-1.81s-11.46-2-16.46-2c-4.07,0-7.17.66-9.5,2,0,0-3.9,2.28-5.56,5.23l1.62.73a33.56,33.56,0,0,1,6.93,5,33.68,33.68,0,0,0,7.19,5.12A6.37,6.37,0,0,1,42,180.72c-.69,1-1.69,2.29-2.74,3.67-5.77,7.55-9.13,12.32-7.2,14.92a6,6,0,0,0,3,.68c12.59,0,19.34-3.27,27.9-7.41,2.47-1.2,5-2.44,8-3.7,5-2.17,10.38-5.63,16.08-9.29,7.55-4.85,15.36-9.87,22.92-12.3a62.3,62.3,0,0,1,19.23-2.7c8,0,16.42,1.07,24.54,2.11,6.06.78,12.32,1.58,18.47,2,2.39.14,4.6.21,6.76.21a78.48,78.48,0,0,0,8.61-.45l.68-.24c4.32-2.65,6.34-8.34,8.29-13.84,1.26-3.54,2.32-6.72,4-8.74a2.06,2.06,0,0,1,.33-.27.4.4,0,0,1,.49.08.25.25,0,0,1,0,.16c-1,21.51-9.67,35.16-18.42,47.3L177,199.14s8.18,0,12.84-1.8c17-5.08,29.84-16.28,39.18-34.14a144.39,144.39,0,0,0,6.16-14.09c.16-.4,1.64-1.14,1.49.93,0,.61-.08,1.29-.13,2h0c0,.42-.06.85-.08,1.28-.25,3-1,9.34-1,9.34l5.25-2.81c12.66-8,22.42-24.14,29.82-49.25,3.09-10.46,5.34-20.85,7.33-30,2.38-11,4.43-20.43,6.78-24.09,3.69-5.74,9.32-9.62,14.77-13.39.75-.51,1.49-1,2.22-1.54,6.86-4.81,13.67-10.36,15.16-20.71l0-.23C317.93,12.92,317,11,316,10.05Z"
|
||||
transform="translate(-7.45 -9.1)"
|
||||
/>
|
||||
</svg>
|
@ -3,31 +3,88 @@
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="Layer_1"
|
||||
data-name="Layer 1"
|
||||
viewBox="0 0 216.56 448.5"
|
||||
><defs
|
||||
><style>
|
||||
.cls-1 {
|
||||
fill: #10aa50;
|
||||
}
|
||||
.cls-2 {
|
||||
fill: #b8c4c2;
|
||||
}
|
||||
.cls-3 {
|
||||
fill: #12924f;
|
||||
}
|
||||
</style></defs
|
||||
><path
|
||||
class="cls-1"
|
||||
d="M202.8,179.68c-23-101.47-71-128.49-83.18-147.59C113,21.7,106.25,5.91,106.25,5.91c-.66,9-1.83,14.7-9.51,21.54C81.36,41.16,16,94.42,10.51,209.72c-5.12,107.5,79,173.8,90.18,180.65,8.54,4.2,19,.08,24-3.77,40.54-27.84,96-102.07,78.06-206.92"
|
||||
viewBox="0 0 128 128"
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#439934"
|
||||
d="M88.038 42.812c1.605 4.643 2.761 9.383 3.141 14.296.472 6.095.256 12.147-1.029 18.142-.035.165-.109.32-.164.48-.403.001-.814-.049-1.208.012-3.329.523-6.655 1.065-9.981 1.604-3.438.557-6.881 1.092-10.313 1.687-1.216.21-2.721-.041-3.212 1.641-.014.046-.154.054-.235.08l.166-10.051-.169-24.252 1.602-.275c2.62-.429 5.24-.864 7.862-1.281 3.129-.497 6.261-.98 9.392-1.465 1.381-.215 2.764-.412 4.148-.618z"
|
||||
/><path
|
||||
class="cls-2"
|
||||
d="M109.73,333.11c-2.11,26.62-3.63,42.11-9,57.29,0,0,3.54,25.33,6,52.17l8.77,0a488.62,488.62,0,0,1,9.57-56.2C113.71,380.8,110.16,356.46,109.73,333.11Z"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#45A538"
|
||||
d="M61.729 110.054c-1.69-1.453-3.439-2.842-5.059-4.37-8.717-8.222-15.093-17.899-18.233-29.566-.865-3.211-1.442-6.474-1.627-9.792-.13-2.322-.318-4.665-.154-6.975.437-6.144 1.325-12.229 3.127-18.147l.099-.138c.175.233.427.439.516.702 1.759 5.18 3.505 10.364 5.242 15.551 5.458 16.3 10.909 32.604 16.376 48.9.107.318.384.579.583.866l-.87 2.969z"
|
||||
/><path
|
||||
class="cls-3"
|
||||
d="M125.06,386.39h0c-11.48-5.3-14.8-30.13-15.31-53.28A1090.8,1090.8,0,0,0,112.2,218.4c-.6-20.07.3-185.92-4.94-210.2,2.12,4.75,7.24,15.91,12.36,23.88,12.23,19.11,60.19,46.13,83.17,147.61C220.7,284.27,165.57,358.37,125.06,386.39Z"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#46A037"
|
||||
d="M88.038 42.812c-1.384.206-2.768.403-4.149.616-3.131.485-6.263.968-9.392 1.465-2.622.417-5.242.852-7.862 1.281l-1.602.275-.012-1.045c-.053-.859-.144-1.717-.154-2.576-.069-5.478-.112-10.956-.18-16.434-.042-3.429-.105-6.857-.175-10.285-.043-2.13-.089-4.261-.185-6.388-.052-1.143-.236-2.28-.311-3.423-.042-.657.016-1.319.029-1.979.817 1.583 1.616 3.178 2.456 4.749 1.327 2.484 3.441 4.314 5.344 6.311 7.523 7.892 12.864 17.068 16.193 27.433z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#409433"
|
||||
d="M65.036 80.753c.081-.026.222-.034.235-.08.491-1.682 1.996-1.431 3.212-1.641 3.432-.594 6.875-1.13 10.313-1.687 3.326-.539 6.652-1.081 9.981-1.604.394-.062.805-.011 1.208-.012-.622 2.22-1.112 4.488-1.901 6.647-.896 2.449-1.98 4.839-3.131 7.182a49.142 49.142 0 01-6.353 9.763c-1.919 2.308-4.058 4.441-6.202 6.548-1.185 1.165-2.582 2.114-3.882 3.161l-.337-.23-1.214-1.038-1.256-2.753a41.402 41.402 0 01-1.394-9.838l.023-.561.171-2.426c.057-.828.133-1.655.168-2.485.129-2.982.241-5.964.359-8.946z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#4FAA41"
|
||||
d="M65.036 80.753c-.118 2.982-.23 5.964-.357 8.947-.035.83-.111 1.657-.168 2.485l-.765.289c-1.699-5.002-3.399-9.951-5.062-14.913-2.75-8.209-5.467-16.431-8.213-24.642a4498.887 4498.887 0 00-6.7-19.867c-.105-.31-.407-.552-.617-.826l4.896-9.002c.168.292.39.565.496.879a6167.476 6167.476 0 016.768 20.118c2.916 8.73 5.814 17.467 8.728 26.198.116.349.308.671.491 1.062l.67-.78-.167 10.052z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#4AA73C"
|
||||
d="M43.155 32.227c.21.274.511.516.617.826a4498.887 4498.887 0 016.7 19.867c2.746 8.211 5.463 16.433 8.213 24.642 1.662 4.961 3.362 9.911 5.062 14.913l.765-.289-.171 2.426-.155.559c-.266 2.656-.49 5.318-.814 7.968-.163 1.328-.509 2.632-.772 3.947-.198-.287-.476-.548-.583-.866-5.467-16.297-10.918-32.6-16.376-48.9a3888.972 3888.972 0 00-5.242-15.551c-.089-.263-.34-.469-.516-.702l3.272-8.84z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#57AE47"
|
||||
d="M65.202 70.702l-.67.78c-.183-.391-.375-.714-.491-1.062-2.913-8.731-5.812-17.468-8.728-26.198a6167.476 6167.476 0 00-6.768-20.118c-.105-.314-.327-.588-.496-.879l6.055-7.965c.191.255.463.482.562.769 1.681 4.921 3.347 9.848 5.003 14.778 1.547 4.604 3.071 9.215 4.636 13.813.105.308.47.526.714.786l.012 1.045c.058 8.082.115 16.167.171 24.251z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#60B24F"
|
||||
d="M65.021 45.404c-.244-.26-.609-.478-.714-.786-1.565-4.598-3.089-9.209-4.636-13.813-1.656-4.93-3.322-9.856-5.003-14.778-.099-.287-.371-.514-.562-.769 1.969-1.928 3.877-3.925 5.925-5.764 1.821-1.634 3.285-3.386 3.352-5.968.003-.107.059-.214.145-.514l.519 1.306c-.013.661-.072 1.322-.029 1.979.075 1.143.259 2.28.311 3.423.096 2.127.142 4.258.185 6.388.069 3.428.132 6.856.175 10.285.067 5.478.111 10.956.18 16.434.008.861.098 1.718.152 2.577z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#A9AA88"
|
||||
d="M62.598 107.085c.263-1.315.609-2.62.772-3.947.325-2.649.548-5.312.814-7.968l.066-.01.066.011a41.402 41.402 0 001.394 9.838c-.176.232-.425.439-.518.701-.727 2.05-1.412 4.116-2.143 6.166-.1.28-.378.498-.574.744l-.747-2.566.87-2.969z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#B6B598"
|
||||
d="M62.476 112.621c.196-.246.475-.464.574-.744.731-2.05 1.417-4.115 2.143-6.166.093-.262.341-.469.518-.701l1.255 2.754c-.248.352-.59.669-.728 1.061l-2.404 7.059c-.099.283-.437.483-.663.722l-.695-3.985z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#C2C1A7"
|
||||
d="M63.171 116.605c.227-.238.564-.439.663-.722l2.404-7.059c.137-.391.48-.709.728-1.061l1.215 1.037c-.587.58-.913 1.25-.717 2.097l-.369 1.208c-.168.207-.411.387-.494.624-.839 2.403-1.64 4.819-2.485 7.222-.107.305-.404.544-.614.812-.109-1.387-.22-2.771-.331-4.158z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#CECDB7"
|
||||
d="M63.503 120.763c.209-.269.506-.508.614-.812.845-2.402 1.646-4.818 2.485-7.222.083-.236.325-.417.494-.624l-.509 5.545c-.136.157-.333.294-.398.477-.575 1.614-1.117 3.24-1.694 4.854-.119.333-.347.627-.525.938-.158-.207-.441-.407-.454-.623-.051-.841-.016-1.688-.013-2.533z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#DBDAC7"
|
||||
d="M63.969 123.919c.178-.312.406-.606.525-.938.578-1.613 1.119-3.239 1.694-4.854.065-.183.263-.319.398-.477l.012 3.64-1.218 3.124-1.411-.495z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#EBE9DC"
|
||||
d="M65.38 124.415l1.218-3.124.251 3.696-1.469-.572z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#CECDB7"
|
||||
d="M67.464 110.898c-.196-.847.129-1.518.717-2.097l.337.23-1.054 1.867z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#4FAA41"
|
||||
d="M64.316 95.172l-.066-.011-.066.01.155-.559-.023.56z"
|
||||
/>
|
||||
</svg>
|
||||
|
@ -4,6 +4,6 @@
|
||||
|
||||
<img
|
||||
alt="plausible logo"
|
||||
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-6 mx-auto'}
|
||||
class={isAbsolute ? 'w-9 absolute top-0 left-0 -m-4' : 'w-6 mx-auto'}
|
||||
src="/plausible.png"
|
||||
/>
|
||||
|
@ -128,9 +128,7 @@ export function findBuildPack(pack, packageManager = 'npm') {
|
||||
if (pack === 'astro') {
|
||||
return {
|
||||
...metaData,
|
||||
installCommand: `yarn install`,
|
||||
buildCommand: `yarn build`,
|
||||
startCommand: null,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
publishDirectory: `dist`,
|
||||
port: 80
|
||||
};
|
||||
@ -138,9 +136,7 @@ export function findBuildPack(pack, packageManager = 'npm') {
|
||||
if (pack === 'eleventy') {
|
||||
return {
|
||||
...metaData,
|
||||
installCommand: `yarn install`,
|
||||
buildCommand: `yarn build`,
|
||||
startCommand: null,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
publishDirectory: `_site`,
|
||||
port: 80
|
||||
};
|
||||
|
@ -138,7 +138,18 @@ export async function getApplicationWebhook({
|
||||
return s;
|
||||
});
|
||||
}
|
||||
return { ...application };
|
||||
const { baseImage, baseBuildImage, baseBuildImages, baseImages } = setDefaultBaseImage(
|
||||
application.buildPack
|
||||
);
|
||||
|
||||
// Set default build images
|
||||
if (!application.baseImage) {
|
||||
application.baseImage = baseImage;
|
||||
}
|
||||
if (!application.baseBuildImage) {
|
||||
application.baseBuildImage = baseBuildImage;
|
||||
}
|
||||
return { ...application, baseBuildImages, baseImages };
|
||||
} catch (e) {
|
||||
throw { status: 404, body: { message: e.message } };
|
||||
}
|
||||
@ -267,6 +278,7 @@ export async function configureApplication({
|
||||
name,
|
||||
fqdn,
|
||||
port,
|
||||
exposePort,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
@ -286,6 +298,7 @@ export async function configureApplication({
|
||||
name: string;
|
||||
fqdn: string;
|
||||
port: number;
|
||||
exposePort: number;
|
||||
installCommand: string;
|
||||
buildCommand: string;
|
||||
startCommand: string;
|
||||
@ -307,6 +320,7 @@ export async function configureApplication({
|
||||
buildPack,
|
||||
fqdn,
|
||||
port,
|
||||
exposePort,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
|
@ -51,10 +51,12 @@ export async function isSecretExists({
|
||||
|
||||
export async function isDomainConfigured({
|
||||
id,
|
||||
fqdn
|
||||
fqdn,
|
||||
checkOwn = false
|
||||
}: {
|
||||
id: string;
|
||||
fqdn: string;
|
||||
checkOwn?: boolean;
|
||||
}): Promise<boolean> {
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace('www.', '');
|
||||
@ -72,12 +74,15 @@ export async function isDomainConfigured({
|
||||
where: {
|
||||
OR: [
|
||||
{ fqdn: { endsWith: `//${nakedDomain}` } },
|
||||
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
|
||||
{ fqdn: { endsWith: `//www.${nakedDomain}` } },
|
||||
{ minio: { apiFqdn: { endsWith: `//${nakedDomain}` } } },
|
||||
{ minio: { apiFqdn: { endsWith: `//www.${nakedDomain}` } } }
|
||||
],
|
||||
id: { not: id }
|
||||
id: { not: checkOwn ? undefined : id }
|
||||
},
|
||||
select: { fqdn: true }
|
||||
});
|
||||
|
||||
const coolifyFqdn = await prisma.setting.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
|
@ -28,7 +28,7 @@ if (!dev) {
|
||||
}
|
||||
|
||||
export const prisma = new PrismaClient({
|
||||
errorFormat: 'pretty',
|
||||
errorFormat: 'minimal',
|
||||
rejectOnNotFound: false
|
||||
});
|
||||
|
||||
@ -58,7 +58,7 @@ export function ErrorHandler(e: {
|
||||
truncatedError.message = 'git clone failed';
|
||||
}
|
||||
if (!e.message?.includes('Coolify Proxy is not running')) {
|
||||
sentry.captureException(truncatedError);
|
||||
// sentry.captureException(truncatedError);
|
||||
}
|
||||
const payload = {
|
||||
status: truncatedError.status || 500,
|
||||
@ -149,6 +149,19 @@ export function generateDatabaseConfiguration(database: Database & { settings: D
|
||||
MONGODB_ROOT_PASSWORD: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MARIADB_ROOT_USER: string;
|
||||
MARIADB_ROOT_PASSWORD: string;
|
||||
MARIADB_USER: string;
|
||||
MARIADB_PASSWORD: string;
|
||||
MARIADB_DATABASE: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
@ -207,6 +220,20 @@ export function generateDatabaseConfiguration(database: Database & { settings: D
|
||||
volume: `${id}-${type}-data:/bitnami/mysql/data`,
|
||||
ulimits: {}
|
||||
};
|
||||
} else if (type === 'mariadb') {
|
||||
return {
|
||||
privatePort: 3306,
|
||||
environmentVariables: {
|
||||
MARIADB_ROOT_USER: rootUser,
|
||||
MARIADB_ROOT_PASSWORD: rootUserPassword,
|
||||
MARIADB_USER: dbUser,
|
||||
MARIADB_PASSWORD: dbUserPassword,
|
||||
MARIADB_DATABASE: defaultDatabase
|
||||
},
|
||||
image: `${baseImage}:${version}`,
|
||||
volume: `${id}-${type}-data:/bitnami/mariadb`,
|
||||
ulimits: {}
|
||||
};
|
||||
} else if (type === 'mongodb') {
|
||||
return {
|
||||
privatePort: 27017,
|
||||
@ -278,6 +305,12 @@ export async function getFreePort() {
|
||||
select: { mysqlPublicPort: true }
|
||||
})
|
||||
).map((a) => a.mysqlPublicPort);
|
||||
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed];
|
||||
const minioUsed = await (
|
||||
await prisma.minio.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
select: { publicPort: true }
|
||||
})
|
||||
).map((a) => a.publicPort);
|
||||
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed];
|
||||
return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
|
||||
}
|
||||
|
@ -184,6 +184,10 @@ export async function updatePasswordInDb(database, user, newPassword, isRoot) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker exec ${id} mysql -u ${rootUser} -p${rootUserPassword} -e \"ALTER USER '${user}'@'%' IDENTIFIED WITH caching_sha2_password BY '${newPassword}';\"`
|
||||
);
|
||||
} else if (type === 'mariadb') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker exec ${id} mysql -u ${rootUser} -p${rootUserPassword} -e \"SET PASSWORD FOR '${user}'@'%' = PASSWORD('${newPassword}');\"`
|
||||
);
|
||||
} else if (type === 'postgresql') {
|
||||
if (isRoot) {
|
||||
await asyncExecShell(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import { startCoolifyProxy } from '$lib/haproxy';
|
||||
import { startCoolifyProxy, startTraefikProxy } from '$lib/haproxy';
|
||||
import { getDatabaseImage } from '.';
|
||||
import { prisma } from './common';
|
||||
import type { DestinationDocker, Service, Application, Prisma } from '@prisma/client';
|
||||
@ -125,7 +125,14 @@ export async function newLocalDestination({
|
||||
}
|
||||
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
||||
}
|
||||
if (isCoolifyProxyUsed) await startCoolifyProxy(engine);
|
||||
if (isCoolifyProxyUsed) {
|
||||
const settings = await prisma.setting.findFirst();
|
||||
if (settings?.isTraefikUsed) {
|
||||
await startTraefikProxy(engine);
|
||||
} else {
|
||||
await startCoolifyProxy(engine);
|
||||
}
|
||||
}
|
||||
return destination.id;
|
||||
}
|
||||
export async function removeDestination({ id }: Pick<DestinationDocker, 'id'>): Promise<void> {
|
||||
@ -133,12 +140,14 @@ export async function removeDestination({ id }: Pick<DestinationDocker, 'id'>):
|
||||
if (destination.isCoolifyProxyUsed) {
|
||||
const host = getEngine(destination.engine);
|
||||
const { network } = destination;
|
||||
const settings = await prisma.setting.findFirst();
|
||||
const containerName = settings.isTraefikUsed ? 'coolify-proxy' : 'coolify-haproxy';
|
||||
const { stdout: found } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=coolify-haproxy --format '{{.}}'`
|
||||
`DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=${containerName} --format '{{.}}'`
|
||||
);
|
||||
if (found) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network disconnect ${network} coolify-haproxy`
|
||||
`DOCKER_HOST="${host}" docker network disconnect ${network} ${containerName}`
|
||||
);
|
||||
await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`);
|
||||
}
|
||||
|
@ -327,35 +327,62 @@ export async function updatePlausibleAnalyticsService({
|
||||
id,
|
||||
fqdn,
|
||||
email,
|
||||
exposePort,
|
||||
username,
|
||||
name
|
||||
name,
|
||||
scriptName
|
||||
}: {
|
||||
id: string;
|
||||
fqdn: string;
|
||||
exposePort?: number;
|
||||
name: string;
|
||||
email: string;
|
||||
username: string;
|
||||
scriptName: string;
|
||||
}): Promise<void> {
|
||||
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
|
||||
await prisma.service.update({ where: { id }, data: { name, fqdn } });
|
||||
await prisma.plausibleAnalytics.update({
|
||||
where: { serviceId: id },
|
||||
data: { email, username, scriptName }
|
||||
});
|
||||
await prisma.service.update({ where: { id }, data: { name, fqdn, exposePort } });
|
||||
}
|
||||
|
||||
export async function updateService({
|
||||
id,
|
||||
fqdn,
|
||||
exposePort,
|
||||
name
|
||||
}: {
|
||||
id: string;
|
||||
fqdn: string;
|
||||
exposePort?: number;
|
||||
name: string;
|
||||
}): Promise<Service> {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name, exposePort } });
|
||||
}
|
||||
export async function updateMinioService({
|
||||
id,
|
||||
fqdn,
|
||||
apiFqdn,
|
||||
exposePort,
|
||||
name
|
||||
}: {
|
||||
id: string;
|
||||
fqdn: string;
|
||||
apiFqdn: string;
|
||||
exposePort?: number;
|
||||
name: string;
|
||||
}): Promise<Service> {
|
||||
return await prisma.service.update({
|
||||
where: { id },
|
||||
data: { fqdn, name, exposePort, minio: { update: { apiFqdn } } }
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateFiderService({
|
||||
id,
|
||||
fqdn,
|
||||
name,
|
||||
exposePort,
|
||||
emailNoreply,
|
||||
emailMailgunApiKey,
|
||||
emailMailgunDomain,
|
||||
@ -368,6 +395,7 @@ export async function updateFiderService({
|
||||
}: {
|
||||
id: string;
|
||||
fqdn: string;
|
||||
exposePort?: number;
|
||||
name: string;
|
||||
emailNoreply: string;
|
||||
emailMailgunApiKey: string;
|
||||
@ -384,6 +412,7 @@ export async function updateFiderService({
|
||||
data: {
|
||||
fqdn,
|
||||
name,
|
||||
exposePort,
|
||||
fider: {
|
||||
update: {
|
||||
emailNoreply,
|
||||
@ -405,22 +434,49 @@ export async function updateWordpress({
|
||||
id,
|
||||
fqdn,
|
||||
name,
|
||||
exposePort,
|
||||
ownMysql,
|
||||
mysqlDatabase,
|
||||
extraConfig
|
||||
extraConfig,
|
||||
mysqlHost,
|
||||
mysqlPort,
|
||||
mysqlUser,
|
||||
mysqlPassword
|
||||
}: {
|
||||
id: string;
|
||||
fqdn: string;
|
||||
name: string;
|
||||
exposePort?: number;
|
||||
ownMysql: boolean;
|
||||
mysqlDatabase: string;
|
||||
extraConfig: string;
|
||||
mysqlHost?: string;
|
||||
mysqlPort?: number;
|
||||
mysqlUser?: string;
|
||||
mysqlPassword?: string;
|
||||
}): Promise<Service> {
|
||||
mysqlPassword = encrypt(mysqlPassword);
|
||||
return await prisma.service.update({
|
||||
where: { id },
|
||||
data: { fqdn, name, wordpress: { update: { mysqlDatabase, extraConfig } } }
|
||||
data: {
|
||||
fqdn,
|
||||
name,
|
||||
exposePort,
|
||||
wordpress: {
|
||||
update: {
|
||||
mysqlDatabase,
|
||||
extraConfig,
|
||||
mysqlHost,
|
||||
mysqlUser,
|
||||
mysqlPassword,
|
||||
mysqlPort
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateMinioService({
|
||||
export async function updateMinioServicePort({
|
||||
id,
|
||||
publicPort
|
||||
}: {
|
||||
@ -434,16 +490,18 @@ export async function updateGhostService({
|
||||
id,
|
||||
fqdn,
|
||||
name,
|
||||
exposePort,
|
||||
mariadbDatabase
|
||||
}: {
|
||||
id: string;
|
||||
fqdn: string;
|
||||
name: string;
|
||||
exposePort?: number;
|
||||
mariadbDatabase: string;
|
||||
}): Promise<Service> {
|
||||
return await prisma.service.update({
|
||||
where: { id },
|
||||
data: { fqdn, name, ghost: { update: { mariadbDatabase } } }
|
||||
data: { fqdn, name, exposePort, ghost: { update: { mariadbDatabase } } }
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,9 @@ frontend http
|
||||
http-request redirect location {{{redirectValue}}} code ${
|
||||
dev ? 302 : 301
|
||||
} if { req.hdr(host) -i {{redirectTo}} }
|
||||
{{#scriptName}}
|
||||
http-request set-path /js/plausible.js if { hdr(host) -i {{domain}} } { path_beg -i /js/{{scriptName}} }
|
||||
{{/scriptName}}
|
||||
{{/services}}
|
||||
|
||||
{{#coolify}}
|
||||
@ -218,7 +221,15 @@ export async function configureHAProxy(): Promise<void> {
|
||||
const services = await listServicesWithIncludes();
|
||||
|
||||
for (const service of services) {
|
||||
const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service;
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
type,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
updatedAt,
|
||||
plausibleAnalytics
|
||||
} = service;
|
||||
if (destinationDockerId) {
|
||||
const { engine } = destinationDocker;
|
||||
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||
@ -232,6 +243,12 @@ export async function configureHAProxy(): Promise<void> {
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||
if (isRunning) {
|
||||
// Plausible Analytics custom script
|
||||
let scriptName = false;
|
||||
if (type === 'plausibleanalytics' && plausibleAnalytics.scriptName !== 'plausible.js') {
|
||||
scriptName = plausibleAnalytics.scriptName;
|
||||
}
|
||||
|
||||
data.services.push({
|
||||
id,
|
||||
port,
|
||||
@ -241,7 +258,8 @@ export async function configureHAProxy(): Promise<void> {
|
||||
isHttps,
|
||||
redirectValue,
|
||||
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
|
||||
updatedAt: updatedAt.getTime()
|
||||
updatedAt: updatedAt.getTime(),
|
||||
scriptName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,22 @@ import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import got, { type Got, type Response } from 'got';
|
||||
import * as db from '$lib/database';
|
||||
import type { DestinationDocker } from '@prisma/client';
|
||||
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
||||
|
||||
export const defaultProxyImage = `coolify-haproxy-alpine:latest`;
|
||||
export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`;
|
||||
export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`;
|
||||
export const defaultTraefikImage = `traefik:v2.6`;
|
||||
|
||||
const mainTraefikEndpoint = dev
|
||||
? 'http://host.docker.internal:3000/webhooks/traefik/main.json'
|
||||
: 'http://coolify:3000/webhooks/traefik/main.json';
|
||||
|
||||
const otherTraefikEndpoint = dev
|
||||
? 'http://host.docker.internal:3000/webhooks/traefik/other.json'
|
||||
: 'http://coolify:3000/webhooks/traefik/other.json';
|
||||
|
||||
export async function haproxyInstance(): Promise<Got> {
|
||||
const { proxyPassword } = await db.listSettings();
|
||||
@ -98,13 +108,21 @@ export async function checkHAProxy(haproxy?: Got): Promise<void> {
|
||||
}
|
||||
|
||||
export async function stopTcpHttpProxy(
|
||||
id: string,
|
||||
destinationDocker: DestinationDocker,
|
||||
publicPort: number
|
||||
publicPort: number,
|
||||
forceName: string = null
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
const { engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
const containerName = `haproxy-for-${publicPort}`;
|
||||
const settings = await db.listSettings();
|
||||
let containerName = `${id}-${publicPort}`;
|
||||
if (!settings.isTraefikUsed) {
|
||||
containerName = `haproxy-for-${publicPort}`;
|
||||
}
|
||||
if (forceName) containerName = forceName;
|
||||
const found = await checkContainer(engine, containerName);
|
||||
|
||||
try {
|
||||
if (found) {
|
||||
return await asyncExecShell(
|
||||
@ -115,12 +133,76 @@ export async function stopTcpHttpProxy(
|
||||
return error;
|
||||
}
|
||||
}
|
||||
export async function startTcpProxy(
|
||||
export async function startTraefikTCPProxy(
|
||||
destinationDocker: DestinationDocker,
|
||||
id: string,
|
||||
publicPort: number,
|
||||
privatePort: number,
|
||||
volume?: string
|
||||
type?: string
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
const { network, engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
const containerName = `${id}-${publicPort}`;
|
||||
const found = await checkContainer(engine, containerName, true);
|
||||
let dependentId = id;
|
||||
if (type === 'wordpressftp') dependentId = `${id}-ftp`;
|
||||
const foundDependentContainer = await checkContainer(engine, dependentId, true);
|
||||
try {
|
||||
if (foundDependentContainer && !found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||
);
|
||||
const ip = JSON.parse(Config)[0].Gateway;
|
||||
const tcpProxy = {
|
||||
version: '3.5',
|
||||
services: {
|
||||
[`${id}-${publicPort}`]: {
|
||||
container_name: containerName,
|
||||
image: 'traefik:v2.6',
|
||||
command: [
|
||||
`--entrypoints.tcp.address=:${publicPort}`,
|
||||
`--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp`,
|
||||
'--providers.http.pollTimeout=2s',
|
||||
'--log.level=error'
|
||||
],
|
||||
ports: [`${publicPort}:${publicPort}`],
|
||||
extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`],
|
||||
volumes: ['/var/run/docker.sock:/var/run/docker.sock'],
|
||||
networks: ['coolify-infra', network]
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: false,
|
||||
name: network
|
||||
},
|
||||
'coolify-infra': {
|
||||
external: false,
|
||||
name: 'coolify-infra'
|
||||
}
|
||||
}
|
||||
};
|
||||
await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy));
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f /tmp/docker-compose-${id}.yaml up -d`
|
||||
);
|
||||
await fs.rm(`/tmp/docker-compose-${id}.yaml`);
|
||||
}
|
||||
if (!foundDependentContainer && found) {
|
||||
return await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
export async function startTcpProxy(
|
||||
destinationDocker: DestinationDocker,
|
||||
id: string,
|
||||
publicPort: number,
|
||||
privatePort: number
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
const { network, engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
@ -128,7 +210,6 @@ export async function startTcpProxy(
|
||||
const containerName = `haproxy-for-${publicPort}`;
|
||||
const found = await checkContainer(engine, containerName, true);
|
||||
const foundDependentContainer = await checkContainer(engine, id, true);
|
||||
|
||||
try {
|
||||
if (foundDependentContainer && !found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
@ -136,9 +217,7 @@ export async function startTcpProxy(
|
||||
);
|
||||
const ip = JSON.parse(Config)[0].Gateway;
|
||||
return await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} ${
|
||||
volume ? `-v ${volume}` : ''
|
||||
} -d coollabsio/${defaultProxyImageTcp}`
|
||||
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageTcp}`
|
||||
);
|
||||
}
|
||||
if (!foundDependentContainer && found) {
|
||||
@ -151,6 +230,75 @@ export async function startTcpProxy(
|
||||
}
|
||||
}
|
||||
|
||||
export async function startTraefikHTTPProxy(
|
||||
destinationDocker: DestinationDocker,
|
||||
id: string,
|
||||
publicPort: number,
|
||||
privatePort: number
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
const { network, engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
|
||||
const containerName = `${id}-${publicPort}`;
|
||||
const found = await checkContainer(engine, containerName, true);
|
||||
const foundDependentContainer = await checkContainer(engine, id, true);
|
||||
|
||||
try {
|
||||
if (foundDependentContainer && !found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||
);
|
||||
const ip = JSON.parse(Config)[0].Gateway;
|
||||
const tcpProxy = {
|
||||
version: '3.5',
|
||||
services: {
|
||||
[`${id}-${publicPort}`]: {
|
||||
container_name: containerName,
|
||||
image: 'traefik:v2.6',
|
||||
command: [
|
||||
`--entrypoints.http.address=:${publicPort}`,
|
||||
`--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`,
|
||||
'--providers.http.pollTimeout=2s',
|
||||
'--certificatesresolvers.letsencrypt.acme.httpchallenge=true',
|
||||
'--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json',
|
||||
'--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http',
|
||||
'--log.level=error'
|
||||
],
|
||||
ports: [`${publicPort}:${publicPort}`],
|
||||
extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`],
|
||||
networks: ['coolify-infra', network],
|
||||
volumes: ['coolify-traefik-letsencrypt:/etc/traefik/acme']
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: false,
|
||||
name: network
|
||||
},
|
||||
'coolify-infra': {
|
||||
external: false,
|
||||
name: 'coolify-infra'
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
'coolify-traefik-letsencrypt': {}
|
||||
}
|
||||
};
|
||||
await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy));
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f /tmp/docker-compose-${id}.yaml up -d`
|
||||
);
|
||||
await fs.rm(`/tmp/docker-compose-${id}.yaml`);
|
||||
}
|
||||
if (!foundDependentContainer && found) {
|
||||
return await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
export async function startHttpProxy(
|
||||
destinationDocker: DestinationDocker,
|
||||
id: string,
|
||||
@ -197,10 +345,50 @@ export async function startCoolifyProxy(engine: string): Promise<void> {
|
||||
`DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}`
|
||||
);
|
||||
await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
|
||||
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
|
||||
}
|
||||
await configureNetworkCoolifyProxy(engine);
|
||||
}
|
||||
|
||||
export async function startTraefikProxy(engine: string): Promise<void> {
|
||||
const host = getEngine(engine);
|
||||
const found = await checkContainer(engine, 'coolify-proxy', true);
|
||||
const { id, proxyPassword, proxyUser } = await db.listSettings();
|
||||
if (!found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||
);
|
||||
const ip = JSON.parse(Config)[0].Gateway;
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker run --restart always \
|
||||
--add-host 'host.docker.internal:host-gateway' \
|
||||
--add-host 'host.docker.internal:${ip}' \
|
||||
-v coolify-traefik-letsencrypt:/etc/traefik/acme \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--network coolify-infra \
|
||||
-p "80:80" \
|
||||
-p "443:443" \
|
||||
-p "8080:8080" \
|
||||
--name coolify-proxy \
|
||||
-d ${defaultTraefikImage} \
|
||||
--api.insecure=true \
|
||||
--entrypoints.web.address=:80 \
|
||||
--entrypoints.websecure.address=:443 \
|
||||
--providers.docker=true \
|
||||
--providers.docker.exposedbydefault=false \
|
||||
--providers.http.endpoint=${mainTraefikEndpoint} \
|
||||
--providers.http.pollTimeout=5s \
|
||||
--certificatesresolvers.letsencrypt.acme.httpchallenge=true \
|
||||
--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \
|
||||
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \
|
||||
--log.level=error`
|
||||
);
|
||||
await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
|
||||
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
|
||||
}
|
||||
await configureNetworkTraefikProxy(engine);
|
||||
}
|
||||
|
||||
export async function isContainerExited(engine: string, containerName: string): Promise<boolean> {
|
||||
let isExited = false;
|
||||
const host = getEngine(engine);
|
||||
@ -245,6 +433,21 @@ export async function checkContainer(
|
||||
return containerFound;
|
||||
}
|
||||
|
||||
export async function getContainerUsage(engine: string, container: string): Promise<any> {
|
||||
const host = getEngine(engine);
|
||||
try {
|
||||
const { stdout } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker container stats ${container} --no-stream --no-trunc --format "{{json .}}"`
|
||||
);
|
||||
return JSON.parse(stdout);
|
||||
} catch (err) {
|
||||
return {
|
||||
MemUsage: 0,
|
||||
CPUPerc: 0,
|
||||
NetIO: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
export async function stopCoolifyProxy(
|
||||
engine: string
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
@ -263,6 +466,24 @@ export async function stopCoolifyProxy(
|
||||
return error;
|
||||
}
|
||||
}
|
||||
export async function stopTraefikProxy(
|
||||
engine: string
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
const host = getEngine(engine);
|
||||
const found = await checkContainer(engine, 'coolify-proxy');
|
||||
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: false });
|
||||
const { id } = await db.prisma.setting.findFirst({});
|
||||
await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
|
||||
try {
|
||||
if (found) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker stop -t 0 coolify-proxy && docker rm coolify-proxy`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureNetworkCoolifyProxy(engine: string): Promise<void> {
|
||||
const host = getEngine(engine);
|
||||
@ -279,3 +500,19 @@ export async function configureNetworkCoolifyProxy(engine: string): Promise<void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureNetworkTraefikProxy(engine: string): Promise<void> {
|
||||
const host = getEngine(engine);
|
||||
const destinations = await db.prisma.destinationDocker.findMany({ where: { engine } });
|
||||
const { stdout: networks } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker ps -a --filter name=coolify-proxy --format '{{json .Networks}}'`
|
||||
);
|
||||
const configuredNetworks = networks.replace(/"/g, '').replace('\n', '').split(',');
|
||||
for (const destination of destinations) {
|
||||
if (!configuredNetworks.includes(destination.network)) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-proxy`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -290,3 +290,30 @@ export async function generateSSLCerts(): Promise<void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function renewSSLCerts(): Promise<void> {
|
||||
if (!dev) {
|
||||
const host = 'unix:///var/run/docker.sock';
|
||||
await asyncExecShell(`docker pull alpine:latest`);
|
||||
const certbotImage =
|
||||
process.arch === 'x64' ? 'certbot/certbot' : 'certbot/certbot:arm64v8-latest';
|
||||
|
||||
const { stdout: certificates } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "ls -1 /etc/letsencrypt/live/ | grep -v README"`
|
||||
);
|
||||
|
||||
for (const certificate of certificates.trim().split('\n')) {
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm --name certbot-renewal -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" ${certbotImage} --cert-name ${certificate} --logs-dir /etc/letsencrypt/logs renew --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080`
|
||||
);
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "test -d /etc/letsencrypt/live/${certificate}/ && cat /etc/letsencrypt/live/${certificate}/fullchain.pem /etc/letsencrypt/live/${certificate}/privkey.pem > /app/ssl/${certificate}.pem"`
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
await reloadHaproxy('unix:///var/run/docker.sock');
|
||||
}
|
||||
}
|
||||
|
@ -178,13 +178,15 @@
|
||||
"delete_application": "Delete application",
|
||||
"permission_denied_delete_application": "You do not have permission to delete this application",
|
||||
"domain_already_in_use": "Domain {{domain}} is already used.",
|
||||
"dns_not_set_error": "DNS not set or propogated for {{domain}}.<br><br>Please check your DNS settings.",
|
||||
"dns_not_set_error": "DNS not set correctly or propogated for {{domain}}.<br><br>Please check your DNS settings.",
|
||||
"domain_required": "Domain is required.",
|
||||
"settings_saved": "Settings saved.",
|
||||
"dns_not_set_partial_error": "DNS not set",
|
||||
"domain_not_valid": "Could not resolve domain or it's not pointing to the server IP address.<br><br>Please check your DNS configuration and try again.",
|
||||
"git_source": "Git Source",
|
||||
"git_repository": "Git Repository",
|
||||
"build_pack": "Build Pack",
|
||||
"base_image": "Deplyoment Image",
|
||||
"base_image": "Deployment Image",
|
||||
"base_image_explainer": "Image that will be used for the deployment.",
|
||||
"base_build_image": "Build Image",
|
||||
"base_build_image_explainer": "Image that will be used during the build process.",
|
||||
@ -204,6 +206,7 @@
|
||||
"enable_automatic_deployment": "Enable Automatic Deployment",
|
||||
"enable_auto_deploy_webhooks": "Enable automatic deployment through webhooks.",
|
||||
"enable_mr_pr_previews": "Enable MR/PR Previews",
|
||||
"expose_a_port": "Expose a port",
|
||||
"enable_preview_deploy_mr_pr_requests": "Enable preview deployments from pull or merge requests.",
|
||||
"debug_logs": "Debug Logs",
|
||||
"enable_debug_log_during_build": "Enable debug logs during build phase.<br><span class='text-red-500 font-bold'>Sensitive information</span> could be visible and saved in logs.",
|
||||
@ -311,7 +314,7 @@
|
||||
"credential_stat_explainer": "Credentials for <a class=\"text-white font-bold\" href=\"{{link}}\" target=\"_blank\">stats</a> page.",
|
||||
"auto_update_enabled": "Auto update enabled?",
|
||||
"auto_update_enabled_explainer": "Enable automatic updates for Coolify. It will be done automatically behind the scenes, if there is no build process running.",
|
||||
"generate_www_non_www_ssl": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-yellow-500'>both DNS entries</span> set in advance.<br><br>Service needs to be restarted.",
|
||||
"generate_www_non_www_ssl": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-yellow-500'>both DNS entries</span> set in advance.",
|
||||
"is_dns_check_enabled": "DNS check enabled?",
|
||||
"is_dns_check_enabled_explainer": "You can disable DNS check before creating SSL certificates.<br><br>Turning it off is useful when Coolify is behind a reverse proxy or tunnel."
|
||||
},
|
||||
|
@ -61,6 +61,7 @@
|
||||
"enable_debug_log_during_build": "Activez les journaux de débogage pendant la phase de build.<br><span class='text-red-500 font-bold'>Les informations sensibles</span> peuvent être visibles et enregistrées dans les journaux.",
|
||||
"enable_mr_pr_previews": "Activer les aperçus MR/PR",
|
||||
"enable_preview_deploy_mr_pr_requests": "Activez les déploiements de prévisualisation à partir de demandes d'extraction ou de fusion.",
|
||||
"expose_a_port": "Exposer un port",
|
||||
"features": "Caractéristiques",
|
||||
"git_repository": "Dépôt Git",
|
||||
"git_source": "Source Git",
|
||||
|
@ -48,6 +48,7 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
denoOptions,
|
||||
exposePort,
|
||||
baseImage,
|
||||
baseBuildImage
|
||||
} = job.data;
|
||||
@ -152,6 +153,7 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
||||
JSON.stringify({
|
||||
buildPack,
|
||||
port,
|
||||
exposePort,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
@ -207,7 +209,7 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
||||
tag,
|
||||
workdir,
|
||||
docker,
|
||||
port,
|
||||
port: exposePort ? `${exposePort}:${port}` : port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
@ -263,7 +265,7 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
port,
|
||||
port: exposePort ? `${exposePort}:${port}` : port,
|
||||
commit,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
@ -298,6 +300,7 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
||||
labels,
|
||||
depends_on: [],
|
||||
restart: 'always',
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
// logging: {
|
||||
// driver: 'fluentd',
|
||||
// },
|
||||
@ -325,7 +328,7 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
||||
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||
} catch (error) {
|
||||
await saveBuildLog({ line: error, buildId, applicationId });
|
||||
sentry.captureException(error);
|
||||
// sentry.captureException(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||
|
@ -41,7 +41,6 @@ export default async function (): Promise<void> {
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
console.log(`Is LowDiskSpace detected? ${lowDiskSpace}`);
|
||||
if (lowDiskSpace) {
|
||||
// Cleanup old coolify images
|
||||
try {
|
||||
|
@ -116,8 +116,8 @@ const cron = async (): Promise<void> => {
|
||||
await queue.proxyTcpHttp.add('proxyTcpHttp', {}, { repeat: { every: 10000 } });
|
||||
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
|
||||
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
|
||||
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
||||
await queue.autoUpdater.add('autoUpdater', {}, { repeat: { every: 60000 } });
|
||||
if (!dev) await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
||||
if (!dev) await queue.autoUpdater.add('autoUpdater', {}, { repeat: { every: 60000 } });
|
||||
};
|
||||
cron().catch((error) => {
|
||||
console.log('cron failed to start');
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler, prisma } from '$lib/database';
|
||||
import { configureHAProxy } from '$lib/haproxy/configuration';
|
||||
|
||||
export default async function (): Promise<void | {
|
||||
@ -6,7 +6,10 @@ export default async function (): Promise<void | {
|
||||
body: { message: string; error: string };
|
||||
}> {
|
||||
try {
|
||||
return await configureHAProxy();
|
||||
const settings = await prisma.setting.findFirst();
|
||||
if (!settings.isTraefikUsed) {
|
||||
return await configureHAProxy();
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error.response?.body || error);
|
||||
}
|
||||
|
@ -1,5 +1,16 @@
|
||||
import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database';
|
||||
import { startCoolifyProxy, startHttpProxy, startTcpProxy } from '$lib/haproxy';
|
||||
import {
|
||||
checkContainer,
|
||||
startCoolifyProxy,
|
||||
startHttpProxy,
|
||||
startTcpProxy,
|
||||
startTraefikHTTPProxy,
|
||||
startTraefikProxy,
|
||||
startTraefikTCPProxy,
|
||||
stopCoolifyProxy,
|
||||
stopTcpHttpProxy,
|
||||
stopTraefikProxy
|
||||
} from '$lib/haproxy';
|
||||
|
||||
export default async function (): Promise<void | {
|
||||
status: number;
|
||||
@ -7,12 +18,23 @@ export default async function (): Promise<void | {
|
||||
}> {
|
||||
try {
|
||||
// Coolify Proxy
|
||||
const engine = '/var/run/docker.sock';
|
||||
const settings = await prisma.setting.findFirst();
|
||||
const localDocker = await prisma.destinationDocker.findFirst({
|
||||
where: { engine: '/var/run/docker.sock' }
|
||||
where: { engine, network: 'coolify' }
|
||||
});
|
||||
if (localDocker && localDocker.isCoolifyProxyUsed) {
|
||||
await startCoolifyProxy('/var/run/docker.sock');
|
||||
if (settings.isTraefikUsed) {
|
||||
const found = await checkContainer(engine, 'coolify-haproxy');
|
||||
if (found) await stopCoolifyProxy(engine);
|
||||
await startTraefikProxy(engine);
|
||||
} else {
|
||||
const found = await checkContainer(engine, 'coolify-proxy');
|
||||
if (found) await stopTraefikProxy(engine);
|
||||
await startCoolifyProxy(engine);
|
||||
}
|
||||
}
|
||||
|
||||
// TCP Proxies
|
||||
const databasesWithPublicPort = await prisma.database.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
@ -21,8 +43,16 @@ export default async function (): Promise<void | {
|
||||
for (const database of databasesWithPublicPort) {
|
||||
const { destinationDockerId, destinationDocker, publicPort, id } = database;
|
||||
if (destinationDockerId) {
|
||||
const { privatePort } = generateDatabaseConfiguration(database);
|
||||
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||
if (destinationDocker.isCoolifyProxyUsed) {
|
||||
const { privatePort } = generateDatabaseConfiguration(database);
|
||||
if (settings.isTraefikUsed) {
|
||||
await stopTcpHttpProxy(id, destinationDocker, publicPort, `haproxy-for-${publicPort}`);
|
||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||
} else {
|
||||
await stopTcpHttpProxy(id, destinationDocker, publicPort, `${id}-${publicPort}`);
|
||||
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const wordpressWithFtp = await prisma.wordpress.findMany({
|
||||
@ -33,20 +63,38 @@ export default async function (): Promise<void | {
|
||||
const { service, ftpPublicPort } = ftp;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId) {
|
||||
await startTcpProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22);
|
||||
if (destinationDocker.isCoolifyProxyUsed) {
|
||||
if (settings.isTraefikUsed) {
|
||||
await stopTcpHttpProxy(
|
||||
id,
|
||||
destinationDocker,
|
||||
ftpPublicPort,
|
||||
`haproxy-for-${ftpPublicPort}`
|
||||
);
|
||||
await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp');
|
||||
} else {
|
||||
await stopTcpHttpProxy(id, destinationDocker, ftpPublicPort, `${id}-${ftpPublicPort}`);
|
||||
await startTcpProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP Proxies
|
||||
const minioInstances = await prisma.minio.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
for (const minio of minioInstances) {
|
||||
const { service, publicPort } = minio;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId) {
|
||||
await startHttpProxy(destinationDocker, id, publicPort, 9000);
|
||||
if (!settings.isTraefikUsed) {
|
||||
const minioInstances = await prisma.minio.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
for (const minio of minioInstances) {
|
||||
const { service, publicPort } = minio;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId) {
|
||||
if (destinationDocker.isCoolifyProxyUsed) {
|
||||
await stopTcpHttpProxy(id, destinationDocker, publicPort, `${id}-${publicPort}`);
|
||||
await startHttpProxy(destinationDocker, id, publicPort, 9000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { generateSSLCerts } from '$lib/letsencrypt';
|
||||
import { prisma } from '$lib/database';
|
||||
|
||||
export default async function (): Promise<void> {
|
||||
try {
|
||||
return await generateSSLCerts();
|
||||
const settings = await prisma.setting.findFirst();
|
||||
if (!settings.isTraefikUsed) {
|
||||
return await generateSSLCerts();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { asyncExecShell } from '$lib/common';
|
||||
import { reloadHaproxy } from '$lib/haproxy';
|
||||
import { renewSSLCerts } from '$lib/letsencrypt';
|
||||
import { prisma } from '$lib/database';
|
||||
|
||||
export default async function (): Promise<void> {
|
||||
await asyncExecShell(
|
||||
`docker run --rm --name certbot-renewal -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs renew`
|
||||
);
|
||||
await reloadHaproxy('unix:///var/run/docker.sock');
|
||||
try {
|
||||
const settings = await prisma.setting.findFirst();
|
||||
if (!settings.isTraefikUsed) {
|
||||
return await renewSSLCerts();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
3
src/lib/realtime.ts
Normal file
3
src/lib/realtime.ts
Normal file
@ -0,0 +1,3 @@
|
||||
// import ioClient from 'socket.io-client';
|
||||
// const socket = ioClient('http://localhost:3000');
|
||||
// export const io = socket;
|
@ -12,3 +12,14 @@ export const features: Readable<{ latestVersion: string; beta: boolean }> = read
|
||||
beta: browser && window.localStorage.getItem('beta') === 'true',
|
||||
latestVersion: browser && window.localStorage.getItem('latestVersion')
|
||||
});
|
||||
|
||||
export const isTraefikUsed: Writable<boolean> = writable(false);
|
||||
|
||||
export const status: Writable<any> = writable({
|
||||
application: {
|
||||
isRunning: false,
|
||||
isExited: false,
|
||||
loading: false,
|
||||
initialLoading: true
|
||||
}
|
||||
});
|
||||
|
@ -12,6 +12,7 @@ export type BuilderJob = {
|
||||
buildPack: BuildPackName;
|
||||
projectId: number;
|
||||
port: number;
|
||||
exposePort?: number;
|
||||
installCommand: string;
|
||||
buildCommand?: string;
|
||||
startCommand?: string;
|
||||
|
@ -18,6 +18,7 @@ export type ComposeFileService = {
|
||||
restart: ComposeFileRestartOption;
|
||||
depends_on?: string[];
|
||||
command?: string;
|
||||
ports?: string[];
|
||||
build?: {
|
||||
context: string;
|
||||
dockerfile: string;
|
||||
|
39
src/routes/_Trend.svelte
Normal file
39
src/routes/_Trend.svelte
Normal file
@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
export let trend;
|
||||
</script>
|
||||
|
||||
{#if trend === 'up'}
|
||||
<span class="-mt-1 inline-flex px-2 text-green-500">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8"
|
||||
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" />
|
||||
<line x1="17" y1="7" x2="7" y2="17" />
|
||||
<polyline points="8 7 17 7 17 16" />
|
||||
</svg></span
|
||||
>
|
||||
{:else if trend === 'down'}
|
||||
<span class="text-red-500 px-2 inline-flex -mt-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8"
|
||||
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" />
|
||||
<line x1="7" y1="7" x2="17" y2="17" />
|
||||
<polyline points="17 8 17 17 8 17" />
|
||||
</svg>
|
||||
</span>
|
||||
{/if}
|
@ -34,23 +34,30 @@
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export let settings;
|
||||
import '../tailwind.css';
|
||||
import { SvelteToast, toast } from '@zerodevx/svelte-toast';
|
||||
import { page, session } from '$app/stores';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { onMount } from 'svelte';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { asyncSleep } from '$lib/components/common';
|
||||
import { del, get, post } from '$lib/api';
|
||||
import { dev } from '$app/env';
|
||||
import { features } from '$lib/store';
|
||||
let isUpdateAvailable = false;
|
||||
import { features, isTraefikUsed } from '$lib/store';
|
||||
import { navigating } from '$app/stores';
|
||||
import PageLoader from '$lib/components/PageLoader.svelte';
|
||||
|
||||
$isTraefikUsed = settings?.isTraefikUsed || false;
|
||||
|
||||
let isUpdateAvailable = false;
|
||||
let updateStatus = {
|
||||
found: false,
|
||||
loading: false,
|
||||
success: null
|
||||
};
|
||||
let latestVersion = 'latest';
|
||||
|
||||
onMount(async () => {
|
||||
if ($session.userId) {
|
||||
const overrideVersion = $features.latestVersion;
|
||||
@ -78,6 +85,7 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
await del(`/logout.json`, {});
|
||||
@ -128,16 +136,23 @@
|
||||
<title>Coolify</title>
|
||||
{#if !$session.whiteLabeled}
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
{:else if $session.whiteLabelDetails.icon}
|
||||
<link rel="icon" href={$session.whiteLabelDetails.icon} />
|
||||
{/if}
|
||||
</svelte:head>
|
||||
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
|
||||
{#if $navigating}
|
||||
<div out:fade={{ delay: 100 }}>
|
||||
<PageLoader />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $session.userId}
|
||||
<nav class="nav-main">
|
||||
<div class="flex h-screen w-full flex-col items-center transition-all duration-100">
|
||||
{#if !$session.whiteLabeled}
|
||||
<div class="my-4 h-10 w-10"><img src="/favicon.png" alt="coolLabs logo" /></div>
|
||||
{/if}
|
||||
<div class="flex flex-col space-y-4 py-2" class:mt-2={$session.whiteLabeled}>
|
||||
<div class="flex flex-col space-y-2 py-2" class:mt-2={$session.whiteLabeled}>
|
||||
<a
|
||||
sveltekit:prefetch
|
||||
href="/"
|
||||
@ -222,7 +237,6 @@
|
||||
<polyline points="10 15 13 18 10 21" />
|
||||
</svg>
|
||||
</a>
|
||||
<div class="border-t border-stone-700" />
|
||||
<a
|
||||
sveltekit:prefetch
|
||||
href="/destinations"
|
||||
@ -284,7 +298,6 @@
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
||||
</svg>
|
||||
</a>
|
||||
<div class="border-t border-stone-700" />
|
||||
<a
|
||||
sveltekit:prefetch
|
||||
href="/services"
|
||||
@ -423,7 +436,7 @@
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col space-y-4 py-2">
|
||||
<div class="flex flex-col space-y-2 py-2">
|
||||
<a
|
||||
sveltekit:prefetch
|
||||
href="/iam"
|
||||
|
@ -17,7 +17,7 @@
|
||||
const endpoint = `/applications/${params.id}.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
let { application, isRunning, isExited, appId, githubToken, gitlabToken } = await res.json();
|
||||
let { application, appId, githubToken, gitlabToken } = await res.json();
|
||||
if (!application || Object.entries(application).length === 0) {
|
||||
return {
|
||||
status: 302,
|
||||
@ -45,13 +45,10 @@
|
||||
return {
|
||||
props: {
|
||||
application,
|
||||
isRunning,
|
||||
isExited,
|
||||
githubToken,
|
||||
gitlabToken
|
||||
},
|
||||
stuff: {
|
||||
isRunning,
|
||||
application,
|
||||
appId
|
||||
}
|
||||
@ -67,8 +64,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
export let isRunning;
|
||||
export let isExited;
|
||||
export let githubToken;
|
||||
export let gitlabToken;
|
||||
import { page, session } from '$app/stores';
|
||||
@ -77,7 +72,7 @@
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
import { del, get, post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
import { gitTokens } from '$lib/store';
|
||||
import { gitTokens, status } from '$lib/store';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { disabledButton } from '$lib/store';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
@ -135,17 +130,31 @@
|
||||
}
|
||||
}
|
||||
async function getStatus() {
|
||||
statusInterval = setInterval(async () => {
|
||||
const data = await get(`/applications/${id}.json`);
|
||||
isRunning = data.isRunning;
|
||||
isExited = data.isExited;
|
||||
}, 1000);
|
||||
if ($status.application.loading) return;
|
||||
$status.application.loading = true;
|
||||
const data = await get(`/applications/${id}/status.json`);
|
||||
$status.application.isRunning = data.isRunning;
|
||||
$status.application.isExited = data.isExited;
|
||||
$status.application.loading = false;
|
||||
$status.application.initialLoading = false;
|
||||
}
|
||||
onDestroy(() => {
|
||||
$status.application.initialLoading = true;
|
||||
clearInterval(statusInterval);
|
||||
});
|
||||
onMount(async () => {
|
||||
await getStatus();
|
||||
if (!application.gitSourceId || !application.destinationDockerId || !application.fqdn) {
|
||||
$status.application.initialLoading = false;
|
||||
$status.application.isRunning = false;
|
||||
$status.application.isExited = false;
|
||||
$status.application.loading = false;
|
||||
return;
|
||||
} else {
|
||||
await getStatus();
|
||||
statusInterval = setInterval(async () => {
|
||||
await getStatus();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -153,16 +162,16 @@
|
||||
{#if loading}
|
||||
<Loading fullscreen cover />
|
||||
{:else}
|
||||
{#if isExited}
|
||||
{#if $status.application.isExited}
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
||||
class=" icons bg-transparent tooltip-bottom text-sm flex items-center text-red-500 tooltip-red-500"
|
||||
class=" icons tooltip-bottom tooltip-red-500 flex items-center bg-transparent text-sm text-red-500"
|
||||
data-tooltip="Application exited with an error!"
|
||||
sveltekit:prefetch
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentcolor"
|
||||
@ -179,20 +188,43 @@
|
||||
</svg>
|
||||
</a>
|
||||
{/if}
|
||||
{#if isRunning}
|
||||
{#if $status.application.initialLoading}
|
||||
<button
|
||||
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
||||
>
|
||||
<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="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
||||
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
||||
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
||||
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
||||
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
||||
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
||||
</svg>
|
||||
</button>
|
||||
{:else if $status.application.isRunning}
|
||||
<button
|
||||
on:click={stopApplication}
|
||||
title="Stop application"
|
||||
type="submit"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
|
||||
class="icons tooltip-bottom flex items-center space-x-2 bg-transparent text-sm text-red-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? $t('application.stop_application')
|
||||
: $t('application.permission_denied_stop_application')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -210,14 +242,14 @@
|
||||
title="Rebuild application"
|
||||
type="submit"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:text-green-500"
|
||||
class="icons tooltip-bottom flex items-center space-x-2 bg-transparent text-sm hover:text-green-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? 'Rebuild application'
|
||||
: 'You do not have permission to rebuild application.'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -239,14 +271,14 @@
|
||||
title="Build and start application"
|
||||
type="submit"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
|
||||
class="icons tooltip-bottom flex items-center space-x-2 bg-transparent text-sm text-green-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? 'Build and start application'
|
||||
: 'You do not have permission to Build and start application.'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -261,18 +293,18 @@
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<div class="border border-coolgray-500 h-8" />
|
||||
<div class="h-8 border border-coolgray-500" />
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-yellow-500 rounded"
|
||||
class="rounded hover:text-yellow-500"
|
||||
class:text-yellow-500={$page.url.pathname === `/applications/${id}`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}`}
|
||||
>
|
||||
<button
|
||||
title="Configurations"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip="Configurations"
|
||||
>
|
||||
<svg
|
||||
@ -301,19 +333,19 @@
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/secrets` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-pink-500 rounded"
|
||||
class="rounded hover:text-pink-500"
|
||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||
>
|
||||
<button
|
||||
title="Secret"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip="Secret"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -333,19 +365,19 @@
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/storage` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-pink-500 rounded"
|
||||
class="rounded hover:text-pink-500"
|
||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||
>
|
||||
<button
|
||||
title="Persistent Storage"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip="Persistent Storage"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -363,19 +395,19 @@
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/previews` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-orange-500 rounded"
|
||||
class="rounded hover:text-orange-500"
|
||||
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||
>
|
||||
<button
|
||||
title="Previews"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip="Previews"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -392,18 +424,18 @@
|
||||
</svg></button
|
||||
></a
|
||||
>
|
||||
<div class="border border-coolgray-500 h-8" />
|
||||
<div class="h-8 border border-coolgray-500" />
|
||||
<a
|
||||
href={!$disabledButton && isRunning ? `/applications/${id}/logs` : null}
|
||||
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-sky-500 rounded"
|
||||
class="rounded hover:text-sky-500"
|
||||
class:text-sky-500={$page.url.pathname === `/applications/${id}/logs`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`}
|
||||
>
|
||||
<button
|
||||
title={$t('application.logs')}
|
||||
disabled={$disabledButton || !isRunning}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
disabled={$disabledButton || !$status.application.isRunning}
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip={$t('application.logs')}
|
||||
>
|
||||
<svg
|
||||
@ -428,14 +460,14 @@
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/logs/build` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-red-500 rounded"
|
||||
class="rounded hover:text-red-500"
|
||||
class:text-red-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
||||
>
|
||||
<button
|
||||
title="Build Logs"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip="Build Logs"
|
||||
>
|
||||
<svg
|
||||
@ -460,7 +492,7 @@
|
||||
</svg>
|
||||
</button></a
|
||||
>
|
||||
<div class="border border-coolgray-500 h-8" />
|
||||
<div class="h-8 border border-coolgray-500" />
|
||||
|
||||
<button
|
||||
on:click={() => deleteApplication(application.name)}
|
||||
@ -468,7 +500,7 @@
|
||||
type="submit"
|
||||
disabled={!$session.isAdmin}
|
||||
class:hover:text-red-500={$session.isAdmin}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip={$session.isAdmin
|
||||
? $t('application.delete_application')
|
||||
: $t('application.permission_denied_delete_application')}
|
||||
|
@ -3,6 +3,61 @@ import { buildQueue } from '$lib/queues';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import * as db from '$lib/database';
|
||||
|
||||
async function cleanupDB(buildId: string) {
|
||||
const data = await db.prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (data?.status === 'queued' || data?.status === 'running') {
|
||||
await db.prisma.build.update({ where: { id: buildId }, data: { status: 'failed' } });
|
||||
}
|
||||
}
|
||||
|
||||
async function stopBuild(buildId, applicationId) {
|
||||
let count = 0;
|
||||
await new Promise<void>(async (resolve, reject) => {
|
||||
const job = await buildQueue.getJob(buildId);
|
||||
if (!job) {
|
||||
await cleanupDB(buildId);
|
||||
return resolve();
|
||||
}
|
||||
const {
|
||||
destinationDocker: { engine }
|
||||
} = job?.data;
|
||||
const host = getEngine(engine);
|
||||
let interval = setInterval(async () => {
|
||||
try {
|
||||
const data = await db.prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (data?.status === 'failed') {
|
||||
clearInterval(interval);
|
||||
return resolve();
|
||||
}
|
||||
if (count > 100) {
|
||||
clearInterval(interval);
|
||||
return reject(new Error('Build canceled'));
|
||||
}
|
||||
|
||||
const { stdout: buildContainers } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker container ls --filter "label=coolify.buildId=${buildId}" --format '{{json .}}'`
|
||||
);
|
||||
if (buildContainers) {
|
||||
const containersArray = buildContainers.trim().split('\n');
|
||||
for (const container of containersArray) {
|
||||
const containerObj = JSON.parse(container);
|
||||
const id = containerObj.ID;
|
||||
if (!containerObj.Names.startsWith(`${applicationId}`)) {
|
||||
await removeDestinationDocker({ id, engine });
|
||||
clearInterval(interval);
|
||||
await saveBuildLog({
|
||||
line: 'Canceled by user!',
|
||||
buildId: job.data.build_id,
|
||||
applicationId: job.data.id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
count++;
|
||||
} catch (error) {}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { buildId, applicationId } = await event.request.json();
|
||||
if (!buildId) {
|
||||
@ -14,50 +69,7 @@ export const post: RequestHandler = async (event) => {
|
||||
};
|
||||
}
|
||||
try {
|
||||
let count = 0;
|
||||
await new Promise<void>(async (resolve, reject) => {
|
||||
const job = await buildQueue.getJob(buildId);
|
||||
const {
|
||||
destinationDocker: { engine }
|
||||
} = job.data;
|
||||
const host = getEngine(engine);
|
||||
let interval = setInterval(async () => {
|
||||
const { status } = await db.prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (status === 'failed') {
|
||||
clearInterval(interval);
|
||||
return resolve();
|
||||
}
|
||||
if (count > 1200) {
|
||||
clearInterval(interval);
|
||||
reject(new Error('Could not cancel build.'));
|
||||
}
|
||||
try {
|
||||
const { stdout: buildContainers } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker container ls --filter "label=coolify.buildId=${buildId}" --format '{{json .}}'`
|
||||
);
|
||||
if (buildContainers) {
|
||||
const containersArray = buildContainers.trim().split('\n');
|
||||
for (const container of containersArray) {
|
||||
const containerObj = JSON.parse(container);
|
||||
const id = containerObj.ID;
|
||||
if (!containerObj.Names.startsWith(`${applicationId}`)) {
|
||||
await removeDestinationDocker({ id, engine });
|
||||
clearInterval(interval);
|
||||
await saveBuildLog({
|
||||
line: 'Canceled by user!',
|
||||
buildId: job.data.build_id,
|
||||
applicationId: job.data.id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
count++;
|
||||
} catch (error) {}
|
||||
}, 100);
|
||||
|
||||
resolve();
|
||||
});
|
||||
|
||||
await stopBuild(buildId, applicationId);
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
|
@ -1,21 +1,44 @@
|
||||
import { dev } from '$app/env';
|
||||
import { getDomain, getUserDetails } from '$lib/common';
|
||||
import { checkDomainsIsValidInDNS, getDomain, getUserDetails, isDNSValid } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { promises as dns } from 'dns';
|
||||
import getPort from 'get-port';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
const domain = event.url.searchParams.get('domain');
|
||||
if (!domain) {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: t.get('application.domain_required')
|
||||
}
|
||||
};
|
||||
}
|
||||
try {
|
||||
await isDNSValid(event, domain);
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
let { fqdn, forceSave } = await event.request.json();
|
||||
let { exposePort, fqdn, forceSave, dualCerts } = await event.request.json();
|
||||
fqdn = fqdn.toLowerCase();
|
||||
|
||||
try {
|
||||
const domain = getDomain(fqdn);
|
||||
const { isDNSCheckEnabled } = await db.prisma.setting.findFirst({});
|
||||
const found = await db.isDomainConfigured({ id, fqdn });
|
||||
if (found) {
|
||||
throw {
|
||||
@ -24,25 +47,22 @@ export const post: RequestHandler = async (event) => {
|
||||
})
|
||||
};
|
||||
}
|
||||
if (!dev && !forceSave) {
|
||||
let ip = [];
|
||||
let localIp = [];
|
||||
dns.setServers(['1.1.1.1', '8.8.8.8']);
|
||||
|
||||
try {
|
||||
localIp = await dns.resolve4(event.url.hostname);
|
||||
} catch (error) {}
|
||||
try {
|
||||
ip = await dns.resolve4(domain);
|
||||
} catch (error) {}
|
||||
if (exposePort) {
|
||||
exposePort = Number(exposePort);
|
||||
|
||||
if (localIp?.length > 0) {
|
||||
if (ip?.length === 0 || !ip.includes(localIp[0])) {
|
||||
throw {
|
||||
message: t.get('application.dns_not_set_error', { domain: domain })
|
||||
};
|
||||
}
|
||||
if (exposePort < 1024 || exposePort > 65535) {
|
||||
throw { message: `Exposed Port needs to be between 1024 and 65535.` };
|
||||
}
|
||||
|
||||
const publicPort = await getPort({ port: exposePort });
|
||||
if (publicPort !== exposePort) {
|
||||
throw { message: `Port ${exposePort} is already in use.` };
|
||||
}
|
||||
}
|
||||
|
||||
if (isDNSCheckEnabled && !dev && !forceSave) {
|
||||
return await checkDomainsIsValidInDNS({ event, fqdn, dualCerts });
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -19,10 +19,13 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { t } from '$lib/translations';
|
||||
import { gitTokens } from '$lib/store';
|
||||
|
||||
export let application;
|
||||
export let appId;
|
||||
|
||||
$gitTokens.githubToken = null;
|
||||
|
||||
import GithubRepositories from './_GithubRepositories.svelte';
|
||||
import GitlabRepositories from './_GitlabRepositories.svelte';
|
||||
</script>
|
||||
|
@ -22,6 +22,7 @@ export const post: RequestHandler = async (event) => {
|
||||
JSON.stringify({
|
||||
buildPack: applicationFound.buildPack,
|
||||
port: applicationFound.port,
|
||||
exposePort: applicationFound.exposePort,
|
||||
installCommand: applicationFound.installCommand,
|
||||
buildCommand: applicationFound.buildCommand,
|
||||
startCommand: applicationFound.startCommand
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { asyncExecShell, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer, isContainerExited } from '$lib/haproxy';
|
||||
import { checkContainer, getContainerUsage, isContainerExited } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import { get as getRequest } from '$lib/api';
|
||||
import { setDefaultConfiguration } from '$lib/buildPacks/common';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
@ -14,21 +12,14 @@ export const get: RequestHandler = async (event) => {
|
||||
const { id } = event.params;
|
||||
|
||||
const appId = process.env['COOLIFY_APP_ID'];
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let githubToken = event.locals.cookies?.githubToken || null;
|
||||
let gitlabToken = event.locals.cookies?.gitlabToken || null;
|
||||
try {
|
||||
const application = await db.getApplication({ id, teamId });
|
||||
if (application.destinationDockerId) {
|
||||
isRunning = await checkContainer(application.destinationDocker.engine, id);
|
||||
isExited = await isContainerExited(application.destinationDocker.engine, id);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
isRunning,
|
||||
isExited,
|
||||
application,
|
||||
appId,
|
||||
githubToken,
|
||||
@ -52,6 +43,7 @@ export const post: RequestHandler = async (event) => {
|
||||
buildPack,
|
||||
fqdn,
|
||||
port,
|
||||
exposePort,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
@ -67,6 +59,9 @@ export const post: RequestHandler = async (event) => {
|
||||
baseBuildImage
|
||||
} = await event.request.json();
|
||||
if (port) port = Number(port);
|
||||
if (exposePort) {
|
||||
exposePort = Number(exposePort);
|
||||
}
|
||||
if (denoOptions) denoOptions = denoOptions.trim();
|
||||
|
||||
try {
|
||||
@ -87,6 +82,7 @@ export const post: RequestHandler = async (event) => {
|
||||
name,
|
||||
fqdn,
|
||||
port,
|
||||
exposePort,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
|
@ -4,8 +4,7 @@
|
||||
if (stuff?.application?.id) {
|
||||
return {
|
||||
props: {
|
||||
application: stuff.application,
|
||||
isRunning: stuff.isRunning
|
||||
application: stuff.application
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -36,33 +35,46 @@
|
||||
baseImages: Array<{ value: string; label: string }>;
|
||||
baseBuildImages: Array<{ value: string; label: string }>;
|
||||
};
|
||||
export let isRunning;
|
||||
import { page, session } from '$app/stores';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { onMount } from 'svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import Select from 'svelte-select';
|
||||
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import Setting from '$lib/components/Setting.svelte';
|
||||
import type Prisma from '@prisma/client';
|
||||
import { notNodeDeployments, staticDeployments } from '$lib/components/common';
|
||||
import { getDomain, notNodeDeployments, staticDeployments } from '$lib/components/common';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { post } from '$lib/api';
|
||||
import { get, post } from '$lib/api';
|
||||
import cuid from 'cuid';
|
||||
import { browser } from '$app/env';
|
||||
import { disabledButton } from '$lib/store';
|
||||
import { disabledButton, status } from '$lib/store';
|
||||
import { t } from '$lib/translations';
|
||||
const { id } = $page.params;
|
||||
|
||||
let domainEl: HTMLInputElement;
|
||||
|
||||
let loading = false;
|
||||
|
||||
let usageLoading = false;
|
||||
let usage = {
|
||||
MemUsage: 0,
|
||||
CPUPerc: 0,
|
||||
NetIO: 0
|
||||
};
|
||||
let usageInterval;
|
||||
|
||||
let forceSave = false;
|
||||
let debug = application.settings.debug;
|
||||
let previews = application.settings.previews;
|
||||
let dualCerts = application.settings.dualCerts;
|
||||
let autodeploy = application.settings.autodeploy;
|
||||
|
||||
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||
let isNonWWWDomainOK = false;
|
||||
let isWWWDomainOK = false;
|
||||
|
||||
$: isDisabled = !$session.isAdmin || $status.application.isRunning;
|
||||
let wsgis = [
|
||||
{
|
||||
value: 'None',
|
||||
@ -74,15 +86,29 @@
|
||||
}
|
||||
];
|
||||
function containerClass() {
|
||||
if (!$session.isAdmin || isRunning) {
|
||||
return 'text-white border border-dashed border-coolgray-300 bg-transparent font-thin px-0';
|
||||
return 'text-white border border-dashed border-coolgray-300 bg-transparent font-thin px-0';
|
||||
}
|
||||
|
||||
async function getUsage() {
|
||||
if (usageLoading) return;
|
||||
usageLoading = true;
|
||||
const data = await get(`/applications/${id}/usage.json`);
|
||||
usage = data.usage;
|
||||
usageLoading = false;
|
||||
}
|
||||
onDestroy(() => {
|
||||
clearInterval(usageInterval);
|
||||
});
|
||||
onMount(async () => {
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
||||
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
await post(`/applications/${id}.json`, { ...application });
|
||||
}
|
||||
}
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
||||
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
}
|
||||
onMount(() => {
|
||||
domainEl.focus();
|
||||
await getUsage();
|
||||
usageInterval = setInterval(async () => {
|
||||
await getUsage();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
async function changeSettings(name) {
|
||||
@ -125,15 +151,34 @@
|
||||
}
|
||||
}
|
||||
async function handleSubmit() {
|
||||
if (loading) return;
|
||||
loading = true;
|
||||
try {
|
||||
await post(`/applications/${id}/check.json`, { fqdn: application.fqdn, forceSave });
|
||||
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||
await post(`/applications/${id}/check.json`, {
|
||||
fqdn: application.fqdn,
|
||||
forceSave,
|
||||
dualCerts,
|
||||
exposePort: application.exposePort
|
||||
});
|
||||
await post(`/applications/${id}.json`, { ...application });
|
||||
$disabledButton = false;
|
||||
forceSave = false;
|
||||
return toast.push('Configurations saved.');
|
||||
} catch ({ error }) {
|
||||
if (error?.startsWith($t('application.dns_not_set_partial_error'))) {
|
||||
forceSave = true;
|
||||
if (dualCerts) {
|
||||
isNonWWWDomainOK = await isDNSValid(getDomain(nonWWWDomain), false);
|
||||
isWWWDomainOK = await isDNSValid(getDomain(`www.${nonWWWDomain}`), true);
|
||||
} else {
|
||||
const isWWW = getDomain(application.fqdn).includes('www.');
|
||||
if (isWWW) {
|
||||
isWWWDomainOK = await isDNSValid(getDomain(`www.${nonWWWDomain}`), true);
|
||||
} else {
|
||||
isNonWWWDomainOK = await isDNSValid(getDomain(nonWWWDomain), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
@ -151,6 +196,19 @@
|
||||
application.baseBuildImage = event.detail.value;
|
||||
await handleSubmit();
|
||||
}
|
||||
|
||||
async function isDNSValid(domain, isWWW) {
|
||||
try {
|
||||
await get(`/applications/${id}/check.json?domain=${domain}`);
|
||||
toast.push('DNS configuration is valid.');
|
||||
isWWW ? (isWWWDomainOK = true) : (isNonWWWDomainOK = true);
|
||||
return true;
|
||||
} catch ({ error }) {
|
||||
errorNotification(error);
|
||||
isWWW ? (isWWWDomainOK = false) : (isNonWWWDomainOK = false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
||||
@ -226,6 +284,33 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-4xl px-6 py-4">
|
||||
<div class="text-2xl font-bold">Application Usage</div>
|
||||
<div class="mx-auto">
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class=" text-sm font-medium text-white">Used Memory / Memory Limit</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white">
|
||||
{usage?.MemUsage}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Used CPU</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
||||
{usage?.CPUPerc}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Network IO</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
||||
{usage?.NetIO}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-auto max-w-4xl px-6">
|
||||
<!-- svelte-ignore missing-declaration -->
|
||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||
@ -319,28 +404,30 @@
|
||||
value={application.destinationDocker.name}
|
||||
id="destination"
|
||||
disabled
|
||||
class="bg-transparent "
|
||||
class="bg-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="baseImage" class="text-base font-bold text-stone-100"
|
||||
>{$t('application.base_image')}</label
|
||||
>
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
isDisabled={!$session.isAdmin || isRunning}
|
||||
containerClasses={containerClass()}
|
||||
id="baseImages"
|
||||
showIndicator={!isRunning}
|
||||
items={application.baseImages}
|
||||
on:select={selectBaseImage}
|
||||
value={application.baseImage}
|
||||
isClearable={false}
|
||||
/>
|
||||
{#if application.buildPack !== 'docker'}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="baseImage" class="text-base font-bold text-stone-100"
|
||||
>{$t('application.base_image')}</label
|
||||
>
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
{isDisabled}
|
||||
containerClasses={isDisabled && containerClass()}
|
||||
id="baseImages"
|
||||
showIndicator={!$status.application.isRunning}
|
||||
items={application.baseImages}
|
||||
on:select={selectBaseImage}
|
||||
value={application.baseImage}
|
||||
isClearable={false}
|
||||
/>
|
||||
</div>
|
||||
<Explainer text={$t('application.base_image_explainer')} />
|
||||
</div>
|
||||
<Explainer text={$t('application.base_image_explainer')} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'}
|
||||
<div class="grid grid-cols-2 items-center pb-8">
|
||||
<label for="baseBuildImage" class="text-base font-bold text-stone-100"
|
||||
@ -349,10 +436,10 @@
|
||||
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
isDisabled={!$session.isAdmin || isRunning}
|
||||
containerClasses={containerClass()}
|
||||
{isDisabled}
|
||||
containerClasses={isDisabled && containerClass()}
|
||||
id="baseBuildImages"
|
||||
showIndicator={!isRunning}
|
||||
showIndicator={!$status.application.isRunning}
|
||||
items={application.baseBuildImages}
|
||||
on:select={selectBaseBuildImage}
|
||||
value={application.baseBuildImage}
|
||||
@ -383,27 +470,62 @@
|
||||
{/if}
|
||||
<Explainer text={$t('application.https_explainer')} />
|
||||
</div>
|
||||
<input
|
||||
readonly={!$session.isAdmin || isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
bind:this={domainEl}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
bind:value={application.fqdn}
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
placeholder="eg: https://coollabs.io"
|
||||
required
|
||||
/>
|
||||
<div>
|
||||
<input
|
||||
readonly={isDisabled}
|
||||
disabled={isDisabled}
|
||||
bind:this={domainEl}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
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
|
||||
class="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="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 class="grid grid-cols-2 items-center pb-8">
|
||||
<Setting
|
||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||
disabled={isRunning}
|
||||
disabled={$status.application.isRunning}
|
||||
isCenter={false}
|
||||
bind:setting={dualCerts}
|
||||
title={$t('application.ssl_www_and_non_www')}
|
||||
description={$t('application.ssl_explainer')}
|
||||
on:click={() => !isRunning && changeSettings('dualCerts')}
|
||||
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
|
||||
/>
|
||||
</div>
|
||||
{#if application.buildPack === 'python'}
|
||||
@ -451,9 +573,24 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||
{#if application.buildPack !== 'docker'}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin && !$status.application.isRunning}
|
||||
disabled={isDisabled}
|
||||
name="exposePort"
|
||||
id="exposePort"
|
||||
bind:value={application.exposePort}
|
||||
placeholder="12345"
|
||||
/>
|
||||
<Explainer
|
||||
text={'You can expose your application to a port on the host system.<br><br>Useful if you would like to use your own reverse proxy or tunnel and also in development mode. Otherwise leave empty.'}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||
<div class="grid grid-cols-2 items-center pt-4">
|
||||
<label for="installCommand" class="text-base font-bold text-stone-100"
|
||||
>{$t('application.install_command')}</label
|
||||
>
|
||||
@ -491,7 +628,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if application.buildPack === 'docker'}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<div class="grid grid-cols-2 items-center pt-4">
|
||||
<label for="dockerFileLocation" class="text-base font-bold text-stone-100"
|
||||
>Dockerfile Location</label
|
||||
>
|
||||
|
@ -1,4 +1,8 @@
|
||||
<div class="lds-ripple absolute left-0">
|
||||
<script>
|
||||
export let position = 'absolute';
|
||||
</script>
|
||||
|
||||
<div class="lds-ripple {position} left-0">
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
|
@ -129,23 +129,28 @@
|
||||
{#if currentStatus === 'running'}
|
||||
<button
|
||||
on:click={cancelBuild}
|
||||
class:animation-spin={cancelInprogress}
|
||||
class="bg-transparent hover:text-red-500 hover:bg-coolgray-500"
|
||||
data-tooltip="Cancel build"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M10 10l4 4m0 -4l-4 4" />
|
||||
</svg>
|
||||
{#if cancelInprogress}
|
||||
Cancelling...
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M10 10l4 4m0 -4l-4 4" />
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@ export const get: RequestHandler = async (event) => {
|
||||
const destinationDocker = await db.getDestinationByApplicationId({ id, teamId });
|
||||
const docker = dockerInstance({ destinationDocker });
|
||||
const listContainers = await docker.engine.listContainers({
|
||||
filters: { network: [destinationDocker.network] }
|
||||
filters: { network: [destinationDocker.network], name: [id] }
|
||||
});
|
||||
const containers = listContainers.filter((container) => {
|
||||
return (
|
||||
@ -30,11 +30,7 @@ export const get: RequestHandler = async (event) => {
|
||||
JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString())
|
||||
)
|
||||
.filter((container) => {
|
||||
return (
|
||||
container.type !== 'manual' &&
|
||||
container.type !== 'webhook_commit' &&
|
||||
container.applicationId === id
|
||||
);
|
||||
return container.pullmergeRequestId && container.applicationId === id;
|
||||
});
|
||||
return {
|
||||
body: {
|
||||
|
@ -31,6 +31,7 @@
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { t } from '$lib/translations';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
const { id } = $page.params;
|
||||
async function refreshSecrets() {
|
||||
@ -39,11 +40,18 @@
|
||||
}
|
||||
async function redeploy(container) {
|
||||
try {
|
||||
await post(`/applications/${id}/deploy.json`, {
|
||||
const { buildId } = await post(`/applications/${id}/deploy.json`, {
|
||||
pullmergeRequestId: container.pullmergeRequestId,
|
||||
branch: container.branch
|
||||
});
|
||||
toast.push('Application redeployed queued.');
|
||||
if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) {
|
||||
return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||
} else {
|
||||
return await goto(`/applications/${id}/logs/build?buildId=${buildId}`, {
|
||||
replaceState: true
|
||||
});
|
||||
}
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
|
36
src/routes/applications/[id]/status.json.ts
Normal file
36
src/routes/applications/[id]/status.json.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { asyncExecShell, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer, getContainerUsage, isContainerExited } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { setDefaultConfiguration } from '$lib/buildPacks/common';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
try {
|
||||
const application = await db.getApplication({ id, teamId });
|
||||
if (application.destinationDockerId) {
|
||||
[isRunning, isExited] = await Promise.all([
|
||||
checkContainer(application.destinationDocker.engine, id),
|
||||
isContainerExited(application.destinationDocker.engine, id)
|
||||
]);
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
isRunning,
|
||||
isExited
|
||||
},
|
||||
headers: {}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
30
src/routes/applications/[id]/usage.json.ts
Normal file
30
src/routes/applications/[id]/usage.json.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { getContainerUsage } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
let usage = {};
|
||||
try {
|
||||
const application = await db.getApplication({ id, teamId });
|
||||
if (application.destinationDockerId) {
|
||||
[usage] = await Promise.all([getContainerUsage(application.destinationDocker.engine, id)]);
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
usage
|
||||
},
|
||||
headers: {}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -60,7 +60,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col flex-wrap justify-center">
|
||||
<div class="flex-col justify-center">
|
||||
{#if !applications || ownApplications.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
|
||||
|
@ -2,45 +2,69 @@ import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import os from 'node:os';
|
||||
import osu from 'node-os-utils';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { userId, teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
try {
|
||||
const applicationsCount = await db.prisma.application.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const sourcesCount = await db.prisma.gitSource.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const destinationsCount = await db.prisma.destinationDocker.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const teamsCount = await db.prisma.permission.count({ where: { userId } });
|
||||
const databasesCount = await db.prisma.database.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const servicesCount = await db.prisma.service.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const teams = await db.prisma.permission.findMany({
|
||||
where: { userId },
|
||||
include: { team: { include: { _count: { select: { users: true } } } } }
|
||||
});
|
||||
return {
|
||||
body: {
|
||||
teams,
|
||||
applicationsCount,
|
||||
sourcesCount,
|
||||
destinationsCount,
|
||||
teamsCount,
|
||||
databasesCount,
|
||||
servicesCount
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
const usage = event.url.searchParams.get('usage');
|
||||
if (usage) {
|
||||
try {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
uptime: os.uptime(),
|
||||
memory: await osu.mem.info(),
|
||||
cpu: {
|
||||
load: os.loadavg(),
|
||||
usage: await osu.cpu.usage(),
|
||||
count: os.cpus().length
|
||||
},
|
||||
disk: await osu.drive.info()
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const applicationsCount = await db.prisma.application.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const sourcesCount = await db.prisma.gitSource.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const destinationsCount = await db.prisma.destinationDocker.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const teamsCount = await db.prisma.permission.count({ where: { userId } });
|
||||
const databasesCount = await db.prisma.database.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const servicesCount = await db.prisma.service.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const teams = await db.prisma.permission.findMany({
|
||||
where: { userId },
|
||||
include: { team: { include: { _count: { select: { users: true } } } } }
|
||||
});
|
||||
const settings = await db.prisma.setting.findFirst();
|
||||
return {
|
||||
body: {
|
||||
teams,
|
||||
applicationsCount,
|
||||
sourcesCount,
|
||||
destinationsCount,
|
||||
teamsCount,
|
||||
databasesCount,
|
||||
servicesCount,
|
||||
settings
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
import MySql from './_MySQL.svelte';
|
||||
import MongoDb from './_MongoDB.svelte';
|
||||
import MariaDb from './_MariaDB.svelte';
|
||||
import PostgreSql from './_PostgreSQL.svelte';
|
||||
import Redis from './_Redis.svelte';
|
||||
import CouchDb from './_CouchDb.svelte';
|
||||
@ -190,6 +191,8 @@
|
||||
<PostgreSql bind:database {isRunning} />
|
||||
{:else if database.type === 'mongodb'}
|
||||
<MongoDb bind:database {isRunning} />
|
||||
{:else if database.type === 'mariadb'}
|
||||
<MariaDb bind:database {isRunning} />
|
||||
{:else if database.type === 'redis'}
|
||||
<Redis bind:database {isRunning} />
|
||||
{:else if database.type === 'couchdb'}
|
||||
|
79
src/routes/databases/[id]/_Databases/_MariaDB.svelte
Normal file
79
src/routes/databases/[id]/_Databases/_MariaDB.svelte
Normal file
@ -0,0 +1,79 @@
|
||||
<script>
|
||||
export let database;
|
||||
export let isRunning;
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">MariaDB</div>
|
||||
</div>
|
||||
<div class="space-y-2 px-10">
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="defaultDatabase" class="text-base font-bold text-stone-100"
|
||||
>{$t('database.default_database')}</label
|
||||
>
|
||||
<CopyPasswordField
|
||||
required
|
||||
readonly={database.defaultDatabase}
|
||||
disabled={database.defaultDatabase}
|
||||
placeholder="{$t('forms.eg')}: mydb"
|
||||
id="defaultDatabase"
|
||||
name="defaultDatabase"
|
||||
bind:value={database.defaultDatabase}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="dbUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
|
||||
<CopyPasswordField
|
||||
readonly
|
||||
disabled
|
||||
placeholder={$t('forms.generated_automatically_after_start')}
|
||||
id="dbUser"
|
||||
name="dbUser"
|
||||
value={database.dbUser}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
|
||||
>{$t('forms.password')}</label
|
||||
>
|
||||
<CopyPasswordField
|
||||
disabled={!isRunning}
|
||||
readonly={!isRunning}
|
||||
placeholder={$t('forms.generated_automatically_after_start')}
|
||||
isPasswordField
|
||||
id="dbUserPassword"
|
||||
name="dbUserPassword"
|
||||
bind:value={database.dbUserPassword}
|
||||
/>
|
||||
<Explainer text="Could be changed while the database is running." />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="rootUser" class="text-base font-bold text-stone-100">{$t('forms.root_user')}</label>
|
||||
<CopyPasswordField
|
||||
readonly
|
||||
disabled
|
||||
placeholder={$t('forms.generated_automatically_after_start')}
|
||||
id="rootUser"
|
||||
name="rootUser"
|
||||
value={database.rootUser}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="rootUserPassword" class="text-base font-bold text-stone-100"
|
||||
>{$t('forms.roots_password')}</label
|
||||
>
|
||||
<CopyPasswordField
|
||||
disabled={!isRunning}
|
||||
readonly={!isRunning}
|
||||
placeholder={$t('forms.generated_automatically_after_start')}
|
||||
isPasswordField
|
||||
id="rootUserPassword"
|
||||
name="rootUserPassword"
|
||||
bind:value={database.rootUserPassword}
|
||||
/>
|
||||
<Explainer text="Could be changed while the database is running." />
|
||||
</div>
|
||||
</div>
|
@ -37,6 +37,7 @@
|
||||
import Clickhouse from '$lib/components/svg/databases/Clickhouse.svelte';
|
||||
import CouchDB from '$lib/components/svg/databases/CouchDB.svelte';
|
||||
import MongoDB from '$lib/components/svg/databases/MongoDB.svelte';
|
||||
import MariaDB from '$lib/components/svg/databases/MariaDB.svelte';
|
||||
import MySQL from '$lib/components/svg/databases/MySQL.svelte';
|
||||
import PostgreSQL from '$lib/components/svg/databases/PostgreSQL.svelte';
|
||||
import Redis from '$lib/components/svg/databases/Redis.svelte';
|
||||
@ -68,6 +69,8 @@
|
||||
<CouchDB isAbsolute />
|
||||
{:else if type.name === 'mongodb'}
|
||||
<MongoDB isAbsolute />
|
||||
{:else if type.name === 'mariadb'}
|
||||
<MariaDB isAbsolute />
|
||||
{:else if type.name === 'mysql'}
|
||||
<MySQL isAbsolute />
|
||||
{:else if type.name === 'postgresql'}
|
||||
|
@ -33,10 +33,40 @@
|
||||
|
||||
<script lang="ts">
|
||||
import DatabaseLinks from '$lib/components/DatabaseLinks.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { get } from '$lib/api';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
export let database;
|
||||
export let settings;
|
||||
export let privatePort;
|
||||
export let isRunning;
|
||||
|
||||
const { id } = $page.params;
|
||||
let usageLoading = false;
|
||||
let usage = {
|
||||
MemUsage: 0,
|
||||
CPUPerc: 0,
|
||||
NetIO: 0
|
||||
};
|
||||
let usageInterval;
|
||||
|
||||
async function getUsage() {
|
||||
if (usageLoading) return;
|
||||
usageLoading = true;
|
||||
const data = await get(`/databases/${id}/usage.json`);
|
||||
usage = data.usage;
|
||||
usageLoading = false;
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(usageInterval);
|
||||
});
|
||||
onMount(async () => {
|
||||
await getUsage();
|
||||
usageInterval = setInterval(async () => {
|
||||
await getUsage();
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex items-center space-x-2 p-6 text-2xl font-bold">
|
||||
@ -49,4 +79,31 @@
|
||||
<DatabaseLinks {database} />
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-4xl px-6 py-4">
|
||||
<div class="text-2xl font-bold">Database Usage</div>
|
||||
<div class="mx-auto">
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class=" text-sm font-medium text-white">Used Memory / Memory Limit</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white">
|
||||
{usage?.MemUsage}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Used CPU</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
||||
{usage?.CPUPerc}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Network IO</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
||||
{usage?.NetIO}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<Databases bind:database {privatePort} {settings} {isRunning} />
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { generateDatabaseConfiguration, ErrorHandler, getFreePort } from '$lib/database';
|
||||
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
||||
import { startTcpProxy, startTraefikTCPProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@ -13,6 +13,7 @@ export const post: RequestHandler = async (event) => {
|
||||
const publicPort = await getFreePort();
|
||||
|
||||
try {
|
||||
const settings = await db.listSettings();
|
||||
await db.setDatabase({ id, isPublic, appendOnly });
|
||||
const database = await db.getDatabase({ id, teamId });
|
||||
const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database;
|
||||
@ -21,7 +22,11 @@ export const post: RequestHandler = async (event) => {
|
||||
if (destinationDockerId) {
|
||||
if (isPublic) {
|
||||
await db.prisma.database.update({ where: { id }, data: { publicPort } });
|
||||
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||
if (settings.isTraefikUsed) {
|
||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||
} else {
|
||||
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||
}
|
||||
} else {
|
||||
await db.prisma.database.update({ where: { id }, data: { publicPort: null } });
|
||||
await stopTcpHttpProxy(destinationDocker, oldPublicPort);
|
||||
|
30
src/routes/databases/[id]/usage.json.ts
Normal file
30
src/routes/databases/[id]/usage.json.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { getContainerUsage } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
let usage = {};
|
||||
try {
|
||||
const database = await db.getDatabase({ id, teamId });
|
||||
if (database.destinationDockerId) {
|
||||
[usage] = await Promise.all([getContainerUsage(database.destinationDocker.engine, id)]);
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
usage
|
||||
},
|
||||
headers: {}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -3,6 +3,7 @@
|
||||
import Clickhouse from '$lib/components/svg/databases/Clickhouse.svelte';
|
||||
import CouchDB from '$lib/components/svg/databases/CouchDB.svelte';
|
||||
import MongoDB from '$lib/components/svg/databases/MongoDB.svelte';
|
||||
import MariaDB from '$lib/components/svg/databases/MariaDB.svelte';
|
||||
import MySQL from '$lib/components/svg/databases/MySQL.svelte';
|
||||
import PostgreSQL from '$lib/components/svg/databases/PostgreSQL.svelte';
|
||||
import Redis from '$lib/components/svg/databases/Redis.svelte';
|
||||
@ -46,7 +47,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col flex-wrap justify-center">
|
||||
<div class="flex-col justify-center">
|
||||
{#if !databases || ownDatabases.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>
|
||||
@ -66,6 +67,8 @@
|
||||
<MongoDB isAbsolute />
|
||||
{:else if database.type === 'mysql'}
|
||||
<MySQL isAbsolute />
|
||||
{:else if database.type === 'mariadb'}
|
||||
<MariaDB isAbsolute />
|
||||
{:else if database.type === 'postgresql'}
|
||||
<PostgreSQL isAbsolute />
|
||||
{:else if database.type === 'redis'}
|
||||
@ -98,6 +101,8 @@
|
||||
<CouchDB isAbsolute />
|
||||
{:else if database.type === 'mongodb'}
|
||||
<MongoDB isAbsolute />
|
||||
{:else if database.type === 'mariadb'}
|
||||
<MariaDB isAbsolute />
|
||||
{:else if database.type === 'mysql'}
|
||||
<MySQL isAbsolute />
|
||||
{:else if database.type === 'postgresql'}
|
||||
|
@ -26,8 +26,12 @@ export const get: RequestHandler = async (event) => {
|
||||
// // await saveSshKey(destination);
|
||||
// payload.state = await checkContainer(engine, 'coolify-haproxy');
|
||||
} else {
|
||||
let containerName = 'coolify-proxy';
|
||||
if (!settings.isTraefikUsed) {
|
||||
containerName = 'coolify-haproxy';
|
||||
}
|
||||
payload.state =
|
||||
destination?.engine && (await checkContainer(destination.engine, 'coolify-haproxy'));
|
||||
destination?.engine && (await checkContainer(destination.engine, containerName));
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
|
@ -39,10 +39,13 @@
|
||||
import { t } from '$lib/translations';
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 text-2xl font-bold">
|
||||
<div class="tracking-tight">{$t('application.destination')}</div>
|
||||
<span class="arrow-right-applications px-1">></span>
|
||||
<span class="pr-2">{destination.name}</span>
|
||||
<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
|
||||
<div class="-mb-5 flex-col">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||
Configuration
|
||||
</div>
|
||||
<span class="text-xs">{destination.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-4xl px-6">
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import * as db from '$lib/database';
|
||||
import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy';
|
||||
import {
|
||||
startCoolifyProxy,
|
||||
startTraefikProxy,
|
||||
stopCoolifyProxy,
|
||||
stopTraefikProxy
|
||||
} from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@ -11,9 +16,16 @@ export const post: RequestHandler = async (event) => {
|
||||
const { engine } = await event.request.json();
|
||||
|
||||
try {
|
||||
await stopCoolifyProxy(engine);
|
||||
await startCoolifyProxy(engine);
|
||||
const settings = await db.prisma.setting.findFirst({});
|
||||
if (settings?.isTraefikUsed) {
|
||||
await stopTraefikProxy(engine);
|
||||
await startTraefikProxy(engine);
|
||||
} else {
|
||||
await stopCoolifyProxy(engine);
|
||||
await startCoolifyProxy(engine);
|
||||
}
|
||||
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy';
|
||||
import * as db from '$lib/database';
|
||||
import {
|
||||
startCoolifyProxy,
|
||||
startTraefikProxy,
|
||||
stopCoolifyProxy,
|
||||
stopTraefikProxy
|
||||
} from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@ -8,14 +14,24 @@ export const post: RequestHandler = async (event) => {
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { engine } = await event.request.json();
|
||||
const settings = await db.prisma.setting.findFirst({});
|
||||
|
||||
try {
|
||||
await startCoolifyProxy(engine);
|
||||
if (settings?.isTraefikUsed) {
|
||||
await startTraefikProxy(engine);
|
||||
} else {
|
||||
await startCoolifyProxy(engine);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
await stopCoolifyProxy(engine);
|
||||
if (settings?.isTraefikUsed) {
|
||||
await stopTraefikProxy(engine);
|
||||
} else {
|
||||
await stopCoolifyProxy(engine);
|
||||
}
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { stopCoolifyProxy } from '$lib/haproxy';
|
||||
import * as db from '$lib/database';
|
||||
import { stopCoolifyProxy, stopTraefikProxy } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@ -9,7 +10,13 @@ export const post: RequestHandler = async (event) => {
|
||||
|
||||
const { engine } = await event.request.json();
|
||||
try {
|
||||
await stopCoolifyProxy(engine);
|
||||
const settings = await db.prisma.setting.findFirst({});
|
||||
if (settings?.isTraefikUsed) {
|
||||
await stopTraefikProxy(engine);
|
||||
} else {
|
||||
await stopCoolifyProxy(engine);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
|
@ -58,7 +58,7 @@
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="flex-col justify-center">
|
||||
{#if !destinations || ownDestinations.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="text-center text-xl font-bold">{$t('destination.no_destination_found')}</div>
|
||||
|
@ -21,6 +21,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { t } from '$lib/translations';
|
||||
import { get } from '$lib/api';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import Trend from './_Trend.svelte';
|
||||
import { session } from '$app/stores';
|
||||
|
||||
export let applicationsCount: number;
|
||||
export let sourcesCount: number;
|
||||
@ -28,89 +32,260 @@
|
||||
export let teamsCount: number;
|
||||
export let databasesCount: number;
|
||||
export let servicesCount: number;
|
||||
let loading = {
|
||||
usage: false
|
||||
};
|
||||
let usageInterval = null;
|
||||
let memoryWarning = false;
|
||||
let cpuWarning = false;
|
||||
let diskWarning = false;
|
||||
|
||||
let trends = {
|
||||
memory: 'stable',
|
||||
cpu: 'stable',
|
||||
disk: 'stable'
|
||||
};
|
||||
let usage = {
|
||||
cpu: {
|
||||
load: [0, 0, 0],
|
||||
count: 0,
|
||||
usage: 0
|
||||
},
|
||||
memory: {
|
||||
totalMemMb: 0,
|
||||
freeMemMb: 0,
|
||||
usedMemMb: 0,
|
||||
freeMemPercentage: 0
|
||||
},
|
||||
disk: {
|
||||
freePercentage: 0,
|
||||
totalGb: 0,
|
||||
usedGb: 0
|
||||
}
|
||||
};
|
||||
async function getStatus() {
|
||||
if (loading.usage) return;
|
||||
try {
|
||||
loading.usage = true;
|
||||
const data = await get(`/dashboard.json?usage=true`);
|
||||
if (data.memory.freeMemPercentage === usage.memory.freeMemPercentage) {
|
||||
trends.memory = 'stable';
|
||||
} else {
|
||||
if (data.memory.freeMemPercentage > usage.memory.freeMemPercentage) {
|
||||
trends.memory = 'up';
|
||||
} else {
|
||||
trends.memory = 'down';
|
||||
}
|
||||
}
|
||||
if (data.cpu.usage === usage.cpu.usage) {
|
||||
trends.cpu = 'stable';
|
||||
} else {
|
||||
if (data.cpu.usage > usage.cpu.usage) {
|
||||
trends.cpu = 'up';
|
||||
} else {
|
||||
trends.cpu = 'down';
|
||||
}
|
||||
}
|
||||
|
||||
if (data.disk.freePercentage === usage.disk.freePercentage) {
|
||||
trends.disk = 'stable';
|
||||
} else {
|
||||
if (data.disk.freePercentage > usage.disk.freePercentage) {
|
||||
trends.disk = 'up';
|
||||
} else {
|
||||
trends.disk = 'down';
|
||||
}
|
||||
}
|
||||
|
||||
usage = data;
|
||||
if (usage.memory.freeMemPercentage < 15) {
|
||||
memoryWarning = true;
|
||||
} else {
|
||||
memoryWarning = false;
|
||||
}
|
||||
if (usage.cpu.usage > 90) {
|
||||
cpuWarning = true;
|
||||
} else {
|
||||
cpuWarning = false;
|
||||
}
|
||||
if (usage.disk.freePercentage < 10) {
|
||||
diskWarning = true;
|
||||
} else {
|
||||
diskWarning = false;
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
loading.usage = false;
|
||||
}
|
||||
}
|
||||
onDestroy(() => {
|
||||
clearInterval(usageInterval);
|
||||
});
|
||||
onMount(async () => {
|
||||
if ($session.teamId === '0') {
|
||||
await getStatus();
|
||||
usageInterval = setInterval(async () => {
|
||||
await getStatus();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">{$t('index.dashboard')}</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 pb-12 tracking-tight sm:pb-16">
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 h-1/2" />
|
||||
<div class="relative mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<dl class="gap-5 gap-y-16 sm:grid sm:grid-cols-3">
|
||||
<a
|
||||
href="/applications"
|
||||
sveltekit:prefetch
|
||||
class="flex cursor-pointer flex-col rounded p-6 text-center text-green-500 no-underline transition duration-150 hover:bg-green-500 hover:text-white"
|
||||
>
|
||||
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
|
||||
{$t('index.applications')}
|
||||
</dt>
|
||||
<dd class="order-1 text-5xl font-extrabold ">
|
||||
{applicationsCount}
|
||||
</dd>
|
||||
</a>
|
||||
<a
|
||||
href="/destinations"
|
||||
sveltekit:prefetch
|
||||
class="flex cursor-pointer flex-col rounded p-6 text-center text-sky-500 no-underline transition duration-150 hover:bg-sky-500 hover:text-white"
|
||||
>
|
||||
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
|
||||
{$t('index.destinations')}
|
||||
</dt>
|
||||
<dd class="order-1 text-5xl font-extrabold ">
|
||||
{destinationsCount}
|
||||
</dd>
|
||||
</a>
|
||||
<a
|
||||
href="/sources"
|
||||
sveltekit:prefetch
|
||||
class="flex cursor-pointer flex-col rounded p-6 text-center text-orange-500 no-underline transition duration-150 hover:bg-orange-500 hover:text-white"
|
||||
>
|
||||
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
|
||||
{$t('index.git_sources')}
|
||||
</dt>
|
||||
<dd class="order-1 text-5xl font-extrabold ">
|
||||
{sourcesCount}
|
||||
</dd>
|
||||
</a>
|
||||
<a
|
||||
href="/databases"
|
||||
sveltekit:prefetch
|
||||
class="flex cursor-pointer flex-col rounded p-6 text-center text-purple-500 no-underline transition duration-150 hover:bg-purple-500 hover:text-white"
|
||||
>
|
||||
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
|
||||
{$t('index.databases')}
|
||||
</dt>
|
||||
<dd class="order-1 text-5xl font-extrabold ">{databasesCount}</dd>
|
||||
</a>
|
||||
<a
|
||||
href="/services"
|
||||
sveltekit:prefetch
|
||||
class="flex cursor-pointer flex-col rounded p-6 text-center text-pink-500 no-underline transition duration-150 hover:bg-pink-500 hover:text-white"
|
||||
>
|
||||
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
|
||||
{$t('index.services')}
|
||||
</dt>
|
||||
<dd class="order-1 text-5xl font-extrabold ">{servicesCount}</dd>
|
||||
</a>
|
||||
<div class="mx-auto max-w-4xl">
|
||||
{#if $session.teamId === '0'}
|
||||
<div class="px-6 text-2xl font-bold">Server Usage</div>
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Total Memory</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{(usage?.memory.totalMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="/iam"
|
||||
sveltekit:prefetch
|
||||
class="flex cursor-pointer flex-col rounded p-6 text-center text-cyan-500 no-underline transition duration-150 hover:bg-cyan-500 hover:text-white"
|
||||
>
|
||||
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
|
||||
{$t('index.teams')}
|
||||
</dt>
|
||||
<dd class="order-1 text-5xl font-extrabold ">
|
||||
{teamsCount}
|
||||
</dd>
|
||||
</a>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Used Memory</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white ">
|
||||
{(usage?.memory.usedMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
|
||||
class:bg-red-500={memoryWarning}
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">Free Memory</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.memory.freeMemPercentage}<span class="text-sm">%</span>
|
||||
{#if !memoryWarning}
|
||||
<Trend trend={trends.memory} />
|
||||
{/if}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Total CPUs</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.cpu.count}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
|
||||
class:bg-red-500={cpuWarning}
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">CPU Usage</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.cpu.usage}<span class="text-sm">%</span>
|
||||
{#if !cpuWarning}
|
||||
<Trend trend={trends.cpu} />
|
||||
{/if}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Load Average (5/10/30mins)</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.cpu.load.join('/')}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Total Disk</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.disk.totalGb}<span class="text-sm">GB</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Used Disk</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.disk.usedGb}<span class="text-sm">GB</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
|
||||
class:bg-red-500={diskWarning}
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">Free Disk</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.disk.freePercentage}<span class="text-sm">%</span>
|
||||
{#if !diskWarning}
|
||||
<Trend trend={trends.disk} />
|
||||
{/if}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="px-6 pt-20 text-2xl font-bold">Resources</div>
|
||||
{/if}
|
||||
<dl class="mt-5 grid grid-cols-1 gap-5 px-2 sm:grid-cols-3">
|
||||
<a
|
||||
href="/applications"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded px-4 py-5 text-center text-green-500 no-underline transition-all duration-100 hover:bg-green-500 hover:text-white sm:p-6 sm:text-left"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.applications')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold ">
|
||||
{applicationsCount}
|
||||
</dd>
|
||||
</a>
|
||||
<a
|
||||
href="/destinations"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded px-4 py-5 text-center text-sky-500 no-underline transition-all duration-100 hover:bg-sky-500 hover:text-white sm:p-6 sm:text-left"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.destinations')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold ">
|
||||
{destinationsCount}
|
||||
</dd>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/sources"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded px-4 py-5 text-center text-orange-500 no-underline transition-all duration-100 hover:bg-orange-500 hover:text-white sm:p-6 sm:text-left"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.git_sources')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold">
|
||||
{sourcesCount}
|
||||
</dd>
|
||||
</a>
|
||||
</dl>
|
||||
<dl class="mt-5 grid grid-cols-1 gap-5 px-2 sm:grid-cols-3">
|
||||
<a
|
||||
href="/databases"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded px-4 py-5 text-center text-purple-500 no-underline transition-all duration-100 hover:bg-purple-500 hover:text-white sm:p-6 sm:text-left"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.databases')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold ">
|
||||
{databasesCount}
|
||||
</dd>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/services"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded px-4 py-5 text-center text-pink-500 no-underline transition-all duration-100 hover:bg-pink-500 hover:text-white sm:p-6 sm:text-left"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.services')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold ">
|
||||
{servicesCount}
|
||||
</dd>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/iam"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded px-4 py-5 text-center text-cyan-500 no-underline transition-all duration-100 hover:bg-cyan-500 hover:text-white sm:p-6 sm:text-left"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.teams')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold ">
|
||||
{teamsCount}
|
||||
</dd>
|
||||
</a>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -30,14 +30,16 @@
|
||||
value={service.minio.rootUserPassword}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="publicPort">{$t('forms.api_port')}</label>
|
||||
<input
|
||||
name="publicPort"
|
||||
id="publicPort"
|
||||
value={service.minio.publicPort}
|
||||
disabled
|
||||
readonly
|
||||
placeholder={$t('forms.generated_automatically_after_start')}
|
||||
/>
|
||||
</div>
|
||||
{#if !service.minio.apiFqdn}
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="publicPort">{$t('forms.api_port')}</label>
|
||||
<input
|
||||
name="publicPort"
|
||||
id="publicPort"
|
||||
value={service.minio.publicPort}
|
||||
disabled
|
||||
readonly
|
||||
placeholder={$t('forms.generated_automatically_after_start')}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -1,13 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { session } from '$app/stores';
|
||||
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
export let service;
|
||||
export let readOnly;
|
||||
export let isRunning;
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">Plausible Analytics</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="scriptName">Script Name</label>
|
||||
<input
|
||||
name="scriptName"
|
||||
id="scriptName"
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
placeholder="plausible.js"
|
||||
bind:value={service.plausibleAnalytics.scriptName}
|
||||
required
|
||||
/>
|
||||
<Explainer
|
||||
text="Useful if you would like to rename the collector script to prevent it blocked by AdBlockers."
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="email">{$t('forms.email')}</label>
|
||||
<input
|
||||
@ -77,16 +96,3 @@
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<!-- <div class="grid grid-cols-3 items-center">
|
||||
<label for="postgresqlPublicPort">Public Port</label>
|
||||
<div class="col-span-2 ">
|
||||
<CopyPasswordField
|
||||
placeholder="{ $t('forms.generated_automatically_after_start') }"
|
||||
readonly
|
||||
disabled
|
||||
id="postgresqlPublicPort"
|
||||
name="postgresqlPublicPort"
|
||||
value={service.plausibleAnalytics.postgresqlPublicPort}
|
||||
/>
|
||||
</div>
|
||||
</div> -->
|
||||
|
@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/env';
|
||||
|
||||
export let service;
|
||||
export let isRunning;
|
||||
export let readOnly;
|
||||
@ -12,6 +14,8 @@
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { t } from '$lib/translations';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import cuid from 'cuid';
|
||||
import { onMount } from 'svelte';
|
||||
import Fider from './_Fider.svelte';
|
||||
import Ghost from './_Ghost.svelte';
|
||||
import Hasura from './_Hasura.svelte';
|
||||
@ -29,9 +33,14 @@
|
||||
let dualCerts = service.dualCerts;
|
||||
|
||||
async function handleSubmit() {
|
||||
if (loading) return;
|
||||
loading = true;
|
||||
try {
|
||||
await post(`/services/${id}/check.json`, { fqdn: service.fqdn });
|
||||
await post(`/services/${id}/check.json`, {
|
||||
fqdn: service.fqdn,
|
||||
otherFqdns: [service.minio.apiFqdn],
|
||||
exposePort: service.exposePort
|
||||
});
|
||||
await post(`/services/${id}/${service.type}.json`, { ...service });
|
||||
return window.location.reload();
|
||||
} catch ({ error }) {
|
||||
@ -62,6 +71,12 @@
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
onMount(async () => {
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !service.fqdn) {
|
||||
service.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
await post(`/services/${id}/${service.type}.json`, { ...service });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl px-6 pb-12">
|
||||
@ -86,6 +101,14 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-row gap-2">
|
||||
{#if service.type === 'minio' && !service.minio.apiFqdn && isRunning}
|
||||
<div class="text-center">
|
||||
<span class="font-bold text-red-500">IMPORTANT!</span> There was a small modification with
|
||||
Minio in the latest version of Coolify. Now you can separate the Console URL from the API URL,
|
||||
so you could use both through SSL. But this proccess cannot be done automatically, so you have
|
||||
to stop your Minio instance, configure the new domain and start it back. Sorry for any inconvenience.
|
||||
</div>
|
||||
{/if}
|
||||
<div class="mt-2 grid grid-cols-2 items-center px-10">
|
||||
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
||||
<div>
|
||||
@ -131,25 +154,62 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 px-10">
|
||||
<div class="flex-col ">
|
||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
||||
>{$t('application.url_fqdn')}</label
|
||||
>
|
||||
<Explainer text={$t('application.https_explainer')} />
|
||||
</div>
|
||||
{#if service.type === 'minio'}
|
||||
<div class="grid grid-cols-2 px-10">
|
||||
<div class="flex-col ">
|
||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">Console URL</label>
|
||||
</div>
|
||||
|
||||
<CopyPasswordField
|
||||
placeholder="eg: https://console.min.io"
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
bind:value={service.fqdn}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 px-10">
|
||||
<div class="flex-col ">
|
||||
<label for="apiFqdn" class="pt-2 text-base font-bold text-stone-100">API URL</label>
|
||||
<Explainer text={$t('application.https_explainer')} />
|
||||
</div>
|
||||
|
||||
<CopyPasswordField
|
||||
placeholder="eg: https://min.io"
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
name="apiFqdn"
|
||||
id="apiFqdn"
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
bind:value={service.minio.apiFqdn}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-2 px-10">
|
||||
<div class="flex-col ">
|
||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
||||
>{$t('application.url_fqdn')}</label
|
||||
>
|
||||
<Explainer text={$t('application.https_explainer')} />
|
||||
</div>
|
||||
|
||||
<CopyPasswordField
|
||||
placeholder="eg: https://analytics.coollabs.io"
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
bind:value={service.fqdn}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<CopyPasswordField
|
||||
placeholder="eg: https://analytics.coollabs.io"
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
bind:value={service.fqdn}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<Setting
|
||||
disabled={isRunning}
|
||||
@ -160,8 +220,23 @@
|
||||
on:click={() => !isRunning && changeSettings('dualCerts')}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
name="exposePort"
|
||||
id="exposePort"
|
||||
bind:value={service.exposePort}
|
||||
placeholder="12345"
|
||||
/>
|
||||
<Explainer
|
||||
text={'You can expose your application to a port on the host system.<br><br>Useful if you would like to use your own reverse proxy or tunnel and also in development mode. Otherwise leave empty.'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if service.type === 'plausibleanalytics'}
|
||||
<PlausibleAnalytics bind:service {readOnly} />
|
||||
<PlausibleAnalytics bind:service {isRunning} {readOnly} />
|
||||
{:else if service.type === 'minio'}
|
||||
<MinIo {service} />
|
||||
{:else if service.type === 'vscodeserver'}
|
||||
|
@ -18,6 +18,7 @@
|
||||
let ftpUser = service.wordpress.ftpUser;
|
||||
let ftpPassword = service.wordpress.ftpPassword;
|
||||
let ftpLoading = false;
|
||||
let ownMysql = service.wordpress.ownMysql;
|
||||
|
||||
function generateUrl(publicPort) {
|
||||
return browser
|
||||
@ -40,7 +41,7 @@
|
||||
publicPort,
|
||||
ftpUser: user,
|
||||
ftpPassword: password
|
||||
} = await post(`/services/${id}/wordpress/settings.json`, {
|
||||
} = await post(`/services/${id}/wordpress/ftp.json`, {
|
||||
ftpEnabled
|
||||
});
|
||||
ftpUrl = generateUrl(publicPort);
|
||||
@ -52,6 +53,18 @@
|
||||
} finally {
|
||||
ftpLoading = false;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if (name === 'ownMysql') {
|
||||
ownMysql = !ownMysql;
|
||||
}
|
||||
await post(`/services/${id}/wordpress/settings.json`, {
|
||||
ownMysql
|
||||
});
|
||||
service.wordpress.ownMysql = ownMysql;
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -106,52 +119,96 @@ define('SUBDOMAIN_INSTALL', false);`
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">MySQL</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<Setting
|
||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||
bind:setting={service.wordpress.ownMysql}
|
||||
disabled={isRunning}
|
||||
on:click={() => !isRunning && changeSettings('ownMysql')}
|
||||
title="Use your own MySQL server"
|
||||
description="Enables the use of your own MySQL server. If you don't have one, you can use the one provided by Coolify."
|
||||
/>
|
||||
</div>
|
||||
{#if service.wordpress.ownMysql}
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlHost">Host</label>
|
||||
<input
|
||||
name="mysqlHost"
|
||||
id="mysqlHost"
|
||||
required
|
||||
readonly={isRunning}
|
||||
disabled={isRunning}
|
||||
bind:value={service.wordpress.mysqlHost}
|
||||
placeholder="{$t('forms.eg')}: db.coolify.io"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlPort">Port</label>
|
||||
<input
|
||||
name="mysqlPort"
|
||||
id="mysqlPort"
|
||||
required
|
||||
readonly={isRunning}
|
||||
disabled={isRunning}
|
||||
bind:value={service.wordpress.mysqlPort}
|
||||
placeholder="{$t('forms.eg')}: 3306"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlDatabase">{$t('index.database')}</label>
|
||||
<input
|
||||
name="mysqlDatabase"
|
||||
id="mysqlDatabase"
|
||||
required
|
||||
readonly={readOnly}
|
||||
disabled={readOnly}
|
||||
readonly={readOnly && !service.wordpress.ownMysql}
|
||||
disabled={readOnly && !service.wordpress.ownMysql}
|
||||
bind:value={service.wordpress.mysqlDatabase}
|
||||
placeholder="{$t('forms.eg')}: wordpress_db"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlRootUser">{$t('forms.root_user')}</label>
|
||||
<input
|
||||
name="mysqlRootUser"
|
||||
id="mysqlRootUser"
|
||||
placeholder="MySQL {$t('forms.root_user')}"
|
||||
value={service.wordpress.mysqlRootUser}
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlRootUserPassword">{$t('forms.roots_password')}</label>
|
||||
<CopyPasswordField
|
||||
id="mysqlRootUserPassword"
|
||||
isPasswordField
|
||||
readonly
|
||||
disabled
|
||||
name="mysqlRootUserPassword"
|
||||
value={service.wordpress.mysqlRootUserPassword}
|
||||
/>
|
||||
</div>
|
||||
{#if !service.wordpress.ownMysql}
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlRootUser">{$t('forms.root_user')}</label>
|
||||
<input
|
||||
name="mysqlRootUser"
|
||||
id="mysqlRootUser"
|
||||
placeholder="MySQL {$t('forms.root_user')}"
|
||||
value={service.wordpress.mysqlRootUser}
|
||||
readonly={isRunning || !service.wordpress.ownMysql}
|
||||
disabled={isRunning || !service.wordpress.ownMysql}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlRootUserPassword">{$t('forms.roots_password')}</label>
|
||||
<CopyPasswordField
|
||||
id="mysqlRootUserPassword"
|
||||
isPasswordField
|
||||
readonly={isRunning || !service.wordpress.ownMysql}
|
||||
disabled={isRunning || !service.wordpress.ownMysql}
|
||||
name="mysqlRootUserPassword"
|
||||
value={service.wordpress.mysqlRootUserPassword}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlUser">{$t('forms.user')}</label>
|
||||
<input name="mysqlUser" id="mysqlUser" value={service.wordpress.mysqlUser} disabled readonly />
|
||||
<input
|
||||
name="mysqlUser"
|
||||
id="mysqlUser"
|
||||
bind:value={service.wordpress.mysqlUser}
|
||||
readonly={isRunning || !service.wordpress.ownMysql}
|
||||
disabled={isRunning || !service.wordpress.ownMysql}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlPassword">{$t('forms.password')}</label>
|
||||
<CopyPasswordField
|
||||
id="mysqlPassword"
|
||||
isPasswordField
|
||||
readonly
|
||||
disabled
|
||||
readonly={isRunning || !service.wordpress.ownMysql}
|
||||
disabled={isRunning || !service.wordpress.ownMysql}
|
||||
name="mysqlPassword"
|
||||
value={service.wordpress.mysqlPassword}
|
||||
bind:value={service.wordpress.mysqlPassword}
|
||||
/>
|
||||
</div>
|
||||
|
21
src/routes/services/[id]/appwrite-wip/index.json.ts
Normal file
21
src/routes/services/[id]/appwrite-wip/index.json.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
const { id } = event.params;
|
||||
|
||||
let { name, fqdn, exposePort } = await event.request.json();
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (exposePort) exposePort = Number(exposePort);
|
||||
|
||||
try {
|
||||
await db.updateService({ id, fqdn, name, exposePort });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
519
src/routes/services/[id]/appwrite-wip/start.json.ts
Normal file
519
src/routes/services/[id]/appwrite-wip/start.json.ts
Normal file
@ -0,0 +1,519 @@
|
||||
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { promises as fs } from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
import type { ComposeFile } from '$lib/types/composeFile';
|
||||
import { getServiceMainPort } from '$lib/components/common';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
||||
service;
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
const port = getServiceMainPort('n8n');
|
||||
|
||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||
const image = getServiceImage(type);
|
||||
|
||||
if (serviceSecret.length > 0) {
|
||||
serviceSecret.forEach((secret) => {
|
||||
variables[secret.name] = secret.value;
|
||||
});
|
||||
}
|
||||
|
||||
const variables = {
|
||||
_APP_ENV: 'production',
|
||||
_APP_VERSION: '',
|
||||
_APP_LOCALE: '',
|
||||
_APP_OPTIONS_ABUSE: '',
|
||||
_APP_OPTIONS_FORCE_HTTPS: '',
|
||||
_APP_OPENSSL_KEY_V1: '',
|
||||
_APP_DOMAIN: '',
|
||||
_APP_DOMAIN_TARGET: '',
|
||||
_APP_CONSOLE_WHITELIST_ROOT: '',
|
||||
_APP_CONSOLE_WHITELIST_EMAILS: '',
|
||||
_APP_CONSOLE_WHITELIST_IPS: '',
|
||||
_APP_SYSTEM_EMAIL_NAME: '',
|
||||
_APP_SYSTEM_EMAIL_ADDRESS: '',
|
||||
_APP_SYSTEM_RESPONSE_FORMAT: '',
|
||||
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS: '',
|
||||
_APP_USAGE_STATS: '',
|
||||
_APP_LOGGING_PROVIDER: '',
|
||||
_APP_LOGGING_CONFIG: '',
|
||||
_APP_USAGE_AGGREGATION_INTERVAL: '',
|
||||
_APP_WORKER_PER_CORE: '',
|
||||
_APP_REDIS_HOST: '',
|
||||
_APP_REDIS_PORT: '',
|
||||
_APP_REDIS_USER: '',
|
||||
_APP_REDIS_PASS: '',
|
||||
_APP_DB_HOST: '',
|
||||
_APP_DB_PORT: '',
|
||||
_APP_DB_SCHEMA: '',
|
||||
_APP_DB_USER: '',
|
||||
_APP_DB_PASS: '',
|
||||
_APP_DB_ROOT_PASS: '',
|
||||
_APP_INFLUXDB_HOST: '',
|
||||
_APP_INFLUXDB_PORT: '',
|
||||
_APP_STATSD_HOST: '',
|
||||
_APP_STATSD_PORT: '',
|
||||
_APP_SMTP_HOST: '',
|
||||
_APP_SMTP_PORT: '',
|
||||
_APP_SMTP_SECURE: '',
|
||||
_APP_SMTP_USERNAME: '',
|
||||
_APP_SMTP_PASSWORD: '',
|
||||
_APP_STORAGE_LIMIT: '',
|
||||
_APP_STORAGE_ANTIVIRUS: '',
|
||||
_APP_STORAGE_ANTIVIRUS_HOST: '',
|
||||
_APP_STORAGE_ANTIVIRUS_PORT: '',
|
||||
_APP_STORAGE_DEVICE: '',
|
||||
_APP_STORAGE_S3_ACCESS_KEY: '',
|
||||
_APP_STORAGE_S3_SECRET: '',
|
||||
_APP_STORAGE_S3_REGION: '',
|
||||
_APP_STORAGE_S3_BUCKET: '',
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY: '',
|
||||
_APP_STORAGE_DO_SPACES_SECRET: '',
|
||||
_APP_STORAGE_DO_SPACES_REGION: '',
|
||||
_APP_STORAGE_DO_SPACES_BUCKET: '',
|
||||
_APP_FUNCTIONS_SIZE_LIMIT: '',
|
||||
_APP_FUNCTIONS_TIMEOUT: '',
|
||||
_APP_FUNCTIONS_BUILD_TIMEOUT: '',
|
||||
_APP_FUNCTIONS_CONTAINERS: '',
|
||||
_APP_FUNCTIONS_CPUS: '',
|
||||
_APP_FUNCTIONS_MEMORY: '',
|
||||
_APP_FUNCTIONS_MEMORY_SWAP: '',
|
||||
_APP_FUNCTIONS_RUNTIMES: '',
|
||||
_APP_EXECUTOR_SECRET: '',
|
||||
_APP_EXECUTOR_RUNTIME_NETWORK: '',
|
||||
_APP_FUNCTIONS_ENVS: '',
|
||||
_APP_FUNCTIONS_INACTIVE_THRESHOLD: '',
|
||||
DOCKERHUB_PULL_USERNAME: '',
|
||||
DOCKERHUB_PULL_PASSWORD: '',
|
||||
DOCKERHUB_PULL_EMAIL: '',
|
||||
_APP_MAINTENANCE_INTERVAL: '',
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION: '',
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE: '',
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT: ''
|
||||
};
|
||||
const config = {
|
||||
appwrite: {
|
||||
image: `${image}:${version}`,
|
||||
volumes: [
|
||||
`${id}-appwrite-uploads:/storage/uploads`,
|
||||
`${id}-appwrite-cache:/storage/cache`,
|
||||
`${id}-appwrite-config:/storage/config`,
|
||||
`${id}-appwrite-certificates:/storage/certificates`,
|
||||
`${id}-appwrite-functions:/storage/functions`
|
||||
],
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_WORKER_PER_CORE: variables._APP_WORKER_PER_CORE,
|
||||
_APP_LOCALE: variables._APP_LOCALE,
|
||||
_APP_CONSOLE_WHITELIST_ROOT: variables._APP_CONSOLE_WHITELIST_ROOT,
|
||||
_APP_CONSOLE_WHITELIST_EMAILS: variables._APP_CONSOLE_WHITELIST_EMAILS,
|
||||
_APP_CONSOLE_WHITELIST_IPS: variables._APP_CONSOLE_WHITELIST_IPS,
|
||||
_APP_SYSTEM_EMAIL_NAME: variables._APP_SYSTEM_EMAIL_NAME,
|
||||
_APP_SYSTEM_EMAIL_ADDRESS: variables._APP_SYSTEM_EMAIL_ADDRESS,
|
||||
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS: variables._APP_SYSTEM_SECURITY_EMAIL_ADDRESS,
|
||||
_APP_SYSTEM_RESPONSE_FORMAT: variables._APP_SYSTEM_RESPONSE_FORMAT,
|
||||
_APP_OPTIONS_ABUSE: variables._APP_OPTIONS_ABUSE,
|
||||
_APP_OPTIONS_FORCE_HTTPS: variables._APP_OPTIONS_FORCE_HTTPS,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_DOMAIN: variables._APP_DOMAIN,
|
||||
_APP_DOMAIN_TARGET: variables._APP_DOMAIN_TARGET,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_SMTP_HOST: variables._APP_SMTP_HOST,
|
||||
_APP_SMTP_PORT: variables._APP_SMTP_PORT,
|
||||
_APP_SMTP_SECURE: variables._APP_SMTP_SECURE,
|
||||
_APP_SMTP_USERNAME: variables._APP_SMTP_USERNAME,
|
||||
_APP_SMTP_PASSWORD: variables._APP_SMTP_PASSWORD,
|
||||
_APP_USAGE_STATS: variables._APP_USAGE_STATS,
|
||||
_APP_INFLUXDB_HOST: variables._APP_INFLUXDB_HOST,
|
||||
_APP_INFLUXDB_PORT: variables._APP_INFLUXDB_PORT,
|
||||
_APP_STORAGE_LIMIT: variables._APP_STORAGE_LIMIT,
|
||||
_APP_STORAGE_ANTIVIRUS: variables._APP_STORAGE_ANTIVIRUS,
|
||||
_APP_STORAGE_ANTIVIRUS_HOST: variables._APP_STORAGE_ANTIVIRUS_HOST,
|
||||
_APP_STORAGE_ANTIVIRUS_PORT: variables._APP_STORAGE_ANTIVIRUS_PORT,
|
||||
_APP_STORAGE_DEVICE: variables._APP_STORAGE_DEVICE,
|
||||
_APP_STORAGE_S3_ACCESS_KEY: variables._APP_STORAGE_S3_ACCESS_KEY,
|
||||
_APP_STORAGE_S3_SECRET: variables._APP_STORAGE_S3_SECRET,
|
||||
_APP_STORAGE_S3_REGION: variables._APP_STORAGE_S3_REGION,
|
||||
_APP_STORAGE_S3_BUCKET: variables._APP_STORAGE_S3_BUCKET,
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY: variables._APP_STORAGE_DO_SPACES_ACCESS_KEY,
|
||||
_APP_STORAGE_DO_SPACES_SECRET: variables._APP_STORAGE_DO_SPACES_SECRET,
|
||||
_APP_STORAGE_DO_SPACES_REGION: variables._APP_STORAGE_DO_SPACES_REGION,
|
||||
_APP_STORAGE_DO_SPACES_BUCKET: variables._APP_STORAGE_DO_SPACES_BUCKET,
|
||||
_APP_FUNCTIONS_SIZE_LIMIT: variables._APP_FUNCTIONS_SIZE_LIMIT,
|
||||
_APP_FUNCTIONS_TIMEOUT: variables._APP_FUNCTIONS_TIMEOUT,
|
||||
_APP_FUNCTIONS_BUILD_TIMEOUT: variables._APP_FUNCTIONS_BUILD_TIMEOUT,
|
||||
_APP_FUNCTIONS_CONTAINERS: variables._APP_FUNCTIONS_CONTAINERS,
|
||||
_APP_FUNCTIONS_CPUS: variables._APP_FUNCTIONS_CPUS,
|
||||
_APP_FUNCTIONS_MEMORY: variables._APP_FUNCTIONS_MEMORY,
|
||||
_APP_FUNCTIONS_MEMORY_SWAP: variables._APP_FUNCTIONS_MEMORY_SWAP,
|
||||
_APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET,
|
||||
_APP_FUNCTIONS_RUNTIMES: variables._APP_FUNCTIONS_RUNTIMES,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG,
|
||||
_APP_STATSD_HOST: variables._APP_STATSD_HOST,
|
||||
_APP_STATSD_PORT: variables._APP_STATSD_PORT,
|
||||
_APP_MAINTENANCE_INTERVAL: variables._APP_MAINTENANCE_INTERVAL,
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION: variables._APP_MAINTENANCE_RETENTION_EXECUTION,
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE: variables._APP_MAINTENANCE_RETENTION_ABUSE,
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT: variables._APP_MAINTENANCE_RETENTION_AUDIT
|
||||
}
|
||||
},
|
||||
appwriteRealtime: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_WORKER_PER_CORE: variables._APP_WORKER_PER_CORE,
|
||||
_APP_OPTIONS_ABUSE: variables._APP_OPTIONS_ABUSE,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_USAGE_STATS: variables._APP_USAGE_STATS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteExecutor: {
|
||||
image: `${image}:${version}`,
|
||||
volumes: [
|
||||
`${id}-appwrite-functions:/storage/functions`,
|
||||
`/tmp:/tmp`,
|
||||
'/var/run/docker.sock:/var/run/docker.sock'
|
||||
],
|
||||
environmentVariables: {
|
||||
DOCKERHUB_PULL_USERNAME: variables.DOCKERHUB_PULL_USERNAME,
|
||||
DOCKERHUB_PULL_PASSWORD: variables.DOCKERHUB_PULL_PASSWORD,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG,
|
||||
_APP_VERSION: variables._APP_VERSION,
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_STORAGE_DEVICE: variables._APP_STORAGE_DEVICE,
|
||||
_APP_STORAGE_S3_ACCESS_KEY: variables._APP_STORAGE_S3_ACCESS_KEY,
|
||||
_APP_STORAGE_S3_SECRET: variables._APP_STORAGE_S3_SECRET,
|
||||
_APP_STORAGE_S3_REGION: variables._APP_STORAGE_S3_REGION,
|
||||
_APP_STORAGE_S3_BUCKET: variables._APP_STORAGE_S3_BUCKET,
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY: variables._APP_STORAGE_DO_SPACES_ACCESS_KEY,
|
||||
_APP_STORAGE_DO_SPACES_SECRET: variables._APP_STORAGE_DO_SPACES_SECRET,
|
||||
_APP_STORAGE_DO_SPACES_REGION: variables._APP_STORAGE_DO_SPACES_REGION,
|
||||
_APP_STORAGE_DO_SPACES_BUCKET: variables._APP_STORAGE_DO_SPACES_BUCKET,
|
||||
_APP_FUNCTIONS_CPUS: variables._APP_FUNCTIONS_CPUS,
|
||||
_APP_FUNCTIONS_MEMORY: variables._APP_FUNCTIONS_MEMORY,
|
||||
_APP_FUNCTIONS_MEMORY_SWAP: variables._APP_FUNCTIONS_MEMORY_SWAP,
|
||||
_APP_FUNCTIONS_TIMEOUT: variables._APP_FUNCTIONS_TIMEOUT,
|
||||
_APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET,
|
||||
_APP_FUNCTIONS_RUNTIMES: variables._APP_FUNCTIONS_RUNTIMES,
|
||||
_APP_FUNCTIONS_INACTIVE_THRESHOLD: variables._APP_FUNCTIONS_INACTIVE_THRESHOLD,
|
||||
_APP_EXECUTOR_RUNTIME_NETWORK: variables._APP_EXECUTOR_RUNTIME_NETWORK
|
||||
}
|
||||
},
|
||||
appwriteWorkerDatabase: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerBuilds: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerAudits: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerWebhooks: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS: variables._APP_SYSTEM_SECURITY_EMAIL_ADDRESS,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerDeletes: {
|
||||
image: `${image}:${version}`,
|
||||
volumes: [
|
||||
`${id}-appwrite-uploads:/storage/uploads`,
|
||||
`${id}-appwrite-cache:/storage/cache`,
|
||||
`${id}-appwrite-certificates:/storage/certificates`
|
||||
],
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_STORAGE_DEVICE: variables._APP_STORAGE_DEVICE,
|
||||
_APP_STORAGE_S3_ACCESS_KEY: variables._APP_STORAGE_S3_ACCESS_KEY,
|
||||
_APP_STORAGE_S3_SECRET: variables._APP_STORAGE_S3_SECRET,
|
||||
_APP_STORAGE_S3_REGION: variables._APP_STORAGE_S3_REGION,
|
||||
_APP_STORAGE_S3_BUCKET: variables._APP_STORAGE_S3_BUCKET,
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY: variables._APP_STORAGE_DO_SPACES_ACCESS_KEY,
|
||||
_APP_STORAGE_DO_SPACES_SECRET: variables._APP_STORAGE_DO_SPACES_SECRET,
|
||||
_APP_STORAGE_DO_SPACES_REGION: variables._APP_STORAGE_DO_SPACES_REGION,
|
||||
_APP_STORAGE_DO_SPACES_BUCKET: variables._APP_STORAGE_DO_SPACES_BUCKET,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerCertificates: {
|
||||
image: `${image}:${version}`,
|
||||
volumes: [
|
||||
`${id}-appwrite-config:/storage/config`,
|
||||
`${id}-appwrite-certificates:/storage/certificates`
|
||||
],
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS: variables._APP_SYSTEM_SECURITY_EMAIL_ADDRESS,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DOMAIN_TARGET: variables._APP_DOMAIN_TARGET,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerFunctions: {
|
||||
image: `${image}:${version}`,
|
||||
envvironmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_FUNCTIONS_TIMEOUT: variables._APP_FUNCTIONS_TIMEOUT,
|
||||
_APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET,
|
||||
_APP_USAGE_STATS: variables._APP_USAGE_STATS,
|
||||
DOCKERHUB_PULL_USERNAME: variables.DOCKERHUB_PULL_USERNAME,
|
||||
DOCKERHUB_PULL_PASSWORD: variables.DOCKERHUB_PULL_PASSWORD
|
||||
}
|
||||
},
|
||||
appwriteWorkerMails: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_SYSTEM_EMAIL_NAME: variables._APP_SYSTEM_EMAIL_NAME,
|
||||
_APP_SYSTEM_EMAIL_ADDRESS: variables._APP_SYSTEM_EMAIL_ADDRESS,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_SMTP_HOST: variables._APP_SMTP_HOST,
|
||||
_APP_SMTP_PORT: variables._APP_SMTP_PORT,
|
||||
_APP_SMTP_SECURE: variables._APP_SMTP_SECURE,
|
||||
_APP_SMTP_USERNAME: variables._APP_SMTP_USERNAME,
|
||||
_APP_SMTP_PASSWORD: variables._APP_SMTP_PASSWORD,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteMaintenance: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_MAINTENANCE_INTERVAL: variables._APP_MAINTENANCE_INTERVAL,
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION: variables._APP_MAINTENANCE_RETENTION_EXECUTION,
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE: variables._APP_MAINTENANCE_RETENTION_ABUSE,
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT: variables._APP_MAINTENANCE_RETENTION_AUDIT
|
||||
}
|
||||
},
|
||||
appwriteUsage: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_INFLUXDB_HOST: variables._APP_INFLUXDB_HOST,
|
||||
_APP_INFLUXDB_PORT: variables._APP_INFLUXDB_PORT,
|
||||
_APP_USAGE_AGGREGATION_INTERVAL: variables._APP_USAGE_AGGREGATION_INTERVAL,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS
|
||||
}
|
||||
},
|
||||
appwriteSchedule: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS
|
||||
}
|
||||
},
|
||||
mariadb: {
|
||||
image: 'mariadb:10.7',
|
||||
volumes: [`${id}-appwrite-mariadb:/var/lib/mysql`],
|
||||
environmentVariables: {
|
||||
MYSQL_ROOT_PASSWORD: variables._APP_DB_ROOT_PASS,
|
||||
MYSQL_DATABASE: variables._APP_DB_SCHEMA,
|
||||
MYSQL_USER: variables._APP_DB_USER,
|
||||
MYSQL_PASSWORD: variables._APP_DB_PASS
|
||||
}
|
||||
},
|
||||
redis: {
|
||||
image: 'redis:6.0-alpine3.12',
|
||||
volumes: [`${id}-appwrite-redis:/data`]
|
||||
},
|
||||
influxdb: {
|
||||
image: 'appwrite/influxdb:1.0.0',
|
||||
volumes: [`${id}-appwrite-influxdb:/var/lib/influxdb`]
|
||||
},
|
||||
telegraf: {
|
||||
image: 'appwrite/telegraf:1.0.0',
|
||||
environmentVariables: {
|
||||
_APP_INFLUXDB_HOST: variables._APP_INFLUXDB_HOST,
|
||||
_APP_INFLUXDB_PORT: variables._APP_INFLUXDB_PORT
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const composeFile: ComposeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[id]: {
|
||||
container_name: id,
|
||||
image: config.image,
|
||||
networks: [network],
|
||||
volumes: [...config.appwrite.volumes],
|
||||
environment: config.environmentVariables,
|
||||
restart: 'always',
|
||||
labels: makeLabelForServices('appwrite'),
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
deploy: {
|
||||
restart_policy: {
|
||||
condition: 'on-failure',
|
||||
delay: '5s',
|
||||
max_attempts: 3,
|
||||
window: '120s'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[config.volume.split(':')[0]]: {
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
35
src/routes/services/[id]/appwrite-wip/stop.json.ts
Normal file
35
src/routes/services/[id]/appwrite-wip/stop.json.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { getUserDetails, removeDestinationDocker } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
||||
if (destinationDockerId) {
|
||||
const engine = destinationDocker.engine;
|
||||
|
||||
try {
|
||||
const found = await checkContainer(engine, id);
|
||||
if (found) {
|
||||
await removeDestinationDocker({ id, engine });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -1,19 +1,56 @@
|
||||
import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { t } from '$lib/translations';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import getPort from 'get-port';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
let { fqdn } = await event.request.json();
|
||||
let { fqdn, exposePort, otherFqdns } = await event.request.json();
|
||||
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (otherFqdns) otherFqdns = otherFqdns.map((fqdn) => fqdn.toLowerCase());
|
||||
if (exposePort) exposePort = Number(exposePort);
|
||||
|
||||
try {
|
||||
const found = await db.isDomainConfigured({ id, fqdn });
|
||||
let found = await db.isDomainConfigured({ id, fqdn });
|
||||
if (found) {
|
||||
throw {
|
||||
message: t.get('application.domain_already_in_use', {
|
||||
domain: getDomain(fqdn).replace('www.', '')
|
||||
})
|
||||
};
|
||||
}
|
||||
if (otherFqdns) {
|
||||
for (const ofqdn of otherFqdns) {
|
||||
const domain = getDomain(ofqdn);
|
||||
const nakedDomain = domain.replace('www.', '');
|
||||
found = await db.isDomainConfigured({ id, fqdn: ofqdn, checkOwn: true });
|
||||
if (found) {
|
||||
throw {
|
||||
message: t.get('application.domain_already_in_use', {
|
||||
domain: nakedDomain
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exposePort) {
|
||||
exposePort = Number(exposePort);
|
||||
|
||||
if (exposePort < 1024 || exposePort > 65535) {
|
||||
throw { message: `Exposed Port needs to be between 1024 and 65535.` };
|
||||
}
|
||||
|
||||
const publicPort = await getPort({ port: exposePort });
|
||||
if (publicPort !== exposePort) {
|
||||
throw { message: `Port ${exposePort} is already in use.` };
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: found ? 500 : 200,
|
||||
body: {
|
||||
|
@ -13,6 +13,7 @@ import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
import type { ComposeFile } from '$lib/types/composeFile';
|
||||
import type { Service, DestinationDocker, Prisma } from '@prisma/client';
|
||||
import { getServiceMainPort } from '$lib/components/common';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
@ -30,6 +31,7 @@ export const post: RequestHandler = async (event) => {
|
||||
destinationDockerId,
|
||||
destinationDocker,
|
||||
serviceSecret,
|
||||
exposePort,
|
||||
fider: {
|
||||
postgresqlUser,
|
||||
postgresqlPassword,
|
||||
@ -48,6 +50,7 @@ export const post: RequestHandler = async (event) => {
|
||||
} = service;
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
const port = getServiceMainPort('fider');
|
||||
|
||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||
const image = getServiceImage(type);
|
||||
@ -97,6 +100,7 @@ export const post: RequestHandler = async (event) => {
|
||||
volumes: [],
|
||||
restart: 'always',
|
||||
labels: makeLabelForServices('fider'),
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
deploy: {
|
||||
restart_policy: {
|
||||
condition: 'on-failure',
|
||||
|
@ -11,11 +11,12 @@ export const post: RequestHandler = async (event) => {
|
||||
let {
|
||||
name,
|
||||
fqdn,
|
||||
exposePort,
|
||||
ghost: { mariadbDatabase }
|
||||
} = await event.request.json();
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
try {
|
||||
await db.updateGhostService({ id, fqdn, name, mariadbDatabase });
|
||||
await db.updateGhostService({ id, fqdn, name, exposePort, mariadbDatabase });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
|
@ -12,6 +12,7 @@ import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
import type { ComposeFile } from '$lib/types/composeFile';
|
||||
import { getServiceMainPort } from '$lib/components/common';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
@ -19,6 +20,8 @@ export const post: RequestHandler = async (event) => {
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
const port = getServiceMainPort('ghost');
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const {
|
||||
@ -27,6 +30,7 @@ export const post: RequestHandler = async (event) => {
|
||||
destinationDockerId,
|
||||
destinationDocker,
|
||||
serviceSecret,
|
||||
exposePort,
|
||||
fqdn,
|
||||
ghost: {
|
||||
defaultEmail,
|
||||
@ -89,6 +93,7 @@ export const post: RequestHandler = async (event) => {
|
||||
volumes: [config.ghost.volume],
|
||||
environment: config.ghost.environmentVariables,
|
||||
restart: 'always',
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
labels: makeLabelForServices('ghost'),
|
||||
depends_on: [`${id}-mariadb`],
|
||||
deploy: {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user