Compare commits

...

113 Commits

Author SHA1 Message Date
Andras Bacsai
08d7593ca9 Merge pull request #444 from coollabsio/next
v2.9.4
2022-06-01 10:43:30 +02:00
Andras Bacsai
a50f7a7cc2 fix: Revert gh and gl cloning 2022-06-01 10:42:17 +02:00
Andras Bacsai
2c33447f9f fix: typo 2022-05-31 23:23:39 +02:00
Andras Bacsai
d67a3f51ec fix: Demo version forms 2022-05-31 23:09:55 +02:00
Andras Bacsai
2719974262 chore: Version++ 2022-05-31 22:41:33 +02:00
Andras Bacsai
eb5aebd58d Merge pull request #442 from coollabsio/next
fix: Only reconfigure coolify proxy if its missconfigured
2022-05-31 22:30:09 +02:00
Andras Bacsai
98dbf3d8a5 fix: Only reconfigure coolify proxy if its missconfigured 2022-05-31 22:29:50 +02:00
Andras Bacsai
d9489a2cb4 Merge pull request #441 from coollabsio/next
fix: versions
2022-05-31 22:18:16 +02:00
Andras Bacsai
95832d34f7 fix: versions 2022-05-31 22:17:51 +02:00
Andras Bacsai
d3e9aea63d Merge pull request #440 from coollabsio/next
v2.9.3
2022-05-31 22:14:06 +02:00
Andras Bacsai
d6972e2ed1 fix: Recurisve clone instead of submodule 2022-05-31 21:52:25 +02:00
Andras Bacsai
50844e98be chore: version++ 2022-05-31 21:52:12 +02:00
Andras Bacsai
5c6fcfebf9 Merge pull request #439 from coollabsio/next
v2.9.2
2022-05-31 20:54:55 +02:00
Andras Bacsai
84cfe6fb42 fix: Add GIT ENV variable for submodules 2022-05-31 20:50:47 +02:00
Andras Bacsai
abf0aeb2a8 fix: Force restart proxy on seeding 2022-05-31 20:50:28 +02:00
Andras Bacsai
a7aca0ce8b fix: Only restart coolify proxy in case of version prior to 2.9.2 2022-05-31 20:25:39 +02:00
Andras Bacsai
67bb5d973b fix: force restart proxy 2022-05-31 19:39:25 +02:00
Andras Bacsai
662948d622 fix: TrustProxy 2022-05-31 19:35:07 +02:00
Andras Bacsai
f5bedfdf7f chore: Version++ 2022-05-31 19:34:55 +02:00
Andras Bacsai
db9db61d92 Merge pull request #436 from coollabsio/next
v2.9.1
2022-05-31 14:00:17 +02:00
Andras Bacsai
d255cb1973 chore: version++ 2022-05-31 13:57:55 +02:00
Andras Bacsai
6529271de2 fix: GitHub fixes 2022-05-31 13:57:42 +02:00
Andras Bacsai
0dd32b5319 Merge pull request #435 from coollabsio/next
v2.9.0 - fixes
2022-05-31 12:47:11 +02:00
Andras Bacsai
b032da798b fix: ftp connection 2022-05-31 12:02:09 +02:00
Andras Bacsai
a1a9f1531e fix: Host key verification 2022-05-31 12:01:55 +02:00
Andras Bacsai
f71b54deb2 fix: Otherfqdns 2022-05-31 11:35:04 +02:00
Andras Bacsai
c63430e342 Merge pull request #426 from christopherklint97/fix/lint-errors-services
fix: remove lint errors in database services
2022-05-31 11:25:10 +02:00
Andras Bacsai
6821b128ad Merge pull request #427 from vasani-arpit/main
adding service request link in template chooser
2022-05-31 11:23:52 +02:00
Andras Bacsai
3f8d44a01c Merge branch 'main' into main 2022-05-31 11:23:34 +02:00
Andras Bacsai
3aef04437c Merge pull request #433 from vasani-arpit/next
Support for Github codespaces and Service request issue template
2022-05-31 11:22:01 +02:00
Andras Bacsai
53e32c038b Merge pull request #434 from coollabsio/next
v2.9.0
2022-05-31 11:20:16 +02:00
Andras Bacsai
1660510614 Merge branch 'main' into next 2022-05-28 21:08:21 +02:00
Arpit Vasani
69f5601b3e Updating docs and removing redundant information 2022-05-28 16:50:45 +00:00
Arpit Vasani
6e22fecc98 adding more commands 2022-05-28 15:19:49 +00:00
Arpit Vasani
d18b2b6a1f Update devcontainer.json 2022-05-28 20:33:47 +05:30
Arpit Vasani
4b0370ac08 Let's see if this works 2022-05-28 14:58:52 +00:00
Arpit Vasani
750ef80777 adding support for github codespaces. 2022-05-28 13:08:52 +00:00
Arpit Vasani
59c62923be gradually redirecting users to fider 2022-05-28 13:00:08 +00:00
Arpit Vasani
68b220d06e Merge remote-tracking branch 'upstream/next' into next 2022-05-28 12:59:30 +00:00
Arpit Vasani
250ea64203 pulling latest changes. 2022-05-28 12:55:55 +00:00
Arpit Vasani
0ab57396d2 Removing redundant information, adding support for github codespaces. 2022-05-28 12:49:56 +00:00
Arpit Vasani
1e36856e65 gradually redirecting users to fider 2022-05-23 14:30:28 +05:30
Andras Bacsai
cfdc8db543 Deleted a file, oops 2022-05-19 16:47:45 +02:00
Andras Bacsai
1f25bc411f feat: database + service usage 2022-05-19 16:43:17 +02:00
Andras Bacsai
972f77c790 ui: css 2022-05-19 16:19:20 +02:00
Andras Bacsai
795f99bb47 fix: new source canceled view 2022-05-19 15:18:19 +02:00
Andras Bacsai
54f7142b2b disable appwrite for now 2022-05-19 15:05:56 +02:00
Andras Bacsai
26eacfc2c0 fix: Instant save on demo instance 2022-05-19 14:38:38 +02:00
Andras Bacsai
e2bf02841f fix: Demo instance save domain instantly 2022-05-19 14:36:49 +02:00
Andras Bacsai
6a59b8d27c fix: do not fetch app state in case of missconfiguration 2022-05-19 14:33:02 +02:00
Andras Bacsai
7fc43ef2bb fix: Remove gh token on git source changes 2022-05-19 14:32:47 +02:00
Andras Bacsai
70a3fc247e remove notifications for now 2022-05-19 14:32:31 +02:00
Andras Bacsai
56ab8312f1 remove notifications for now 2022-05-19 14:32:10 +02:00
Andras Bacsai
6fb6a514ac fix: Minio urls + domain checks 2022-05-19 13:45:17 +02:00
Andras Bacsai
b01f5f47b3 fix: PR deployments view 2022-05-19 13:44:45 +02:00
Andras Bacsai
ebdd3601b3 remove console.logs 2022-05-19 10:21:18 +02:00
Andras Bacsai
c0d711170b fix: Proxy for http 2022-05-19 10:20:43 +02:00
Christopher Klint
01ea86479d fix: lint errors in database services 2022-05-18 21:09:01 +02:00
Andras Bacsai
eb62888c39 fix: WIP Traefik 2022-05-18 16:54:04 +02:00
Andras Bacsai
b006fe8f68 fix: remove debug things 2022-05-18 12:33:24 +02:00
Andras Bacsai
dc3add495c wip(fix): traefik 2022-05-18 12:32:53 +02:00
Andras Bacsai
59086e9eb4 fixes 2022-05-17 15:54:11 +02:00
Andras Bacsai
e563988596 fix: Traefik 2022-05-17 14:06:07 +02:00
Andras Bacsai
5a206a140c fix: remove console.log 2022-05-17 13:25:42 +02:00
Andras Bacsai
dbf910ff38 feat: PageLoader 2022-05-17 11:16:58 +02:00
Andras Bacsai
35b31dce2b WIP: Notifications and application usage 2022-05-17 10:14:06 +02:00
Andras Bacsai
1ec620be4b WIP: Traefik 2022-05-16 23:56:54 +02:00
Andras Bacsai
8516ac671a WIP: Traefik 2022-05-16 23:20:50 +02:00
Andras Bacsai
3b7fdebe8c Merge pull request #425 from coollabsio/quickfix
v2.8.2
2022-05-16 16:19:58 +02:00
Andras Bacsai
17ac3048ac chore: Version++ 2022-05-16 16:17:41 +02:00
Andras Bacsai
4e43efef50 fix: Gastby buildpack 2022-05-16 16:17:32 +02:00
Andras Bacsai
4f4f5b1c01 WIP: Traefik 2022-05-16 16:11:35 +02:00
Andras Bacsai
1fa5c5e021 WIP: Traefik migration 2022-05-14 15:23:41 +02:00
Andras Bacsai
436e0e3a2b WIP: Traefik 2022-05-13 18:09:12 +02:00
Andras Bacsai
e717c1d599 add migration 2022-05-13 17:49:38 +02:00
Andras Bacsai
ae5d90eb47 WIP: Traefik 2022-05-12 16:53:22 +02:00
Andras Bacsai
c095cb58b3 migration 2022-05-12 13:02:40 +02:00
Andras Bacsai
6bba37c36d WIP: Traefik?! 2022-05-12 13:02:14 +02:00
Andras Bacsai
60a428a952 Remove WS for now 2022-05-11 23:21:45 +02:00
Andras Bacsai
16b7c1708b WIP: Testing WS 2022-05-11 16:15:34 +02:00
Andras Bacsai
3435f92fcb WIP: Appwrite 2022-05-11 12:02:09 +02:00
Andras Bacsai
cef571b8cc chore: version++ 2022-05-11 11:03:01 +02:00
Andras Bacsai
242bc61e2d Readme update 2022-05-11 11:02:28 +02:00
Andras Bacsai
c917135bd3 fix: Service checks 2022-05-11 11:02:21 +02:00
Andras Bacsai
3802158ad5 Merge pull request #419 from coollabsio/next
v2.8.1
2022-05-10 18:25:38 +02:00
Andras Bacsai
e452f68614 fix: UI 2022-05-10 18:25:23 +02:00
Andras Bacsai
9586213dd1 fix: WP custom db 2022-05-10 18:21:05 +02:00
Andras Bacsai
30781f218c Merge branch 'main' into next 2022-05-10 14:59:57 +02:00
Andras Bacsai
697c42ff66 update readme 2022-05-10 14:58:49 +02:00
Andras Bacsai
37d8f1847c fix: Default Python package 2022-05-10 14:26:25 +02:00
Andras Bacsai
2af13fff55 chore: Version++ 2022-05-10 14:16:04 +02:00
Andras Bacsai
51e8ca8de0 fix: UI 2022-05-10 13:44:36 +02:00
Andras Bacsai
06228cd2a7 fix: UI 2022-05-10 13:40:30 +02:00
Andras Bacsai
0033baafdc fix: UI 2022-05-10 13:36:39 +02:00
Andras Bacsai
79dfc6a660 Merge pull request #416 from coollabsio/next
v2.8.0
2022-05-10 11:50:07 +02:00
Andras Bacsai
972b0fa811 fix: Remove RC python 2022-05-10 11:14:25 +02:00
Andras Bacsai
ad51a9ebc8 feat: Python image selection 2022-05-10 11:10:58 +02:00
Andras Bacsai
51a40d049d fix: UI 2022-05-10 10:54:47 +02:00
Andras Bacsai
8b3113bd92 fix: UI 2022-05-10 10:34:50 +02:00
Andras Bacsai
d6b6938555 fix: Navbar UI 2022-05-10 10:14:48 +02:00
Andras Bacsai
ce52608f19 feat: WP could have custom db 2022-05-10 10:12:13 +02:00
Andras Bacsai
ede37d296b fix: UI 2022-05-09 23:52:49 +02:00
Andras Bacsai
6374b1284b fix Mongodb icon 2022-05-09 23:51:40 +02:00
Andras Bacsai
6ac8dd8907 fix: Expose ports for services 2022-05-09 15:18:25 +02:00
Andras Bacsai
24c655d7ef feat: Custom script path for Plausible 2022-05-09 15:05:24 +02:00
Andras Bacsai
1f087cc29a fix: Server usage only shown for root team 2022-05-09 14:11:34 +02:00
Andras Bacsai
c3684a1650 chore: version++ 2022-05-09 14:08:13 +02:00
Andras Bacsai
a410fd0776 feat: usage on dashboard 2022-05-09 14:03:07 +02:00
Andras Bacsai
271fb1358d feat: show usage trends 2022-05-09 13:01:00 +02:00
Andras Bacsai
a4d53a28eb feat: Basic server usage on dashboard 2022-05-09 12:45:17 +02:00
Andras Bacsai
e69e32f6c7 fix: default packagemanager 2022-05-09 09:12:21 +02:00
Andras Bacsai
650409dde3 chore: version++ 2022-05-07 10:41:52 +02:00
Andras Bacsai
f3f4bb5105 fix: No image for Docker buildpack 2022-05-07 10:41:31 +02:00
98 changed files with 3824 additions and 896 deletions

16
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node/.devcontainer/base.Dockerfile
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
ARG VARIANT="16-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment if you want to install an additional version of node using nvm
# ARG EXTRA_NODE_VERSION=10
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
# [Optional] Uncomment if you want to install more global node modules
RUN su node -c "npm install -g pnpm"

View File

@@ -0,0 +1,28 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node
{
"name": "Node.js",
"build": {
"dockerfile": "Dockerfile",
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local arm64/Apple Silicon.
"args": {
"VARIANT": "16-bullseye"
}
},
// Set *default* container specific settings.json values on container create.
"settings": {},
// Add the IDs of extensions you want installed when the container is created.
"extensions": ["dbaeumer.vscode-eslint", "svelte.svelte-vscode"],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [3000],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "cp .env.template .env && pnpm install && pnpm db:push && pnpm db:seed",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "node",
"features": {
"docker-in-docker": "20.10",
"github-cli": "latest"
}
}

View File

@@ -3,3 +3,6 @@ contact_links:
- name: 🤔 Questions and Help - name: 🤔 Questions and Help
url: https://discord.com/invite/6rDM4fkymF url: https://discord.com/invite/6rDM4fkymF
about: Reach out to us on discord or our github discussions page. about: Reach out to us on discord or our github discussions page.
- name: 🙋‍♂️ service request
url: https://feedback.coolify.io/
about: want to request a new service? for e.g wordpress, hasura, appwrite etc...

View File

@@ -15,7 +15,13 @@ This is a little list of what you can do to help the project:
## 👋 Introduction ## 👋 Introduction
🔴 At the moment, Coolify **doesn't support Windows**. You must use Linux or MacOS. ### Setup with github codespaces
If you have github codespaces enabled then you can just create a codespace and run `pnpm dev` to run your the dev environment. All the required dependencies and packages has been configured for you already.
### Setup locally in your machine
> 🔴 At the moment, Coolify **doesn't support Windows**. You must use Linux or MacOS. 💡 Although windows users can use github codespaces for development
#### Recommended Pull Request Guideline #### Recommended Pull Request Guideline
@@ -35,20 +41,16 @@ Due to the lock file, this repository is best with [pnpm](https://pnpm.io). I re
You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally. You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
#### Setup a local development environment #### Steps for local setup
- Copy `.env.template` to `.env` and set the `COOLIFY_APP_ID` environment variable to something cool. 1. Copy `.env.template` to `.env` and set the `COOLIFY_APP_ID` environment variable to something cool.
- Install dependencies with `pnpm install`. 2. Install dependencies with `pnpm install`.
- Need to create a local SQlite database with `pnpm db:push`. 3. Need to create a local SQlite database with `pnpm db:push`.
- This will apply all migrations at `db/dev.db`.
- Seed the database with base entities with `pnpm db:seed`
- You can start coding after starting `pnpm dev`.
#### How to start after you set up your local fork? This will apply all migrations at `db/dev.db`.
This repository works better with [pnpm](https://pnpm.io) due to the lock file. I recommend you to give it a try and use `pnpm` as well because it is cool and efficient! 4. Seed the database with base entities with `pnpm db:seed`
5. You can start coding after starting `pnpm dev`.
You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
## 🧑‍💻 Developer contribution ## 🧑‍💻 Developer contribution

View File

@@ -9,6 +9,7 @@ https://demo.coolify.io/
(If it is unresponsive, that means someone overloaded the server. 🙃) (If it is unresponsive, that means someone overloaded the server. 🙃)
## Feedback ## 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! 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 ## How to install
@@ -61,6 +62,8 @@ These are the predefined build packs, but with the Docker build pack, you can ho
- Laravel - Laravel
- Rust - Rust
- Docker - Docker
- Python
- Deno
### Databases ### Databases
@@ -77,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: 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) - [Ghost](https://ghost.org)
- [Plausible Analytics](https://plausible.io) - [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
- [NocoDB](https://nocodb.com) - [NocoDB](https://nocodb.com)
- [VSCode Server](https://github.com/cdr/code-server) - [VSCode Server](https://github.com/cdr/code-server)
- [MinIO](https://min.io) - [MinIO](https://min.io)

View 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}

View 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

View File

@@ -39,3 +39,5 @@ volumes:
name: coolify-ssl-certs name: coolify-ssl-certs
coolify-letsencrypt: coolify-letsencrypt:
name: coolify-letsencrypt name: coolify-letsencrypt
coolify-traefik-letsencrypt:
name: coolify-traefik-letsencrypt

View File

@@ -1,7 +1,7 @@
{ {
"name": "coolify", "name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.", "description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "2.7.0", "version": "2.9.4",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0", "dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0",
@@ -30,61 +30,63 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "1.0.0-next.73", "@sveltejs/adapter-node": "1.0.0-next.73",
"@sveltejs/kit": "1.0.0-next.316", "@sveltejs/adapter-static": "1.0.0-next.31",
"@types/js-cookie": "3.0.1", "@sveltejs/kit": "1.0.0-next.334",
"@types/js-cookie": "3.0.2",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
"@types/node": "17.0.25", "@types/node": "17.0.34",
"@types/node-forge": "1.0.1", "@types/node-forge": "1.0.2",
"@typescript-eslint/eslint-plugin": "4.31.1", "@typescript-eslint/eslint-plugin": "4.31.1",
"@typescript-eslint/parser": "4.31.1", "@typescript-eslint/parser": "4.31.1",
"@zerodevx/svelte-toast": "0.7.1", "@zerodevx/svelte-toast": "0.7.1",
"autoprefixer": "10.4.4", "autoprefixer": "10.4.7",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cross-var": "1.1.0", "cross-var": "1.1.0",
"eslint": "7.32.0", "eslint": "7.32.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.5.0",
"eslint-plugin-svelte3": "3.4.1", "eslint-plugin-svelte3": "3.4.1",
"husky": "7.0.4", "husky": "7.0.4",
"lint-staged": "12.4.0", "lint-staged": "12.4.1",
"postcss": "8.4.12", "postcss": "8.4.13",
"prettier": "2.6.2", "prettier": "2.6.2",
"prettier-plugin-svelte": "2.7.0", "prettier-plugin-svelte": "2.7.0",
"prettier-plugin-tailwindcss": "0.1.10", "prettier-plugin-tailwindcss": "0.1.11",
"prisma": "3.11.1", "prisma": "3.11.1",
"svelte": "3.47.0", "svelte": "3.48.0",
"svelte-check": "2.7.0", "svelte-check": "2.7.1",
"svelte-preprocess": "4.10.6", "svelte-preprocess": "4.10.6",
"svelte-select": "4.4.7", "svelte-select": "4.4.7",
"sveltekit-i18n": "2.1.2", "sveltekit-i18n": "2.2.1",
"tailwindcss": "3.0.24", "tailwindcss": "3.0.24",
"ts-node": "10.7.0", "ts-node": "10.7.0",
"tslib": "2.3.1", "tslib": "2.4.0",
"typescript": "4.6.3" "typescript": "4.6.4"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@iarna/toml": "2.2.5", "@iarna/toml": "2.2.5",
"@prisma/client": "3.11.1", "@prisma/client": "3.11.1",
"@sentry/node": "6.19.6", "@sentry/node": "6.19.7",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bullmq": "1.80.4", "bullmq": "1.82.2",
"compare-versions": "4.1.3", "compare-versions": "4.1.3",
"cookie": "0.5.0", "cookie": "0.5.0",
"cuid": "2.1.8", "cuid": "2.1.8",
"dayjs": "1.11.1", "dayjs": "1.11.2",
"dockerode": "3.3.1", "dockerode": "3.3.1",
"dotenv-extended": "2.9.0", "dotenv-extended": "2.9.0",
"generate-password": "1.7.0", "generate-password": "1.7.0",
"get-port": "6.1.2", "get-port": "6.1.2",
"got": "12.0.3", "got": "12.0.4",
"is-ip": "^4.0.0", "is-ip": "4.0.0",
"js-cookie": "3.0.1", "js-cookie": "3.0.1",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "8.5.1",
"mustache": "4.2.0", "mustache": "4.2.0",
"node-forge": "1.3.1", "node-forge": "1.3.1",
"node-os-utils": "1.3.6",
"p-limit": "4.0.0", "p-limit": "4.0.0",
"svelte-kit-cookie-session": "2.1.3", "svelte-kit-cookie-session": "2.1.4",
"tailwindcss-scrollbar": "0.1.0", "tailwindcss-scrollbar": "0.1.0",
"unique-names-generator": "4.7.1" "unique-names-generator": "4.7.1"
}, },

414
pnpm-lock.yaml generated
View File

@@ -3,25 +3,26 @@ lockfileVersion: 5.4
specifiers: specifiers:
'@iarna/toml': 2.2.5 '@iarna/toml': 2.2.5
'@prisma/client': 3.11.1 '@prisma/client': 3.11.1
'@sentry/node': 6.19.6 '@sentry/node': 6.19.7
'@sveltejs/adapter-node': 1.0.0-next.73 '@sveltejs/adapter-node': 1.0.0-next.73
'@sveltejs/kit': 1.0.0-next.316 '@sveltejs/adapter-static': 1.0.0-next.31
'@types/js-cookie': 3.0.1 '@sveltejs/kit': 1.0.0-next.334
'@types/js-cookie': 3.0.2
'@types/js-yaml': 4.0.5 '@types/js-yaml': 4.0.5
'@types/node': 17.0.25 '@types/node': 17.0.34
'@types/node-forge': 1.0.1 '@types/node-forge': 1.0.2
'@typescript-eslint/eslint-plugin': 4.31.1 '@typescript-eslint/eslint-plugin': 4.31.1
'@typescript-eslint/parser': 4.31.1 '@typescript-eslint/parser': 4.31.1
'@zerodevx/svelte-toast': 0.7.1 '@zerodevx/svelte-toast': 0.7.1
autoprefixer: 10.4.4 autoprefixer: 10.4.7
bcryptjs: 2.4.3 bcryptjs: 2.4.3
bullmq: 1.80.4 bullmq: 1.82.2
compare-versions: 4.1.3 compare-versions: 4.1.3
cookie: 0.5.0 cookie: 0.5.0
cross-env: 7.0.3 cross-env: 7.0.3
cross-var: 1.1.0 cross-var: 1.1.0
cuid: 2.1.8 cuid: 2.1.8
dayjs: 1.11.1 dayjs: 1.11.2
dockerode: 3.3.1 dockerode: 3.3.1
dotenv-extended: 2.9.0 dotenv-extended: 2.9.0
eslint: 7.32.0 eslint: 7.32.0
@@ -29,92 +30,95 @@ specifiers:
eslint-plugin-svelte3: 3.4.1 eslint-plugin-svelte3: 3.4.1
generate-password: 1.7.0 generate-password: 1.7.0
get-port: 6.1.2 get-port: 6.1.2
got: 12.0.3 got: 12.0.4
husky: 7.0.4 husky: 7.0.4
is-ip: ^4.0.0 is-ip: 4.0.0
js-cookie: 3.0.1 js-cookie: 3.0.1
js-yaml: 4.1.0 js-yaml: 4.1.0
jsonwebtoken: 8.5.1 jsonwebtoken: 8.5.1
lint-staged: 12.4.0 lint-staged: 12.4.1
mustache: 4.2.0 mustache: 4.2.0
node-forge: 1.3.1 node-forge: 1.3.1
node-os-utils: 1.3.6
p-limit: 4.0.0 p-limit: 4.0.0
postcss: 8.4.12 postcss: 8.4.13
prettier: 2.6.2 prettier: 2.6.2
prettier-plugin-svelte: 2.7.0 prettier-plugin-svelte: 2.7.0
prettier-plugin-tailwindcss: 0.1.10 prettier-plugin-tailwindcss: 0.1.11
prisma: 3.11.1 prisma: 3.11.1
svelte: 3.47.0 svelte: 3.48.0
svelte-check: 2.7.0 svelte-check: 2.7.1
svelte-kit-cookie-session: 2.1.3 svelte-kit-cookie-session: 2.1.4
svelte-preprocess: 4.10.6 svelte-preprocess: 4.10.6
svelte-select: 4.4.7 svelte-select: 4.4.7
sveltekit-i18n: 2.1.2 sveltekit-i18n: 2.2.1
tailwindcss: 3.0.24 tailwindcss: 3.0.24
tailwindcss-scrollbar: 0.1.0 tailwindcss-scrollbar: 0.1.0
ts-node: 10.7.0 ts-node: 10.7.0
tslib: 2.3.1 tslib: 2.4.0
typescript: 4.6.3 typescript: 4.6.4
unique-names-generator: 4.7.1 unique-names-generator: 4.7.1
dependencies: dependencies:
'@iarna/toml': 2.2.5 '@iarna/toml': 2.2.5
'@prisma/client': 3.11.1_prisma@3.11.1 '@prisma/client': 3.11.1_prisma@3.11.1
'@sentry/node': 6.19.6 '@sentry/node': 6.19.7
bcryptjs: 2.4.3 bcryptjs: 2.4.3
bullmq: 1.80.4 bullmq: 1.82.2
compare-versions: 4.1.3 compare-versions: 4.1.3
cookie: 0.5.0 cookie: 0.5.0
cuid: 2.1.8 cuid: 2.1.8
dayjs: 1.11.1 dayjs: 1.11.2
dockerode: 3.3.1 dockerode: 3.3.1
dotenv-extended: 2.9.0 dotenv-extended: 2.9.0
generate-password: 1.7.0 generate-password: 1.7.0
get-port: 6.1.2 get-port: 6.1.2
got: 12.0.3 got: 12.0.4
is-ip: 4.0.0 is-ip: 4.0.0
js-cookie: 3.0.1 js-cookie: 3.0.1
js-yaml: 4.1.0 js-yaml: 4.1.0
jsonwebtoken: 8.5.1 jsonwebtoken: 8.5.1
mustache: 4.2.0 mustache: 4.2.0
node-forge: 1.3.1 node-forge: 1.3.1
node-os-utils: 1.3.6
p-limit: 4.0.0 p-limit: 4.0.0
svelte-kit-cookie-session: 2.1.3 svelte-kit-cookie-session: 2.1.4
tailwindcss-scrollbar: 0.1.0_tailwindcss@3.0.24 tailwindcss-scrollbar: 0.1.0_tailwindcss@3.0.24
unique-names-generator: 4.7.1 unique-names-generator: 4.7.1
devDependencies: devDependencies:
'@sveltejs/adapter-node': 1.0.0-next.73 '@sveltejs/adapter-node': 1.0.0-next.73
'@sveltejs/kit': 1.0.0-next.316_svelte@3.47.0 '@sveltejs/adapter-static': 1.0.0-next.31
'@types/js-cookie': 3.0.1 '@sveltejs/kit': 1.0.0-next.334_svelte@3.48.0
'@types/js-cookie': 3.0.2
'@types/js-yaml': 4.0.5 '@types/js-yaml': 4.0.5
'@types/node': 17.0.25 '@types/node': 17.0.34
'@types/node-forge': 1.0.1 '@types/node-forge': 1.0.2
'@typescript-eslint/eslint-plugin': 4.31.1_r3ph5xlwsrsg4ewthrjemd3cfq '@typescript-eslint/eslint-plugin': 4.31.1_lii63oz3usekbu5ehvrcuwn5jy
'@typescript-eslint/parser': 4.31.1_hrkuebk64jiu2ut2d2sm4oylnu '@typescript-eslint/parser': 4.31.1_e4zyhrvfnqudwdx5bevnvkluy4
'@zerodevx/svelte-toast': 0.7.1 '@zerodevx/svelte-toast': 0.7.1
autoprefixer: 10.4.4_postcss@8.4.12 autoprefixer: 10.4.7_postcss@8.4.13
cross-env: 7.0.3 cross-env: 7.0.3
cross-var: 1.1.0 cross-var: 1.1.0
eslint: 7.32.0 eslint: 7.32.0
eslint-config-prettier: 8.5.0_eslint@7.32.0 eslint-config-prettier: 8.5.0_eslint@7.32.0
eslint-plugin-svelte3: 3.4.1_4oxeyilw5mxcaksmcxtpjddhfe eslint-plugin-svelte3: 3.4.1_6wevxxng4y4ff26nzlndg2wnpa
husky: 7.0.4 husky: 7.0.4
lint-staged: 12.4.0 lint-staged: 12.4.1
postcss: 8.4.12 postcss: 8.4.13
prettier: 2.6.2 prettier: 2.6.2
prettier-plugin-svelte: 2.7.0_sqtt6dzjlskmywoml5ykunxlce prettier-plugin-svelte: 2.7.0_kkjbqzpydplecjtkxrgomroeru
prettier-plugin-tailwindcss: 0.1.10_prettier@2.6.2 prettier-plugin-tailwindcss: 0.1.11_prettier@2.6.2
prisma: 3.11.1 prisma: 3.11.1
svelte: 3.47.0 svelte: 3.48.0
svelte-check: 2.7.0_cp6olp7pwsfaq5mjijwt65d6uy svelte-check: 2.7.1_f2ke6qjyzu5axsjd6yk3u4tn7a
svelte-preprocess: 4.10.6_igaqrb5onrwvsmrrc32h4m72ha svelte-preprocess: 4.10.6_nq4dx2skq5drra53vttuo4lltu
svelte-select: 4.4.7 svelte-select: 4.4.7
sveltekit-i18n: 2.1.2_svelte@3.47.0 sveltekit-i18n: 2.2.1_svelte@3.48.0
tailwindcss: 3.0.24_ts-node@10.7.0 tailwindcss: 3.0.24_ts-node@10.7.0
ts-node: 10.7.0_3z6inmgn4ud4moqealnfxgbl2m ts-node: 10.7.0_3smuweqyuzdazdnyhhezld6mfa
tslib: 2.3.1 tslib: 2.4.0
typescript: 4.6.3 typescript: 4.6.4
packages: packages:
/@babel/code-frame/7.12.11: /@babel/code-frame/7.12.11:
@@ -212,6 +216,31 @@ packages:
} }
dev: false dev: false
/@jridgewell/resolve-uri/3.0.7:
resolution:
{
integrity: sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==
}
engines: { node: '>=6.0.0' }
dev: true
/@jridgewell/sourcemap-codec/1.4.13:
resolution:
{
integrity: sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==
}
dev: true
/@jridgewell/trace-mapping/0.3.13:
resolution:
{
integrity: sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==
}
dependencies:
'@jridgewell/resolve-uri': 3.0.7
'@jridgewell/sourcemap-codec': 1.4.13
dev: true
/@nodelib/fs.scandir/2.1.5: /@nodelib/fs.scandir/2.1.5:
resolution: resolution:
{ {
@@ -285,55 +314,55 @@ packages:
picomatch: 2.3.0 picomatch: 2.3.0
dev: true dev: true
/@sentry/core/6.19.6: /@sentry/core/6.19.7:
resolution: resolution:
{ {
integrity: sha512-biEotGRr44/vBCOegkTfC9rwqaqRKIpFljKGyYU6/NtzMRooktqOhjmjmItNCMRknArdeaQwA8lk2jcZDXX3Og== integrity: sha512-tOfZ/umqB2AcHPGbIrsFLcvApdTm9ggpi/kQZFkej7kMphjT+SGBiQfYtjyg9jcRW+ilAR4JXC9BGKsdEQ+8Vw==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dependencies: dependencies:
'@sentry/hub': 6.19.6 '@sentry/hub': 6.19.7
'@sentry/minimal': 6.19.6 '@sentry/minimal': 6.19.7
'@sentry/types': 6.19.6 '@sentry/types': 6.19.7
'@sentry/utils': 6.19.6 '@sentry/utils': 6.19.7
tslib: 1.14.1 tslib: 1.14.1
dev: false dev: false
/@sentry/hub/6.19.6: /@sentry/hub/6.19.7:
resolution: resolution:
{ {
integrity: sha512-PuEOBZxvx3bjxcXmWWZfWXG+orojQiWzv9LQXjIgroVMKM/GG4QtZbnWl1hOckUj7WtKNl4hEGO2g/6PyCV/vA== integrity: sha512-y3OtbYFAqKHCWezF0EGGr5lcyI2KbaXW2Ik7Xp8Mu9TxbSTuwTe4rTntwg8ngPjUQU3SUHzgjqVB8qjiGqFXCA==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dependencies: dependencies:
'@sentry/types': 6.19.6 '@sentry/types': 6.19.7
'@sentry/utils': 6.19.6 '@sentry/utils': 6.19.7
tslib: 1.14.1 tslib: 1.14.1
dev: false dev: false
/@sentry/minimal/6.19.6: /@sentry/minimal/6.19.7:
resolution: resolution:
{ {
integrity: sha512-T1NKcv+HTlmd8EbzUgnGPl4ySQGHWMCyZ8a8kXVMZOPDzphN3fVIzkYzWmSftCWp0rpabXPt9aRF2mfBKU+mAQ== integrity: sha512-wcYmSJOdvk6VAPx8IcmZgN08XTXRwRtB1aOLZm+MVHjIZIhHoBGZJYTVQS/BWjldsamj2cX3YGbGXNunaCfYJQ==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dependencies: dependencies:
'@sentry/hub': 6.19.6 '@sentry/hub': 6.19.7
'@sentry/types': 6.19.6 '@sentry/types': 6.19.7
tslib: 1.14.1 tslib: 1.14.1
dev: false dev: false
/@sentry/node/6.19.6: /@sentry/node/6.19.7:
resolution: resolution:
{ {
integrity: sha512-kHQMfsy40ZxxdS9zMPmXCOOLWOJbQj6/aVSHt/L1QthYcgkAi7NJQNXnQIPWQDe8eP3DfNIWM7dc446coqjXrQ== integrity: sha512-gtmRC4dAXKODMpHXKfrkfvyBL3cI8y64vEi3fDD046uqYcrWdgoQsffuBbxMAizc6Ez1ia+f0Flue6p15Qaltg==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dependencies: dependencies:
'@sentry/core': 6.19.6 '@sentry/core': 6.19.7
'@sentry/hub': 6.19.6 '@sentry/hub': 6.19.7
'@sentry/types': 6.19.6 '@sentry/types': 6.19.7
'@sentry/utils': 6.19.6 '@sentry/utils': 6.19.7
cookie: 0.4.2 cookie: 0.4.2
https-proxy-agent: 5.0.0 https-proxy-agent: 5.0.0
lru_map: 0.3.3 lru_map: 0.3.3
@@ -342,22 +371,22 @@ packages:
- supports-color - supports-color
dev: false dev: false
/@sentry/types/6.19.6: /@sentry/types/6.19.7:
resolution: resolution:
{ {
integrity: sha512-QH34LMJidEUPZK78l+Frt3AaVFJhEmIi05Zf8WHd9/iTt+OqvCHBgq49DDr1FWFqyYWm/QgW/3bIoikFpfsXyQ== integrity: sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dev: false dev: false
/@sentry/utils/6.19.6: /@sentry/utils/6.19.7:
resolution: resolution:
{ {
integrity: sha512-fAMWcsguL0632eWrROp/vhPgI7sBj/JROWVPzpabwVkm9z3m1rQm6iLFn4qfkZL8Ozy6NVZPXOQ7EXmeU24byg== integrity: sha512-z95ECmE3i9pbWoXQrD/7PgkBAzJYR+iXtPuTkpBjDKs86O3mT+PXOT3BAn79w2wkn7/i3vOGD2xVr1uiMl26dA==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dependencies: dependencies:
'@sentry/types': 6.19.6 '@sentry/types': 6.19.7
tslib: 1.14.1 tslib: 1.14.1
dev: false dev: false
@@ -378,19 +407,29 @@ packages:
tiny-glob: 0.2.9 tiny-glob: 0.2.9
dev: true dev: true
/@sveltejs/kit/1.0.0-next.316_svelte@3.47.0: /@sveltejs/adapter-static/1.0.0-next.31:
resolution: resolution:
{ {
integrity: sha512-oLjWOWzjriJD2t210r7ALuH/8ZADrJGsOODzRCRSJvRBCt0Q7VKVLqwKbM/RlZzD1k8Af2uRodQT11kP98hAIA== integrity: sha512-d9RNA/de5ljb+gN8mKA3YfmfJoTbYFdH96NYDD8u4Lu9O/ZnseUxOAcAmD4/LKbLXOY/oYhRpt029xT2owyI3Q==
} }
engines: { node: '>=14.13' } dependencies:
tiny-glob: 0.2.9
dev: true
/@sveltejs/kit/1.0.0-next.334_svelte@3.48.0:
resolution:
{
integrity: sha512-HPMF1oYBfyOG6wfU0Y6F4SID8jphue9yF+PXJqVTDBL5Z2WBG2ogum6MavE8aWhq+g2H6w5y0jNT8+8DO2KTCA==
}
engines: { node: '>=16' }
hasBin: true hasBin: true
peerDependencies: peerDependencies:
svelte: ^3.44.0 svelte: ^3.44.0
dependencies: dependencies:
'@sveltejs/vite-plugin-svelte': 1.0.0-next.33_svelte@3.47.0+vite@2.9.1 '@sveltejs/vite-plugin-svelte': 1.0.0-next.33_svelte@3.48.0+vite@2.9.1
chokidar: 3.5.3
sade: 1.7.4 sade: 1.7.4
svelte: 3.47.0 svelte: 3.48.0
vite: 2.9.1 vite: 2.9.1
transitivePeerDependencies: transitivePeerDependencies:
- diff-match-patch - diff-match-patch
@@ -400,7 +439,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@sveltejs/vite-plugin-svelte/1.0.0-next.33_svelte@3.47.0+vite@2.9.1: /@sveltejs/vite-plugin-svelte/1.0.0-next.33_svelte@3.48.0+vite@2.9.1:
resolution: resolution:
{ {
integrity: sha512-aj0h2+ZixgT+yoJFIs8dRRw/Cj9tgNu3+hY4CJikpa04mfhR61wXqJFfi2ZEFMUvFda5nCxKYIChFkc6wq5fJA== integrity: sha512-aj0h2+ZixgT+yoJFIs8dRRw/Cj9tgNu3+hY4CJikpa04mfhR61wXqJFfi2ZEFMUvFda5nCxKYIChFkc6wq5fJA==
@@ -419,22 +458,22 @@ packages:
kleur: 4.1.4 kleur: 4.1.4
magic-string: 0.25.7 magic-string: 0.25.7
require-relative: 0.8.7 require-relative: 0.8.7
svelte: 3.47.0 svelte: 3.48.0
svelte-hmr: 0.14.9_svelte@3.47.0 svelte-hmr: 0.14.9_svelte@3.48.0
vite: 2.9.1 vite: 2.9.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@sveltekit-i18n/base/1.1.1_svelte@3.47.0: /@sveltekit-i18n/base/1.2.1_svelte@3.48.0:
resolution: resolution:
{ {
integrity: sha512-J/sMU0OwS3dCLOuilHMBqu8vZHuuXiNV9vFJx8Nb4/b5BlR/KCZ4bCXI8wZR02GHeCOYKZxWus07CM1scxa/jw== integrity: sha512-F8gqG2+KAOeT0o2wYlUrW3TRCX7zaD7rBy/1CEVNw0irfw9TgFf/ODmhubkHHT3+6Zk+SMz8RNgeuffBfAMbJw==
} }
peerDependencies: peerDependencies:
svelte: ^3.x svelte: ^3.x
dependencies: dependencies:
svelte: 3.47.0 svelte: 3.48.0
optionalDependencies: optionalDependencies:
'@sveltekit-i18n/parser-default': 1.0.3 '@sveltekit-i18n/parser-default': 1.0.3
dev: true dev: true
@@ -492,7 +531,7 @@ packages:
dependencies: dependencies:
'@types/http-cache-semantics': 4.0.1 '@types/http-cache-semantics': 4.0.1
'@types/keyv': 3.1.3 '@types/keyv': 3.1.3
'@types/node': 17.0.25 '@types/node': 17.0.34
'@types/responselike': 1.0.0 '@types/responselike': 1.0.0
dev: false dev: false
@@ -503,10 +542,10 @@ packages:
} }
dev: false dev: false
/@types/js-cookie/3.0.1: /@types/js-cookie/3.0.2:
resolution: resolution:
{ {
integrity: sha512-7wg/8gfHltklehP+oyJnZrz9XBuX5ZPP4zB6UsI84utdlkRYLnOm2HfpLXazTwZA+fpGn0ir8tGNgVnMEleBGQ== integrity: sha512-6+0ekgfusHftJNYpihfkMu8BWdeHs9EOJuGcSofErjstGPfPGEu9yTu4t460lTzzAMl2cM5zngQJqPMHbbnvYA==
} }
dev: true dev: true
@@ -530,22 +569,22 @@ packages:
integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg== integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==
} }
dependencies: dependencies:
'@types/node': 17.0.25 '@types/node': 17.0.34
dev: false dev: false
/@types/node-forge/1.0.1: /@types/node-forge/1.0.2:
resolution: resolution:
{ {
integrity: sha512-96ELNKv9tQJ19afdBUiM5iDw7OYEc53iUc51gAPR2aGaqRsO1DBROjqgZRjZa1tkPj7TnEOR0EnyAX6iryGkzA== integrity: sha512-J1OkeZGaW0y9Y7xD49Ja8O82B9l5nZDeoYuGWqIOYPAf9LR+xF23k9ILdzv8dH+2H033fx3D5oiA0GlmicI+sg==
} }
dependencies: dependencies:
'@types/node': 17.0.25 '@types/node': 17.0.34
dev: true dev: true
/@types/node/17.0.25: /@types/node/17.0.34:
resolution: resolution:
{ {
integrity: sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w== integrity: sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==
} }
/@types/pug/2.0.5: /@types/pug/2.0.5:
@@ -561,7 +600,7 @@ packages:
integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
} }
dependencies: dependencies:
'@types/node': 17.0.25 '@types/node': 17.0.34
dev: false dev: false
/@types/sass/1.16.1: /@types/sass/1.16.1:
@@ -570,10 +609,10 @@ packages:
integrity: sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ== integrity: sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ==
} }
dependencies: dependencies:
'@types/node': 17.0.25 '@types/node': 17.0.34
dev: true dev: true
/@typescript-eslint/eslint-plugin/4.31.1_r3ph5xlwsrsg4ewthrjemd3cfq: /@typescript-eslint/eslint-plugin/4.31.1_lii63oz3usekbu5ehvrcuwn5jy:
resolution: resolution:
{ {
integrity: sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA== integrity: sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA==
@@ -587,21 +626,21 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/experimental-utils': 4.31.1_hrkuebk64jiu2ut2d2sm4oylnu '@typescript-eslint/experimental-utils': 4.31.1_e4zyhrvfnqudwdx5bevnvkluy4
'@typescript-eslint/parser': 4.31.1_hrkuebk64jiu2ut2d2sm4oylnu '@typescript-eslint/parser': 4.31.1_e4zyhrvfnqudwdx5bevnvkluy4
'@typescript-eslint/scope-manager': 4.31.1 '@typescript-eslint/scope-manager': 4.31.1
debug: 4.3.3 debug: 4.3.3
eslint: 7.32.0 eslint: 7.32.0
functional-red-black-tree: 1.0.1 functional-red-black-tree: 1.0.1
regexpp: 3.2.0 regexpp: 3.2.0
semver: 7.3.5 semver: 7.3.5
tsutils: 3.21.0_typescript@4.6.3 tsutils: 3.21.0_typescript@4.6.4
typescript: 4.6.3 typescript: 4.6.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/experimental-utils/4.31.1_hrkuebk64jiu2ut2d2sm4oylnu: /@typescript-eslint/experimental-utils/4.31.1_e4zyhrvfnqudwdx5bevnvkluy4:
resolution: resolution:
{ {
integrity: sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q== integrity: sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==
@@ -613,7 +652,7 @@ packages:
'@types/json-schema': 7.0.9 '@types/json-schema': 7.0.9
'@typescript-eslint/scope-manager': 4.31.1 '@typescript-eslint/scope-manager': 4.31.1
'@typescript-eslint/types': 4.31.1 '@typescript-eslint/types': 4.31.1
'@typescript-eslint/typescript-estree': 4.31.1_typescript@4.6.3 '@typescript-eslint/typescript-estree': 4.31.1_typescript@4.6.4
eslint: 7.32.0 eslint: 7.32.0
eslint-scope: 5.1.1 eslint-scope: 5.1.1
eslint-utils: 3.0.0_eslint@7.32.0 eslint-utils: 3.0.0_eslint@7.32.0
@@ -622,7 +661,7 @@ packages:
- typescript - typescript
dev: true dev: true
/@typescript-eslint/parser/4.31.1_hrkuebk64jiu2ut2d2sm4oylnu: /@typescript-eslint/parser/4.31.1_e4zyhrvfnqudwdx5bevnvkluy4:
resolution: resolution:
{ {
integrity: sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ== integrity: sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ==
@@ -637,10 +676,10 @@ packages:
dependencies: dependencies:
'@typescript-eslint/scope-manager': 4.31.1 '@typescript-eslint/scope-manager': 4.31.1
'@typescript-eslint/types': 4.31.1 '@typescript-eslint/types': 4.31.1
'@typescript-eslint/typescript-estree': 4.31.1_typescript@4.6.3 '@typescript-eslint/typescript-estree': 4.31.1_typescript@4.6.4
debug: 4.3.3 debug: 4.3.3
eslint: 7.32.0 eslint: 7.32.0
typescript: 4.6.3 typescript: 4.6.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@@ -664,7 +703,7 @@ packages:
engines: { node: ^8.10.0 || ^10.13.0 || >=11.10.1 } engines: { node: ^8.10.0 || ^10.13.0 || >=11.10.1 }
dev: true dev: true
/@typescript-eslint/typescript-estree/4.31.1_typescript@4.6.3: /@typescript-eslint/typescript-estree/4.31.1_typescript@4.6.4:
resolution: resolution:
{ {
integrity: sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg== integrity: sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==
@@ -682,8 +721,8 @@ packages:
globby: 11.0.4 globby: 11.0.4
is-glob: 4.0.3 is-glob: 4.0.3
semver: 7.3.5 semver: 7.3.5
tsutils: 3.21.0_typescript@4.6.3 tsutils: 3.21.0_typescript@4.6.4
typescript: 4.6.3 typescript: 4.6.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@@ -956,22 +995,22 @@ packages:
typpy: 2.3.11 typpy: 2.3.11
dev: false dev: false
/autoprefixer/10.4.4_postcss@8.4.12: /autoprefixer/10.4.7_postcss@8.4.13:
resolution: resolution:
{ {
integrity: sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA== integrity: sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==
} }
engines: { node: ^10 || ^12 || >=14 } engines: { node: ^10 || ^12 || >=14 }
hasBin: true hasBin: true
peerDependencies: peerDependencies:
postcss: ^8.1.0 postcss: ^8.1.0
dependencies: dependencies:
browserslist: 4.20.2 browserslist: 4.20.3
caniuse-lite: 1.0.30001320 caniuse-lite: 1.0.30001338
fraction.js: 4.2.0 fraction.js: 4.2.0
normalize-range: 0.1.2 normalize-range: 0.1.2
picocolors: 1.0.0 picocolors: 1.0.0
postcss: 8.4.12 postcss: 8.4.13
postcss-value-parser: 4.2.0 postcss-value-parser: 4.2.0
dev: true dev: true
@@ -1653,18 +1692,18 @@ packages:
fill-range: 7.0.1 fill-range: 7.0.1
dev: true dev: true
/browserslist/4.20.2: /browserslist/4.20.3:
resolution: resolution:
{ {
integrity: sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== integrity: sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==
} }
engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 }
hasBin: true hasBin: true
dependencies: dependencies:
caniuse-lite: 1.0.30001320 caniuse-lite: 1.0.30001338
electron-to-chromium: 1.4.93 electron-to-chromium: 1.4.137
escalade: 3.1.1 escalade: 3.1.1
node-releases: 2.0.2 node-releases: 2.0.4
picocolors: 1.0.0 picocolors: 1.0.0
dev: true dev: true
@@ -1686,10 +1725,10 @@ packages:
ieee754: 1.2.1 ieee754: 1.2.1
dev: false dev: false
/bullmq/1.80.4: /bullmq/1.82.2:
resolution: resolution:
{ {
integrity: sha512-j3PyjU16gqmb3Md9QjMInJdbMvxIlSJx7mojtoP06LV9MfhzW75DkDrpSuJlF0H+0+u6MViV4hhaGTxky5OJWw== integrity: sha512-pDmMl6HmL/7B41ldBK4lnmGUcobkI/n/a0T3d/volMWC0ULxsaZ6R6fDePk23LwH9Fxu4o9Ny+zurCL3vG7lbg==
} }
dependencies: dependencies:
cron-parser: 4.2.1 cron-parser: 4.2.1
@@ -1753,10 +1792,10 @@ packages:
engines: { node: '>=6' } engines: { node: '>=6' }
dev: false dev: false
/caniuse-lite/1.0.30001320: /caniuse-lite/1.0.30001338:
resolution: resolution:
{ {
integrity: sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA== integrity: sha512-1gLHWyfVoRDsHieO+CaeYe7jSo/MT7D7lhaXUiwwbuR5BwQxORs0f1tAwUSQr3YbxRXJvxHM/PA5FfPQRnsPeQ==
} }
dev: true dev: true
@@ -2048,10 +2087,10 @@ packages:
} }
dev: false dev: false
/dayjs/1.11.1: /dayjs/1.11.2:
resolution: resolution:
{ {
integrity: sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA== integrity: sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==
} }
dev: false dev: false
@@ -2267,10 +2306,10 @@ packages:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
dev: false dev: false
/electron-to-chromium/1.4.93: /electron-to-chromium/1.4.137:
resolution: resolution:
{ {
integrity: sha512-ywq9Pc5Gwwpv7NG767CtoU8xF3aAUQJjH9//Wy3MBCg4w5JSLbJUq2L8IsCdzPMjvSgxuue9WcVaTOyyxCL0aQ== integrity: sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==
} }
dev: true dev: true
@@ -2615,7 +2654,7 @@ packages:
eslint: 7.32.0 eslint: 7.32.0
dev: true dev: true
/eslint-plugin-svelte3/3.4.1_4oxeyilw5mxcaksmcxtpjddhfe: /eslint-plugin-svelte3/3.4.1_6wevxxng4y4ff26nzlndg2wnpa:
resolution: resolution:
{ {
integrity: sha512-7p59WG8qV8L6wLdl4d/c3mdjkgVglQCdv5XOTk/iNPBKXuuV+Q0eFP5Wa6iJd/G2M1qR3BkLPEzaANOqKAZczw== integrity: sha512-7p59WG8qV8L6wLdl4d/c3mdjkgVglQCdv5XOTk/iNPBKXuuV+Q0eFP5Wa6iJd/G2M1qR3BkLPEzaANOqKAZczw==
@@ -2626,7 +2665,7 @@ packages:
svelte: ^3.2.0 svelte: ^3.2.0
dependencies: dependencies:
eslint: 7.32.0 eslint: 7.32.0
svelte: 3.47.0 svelte: 3.48.0
dev: true dev: true
/eslint-scope/5.1.1: /eslint-scope/5.1.1:
@@ -3080,10 +3119,10 @@ packages:
} }
dev: true dev: true
/got/12.0.3: /got/12.0.4:
resolution: resolution:
{ {
integrity: sha512-hmdcXi/S0gcAtDg4P8j/rM7+j3o1Aq6bXhjxkDhRY2ipe7PHpvx/14DgTY2czHOLaGeU8VRvRecidwfu9qdFug== integrity: sha512-2Eyz4iU/ktq7wtMFXxzK7g5p35uNYLLdiZarZ5/Yn3IJlNEpBd5+dCgcAyxN8/8guZLszffwe3wVyw+DEVrpBg==
} }
engines: { node: '>=14.16' } engines: { node: '>=14.16' }
dependencies: dependencies:
@@ -3544,10 +3583,10 @@ packages:
engines: { node: '>=10' } engines: { node: '>=10' }
dev: true dev: true
/lint-staged/12.4.0: /lint-staged/12.4.1:
resolution: resolution:
{ {
integrity: sha512-3X7MR0h9b7qf4iXf/1n7RlVAx+EzpAZXoCEMhVSpaBlgKDfH2ewf+QUm7BddFyq29v4dgPP+8+uYpWuSWx035A== integrity: sha512-PTXgzpflrQ+pODQTG116QNB+Q6uUTDg5B5HqGvNhoQSGt8Qy+MA/6zSnR8n38+sxP5TapzeQGTvoKni0KRS8Vg==
} }
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
hasBin: true hasBin: true
@@ -3884,10 +3923,10 @@ packages:
dev: false dev: false
optional: true optional: true
/nanoid/3.3.1: /nanoid/3.3.4:
resolution: resolution:
{ {
integrity: sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
} }
engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 }
hasBin: true hasBin: true
@@ -3914,10 +3953,17 @@ packages:
dev: false dev: false
optional: true optional: true
/node-releases/2.0.2: /node-os-utils/1.3.6:
resolution: resolution:
{ {
integrity: sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== integrity: sha512-WympE9ELtdOzNak/rAuuIV5DwvX/PTJtN0LjyWeGyTTR2Kt0sY56ldLoGbVBnfM1dz46VeO3sHcNZI5BZ+EB+w==
}
dev: false
/node-releases/2.0.4:
resolution:
{
integrity: sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==
} }
dev: true dev: true
@@ -4110,7 +4156,7 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/postcss-js/4.0.0_postcss@8.4.12: /postcss-js/4.0.0_postcss@8.4.13:
resolution: resolution:
{ {
integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==
@@ -4120,10 +4166,10 @@ packages:
postcss: ^8.3.3 postcss: ^8.3.3
dependencies: dependencies:
camelcase-css: 2.0.1 camelcase-css: 2.0.1
postcss: 8.4.12 postcss: 8.4.13
dev: true dev: true
/postcss-load-config/3.1.4_ysmyu6g5dtd6yanj6zrab4uqoy: /postcss-load-config/3.1.4_4jqnslpwnj4ifyjfqbkuebd4fy:
resolution: resolution:
{ {
integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
@@ -4139,12 +4185,12 @@ packages:
optional: true optional: true
dependencies: dependencies:
lilconfig: 2.0.5 lilconfig: 2.0.5
postcss: 8.4.12 postcss: 8.4.13
ts-node: 10.7.0_3z6inmgn4ud4moqealnfxgbl2m ts-node: 10.7.0_3smuweqyuzdazdnyhhezld6mfa
yaml: 1.10.2 yaml: 1.10.2
dev: true dev: true
/postcss-nested/5.0.6_postcss@8.4.12: /postcss-nested/5.0.6_postcss@8.4.13:
resolution: resolution:
{ {
integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA== integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==
@@ -4153,7 +4199,7 @@ packages:
peerDependencies: peerDependencies:
postcss: ^8.2.14 postcss: ^8.2.14
dependencies: dependencies:
postcss: 8.4.12 postcss: 8.4.13
postcss-selector-parser: 6.0.10 postcss-selector-parser: 6.0.10
dev: true dev: true
@@ -4175,14 +4221,14 @@ packages:
} }
dev: true dev: true
/postcss/8.4.12: /postcss/8.4.13:
resolution: resolution:
{ {
integrity: sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== integrity: sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==
} }
engines: { node: ^10 || ^12 || >=14 } engines: { node: ^10 || ^12 || >=14 }
dependencies: dependencies:
nanoid: 3.3.1 nanoid: 3.3.4
picocolors: 1.0.0 picocolors: 1.0.0
source-map-js: 1.0.2 source-map-js: 1.0.2
dev: true dev: true
@@ -4195,7 +4241,7 @@ packages:
engines: { node: '>= 0.8.0' } engines: { node: '>= 0.8.0' }
dev: true dev: true
/prettier-plugin-svelte/2.7.0_sqtt6dzjlskmywoml5ykunxlce: /prettier-plugin-svelte/2.7.0_kkjbqzpydplecjtkxrgomroeru:
resolution: resolution:
{ {
integrity: sha512-fQhhZICprZot2IqEyoiUYLTRdumULGRvw0o4dzl5jt0jfzVWdGqeYW27QTWAeXhoupEZJULmNoH3ueJwUWFLIA== integrity: sha512-fQhhZICprZot2IqEyoiUYLTRdumULGRvw0o4dzl5jt0jfzVWdGqeYW27QTWAeXhoupEZJULmNoH3ueJwUWFLIA==
@@ -4205,13 +4251,13 @@ packages:
svelte: ^3.2.0 svelte: ^3.2.0
dependencies: dependencies:
prettier: 2.6.2 prettier: 2.6.2
svelte: 3.47.0 svelte: 3.48.0
dev: true dev: true
/prettier-plugin-tailwindcss/0.1.10_prettier@2.6.2: /prettier-plugin-tailwindcss/0.1.11_prettier@2.6.2:
resolution: resolution:
{ {
integrity: sha512-ooDGNuXUjgCXfShliVYQ6+0iXqUFXn+zdNInPe0WZN9qINt9srbLGFGY5jeVL4MXtY20/4S8JaBcd8l6N6NfCQ== integrity: sha512-a28+1jvpIZQdZ/W97wOXb6VqI762MKE/TxMMuibMEHhyYsSxQA8Ek30KObd5kJI2HF1ldtSYprFayXJXi3pz8Q==
} }
engines: { node: '>=12.17.0' } engines: { node: '>=12.17.0' }
peerDependencies: peerDependencies:
@@ -4513,7 +4559,7 @@ packages:
integrity: sha512-PwDt186XaL3QN5qXj/H9DGyHhP3/RYYgZZwqBv9Tv8rsAaiwFH1IsJJlcgD37J7UW5a6O67qX0KWKS3/pu0m4w== integrity: sha512-PwDt186XaL3QN5qXj/H9DGyHhP3/RYYgZZwqBv9Tv8rsAaiwFH1IsJJlcgD37J7UW5a6O67qX0KWKS3/pu0m4w==
} }
dependencies: dependencies:
tslib: 2.3.1 tslib: 2.4.0
dev: true dev: true
/sade/1.7.4: /sade/1.7.4:
@@ -4698,14 +4744,6 @@ packages:
engines: { node: '>=0.10.0' } engines: { node: '>=0.10.0' }
dev: true dev: true
/source-map/0.7.3:
resolution:
{
integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
}
engines: { node: '>= 8' }
dev: true
/sourcemap-codec/1.4.8: /sourcemap-codec/1.4.8:
resolution: resolution:
{ {
@@ -4878,24 +4916,24 @@ packages:
engines: { node: '>= 0.4' } engines: { node: '>= 0.4' }
dev: true dev: true
/svelte-check/2.7.0_cp6olp7pwsfaq5mjijwt65d6uy: /svelte-check/2.7.1_f2ke6qjyzu5axsjd6yk3u4tn7a:
resolution: resolution:
{ {
integrity: sha512-GrvG24j0+i8AOm0k0KyJ6Dqc+TAR2yzB7rtS4nljHStunVxCTr/1KYlv4EsOeoqtHLzeWMOd5D2O6nDdP/yw4A== integrity: sha512-vHVu2+SQ6ibt77iTQaq2oiOjBgGL48qqcg0ZdEOsP5pPOjgeyR9QbnaEdzdBs9nsVYBc/42haKtzb2uFqS8GVw==
} }
hasBin: true hasBin: true
peerDependencies: peerDependencies:
svelte: ^3.24.0 svelte: ^3.24.0
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.13
chokidar: 3.5.3 chokidar: 3.5.3
fast-glob: 3.2.11 fast-glob: 3.2.11
import-fresh: 3.3.0 import-fresh: 3.3.0
picocolors: 1.0.0 picocolors: 1.0.0
sade: 1.7.4 sade: 1.7.4
source-map: 0.7.3 svelte: 3.48.0
svelte: 3.47.0 svelte-preprocess: 4.10.6_nq4dx2skq5drra53vttuo4lltu
svelte-preprocess: 4.10.6_igaqrb5onrwvsmrrc32h4m72ha typescript: 4.6.4
typescript: 4.6.3
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- coffeescript - coffeescript
@@ -4909,7 +4947,7 @@ packages:
- sugarss - sugarss
dev: true dev: true
/svelte-hmr/0.14.9_svelte@3.47.0: /svelte-hmr/0.14.9_svelte@3.48.0:
resolution: resolution:
{ {
integrity: sha512-bKE9+4qb4sAnA+TKHiYurUl970rjA0XmlP9TEP7K/ncyWz3m81kA4HOgmlZK/7irGK7gzZlaPDI3cmf8fp/+tg== integrity: sha512-bKE9+4qb4sAnA+TKHiYurUl970rjA0XmlP9TEP7K/ncyWz3m81kA4HOgmlZK/7irGK7gzZlaPDI3cmf8fp/+tg==
@@ -4917,17 +4955,17 @@ packages:
peerDependencies: peerDependencies:
svelte: '>=3.19.0' svelte: '>=3.19.0'
dependencies: dependencies:
svelte: 3.47.0 svelte: 3.48.0
dev: true dev: true
/svelte-kit-cookie-session/2.1.3: /svelte-kit-cookie-session/2.1.4:
resolution: resolution:
{ {
integrity: sha512-7Xk3CNbpLAi1KodlsV5W5jULQ2NxQunaXtAYqAuzIEXIq2EwC4oDa25kdmHjNe33epV0t4r0WwxZOuSdJPsapg== integrity: sha512-z/ckxHWguYyy66UqfId4Lu+A77ft/3mV5oozbRTI9bnQY0tyJfns/SM0ikdkj7OV5GiI8kq7GSOGvajWwyGeZw==
} }
dev: false dev: false
/svelte-preprocess/4.10.6_igaqrb5onrwvsmrrc32h4m72ha: /svelte-preprocess/4.10.6_nq4dx2skq5drra53vttuo4lltu:
resolution: resolution:
{ {
integrity: sha512-I2SV1w/AveMvgIQlUF/ZOO3PYVnhxfcpNyGt8pxpUVhPfyfL/CZBkkw/KPfuFix5FJ9TnnNYMhACK3DtSaYVVQ== integrity: sha512-I2SV1w/AveMvgIQlUF/ZOO3PYVnhxfcpNyGt8pxpUVhPfyfL/CZBkkw/KPfuFix5FJ9TnnNYMhACK3DtSaYVVQ==
@@ -4975,11 +5013,11 @@ packages:
'@types/sass': 1.16.1 '@types/sass': 1.16.1
detect-indent: 6.1.0 detect-indent: 6.1.0
magic-string: 0.25.7 magic-string: 0.25.7
postcss: 8.4.12 postcss: 8.4.13
sorcery: 0.10.0 sorcery: 0.10.0
strip-indent: 3.0.0 strip-indent: 3.0.0
svelte: 3.47.0 svelte: 3.48.0
typescript: 4.6.3 typescript: 4.6.4
dev: true dev: true
/svelte-select/4.4.7: /svelte-select/4.4.7:
@@ -4989,25 +5027,25 @@ packages:
} }
dev: true dev: true
/svelte/3.47.0: /svelte/3.48.0:
resolution: resolution:
{ {
integrity: sha512-4JaJp3HEoTCGARRWZQIZDUanhYv0iyoHikklVHVLH9xFE9db22g4TDv7CPeNA8HD1JgjXI1vlhR1JZvvhaTu2Q== integrity: sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ==
} }
engines: { node: '>= 8' } engines: { node: '>= 8' }
dev: true dev: true
/sveltekit-i18n/2.1.2_svelte@3.47.0: /sveltekit-i18n/2.2.1_svelte@3.48.0:
resolution: resolution:
{ {
integrity: sha512-s5YxcbNd2EWNZaZR1A4Drt8s53E4fpUkN4XIWd3VRpw1pihZVWssqmBW1qkjQ6AB0kiu1Qwule+vt1HkbQOjrg== integrity: sha512-1CyaRN6dBvp467JjBdji+nJf+7pZ3myFu+2YaCuGSAt09Cvt5ndfRbzy+aAd5WJdk6Lu/hnPEE7ZZFauTbDRNw==
} }
peerDependencies: peerDependencies:
svelte: ^3.x svelte: ^3.x
dependencies: dependencies:
'@sveltekit-i18n/base': 1.1.1_svelte@3.47.0 '@sveltekit-i18n/base': 1.2.1_svelte@3.48.0
'@sveltekit-i18n/parser-default': 1.0.3 '@sveltekit-i18n/parser-default': 1.0.3
svelte: 3.47.0 svelte: 3.48.0
dev: true dev: true
/table/6.7.2: /table/6.7.2:
@@ -5057,10 +5095,10 @@ packages:
normalize-path: 3.0.0 normalize-path: 3.0.0
object-hash: 3.0.0 object-hash: 3.0.0
picocolors: 1.0.0 picocolors: 1.0.0
postcss: 8.4.12 postcss: 8.4.13
postcss-js: 4.0.0_postcss@8.4.12 postcss-js: 4.0.0_postcss@8.4.13
postcss-load-config: 3.1.4_ysmyu6g5dtd6yanj6zrab4uqoy postcss-load-config: 3.1.4_4jqnslpwnj4ifyjfqbkuebd4fy
postcss-nested: 5.0.6_postcss@8.4.12 postcss-nested: 5.0.6_postcss@8.4.13
postcss-selector-parser: 6.0.10 postcss-selector-parser: 6.0.10
postcss-value-parser: 4.2.0 postcss-value-parser: 4.2.0
quick-lru: 5.1.1 quick-lru: 5.1.1
@@ -5133,7 +5171,7 @@ packages:
engines: { node: '>=0.10.0' } engines: { node: '>=0.10.0' }
dev: true dev: true
/ts-node/10.7.0_3z6inmgn4ud4moqealnfxgbl2m: /ts-node/10.7.0_3smuweqyuzdazdnyhhezld6mfa:
resolution: resolution:
{ {
integrity: sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== integrity: sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==
@@ -5155,14 +5193,14 @@ packages:
'@tsconfig/node12': 1.0.9 '@tsconfig/node12': 1.0.9
'@tsconfig/node14': 1.0.1 '@tsconfig/node14': 1.0.1
'@tsconfig/node16': 1.0.2 '@tsconfig/node16': 1.0.2
'@types/node': 17.0.25 '@types/node': 17.0.34
acorn: 8.5.0 acorn: 8.5.0
acorn-walk: 8.2.0 acorn-walk: 8.2.0
arg: 4.1.3 arg: 4.1.3
create-require: 1.1.1 create-require: 1.1.1
diff: 4.0.2 diff: 4.0.2
make-error: 1.3.6 make-error: 1.3.6
typescript: 4.6.3 typescript: 4.6.4
v8-compile-cache-lib: 3.0.0 v8-compile-cache-lib: 3.0.0
yn: 3.1.1 yn: 3.1.1
dev: true dev: true
@@ -5173,14 +5211,14 @@ packages:
integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
} }
/tslib/2.3.1: /tslib/2.4.0:
resolution: resolution:
{ {
integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
} }
dev: true dev: true
/tsutils/3.21.0_typescript@4.6.3: /tsutils/3.21.0_typescript@4.6.4:
resolution: resolution:
{ {
integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
@@ -5190,7 +5228,7 @@ packages:
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
dependencies: dependencies:
tslib: 1.14.1 tslib: 1.14.1
typescript: 4.6.3 typescript: 4.6.4
dev: true dev: true
/tweetnacl/0.14.5: /tweetnacl/0.14.5:
@@ -5223,10 +5261,10 @@ packages:
engines: { node: '>=10' } engines: { node: '>=10' }
dev: true dev: true
/typescript/4.6.3: /typescript/4.6.4:
resolution: resolution:
{ {
integrity: sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== integrity: sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
} }
engines: { node: '>=4.2.0' } engines: { node: '>=4.2.0' }
hasBin: true hasBin: true
@@ -5303,7 +5341,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
esbuild: 0.14.34 esbuild: 0.14.34
postcss: 8.4.12 postcss: 8.4.13
resolve: 1.22.0 resolve: 1.22.0
rollup: 2.61.1 rollup: 2.61.1
optionalDependencies: optionalDependencies:

View File

@@ -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;

View File

@@ -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;

View 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;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Minio" ADD COLUMN "apiFqdn" TEXT;

View File

@@ -20,6 +20,7 @@ model Setting {
proxyHash String? proxyHash String?
isAutoUpdateEnabled Boolean @default(false) isAutoUpdateEnabled Boolean @default(false)
isDNSCheckEnabled Boolean @default(true) isDNSCheckEnabled Boolean @default(true)
isTraefikUsed Boolean @default(true)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
@@ -322,6 +323,7 @@ model PlausibleAnalytics {
postgresqlDatabase String postgresqlDatabase String
postgresqlPublicPort Int? postgresqlPublicPort Int?
secretKeyBase String? secretKeyBase String?
scriptName String @default("plausible.js")
serviceId String @unique serviceId String @unique
service Service @relation(fields: [serviceId], references: [id]) service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -333,6 +335,7 @@ model Minio {
rootUser String rootUser String
rootUserPassword String rootUserPassword String
publicPort Int? publicPort Int?
apiFqdn String?
serviceId String @unique serviceId String @unique
service Service @relation(fields: [serviceId], references: [id]) service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -352,6 +355,9 @@ model Wordpress {
id String @id @default(cuid()) id String @id @default(cuid())
extraConfig String? extraConfig String?
tablePrefix String? tablePrefix String?
ownMysql Boolean @default(false)
mysqlHost String?
mysqlPort Int?
mysqlUser String mysqlUser String
mysqlPassword String mysqlPassword String
mysqlRootUser String mysqlRootUser String

View File

@@ -5,6 +5,12 @@ const prisma = new PrismaClient();
const crypto = require('crypto'); const crypto = require('crypto');
const generator = require('generate-password'); const generator = require('generate-password');
const cuid = require('cuid'); const cuid = require('cuid');
const compare = require('compare-versions');
const { version } = require('../package.json');
const child = require('child_process');
const util = require('util');
const algorithm = 'aes-256-ctr';
function generatePassword(length = 24) { function generatePassword(length = 24) {
return generator.generate({ return generator.generate({
@@ -13,7 +19,7 @@ function generatePassword(length = 24) {
strict: true strict: true
}); });
} }
const algorithm = 'aes-256-ctr'; const asyncExecShell = util.promisify(child.exec);
async function main() { async function main() {
// Enable registration for the first user // Enable registration for the first user
@@ -64,6 +70,56 @@ async function main() {
} }
}); });
} }
if (settings.isTraefikUsed) {
// Force stop Coolify Proxy, as it had a bug in < 2.9.2. TrustProxy + api.insecure
try {
const { stdout } = await asyncExecShell(
`docker inspect coolify-proxy --format '{{json .Config.Cmd}}'`
);
if (
!stdout
.replaceAll('[', '')
.replaceAll(']', '')
.replaceAll('"', '')
.replace('\n', '')
.split(',')
.includes('--entrypoints.web.forwardedHeaders.insecure=true')
) {
console.log('Reconfiguring Coolify Proxy (Traefik)...');
await asyncExecShell(`docker stop -t 0 coolify-proxy && docker rm coolify-proxy`);
const { stdout: Config } = await asyncExecShell(
`docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
const ip = JSON.parse(Config)[0].Gateway;
await asyncExecShell(
`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" \
--name coolify-proxy \
-d traefik:v2.6 \
--entrypoints.web.address=:80 \
--entrypoints.web.forwardedHeaders.insecure=true \
--entrypoints.websecure.address=:443 \
--entrypoints.websecure.forwardedHeaders.insecure=true \
--providers.docker=true \
--providers.docker.exposedbydefault=false \
--providers.http.endpoint=http://coolify:3000/webhooks/traefik/main.json \
--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`
);
}
} catch (error) {
console.log(error);
}
}
} }
main() main()
.catch((e) => { .catch((e) => {

View File

@@ -3,7 +3,6 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Coolify</title>
%svelte.head% %svelte.head%
</head> </head>
<body> <body>

View File

@@ -405,7 +405,72 @@ export function setDefaultBaseImage(buildPack) {
label: 'webdevops/php-nginx:7.1-alpine' 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 = { let payload = {
baseImage: null, baseImage: null,
baseBuildImage: null, baseBuildImage: null,
@@ -425,7 +490,8 @@ export function setDefaultBaseImage(buildPack) {
payload.baseBuildImages = nodeVersions; payload.baseBuildImages = nodeVersions;
} }
if (buildPack === 'python') { if (buildPack === 'python') {
payload.baseImage = 'python:3-alpine'; payload.baseImage = 'python:3.10-alpine';
payload.baseImages = pythonVersions;
} }
if (buildPack === 'rust') { if (buildPack === 'rust') {
payload.baseImage = 'rust:latest'; payload.baseImage = 'rust:latest';

View File

@@ -19,8 +19,8 @@ const createDockerfile = async (data, imageforBuild): Promise<void> => {
export default async function (data) { export default async function (data) {
try { try {
const { baseImage, baseBuildImage } = data; const { baseImage, baseBuildImage } = data;
await buildCacheImageWithNode(data, baseImage); await buildCacheImageWithNode(data, baseBuildImage);
await createDockerfile(data, baseBuildImage); await createDockerfile(data, baseImage);
await buildImage(data); await buildImage(data);
} catch (error) { } catch (error) {
throw error; throw error;

View File

@@ -96,12 +96,16 @@ export const getUserDetails = async (
const userId = event?.locals?.session?.data?.userId || null; const userId = event?.locals?.session?.data?.userId || null;
let permission = 'read'; let permission = 'read';
if (teamId && userId) { if (teamId && userId) {
const data = await db.prisma.permission.findFirst({ try {
where: { teamId, userId }, const data = await db.prisma.permission.findFirst({
select: { permission: true }, where: { teamId, userId },
rejectOnNotFound: true select: { permission: true },
}); rejectOnNotFound: true
if (data.permission) permission = data.permission; });
if (data.permission) permission = data.permission;
} catch (error) {
console.log(error);
}
} }
const payload = { const payload = {

View 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>

View File

@@ -219,6 +219,18 @@ export const supportedServiceTypesAndVersions = [
ports: { ports: {
main: 3000 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
// }
// }
} }
]; ];

View File

@@ -3,13 +3,88 @@
</script> </script>
<svg <svg
class={isAbsolute ? 'absolute top-0 left-0 -m-10 h-20 w-20' : 'mx-auto w-8 h-8'} viewBox="0 0 128 128"
id="Layer_1" class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 361.67651 499.33603"
><path
d="M203.77731,148.85754c-10.8147-12.762-20.13269-25.8139-22.02478-28.49224a.426.426,0,0,0-.70032.00006c-1.89172,2.6784-11.20758,15.73022-22.02191,28.49218-92.69141,118.085,14.62982,197.75507,14.62982,197.75507l.87.60461c.8136,12.32624,2.83508,30.041,2.83508,30.041H185.442s2.01282-17.63849,2.83-29.96106l.87549-.68451S296.46774,266.94257,203.77731,148.85754ZM181.404,344.88123h-.001s-4.811-4.10383-6.10962-6.16l-.01172-.22131,5.81946-128.56055a.30281.30281,0,0,1,.605,0l5.81946,128.56036-.01135.22065C186.21652,340.77625,181.404,344.88123,181.404,344.88123Z"
fill="#00684a"
/></svg
> >
<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
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
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>

View File

@@ -4,6 +4,6 @@
<img <img
alt="plausible logo" 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" src="/plausible.png"
/> />

View File

@@ -128,9 +128,7 @@ export function findBuildPack(pack, packageManager = 'npm') {
if (pack === 'astro') { if (pack === 'astro') {
return { return {
...metaData, ...metaData,
installCommand: `yarn install`, ...defaultBuildAndDeploy(packageManager),
buildCommand: `yarn build`,
startCommand: null,
publishDirectory: `dist`, publishDirectory: `dist`,
port: 80 port: 80
}; };
@@ -138,9 +136,7 @@ export function findBuildPack(pack, packageManager = 'npm') {
if (pack === 'eleventy') { if (pack === 'eleventy') {
return { return {
...metaData, ...metaData,
installCommand: `yarn install`, ...defaultBuildAndDeploy(packageManager),
buildCommand: `yarn build`,
startCommand: null,
publishDirectory: `_site`, publishDirectory: `_site`,
port: 80 port: 80
}; };

View File

@@ -51,10 +51,12 @@ export async function isSecretExists({
export async function isDomainConfigured({ export async function isDomainConfigured({
id, id,
fqdn fqdn,
checkOwn = false
}: { }: {
id: string; id: string;
fqdn: string; fqdn: string;
checkOwn?: boolean;
}): Promise<boolean> { }): Promise<boolean> {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const nakedDomain = domain.replace('www.', ''); const nakedDomain = domain.replace('www.', '');
@@ -72,12 +74,15 @@ export async function isDomainConfigured({
where: { where: {
OR: [ OR: [
{ fqdn: { endsWith: `//${nakedDomain}` } }, { 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 } select: { fqdn: true }
}); });
const coolifyFqdn = await prisma.setting.findFirst({ const coolifyFqdn = await prisma.setting.findFirst({
where: { where: {
OR: [ OR: [

View File

@@ -28,7 +28,7 @@ if (!dev) {
} }
export const prisma = new PrismaClient({ export const prisma = new PrismaClient({
errorFormat: 'pretty', errorFormat: 'minimal',
rejectOnNotFound: false rejectOnNotFound: false
}); });
@@ -305,6 +305,12 @@ export async function getFreePort() {
select: { mysqlPublicPort: true } select: { mysqlPublicPort: true }
}) })
).map((a) => a.mysqlPublicPort); ).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 }); return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
} }

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getEngine } from '$lib/common'; import { asyncExecShell, getEngine } from '$lib/common';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import { startCoolifyProxy } from '$lib/haproxy'; import { startCoolifyProxy, startTraefikProxy } from '$lib/haproxy';
import { getDatabaseImage } from '.'; import { getDatabaseImage } from '.';
import { prisma } from './common'; import { prisma } from './common';
import type { DestinationDocker, Service, Application, Prisma } from '@prisma/client'; 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 } }); 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; return destination.id;
} }
export async function removeDestination({ id }: Pick<DestinationDocker, 'id'>): Promise<void> { 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) { if (destination.isCoolifyProxyUsed) {
const host = getEngine(destination.engine); const host = getEngine(destination.engine);
const { network } = destination; const { network } = destination;
const settings = await prisma.setting.findFirst();
const containerName = settings.isTraefikUsed ? 'coolify-proxy' : 'coolify-haproxy';
const { stdout: found } = await asyncExecShell( 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) { if (found) {
await asyncExecShell( 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}`); await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`);
} }

View File

@@ -67,16 +67,10 @@ export async function getSource({
return body; return body;
} }
export async function addGitHubSource({ id, teamId, type, name, htmlUrl, apiUrl, organization }) { export async function addGitHubSource({ id, teamId, type, name, htmlUrl, apiUrl, organization }) {
await prisma.gitSource.update({ return await prisma.gitSource.update({
where: { id }, where: { id },
data: { type, name, htmlUrl, apiUrl, organization } data: { type, name, htmlUrl, apiUrl, organization }
}); });
return await prisma.githubApp.create({
data: {
teams: { connect: { id: teamId } },
gitSource: { connect: { id } }
}
});
} }
export async function addGitLabSource({ export async function addGitLabSource({
id, id,

View File

@@ -18,7 +18,7 @@ const include: Prisma.ServiceInclude = {
hasura: true, hasura: true,
fider: true fider: true
}; };
export async function listServicesWithIncludes() { export async function listServicesWithIncludes(): Promise<Service[]> {
return await prisma.service.findMany({ return await prisma.service.findMany({
include, include,
orderBy: { createdAt: 'desc' } orderBy: { createdAt: 'desc' }
@@ -329,7 +329,8 @@ export async function updatePlausibleAnalyticsService({
email, email,
exposePort, exposePort,
username, username,
name name,
scriptName
}: { }: {
id: string; id: string;
fqdn: string; fqdn: string;
@@ -337,8 +338,12 @@ export async function updatePlausibleAnalyticsService({
name: string; name: string;
email: string; email: string;
username: string; username: string;
scriptName: string;
}): Promise<void> { }): Promise<void> {
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } }); await prisma.plausibleAnalytics.update({
where: { serviceId: id },
data: { email, username, scriptName }
});
await prisma.service.update({ where: { id }, data: { name, fqdn, exposePort } }); await prisma.service.update({ where: { id }, data: { name, fqdn, exposePort } });
} }
@@ -355,7 +360,24 @@ export async function updateService({
}): Promise<Service> { }): Promise<Service> {
return await prisma.service.update({ where: { id }, data: { fqdn, name, exposePort } }); 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({ export async function updateFiderService({
id, id,
fqdn, fqdn,
@@ -414,22 +436,46 @@ export async function updateWordpress({
name, name,
exposePort, exposePort,
mysqlDatabase, mysqlDatabase,
extraConfig extraConfig,
mysqlHost,
mysqlPort,
mysqlUser,
mysqlPassword
}: { }: {
id: string; id: string;
fqdn: string; fqdn: string;
name: string; name: string;
exposePort?: number; exposePort?: number;
ownMysql: boolean;
mysqlDatabase: string; mysqlDatabase: string;
extraConfig: string; extraConfig: string;
mysqlHost?: string;
mysqlPort?: number;
mysqlUser?: string;
mysqlPassword?: string;
}): Promise<Service> { }): Promise<Service> {
mysqlPassword = encrypt(mysqlPassword);
return await prisma.service.update({ return await prisma.service.update({
where: { id }, where: { id },
data: { fqdn, name, exposePort, 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, id,
publicPort publicPort
}: { }: {

View File

@@ -55,6 +55,9 @@ frontend http
http-request redirect location {{{redirectValue}}} code ${ http-request redirect location {{{redirectValue}}} code ${
dev ? 302 : 301 dev ? 302 : 301
} if { req.hdr(host) -i {{redirectTo}} } } 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}} {{/services}}
{{#coolify}} {{#coolify}}
@@ -218,7 +221,15 @@ export async function configureHAProxy(): Promise<void> {
const services = await listServicesWithIncludes(); const services = await listServicesWithIncludes();
for (const service of services) { for (const service of services) {
const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service; const {
fqdn,
id,
type,
destinationDocker,
destinationDockerId,
updatedAt,
plausibleAnalytics
} = service;
if (destinationDockerId) { if (destinationDockerId) {
const { engine } = destinationDocker; const { engine } = destinationDocker;
const found = supportedServiceTypesAndVersions.find((a) => a.name === type); const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
@@ -232,6 +243,12 @@ export async function configureHAProxy(): Promise<void> {
const isWWW = fqdn.includes('www.'); const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`; const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
if (isRunning) { if (isRunning) {
// Plausible Analytics custom script
let scriptName = false;
if (type === 'plausibleanalytics' && plausibleAnalytics.scriptName !== 'plausible.js') {
scriptName = plausibleAnalytics.scriptName;
}
data.services.push({ data.services.push({
id, id,
port, port,
@@ -241,7 +258,8 @@ export async function configureHAProxy(): Promise<void> {
isHttps, isHttps,
redirectValue, redirectValue,
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain, redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
updatedAt: updatedAt.getTime() updatedAt: updatedAt.getTime(),
scriptName
}); });
} }
} }

View File

@@ -3,12 +3,22 @@ import { asyncExecShell, getEngine } from '$lib/common';
import got, { type Got, type Response } from 'got'; import got, { type Got, type Response } from 'got';
import * as db from '$lib/database'; import * as db from '$lib/database';
import type { DestinationDocker } from '@prisma/client'; 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'; const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
export const defaultProxyImage = `coolify-haproxy-alpine:latest`; export const defaultProxyImage = `coolify-haproxy-alpine:latest`;
export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`; export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`;
export const defaultProxyImageHttp = `coolify-haproxy-http-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> { export async function haproxyInstance(): Promise<Got> {
const { proxyPassword } = await db.listSettings(); const { proxyPassword } = await db.listSettings();
@@ -98,13 +108,21 @@ export async function checkHAProxy(haproxy?: Got): Promise<void> {
} }
export async function stopTcpHttpProxy( export async function stopTcpHttpProxy(
id: string,
destinationDocker: DestinationDocker, destinationDocker: DestinationDocker,
publicPort: number publicPort: number,
forceName: string = null
): Promise<{ stdout: string; stderr: string } | Error> { ): Promise<{ stdout: string; stderr: string } | Error> {
const { engine } = destinationDocker; const { engine } = destinationDocker;
const host = getEngine(engine); 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); const found = await checkContainer(engine, containerName);
try { try {
if (found) { if (found) {
return await asyncExecShell( return await asyncExecShell(
@@ -115,12 +133,77 @@ export async function stopTcpHttpProxy(
return error; return error;
} }
} }
export async function startTcpProxy( export async function startTraefikTCPProxy(
destinationDocker: DestinationDocker, destinationDocker: DestinationDocker,
id: string, id: string,
publicPort: number, publicPort: number,
privatePort: 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}`,
`--entryPoints.tcp.forwardedHeaders.insecure=true`,
`--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp&address=${dependentId}`,
'--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> { ): Promise<{ stdout: string; stderr: string } | Error> {
const { network, engine } = destinationDocker; const { network, engine } = destinationDocker;
const host = getEngine(engine); const host = getEngine(engine);
@@ -128,7 +211,6 @@ export async function startTcpProxy(
const containerName = `haproxy-for-${publicPort}`; const containerName = `haproxy-for-${publicPort}`;
const found = await checkContainer(engine, containerName, true); const found = await checkContainer(engine, containerName, true);
const foundDependentContainer = await checkContainer(engine, id, true); const foundDependentContainer = await checkContainer(engine, id, true);
try { try {
if (foundDependentContainer && !found) { if (foundDependentContainer && !found) {
const { stdout: Config } = await asyncExecShell( const { stdout: Config } = await asyncExecShell(
@@ -136,9 +218,7 @@ export async function startTcpProxy(
); );
const ip = JSON.parse(Config)[0].Gateway; const ip = JSON.parse(Config)[0].Gateway;
return await asyncExecShell( 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} ${ `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}`
volume ? `-v ${volume}` : ''
} -d coollabsio/${defaultProxyImageTcp}`
); );
} }
if (!foundDependentContainer && found) { if (!foundDependentContainer && found) {
@@ -151,6 +231,76 @@ 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}`,
`--entryPoints.http.forwardedHeaders.insecure=true`,
`--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( export async function startHttpProxy(
destinationDocker: DestinationDocker, destinationDocker: DestinationDocker,
id: string, id: string,
@@ -197,10 +347,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}` `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.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
} }
await configureNetworkCoolifyProxy(engine); 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" \
--name coolify-proxy \
-d ${defaultTraefikImage} \
--entrypoints.web.address=:80 \
--entrypoints.web.forwardedHeaders.insecure=true \
--entrypoints.websecure.address=:443 \
--entrypoints.websecure.forwardedHeaders.insecure=true \
--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> { export async function isContainerExited(engine: string, containerName: string): Promise<boolean> {
let isExited = false; let isExited = false;
const host = getEngine(engine); const host = getEngine(engine);
@@ -245,6 +435,21 @@ export async function checkContainer(
return containerFound; 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( export async function stopCoolifyProxy(
engine: string engine: string
): Promise<{ stdout: string; stderr: string } | Error> { ): Promise<{ stdout: string; stderr: string } | Error> {
@@ -263,6 +468,24 @@ export async function stopCoolifyProxy(
return error; 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> { export async function configureNetworkCoolifyProxy(engine: string): Promise<void> {
const host = getEngine(engine); const host = getEngine(engine);
@@ -279,3 +502,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`
);
}
}
}

View File

@@ -1,4 +1,4 @@
import { ErrorHandler } from '$lib/database'; import { ErrorHandler, prisma } from '$lib/database';
import { configureHAProxy } from '$lib/haproxy/configuration'; import { configureHAProxy } from '$lib/haproxy/configuration';
export default async function (): Promise<void | { export default async function (): Promise<void | {
@@ -6,7 +6,10 @@ export default async function (): Promise<void | {
body: { message: string; error: string }; body: { message: string; error: string };
}> { }> {
try { try {
return await configureHAProxy(); const settings = await prisma.setting.findFirst();
if (!settings.isTraefikUsed) {
return await configureHAProxy();
}
} catch (error) { } catch (error) {
return ErrorHandler(error.response?.body || error); return ErrorHandler(error.response?.body || error);
} }

View File

@@ -1,5 +1,16 @@
import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database'; 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 | { export default async function (): Promise<void | {
status: number; status: number;
@@ -7,12 +18,23 @@ export default async function (): Promise<void | {
}> { }> {
try { try {
// Coolify Proxy // Coolify Proxy
const engine = '/var/run/docker.sock';
const settings = await prisma.setting.findFirst();
const localDocker = await prisma.destinationDocker.findFirst({ const localDocker = await prisma.destinationDocker.findFirst({
where: { engine: '/var/run/docker.sock' } where: { engine, network: 'coolify' }
}); });
if (localDocker && localDocker.isCoolifyProxyUsed) { 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 // TCP Proxies
const databasesWithPublicPort = await prisma.database.findMany({ const databasesWithPublicPort = await prisma.database.findMany({
where: { publicPort: { not: null } }, where: { publicPort: { not: null } },
@@ -21,8 +43,16 @@ export default async function (): Promise<void | {
for (const database of databasesWithPublicPort) { for (const database of databasesWithPublicPort) {
const { destinationDockerId, destinationDocker, publicPort, id } = database; const { destinationDockerId, destinationDocker, publicPort, id } = database;
if (destinationDockerId) { if (destinationDockerId) {
const { privatePort } = generateDatabaseConfiguration(database); if (destinationDocker.isCoolifyProxyUsed) {
await startTcpProxy(destinationDocker, id, publicPort, privatePort); 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({ const wordpressWithFtp = await prisma.wordpress.findMany({
@@ -33,20 +63,38 @@ export default async function (): Promise<void | {
const { service, ftpPublicPort } = ftp; const { service, ftpPublicPort } = ftp;
const { destinationDockerId, destinationDocker, id } = service; const { destinationDockerId, destinationDocker, id } = service;
if (destinationDockerId) { 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 // HTTP Proxies
const minioInstances = await prisma.minio.findMany({ if (!settings.isTraefikUsed) {
where: { publicPort: { not: null } }, const minioInstances = await prisma.minio.findMany({
include: { service: { include: { destinationDocker: true } } } where: { publicPort: { not: null } },
}); include: { service: { include: { destinationDocker: true } } }
for (const minio of minioInstances) { });
const { service, publicPort } = minio; for (const minio of minioInstances) {
const { destinationDockerId, destinationDocker, id } = service; const { service, publicPort } = minio;
if (destinationDockerId) { const { destinationDockerId, destinationDocker, id } = service;
await startHttpProxy(destinationDocker, id, publicPort, 9000); if (destinationDockerId) {
if (destinationDocker.isCoolifyProxyUsed) {
await stopTcpHttpProxy(id, destinationDocker, publicPort, `${id}-${publicPort}`);
await startHttpProxy(destinationDocker, id, publicPort, 9000);
}
}
} }
} }
} catch (error) { } catch (error) {

View File

@@ -1,8 +1,12 @@
import { generateSSLCerts } from '$lib/letsencrypt'; import { generateSSLCerts } from '$lib/letsencrypt';
import { prisma } from '$lib/database';
export default async function (): Promise<void> { export default async function (): Promise<void> {
try { try {
return await generateSSLCerts(); const settings = await prisma.setting.findFirst();
if (!settings.isTraefikUsed) {
return await generateSSLCerts();
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
throw error; throw error;

View File

@@ -1,8 +1,12 @@
import { renewSSLCerts } from '$lib/letsencrypt'; import { renewSSLCerts } from '$lib/letsencrypt';
import { prisma } from '$lib/database';
export default async function (): Promise<void> { export default async function (): Promise<void> {
try { try {
return await renewSSLCerts(); const settings = await prisma.setting.findFirst();
if (!settings.isTraefikUsed) {
return await renewSSLCerts();
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
throw error; throw error;

3
src/lib/realtime.ts Normal file
View File

@@ -0,0 +1,3 @@
// import ioClient from 'socket.io-client';
// const socket = ioClient('http://localhost:3000');
// export const io = socket;

View File

@@ -12,3 +12,14 @@ export const features: Readable<{ latestVersion: string; beta: boolean }> = read
beta: browser && window.localStorage.getItem('beta') === 'true', beta: browser && window.localStorage.getItem('beta') === 'true',
latestVersion: browser && window.localStorage.getItem('latestVersion') 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
}
});

39
src/routes/_Trend.svelte Normal file
View 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}

View File

@@ -34,23 +34,30 @@
</script> </script>
<script> <script>
export let settings;
import '../tailwind.css'; import '../tailwind.css';
import { SvelteToast, toast } from '@zerodevx/svelte-toast'; import { SvelteToast, toast } from '@zerodevx/svelte-toast';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { fade } from 'svelte/transition';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { asyncSleep } from '$lib/components/common'; import { asyncSleep } from '$lib/components/common';
import { del, get, post } from '$lib/api'; import { del, get, post } from '$lib/api';
import { dev } from '$app/env'; import { dev } from '$app/env';
import { features } from '$lib/store'; import { features, isTraefikUsed } from '$lib/store';
let isUpdateAvailable = false; import { navigating } from '$app/stores';
import PageLoader from '$lib/components/PageLoader.svelte';
$isTraefikUsed = settings?.isTraefikUsed || false;
let isUpdateAvailable = false;
let updateStatus = { let updateStatus = {
found: false, found: false,
loading: false, loading: false,
success: null success: null
}; };
let latestVersion = 'latest'; let latestVersion = 'latest';
onMount(async () => { onMount(async () => {
if ($session.userId) { if ($session.userId) {
const overrideVersion = $features.latestVersion; const overrideVersion = $features.latestVersion;
@@ -78,6 +85,7 @@
} }
} }
}); });
async function logout() { async function logout() {
try { try {
await del(`/logout.json`, {}); await del(`/logout.json`, {});
@@ -128,16 +136,23 @@
<title>Coolify</title> <title>Coolify</title>
{#if !$session.whiteLabeled} {#if !$session.whiteLabeled}
<link rel="icon" href="/favicon.png" /> <link rel="icon" href="/favicon.png" />
{:else if $session.whiteLabelDetails.icon}
<link rel="icon" href={$session.whiteLabelDetails.icon} />
{/if} {/if}
</svelte:head> </svelte:head>
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} /> <SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
{#if $navigating}
<div out:fade={{ delay: 100 }}>
<PageLoader />
</div>
{/if}
{#if $session.userId} {#if $session.userId}
<nav class="nav-main"> <nav class="nav-main">
<div class="flex h-screen w-full flex-col items-center transition-all duration-100"> <div class="flex h-screen w-full flex-col items-center transition-all duration-100">
{#if !$session.whiteLabeled} {#if !$session.whiteLabeled}
<div class="my-4 h-10 w-10"><img src="/favicon.png" alt="coolLabs logo" /></div> <div class="my-4 h-10 w-10"><img src="/favicon.png" alt="coolLabs logo" /></div>
{/if} {/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 <a
sveltekit:prefetch sveltekit:prefetch
href="/" href="/"
@@ -222,7 +237,6 @@
<polyline points="10 15 13 18 10 21" /> <polyline points="10 15 13 18 10 21" />
</svg> </svg>
</a> </a>
<div class="border-t border-stone-700" />
<a <a
sveltekit:prefetch sveltekit:prefetch
href="/destinations" href="/destinations"
@@ -284,7 +298,6 @@
<path d="M4 12v6a8 3 0 0 0 16 0v-6" /> <path d="M4 12v6a8 3 0 0 0 16 0v-6" />
</svg> </svg>
</a> </a>
<div class="border-t border-stone-700" />
<a <a
sveltekit:prefetch sveltekit:prefetch
href="/services" href="/services"
@@ -423,7 +436,7 @@
{/if} {/if}
{/if} {/if}
</div> </div>
<div class="flex flex-col space-y-4 py-2"> <div class="flex flex-col space-y-2 py-2">
<a <a
sveltekit:prefetch sveltekit:prefetch
href="/iam" href="/iam"

View File

@@ -17,7 +17,7 @@
const endpoint = `/applications/${params.id}.json`; const endpoint = `/applications/${params.id}.json`;
const res = await fetch(endpoint); const res = await fetch(endpoint);
if (res.ok) { 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) { if (!application || Object.entries(application).length === 0) {
return { return {
status: 302, status: 302,
@@ -45,13 +45,10 @@
return { return {
props: { props: {
application, application,
isRunning,
isExited,
githubToken, githubToken,
gitlabToken gitlabToken
}, },
stuff: { stuff: {
isRunning,
application, application,
appId appId
} }
@@ -67,8 +64,6 @@
<script lang="ts"> <script lang="ts">
export let application; export let application;
export let isRunning;
export let isExited;
export let githubToken; export let githubToken;
export let gitlabToken; export let gitlabToken;
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
@@ -77,7 +72,7 @@
import Loading from '$lib/components/Loading.svelte'; import Loading from '$lib/components/Loading.svelte';
import { del, get, post } from '$lib/api'; import { del, get, post } from '$lib/api';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { gitTokens } from '$lib/store'; import { gitTokens, status } from '$lib/store';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { disabledButton } from '$lib/store'; import { disabledButton } from '$lib/store';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
@@ -135,17 +130,31 @@
} }
} }
async function getStatus() { async function getStatus() {
statusInterval = setInterval(async () => { if ($status.application.loading) return;
const data = await get(`/applications/${id}.json`); $status.application.loading = true;
isRunning = data.isRunning; const data = await get(`/applications/${id}/status.json`);
isExited = data.isExited; $status.application.isRunning = data.isRunning;
}, 1000); $status.application.isExited = data.isExited;
$status.application.loading = false;
$status.application.initialLoading = false;
} }
onDestroy(() => { onDestroy(() => {
$status.application.initialLoading = true;
clearInterval(statusInterval); clearInterval(statusInterval);
}); });
onMount(async () => { 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> </script>
@@ -153,16 +162,16 @@
{#if loading} {#if loading}
<Loading fullscreen cover /> <Loading fullscreen cover />
{:else} {:else}
{#if isExited} {#if $status.application.isExited}
<a <a
href={!$disabledButton ? `/applications/${id}/logs` : null} 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!" data-tooltip="Application exited with an error!"
sveltekit:prefetch sveltekit:prefetch
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6" class="h-6 w-6"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="currentcolor" stroke="currentcolor"
@@ -179,20 +188,43 @@
</svg> </svg>
</a> </a>
{/if} {/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 <button
on:click={stopApplication} on:click={stopApplication}
title="Stop application" title="Stop application"
type="submit" type="submit"
disabled={$disabledButton} 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 data-tooltip={$session.isAdmin
? $t('application.stop_application') ? $t('application.stop_application')
: $t('application.permission_denied_stop_application')} : $t('application.permission_denied_stop_application')}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6" class="h-6 w-6"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="currentColor" stroke="currentColor"
@@ -210,14 +242,14 @@
title="Rebuild application" title="Rebuild application"
type="submit" type="submit"
disabled={$disabledButton} 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 data-tooltip={$session.isAdmin
? 'Rebuild application' ? 'Rebuild application'
: 'You do not have permission to rebuild application.'} : 'You do not have permission to rebuild application.'}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6" class="h-6 w-6"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="currentColor" stroke="currentColor"
@@ -239,14 +271,14 @@
title="Build and start application" title="Build and start application"
type="submit" type="submit"
disabled={$disabledButton} 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 data-tooltip={$session.isAdmin
? 'Build and start application' ? 'Build and start application'
: 'You do not have permission to Build and start application.'} : 'You do not have permission to Build and start application.'}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6" class="h-6 w-6"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="currentColor" stroke="currentColor"
@@ -261,18 +293,18 @@
</form> </form>
{/if} {/if}
<div class="border border-coolgray-500 h-8" /> <div class="h-8 border border-coolgray-500" />
<a <a
href={!$disabledButton ? `/applications/${id}` : null} href={!$disabledButton ? `/applications/${id}` : null}
sveltekit:prefetch sveltekit:prefetch
class="hover:text-yellow-500 rounded" class="rounded hover:text-yellow-500"
class:text-yellow-500={$page.url.pathname === `/applications/${id}`} class:text-yellow-500={$page.url.pathname === `/applications/${id}`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}`} class:bg-coolgray-500={$page.url.pathname === `/applications/${id}`}
> >
<button <button
title="Configurations" title="Configurations"
disabled={$disabledButton} disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm" class="icons tooltip-bottom bg-transparent text-sm"
data-tooltip="Configurations" data-tooltip="Configurations"
> >
<svg <svg
@@ -301,19 +333,19 @@
<a <a
href={!$disabledButton ? `/applications/${id}/secrets` : null} href={!$disabledButton ? `/applications/${id}/secrets` : null}
sveltekit:prefetch 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:text-pink-500={$page.url.pathname === `/applications/${id}/secrets`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`} class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`}
> >
<button <button
title="Secret" title="Secret"
disabled={$disabledButton} disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm" class="icons tooltip-bottom bg-transparent text-sm"
data-tooltip="Secret" data-tooltip="Secret"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6" class="h-6 w-6"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="currentColor" stroke="currentColor"
@@ -333,19 +365,19 @@
<a <a
href={!$disabledButton ? `/applications/${id}/storage` : null} href={!$disabledButton ? `/applications/${id}/storage` : null}
sveltekit:prefetch 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:text-pink-500={$page.url.pathname === `/applications/${id}/storage`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storage`} class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storage`}
> >
<button <button
title="Persistent Storage" title="Persistent Storage"
disabled={$disabledButton} disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm" class="icons tooltip-bottom bg-transparent text-sm"
data-tooltip="Persistent Storage" data-tooltip="Persistent Storage"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6" class="h-6 w-6"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="currentColor" stroke="currentColor"
@@ -363,19 +395,19 @@
<a <a
href={!$disabledButton ? `/applications/${id}/previews` : null} href={!$disabledButton ? `/applications/${id}/previews` : null}
sveltekit:prefetch 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:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`} class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
> >
<button <button
title="Previews" title="Previews"
disabled={$disabledButton} disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm" class="icons tooltip-bottom bg-transparent text-sm"
data-tooltip="Previews" data-tooltip="Previews"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6" class="h-6 w-6"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="currentColor" stroke="currentColor"
@@ -392,18 +424,18 @@
</svg></button </svg></button
></a ></a
> >
<div class="border border-coolgray-500 h-8" /> <div class="h-8 border border-coolgray-500" />
<a <a
href={!$disabledButton && isRunning ? `/applications/${id}/logs` : null} href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
sveltekit:prefetch 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:text-sky-500={$page.url.pathname === `/applications/${id}/logs`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`} class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`}
> >
<button <button
title={$t('application.logs')} title={$t('application.logs')}
disabled={$disabledButton || !isRunning} disabled={$disabledButton || !$status.application.isRunning}
class="icons bg-transparent tooltip-bottom text-sm" class="icons tooltip-bottom bg-transparent text-sm"
data-tooltip={$t('application.logs')} data-tooltip={$t('application.logs')}
> >
<svg <svg
@@ -428,14 +460,14 @@
<a <a
href={!$disabledButton ? `/applications/${id}/logs/build` : null} href={!$disabledButton ? `/applications/${id}/logs/build` : null}
sveltekit:prefetch 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:text-red-500={$page.url.pathname === `/applications/${id}/logs/build`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs/build`} class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs/build`}
> >
<button <button
title="Build Logs" title="Build Logs"
disabled={$disabledButton} disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm" class="icons tooltip-bottom bg-transparent text-sm"
data-tooltip="Build Logs" data-tooltip="Build Logs"
> >
<svg <svg
@@ -460,7 +492,7 @@
</svg> </svg>
</button></a </button></a
> >
<div class="border border-coolgray-500 h-8" /> <div class="h-8 border border-coolgray-500" />
<button <button
on:click={() => deleteApplication(application.name)} on:click={() => deleteApplication(application.name)}
@@ -468,7 +500,7 @@
type="submit" type="submit"
disabled={!$session.isAdmin} disabled={!$session.isAdmin}
class:hover:text-red-500={$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 data-tooltip={$session.isAdmin
? $t('application.delete_application') ? $t('application.delete_application')
: $t('application.permission_denied_delete_application')} : $t('application.permission_denied_delete_application')}

View File

@@ -52,7 +52,7 @@ export const post: RequestHandler = async (event) => {
exposePort = Number(exposePort); exposePort = Number(exposePort);
if (exposePort < 1024 || exposePort > 65535) { if (exposePort < 1024 || exposePort > 65535) {
throw { message: `Expose Port needs to be between 1024 and 65535.` }; throw { message: `Exposed Port needs to be between 1024 and 65535.` };
} }
const publicPort = await getPort({ port: exposePort }); const publicPort = await getPort({ port: exposePort });

View File

@@ -19,10 +19,13 @@
<script lang="ts"> <script lang="ts">
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { gitTokens } from '$lib/store';
export let application; export let application;
export let appId; export let appId;
$gitTokens.githubToken = null;
import GithubRepositories from './_GithubRepositories.svelte'; import GithubRepositories from './_GithubRepositories.svelte';
import GitlabRepositories from './_GitlabRepositories.svelte'; import GitlabRepositories from './_GitlabRepositories.svelte';
</script> </script>

View File

@@ -1,7 +1,7 @@
import { getUserDetails } from '$lib/common'; import { asyncExecShell, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { ErrorHandler } 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 type { RequestHandler } from '@sveltejs/kit';
import { setDefaultConfiguration } from '$lib/buildPacks/common'; import { setDefaultConfiguration } from '$lib/buildPacks/common';
@@ -12,21 +12,14 @@ export const get: RequestHandler = async (event) => {
const { id } = event.params; const { id } = event.params;
const appId = process.env['COOLIFY_APP_ID']; const appId = process.env['COOLIFY_APP_ID'];
let isRunning = false;
let isExited = false;
let githubToken = event.locals.cookies?.githubToken || null; let githubToken = event.locals.cookies?.githubToken || null;
let gitlabToken = event.locals.cookies?.gitlabToken || null; let gitlabToken = event.locals.cookies?.gitlabToken || null;
try { try {
const application = await db.getApplication({ id, teamId }); 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 { return {
status: 200, status: 200,
body: { body: {
isRunning,
isExited,
application, application,
appId, appId,
githubToken, githubToken,

View File

@@ -4,8 +4,7 @@
if (stuff?.application?.id) { if (stuff?.application?.id) {
return { return {
props: { props: {
application: stuff.application, application: stuff.application
isRunning: stuff.isRunning
} }
}; };
} }
@@ -36,10 +35,9 @@
baseImages: Array<{ value: string; label: string }>; baseImages: Array<{ value: string; label: string }>;
baseBuildImages: Array<{ value: string; label: string }>; baseBuildImages: Array<{ value: string; label: string }>;
}; };
export let isRunning;
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import Select from 'svelte-select'; import Select from 'svelte-select';
import Explainer from '$lib/components/Explainer.svelte'; import Explainer from '$lib/components/Explainer.svelte';
@@ -50,13 +48,22 @@
import { get, post } from '$lib/api'; import { get, post } from '$lib/api';
import cuid from 'cuid'; import cuid from 'cuid';
import { browser } from '$app/env'; import { browser } from '$app/env';
import { disabledButton } from '$lib/store'; import { disabledButton, status } from '$lib/store';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
const { id } = $page.params; const { id } = $page.params;
let domainEl: HTMLInputElement; let domainEl: HTMLInputElement;
let loading = false; let loading = false;
let usageLoading = false;
let usage = {
MemUsage: 0,
CPUPerc: 0,
NetIO: 0
};
let usageInterval;
let forceSave = false; let forceSave = false;
let debug = application.settings.debug; let debug = application.settings.debug;
let previews = application.settings.previews; let previews = application.settings.previews;
@@ -67,6 +74,7 @@
let isNonWWWDomainOK = false; let isNonWWWDomainOK = false;
let isWWWDomainOK = false; let isWWWDomainOK = false;
$: isDisabled = !$session.isAdmin || $status.application.isRunning;
let wsgis = [ let wsgis = [
{ {
value: 'None', value: 'None',
@@ -78,15 +86,29 @@
} }
]; ];
function containerClass() { 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 handleSubmit();
} }
}
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
application.fqdn = `http://${cuid()}.demo.coolify.io`;
}
onMount(() => {
domainEl.focus(); domainEl.focus();
await getUsage();
usageInterval = setInterval(async () => {
await getUsage();
}, 1000);
}); });
async function changeSettings(name) { async function changeSettings(name) {
@@ -129,6 +151,7 @@
} }
} }
async function handleSubmit() { async function handleSubmit() {
if (loading) return;
loading = true; loading = true;
try { try {
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, ''); nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
@@ -261,6 +284,33 @@
</a> </a>
</div> </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"> <div class="mx-auto max-w-4xl px-6">
<!-- svelte-ignore missing-declaration --> <!-- svelte-ignore missing-declaration -->
<form on:submit|preventDefault={handleSubmit} class="py-4"> <form on:submit|preventDefault={handleSubmit} class="py-4">
@@ -354,28 +404,30 @@
value={application.destinationDocker.name} value={application.destinationDocker.name}
id="destination" id="destination"
disabled disabled
class="bg-transparent " class="bg-transparent"
/> />
</div> </div>
</div> </div>
<div class="grid grid-cols-2 items-center"> {#if application.buildPack !== 'docker'}
<label for="baseImage" class="text-base font-bold text-stone-100" <div class="grid grid-cols-2 items-center">
>{$t('application.base_image')}</label <label for="baseImage" class="text-base font-bold text-stone-100"
> >{$t('application.base_image')}</label
<div class="custom-select-wrapper"> >
<Select <div class="custom-select-wrapper">
isDisabled={!$session.isAdmin || isRunning} <Select
containerClasses={containerClass()} {isDisabled}
id="baseImages" containerClasses={isDisabled && containerClass()}
showIndicator={!isRunning} id="baseImages"
items={application.baseImages} showIndicator={!$status.application.isRunning}
on:select={selectBaseImage} items={application.baseImages}
value={application.baseImage} on:select={selectBaseImage}
isClearable={false} value={application.baseImage}
/> isClearable={false}
/>
</div>
<Explainer text={$t('application.base_image_explainer')} />
</div> </div>
<Explainer text={$t('application.base_image_explainer')} /> {/if}
</div>
{#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'} {#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'}
<div class="grid grid-cols-2 items-center pb-8"> <div class="grid grid-cols-2 items-center pb-8">
<label for="baseBuildImage" class="text-base font-bold text-stone-100" <label for="baseBuildImage" class="text-base font-bold text-stone-100"
@@ -384,10 +436,10 @@
<div class="custom-select-wrapper"> <div class="custom-select-wrapper">
<Select <Select
isDisabled={!$session.isAdmin || isRunning} {isDisabled}
containerClasses={containerClass()} containerClasses={isDisabled && containerClass()}
id="baseBuildImages" id="baseBuildImages"
showIndicator={!isRunning} showIndicator={!$status.application.isRunning}
items={application.baseBuildImages} items={application.baseBuildImages}
on:select={selectBaseBuildImage} on:select={selectBaseBuildImage}
value={application.baseBuildImage} value={application.baseBuildImage}
@@ -420,8 +472,8 @@
</div> </div>
<div> <div>
<input <input
readonly={!$session.isAdmin || isRunning} readonly={isDisabled}
disabled={!$session.isAdmin || isRunning} disabled={isDisabled}
bind:this={domainEl} bind:this={domainEl}
name="fqdn" name="fqdn"
id="fqdn" id="fqdn"
@@ -468,12 +520,12 @@
<div class="grid grid-cols-2 items-center pb-8"> <div class="grid grid-cols-2 items-center pb-8">
<Setting <Setting
dataTooltip={$t('forms.must_be_stopped_to_modify')} dataTooltip={$t('forms.must_be_stopped_to_modify')}
disabled={isRunning} disabled={$status.application.isRunning}
isCenter={false} isCenter={false}
bind:setting={dualCerts} bind:setting={dualCerts}
title={$t('application.ssl_www_and_non_www')} title={$t('application.ssl_www_and_non_www')}
description={$t('application.ssl_explainer')} description={$t('application.ssl_explainer')}
on:click={() => !isRunning && changeSettings('dualCerts')} on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
/> />
</div> </div>
{#if application.buildPack === 'python'} {#if application.buildPack === 'python'}
@@ -525,7 +577,8 @@
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label> <label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
<input <input
readonly={!$session.isAdmin} readonly={!$session.isAdmin && !$status.application.isRunning}
disabled={isDisabled}
name="exposePort" name="exposePort"
id="exposePort" id="exposePort"
bind:value={application.exposePort} bind:value={application.exposePort}

View File

@@ -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 /> <div />
</div> </div>

View File

@@ -17,7 +17,7 @@ export const get: RequestHandler = async (event) => {
const destinationDocker = await db.getDestinationByApplicationId({ id, teamId }); const destinationDocker = await db.getDestinationByApplicationId({ id, teamId });
const docker = dockerInstance({ destinationDocker }); const docker = dockerInstance({ destinationDocker });
const listContainers = await docker.engine.listContainers({ const listContainers = await docker.engine.listContainers({
filters: { network: [destinationDocker.network] } filters: { network: [destinationDocker.network], name: [id] }
}); });
const containers = listContainers.filter((container) => { const containers = listContainers.filter((container) => {
return ( return (
@@ -30,11 +30,7 @@ export const get: RequestHandler = async (event) => {
JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString()) JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString())
) )
.filter((container) => { .filter((container) => {
return ( return container.pullmergeRequestId && container.applicationId === id;
container.type !== 'manual' &&
container.type !== 'webhook_commit' &&
container.applicationId === id
);
}); });
return { return {
body: { body: {

View File

@@ -31,6 +31,7 @@
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { goto } from '$app/navigation';
const { id } = $page.params; const { id } = $page.params;
async function refreshSecrets() { async function refreshSecrets() {
@@ -39,11 +40,18 @@
} }
async function redeploy(container) { async function redeploy(container) {
try { try {
await post(`/applications/${id}/deploy.json`, { const { buildId } = await post(`/applications/${id}/deploy.json`, {
pullmergeRequestId: container.pullmergeRequestId, pullmergeRequestId: container.pullmergeRequestId,
branch: container.branch branch: container.branch
}); });
toast.push('Application redeployed queued.'); 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 }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }

View 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);
}
};

View 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);
}
};

View File

@@ -60,7 +60,7 @@
</div> </div>
{/if} {/if}
</div> </div>
<div class="flex flex-col flex-wrap justify-center"> <div class="flex-col justify-center">
{#if !applications || ownApplications.length === 0} {#if !applications || ownApplications.length === 0}
<div class="flex-col"> <div class="flex-col">
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div> <div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>

View File

@@ -2,45 +2,69 @@ import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import os from 'node:os';
import osu from 'node-os-utils';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
const { userId, teamId, status, body } = await getUserDetails(event); const { userId, teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const usage = event.url.searchParams.get('usage');
try { if (usage) {
const applicationsCount = await db.prisma.application.count({ try {
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } } return {
}); status: 200,
const sourcesCount = await db.prisma.gitSource.count({ body: {
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } } uptime: os.uptime(),
}); memory: await osu.mem.info(),
const destinationsCount = await db.prisma.destinationDocker.count({ cpu: {
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } } load: os.loadavg(),
}); usage: await osu.cpu.usage(),
const teamsCount = await db.prisma.permission.count({ where: { userId } }); count: os.cpus().length
const databasesCount = await db.prisma.database.count({ },
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } } disk: await osu.drive.info()
}); }
const servicesCount = await db.prisma.service.count({ };
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } } } catch (error) {
}); return ErrorHandler(error);
const teams = await db.prisma.permission.findMany({ }
where: { userId }, } else {
include: { team: { include: { _count: { select: { users: true } } } } } try {
}); const applicationsCount = await db.prisma.application.count({
return { where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
body: { });
teams, const sourcesCount = await db.prisma.gitSource.count({
applicationsCount, where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
sourcesCount, });
destinationsCount, const destinationsCount = await db.prisma.destinationDocker.count({
teamsCount, where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
databasesCount, });
servicesCount const teamsCount = await db.prisma.permission.count({ where: { userId } });
} const databasesCount = await db.prisma.database.count({
}; where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
} catch (error) { });
return ErrorHandler(error); 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);
}
} }
}; };

View File

@@ -33,10 +33,40 @@
<script lang="ts"> <script lang="ts">
import DatabaseLinks from '$lib/components/DatabaseLinks.svelte'; 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 database;
export let settings; export let settings;
export let privatePort; export let privatePort;
export let isRunning; 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> </script>
<div class="flex items-center space-x-2 p-6 text-2xl font-bold"> <div class="flex items-center space-x-2 p-6 text-2xl font-bold">
@@ -49,4 +79,31 @@
<DatabaseLinks {database} /> <DatabaseLinks {database} />
</div> </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} /> <Databases bind:database {privatePort} {settings} {isRunning} />

View File

@@ -1,7 +1,7 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { generateDatabaseConfiguration, ErrorHandler, getFreePort } 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'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -13,6 +13,7 @@ export const post: RequestHandler = async (event) => {
const publicPort = await getFreePort(); const publicPort = await getFreePort();
try { try {
const settings = await db.listSettings();
await db.setDatabase({ id, isPublic, appendOnly }); await db.setDatabase({ id, isPublic, appendOnly });
const database = await db.getDatabase({ id, teamId }); const database = await db.getDatabase({ id, teamId });
const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database; const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database;
@@ -21,7 +22,11 @@ export const post: RequestHandler = async (event) => {
if (destinationDockerId) { if (destinationDockerId) {
if (isPublic) { if (isPublic) {
await db.prisma.database.update({ where: { id }, data: { publicPort } }); 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 { } else {
await db.prisma.database.update({ where: { id }, data: { publicPort: null } }); await db.prisma.database.update({ where: { id }, data: { publicPort: null } });
await stopTcpHttpProxy(destinationDocker, oldPublicPort); await stopTcpHttpProxy(destinationDocker, oldPublicPort);

View 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);
}
};

View File

@@ -47,7 +47,7 @@
</div> </div>
</div> </div>
<div class="flex flex-col flex-wrap justify-center"> <div class="flex-col justify-center">
{#if !databases || ownDatabases.length === 0} {#if !databases || ownDatabases.length === 0}
<div class="flex-col"> <div class="flex-col">
<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div> <div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>

View File

@@ -26,8 +26,12 @@ export const get: RequestHandler = async (event) => {
// // await saveSshKey(destination); // // await saveSshKey(destination);
// payload.state = await checkContainer(engine, 'coolify-haproxy'); // payload.state = await checkContainer(engine, 'coolify-haproxy');
} else { } else {
let containerName = 'coolify-proxy';
if (!settings.isTraefikUsed) {
containerName = 'coolify-haproxy';
}
payload.state = payload.state =
destination?.engine && (await checkContainer(destination.engine, 'coolify-haproxy')); destination?.engine && (await checkContainer(destination.engine, containerName));
} }
return { return {
status: 200, status: 200,

View File

@@ -39,10 +39,13 @@
import { t } from '$lib/translations'; import { t } from '$lib/translations';
</script> </script>
<div class="flex space-x-1 p-6 text-2xl font-bold"> <div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
<div class="tracking-tight">{$t('application.destination')}</div> <div class="-mb-5 flex-col">
<span class="arrow-right-applications px-1">></span> <div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
<span class="pr-2">{destination.name}</span> Configuration
</div>
<span class="text-xs">{destination.name}</span>
</div>
</div> </div>
<div class="mx-auto max-w-4xl px-6"> <div class="mx-auto max-w-4xl px-6">

View File

@@ -1,7 +1,12 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import * as db 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'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -11,9 +16,16 @@ export const post: RequestHandler = async (event) => {
const { engine } = await event.request.json(); const { engine } = await event.request.json();
try { try {
await stopCoolifyProxy(engine); const settings = await db.prisma.setting.findFirst({});
await startCoolifyProxy(engine); if (settings?.isTraefikUsed) {
await stopTraefikProxy(engine);
await startTraefikProxy(engine);
} else {
await stopCoolifyProxy(engine);
await startCoolifyProxy(engine);
}
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true }); await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
return { return {
status: 200 status: 200
}; };

View File

@@ -1,6 +1,12 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { ErrorHandler } from '$lib/database'; 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'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -8,14 +14,24 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { engine } = await event.request.json(); const { engine } = await event.request.json();
const settings = await db.prisma.setting.findFirst({});
try { try {
await startCoolifyProxy(engine); if (settings?.isTraefikUsed) {
await startTraefikProxy(engine);
} else {
await startCoolifyProxy(engine);
}
return { return {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
await stopCoolifyProxy(engine); if (settings?.isTraefikUsed) {
await stopTraefikProxy(engine);
} else {
await stopCoolifyProxy(engine);
}
return ErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,7 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { ErrorHandler } from '$lib/database'; 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'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -9,7 +10,13 @@ export const post: RequestHandler = async (event) => {
const { engine } = await event.request.json(); const { engine } = await event.request.json();
try { try {
await stopCoolifyProxy(engine); const settings = await db.prisma.setting.findFirst({});
if (settings?.isTraefikUsed) {
await stopTraefikProxy(engine);
} else {
await stopCoolifyProxy(engine);
}
return { return {
status: 200 status: 200
}; };

View File

@@ -58,7 +58,7 @@
</a> </a>
{/if} {/if}
</div> </div>
<div class="flex justify-center"> <div class="flex-col justify-center">
{#if !destinations || ownDestinations.length === 0} {#if !destinations || ownDestinations.length === 0}
<div class="flex-col"> <div class="flex-col">
<div class="text-center text-xl font-bold">{$t('destination.no_destination_found')}</div> <div class="text-center text-xl font-bold">{$t('destination.no_destination_found')}</div>

View File

@@ -21,6 +21,10 @@
<script lang="ts"> <script lang="ts">
import { t } from '$lib/translations'; 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 applicationsCount: number;
export let sourcesCount: number; export let sourcesCount: number;
@@ -28,89 +32,260 @@
export let teamsCount: number; export let teamsCount: number;
export let databasesCount: number; export let databasesCount: number;
export let servicesCount: 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> </script>
<div class="flex space-x-1 p-6 font-bold"> <div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('index.dashboard')}</div> <div class="mr-4 text-2xl tracking-tight">{$t('index.dashboard')}</div>
</div> </div>
<div class="mt-10 pb-12 tracking-tight sm:pb-16"> <div class="mt-10 pb-12 tracking-tight sm:pb-16">
<div class="relative"> <div class="mx-auto max-w-4xl">
<div class="absolute inset-0 h-1/2" /> {#if $session.teamId === '0'}
<div class="relative mx-auto px-4 sm:px-6 lg:px-8"> <div class="px-6 text-2xl font-bold">Server Usage</div>
<div class="mx-auto max-w-4xl"> <dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
<dl class="gap-5 gap-y-16 sm:grid sm:grid-cols-3"> <div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<a <dt class="truncate text-sm font-medium text-white">Total Memory</dt>
href="/applications" <dd class="mt-1 text-3xl font-semibold text-white">
sveltekit:prefetch {(usage?.memory.totalMemMb).toFixed(0)}<span class="text-sm">MB</span>
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" </dd>
> </div>
<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>
<a <div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
href="/iam" <dt class="truncate text-sm font-medium text-white">Used Memory</dt>
sveltekit:prefetch <dd class="mt-1 text-3xl font-semibold text-white ">
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" {(usage?.memory.usedMemMb).toFixed(0)}<span class="text-sm">MB</span>
> </dd>
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white"> </div>
{$t('index.teams')}
</dt> <div
<dd class="order-1 text-5xl font-extrabold "> class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
{teamsCount} class:bg-red-500={memoryWarning}
</dd> >
</a> <dt class="truncate text-sm font-medium text-white">Free Memory</dt>
</dl> <dd class="mt-1 text-3xl font-semibold text-white">
</div> {usage?.memory.freeMemPercentage}<span class="text-sm">%</span>
</div> {#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>
</div> </div>

View File

@@ -30,14 +30,16 @@
value={service.minio.rootUserPassword} value={service.minio.rootUserPassword}
/> />
</div> </div>
<div class="grid grid-cols-2 items-center px-10"> {#if !service.minio.apiFqdn}
<label for="publicPort">{$t('forms.api_port')}</label> <div class="grid grid-cols-2 items-center px-10">
<input <label for="publicPort">{$t('forms.api_port')}</label>
name="publicPort" <input
id="publicPort" name="publicPort"
value={service.minio.publicPort} id="publicPort"
disabled value={service.minio.publicPort}
readonly disabled
placeholder={$t('forms.generated_automatically_after_start')} readonly
/> placeholder={$t('forms.generated_automatically_after_start')}
</div> />
</div>
{/if}

View File

@@ -1,13 +1,32 @@
<script lang="ts"> <script lang="ts">
import { session } from '$app/stores';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte'; import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
export let service; export let service;
export let readOnly; export let readOnly;
export let isRunning;
</script> </script>
<div class="flex space-x-1 py-5 font-bold"> <div class="flex space-x-1 py-5 font-bold">
<div class="title">Plausible Analytics</div> <div class="title">Plausible Analytics</div>
</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"> <div class="grid grid-cols-2 items-center px-10">
<label for="email">{$t('forms.email')}</label> <label for="email">{$t('forms.email')}</label>
<input <input
@@ -77,16 +96,3 @@
disabled disabled
/> />
</div> </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> -->

View File

@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import { browser } from '$app/env';
export let service; export let service;
export let isRunning; export let isRunning;
export let readOnly; export let readOnly;
@@ -12,6 +14,8 @@
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import cuid from 'cuid';
import { onMount } from 'svelte';
import Fider from './_Fider.svelte'; import Fider from './_Fider.svelte';
import Ghost from './_Ghost.svelte'; import Ghost from './_Ghost.svelte';
import Hasura from './_Hasura.svelte'; import Hasura from './_Hasura.svelte';
@@ -27,12 +31,16 @@
let loading = false; let loading = false;
let loadingVerification = false; let loadingVerification = false;
let dualCerts = service.dualCerts; let dualCerts = service.dualCerts;
let showExposePort = service.exposePort !== null;
async function handleSubmit() { async function handleSubmit() {
if (loading) return;
loading = true; loading = true;
try { try {
await post(`/services/${id}/check.json`, { fqdn: service.fqdn }); await post(`/services/${id}/check.json`, {
fqdn: service.fqdn,
otherFqdns: service.minio?.apiFqdn ? [service.minio?.apiFqdn] : [],
exposePort: service.exposePort
});
await post(`/services/${id}/${service.type}.json`, { ...service }); await post(`/services/${id}/${service.type}.json`, { ...service });
return window.location.reload(); return window.location.reload();
} catch ({ error }) { } catch ({ error }) {
@@ -63,6 +71,28 @@
return errorNotification(error); return errorNotification(error);
} }
} }
onMount(async () => {
if (browser && window.location.hostname === 'demo.coolify.io' && !service.fqdn) {
service.fqdn = `http://${cuid()}.demo.coolify.io`;
if (service.type === 'wordpress') {
service.wordpress.mysqlDatabase = 'db';
}
if (service.type === 'plausibleanalytics') {
service.plausibleAnalytics.email = 'noreply@demo.com';
service.plausibleAnalytics.username = 'admin';
}
if (service.type === 'minio') {
service.minio.apiFqdn = `http://${cuid()}.demo.coolify.io`;
}
if (service.type === 'ghost') {
service.ghost.mariadbDatabase = 'db';
}
if (service.type === 'fider') {
service.fider.emailNoreply = 'noreply@demo.com';
}
await handleSubmit();
}
});
</script> </script>
<div class="mx-auto max-w-4xl px-6 pb-12"> <div class="mx-auto max-w-4xl px-6 pb-12">
@@ -87,6 +117,14 @@
</div> </div>
<div class="grid grid-flow-row gap-2"> <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"> <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> <label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<div> <div>
@@ -132,25 +170,62 @@
{/if} {/if}
</div> </div>
</div> </div>
<div class="grid grid-cols-2 px-10"> {#if service.type === 'minio'}
<div class="flex-col "> <div class="grid grid-cols-2 px-10">
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100" <div class="flex-col ">
>{$t('application.url_fqdn')}</label <label for="fqdn" class="pt-2 text-base font-bold text-stone-100">Console URL</label>
> </div>
<Explainer text={$t('application.https_explainer')} />
</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"> <div class="grid grid-cols-2 items-center px-10">
<Setting <Setting
disabled={isRunning} disabled={isRunning}
@@ -161,34 +236,23 @@
on:click={() => !isRunning && changeSettings('dualCerts')} on:click={() => !isRunning && changeSettings('dualCerts')}
/> />
</div> </div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center px-10">
<Setting <label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
isCenter={false} <input
bind:setting={showExposePort} readonly={!$session.isAdmin && !isRunning}
on:click={() => { disabled={!$session.isAdmin || isRunning}
showExposePort = !showExposePort; name="exposePort"
service.exposePort = undefined; id="exposePort"
}} bind:value={service.exposePort}
title={$t('application.expose_a_port')} placeholder="12345"
description="Expose a port to the host system" />
<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> </div>
{#if showExposePort}
<div class="grid grid-cols-2 items-center">
<label for="exposePort" class="text-base font-bold text-stone-100">Expose Port</label>
<input
readonly={!$session.isAdmin}
name="exposePort"
id="exposePort"
bind:value={service.exposePort}
placeholder="12345"
/>
</div>
{/if}
{#if service.type === 'plausibleanalytics'} {#if service.type === 'plausibleanalytics'}
<PlausibleAnalytics bind:service {readOnly} /> <PlausibleAnalytics bind:service {isRunning} {readOnly} />
{:else if service.type === 'minio'} {:else if service.type === 'minio'}
<MinIo {service} /> <MinIo {service} />
{:else if service.type === 'vscodeserver'} {:else if service.type === 'vscodeserver'}

View File

@@ -18,6 +18,7 @@
let ftpUser = service.wordpress.ftpUser; let ftpUser = service.wordpress.ftpUser;
let ftpPassword = service.wordpress.ftpPassword; let ftpPassword = service.wordpress.ftpPassword;
let ftpLoading = false; let ftpLoading = false;
let ownMysql = service.wordpress.ownMysql;
function generateUrl(publicPort) { function generateUrl(publicPort) {
return browser return browser
@@ -40,7 +41,7 @@
publicPort, publicPort,
ftpUser: user, ftpUser: user,
ftpPassword: password ftpPassword: password
} = await post(`/services/${id}/wordpress/settings.json`, { } = await post(`/services/${id}/wordpress/ftp.json`, {
ftpEnabled ftpEnabled
}); });
ftpUrl = generateUrl(publicPort); ftpUrl = generateUrl(publicPort);
@@ -52,6 +53,18 @@
} finally { } finally {
ftpLoading = false; 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> </script>
@@ -106,52 +119,96 @@ define('SUBDOMAIN_INSTALL', false);`
<div class="flex space-x-1 py-5 font-bold"> <div class="flex space-x-1 py-5 font-bold">
<div class="title">MySQL</div> <div class="title">MySQL</div>
</div> </div>
<div class="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"> <div class="grid grid-cols-2 items-center px-10">
<label for="mysqlDatabase">{$t('index.database')}</label> <label for="mysqlDatabase">{$t('index.database')}</label>
<input <input
name="mysqlDatabase" name="mysqlDatabase"
id="mysqlDatabase" id="mysqlDatabase"
required required
readonly={readOnly} readonly={readOnly && !service.wordpress.ownMysql}
disabled={readOnly} disabled={readOnly && !service.wordpress.ownMysql}
bind:value={service.wordpress.mysqlDatabase} bind:value={service.wordpress.mysqlDatabase}
placeholder="{$t('forms.eg')}: wordpress_db" placeholder="{$t('forms.eg')}: wordpress_db"
/> />
</div> </div>
<div class="grid grid-cols-2 items-center px-10"> {#if !service.wordpress.ownMysql}
<label for="mysqlRootUser">{$t('forms.root_user')}</label> <div class="grid grid-cols-2 items-center px-10">
<input <label for="mysqlRootUser">{$t('forms.root_user')}</label>
name="mysqlRootUser" <input
id="mysqlRootUser" name="mysqlRootUser"
placeholder="MySQL {$t('forms.root_user')}" id="mysqlRootUser"
value={service.wordpress.mysqlRootUser} placeholder="MySQL {$t('forms.root_user')}"
disabled value={service.wordpress.mysqlRootUser}
readonly readonly={isRunning || !service.wordpress.ownMysql}
/> disabled={isRunning || !service.wordpress.ownMysql}
</div> />
<div class="grid grid-cols-2 items-center px-10"> </div>
<label for="mysqlRootUserPassword">{$t('forms.roots_password')}</label> <div class="grid grid-cols-2 items-center px-10">
<CopyPasswordField <label for="mysqlRootUserPassword">{$t('forms.roots_password')}</label>
id="mysqlRootUserPassword" <CopyPasswordField
isPasswordField id="mysqlRootUserPassword"
readonly isPasswordField
disabled readonly={isRunning || !service.wordpress.ownMysql}
name="mysqlRootUserPassword" disabled={isRunning || !service.wordpress.ownMysql}
value={service.wordpress.mysqlRootUserPassword} name="mysqlRootUserPassword"
/> value={service.wordpress.mysqlRootUserPassword}
</div> />
</div>
{/if}
<div class="grid grid-cols-2 items-center px-10"> <div class="grid grid-cols-2 items-center px-10">
<label for="mysqlUser">{$t('forms.user')}</label> <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>
<div class="grid grid-cols-2 items-center px-10"> <div class="grid grid-cols-2 items-center px-10">
<label for="mysqlPassword">{$t('forms.password')}</label> <label for="mysqlPassword">{$t('forms.password')}</label>
<CopyPasswordField <CopyPasswordField
id="mysqlPassword" id="mysqlPassword"
isPasswordField isPasswordField
readonly readonly={isRunning || !service.wordpress.ownMysql}
disabled disabled={isRunning || !service.wordpress.ownMysql}
name="mysqlPassword" name="mysqlPassword"
value={service.wordpress.mysqlPassword} bind:value={service.wordpress.mysqlPassword}
/> />
</div> </div>

View 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);
}
};

View 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);
}
};

View 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);
}
};

View File

@@ -1,19 +1,56 @@
import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common'; import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { t } from '$lib/translations';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import getPort from 'get-port';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
const { status, body } = await getUserDetails(event); const { status, body } = await getUserDetails(event);
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { id } = event.params; const { id } = event.params;
let { fqdn } = await event.request.json(); let { fqdn, exposePort, otherFqdns } = await event.request.json();
if (fqdn) fqdn = fqdn.toLowerCase(); if (fqdn) fqdn = fqdn.toLowerCase();
if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase());
if (exposePort) exposePort = Number(exposePort);
try { 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 && otherFqdns.length > 0) {
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 { return {
status: found ? 500 : 200, status: found ? 500 : 200,
body: { body: {

View File

@@ -13,6 +13,7 @@ import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common'; import { makeLabelForServices } from '$lib/buildPacks/common';
import type { ComposeFile } from '$lib/types/composeFile'; import type { ComposeFile } from '$lib/types/composeFile';
import type { Service, DestinationDocker, Prisma } from '@prisma/client'; import type { Service, DestinationDocker, Prisma } from '@prisma/client';
import { getServiceMainPort } from '$lib/components/common';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event); const { teamId, status, body } = await getUserDetails(event);
@@ -30,6 +31,7 @@ export const post: RequestHandler = async (event) => {
destinationDockerId, destinationDockerId,
destinationDocker, destinationDocker,
serviceSecret, serviceSecret,
exposePort,
fider: { fider: {
postgresqlUser, postgresqlUser,
postgresqlPassword, postgresqlPassword,
@@ -48,6 +50,7 @@ export const post: RequestHandler = async (event) => {
} = service; } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('fider');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type); const image = getServiceImage(type);
@@ -97,6 +100,7 @@ export const post: RequestHandler = async (event) => {
volumes: [], volumes: [],
restart: 'always', restart: 'always',
labels: makeLabelForServices('fider'), labels: makeLabelForServices('fider'),
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
deploy: { deploy: {
restart_policy: { restart_policy: {
condition: 'on-failure', condition: 'on-failure',

View File

@@ -7,6 +7,7 @@ import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common'; import { makeLabelForServices } from '$lib/buildPacks/common';
import type { ComposeFile } from '$lib/types/composeFile'; import type { ComposeFile } from '$lib/types/composeFile';
import type { Service, DestinationDocker, Prisma } from '@prisma/client'; import type { Service, DestinationDocker, Prisma } from '@prisma/client';
import { getServiceMainPort } from '$lib/components/common';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event); const { teamId, status, body } = await getUserDetails(event);
@@ -23,10 +24,12 @@ export const post: RequestHandler = async (event) => {
destinationDockerId, destinationDockerId,
destinationDocker, destinationDocker,
serviceSecret, serviceSecret,
exposePort,
hasura: { postgresqlUser, postgresqlPassword, postgresqlDatabase } hasura: { postgresqlUser, postgresqlPassword, postgresqlDatabase }
} = service; } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('hasura');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type); const image = getServiceImage(type);
@@ -65,6 +68,7 @@ export const post: RequestHandler = async (event) => {
volumes: [], volumes: [],
restart: 'always', restart: 'always',
labels: makeLabelForServices('hasura'), labels: makeLabelForServices('hasura'),
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
deploy: { deploy: {
restart_policy: { restart_policy: {
condition: 'on-failure', condition: 'on-failure',

View File

@@ -30,19 +30,43 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import cuid from 'cuid';
import { browser } from '$app/env';
import ServiceLinks from '$lib/components/ServiceLinks.svelte'; import ServiceLinks from '$lib/components/ServiceLinks.svelte';
import Services from './_Services/_Services.svelte'; import Services from './_Services/_Services.svelte';
import { get } from '$lib/api';
import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
export let service; export let service;
export let isRunning; export let isRunning;
export let readOnly; export let readOnly;
export let settings; export let settings;
if (browser && window.location.hostname === 'demo.coolify.io' && !service.fqdn) { const { id } = $page.params;
service.fqdn = `http://${cuid()}.demo.coolify.io`; 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(`/services/${id}/usage.json`);
usage = data.usage;
usageLoading = false;
} }
onDestroy(() => {
clearInterval(usageInterval);
});
onMount(async () => {
await getUsage();
usageInterval = setInterval(async () => {
await getUsage();
}, 1000);
});
</script> </script>
<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold"> <div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
@@ -52,6 +76,7 @@
</div> </div>
<span class="text-xs">{service.name}</span> <span class="text-xs">{service.name}</span>
</div> </div>
{#if service.fqdn} {#if service.fqdn}
<a <a
href={service.fqdn} href={service.fqdn}
@@ -77,5 +102,31 @@
<ServiceLinks {service} /> <ServiceLinks {service} />
</div> </div>
<div class="mx-auto max-w-4xl px-6 py-4">
<div class="text-2xl font-bold">Service 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>
<Services bind:service {isRunning} {readOnly} {settings} /> <Services bind:service {isRunning} {readOnly} {settings} />

View File

@@ -9,12 +9,17 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params; const { id } = event.params;
let { name, fqdn, exposePort } = await event.request.json(); let {
name,
fqdn,
exposePort,
minio: { apiFqdn }
} = await event.request.json();
if (fqdn) fqdn = fqdn.toLowerCase(); if (fqdn) fqdn = fqdn.toLowerCase();
if (exposePort) exposePort = Number(exposePort); if (exposePort) exposePort = Number(exposePort);
if (apiFqdn) apiFqdn = apiFqdn.toLowerCase();
try { try {
await db.updateService({ id, fqdn, name, exposePort }); await db.updateMinioService({ id, fqdn, apiFqdn, name, exposePort });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return ErrorHandler(error); return ErrorHandler(error);

View File

@@ -3,7 +3,6 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { startHttpProxy } from '$lib/haproxy';
import { ErrorHandler, getFreePort, getServiceImage } from '$lib/database'; import { ErrorHandler, getFreePort, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common'; import { makeLabelForServices } from '$lib/buildPacks/common';
import type { ComposeFile } from '$lib/types/composeFile'; import type { ComposeFile } from '$lib/types/composeFile';
@@ -35,7 +34,6 @@ export const post: RequestHandler = async (event) => {
const publicPort = await getFreePort(); const publicPort = await getFreePort();
const consolePort = 9001; const consolePort = 9001;
const apiPort = 9000;
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type); const image = getServiceImage(type);
@@ -65,7 +63,7 @@ export const post: RequestHandler = async (event) => {
networks: [network], networks: [network],
volumes: [config.volume], volumes: [config.volume],
restart: 'always', restart: 'always',
...(exposePort && { ports: [`${port}:${port}`] }), ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
labels: makeLabelForServices('minio'), labels: makeLabelForServices('minio'),
deploy: { deploy: {
restart_policy: { restart_policy: {
@@ -94,8 +92,7 @@ export const post: RequestHandler = async (event) => {
try { try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await db.updateMinioService({ id, publicPort }); await db.updateMinioServicePort({ id, publicPort });
await startHttpProxy(destinationDocker, id, publicPort, apiPort);
return { return {
status: 200 status: 200
}; };

View File

@@ -12,12 +12,7 @@ export const post: RequestHandler = async (event) => {
try { try {
const service = await db.getService({ id, teamId }); const service = await db.getService({ id, teamId });
const { const { destinationDockerId, destinationDocker } = service;
destinationDockerId,
destinationDocker,
fqdn,
minio: { publicPort }
} = service;
await db.updateMinioService({ id, publicPort: null }); await db.updateMinioService({ id, publicPort: null });
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine; const engine = destinationDocker.engine;
@@ -30,11 +25,6 @@ export const post: RequestHandler = async (event) => {
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
try {
await stopTcpHttpProxy(destinationDocker, publicPort);
} catch (error) {
console.log(error);
}
} }
return { return {

View File

@@ -48,6 +48,7 @@ export const post: RequestHandler = async (event) => {
environment: config.environmentVariables, environment: config.environmentVariables,
restart: 'always', restart: 'always',
labels: makeLabelForServices('n8n'), labels: makeLabelForServices('n8n'),
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
deploy: { deploy: {
restart_policy: { restart_policy: {
condition: 'on-failure', condition: 'on-failure',

View File

@@ -12,15 +12,28 @@ export const post: RequestHandler = async (event) => {
name, name,
fqdn, fqdn,
exposePort, exposePort,
plausibleAnalytics: { email, username } plausibleAnalytics: { email, username, scriptName }
} = await event.request.json(); } = await event.request.json();
if (fqdn) fqdn = fqdn.toLowerCase(); if (fqdn) fqdn = fqdn.toLowerCase();
if (email) email = email.toLowerCase(); if (email) email = email.toLowerCase();
if (exposePort) exposePort = Number(exposePort); if (exposePort) exposePort = Number(exposePort);
if (scriptName) {
scriptName = scriptName.toLowerCase();
if (scriptName.startsWith('/')) {
scriptName = scriptName.replaceAll(/\//gi, '');
}
}
try { try {
await db.updatePlausibleAnalyticsService({ id, fqdn, name, email, username, exposePort }); await db.updatePlausibleAnalyticsService({
id,
fqdn,
name,
email,
username,
exposePort,
scriptName
});
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return ErrorHandler(error); return ErrorHandler(error);

View File

@@ -135,7 +135,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
networks: [network], networks: [network],
environment: config.plausibleAnalytics.environmentVariables, environment: config.plausibleAnalytics.environmentVariables,
restart: 'always', restart: 'always',
...(exposePort && { ports: [`${port}:${exposePort}`] }), ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
depends_on: [`${id}-postgresql`, `${id}-clickhouse`], depends_on: [`${id}-postgresql`, `${id}-clickhouse`],
labels: makeLabelForServices('plausibleAnalytics'), labels: makeLabelForServices('plausibleAnalytics'),
deploy: { deploy: {

View File

@@ -159,7 +159,7 @@ export const post: RequestHandler = async (event) => {
networks: [network], networks: [network],
volumes: [], volumes: [],
restart: 'always', restart: 'always',
...(exposePort ? { ports: [`${port}:${port}`] } : {}), ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
labels: makeLabelForServices('umami'), labels: makeLabelForServices('umami'),
deploy: { deploy: {
restart_policy: { restart_policy: {

View 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 service = await db.getService({ id, teamId });
if (service.destinationDockerId) {
[usage] = await Promise.all([getContainerUsage(service.destinationDocker.engine, id)]);
}
return {
status: 200,
body: {
usage
},
headers: {}
};
} catch (error) {
console.log(error);
return ErrorHandler(error);
}
};

View File

@@ -78,7 +78,7 @@ export const post: RequestHandler = async (event) => {
networks: [network], networks: [network],
volumes: [config.volume, ...volumes], volumes: [config.volume, ...volumes],
restart: 'always', restart: 'always',
...(exposePort ? { ports: [`${port}:${exposePort}`] } : {}), ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
labels: makeLabelForServices('vscodeServer'), labels: makeLabelForServices('vscodeServer'),
deploy: { deploy: {
restart_policy: { restart_policy: {

View File

@@ -0,0 +1,187 @@
import { dev } from '$app/env';
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
import { decrypt, encrypt } from '$lib/crypto';
import * as db from '$lib/database';
import { ErrorHandler, generatePassword, getFreePort } from '$lib/database';
import {
checkContainer,
startTcpProxy,
startTraefikTCPProxy,
stopTcpHttpProxy
} from '$lib/haproxy';
import type { ComposeFile } from '$lib/types/composeFile';
import type { RequestHandler } from '@sveltejs/kit';
import cuid from 'cuid';
import fs from 'fs/promises';
import yaml from 'js-yaml';
export const post: RequestHandler = async (event) => {
const { status, body, teamId } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { ftpEnabled } = await event.request.json();
const publicPort = await getFreePort();
let ftpUser = cuid();
let ftpPassword = generatePassword();
const hostkeyDir = dev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
try {
const data = await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpEnabled },
include: { service: { include: { destinationDocker: true } } }
});
const {
service: { destinationDockerId, destinationDocker },
ftpPublicPort,
ftpUser: user,
ftpPassword: savedPassword,
ftpHostKey,
ftpHostKeyPrivate
} = data;
const { network, engine } = destinationDocker;
const settings = await db.prisma.setting.findFirst();
const host = getEngine(engine);
if (ftpEnabled) {
if (user) ftpUser = user;
if (savedPassword) ftpPassword = decrypt(savedPassword);
const { stdout: password } = await asyncExecShell(
`echo ${ftpPassword} | openssl passwd -1 -stdin`
);
if (destinationDockerId) {
try {
await fs.stat(hostkeyDir);
} catch (error) {
await asyncExecShell(`mkdir -p ${hostkeyDir}`);
}
if (!ftpHostKey) {
await asyncExecShell(
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
);
const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`);
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpHostKey: encrypt(ftpHostKey) }
});
} else {
await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`);
}
if (!ftpHostKeyPrivate) {
await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`);
const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`);
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
});
} else {
await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`);
}
await db.prisma.wordpress.update({
where: { serviceId: id },
data: {
ftpPublicPort: publicPort,
ftpUser: user ? undefined : ftpUser,
ftpPassword: savedPassword ? undefined : encrypt(ftpPassword)
}
});
try {
const isRunning = await checkContainer(engine, `${id}-ftp`);
if (isRunning) {
await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
);
}
} catch (error) {
console.log(error);
//
}
const volumes = [
`${id}-wordpress-data:/home/${ftpUser}`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.sh:/etc/sftp.d/chmod.sh`
];
const compose: ComposeFile = {
version: '3.8',
services: {
[`${id}-ftp`]: {
image: `atmoz/sftp:alpine`,
command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`,
extra_hosts: ['host.docker.internal:host-gateway'],
container_name: `${id}-ftp`,
volumes,
networks: [network],
depends_on: [],
restart: 'always'
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[`${id}-wordpress-data`]: {
external: true,
name: `${id}-wordpress-data`
}
}
};
await fs.writeFile(
`${hostkeyDir}/${id}.sh`,
`#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key`
);
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
);
}
return {
status: 201,
body: {
publicPort,
ftpUser,
ftpPassword
}
};
} else {
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpPublicPort: null }
});
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
);
} catch (error) {
//
}
await stopTcpHttpProxy(id, destinationDocker, ftpPublicPort);
return {
status: 200,
body: {}
};
}
} catch (error) {
console.log(error);
return ErrorHandler(error);
} finally {
await asyncExecShell(
`rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
);
}
};

View File

@@ -12,13 +12,26 @@ export const post: RequestHandler = async (event) => {
name, name,
fqdn, fqdn,
exposePort, exposePort,
wordpress: { extraConfig, mysqlDatabase } ownMysql,
wordpress: { extraConfig, mysqlDatabase, mysqlHost, mysqlPort, mysqlUser, mysqlPassword }
} = await event.request.json(); } = await event.request.json();
if (fqdn) fqdn = fqdn.toLowerCase(); if (fqdn) fqdn = fqdn.toLowerCase();
if (exposePort) exposePort = Number(exposePort); if (exposePort) exposePort = Number(exposePort);
if (mysqlPort) mysqlPort = Number(mysqlPort);
try { try {
await db.updateWordpress({ id, fqdn, name, extraConfig, mysqlDatabase, exposePort }); await db.updateWordpress({
id,
fqdn,
name,
extraConfig,
ownMysql,
mysqlDatabase,
exposePort,
mysqlHost,
mysqlPort,
mysqlUser,
mysqlPassword
});
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return ErrorHandler(error); return ErrorHandler(error);

View File

@@ -16,170 +16,17 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params; const { id } = event.params;
const { ftpEnabled } = await event.request.json(); const { ownMysql } = await event.request.json();
const publicPort = await getFreePort();
let ftpUser = cuid();
let ftpPassword = generatePassword();
const hostkeyDir = dev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
try { try {
const data = await db.prisma.wordpress.update({ await db.prisma.wordpress.update({
where: { serviceId: id }, where: { serviceId: id },
data: { ftpEnabled }, data: { ownMysql }
include: { service: { include: { destinationDocker: true } } }
}); });
const { return {
service: { destinationDockerId, destinationDocker }, status: 201
ftpPublicPort: oldPublicPort, };
ftpUser: user,
ftpPassword: savedPassword,
ftpHostKey,
ftpHostKeyPrivate
} = data;
if (user) ftpUser = user;
if (savedPassword) ftpPassword = decrypt(savedPassword);
const { stdout: password } = await asyncExecShell(
`echo ${ftpPassword} | openssl passwd -1 -stdin`
);
if (destinationDockerId) {
try {
await fs.stat(hostkeyDir);
} catch (error) {
await asyncExecShell(`mkdir -p ${hostkeyDir}`);
}
if (!ftpHostKey) {
await asyncExecShell(
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
);
const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`);
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpHostKey: encrypt(ftpHostKey) }
});
} else {
await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`);
}
if (!ftpHostKeyPrivate) {
await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`);
const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`);
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
});
} else {
await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`);
}
const { network, engine } = destinationDocker;
const host = getEngine(engine);
if (ftpEnabled) {
await db.prisma.wordpress.update({
where: { serviceId: id },
data: {
ftpPublicPort: publicPort,
ftpUser: user ? undefined : ftpUser,
ftpPassword: savedPassword ? undefined : encrypt(ftpPassword)
}
});
try {
const isRunning = await checkContainer(engine, `${id}-ftp`);
if (isRunning) {
await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
);
}
} catch (error) {
console.log(error);
//
}
const volumes = [
`${id}-wordpress-data:/home/${ftpUser}`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.sh:/etc/sftp.d/chmod.sh`
];
const compose: ComposeFile = {
version: '3.8',
services: {
[`${id}-ftp`]: {
image: `atmoz/sftp:alpine`,
command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`,
extra_hosts: ['host.docker.internal:host-gateway'],
container_name: `${id}-ftp`,
volumes,
networks: [network],
depends_on: [],
restart: 'always'
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[`${id}-wordpress-data`]: {
external: true,
name: `${id}-wordpress-data`
}
}
};
await fs.writeFile(
`${hostkeyDir}/${id}.sh`,
`#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key`
);
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
);
await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22);
} else {
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpPublicPort: null }
});
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
);
} catch (error) {
//
}
await stopTcpHttpProxy(destinationDocker, oldPublicPort);
}
}
if (ftpEnabled) {
return {
status: 201,
body: {
publicPort,
ftpUser,
ftpPassword
}
};
} else {
return {
status: 200,
body: {}
};
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return ErrorHandler(error); return ErrorHandler(error);
} finally {
await asyncExecShell(
`rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
);
} }
}; };

View File

@@ -26,11 +26,14 @@ export const post: RequestHandler = async (event) => {
exposePort, exposePort,
wordpress: { wordpress: {
mysqlDatabase, mysqlDatabase,
mysqlHost,
mysqlPort,
mysqlUser, mysqlUser,
mysqlPassword, mysqlPassword,
extraConfig, extraConfig,
mysqlRootUser, mysqlRootUser,
mysqlRootUserPassword mysqlRootUserPassword,
ownMysql
} }
} = service; } = service;
@@ -45,7 +48,7 @@ export const post: RequestHandler = async (event) => {
image: `${image}:${version}`, image: `${image}:${version}`,
volume: `${id}-wordpress-data:/var/www/html`, volume: `${id}-wordpress-data:/var/www/html`,
environmentVariables: { environmentVariables: {
WORDPRESS_DB_HOST: `${id}-mysql`, WORDPRESS_DB_HOST: ownMysql ? `${mysqlHost}:${mysqlPort}` : `${id}-mysql`,
WORDPRESS_DB_USER: mysqlUser, WORDPRESS_DB_USER: mysqlUser,
WORDPRESS_DB_PASSWORD: mysqlPassword, WORDPRESS_DB_PASSWORD: mysqlPassword,
WORDPRESS_DB_NAME: mysqlDatabase, WORDPRESS_DB_NAME: mysqlDatabase,
@@ -69,7 +72,7 @@ export const post: RequestHandler = async (event) => {
config.wordpress.environmentVariables[secret.name] = secret.value; config.wordpress.environmentVariables[secret.name] = secret.value;
}); });
} }
const composeFile: ComposeFile = { let composeFile: ComposeFile = {
version: '3.8', version: '3.8',
services: { services: {
[id]: { [id]: {
@@ -79,8 +82,7 @@ export const post: RequestHandler = async (event) => {
volumes: [config.wordpress.volume], volumes: [config.wordpress.volume],
networks: [network], networks: [network],
restart: 'always', restart: 'always',
...(exposePort ? { ports: [`${port}:${port}`] } : {}), ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
depends_on: [`${id}-mysql`],
labels: makeLabelForServices('wordpress'), labels: makeLabelForServices('wordpress'),
deploy: { deploy: {
restart_policy: { restart_policy: {
@@ -90,22 +92,6 @@ export const post: RequestHandler = async (event) => {
window: '120s' window: '120s'
} }
} }
},
[`${id}-mysql`]: {
container_name: `${id}-mysql`,
image: config.mysql.image,
volumes: [config.mysql.volume],
environment: config.mysql.environmentVariables,
networks: [network],
restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {
@@ -116,12 +102,32 @@ export const post: RequestHandler = async (event) => {
volumes: { volumes: {
[config.wordpress.volume.split(':')[0]]: { [config.wordpress.volume.split(':')[0]]: {
name: config.wordpress.volume.split(':')[0] name: config.wordpress.volume.split(':')[0]
},
[config.mysql.volume.split(':')[0]]: {
name: config.mysql.volume.split(':')[0]
} }
} }
}; };
if (!ownMysql) {
composeFile.services[id].depends_on = [`${id}-mysql`];
composeFile.services[`${id}-mysql`] = {
container_name: `${id}-mysql`,
image: config.mysql.image,
volumes: [config.mysql.volume],
environment: config.mysql.environmentVariables,
networks: [network],
restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
};
composeFile.volumes[config.mysql.volume.split(':')[0]] = {
name: config.mysql.volume.split(':')[0]
};
}
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try { try {

View File

@@ -55,7 +55,7 @@
</div> </div>
</div> </div>
<div class="flex flex-col flex-wrap justify-center"> <div class="flex-col justify-center">
{#if !services || ownServices.length === 0} {#if !services || ownServices.length === 0}
<div class="flex-col"> <div class="flex-col">
<div class="text-center text-xl font-bold">{$t('service.no_service')}</div> <div class="text-center text-xl font-bold">{$t('service.no_service')}</div>

View File

@@ -72,8 +72,7 @@ export const post: RequestHandler = async (event) => {
minPort, minPort,
maxPort, maxPort,
isAutoUpdateEnabled, isAutoUpdateEnabled,
isDNSCheckEnabled, isDNSCheckEnabled
forceSave
} = await event.request.json(); } = await event.request.json();
try { try {
const { id } = await db.listSettings(); const { id } = await db.listSettings();

View File

@@ -37,12 +37,13 @@
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { features } from '$lib/store'; import { features, isTraefikUsed } from '$lib/store';
let isRegistrationEnabled = settings.isRegistrationEnabled; let isRegistrationEnabled = settings.isRegistrationEnabled;
let dualCerts = settings.dualCerts; let dualCerts = settings.dualCerts;
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled; let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
let isDNSCheckEnabled = settings.isDNSCheckEnabled; let isDNSCheckEnabled = settings.isDNSCheckEnabled;
$isTraefikUsed = settings.isTraefikUsed;
let minPort = settings.minPort; let minPort = settings.minPort;
let maxPort = settings.maxPort; let maxPort = settings.maxPort;
@@ -55,7 +56,8 @@
let isFqdnSet = !!settings.fqdn; let isFqdnSet = !!settings.fqdn;
let loading = { let loading = {
save: false, save: false,
remove: false remove: false,
proxyMigration: false
}; };
async function removeFqdn() { async function removeFqdn() {
@@ -86,6 +88,7 @@
if (name === 'isDNSCheckEnabled') { if (name === 'isDNSCheckEnabled') {
isDNSCheckEnabled = !isDNSCheckEnabled; isDNSCheckEnabled = !isDNSCheckEnabled;
} }
await post(`/settings.json`, { await post(`/settings.json`, {
isRegistrationEnabled, isRegistrationEnabled,
dualCerts, dualCerts,
@@ -156,6 +159,20 @@
function resetView() { function resetView() {
forceSave = false; forceSave = false;
} }
async function migrateProxy(to) {
if (loading.proxyMigration) return;
try {
loading.proxyMigration = true;
await post(`/update.json`, { type: to });
const data = await get(`/settings.json`);
$isTraefikUsed = data.settings.isTraefikUsed;
return toast.push('Proxy migration started, it takes a few seconds.');
} catch ({ error }) {
return errorNotification(error);
} finally {
loading.proxyMigration = false;
}
}
</script> </script>
<div class="flex space-x-1 p-6 font-bold"> <div class="flex space-x-1 p-6 font-bold">
@@ -192,6 +209,26 @@
</div> </div>
<div class="grid grid-flow-row gap-2 px-10"> <div class="grid grid-flow-row gap-2 px-10">
<!-- <Language /> --> <!-- <Language /> -->
<div class="grid grid-cols-2 items-center">
<div class="flex items-center py-2 pr-8">
<div class="flex w-96 flex-col">
<div class="text-xs font-bold text-stone-100 md:text-base">New Proxy Available!</div>
<Explainer
text="We are changing to <span class='text-sky-500 font-bold'>Traefik</span> as <span class='text-red-500 font-bold'>HAProxy</span> had several problems and uses a LOT of unnecessary memory (<span class='text-sky-500 font-bold'>~20MB</span> vs <span class='text-red-500 font-bold'>~200MB</span>).<br><br>You can switch back to HAProxy if something is not working and <span class='text-yellow-500 font-bold'>please let us know</span>!"
/>
</div>
</div>
<button
disabled={loading.proxyMigration}
class="bg-green-600 text-white hover:bg-green-500"
on:click={() => migrateProxy($isTraefikUsed ? 'haproxy' : 'traefik')}
>{loading.proxyMigration
? 'Migrating...'
: $isTraefikUsed
? 'Switch back to HAProxy'
: 'Migrate to Traefik'}</button
>
</div>
<div class="grid grid-cols-2 items-start"> <div class="grid grid-cols-2 items-start">
<div class="flex-col"> <div class="flex-col">
<div class="pt-2 text-base font-bold text-stone-100"> <div class="pt-2 text-base font-bold text-stone-100">

View File

@@ -84,6 +84,9 @@
<form on:submit|preventDefault={newGithubApp} class="py-4"> <form on:submit|preventDefault={newGithubApp} class="py-4">
<div class="flex space-x-1 pb-5 font-bold"> <div class="flex space-x-1 pb-5 font-bold">
<div class="title">General</div> <div class="title">General</div>
{#if source.apiUrl && source.htmlUrl && source.name}
<button class=" bg-orange-600" type="submit">Create new GitHub App</button>
{/if}
</div> </div>
<div class="grid grid-flow-row gap-2 px-10"> <div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-flow-row gap-2"> <div class="grid grid-flow-row gap-2">
@@ -117,11 +120,6 @@
/> />
</div> </div>
</div> </div>
{#if source.apiUrl && source.htmlUrl && source.name}
<div class="text-center">
<button class=" mt-8 bg-orange-600" type="submit">Create new GitHub App</button>
</div>
{/if}
</form> </form>
{:else if source.githubApp?.installationId} {:else if source.githubApp?.installationId}
<form on:submit|preventDefault={handleSubmit} class="py-4"> <form on:submit|preventDefault={handleSubmit} class="py-4">
@@ -173,7 +171,7 @@
</form> </form>
{:else} {:else}
<div class="text-center"> <div class="text-center">
<button class=" bg-orange-600 mt-8" on:click={() => installRepositories(source)} <button class=" mt-8 bg-orange-600" on:click={() => installRepositories(source)}
>Install Repositories</button >Install Repositories</button
> >
</div> </div>

View File

@@ -68,10 +68,48 @@
} }
</script> </script>
<div class="flex space-x-1 p-6 px-6 text-2xl font-bold"> <div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
<div class="tracking-tight">{$t('application.git_source')}</div> <div class="-mb-5 flex-col">
<span class="arrow-right-applications px-1 text-orange-500">></span> <div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
<span class="pr-2">{source.name}</span> Configuration
</div>
<span class="text-xs">{source.name}</span>
</div>
{#if source?.type === 'gitlab'}
<svg viewBox="0 0 128 128" class="w-8">
<path
fill="#FC6D26"
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
fill="#FC6D26"
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
/><path
fill="#FCA326"
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
/><path
fill="#E24329"
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
fill="#FCA326"
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
/><path
fill="#E24329"
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
/>
</svg>
{:else if source?.type === 'github'}
<svg viewBox="0 0 128 128" class="w-8">
<g fill="#ffffff"
><path
fill-rule="evenodd"
clip-rule="evenodd"
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
/><path
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
/></g
>
</svg>
{/if}
</div> </div>
<div class="flex flex-col justify-center"> <div class="flex flex-col justify-center">

View File

@@ -62,7 +62,7 @@
</button> </button>
{/if} {/if}
</div> </div>
<div class="flex flex-col flex-wrap justify-center"> <div class="flex-col justify-center">
{#if !sources || ownSources.length === 0} {#if !sources || ownSources.length === 0}
<div class="flex-col"> <div class="flex-col">
<div class="text-center text-xl font-bold">{$t('source.no_git_sources_found')}</div> <div class="text-center text-xl font-bold">{$t('source.no_git_sources_found')}</div>
@@ -74,11 +74,48 @@
{#each ownSources as source} {#each ownSources as source}
<a href="/sources/{source.id}" class="w-96 p-2 no-underline"> <a href="/sources/{source.id}" class="w-96 p-2 no-underline">
<div <div
class="box-selection group hover:bg-orange-600" class="box-selection group relative hover:bg-orange-600"
class:border-red-500={source.gitlabApp && !source.gitlabAppId} class:border-red-500={source.gitlabApp && !source.gitlabAppId}
class:border-0={source.gitlabApp && !source.gitlabAppId} class:border-0={source.gitlabApp && !source.gitlabAppId}
class:border-l-4={source.gitlabApp && !source.gitlabAppId} class:border-l-4={source.gitlabApp && !source.gitlabAppId}
> >
<div class="absolute top-0 left-0 -m-5 h-10 w-10">
{#if source?.type === 'gitlab'}
<svg viewBox="0 0 128 128">
<path
fill="#FC6D26"
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
fill="#FC6D26"
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
/><path
fill="#FCA326"
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
/><path
fill="#E24329"
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
fill="#FCA326"
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
/><path
fill="#E24329"
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
/>
</svg>
{:else if source?.type === 'github'}
<svg viewBox="0 0 128 128">
<g fill="#ffffff"
><path
fill-rule="evenodd"
clip-rule="evenodd"
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
/><path
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
/></g
>
</svg>
{/if}
</div>
<div class="truncate text-center text-xl font-bold">{source.name}</div> <div class="truncate text-center text-xl font-bold">{source.name}</div>
{#if $session.teamId === '0' && otherSources.length > 0} {#if $session.teamId === '0' && otherSources.length > 0}
<div class="truncate text-center">{source.teams[0].name}</div> <div class="truncate text-center">{source.teams[0].name}</div>

View File

@@ -34,14 +34,14 @@ export const get: RequestHandler = async (request) => {
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
const { type, latestVersion } = await event.request.json(); const { type, latestVersion } = await event.request.json();
const settings = await db.prisma.setting.findFirst();
if (type === 'update') { if (type === 'update') {
try { try {
if (!dev) { if (!dev) {
const { isAutoUpdateEnabled } = await db.prisma.setting.findFirst();
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`); await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
await asyncExecShell(`env | grep COOLIFY > .env`); await asyncExecShell(`env | grep COOLIFY > .env`);
await asyncExecShell( await asyncExecShell(
`sed -i '/COOLIFY_AUTO_UPDATE=/c\COOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` `sed -i '/COOLIFY_AUTO_UPDATE=/c\COOLIFY_AUTO_UPDATE=${settings.isAutoUpdateEnabled}' .env`
); );
await asyncExecShell( await asyncExecShell(
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-redis && docker rm coolify coolify-redis && docker compose up -d --force-recreate"` `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-redis && docker rm coolify coolify-redis && docker compose up -d --force-recreate"`
@@ -61,6 +61,44 @@ export const post: RequestHandler = async (event) => {
} catch (error) { } catch (error) {
return ErrorHandler(error); return ErrorHandler(error);
} }
} else if (type === 'traefik') {
try {
// const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy');
// if (found) {
// await asyncExecShell(`docker stop -t 0 coolify-haproxy`);
// await asyncExecShell(`docker rm coolify-haproxy`);
// }
// await startTraefikProxy('/var/run/docker.sock');
await db.prisma.setting.update({
where: { id: settings.id },
data: { isTraefikUsed: true }
});
return {
status: 200,
body: {}
};
} catch (error) {
return ErrorHandler(error);
}
} else if (type === 'haproxy') {
try {
// const found = await checkContainer('/var/run/docker.sock', 'coolify-proxy');
// if (found) {
// await asyncExecShell(`docker stop -t 0 coolify-proxy`);
// await asyncExecShell(`docker rm coolify-proxy`);
// }
// await startCoolifyProxy('/var/run/docker.sock');
await db.prisma.setting.update({
where: { id: settings.id },
data: { isTraefikUsed: false }
});
return {
status: 200,
body: {}
};
} catch (error) {
return ErrorHandler(error);
}
} }
return { return {
status: 500 status: 500

View File

@@ -0,0 +1,360 @@
import { dev } from '$app/env';
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import { supportedServiceTypesAndVersions } from '$lib/components/common';
import * as db from '$lib/database';
import { listServicesWithIncludes } from '$lib/database';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
function configureMiddleware(
{ id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts },
traefik
) {
if (isHttps) {
traefik.http.routers[id] = {
entrypoints: ['web'],
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
service: `${id}`,
middlewares: ['redirect-to-https']
};
traefik.http.services[id] = {
loadbalancer: {
servers: [
{
url: `http://${container}:${port}`
}
]
}
};
if (isDualCerts) {
traefik.http.routers[`${id}-secure`] = {
entrypoints: ['websecure'],
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
service: `${id}`,
tls: {
certresolver: 'letsencrypt'
},
middlewares: []
};
} else {
if (isWWW) {
traefik.http.routers[`${id}-secure-www`] = {
entrypoints: ['websecure'],
rule: `Host(\`www.${nakedDomain}\`)`,
service: `${id}`,
tls: {
certresolver: 'letsencrypt'
},
middlewares: []
};
traefik.http.routers[`${id}-secure`] = {
entrypoints: ['websecure'],
rule: `Host(\`${nakedDomain}\`)`,
service: `${id}`,
tls: {
domains: {
main: `${domain}`
}
},
middlewares: ['redirect-to-www']
};
traefik.http.routers[`${id}`].middlewares.push('redirect-to-www');
} else {
traefik.http.routers[`${id}-secure-www`] = {
entrypoints: ['websecure'],
rule: `Host(\`www.${nakedDomain}\`)`,
service: `${id}`,
tls: {
domains: {
main: `${domain}`
}
},
middlewares: ['redirect-to-non-www']
};
traefik.http.routers[`${id}-secure`] = {
entrypoints: ['websecure'],
rule: `Host(\`${domain}\`)`,
service: `${id}`,
tls: {
certresolver: 'letsencrypt'
},
middlewares: []
};
traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www');
}
}
} else {
traefik.http.routers[id] = {
entrypoints: ['web'],
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
service: `${id}`,
middlewares: []
};
traefik.http.routers[`${id}-secure`] = {
entrypoints: ['websecure'],
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
service: `${id}`,
tls: {
domains: {
main: `${nakedDomain}`
}
},
middlewares: ['redirect-to-http']
};
traefik.http.services[id] = {
loadbalancer: {
servers: [
{
url: `http://${container}:${port}`
}
]
}
};
if (!isDualCerts) {
if (isWWW) {
traefik.http.routers[`${id}`].middlewares.push('redirect-to-www');
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-www');
} else {
traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www');
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-non-www');
}
}
}
}
export const get: RequestHandler = async (event) => {
const traefik = {
http: {
routers: {},
services: {},
middlewares: {
'redirect-to-https': {
redirectscheme: {
scheme: 'https'
}
},
'redirect-to-http': {
redirectscheme: {
scheme: 'http'
}
},
'redirect-to-non-www': {
redirectregex: {
regex: '^https?://www\\.(.+)',
replacement: 'http://${1}'
}
},
'redirect-to-www': {
redirectregex: {
regex: '^https?://(?:www\\.)?(.+)',
replacement: 'http://www.${1}'
}
}
}
}
};
const applications = await db.prisma.application.findMany({
include: { destinationDocker: true, settings: true }
});
const data = {
applications: [],
services: [],
coolify: []
};
for (const application of applications) {
const {
fqdn,
id,
port,
destinationDocker,
destinationDockerId,
settings: { previews, dualCerts }
} = application;
if (destinationDockerId) {
const { engine, network } = destinationDocker;
const isRunning = await checkContainer(engine, id);
if (fqdn) {
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
if (isRunning) {
data.applications.push({
id,
container: id,
port: port || 3000,
domain,
nakedDomain,
isRunning,
isHttps,
isWWW,
isDualCerts: dualCerts
});
}
if (previews) {
const host = getEngine(engine);
const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
);
const containers = stdout
.trim()
.split('\n')
.filter((a) => a)
.map((c) => c.replace(/"/g, ''));
if (containers.length > 0) {
for (const container of containers) {
const previewDomain = `${container.split('-')[1]}.${domain}`;
const nakedDomain = previewDomain.replace(/^www\./, '');
data.applications.push({
id: container,
container,
port: port || 3000,
domain: previewDomain,
isRunning,
nakedDomain,
isHttps,
isWWW,
isDualCerts: dualCerts
});
}
}
}
}
}
}
const services = await listServicesWithIncludes();
for (const service of services) {
const {
fqdn,
id,
type,
destinationDocker,
destinationDockerId,
dualCerts,
plausibleAnalytics
} = service;
if (destinationDockerId) {
const { engine } = destinationDocker;
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
if (found) {
const port = found.ports.main;
const publicPort = service[type]?.publicPort;
const isRunning = await checkContainer(engine, id);
if (fqdn) {
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
if (isRunning) {
// Plausible Analytics custom script
let scriptName = false;
if (type === 'plausibleanalytics' && plausibleAnalytics.scriptName !== 'plausible.js') {
scriptName = plausibleAnalytics.scriptName;
}
let container = id;
let otherDomain = null;
let otherNakedDomain = null;
let otherIsHttps = null;
let otherIsWWW = null;
if (type === 'minio' && service.minio.apiFqdn) {
otherDomain = getDomain(service.minio.apiFqdn);
otherNakedDomain = otherDomain.replace(/^www\./, '');
otherIsHttps = service.minio.apiFqdn.startsWith('https://');
otherIsWWW = service.minio.apiFqdn.includes('www.');
}
data.services.push({
id,
container,
type,
otherDomain,
otherNakedDomain,
otherIsHttps,
otherIsWWW,
port,
publicPort,
domain,
nakedDomain,
isRunning,
isHttps,
isWWW,
isDualCerts: dualCerts,
scriptName
});
}
}
}
}
}
const { fqdn, dualCerts } = await db.prisma.setting.findFirst();
if (fqdn) {
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
data.coolify.push({
id: dev ? 'host.docker.internal' : 'coolify',
container: dev ? 'host.docker.internal' : 'coolify',
port: 3000,
domain,
nakedDomain,
isHttps,
isWWW,
isDualCerts: dualCerts
});
}
for (const application of data.applications) {
configureMiddleware(application, traefik);
}
for (const service of data.services) {
const { id, scriptName } = service;
configureMiddleware(service, traefik);
if (service.type === 'minio') {
service.id = id + '-minio';
service.container = id;
service.domain = service.otherDomain;
service.nakedDomain = service.otherNakedDomain;
service.isHttps = service.otherIsHttps;
service.isWWW = service.otherIsWWW;
service.port = 9000;
configureMiddleware(service, traefik);
}
if (scriptName) {
traefik.http.middlewares[`${id}-redir`] = {
replacepathregex: {
regex: `/js/${scriptName}`,
replacement: '/js/plausible.js'
}
};
if (traefik.http.routers[id].middlewares.length > 0) {
traefik.http.routers[id].middlewares.push(`${id}-redir`);
} else {
traefik.http.routers[id].middlewares = [`${id}-redir`];
}
}
}
for (const coolify of data.coolify) {
configureMiddleware(coolify, traefik);
}
if (Object.keys(traefik.http.routers).length === 0) {
traefik.http.routers = null;
}
if (Object.keys(traefik.http.services).length === 0) {
traefik.http.services = null;
}
return {
status: 200,
body: {
...traefik
}
};
};

View File

@@ -0,0 +1,137 @@
import { dev } from '$app/env';
import { getDomain } from '$lib/common';
import * as db from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const id = event.url.searchParams.get('id');
if (id) {
const privatePort = event.url.searchParams.get('privatePort');
const publicPort = event.url.searchParams.get('publicPort');
const type = event.url.searchParams.get('type');
const address = event.url.searchParams.get('address') || id;
let traefik = {};
if (publicPort && type && privatePort) {
if (type === 'tcp') {
traefik = {
[type]: {
routers: {
[id]: {
entrypoints: [type],
rule: `HostSNI(\`*\`)`,
service: id
}
},
services: {
[id]: {
loadbalancer: {
servers: [{ address: `${address}:${privatePort}` }]
}
}
}
}
};
} else if (type === 'http') {
const service = await db.prisma.service.findFirst({
where: { id },
include: { minio: true }
});
if (service) {
if (service.type === 'minio') {
if (service?.minio?.apiFqdn) {
const {
minio: { apiFqdn }
} = service;
const domain = getDomain(apiFqdn);
const isHttps = apiFqdn.startsWith('https://');
traefik = {
[type]: {
routers: {
[id]: {
entrypoints: [type],
rule: `Host(\`${domain}\`)`,
service: id
}
},
services: {
[id]: {
loadbalancer: {
servers: [{ url: `http://${id}:${privatePort}` }]
}
}
}
}
};
if (isHttps) {
if (dev) {
traefik[type].routers[id].tls = {
domains: {
main: `${domain}`
}
};
} else {
traefik[type].routers[id].tls = {
certresolver: 'letsencrypt'
};
}
}
}
} else {
if (service?.fqdn) {
const domain = getDomain(service.fqdn);
const isHttps = service.fqdn.startsWith('https://');
traefik = {
[type]: {
routers: {
[id]: {
entrypoints: [type],
rule: `Host(\`${domain}:${privatePort}\`)`,
service: id
}
},
services: {
[id]: {
loadbalancer: {
servers: [{ url: `http://${id}:${privatePort}` }]
}
}
}
}
};
if (isHttps) {
if (dev) {
traefik[type].routers[id].tls = {
domains: {
main: `${domain}`
}
};
} else {
traefik[type].routers[id].tls = {
certresolver: 'letsencrypt'
};
}
}
}
}
} else {
return {
status: 500
};
}
}
} else {
return {
status: 500
};
}
return {
status: 200,
body: {
...traefik
}
};
}
return {
status: 500
};
};

View File

@@ -49,6 +49,9 @@ textarea {
@apply h-12 w-96 rounded border-none bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm; @apply h-12 w-96 rounded border-none bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm;
} }
#svelte .custom-select-wrapper .spinner {
@apply text-coollabs-100;
}
#svelte .listContainer { #svelte .listContainer {
@apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-green-500 scrollbar-track-coolgray-200; @apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-green-500 scrollbar-track-coolgray-200;
} }
@@ -66,26 +69,6 @@ textarea {
select { select {
@apply h-12 w-96 rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm; @apply h-12 w-96 rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm;
} }
.svelte-select {
--background: rgb(32 32 32);
--inputColor: white;
--multiItemPadding: 0;
--multiSelectPadding: 0 0.5rem 0 0.5rem;
--border: none;
--placeholderColor: rgb(87 83 78);
--listBackground: rgb(32 32 32);
--itemColor: white;
--itemHoverBG: rgb(107 22 237);
--multiItemBG: rgb(32 32 32);
--multiClearHoverBG: transparent;
--multiClearHoverFill: rgb(239 68 68);
--multiItemActiveBG: transparent;
--multiClearBG: transparent;
--clearSelectFocusColor: white;
--clearSelectHoverColor: rgb(239 68 68);
--multiItemBorderRadius: 0.25rem;
--listShadow: none;
}
label { label {
@apply inline-block w-64 text-xs tracking-tight md:text-sm; @apply inline-block w-64 text-xs tracking-tight md:text-sm;
@@ -107,7 +90,7 @@ a {
@apply mr-4 text-base tracking-tight md:text-2xl; @apply mr-4 text-base tracking-tight md:text-2xl;
} }
.nav-main { .nav-main {
@apply fixed top-0 left-0 min-h-screen w-16 min-w-[4rem] border-r border-stone-800 bg-coolgray-200; @apply fixed top-0 left-0 min-h-screen w-16 min-w-[4rem] overflow-auto border-r border-stone-800 bg-coolgray-200 scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200 xl:overflow-visible;
} }
.nav-side { .nav-side {
@@ -356,7 +339,7 @@ a {
} }
.box-selection { .box-selection {
@apply min-w-[16rem] max-w-[24rem] justify-center rounded-lg border-transparent bg-coolgray-200 p-6 shadow-lg transition duration-150 hover:scale-105 hover:border-transparent hover:bg-coolgray-400; @apply min-w-[16rem] max-w-[24rem] justify-center rounded border-transparent bg-coolgray-200 p-6 hover:border-transparent hover:bg-coolgray-400;
} }
._toastBar { ._toastBar {

View File

@@ -1,6 +1,5 @@
import preprocess from 'svelte-preprocess'; import preprocess from 'svelte-preprocess';
import adapter from '@sveltejs/adapter-node'; import adapter from '@sveltejs/adapter-node';
const config = { const config = {
preprocess: preprocess(), preprocess: preprocess(),
kit: { kit: {
@@ -10,6 +9,12 @@ const config = {
}, },
floc: true, floc: true,
vite: { vite: {
proxy: {
'/api/v2': {
target: 'http://localhost:3001/api/v2',
changeOrigin: true
}
},
optimizeDeps: { optimizeDeps: {
exclude: ['svelte-kit-cookie-session'] exclude: ['svelte-kit-cookie-session']
}, },