Compare commits

...

75 Commits

Author SHA1 Message Date
Andras Bacsai
df58fcee16 Merge pull request #148 from coollabsio/next
v2.0.14
2022-02-18 22:18:42 +01:00
Andras Bacsai
ea3ffc429f fix: Plausible volume fixed 2022-02-18 22:05:02 +01:00
Andras Bacsai
2efca7a2b5 feat: Basic password reset form 2022-02-18 21:37:40 +01:00
Andras Bacsai
9db448a5e2 fix: follow icon 2022-02-18 15:51:59 +01:00
Andras Bacsai
feee90beef fix: Random port for certbot 2022-02-18 15:37:38 +01:00
Andras Bacsai
906a63b6b5 fix: ghToken in session now 2022-02-18 15:29:32 +01:00
Andras Bacsai
2ce64ac213 fix: hook.ts - relogin needed
updated packages
fix: Lots of typescript thingy
fix: ssl request flow
fix: proxy cleanup flow
2022-02-18 13:59:23 +01:00
Andras Bacsai
4d8bf57135 fix: Remove force SSL when doing let's encrypt request 2022-02-18 09:00:23 +01:00
Andras Bacsai
c5348ce4b3 fix: Minor fixes 2022-02-18 08:48:05 +01:00
Andras Bacsai
7f87c03f97 fix: Running state css 2022-02-18 00:25:15 +01:00
Andras Bacsai
9469f148ff fix: Grr 2022-02-18 00:22:17 +01:00
Andras Bacsai
ffb7dc4ec2 fix: SSL off for services 2022-02-18 00:17:07 +01:00
Andras Bacsai
242b8fa746 fix: Remove SSL with stop 2022-02-18 00:15:46 +01:00
Andras Bacsai
50cae5ac3b fix: Lets encrypt 2022-02-18 00:08:50 +01:00
Andras Bacsai
6a71233eb2 fix: Typo 2022-02-17 23:58:21 +01:00
Andras Bacsai
1aff8933c9 fix: Local docker host 2022-02-17 23:49:25 +01:00
Andras Bacsai
0ed87a5dfc fix: SSL app off 2022-02-17 23:47:37 +01:00
Andras Bacsai
24a6bcbd1e fix: Builder debug logging is better now
fix: www and non-www domain checker
2022-02-17 23:42:23 +01:00
Andras Bacsai
ca7f3da19d fix: Getport for letsencrypt
design: Add more scroll design
2022-02-17 22:56:15 +01:00
Andras Bacsai
bf047e2a3c feat: Dual certificates
desing: Lots of design/css updates
version++
2022-02-17 22:14:06 +01:00
Andras Bacsai
4454287be9 Merge branch 'main' into next 2022-02-17 10:38:47 +01:00
Andras Bacsai
3bd2183655 Merge pull request #143 from coollabsio/fix/login-quickfix
v2.0.13
2022-02-17 10:23:26 +01:00
Andras Bacsai
1f7080e8f8 fix: Login issues 2022-02-17 10:22:38 +01:00
Andras Bacsai
8b20761e8b fix: Buggy svelte-kit-cookie-session 2022-02-17 10:11:46 +01:00
Andras Bacsai
655d0b5d5f fix: Secure cookie disabled by default 2022-02-16 17:13:24 +01:00
Andras Bacsai
91849cdd3a fix www - non-www ssl 2022-02-16 00:00:49 +01:00
Andras Bacsai
df25a694c3 fix: Add no user redis to uri 2022-02-15 23:59:26 +01:00
Andras Bacsai
eabaca145e Fix 2022-02-15 23:38:10 +01:00
Andras Bacsai
2f0e458765 Expand flag for lets encrypt 2022-02-15 23:12:28 +01:00
Andras Bacsai
ff8037f231 feat: Generate www & non-www SSL certs 2022-02-15 23:02:03 +01:00
Andras Bacsai
a116028e1b Merge pull request #137 from coollabsio/next
v2.0.12
2022-02-15 22:03:10 +01:00
Andras Bacsai
e606a02b29 Update packages 2022-02-15 21:49:14 +01:00
Andras Bacsai
531c712ea5 fix: Some nasty bug
fix: Automatic reconfiguration of all services and service proxies
2022-02-15 21:44:36 +01:00
Andras Bacsai
3ae7624361 fix: Cleanup images 2022-02-15 20:37:00 +01:00
Andras Bacsai
fed83462fa remove unncesarry logs 2022-02-15 20:34:40 +01:00
Andras Bacsai
58c9f937c5 fix: Cleanup every 10 mins 2022-02-15 19:14:23 +01:00
Andras Bacsai
5d14b9209d fix: Coolify image cleanup 2022-02-15 19:13:50 +01:00
Andras Bacsai
305a95fa74 fix: TransactionIds 2022-02-15 18:47:20 +01:00
Andras Bacsai
b29c1e702a fix: isDomainConfigured 2022-02-15 18:04:58 +01:00
Andras Bacsai
b04d75ab08 dx: only allow cleanup in production 2022-02-15 17:46:37 +01:00
Andras Bacsai
25abfaadb9 chore: version++ 2022-02-15 17:45:58 +01:00
Andras Bacsai
1df81b8698 fix: Unnecessary proxy restart
feat: Restart proxy
2022-02-15 17:45:20 +01:00
Andras Bacsai
4487846fd7 feat: Force restart coolify proxy
fix: Cannot configure coolify proxy
2022-02-15 15:55:55 +01:00
Andras Bacsai
86918f5160 Merge pull request #134 from coollabsio/next
fix: Error with follow logs
2022-02-15 10:59:58 +01:00
Andras Bacsai
bc723b3f15 fix: Error with follow logs 2022-02-15 10:57:58 +01:00
Andras Bacsai
1881e646d4 Merge pull request #132 from coollabsio/next
v2.0.11
2022-02-15 10:51:25 +01:00
Andras Bacsai
aa98808a1a fix: Typo 2022-02-15 10:46:44 +01:00
Andras Bacsai
f9a2232703 fix: Small fixes 2022-02-15 10:42:26 +01:00
Andras Bacsai
19d6be8663 fix: Load more button 2022-02-15 10:35:22 +01:00
Andras Bacsai
0eb7c890ad fix: GitHub sync PR's 2022-02-15 10:25:23 +01:00
Andras Bacsai
7bfa68aa58 chore: version++ 2022-02-15 09:40:04 +01:00
Andras Bacsai
857a38050e fix: Window error in SSR 2022-02-15 09:39:45 +01:00
Andras Bacsai
c5b7f92caf feat: Follow logs 2022-02-15 09:38:16 +01:00
Andras Bacsai
df31ffd7fb Merge pull request #127 from SaraVieira/logs-improvements
UX improvements for the log page
2022-02-15 09:02:43 +01:00
Andras Bacsai
0df0322d36 Merge pull request #129 from coollabsio/next
v2.0.10
2022-02-15 08:52:45 +01:00
Andras Bacsai
260552322d chore: Version++ 2022-02-15 08:50:17 +01:00
Andras Bacsai
88ef6496a2 fix: Coolify proxy start 2022-02-15 08:50:02 +01:00
Andras Bacsai
bdf123bf7b fix: Stopping service without proxy 2022-02-15 08:38:16 +01:00
Andras Bacsai
8fc3760eef fix: Error handling 2022-02-14 16:52:00 +01:00
Sara Vieira
5656f6f709 add env example back 2022-02-14 16:01:08 +01:00
Andras Bacsai
53e7e8b77e version bump 2022-02-14 15:59:00 +01:00
Andras Bacsai
b990915b7a fix: Typo 2022-02-14 15:58:44 +01:00
Sara Vieira
15b7822ffd put db in the right place 2022-02-14 15:58:42 +01:00
Sara Vieira
cfa28419cb add yarn lock to gitignore 2022-02-14 15:56:28 +01:00
Sara Vieira
30ef0d2a3a some log improvements 2022-02-14 15:55:19 +01:00
Andras Bacsai
755f99200a Create README.md 2022-02-14 11:32:19 +01:00
Andras Bacsai
7af79ed3a2 Update README.md 2022-02-14 11:29:56 +01:00
Andras Bacsai
2971e14269 Merge pull request #123 from coollabsio/next
v2.0.8
2022-02-14 10:09:07 +01:00
Andras Bacsai
01954aaf30 Merge pull request #122 from habibyuri/patch-1
Allow Docker Apache write permissions
2022-02-14 10:03:31 +01:00
Andras Bacsai
da018a8f2a fix: Branch used does not throw error 2022-02-14 09:57:17 +01:00
Andras Bacsai
77400bbbb0 fix: Truncate git clone errors 2022-02-14 09:48:46 +01:00
Andras Bacsai
3c3333d3df fix: Validate secrets 2022-02-14 09:47:09 +01:00
Andras Bacsai
4963bd4144 - Bump version
- Fix type
2022-02-14 09:28:56 +01:00
Andras Bacsai
b4a418dded - Rename error handler.
- Truncate errors.
- Better error tags, release version etc.
2022-02-14 09:28:37 +01:00
Yuri Habib
a724b0daee Allow Docker Apache write permissions
I had a permission problem using PHP scripts, 
Running ` chown -R www-data /var/www/html ` after SSHing into the docker container helped fixing the problem.
2022-02-13 14:58:13 -08:00
157 changed files with 2050 additions and 1464 deletions

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@ node_modules
/build /build
/.svelte-kit /.svelte-kit
/package /package
/yarn.lock
.env .env
.env.prod .env.prod

View File

@@ -71,9 +71,7 @@ You can use the official ones or your self hosted version!
## Roadmap ## Roadmap
[See the Roadmap here](https://github.com/coollabsio/coolify/projects/1) [See the Roadmap here](https://github.com/orgs/coollabsio/projects/3/views/8)
(Will be updated soon!)
## License ## License

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.0.7", "version": "2.0.14",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0", "dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
@@ -25,12 +25,12 @@
"prepare": "husky install" "prepare": "husky install"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "1.0.0-next.67", "@sveltejs/adapter-node": "1.0.0-next.68",
"@sveltejs/adapter-static": "1.0.0-next.27", "@sveltejs/adapter-static": "1.0.0-next.28",
"@sveltejs/kit": "1.0.0-next.259", "@sveltejs/kit": "1.0.0-next.278",
"@types/bcrypt": "5.0.0", "@types/bcrypt": "5.0.0",
"@types/js-cookie": "3.0.1", "@types/js-cookie": "3.0.1",
"@types/node": "17.0.16", "@types/node": "17.0.18",
"@types/node-forge": "1.0.0", "@types/node-forge": "1.0.0",
"@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",
@@ -41,16 +41,16 @@
"eslint-config-prettier": "8.3.0", "eslint-config-prettier": "8.3.0",
"eslint-plugin-svelte3": "3.2.1", "eslint-plugin-svelte3": "3.2.1",
"husky": "7.0.4", "husky": "7.0.4",
"lint-staged": "12.3.3", "lint-staged": "12.3.4",
"postcss": "8.4.6", "postcss": "8.4.6",
"prettier": "2.5.1", "prettier": "2.5.1",
"prettier-plugin-svelte": "2.6.0", "prettier-plugin-svelte": "2.6.0",
"prettier-plugin-tailwindcss": "0.1.7", "prettier-plugin-tailwindcss": "0.1.7",
"prisma": "3.9.1", "prisma": "3.9.2",
"svelte": "3.46.4", "svelte": "3.46.4",
"svelte-check": "2.4.3", "svelte-check": "2.4.3",
"svelte-preprocess": "4.10.3", "svelte-preprocess": "4.10.3",
"tailwindcss": "3.0.19", "tailwindcss": "3.0.23",
"ts-node": "10.5.0", "ts-node": "10.5.0",
"tslib": "2.3.1", "tslib": "2.3.1",
"typescript": "4.5.5" "typescript": "4.5.5"
@@ -58,10 +58,10 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@iarna/toml": "2.2.5", "@iarna/toml": "2.2.5",
"@prisma/client": "3.9.1", "@prisma/client": "3.9.2",
"@sentry/node": "6.17.6", "@sentry/node": "6.17.9",
"bcrypt": "5.0.1", "bcrypt": "5.0.1",
"bullmq": "1.69.0", "bullmq": "1.73.0",
"compare-versions": "4.1.3", "compare-versions": "4.1.3",
"cookie": "0.4.2", "cookie": "0.4.2",
"cuid": "2.1.8", "cuid": "2.1.8",
@@ -69,14 +69,15 @@
"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.0.0", "get-port": "6.1.0",
"got": "12.0.1", "got": "12.0.1",
"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",
"node-forge": "1.2.1", "node-forge": "1.2.1",
"svelte-kit-cookie-session": "2.0.3", "svelte-kit-cookie-session": "2.1.2",
"unique-names-generator": "4.6.0" "tailwindcss-scrollbar": "^0.1.0",
"unique-names-generator": "4.7.1"
}, },
"prisma": { "prisma": {
"seed": "node prisma/seed.cjs" "seed": "node prisma/seed.cjs"

200
pnpm-lock.yaml generated
View File

@@ -2,21 +2,21 @@ lockfileVersion: 5.3
specifiers: specifiers:
'@iarna/toml': 2.2.5 '@iarna/toml': 2.2.5
'@prisma/client': 3.9.1 '@prisma/client': 3.9.2
'@sentry/node': 6.17.6 '@sentry/node': 6.17.9
'@sveltejs/adapter-node': 1.0.0-next.67 '@sveltejs/adapter-node': 1.0.0-next.68
'@sveltejs/adapter-static': 1.0.0-next.27 '@sveltejs/adapter-static': 1.0.0-next.28
'@sveltejs/kit': 1.0.0-next.259 '@sveltejs/kit': 1.0.0-next.278
'@types/bcrypt': 5.0.0 '@types/bcrypt': 5.0.0
'@types/js-cookie': 3.0.1 '@types/js-cookie': 3.0.1
'@types/node': 17.0.16 '@types/node': 17.0.18
'@types/node-forge': 1.0.0 '@types/node-forge': 1.0.0
'@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.6.3 '@zerodevx/svelte-toast': 0.6.3
autoprefixer: 10.4.2 autoprefixer: 10.4.2
bcrypt: 5.0.1 bcrypt: 5.0.1
bullmq: 1.69.0 bullmq: 1.73.0
compare-versions: 4.1.3 compare-versions: 4.1.3
cookie: 0.4.2 cookie: 0.4.2
cross-var: 1.1.0 cross-var: 1.1.0
@@ -28,35 +28,36 @@ specifiers:
eslint-config-prettier: 8.3.0 eslint-config-prettier: 8.3.0
eslint-plugin-svelte3: 3.2.1 eslint-plugin-svelte3: 3.2.1
generate-password: 1.7.0 generate-password: 1.7.0
get-port: 6.0.0 get-port: 6.1.0
got: 12.0.1 got: 12.0.1
husky: 7.0.4 husky: 7.0.4
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.3.3 lint-staged: 12.3.4
node-forge: 1.2.1 node-forge: 1.2.1
postcss: 8.4.6 postcss: 8.4.6
prettier: 2.5.1 prettier: 2.5.1
prettier-plugin-svelte: 2.6.0 prettier-plugin-svelte: 2.6.0
prettier-plugin-tailwindcss: 0.1.7 prettier-plugin-tailwindcss: 0.1.7
prisma: 3.9.1 prisma: 3.9.2
svelte: 3.46.4 svelte: 3.46.4
svelte-check: 2.4.3 svelte-check: 2.4.3
svelte-kit-cookie-session: 2.0.3 svelte-kit-cookie-session: 2.1.2
svelte-preprocess: 4.10.3 svelte-preprocess: 4.10.3
tailwindcss: 3.0.19 tailwindcss: 3.0.23
tailwindcss-scrollbar: ^0.1.0
ts-node: 10.5.0 ts-node: 10.5.0
tslib: 2.3.1 tslib: 2.3.1
typescript: 4.5.5 typescript: 4.5.5
unique-names-generator: 4.6.0 unique-names-generator: 4.7.1
dependencies: dependencies:
'@iarna/toml': 2.2.5 '@iarna/toml': 2.2.5
'@prisma/client': 3.9.1_prisma@3.9.1 '@prisma/client': 3.9.2_prisma@3.9.2
'@sentry/node': 6.17.6 '@sentry/node': 6.17.9
bcrypt: 5.0.1 bcrypt: 5.0.1
bullmq: 1.69.0 bullmq: 1.73.0
compare-versions: 4.1.3 compare-versions: 4.1.3
cookie: 0.4.2 cookie: 0.4.2
cuid: 2.1.8 cuid: 2.1.8
@@ -64,22 +65,23 @@ dependencies:
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.0.0 get-port: 6.1.0
got: 12.0.1 got: 12.0.1
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
node-forge: 1.2.1 node-forge: 1.2.1
svelte-kit-cookie-session: 2.0.3 svelte-kit-cookie-session: 2.1.2
unique-names-generator: 4.6.0 tailwindcss-scrollbar: 0.1.0_tailwindcss@3.0.23
unique-names-generator: 4.7.1
devDependencies: devDependencies:
'@sveltejs/adapter-node': 1.0.0-next.67 '@sveltejs/adapter-node': 1.0.0-next.68
'@sveltejs/adapter-static': 1.0.0-next.27 '@sveltejs/adapter-static': 1.0.0-next.28
'@sveltejs/kit': 1.0.0-next.259_svelte@3.46.4 '@sveltejs/kit': 1.0.0-next.278_svelte@3.46.4
'@types/bcrypt': 5.0.0 '@types/bcrypt': 5.0.0
'@types/js-cookie': 3.0.1 '@types/js-cookie': 3.0.1
'@types/node': 17.0.16 '@types/node': 17.0.18
'@types/node-forge': 1.0.0 '@types/node-forge': 1.0.0
'@typescript-eslint/eslint-plugin': 4.31.1_5d7752337e5ea49772097d8af1823bf9 '@typescript-eslint/eslint-plugin': 4.31.1_5d7752337e5ea49772097d8af1823bf9
'@typescript-eslint/parser': 4.31.1_eslint@7.32.0+typescript@4.5.5 '@typescript-eslint/parser': 4.31.1_eslint@7.32.0+typescript@4.5.5
@@ -90,17 +92,17 @@ devDependencies:
eslint-config-prettier: 8.3.0_eslint@7.32.0 eslint-config-prettier: 8.3.0_eslint@7.32.0
eslint-plugin-svelte3: 3.2.1_eslint@7.32.0+svelte@3.46.4 eslint-plugin-svelte3: 3.2.1_eslint@7.32.0+svelte@3.46.4
husky: 7.0.4 husky: 7.0.4
lint-staged: 12.3.3 lint-staged: 12.3.4
postcss: 8.4.6 postcss: 8.4.6
prettier: 2.5.1 prettier: 2.5.1
prettier-plugin-svelte: 2.6.0_prettier@2.5.1+svelte@3.46.4 prettier-plugin-svelte: 2.6.0_prettier@2.5.1+svelte@3.46.4
prettier-plugin-tailwindcss: 0.1.7_prettier@2.5.1 prettier-plugin-tailwindcss: 0.1.7_prettier@2.5.1
prisma: 3.9.1 prisma: 3.9.2
svelte: 3.46.4 svelte: 3.46.4
svelte-check: 2.4.3_postcss@8.4.6+svelte@3.46.4 svelte-check: 2.4.3_postcss@8.4.6+svelte@3.46.4
svelte-preprocess: 4.10.3_88b359da5cac6d8f6ee1bbb7080a3fa9 svelte-preprocess: 4.10.3_88b359da5cac6d8f6ee1bbb7080a3fa9
tailwindcss: 3.0.19_27d966e3a2f4b84fbc8a2f9653dbb362 tailwindcss: 3.0.23_c940fbabf228b85b1c73d314b43e31f1
ts-node: 10.5.0_99ae9436e134a034c8d45fdd98ebbf22 ts-node: 10.5.0_f3bd4037939c2ed2942ba074291f8ef2
tslib: 2.3.1 tslib: 2.3.1
typescript: 4.5.5 typescript: 4.5.5
@@ -250,10 +252,10 @@ packages:
fastq: 1.13.0 fastq: 1.13.0
dev: true dev: true
/@prisma/client/3.9.1_prisma@3.9.1: /@prisma/client/3.9.2_prisma@3.9.2:
resolution: resolution:
{ {
integrity: sha512-aLwfXKLvL+loQ0IuPPCXkcq8cXBg1IeoHHa5lqQu3dJHdj45wnislA/Ny4UxRQjD5FXqrfAb8sWtF+jhdmjFTg== integrity: sha512-VlEIYVMyfFZHbVBOlunPl47gmP/Z0zzPjPj8I7uKEIaABqrUy50ru3XS0aZd8GFvevVwt7p91xxkUjNjrWhKAQ==
} }
engines: { node: '>=12.6' } engines: { node: '>=12.6' }
requiresBuild: true requiresBuild: true
@@ -264,7 +266,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@prisma/engines-version': 3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009 '@prisma/engines-version': 3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009
prisma: 3.9.1 prisma: 3.9.2
dev: false dev: false
/@prisma/engines-version/3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009: /@prisma/engines-version/3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009:
@@ -293,56 +295,56 @@ packages:
picomatch: 2.3.0 picomatch: 2.3.0
dev: true dev: true
/@sentry/core/6.17.6: /@sentry/core/6.17.9:
resolution: resolution:
{ {
integrity: sha512-wSNsQSqsW8vQ2HEvUEXYOJnzTyVDSWbyH4RHrWV1pQM8zqGx/qfz0sKFM5XFnE9ZeaXKL8LXV3v5i73v+z8lew== integrity: sha512-14KalmTholGUtgdh9TklO+jUpyQ/D3OGkhlH1rnGQGoJgFy2eYm+s+MnUEMxFdGIUCz5kOteuNqYZxaDmFagpQ==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dependencies: dependencies:
'@sentry/hub': 6.17.6 '@sentry/hub': 6.17.9
'@sentry/minimal': 6.17.6 '@sentry/minimal': 6.17.9
'@sentry/types': 6.17.6 '@sentry/types': 6.17.9
'@sentry/utils': 6.17.6 '@sentry/utils': 6.17.9
tslib: 1.14.1 tslib: 1.14.1
dev: false dev: false
/@sentry/hub/6.17.6: /@sentry/hub/6.17.9:
resolution: resolution:
{ {
integrity: sha512-Ps9nk+DoFia8jhZ1lucdRE0vDx8hqXOsKXJE8a3hK/Ndki0J9jedYqBeLqSgiFG4qRjXpNFcD6TEM6tnQrv5lw== integrity: sha512-34EdrweWDbBV9EzEFIXcO+JeoyQmKzQVJxpTKZoJA6PUwf2NrndaUdjlkDEtBEzjuLUTxhLxtOzEsYs1O6RVcg==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dependencies: dependencies:
'@sentry/types': 6.17.6 '@sentry/types': 6.17.9
'@sentry/utils': 6.17.6 '@sentry/utils': 6.17.9
tslib: 1.14.1 tslib: 1.14.1
dev: false dev: false
/@sentry/minimal/6.17.6: /@sentry/minimal/6.17.9:
resolution: resolution:
{ {
integrity: sha512-PLGf8WlhtdHuY6ofwYR3nyClr/TYHHAW6i0r62OZCOXTqnFPJorZpAz3VCCP2jMJmbgVbo03wN+u/xAA/zwObA== integrity: sha512-T3PMCHcKk6lkZq6zKgANrYJJxXBXKOe+ousV1Fas1rVBMv7dtKfsa4itqQHszcW9shusPDiaQKIJ4zRLE5LKmg==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dependencies: dependencies:
'@sentry/hub': 6.17.6 '@sentry/hub': 6.17.9
'@sentry/types': 6.17.6 '@sentry/types': 6.17.9
tslib: 1.14.1 tslib: 1.14.1
dev: false dev: false
/@sentry/node/6.17.6: /@sentry/node/6.17.9:
resolution: resolution:
{ {
integrity: sha512-T1s0yPbGvYpoh9pJgLvpy7s+jVwCyf0ieEoN9rSbnPwbi2vm6MfoV5wtGrE0cBHTPgnyOMv+zq4Q3ww6dfr7Pw== integrity: sha512-jbn+q7qPGOh6D7nYoYGaAlmuvMDpQmyMwBtUVYybuZp2AALe43O3Z4LtoJ+1+F31XowpsIPZx1mwNs4ZrILskA==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dependencies: dependencies:
'@sentry/core': 6.17.6 '@sentry/core': 6.17.9
'@sentry/hub': 6.17.6 '@sentry/hub': 6.17.9
'@sentry/tracing': 6.17.6 '@sentry/tracing': 6.17.9
'@sentry/types': 6.17.6 '@sentry/types': 6.17.9
'@sentry/utils': 6.17.6 '@sentry/utils': 6.17.9
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
@@ -351,36 +353,36 @@ packages:
- supports-color - supports-color
dev: false dev: false
/@sentry/tracing/6.17.6: /@sentry/tracing/6.17.9:
resolution: resolution:
{ {
integrity: sha512-+h5ov+zEm5WH9+vmFfdT4EIqBOW7Tggzh0BDz8QRStRc2JbvEiSZDs+HlsycBwWMQi/ucJs93FPtNnWjW+xvBw== integrity: sha512-5Rb/OS4ryNJLvz2nv6wyjwhifjy6veqaF9ffLrwFYij/WDy7m62ASBblxgeiI3fbPLX0aBRFWIJAq1vko26+AQ==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dependencies: dependencies:
'@sentry/hub': 6.17.6 '@sentry/hub': 6.17.9
'@sentry/minimal': 6.17.6 '@sentry/minimal': 6.17.9
'@sentry/types': 6.17.6 '@sentry/types': 6.17.9
'@sentry/utils': 6.17.6 '@sentry/utils': 6.17.9
tslib: 1.14.1 tslib: 1.14.1
dev: false dev: false
/@sentry/types/6.17.6: /@sentry/types/6.17.9:
resolution: resolution:
{ {
integrity: sha512-peGM873lDJtHd/jwW9Egr/hhxLuF0bcPIf2kMZlvEvW/G5GCbuaCR4ArQJlh7vQyma+NLn/XdojpJkC0TomKrw== integrity: sha512-xuulX6qUCL14ayEOh/h6FUIvZtsi1Bx34dSOaWDrjXUOJHJAM7214uiqW1GZxPJ13YuaUIubjTSfDmSQ9CBzTw==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dev: false dev: false
/@sentry/utils/6.17.6: /@sentry/utils/6.17.9:
resolution: resolution:
{ {
integrity: sha512-RI797N8Ax5yuKUftVX6dc0XmXqo5CN7XqJYPFzYC8udutQ4L8ZYadtUcqNsdz1ZQxl+rp0XK9Q6wjoWmsI2RXA== integrity: sha512-4eo9Z3JlJCGlGrQRbtZWL+L9NnlUXgTbfK3Lk7oO8D1ev8R5b5+iE6tZHTvU5rQRcq6zu+POT+tK5u9oxc/rnQ==
} }
engines: { node: '>=6' } engines: { node: '>=6' }
dependencies: dependencies:
'@sentry/types': 6.17.6 '@sentry/types': 6.17.9
tslib: 1.14.1 tslib: 1.14.1
dev: false dev: false
@@ -392,28 +394,28 @@ packages:
engines: { node: '>=10' } engines: { node: '>=10' }
dev: false dev: false
/@sveltejs/adapter-node/1.0.0-next.67: /@sveltejs/adapter-node/1.0.0-next.68:
resolution: resolution:
{ {
integrity: sha512-+LuLn91xARZsRANiQNIIDpMMncUTnP2pJc8tyL+FdpVvs5UtlvkYJpeCBPFqjjseRpIIbi8Slu89GCdrRXBDUg== integrity: sha512-MiEjtl15Aupm6bjirVlq0kkc9AL8qDXz/blsh4jYMsaiidmcEHeDgfZQFM5YiXy95DbxV30MAkhwCQiYK/J8Kw==
} }
dependencies: dependencies:
tiny-glob: 0.2.9 tiny-glob: 0.2.9
dev: true dev: true
/@sveltejs/adapter-static/1.0.0-next.27: /@sveltejs/adapter-static/1.0.0-next.28:
resolution: resolution:
{ {
integrity: sha512-dcN1p1D7ZY/a9SClfN14mgm9pyWbLxdwM9gzPMZG6xXOoqMtwI03aZOFgGGumHPdv+XcGRZM96vUSRoDm6vBJQ== integrity: sha512-c4xLyeSwnbGQxe4f1SLpHTbxZDm3TEr43scR3tOlVgQN+mnAL9aDdl3nTtdzWmrUDmDEmY4GriAwLyFLZuINLw==
} }
dependencies: dependencies:
tiny-glob: 0.2.9 tiny-glob: 0.2.9
dev: true dev: true
/@sveltejs/kit/1.0.0-next.259_svelte@3.46.4: /@sveltejs/kit/1.0.0-next.278_svelte@3.46.4:
resolution: resolution:
{ {
integrity: sha512-+Tss6cQXmpi4Jno/ZP0zJ3INBLMED+WeW4UI81tmexheC76Y2p+cbInneKO/REx/8QFo1iroYrWAUkZPsOg8Ew== integrity: sha512-WT93Wnu05X9WG9BMMk/dj0gy6R7iXm9aXRDVgmIl9z8jT2ukejgmkhi5IwBYrK0OMIUALRVfukn+iy+srPc91Q==
} }
engines: { node: '>=14.13' } engines: { node: '>=14.13' }
hasBin: true hasBin: true
@@ -502,7 +504,7 @@ packages:
integrity: sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw== integrity: sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==
} }
dependencies: dependencies:
'@types/node': 17.0.16 '@types/node': 17.0.18
dev: true dev: true
/@types/cacheable-request/6.0.2: /@types/cacheable-request/6.0.2:
@@ -513,7 +515,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.16 '@types/node': 17.0.18
'@types/responselike': 1.0.0 '@types/responselike': 1.0.0
dev: false dev: false
@@ -544,7 +546,7 @@ packages:
integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg== integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==
} }
dependencies: dependencies:
'@types/node': 17.0.16 '@types/node': 17.0.18
dev: false dev: false
/@types/node-forge/1.0.0: /@types/node-forge/1.0.0:
@@ -553,13 +555,13 @@ packages:
integrity: sha512-h0bgwPKq5u99T9Gor4qtV1lCZ41xNkai0pie1n/a2mh2/4+jENWOlo7AJ4YKxTZAnSZ8FRurUpdIN7ohaPPuHA== integrity: sha512-h0bgwPKq5u99T9Gor4qtV1lCZ41xNkai0pie1n/a2mh2/4+jENWOlo7AJ4YKxTZAnSZ8FRurUpdIN7ohaPPuHA==
} }
dependencies: dependencies:
'@types/node': 17.0.16 '@types/node': 17.0.18
dev: true dev: true
/@types/node/17.0.16: /@types/node/17.0.18:
resolution: resolution:
{ {
integrity: sha512-ydLaGVfQOQ6hI1xK2A5nVh8bl0OGoIfYMxPWHqqYe9bTkWCfqiVvZoh2I/QF2sNSkZzZyROBoTefIEI+PB6iIA== integrity: sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==
} }
/@types/parse-json/4.0.0: /@types/parse-json/4.0.0:
@@ -582,7 +584,7 @@ packages:
integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
} }
dependencies: dependencies:
'@types/node': 17.0.16 '@types/node': 17.0.18
dev: false dev: false
/@types/sass/1.16.1: /@types/sass/1.16.1:
@@ -591,7 +593,7 @@ packages:
integrity: sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ== integrity: sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ==
} }
dependencies: dependencies:
'@types/node': 17.0.16 '@types/node': 17.0.18
dev: true dev: true
/@typescript-eslint/eslint-plugin/4.31.1_5d7752337e5ea49772097d8af1823bf9: /@typescript-eslint/eslint-plugin/4.31.1_5d7752337e5ea49772097d8af1823bf9:
@@ -1746,10 +1748,10 @@ packages:
ieee754: 1.2.1 ieee754: 1.2.1
dev: false dev: false
/bullmq/1.69.0: /bullmq/1.73.0:
resolution: resolution:
{ {
integrity: sha512-1aIO7bN0HQeADWoXa+I72GgofvoBFRs/kcoveB3KN8ytKv7QJbGhtks0pYNhHn/P9H3OWHWDccpNEfnv3VGfcw== integrity: sha512-+BF7yeGagYD/iMkM3FA8Wvb3j3MyKE/OdXv404+nQjUsKXfL7PbqX5NSA9lBtFzOdyFx9ZWyKRnBwuGQsLfM0w==
} }
dependencies: dependencies:
cron-parser: 2.18.0 cron-parser: 2.18.0
@@ -3114,10 +3116,10 @@ packages:
engines: { node: '>=8' } engines: { node: '>=8' }
dev: false dev: false
/get-port/6.0.0: /get-port/6.1.0:
resolution: resolution:
{ {
integrity: sha512-qSVkVF6Eq1GdL/cBNiFuP4nUHMF7OEMTqEjC6alR2N90u8BFOoO0PFhNTX2QtAUoGrz8NnrSWj85TZ8YXZ6LOA== integrity: sha512-JKnPFW/G2ZRirH/25sLK1aLBQktJfQLixzMMuMBP8A2G/ivSaIwdTnlJeO7PWeyhyIGVorezNf6+CXZU9i0cIQ==
} }
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
dev: false dev: false
@@ -3718,10 +3720,10 @@ packages:
resolution: { integrity: sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= } resolution: { integrity: sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= }
dev: true dev: true
/lint-staged/12.3.3: /lint-staged/12.3.4:
resolution: resolution:
{ {
integrity: sha512-OqcLsqcPOqzvsfkxjeBpZylgJ3SRG1RYqc9LxC6tkt6tNsq1bNVkAixBwX09f6CobcHswzqVOCBpFR1Fck0+ag== integrity: sha512-yv/iK4WwZ7/v0GtVkNb3R82pdL9M+ScpIbJLJNyCXkJ1FGaXvRCOg/SeL59SZtPpqZhE7BD6kPKFLIDUhDx2/w==
} }
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
@@ -4397,7 +4399,7 @@ packages:
dependencies: dependencies:
import-cwd: 3.0.0 import-cwd: 3.0.0
lilconfig: 2.0.4 lilconfig: 2.0.4
ts-node: 10.5.0_99ae9436e134a034c8d45fdd98ebbf22 ts-node: 10.5.0_f3bd4037939c2ed2942ba074291f8ef2
yaml: 1.10.2 yaml: 1.10.2
dev: true dev: true
@@ -4486,10 +4488,10 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/prisma/3.9.1: /prisma/3.9.2:
resolution: resolution:
{ {
integrity: sha512-IGcJAu5LzlFv+i+NNhOEh1J1xVVttsVdRBxmrMN7eIH+7mRN6L89Hz1npUAiz4jOpNlHC7n9QwaOYZGxTqlwQw== integrity: sha512-i9eK6cexV74OgeWaH3+e6S07kvC9jEZTl6BqtBH398nlCU0tck7mE9dicY6YQd+euvMjjCtY89q4NgmaPnUsSg==
} }
engines: { node: '>=12.6' } engines: { node: '>=12.6' }
hasBin: true hasBin: true
@@ -5203,10 +5205,10 @@ packages:
svelte: 3.46.4 svelte: 3.46.4
dev: true dev: true
/svelte-kit-cookie-session/2.0.3: /svelte-kit-cookie-session/2.1.2:
resolution: resolution:
{ {
integrity: sha512-jOBUpvrkt/fI5zaqWsWHDDIGnfuPQt3/PC1FDJpEV/E/hA8DvGO52esFny1HvUAP1tkVZ5FU3k6Yd3HyQH5oUQ== integrity: sha512-PfxIWDhiyYWu7iKlL0GHpmwDrdFh+rX/WmBzOuvctF25UqngIo9MCiegWBSBLE1RBwNs5UqaIeI8+vligmY07g==
} }
dev: false dev: false
@@ -5288,16 +5290,26 @@ packages:
strip-ansi: 6.0.1 strip-ansi: 6.0.1
dev: true dev: true
/tailwindcss/3.0.19_27d966e3a2f4b84fbc8a2f9653dbb362: /tailwindcss-scrollbar/0.1.0_tailwindcss@3.0.23:
resolution: resolution:
{ {
integrity: sha512-rjsdfz/qZya5xQ0OVynEMETgWq1CacmftgMYeXXh6bRM5vxsNwRSbMJsCCIjq/w67om9VP/AFMolOwiE+5VKig== integrity: sha512-egipxw4ooQDh94x02XQpPck0P0sfwazwoUGfA9SedPATIuYDR+6qe8d31Gl7YsSMRiOKDkkqfI0kBvEw9lT/Hg==
}
peerDependencies:
tailwindcss: '>= 2.x.x'
dependencies:
tailwindcss: 3.0.23_c940fbabf228b85b1c73d314b43e31f1
dev: false
/tailwindcss/3.0.23_c940fbabf228b85b1c73d314b43e31f1:
resolution:
{
integrity: sha512-+OZOV9ubyQ6oI2BXEhzw4HrqvgcARY38xv3zKcjnWtMIZstEsXdI9xftd1iB7+RbOnj2HOEzkA0OyB5BaSxPQA==
} }
engines: { node: '>=12.13.0' } engines: { node: '>=12.13.0' }
hasBin: true hasBin: true
peerDependencies: peerDependencies:
autoprefixer: ^10.0.2 autoprefixer: ^10.0.2
postcss: ^8.0.9
dependencies: dependencies:
arg: 5.0.1 arg: 5.0.1
autoprefixer: 10.4.2_postcss@8.4.6 autoprefixer: 10.4.2_postcss@8.4.6
@@ -5408,7 +5420,7 @@ packages:
engines: { node: '>=0.10.0' } engines: { node: '>=0.10.0' }
dev: true dev: true
/ts-node/10.5.0_99ae9436e134a034c8d45fdd98ebbf22: /ts-node/10.5.0_f3bd4037939c2ed2942ba074291f8ef2:
resolution: resolution:
{ {
integrity: sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw== integrity: sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw==
@@ -5430,7 +5442,7 @@ 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.16 '@types/node': 17.0.18
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
@@ -5516,10 +5528,10 @@ packages:
function.name: 1.0.13 function.name: 1.0.13
dev: false dev: false
/unique-names-generator/4.6.0: /unique-names-generator/4.7.1:
resolution: resolution:
{ {
integrity: sha512-m0fke1emBeT96UYn2psPQYwljooDWRTKt9oUZ5vlt88ZFMBGxqwPyLHXwCfkbgdm8jzioCp7oIpo6KdM+fnUlQ== integrity: sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==
} }
engines: { node: '>=8' } engines: { node: '>=8' }
dev: false dev: false

View File

@@ -0,0 +1,47 @@
-- 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,
"proxyPassword" TEXT NOT NULL,
"proxyUser" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_Setting" ("createdAt", "fqdn", "id", "isRegistrationEnabled", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "fqdn", "id", "isRegistrationEnabled", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting";
DROP TABLE "Setting";
ALTER TABLE "new_Setting" RENAME TO "Setting";
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
CREATE TABLE "new_ApplicationSettings" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT NOT NULL,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"debug" BOOLEAN NOT NULL DEFAULT false,
"previews" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_ApplicationSettings" ("applicationId", "createdAt", "debug", "id", "previews", "updatedAt") SELECT "applicationId", "createdAt", "debug", "id", "previews", "updatedAt" FROM "ApplicationSettings";
DROP TABLE "ApplicationSettings";
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
CREATE TABLE "new_Service" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"fqdn" TEXT,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"type" TEXT,
"version" TEXT,
"destinationDockerId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Service_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_Service" ("createdAt", "destinationDockerId", "fqdn", "id", "name", "type", "updatedAt", "version") SELECT "createdAt", "destinationDockerId", "fqdn", "id", "name", "type", "updatedAt", "version" FROM "Service";
DROP TABLE "Service";
ALTER TABLE "new_Service" RENAME TO "Service";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -11,6 +11,7 @@ model Setting {
id String @id @default(cuid()) id String @id @default(cuid())
fqdn String? @unique fqdn String? @unique
isRegistrationEnabled Boolean @default(false) isRegistrationEnabled Boolean @default(false)
dualCerts Boolean @default(false)
proxyPassword String proxyPassword String
proxyUser String proxyUser String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -97,6 +98,7 @@ model ApplicationSettings {
id String @id @default(cuid()) id String @id @default(cuid())
application Application @relation(fields: [applicationId], references: [id]) application Application @relation(fields: [applicationId], references: [id])
applicationId String @unique applicationId String @unique
dualCerts Boolean @default(false)
debug Boolean @default(false) debug Boolean @default(false)
previews Boolean @default(false) previews Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -234,6 +236,7 @@ model Service {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
fqdn String? fqdn String?
dualCerts Boolean @default(false)
type String? type String?
version String? version String?
teams Team[] teams Team[]

View File

@@ -1,74 +1,26 @@
/// <reference types="@sveltejs/kit" /> /// <reference types="@sveltejs/kit" />
interface Cookies {
teamId?: string; declare namespace App {
gitlabToken?: string; interface Locals {
'kit.session'?: string; session: import('svelte-kit-cookie-session').Session<SessionData>;
} cookies: Record<string, string>;
interface Locals { }
gitlabToken?: string; interface Platform {}
user: { interface Session extends SessionData {}
teamId: string; interface Stuff {}
permission: string;
isAdmin: boolean;
};
session: {
data: {
uid?: string;
teams?: string[];
expires?: string;
};
};
} }
type Applications = { interface SessionData {
name: string; version?: string;
domain: string; userId?: string | null;
}; teamId?: string | null;
permission?: string;
interface Hash { isAdmin?: boolean;
iv: string; expires?: string | null;
content: string; gitlabToken?: string | null;
ghToken?: string | null;
} }
interface BuildPack {
name: string;
}
// TODO: Not used, not working what?!
enum GitSource {
Github = 'github',
Gitlab = 'gitlab',
Bitbucket = 'bitbucket'
}
type RawHaproxyConfiguration = {
_version: number;
data: string;
};
type NewTransaction = {
_version: number;
id: string;
status: string;
};
type HttpRequestRuleForceSSL = {
return_hdrs: null;
cond: string;
cond_test: string;
index: number;
redir_code: number;
redir_type: string;
redir_value: string;
type: string;
};
// TODO: No any please
type HttpRequestRule = {
_version: number;
data: Array<any>;
};
type DateTimeFormatOptions = { type DateTimeFormatOptions = {
localeMatcher?: 'lookup' | 'best fit'; localeMatcher?: 'lookup' | 'best fit';
weekday?: 'long' | 'short' | 'narrow'; weekday?: 'long' | 'short' | 'narrow';
@@ -84,3 +36,24 @@ type DateTimeFormatOptions = {
hour12?: boolean; hour12?: boolean;
timeZone?: string; timeZone?: string;
}; };
interface Hash {
iv: string;
content: string;
}
type RawHaproxyConfiguration = {
_version: number;
data: string;
};
type NewTransaction = {
_version: number;
id: string;
status: string;
};
type Application = {
name: string;
domain: string;
};

View File

@@ -2,7 +2,7 @@ import dotEnvExtended from 'dotenv-extended';
dotEnvExtended.load(); dotEnvExtended.load();
import type { GetSession } from '@sveltejs/kit'; import type { GetSession } from '@sveltejs/kit';
import { handleSession } from 'svelte-kit-cookie-session'; import { handleSession } from 'svelte-kit-cookie-session';
import { getUserDetails, isTeamIdTokenAvailable, sentry } from '$lib/common'; import { getUserDetails, sentry } from '$lib/common';
import { version } from '$lib/common'; import { version } from '$lib/common';
import cookie from 'cookie'; import cookie from 'cookie';
import { dev } from '$app/env'; import { dev } from '$app/env';
@@ -10,27 +10,38 @@ import { dev } from '$app/env';
export const handle = handleSession( export const handle = handleSession(
{ {
secret: process.env['COOLIFY_SECRET_KEY'], secret: process.env['COOLIFY_SECRET_KEY'],
expires: 30 expires: 30,
cookie: { secure: false }
}, },
async function ({ event, resolve }) { async function ({ event, resolve }) {
let response; let response;
try { try {
const cookies: Cookies = cookie.parse(event.request.headers.get('cookie') || ''); if (event.locals.cookies) {
if (cookies['kit.session']) { let gitlabToken = event.locals.cookies.gitlabToken || null;
const { permission, teamId } = await getUserDetails(event, false); let ghToken = event.locals.cookies.ghToken;
event.locals.user = { if (event.locals.cookies['kit.session']) {
teamId, const { permission, teamId, userId } = await getUserDetails(event, false);
permission, const newSession = {
isAdmin: permission === 'admin' || permission === 'owner' userId,
}; teamId,
} permission,
if (cookies.gitlabToken) { isAdmin: permission === 'admin' || permission === 'owner',
event.locals.gitlabToken = cookies.gitlabToken; expires: event.locals.session.data.expires,
gitlabToken,
ghToken
};
if (JSON.stringify(event.locals.session.data) !== JSON.stringify(newSession)) {
event.locals.session.data = { ...newSession };
}
}
} }
response = await resolve(event, { response = await resolve(event, {
ssr: !event.url.pathname.startsWith('/webhooks/success') ssr: !event.url.pathname.startsWith('/webhooks/success')
}); });
} catch (error) { } catch (error) {
console.log(error);
response = await resolve(event, { response = await resolve(event, {
ssr: !event.url.pathname.startsWith('/webhooks/success') ssr: !event.url.pathname.startsWith('/webhooks/success')
}); });
@@ -61,17 +72,13 @@ export const handle = handleSession(
} }
); );
export const getSession: GetSession = function (request) { export const getSession: GetSession = function ({ locals }) {
return { return {
version, version,
gitlabToken: request.locals?.gitlabToken || null, ...locals.session.data
uid: request.locals.session.data?.uid || null,
teamId: request.locals.user?.teamId || null,
permission: request.locals.user?.permission,
isAdmin: request.locals.user?.isAdmin || false
}; };
}; };
export async function handleError({ error, event }) { export async function handleError({ error, event }) {
if (!dev) sentry.captureException(error, { event }); if (!dev) sentry.captureException(error, event);
} }

View File

@@ -74,26 +74,12 @@ export async function makeLabelForStandaloneDatabase({ id, image, volume }) {
]; ];
} }
export async function makeLabelForPlausibleAnalytics({ id, images, volume }) { export function makeLabelForServices(type) {
const service = await db.prisma.service.findFirst({
where: { id },
include: { plausibleAnalytics: true }
});
delete service.destinationDockerId;
delete service.createdAt;
delete service.updatedAt;
return [ return [
'coolify.managed=true', 'coolify.managed=true',
`coolify.version=${version}`, `coolify.version=${version}`,
`coolify.type=service-plausibleanalytics`, `coolify.type=service`,
`coolify.configuration=${base64Encode( `coolify.service.type=${type}`
JSON.stringify({
version,
images,
volume,
...service
})
)}`
]; ];
} }

View File

@@ -11,6 +11,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`COPY ./${baseDirectory || ''} /var/www/html`); Dockerfile.push(`COPY ./${baseDirectory || ''} /var/www/html`);
Dockerfile.push(`EXPOSE 80`); Dockerfile.push(`EXPOSE 80`);
Dockerfile.push('CMD ["apache2-foreground"]'); Dockerfile.push('CMD ["apache2-foreground"]');
Dockerfile.push('RUN chown -R www-data /var/www/html');
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
}; };

View File

@@ -8,16 +8,26 @@ import * as db from '$lib/database';
import { buildLogQueue } from './queues'; import { buildLogQueue } from './queues';
import { version as currentVersion } from '../../package.json'; import { version as currentVersion } from '../../package.json';
import { dockerInstance } from './docker';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import Cookie from 'cookie'; import Cookie from 'cookie';
import os from 'os';
try { try {
if (!dev) { if (!dev) {
Sentry.init({ Sentry.init({
dsn: process.env['COOLIFY_SENTRY_DSN'], dsn: process.env['COOLIFY_SENTRY_DSN'],
tracesSampleRate: 0, tracesSampleRate: 0,
environment: 'production' environment: 'production',
debug: true,
release: currentVersion,
initialScope: {
tags: {
appId: process.env['COOLIFY_APP_ID'],
'os.arch': os.arch(),
'os.platform': os.platform(),
'os.release': os.release()
}
}
}); });
} }
} catch (err) { } catch (err) {
@@ -57,7 +67,7 @@ export const isTeamIdTokenAvailable = (request) => {
}; };
export const getTeam = (event) => { export const getTeam = (event) => {
const cookies: Cookies = Cookie.parse(event.request.headers.get('cookie')); const cookies = Cookie.parse(event.request.headers.get('cookie'));
if (cookies.teamId) { if (cookies.teamId) {
return cookies.teamId; return cookies.teamId;
} else if (event.locals.session.data.teamId) { } else if (event.locals.session.data.teamId) {
@@ -68,7 +78,7 @@ export const getTeam = (event) => {
export const getUserDetails = async (event, isAdminRequired = true) => { export const getUserDetails = async (event, isAdminRequired = true) => {
const teamId = getTeam(event); const teamId = getTeam(event);
const userId = event.locals.session.data.uid || null; const userId = event.locals.session.data.userId || null;
const { permission = 'read' } = await db.prisma.permission.findFirst({ const { permission = 'read' } = await db.prisma.permission.findFirst({
where: { teamId, userId }, where: { teamId, userId },
select: { permission: true }, select: { permission: true },

View File

@@ -29,7 +29,7 @@
} }
</script> </script>
<span <div
class="relative" class="relative"
on:mouseenter={() => showActions(true)} on:mouseenter={() => showActions(true)}
on:mouseleave={() => showActions(false)} on:mouseleave={() => showActions(false)}
@@ -78,7 +78,7 @@
{/if} {/if}
{#if actionsShow} {#if actionsShow}
<div class="absolute top-0 right-0 mx-2 cursor-pointer text-warmGray-600 hover:text-white"> <div class="absolute top-0 right-0 m-3 cursor-pointer text-warmGray-600 hover:text-white">
<div class="flex space-x-2"> <div class="flex space-x-2">
{#if isPasswordField} {#if isPasswordField}
<div on:click={() => (showPassword = !showPassword)}> <div on:click={() => (showPassword = !showPassword)}>
@@ -142,4 +142,4 @@
</div> </div>
</div> </div>
{/if} {/if}
</span> </div>

View File

@@ -1,6 +1,6 @@
<script> <script>
export let text; export let text;
export let maxWidthClass = 'max-w-[24rem]'; export let customClass = 'max-w-[24rem]';
</script> </script>
<div class="py-1 text-xs text-stone-400 {maxWidthClass}">{@html text}</div> <div class="py-1 text-xs text-stone-400 {customClass}">{@html text}</div>

View File

@@ -11,7 +11,7 @@
<span class="loader" /> <span class="loader" />
</div> </div>
{:else} {:else}
<div class=" main h-64 py-24 left-0 top-0 flex flex-wrap content-center mx-auto"> <div class="main h-64 py-24 left-0 top-0 flex flex-wrap content-center mx-auto">
<span class="loader" /> <span class="loader" />
</div> </div>
{/if} {/if}

View File

@@ -4,15 +4,18 @@
export let setting; export let setting;
export let title; export let title;
export let description; export let description;
export let isPadding = true; export let isCenter = true;
export let disabled = false; export let disabled = false;
export let dataTooltip = null;
</script> </script>
<li class="flex items-center py-4"> <div class="flex items-center py-4 pr-8">
<div class="flex w-96 flex-col" class:px-4={isPadding} class:pr-32={!isPadding}> <div class="flex w-96 flex-col">
<p class="text-xs font-bold text-stone-100 md:text-base">{title}</p> <div class="text-xs font-bold text-stone-100 md:text-base">{title}</div>
<Explainer text={description} /> <Explainer text={description} />
</div> </div>
</div>
<div class:tooltip={dataTooltip} class:text-center={isCenter} data-tooltip={dataTooltip}>
<div <div
type="button" type="button"
on:click on:click
@@ -58,5 +61,4 @@
</span> </span>
</span> </span>
</div> </div>
<!-- {/if} --> </div>
</li>

View File

@@ -1,5 +1,5 @@
import { decrypt, encrypt } from '$lib/crypto'; import { decrypt, encrypt } from '$lib/crypto';
import { removeProxyConfiguration, removeWwwRedirection } from '$lib/haproxy'; import { removeProxyConfiguration } from '$lib/haproxy';
import { asyncExecShell, getEngine } from '$lib/common'; import { asyncExecShell, getEngine } from '$lib/common';
import { getDomain, removeDestinationDocker } from '$lib/common'; import { getDomain, removeDestinationDocker } from '$lib/common';
@@ -59,10 +59,14 @@ export async function removeApplication({ id, teamId }) {
const id = containerObj.ID; const id = containerObj.ID;
const preview = containerObj.Image.split('-')[1]; const preview = containerObj.Image.split('-')[1];
await removeDestinationDocker({ id, engine: destinationDocker.engine }); await removeDestinationDocker({ id, engine: destinationDocker.engine });
if (preview) { try {
await removeProxyConfiguration({ domain: `${preview}.${domain}` }); if (preview) {
} else { await removeProxyConfiguration({ domain: `${preview}.${domain}` });
await removeProxyConfiguration({ domain }); } else {
await removeProxyConfiguration({ domain });
}
} catch (error) {
console.log(error);
} }
} }
} }
@@ -113,6 +117,13 @@ export async function getApplicationWebhook({ projectId, branch }) {
throw { status: 404, body: { message: e.message } }; throw { status: 404, body: { message: e.message } };
} }
} }
export async function getApplicationById({ id }) {
const body = await prisma.application.findFirst({
where: { id }
});
return { ...body };
}
export async function getApplication({ id, teamId }) { export async function getApplication({ id, teamId }) {
let body = await prisma.application.findFirst({ let body = await prisma.application.findFirst({
where: { id, teams: { some: { id: teamId } } }, where: { id, teams: { some: { id: teamId } } },
@@ -198,10 +209,10 @@ export async function configureApplication({
}); });
} }
export async function setApplicationSettings({ id, debug, previews }) { export async function setApplicationSettings({ id, debug, previews, dualCerts }) {
return await prisma.application.update({ return await prisma.application.update({
where: { id }, where: { id },
data: { settings: { update: { debug, previews } } }, data: { settings: { update: { debug, previews, dualCerts } } },
include: { destinationDocker: true } include: { destinationDocker: true }
}); });
} }

View File

@@ -21,16 +21,35 @@ export async function isSecretExists({ id, name }) {
export async function isDomainConfigured({ id, fqdn }) { export async function isDomainConfigured({ id, fqdn }) {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const nakedDomain = domain.replace('www.', '');
const foundApp = await prisma.application.findFirst({ const foundApp = await prisma.application.findFirst({
where: { fqdn: { endsWith: domain }, id: { not: id } }, where: {
OR: [
{ fqdn: { endsWith: `//${nakedDomain}` } },
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
],
id: { not: id }
},
select: { fqdn: true } select: { fqdn: true }
}); });
const foundService = await prisma.service.findFirst({ const foundService = await prisma.service.findFirst({
where: { fqdn: { endsWith: domain }, id: { not: id } }, where: {
OR: [
{ fqdn: { endsWith: `//${nakedDomain}` } },
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
],
id: { not: id }
},
select: { fqdn: true } select: { fqdn: true }
}); });
const coolifyFqdn = await prisma.setting.findFirst({ const coolifyFqdn = await prisma.setting.findFirst({
where: { fqdn: { endsWith: domain }, id: { not: id } }, where: {
OR: [
{ fqdn: { endsWith: `//${nakedDomain}` } },
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
],
id: { not: id }
},
select: { fqdn: true } select: { fqdn: true }
}); });
if (foundApp || foundService || coolifyFqdn) return true; if (foundApp || foundService || coolifyFqdn) return true;

View File

@@ -2,6 +2,7 @@ import { dev } from '$app/env';
import { sentry } from '$lib/common'; import { sentry } from '$lib/common';
import * as Prisma from '@prisma/client'; import * as Prisma from '@prisma/client';
import { default as ProdPrisma } from '@prisma/client'; import { default as ProdPrisma } from '@prisma/client';
import type { PrismaClientOptions } from '@prisma/client/runtime';
import generator from 'generate-password'; import generator from 'generate-password';
import forge from 'node-forge'; import forge from 'node-forge';
@@ -19,40 +20,45 @@ if (!dev) {
PrismaClient = ProdPrisma.PrismaClient; PrismaClient = ProdPrisma.PrismaClient;
P = ProdPrisma.Prisma; P = ProdPrisma.Prisma;
} }
let prismaOptions = {
rejectOnNotFound: false
};
if (dev) {
prismaOptions = {
errorFormat: 'pretty',
rejectOnNotFound: false,
log: [
{
emit: 'event',
level: 'query'
}
]
};
}
export const prisma = new PrismaClient(prismaOptions);
export function PrismaErrorHandler(e) { export const prisma = new PrismaClient({
errorFormat: 'pretty',
rejectOnNotFound: false
});
export function ErrorHandler(e) {
if (e! instanceof Error) { if (e! instanceof Error) {
e = new Error(e.toString()); e = new Error(e.toString());
} }
sentry.captureException(e); let truncatedError = e;
if (e.stdout) {
truncatedError = e.stdout;
}
if (e.message?.includes('docker run')) {
let truncatedArray = [];
truncatedArray = truncatedError.message.split('-').filter((line) => {
if (!line.startsWith('e ')) {
return line;
}
});
truncatedError.message = truncatedArray.join('-');
}
if (e.message?.includes('git clone')) {
truncatedError.message = 'git clone failed';
}
sentry.captureException(truncatedError);
const payload = { const payload = {
status: e.status || 500, status: truncatedError.status || 500,
body: { body: {
message: 'Ooops, something is not okay, are you okay?', message: 'Ooops, something is not okay, are you okay?',
error: e.error || e.message error: truncatedError.error || truncatedError.message
} }
}; };
if (e.name === 'NotFoundError') { if (truncatedError?.name === 'NotFoundError') {
payload.status = 404; payload.status = 404;
} }
if (e instanceof P.PrismaClientKnownRequestError) { if (truncatedError instanceof P.PrismaClientKnownRequestError) {
if (e.code === 'P2002') { if (truncatedError?.code === 'P2002') {
payload.body.message = 'Already exists. Choose another name.'; payload.body.message = 'Already exists. Choose another name.';
} }
} }
@@ -101,27 +107,55 @@ export const supportedServiceTypesAndVersions = [
name: 'plausibleanalytics', name: 'plausibleanalytics',
fancyName: 'Plausible Analytics', fancyName: 'Plausible Analytics',
baseImage: 'plausible/analytics', baseImage: 'plausible/analytics',
versions: ['latest'] versions: ['latest'],
ports: {
main: 8000
}
},
{
name: 'nocodb',
fancyName: 'NocoDB',
baseImage: 'nocodb/nocodb',
versions: ['latest'],
ports: {
main: 8080
}
},
{
name: 'minio',
fancyName: 'MinIO',
baseImage: 'minio/minio',
versions: ['latest'],
ports: {
main: 9001
}
}, },
{ name: 'nocodb', fancyName: 'NocoDB', baseImage: 'nocodb/nocodb', versions: ['latest'] },
{ name: 'minio', fancyName: 'MinIO', baseImage: 'minio/minio', versions: ['latest'] },
{ {
name: 'vscodeserver', name: 'vscodeserver',
fancyName: 'VSCode Server', fancyName: 'VSCode Server',
baseImage: 'codercom/code-server', baseImage: 'codercom/code-server',
versions: ['latest'] versions: ['latest'],
ports: {
main: 8080
}
}, },
{ {
name: 'wordpress', name: 'wordpress',
fancyName: 'Wordpress', fancyName: 'Wordpress',
baseImage: 'wordpress', baseImage: 'wordpress',
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'] versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
ports: {
main: 80
}
}, },
{ {
name: 'vaultwarden', name: 'vaultwarden',
fancyName: 'Vaultwarden', fancyName: 'Vaultwarden',
baseImage: 'vaultwarden/server', baseImage: 'vaultwarden/server',
versions: ['latest'] versions: ['latest'],
ports: {
main: 80
}
} }
]; ];

View File

@@ -2,7 +2,7 @@ import { decrypt, encrypt } from '$lib/crypto';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import cuid from 'cuid'; import cuid from 'cuid';
import { generatePassword } from '.'; import { generatePassword } from '.';
import { prisma, PrismaErrorHandler } from './common'; import { prisma, ErrorHandler } from './common';
import getPort from 'get-port'; import getPort from 'get-port';
import { asyncExecShell, getEngine, removeContainer } from '$lib/common'; import { asyncExecShell, getEngine, removeContainer } from '$lib/common';

View File

@@ -38,7 +38,7 @@ export async function configureDestinationForDatabase({ id, destinationId }) {
if (type && version) { if (type && version) {
const baseImage = getDatabaseImage(type); const baseImage = getDatabaseImage(type);
asyncExecShell( asyncExecShell(
`DOCKER_HOST=${host} docker pull ${baseImage}:${version} && echo "FROM ${baseImage}:${version}" | docker build --label coolify.managed="true" -t "${baseImage}:${version}" -` `DOCKER_HOST=${host} docker pull ${baseImage}:${version} && echo "FROM ${baseImage}:${version}" | docker build --label coolify.image="true" -t "${baseImage}:${version}" -`
); );
} }
} }

View File

@@ -1,4 +1,4 @@
import { prisma, PrismaErrorHandler } from './common'; import { prisma, ErrorHandler } from './common';
export async function listLogs({ buildId, last = 0 }) { export async function listLogs({ buildId, last = 0 }) {
try { try {
@@ -8,6 +8,6 @@ export async function listLogs({ buildId, last = 0 }) {
}); });
return [...body]; return [...body];
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
} }

View File

@@ -107,13 +107,20 @@ export async function configureServiceType({ id, type }) {
}); });
} }
} }
export async function setService({ id, version }) { export async function setServiceVersion({ id, version }) {
return await prisma.service.update({ return await prisma.service.update({
where: { id }, where: { id },
data: { version } data: { version }
}); });
} }
export async function setServiceSettings({ id, dualCerts }) {
return await prisma.service.update({
where: { id },
data: { dualCerts }
});
}
export async function updatePlausibleAnalyticsService({ id, fqdn, email, username, name }) { export async function updatePlausibleAnalyticsService({ id, fqdn, email, username, name }) {
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } }); await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
await prisma.service.update({ where: { id }, data: { name, fqdn } }); await prisma.service.update({ where: { id }, data: { name, fqdn } });

View File

@@ -6,19 +6,23 @@ import { asyncExecShell, uniqueName } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { startCoolifyProxy } from '$lib/haproxy'; import { startCoolifyProxy } from '$lib/haproxy';
export async function hashPassword(password: string) {
export async function login({ email, password }) {
const saltRounds = 15; const saltRounds = 15;
return bcrypt.hash(password, saltRounds);
}
export async function login({ email, password }) {
const users = await prisma.user.count(); const users = await prisma.user.count();
const userFound = await prisma.user.findUnique({ const userFound = await prisma.user.findUnique({
where: { email }, where: { email },
include: { teams: true }, include: { teams: true, permission: true },
rejectOnNotFound: false rejectOnNotFound: false
}); });
// Registration disabled if database is not seeded properly // Registration disabled if database is not seeded properly
const { isRegistrationEnabled, id } = await db.listSettings(); const { isRegistrationEnabled, id } = await db.listSettings();
let uid = cuid(); let uid = cuid();
let permission = 'read';
let isAdmin = false;
// Disable registration if we are registering the first user. // Disable registration if we are registering the first user.
if (users === 0) { if (users === 0) {
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } }); await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
@@ -50,6 +54,8 @@ export async function login({ email, password }) {
}; };
} }
uid = userFound.id; uid = userFound.id;
// permission = userFound.permission;
isAdmin = true;
} }
} else { } else {
// If registration disabled, return 403 // If registration disabled, return 403
@@ -59,8 +65,10 @@ export async function login({ email, password }) {
}; };
} }
const hashedPassword = await bcrypt.hash(password, saltRounds); const hashedPassword = await hashPassword(password);
if (users === 0) { if (users === 0) {
permission = 'owner';
isAdmin = true;
await prisma.user.create({ await prisma.user.create({
data: { data: {
id: uid, id: uid,
@@ -103,8 +111,10 @@ export async function login({ email, password }) {
'Set-Cookie': `teamId=${uid}; HttpOnly; Path=/; Max-Age=15778800;` 'Set-Cookie': `teamId=${uid}; HttpOnly; Path=/; Max-Age=15778800;`
}, },
body: { body: {
uid, userId: uid,
teamId: uid teamId: uid,
permission,
isAdmin
} }
}; };
} }

View File

@@ -2,7 +2,6 @@ import { dev } from '$app/env';
import { asyncExecShell, getDomain, getEngine } from '$lib/common'; import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import got from 'got'; import got from 'got';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { letsEncrypt } from '$lib/letsencrypt';
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555'; const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
@@ -49,7 +48,8 @@ export async function completeTransaction(transactionId) {
return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`); return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
} }
export async function removeProxyConfiguration({ domain }) { export async function removeProxyConfiguration(fqdn) {
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
const backendFound = await haproxy const backendFound = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`) .get(`v2/services/haproxy/configuration/backends/${domain}`)
@@ -65,111 +65,102 @@ export async function removeProxyConfiguration({ domain }) {
.json(); .json();
await completeTransaction(transactionId); await completeTransaction(transactionId);
} }
await removeWwwRedirection(domain); await forceSSLOffApplication(domain);
await removeWwwRedirection(fqdn);
} }
export async function forceSSLOffApplication({ domain }) { export async function forceSSLOffApplication(domain) {
if (!dev) { const haproxy = await haproxyInstance();
const haproxy = await haproxyInstance(); await checkHAProxy(haproxy);
await checkHAProxy(haproxy); let transactionId;
const transactionId = await getNextTransactionId(); try {
try { const rules: any = await haproxy
const rules: any = await haproxy .get(`v2/services/haproxy/configuration/http_request_rules`, {
.get(`v2/services/haproxy/configuration/http_request_rules`, { searchParams: {
searchParams: { parent_name: 'http',
parent_name: 'http', parent_type: 'frontend'
parent_type: 'frontend'
}
})
.json();
if (rules.data.length > 0) {
const rule = rules.data.find((rule) => rule.cond_test.includes(`-i ${domain}`));
if (rule) {
await haproxy
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
} }
})
.json();
if (rules.data.length > 0) {
const rule = rules.data.find((rule) =>
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
);
if (rule) {
transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
} }
} catch (error) {
console.log(error);
} finally {
await completeTransaction(transactionId);
} }
} else { } catch (error) {
console.log(`[DEBUG] Removing ssl for ${domain}`); console.log(error);
} finally {
if (transactionId) await completeTransaction(transactionId);
} }
} }
export async function forceSSLOnApplication({ domain }) { export async function forceSSLOnApplication(domain) {
if (!dev) { const haproxy = await haproxyInstance();
const haproxy = await haproxyInstance(); await checkHAProxy(haproxy);
try { let transactionId;
await checkHAProxy(haproxy); try {
} catch (error) { const rules: any = await haproxy
return; .get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
let nextRule = 0;
if (rules.data.length > 0) {
const rule = rules.data.find((rule) =>
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
);
if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1;
} }
const transactionId = await getNextTransactionId(); transactionId = await getNextTransactionId();
try { await haproxy
const rules: any = await haproxy .post(`v2/services/haproxy/configuration/http_request_rules`, {
.get(`v2/services/haproxy/configuration/http_request_rules`, { searchParams: {
searchParams: { transaction_id: transactionId,
parent_name: 'http', parent_name: 'http',
parent_type: 'frontend' parent_type: 'frontend'
} },
}) json: {
.json(); index: nextRule,
let nextRule = 0; cond: 'if',
if (rules.data.length > 0) { cond_test: `{ hdr(host) -i ${domain} } !{ ssl_fc }`,
const rule = rules.data.find((rule) => type: 'redirect',
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`) redir_type: 'scheme',
); redir_value: 'https',
if (rule) return; redir_code: dev ? 302 : 301
nextRule = rules.data[rules.data.length - 1].index + 1; }
} })
await haproxy .json();
.post(`v2/services/haproxy/configuration/http_request_rules`, { } catch (error) {
searchParams: { console.log(error);
transaction_id: transactionId, throw error;
parent_name: 'http', } finally {
parent_type: 'frontend' if (transactionId) await completeTransaction(transactionId);
},
json: {
index: nextRule,
cond: 'if',
cond_test: `{ hdr(host) -i ${domain} } !{ ssl_fc }`,
type: 'redirect',
redir_type: 'scheme',
redir_value: 'https',
redir_code: 301
}
})
.json();
} catch (error) {
console.log(error);
throw error;
} finally {
await completeTransaction(transactionId);
}
} else {
console.log(`[DEBUG] Adding ssl for ${domain}`);
} }
} }
export async function deleteProxy({ id }) { export async function deleteProxy({ id }) {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
try { await checkHAProxy(haproxy);
await checkHAProxy(haproxy); let transactionId;
} catch (error) {
return;
}
const transactionId = await getNextTransactionId();
try { try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json(); await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json();
transactionId = await getNextTransactionId();
await haproxy await haproxy
.delete(`v2/services/haproxy/configuration/backends/${id}`, { .delete(`v2/services/haproxy/configuration/backends/${id}`, {
searchParams: { searchParams: {
@@ -188,7 +179,7 @@ export async function deleteProxy({ id }) {
} catch (error) { } catch (error) {
console.log(error.response.body); console.log(error.response.body);
} finally { } finally {
await completeTransaction(transactionId); if (transactionId) await completeTransaction(transactionId);
} }
} }
@@ -198,11 +189,7 @@ export async function reloadHaproxy(engine) {
} }
export async function configureProxyForApplication({ domain, imageId, applicationId, port }) { export async function configureProxyForApplication({ domain, imageId, applicationId, port }) {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
try { await checkHAProxy(haproxy);
await checkHAProxy(haproxy);
} catch (error) {
return;
}
let serverConfigured = false; let serverConfigured = false;
let backendAvailable: any = null; let backendAvailable: any = null;
@@ -224,7 +211,7 @@ export async function configureProxyForApplication({ domain, imageId, applicatio
if (backendAvailable.data.forwardfor.enabled === 'enabled') { if (backendAvailable.data.forwardfor.enabled === 'enabled') {
if (backendAvailable.data.name === domain) { if (backendAvailable.data.name === domain) {
if (server.data.check === 'enabled') { if (server.data.check === 'enabled') {
if (server.data.address === applicationId) { if (server.data.address === imageId) {
if (server.data.port === port) { if (server.data.port === port) {
serverConfigured = true; serverConfigured = true;
} }
@@ -282,16 +269,13 @@ export async function configureProxyForApplication({ domain, imageId, applicatio
export async function configureCoolifyProxyOff(fqdn) { export async function configureCoolifyProxyOff(fqdn) {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
try { await checkHAProxy(haproxy);
await checkHAProxy(haproxy);
} catch (error) {
return;
}
try { try {
const transactionId = await getNextTransactionId();
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json(); await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
const transactionId = await getNextTransactionId();
await haproxy await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, { .delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: { searchParams: {
@@ -300,30 +284,27 @@ export async function configureCoolifyProxyOff(fqdn) {
}) })
.json(); .json();
await completeTransaction(transactionId); await completeTransaction(transactionId);
if (!dev) { if (isHttps) await forceSSLOffApplication(domain);
await forceSSLOffApplication({ domain }); await removeWwwRedirection(fqdn);
}
await setWwwRedirection(fqdn);
} catch (error) { } catch (error) {
throw error?.response?.body || error; throw error?.response?.body || error;
} }
} }
export async function checkHAProxy(haproxy) { export async function checkHAProxy(haproxy?: any) {
if (!haproxy) haproxy = await haproxyInstance(); if (!haproxy) haproxy = await haproxyInstance();
try { try {
await haproxy.get('v2/info'); await haproxy.get('v2/info');
} catch (error) { } catch (error) {
throw 'HAProxy is not running, but it should be!'; throw {
message:
'Coolify Proxy is not running, but it should be!<br><br>Start it in the "Destinations" menu.'
};
} }
} }
export async function configureCoolifyProxyOn(fqdn) { export async function configureCoolifyProxyOn(fqdn) {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
try { await checkHAProxy(haproxy);
await checkHAProxy(haproxy);
} catch (error) {
return;
}
let serverConfigured = false; let serverConfigured = false;
let backendAvailable: any = null; let backendAvailable: any = null;
try { try {
@@ -522,62 +503,66 @@ export async function configureNetworkCoolifyProxy(engine) {
export async function configureSimpleServiceProxyOn({ id, domain, port }) { export async function configureSimpleServiceProxyOn({ id, domain, port }) {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
let serverConfigured = false;
let backendAvailable: any = null;
try { try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json(); backendAvailable = await haproxy
const transactionId = await getNextTransactionId(); .get(`v2/services/haproxy/configuration/backends/${domain}`)
await haproxy .json();
.delete(`v2/services/haproxy/configuration/backends/${domain}`, { const server: any = await haproxy
.get(`v2/services/haproxy/configuration/servers/${id}`, {
searchParams: { searchParams: {
transaction_id: transactionId backend: domain
} }
}) })
.json(); .json();
await completeTransaction(transactionId); if (backendAvailable && server) {
// Very sophisticated way to check if the server is already configured in proxy
if (backendAvailable.data.forwardfor.enabled === 'enabled') {
if (backendAvailable.data.name === domain) {
if (server.data.check === 'enabled') {
if (server.data.address === id) {
if (server.data.port === port) {
serverConfigured = true;
}
}
}
}
}
}
} catch (error) {} } catch (error) {}
try { if (serverConfigured) return;
const transactionId = await getNextTransactionId(); const transactionId = await getNextTransactionId();
await haproxy.post('v2/services/haproxy/configuration/backends', { await haproxy.post('v2/services/haproxy/configuration/backends', {
searchParams: { searchParams: {
transaction_id: transactionId transaction_id: transactionId
}, },
json: { json: {
'init-addr': 'last,libc,none', 'init-addr': 'last,libc,none',
forwardfor: { enabled: 'enabled' }, forwardfor: { enabled: 'enabled' },
name: domain name: domain
} }
}); });
await haproxy.post('v2/services/haproxy/configuration/servers', { await haproxy.post('v2/services/haproxy/configuration/servers', {
searchParams: { searchParams: {
transaction_id: transactionId, transaction_id: transactionId,
backend: domain backend: domain
}, },
json: { json: {
address: id,
check: 'enabled',
name: id,
port: port
}
});
console.log({
address: id, address: id,
check: 'enabled', check: 'enabled',
name: id, name: id,
port: port port: port
}); }
await completeTransaction(transactionId); });
} catch (error) { await completeTransaction(transactionId);
console.log(error);
}
} }
export async function configureSimpleServiceProxyOff({ domain }) { export async function configureSimpleServiceProxyOff(fqdn) {
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
try { await checkHAProxy(haproxy);
await checkHAProxy(haproxy);
} catch (error) {
return;
}
try { try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json(); await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
const transactionId = await getNextTransactionId(); const transactionId = await getNextTransactionId();
@@ -590,18 +575,18 @@ export async function configureSimpleServiceProxyOff({ domain }) {
.json(); .json();
await completeTransaction(transactionId); await completeTransaction(transactionId);
} catch (error) {} } catch (error) {}
await removeWwwRedirection(domain); await forceSSLOffApplication(domain);
await removeWwwRedirection(fqdn);
return; return;
} }
export async function removeWwwRedirection(domain) { export async function removeWwwRedirection(fqdn) {
const haproxy = await haproxyInstance(); const domain = getDomain(fqdn);
try { const isHttps = fqdn.startsWith('https://');
await checkHAProxy(haproxy); const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
} catch (error) {
return;
}
const haproxy = await haproxyInstance();
await checkHAProxy();
const rules: any = await haproxy const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, { .get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: { searchParams: {
@@ -611,9 +596,7 @@ export async function removeWwwRedirection(domain) {
}) })
.json(); .json();
if (rules.data.length > 0) { if (rules.data.length > 0) {
const rule = rules.data.find((rule) => const rule = rules.data.find((rule) => rule.redir_value.includes(redirectValue));
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
);
if (rule) { if (rule) {
const transactionId = await getNextTransactionId(); const transactionId = await getNextTransactionId();
await haproxy await haproxy
@@ -631,17 +614,14 @@ export async function removeWwwRedirection(domain) {
} }
export async function setWwwRedirection(fqdn) { export async function setWwwRedirection(fqdn) {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
try { await checkHAProxy(haproxy);
await checkHAProxy(haproxy); let transactionId;
} catch (error) {
return;
}
const transactionId = await getNextTransactionId();
try { try {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.'); const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
const contTest = `{ req.hdr(host) -i ${isWWW ? domain.replace('www.', '') : `www.${domain}`} }`; const contTest = `{ req.hdr(host) -i ${isWWW ? domain.replace('www.', '') : `www.${domain}`} }`;
const rules: any = await haproxy const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, { .get(`v2/services/haproxy/configuration/http_request_rules`, {
@@ -653,13 +633,12 @@ export async function setWwwRedirection(fqdn) {
.json(); .json();
let nextRule = 0; let nextRule = 0;
if (rules.data.length > 0) { if (rules.data.length > 0) {
const rule = rules.data.find((rule) => const rule = rules.data.find((rule) => rule.redir_value.includes(redirectValue));
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
);
if (rule) return; if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1; nextRule = rules.data[rules.data.length - 1].index + 1;
} }
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
transactionId = await getNextTransactionId();
await haproxy await haproxy
.post(`v2/services/haproxy/configuration/http_request_rules`, { .post(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: { searchParams: {
@@ -682,6 +661,6 @@ export async function setWwwRedirection(fqdn) {
console.log(error); console.log(error);
throw error; throw error;
} finally { } finally {
await completeTransaction(transactionId); if (transactionId) await completeTransaction(transactionId);
} }
} }

View File

@@ -2,7 +2,7 @@ import { asyncExecShell, saveBuildLog } from '$lib/common';
import got from 'got'; import got from 'got';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
export default async function ({ export default async function ({
applicationId, applicationId,
@@ -45,6 +45,6 @@ export default async function ({
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`); const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
return commit.replace('\n', ''); return commit.replace('\n', '');
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
} }

View File

@@ -1,5 +1,5 @@
import { asyncExecShell, saveBuildLog } from '$lib/common'; import { asyncExecShell, saveBuildLog } from '$lib/common';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
export default async function ({ export default async function ({
applicationId, applicationId,

View File

@@ -1,48 +1,78 @@
import { dev } from '$app/env'; import { dev } from '$app/env';
import { forceSSLOffApplication, forceSSLOnApplication, getNextTransactionId } from '$lib/haproxy'; import { forceSSLOffApplication, forceSSLOnApplication } from '$lib/haproxy';
import { asyncExecShell, getEngine } from './common'; import { asyncExecShell, getEngine } from './common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import cuid from 'cuid';
import getPort from 'get-port';
export async function letsEncrypt({ domain, isCoolify = false, id = null }) { export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
try { try {
if (dev) { const nakedDomain = domain.replace('www.', '');
return await forceSSLOnApplication({ domain }); const wwwDomain = `www.${nakedDomain}`;
} else { const randomCuid = cuid();
if (isCoolify) { const randomPort = 9080;
await asyncExecShell(
`docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
);
const { stderr } = await asyncExecShell( let host;
`docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem` let dualCerts = false;
); if (isCoolify) {
if (stderr) throw new Error(stderr); const data = await db.prisma.setting.findFirst();
return; dualCerts = data.dualCerts;
host = 'unix:///var/run/docker.sock';
} else {
// Check Application
const applicationData = await db.prisma.application.findUnique({
where: { id },
include: { destinationDocker: true, settings: true }
});
if (applicationData) {
if (applicationData?.destinationDockerId && applicationData?.destinationDocker) {
host = getEngine(applicationData.destinationDocker.engine);
}
if (applicationData?.settings?.dualCerts) {
dualCerts = applicationData.settings.dualCerts;
}
} }
let data: any = await db.prisma.application.findUnique({ // Check Service
const serviceData = await db.prisma.service.findUnique({
where: { id }, where: { id },
include: { destinationDocker: true } include: { destinationDocker: true }
}); });
if (!data) { if (serviceData) {
data = await db.prisma.service.findUnique({ if (serviceData?.destinationDockerId && serviceData?.destinationDocker) {
where: { id }, host = getEngine(serviceData.destinationDocker.engine);
include: { destinationDocker: true } }
}); if (serviceData?.dualCerts) {
} dualCerts = serviceData.dualCerts;
// Set SSL with Let's encrypt }
if (data.destinationDockerId && data.destinationDocker) {
const host = getEngine(data.destinationDocker.engine);
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
);
const { stderr } = await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm --name bash -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
);
if (stderr) throw new Error(stderr);
await forceSSLOnApplication({ domain });
} }
} }
await forceSSLOffApplication(domain);
if (dualCerts) {
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
dev ? '--test-cert' : ''
}`
);
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "test -d /etc/letsencrypt/live/${nakedDomain}/ && cat /etc/letsencrypt/live/${nakedDomain}/fullchain.pem /etc/letsencrypt/live/${nakedDomain}/privkey.pem > /app/ssl/${nakedDomain}.pem || cat /etc/letsencrypt/live/${wwwDomain}/fullchain.pem /etc/letsencrypt/live/${wwwDomain}/privkey.pem > /app/ssl/${wwwDomain}.pem"`
);
} else {
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${domain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
dev ? '--test-cert' : ''
}`
);
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem"`
);
}
} catch (error) { } catch (error) {
throw error; if (error.code !== 0) {
throw error;
}
} finally {
if (!isCoolify) {
await forceSSLOnApplication(domain);
}
} }
} }

View File

@@ -239,6 +239,8 @@ export default async function (job) {
if (stderr) console.log(stderr); if (stderr) console.log(stderr);
saveBuildLog({ line: 'Deployment successful!', buildId, applicationId }); saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
} catch (error) { } catch (error) {
saveBuildLog({ line: error, buildId, applicationId });
sentry.captureException(error);
throw new Error(error); throw new Error(error);
} }
try { try {
@@ -257,7 +259,9 @@ export default async function (job) {
}); });
} }
} catch (error) { } catch (error) {
saveBuildLog({ line: error.stdout || error, buildId, applicationId });
sentry.captureException(error); sentry.captureException(error);
throw new Error(error);
} }
} }
} }

View File

@@ -23,7 +23,7 @@ export default async function () {
]; ];
for (const image of images) { for (const image of images) {
await asyncExecShell( await asyncExecShell(
`DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.managed="true" -t "${image}" -` `DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.image="true" -t "${image}" -`
); );
} }
} catch (error) {} } catch (error) {}
@@ -35,11 +35,17 @@ export default async function () {
// Cleanup images that are not managed by coolify // Cleanup images that are not managed by coolify
try { try {
await asyncExecShell( await asyncExecShell(
`DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.managed=true' -a -f` `DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.image=true' -a -f`
); );
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
// Cleanup dangling images
try {
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f`);
} catch (error) {
console.log(error);
}
} }
} }
} }

View File

@@ -87,7 +87,7 @@ const cron = async () => {
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } }); await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
// await queue.ssl.add('ssl', {}, { repeat: { every: 10000 } }); // await queue.ssl.add('ssl', {}, { repeat: { every: 10000 } });
await queue.cleanup.add('cleanup', {}, { repeat: { every: 3600000 } }); if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 600000 } });
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } }); await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
const events = { const events = {
@@ -127,7 +127,6 @@ buildWorker.on('completed', async (job: Bullmq.Job) => {
}); });
buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => { buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
console.log(failedReason);
try { try {
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } }); await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } });
} catch (error) { } catch (error) {
@@ -136,7 +135,11 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
const workdir = `/tmp/build-sources/${job.data.repository}`; const workdir = `/tmp/build-sources/${job.data.repository}`;
await asyncExecShell(`rm -fr ${workdir}`); await asyncExecShell(`rm -fr ${workdir}`);
} }
saveBuildLog({ line: 'Failed build!', buildId: job.data.build_id, applicationId: job.data.id }); saveBuildLog({
line: 'Failed to deploy!',
buildId: job.data.build_id,
applicationId: job.data.id
});
saveBuildLog({ saveBuildLog({
line: `Reason: ${failedReason.toString()}`, line: `Reason: ${failedReason.toString()}`,
buildId: job.data.build_id, buildId: job.data.build_id,
@@ -144,7 +147,7 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
}); });
}); });
const buildLogQueueName = dev ? cuid() : 'log_queue'; const buildLogQueueName = 'log_queue';
const buildLogQueue = new Queue(buildLogQueueName, connectionOptions); const buildLogQueue = new Queue(buildLogQueueName, connectionOptions);
const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), { const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), {
concurrency: 1, concurrency: 1,

View File

@@ -1,14 +1,16 @@
import { getDomain } from '$lib/common'; import { getDomain } from '$lib/common';
import { prisma } from '$lib/database'; import { getApplicationById, prisma, supportedServiceTypesAndVersions } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import { import {
checkContainer, checkContainer,
configureCoolifyProxyOn, configureCoolifyProxyOn,
configureProxyForApplication, configureProxyForApplication,
configureSimpleServiceProxyOn,
forceSSLOnApplication, forceSSLOnApplication,
reloadHaproxy, reloadHaproxy,
setWwwRedirection, setWwwRedirection,
startCoolifyProxy startCoolifyProxy,
startHttpProxy
} from '$lib/haproxy'; } from '$lib/haproxy';
import * as db from '$lib/database'; import * as db from '$lib/database';
@@ -24,44 +26,82 @@ export default async function () {
(container) => container.Labels['coolify.managed'] (container) => container.Labels['coolify.managed']
); );
for (const configuration of configurations) { for (const configuration of configurations) {
const parsedConfiguration = JSON.parse( if (configuration.Labels['coolify.configuration']) {
Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString() const parsedConfiguration = JSON.parse(
); Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString()
if (configuration.Labels['coolify.type'] === 'standalone-application') { );
const { fqdn, applicationId, port, pullmergeRequestId } = parsedConfiguration; if (
if (fqdn) { parsedConfiguration &&
const domain = getDomain(fqdn); configuration.Labels['coolify.type'] === 'standalone-application'
await configureProxyForApplication({ ) {
domain, const { fqdn, applicationId, port, pullmergeRequestId } = parsedConfiguration;
imageId: pullmergeRequestId if (fqdn) {
? `${applicationId}-${pullmergeRequestId}` const found = await getApplicationById({ id: applicationId });
: applicationId, if (found) {
applicationId, const domain = getDomain(fqdn);
port await configureProxyForApplication({
}); domain,
const isHttps = fqdn.startsWith('https://'); imageId: pullmergeRequestId
if (isHttps) await forceSSLOnApplication({ domain }); ? `${applicationId}-${pullmergeRequestId}`
await setWwwRedirection(fqdn); : applicationId,
applicationId,
port
});
const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication(domain);
await setWwwRedirection(fqdn);
}
}
}
}
}
for (const container of containers) {
const image = container.Image.split(':')[0];
const found = supportedServiceTypesAndVersions.find((a) => a.baseImage === image);
if (found) {
const type = found.name;
const mainPort = found.ports.main;
const id = container.Names[0].replace('/', '');
const service = await db.prisma.service.findUnique({
where: { id },
include: {
destinationDocker: true,
minio: true,
plausibleAnalytics: true,
vscodeserver: true,
wordpress: true
}
});
const { fqdn } = service;
const domain = getDomain(fqdn);
await configureSimpleServiceProxyOn({ id, domain, port: mainPort });
const publicPort = service[type]?.publicPort;
if (publicPort) {
const containerFound = await checkContainer(
destination.engine,
`haproxy-for-${publicPort}`
);
if (!containerFound) {
await startHttpProxy(destination, id, publicPort, 9000);
}
} }
} }
} }
} }
} }
const services = await prisma.service.findMany({});
// Check Coolify FQDN and configure proxy if needed // Check Coolify FQDN and configure proxy if needed
const { fqdn } = await db.listSettings(); const { fqdn } = await db.listSettings();
if (fqdn) { if (fqdn) {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy'); await startCoolifyProxy('/var/run/docker.sock');
if (!found) await startCoolifyProxy('/var/run/docker.sock'); await configureCoolifyProxyOn(fqdn);
await configureCoolifyProxyOn({ domain });
await setWwwRedirection(fqdn); await setWwwRedirection(fqdn);
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication({ domain }); if (isHttps) await forceSSLOnApplication(domain);
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
throw error; throw error;
} finally {
// await reloadHaproxy('/var/run/docker.sock');
} }
} }

View File

@@ -1,5 +1,7 @@
export const publicPaths = [ export const publicPaths = [
'/login', '/login',
'/reset',
'/reset/password',
'/webhooks/success', '/webhooks/success',
'/webhooks/github', '/webhooks/github',
'/webhooks/github/install', '/webhooks/github/install',

View File

@@ -2,14 +2,14 @@
import type { Load } from '@sveltejs/kit'; import type { Load } from '@sveltejs/kit';
import { publicPaths } from '$lib/settings'; import { publicPaths } from '$lib/settings';
export const load: Load = async ({ fetch, url, params, session }) => { export const load: Load = async ({ fetch, url, session }) => {
if (!session.uid && !publicPaths.includes(url.pathname)) { if (!session.userId && !publicPaths.includes(url.pathname)) {
return { return {
status: 302, status: 302,
redirect: '/login' redirect: '/login'
}; };
} }
if (!session.uid) { if (!session.userId) {
return {}; return {};
} }
const endpoint = `/teams.json`; const endpoint = `/teams.json`;
@@ -49,7 +49,7 @@
}; };
let latestVersion = 'latest'; let latestVersion = 'latest';
onMount(async () => { onMount(async () => {
if ($session.uid) { if ($session.userId) {
const overrideVersion = browser && window.localStorage.getItem('latestVersion'); const overrideVersion = browser && window.localStorage.getItem('latestVersion');
try { try {
await get(`/login.json`); await get(`/login.json`);
@@ -84,7 +84,7 @@
} }
async function switchTeam() { async function switchTeam() {
try { try {
await post(`/index.json?from=${$page.url.pathname}`, { await post(`/dashboard.json?from=${$page.url.pathname}`, {
cookie: 'teamId', cookie: 'teamId',
value: selectedTeamId value: selectedTeamId
}); });
@@ -96,10 +96,9 @@
async function update() { async function update() {
updateStatus.loading = true; updateStatus.loading = true;
// if (!dev) {
try { try {
await post(`/update.json`, { type: 'update', latestVersion }); await post(`/update.json`, { type: 'update', latestVersion });
toast.push('Update completed. Waiting for the new version to start...'); toast.push('Update completed.<br>Waiting for the new version to start...');
let reachable = false; let reachable = false;
let tries = 0; let tries = 0;
do { do {
@@ -119,10 +118,9 @@
await asyncSleep(3000); await asyncSleep(3000);
return window.location.reload(); return window.location.reload();
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error);
} finally {
updateStatus.success = false; updateStatus.success = false;
updateStatus.loading = false; updateStatus.loading = false;
return errorNotification(error);
} }
} }
</script> </script>
@@ -131,7 +129,7 @@
<title>Coolify</title> <title>Coolify</title>
</svelte:head> </svelte:head>
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} /> <SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
{#if $session.uid} {#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">
<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>

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) {
const { application, githubToken, ghToken, isRunning, appId } = await res.json(); const { application, isRunning, appId } = await res.json();
if (!application || Object.entries(application).length === 0) { if (!application || Object.entries(application).length === 0) {
return { return {
status: 302, status: 302,
@@ -42,8 +42,6 @@
}, },
stuff: { stuff: {
isRunning, isRunning,
ghToken,
githubToken,
application, application,
appId appId
} }

View File

@@ -1,6 +1,6 @@
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 { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -16,13 +16,13 @@ export const post: RequestHandler = async (event) => {
const found = await db.isDomainConfigured({ id, fqdn }); const found = await db.isDomainConfigured({ id, fqdn });
if (found) { if (found) {
throw { throw {
message: `Domain ${getDomain(fqdn)} is already configured.` message: `Domain ${getDomain(fqdn).replace('www.', '')} is already configured.`
}; };
} }
return { return {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,13 +1,11 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
export let githubToken;
export let application; export let application;
import { page } from '$app/stores'; import { page, session } from '$app/stores';
import { get, post } from '$lib/api'; import { get, post } from '$lib/api';
import { getGithubToken } from '$lib/components/common'; import { errorNotification } from '$lib/form';
import { enhance, errorNotification } from '$lib/form';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
const { id } = $page.params; const { id } = $page.params;
@@ -30,19 +28,16 @@
branch: undefined branch: undefined
}; };
let showSave = false; let showSave = false;
let token = null;
async function loadRepositoriesByPage(page = 0) { async function loadRepositoriesByPage(page = 0) {
try { try {
return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, { return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, {
Authorization: `token ${token}` Authorization: `token ${$session.ghToken}`
}); });
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
} }
async function loadRepositories() { async function loadRepositories() {
token = await getGithubToken({ apiUrl, githubToken, application });
let page = 1; let page = 1;
let reposCount = 0; let reposCount = 0;
const loadedRepos = await loadRepositoriesByPage(); const loadedRepos = await loadRepositoriesByPage();
@@ -63,7 +58,7 @@
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id; selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
try { try {
branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, { branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
Authorization: `token ${token}` Authorization: `token ${$session.ghToken}`
}); });
return; return;
} catch ({ error }) { } catch ({ error }) {
@@ -74,13 +69,18 @@
} }
async function isBranchAlreadyUsed() { async function isBranchAlreadyUsed() {
try { try {
return await get( const data = await get(
`/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}` `/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}`
); );
} catch ({ error }) { if (data.used) {
return errorNotification(error); errorNotification('This branch is already used by another application.');
} finally { showSave = false;
return true;
}
showSave = true; showSave = true;
} catch ({ error }) {
showSave = false;
return errorNotification(error);
} }
} }

View File

@@ -8,7 +8,6 @@
import cuid from 'cuid'; import cuid from 'cuid';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { del, get, post, put } from '$lib/api'; import { del, get, post, put } from '$lib/api';
const { id } = $page.params; const { id } = $page.params;
const from = $page.url.searchParams.get('from'); const from = $page.url.searchParams.get('from');
@@ -132,14 +131,18 @@
} }
async function isBranchAlreadyUsed() { async function isBranchAlreadyUsed() {
const url = `/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`;
try { try {
await get(url); const data = await get(
`/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
);
if (data.used) {
errorNotification('This branch is already used by another application.');
showSave = false;
return true;
}
showSave = true; showSave = true;
} catch (error) { } catch ({ error }) {
showSave = false; return errorNotification(error);
return errorNotification('Branch already configured');
} }
} }
async function checkSSHKey(sshkeyUrl) { async function checkSSHKey(sshkeyUrl) {
@@ -195,7 +198,6 @@
const deployKeyFound = deployKeys.filter((dk) => dk.title === `${appId}-coolify-deploy-key`); const deployKeyFound = deployKeys.filter((dk) => dk.title === `${appId}-coolify-deploy-key`);
if (deployKeyFound.length > 0) { if (deployKeyFound.length > 0) {
for (const deployKey of deployKeyFound) { for (const deployKey of deployKeyFound) {
console.log(`${deployKeyUrl}/${deployKey.id}`);
await del( await del(
`${deployKeyUrl}/${deployKey.id}`, `${deployKeyUrl}/${deployKey.id}`,
{}, {},

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 type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event); const { teamId, status, body } = await getUserDetails(event);
@@ -21,7 +21,7 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -36,6 +36,6 @@ export const post: RequestHandler = async (event) => {
await db.configureBuildPack({ id, buildPack }); await db.configureBuildPack({ id, buildPack });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,7 +1,7 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import type { Load } from '@sveltejs/kit'; import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => { export const load: Load = async ({ fetch, params, url, stuff }) => {
const { application, ghToken } = stuff; const { application } = stuff;
if (application?.buildPack && !url.searchParams.get('from')) { if (application?.buildPack && !url.searchParams.get('from')) {
return { return {
status: 302, status: 302,
@@ -14,8 +14,7 @@
return { return {
props: { props: {
...(await res.json()), ...(await res.json()),
application, application
ghToken
} }
}; };
} }
@@ -43,7 +42,6 @@
export let projectId; export let projectId;
export let repository; export let repository;
export let branch; export let branch;
export let ghToken;
export let type; export let type;
export let application; export let application;
@@ -96,7 +94,7 @@
} }
} else if (type === 'github') { } else if (type === 'github') {
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, { const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
Authorization: `Bearer ${ghToken}`, Authorization: `Bearer ${$session.ghToken || ghToken}`,
Accept: 'application/vnd.github.v2.json' Accept: 'application/vnd.github.v2.json'
}); });
const packageJson = files.find( const packageJson = files.find(
@@ -113,7 +111,7 @@
foundConfig.buildPack = 'docker'; foundConfig.buildPack = 'docker';
} else if (packageJson) { } else if (packageJson) {
const data = await get(`${packageJson.git_url}`, { const data = await get(`${packageJson.git_url}`, {
Authorization: `Bearer ${ghToken}`, Authorization: `Bearer ${$session.ghToken}`,
Accept: 'application/vnd.github.v2.raw' Accept: 'application/vnd.github.v2.raw'
}); });
const json = JSON.parse(data) || {}; const json = JSON.parse(data) || {};

View File

@@ -1,5 +1,5 @@
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -12,6 +12,6 @@ export const post: RequestHandler = async (event) => {
await db.updateDeployKey({ id, deployKeyId }); await db.updateDeployKey({ id, deployKeyId });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
await db.configureDestinationForApplication({ id, destinationId }); await db.configureDestinationForApplication({ id, destinationId });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -14,16 +14,14 @@ export const get: RequestHandler = async (event) => {
try { try {
const found = await db.isBranchAlreadyUsed({ repository, branch, id }); const found = await db.isBranchAlreadyUsed({ repository, branch, id });
if (found) {
throw {
error: `Branch ${branch} is already used by another application`
};
}
return { return {
status: 200 status: 200,
body: {
used: found ? true : false
}
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -42,6 +40,6 @@ export const post: RequestHandler = async (event) => {
await db.configureGitRepository({ id, repository, branch, projectId, webhookToken }); await db.configureGitRepository({ id, repository, branch, projectId, webhookToken });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,7 +1,7 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import type { Load } from '@sveltejs/kit'; import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ params, url, stuff }) => { export const load: Load = async ({ params, url, stuff }) => {
const { application, githubToken, appId } = stuff; const { application, appId } = stuff;
if (application?.branch && application?.repository && !url.searchParams.get('from')) { if (application?.branch && application?.repository && !url.searchParams.get('from')) {
return { return {
status: 302, status: 302,
@@ -10,7 +10,6 @@
} }
return { return {
props: { props: {
githubToken,
application, application,
appId appId
} }
@@ -20,7 +19,6 @@
<script lang="ts"> <script lang="ts">
export let application; export let application;
export let githubToken;
export let appId; export let appId;
import GithubRepositories from './_GithubRepositories.svelte'; import GithubRepositories from './_GithubRepositories.svelte';
import GitlabRepositories from './_GitlabRepositories.svelte'; import GitlabRepositories from './_GitlabRepositories.svelte';
@@ -31,7 +29,7 @@
</div> </div>
<div class="flex flex-wrap justify-center"> <div class="flex flex-wrap justify-center">
{#if application.gitSource.type === 'github'} {#if application.gitSource.type === 'github'}
<GithubRepositories {application} {githubToken} /> <GithubRepositories {application} />
{:else if application.gitSource.type === 'gitlab'} {:else if application.gitSource.type === 'gitlab'}
<GitlabRepositories {application} {appId} /> <GitlabRepositories {application} {appId} />
{/if} {/if}

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
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,6 @@ export const post: RequestHandler = async (event) => {
await db.configureGitsource({ id, gitSourceId }); await db.configureGitsource({ id, gitSourceId });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -7,7 +7,7 @@ export const get: RequestHandler = async (event) => {
try { try {
return await db.getSshKey({ id }); return await db.getSshKey({ id });
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -15,6 +15,6 @@ export const post: RequestHandler = async (event) => {
try { try {
return await db.generateSshKey({ id }); return await db.generateSshKey({ id });
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const del: RequestHandler = async (event) => { export const del: RequestHandler = async (event) => {
@@ -14,6 +14,6 @@ export const del: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -4,7 +4,7 @@ import cuid from 'cuid';
import crypto from 'crypto'; import crypto from 'crypto';
import { buildQueue } from '$lib/queues'; import { buildQueue } from '$lib/queues';
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
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);
@@ -37,6 +37,6 @@ export const post: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,7 +1,7 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import { getGithubToken } from '$lib/components/common'; import { getGithubToken } from '$lib/components/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { checkContainer } from '$lib/haproxy'; import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
@@ -14,37 +14,44 @@ export const get: RequestHandler = async (event) => {
let githubToken = null; let githubToken = null;
let ghToken = null; let ghToken = null;
let isRunning = false; let isRunning = false;
const { id } = event.params; const { id } = event.params;
try { try {
const application = await db.getApplication({ id, teamId }); const application = await db.getApplication({ id, teamId });
const { gitSource } = application; const { gitSource } = application;
if (gitSource?.type === 'github' && gitSource?.githubApp) { if (gitSource?.type === 'github' && gitSource?.githubApp) {
const payload = { if (!event.locals.session.data.ghToken) {
iat: Math.round(new Date().getTime() / 1000), const payload = {
exp: Math.round(new Date().getTime() / 1000 + 60), iat: Math.round(new Date().getTime() / 1000),
iss: gitSource.githubApp.appId exp: Math.round(new Date().getTime() / 1000 + 600),
}; iss: gitSource.githubApp.appId
githubToken = jsonwebtoken.sign(payload, gitSource.githubApp.privateKey, { };
algorithm: 'RS256' githubToken = jsonwebtoken.sign(payload, gitSource.githubApp.privateKey, {
}); algorithm: 'RS256'
ghToken = await getGithubToken({ apiUrl: gitSource.apiUrl, application, githubToken }); });
ghToken = await getGithubToken({ apiUrl: gitSource.apiUrl, application, githubToken });
}
} }
if (application.destinationDockerId) { if (application.destinationDockerId) {
isRunning = await checkContainer(application.destinationDocker.engine, id); isRunning = await checkContainer(application.destinationDocker.engine, id);
} }
return { const payload = {
body: { body: {
isRunning, isRunning,
ghToken,
githubToken,
application, application,
appId appId
} },
headers: {}
}; };
if (ghToken) {
payload.headers = {
'set-cookie': [`ghToken=${ghToken}; HttpOnly; Path=/; Max-Age=15778800;`]
};
}
return payload;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -82,6 +89,6 @@ export const post: RequestHandler = async (event) => {
}); });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -42,7 +42,7 @@
import Explainer from '$lib/components/Explainer.svelte'; import Explainer from '$lib/components/Explainer.svelte';
import Setting from '$lib/components/Setting.svelte'; import Setting from '$lib/components/Setting.svelte';
import type Prisma from '@prisma/client'; import type Prisma from '@prisma/client';
import { getDomain, notNodeDeployments, staticDeployments } from '$lib/components/common'; import { notNodeDeployments, staticDeployments } from '$lib/components/common';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { post } from '$lib/api'; import { post } from '$lib/api';
const { id } = $page.params; const { id } = $page.params;
@@ -52,6 +52,7 @@
let loading = false; let loading = false;
let debug = application.settings.debug; let debug = application.settings.debug;
let previews = application.settings.previews; let previews = application.settings.previews;
let dualCerts = application.settings.dualCerts;
onMount(() => { onMount(() => {
domainEl.focus(); domainEl.focus();
@@ -64,8 +65,11 @@
if (name === 'previews') { if (name === 'previews') {
previews = !previews; previews = !previews;
} }
if (name === 'dualCerts') {
dualCerts = !dualCerts;
}
try { try {
await post(`/applications/${id}/settings.json`, { previews, debug }); await post(`/applications/${id}/settings.json`, { previews, debug, dualCerts });
return toast.push('Settings saved.'); return toast.push('Settings saved.');
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
@@ -252,7 +256,7 @@
</div> </div>
<div class="grid grid-flow-row gap-2 px-10"> <div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-3"> <div class="grid grid-cols-3">
<label for="fqdn" class="pt-2">Domain (FQDN)</label> <label for="fqdn" class="relative pt-2">Domain (FQDN)</label>
<div class="col-span-2"> <div class="col-span-2">
<input <input
readonly={!$session.isAdmin || isRunning} readonly={!$session.isAdmin || isRunning}
@@ -266,11 +270,21 @@
required required
/> />
<Explainer <Explainer
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application." text="If you specify <span class='text-green-500 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-500 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
/> />
</div> </div>
</div> </div>
<div class="grid grid-cols-2 items-center pb-8">
<Setting
dataTooltip="Must be stopped to modify."
disabled={isRunning}
isCenter={false}
bind:setting={dualCerts}
title="Generate SSL for www and non-www?"
description="It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-green-500'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both."
on:click={() => !isRunning && changeSettings('dualCerts')}
/>
</div>
{#if !staticDeployments.includes(application.buildPack)} {#if !staticDeployments.includes(application.buildPack)}
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-3 items-center">
<label for="port">Port</label> <label for="port">Port</label>
@@ -285,6 +299,7 @@
</div> </div>
</div> </div>
{/if} {/if}
{#if !notNodeDeployments.includes(application.buildPack)} {#if !notNodeDeployments.includes(application.buildPack)}
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-3 items-center">
<label for="installCommand">Install Command</label> <label for="installCommand">Install Command</label>
@@ -361,8 +376,7 @@
<div class="flex space-x-1 pb-5 font-bold"> <div class="flex space-x-1 pb-5 font-bold">
<div class="title">Features</div> <div class="title">Features</div>
</div> </div>
<div class="px-4 pb-10 sm:px-6"> <!-- <ul class="mt-2 divide-y divide-stone-800">
<!-- <ul class="mt-2 divide-y divide-stone-800">
<Setting <Setting
bind:setting={forceSSL} bind:setting={forceSSL}
on:click={() => changeSettings('forceSSL')} on:click={() => changeSettings('forceSSL')}
@@ -370,21 +384,24 @@
description="Creates a https redirect for all requests from http and also generates a https certificate for the domain through Let's Encrypt." description="Creates a https redirect for all requests from http and also generates a https certificate for the domain through Let's Encrypt."
/> />
</ul> --> </ul> -->
<ul class="mt-2 divide-y divide-stone-800"> <div class="px-10 pb-10">
<div class="grid grid-cols-2 items-center">
<Setting <Setting
isCenter={false}
bind:setting={previews} bind:setting={previews}
on:click={() => changeSettings('previews')} on:click={() => changeSettings('previews')}
title="Enable MR/PR Previews" title="Enable MR/PR Previews"
description="Creates previews from pull and merge requests." description="Creates previews from pull and merge requests."
/> />
</ul> </div>
<ul class="mt-2 divide-y divide-stone-800"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
isCenter={false}
bind:setting={debug} bind:setting={debug}
on:click={() => changeSettings('debug')} on:click={() => changeSettings('debug')}
title="Debug Logs" title="Debug Logs"
description="Enable debug logs during build phase. <br>(<span class='text-red-500'>sensitive information</span> could be visible in logs)" description="Enable debug logs during build phase. <br>(<span class='text-red-500'>sensitive information</span> could be visible in logs)"
/> />
</ul> </div>
</div> </div>
</div> </div>

View File

@@ -15,16 +15,32 @@
let loading = true; let loading = true;
let currentStatus; let currentStatus;
let streamInterval; let streamInterval;
let followingBuild;
let followingInterval;
let logsEl;
const { id } = $page.params; const { id } = $page.params;
const cleanAnsiCodes = (str: string) => str.replace(/\x1B\[(\d+)m/g, '');
function followBuild() {
followingBuild = !followingBuild;
if (followingBuild) {
followingInterval = setInterval(() => {
logsEl.scrollTop = logsEl.scrollHeight;
window.scrollTo(0, document.body.scrollHeight);
}, 100);
} else {
window.clearInterval(followingInterval);
}
}
async function streamLogs(sequence = 0) { async function streamLogs(sequence = 0) {
try { try {
let { logs: responseLogs, status } = await get( let { logs: responseLogs, status } = await get(
`/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${sequence}` `/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${sequence}`
); );
currentStatus = status; currentStatus = status;
logs = logs.concat(responseLogs); logs = logs.concat(responseLogs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
loading = false; loading = false;
streamInterval = setInterval(async () => { streamInterval = setInterval(async () => {
if (status !== 'running') { if (status !== 'running') {
@@ -38,18 +54,21 @@
); );
status = data.status; status = data.status;
currentStatus = status; currentStatus = status;
logs = logs.concat(data.logs);
logs = logs.concat(data.logs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
dispatch('updateBuildStatus', { status }); dispatch('updateBuildStatus', { status });
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
}, 1000); }, 1000);
} catch ({ error }) { } catch ({ error }) {
console.log(error);
return errorNotification(error); return errorNotification(error);
} }
} }
onDestroy(() => { onDestroy(() => {
clearInterval(streamInterval); clearInterval(streamInterval);
clearInterval(followingInterval);
}); });
onMount(async () => { onMount(async () => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
@@ -60,12 +79,38 @@
{#if loading} {#if loading}
<Loading /> <Loading />
{:else} {:else}
<div class="relative"> <div class="relative ">
{#if currentStatus === 'running'} {#if currentStatus === 'running'}
<LoadingLogs /> <LoadingLogs />
{/if} {/if}
<div class="flex justify-end sticky top-0 p-2">
<button
on:click={followBuild}
class="bg-transparent"
data-tooltip="Follow logs"
class:text-green-500={followingBuild}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="12" cy="12" r="9" />
<line x1="8" y1="12" x2="12" y2="16" />
<line x1="12" y1="8" x2="12" y2="16" />
<line x1="16" y1="12" x2="12" y2="16" />
</svg>
</button>
</div>
<div <div
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words" class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
bind:this={logsEl}
> >
{#each logs as log} {#each logs as log}
<div>{log.line + '\n'}</div> <div>{log.line + '\n'}</div>

View File

@@ -1,6 +1,6 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -23,6 +23,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dayjs } from '$lib/dayjs'; import { dayjs } from '$lib/dayjs';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -35,6 +35,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -33,7 +33,6 @@
export let buildCount; export let buildCount;
let buildId; let buildId;
$: buildId;
let skip = 0; let skip = 0;
let noMoreBuilds = buildCount < 5 || buildCount <= skip; let noMoreBuilds = buildCount < 5 || buildCount <= skip;
@@ -83,7 +82,7 @@
} }
async function loadBuild(build) { async function loadBuild(build) {
buildId = build; buildId = build;
goto(`/applications/${id}/logs/build?buildId=${buildId}`); await goto(`/applications/${id}/logs/build?buildId=${buildId}`);
} }
</script> </script>
@@ -92,45 +91,49 @@
Build logs of <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a> Build logs of <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
</div> </div>
</div> </div>
<div class="flex flex-row justify-start space-x-2 px-10 pt-6 "> <div class="block flex-row justify-start space-x-2 px-5 pt-6 sm:px-10 md:flex">
<div class="min-w-[16rem] space-y-2"> <div class="mb-4 min-w-[16rem] space-y-2 md:mb-0 ">
{#each builds as build (build.id)} <div class="top-4 md:sticky">
<div {#each builds as build, index (build.id)}
data-tooltip={new Intl.DateTimeFormat('default', dateOptions).format( <div
new Date(build.createdAt) data-tooltip={new Intl.DateTimeFormat('default', dateOptions).format(
) + `\n${build.status}`} new Date(build.createdAt)
on:click={() => loadBuild(build.id)} ) + `\n${build.status}`}
class="tooltip-top flex cursor-pointer items-center justify-center rounded-r border-l-2 border-transparent py-4 no-underline transition-all duration-100 hover:bg-coolgray-400 hover:shadow-xl" on:click={() => loadBuild(build.id)}
class:bg-coolgray-400={buildId === build.id} class:rounded-tr={index === 0}
class:border-red-500={build.status === 'failed'} class:rounded-br={index === builds.length - 1}
class:border-green-500={build.status === 'success'} class="tooltip-top flex cursor-pointer items-center justify-center border-l-2 border-transparent py-4 no-underline transition-all duration-100 hover:bg-coolgray-400 hover:shadow-xl "
class:border-yellow-500={build.status === 'inprogress'} class:bg-coolgray-400={buildId === build.id}
> class:border-red-500={build.status === 'failed'}
<div class="flex-col px-2"> class:border-green-500={build.status === 'success'}
<div class="text-sm font-bold"> class:border-yellow-500={build.status === 'running'}
{application.branch} >
<div class="flex-col px-2">
<div class="text-sm font-bold">
{application.branch}
</div>
<div class="text-xs">
{build.type}
</div>
</div> </div>
<div class="text-xs"> <div class="flex-1" />
{build.type}
</div>
</div>
<div class="flex-1" />
<div class="w-48 text-center text-xs"> <div class="w-48 text-center text-xs">
{#if build.status === 'running'} {#if build.status === 'running'}
<div class="font-bold">Running</div> <div class="font-bold">Running</div>
{:else} {:else}
<div>{build.since}</div> <div>{build.since}</div>
<div>Finished in <span class="font-bold">{build.took}s</span></div> <div>Finished in <span class="font-bold">{build.took}s</span></div>
{/if} {/if}
</div>
</div> </div>
</div> {/each}
{/each} </div>
{#if buildCount > 0 && !noMoreBuilds} <div class="flex space-x-2">
<button class="w-full" on:click={loadMoreBuilds}>Load More</button> <button disabled={noMoreBuilds} class="w-full" on:click={loadMoreBuilds}>Load More</button>
{/if} </div>
</div> </div>
<div class="w-96 flex-1"> <div class="flex-1 md:w-96">
{#if buildId} {#if buildId}
{#key buildId} {#key buildId}
<svelte:component this={BuildLog} {buildId} on:updateBuildStatus={updateBuildStatus} /> <svelte:component this={BuildLog} {buildId} on:updateBuildStatus={updateBuildStatus} />

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dayjs } from '$lib/dayjs'; import { dayjs } from '$lib/dayjs';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -48,6 +48,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -27,19 +27,23 @@
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { get } from '$lib/api'; import { get } from '$lib/api';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
let loadLogsInterval = null; let loadLogsInterval = null;
let logs = []; let logs = [];
let followingBuild;
let followingInterval;
let logsEl;
const { id } = $page.params; const { id } = $page.params;
onMount(async () => { onMount(async () => {
loadLogs(); loadLogs();
loadLogsInterval = setInterval(() => { loadLogsInterval = setInterval(() => {
loadLogs(); loadLogs();
}, 3000); }, 1000);
}); });
onDestroy(() => { onDestroy(() => {
clearInterval(loadLogsInterval); clearInterval(loadLogsInterval);
clearInterval(followingInterval);
}); });
async function loadLogs() { async function loadLogs() {
try { try {
@@ -50,6 +54,18 @@
return errorNotification(error); return errorNotification(error);
} }
} }
function followBuild() {
followingBuild = !followingBuild;
if (followingBuild) {
followingInterval = setInterval(() => {
logsEl.scrollTop = logsEl.scrollHeight;
window.scrollTo(0, document.body.scrollHeight);
}, 100);
} else {
window.clearInterval(followingInterval);
}
}
</script> </script>
<div class="flex space-x-1 p-6 font-bold"> <div class="flex space-x-1 p-6 font-bold">
@@ -61,14 +77,42 @@
{#if logs.length === 0} {#if logs.length === 0}
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div> <div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
{:else} {:else}
<div class="relative w-full"> <div class="relative">
<LoadingLogs /> <LoadingLogs />
<div class="flex justify-end sticky top-0 p-2">
<button
on:click={followBuild}
class="bg-transparent"
data-tooltip="Follow logs"
class:text-green-500={followingBuild}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="12" cy="12" r="9" />
<line x1="8" y1="12" x2="12" y2="16" />
<line x1="12" y1="8" x2="12" y2="16" />
<line x1="16" y1="12" x2="12" y2="16" />
</svg>
</button>
</div>
<div <div
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 p-6 whitespace-pre-wrap break-words w-full" class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
bind:this={logsEl}
> >
{#each logs as log} <div class="px-2">
{log + '\n'} {#each logs as log}
{/each} {log + '\n'}
{/each}
</div>
</div> </div>
</div> </div>
{/if} {/if}

View File

@@ -1,6 +1,6 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
@@ -39,6 +39,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -9,7 +9,8 @@
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let nameEl;
let valueEl;
const { id } = $page.params; const { id } = $page.params;
async function removeSecret() { async function removeSecret() {
try { try {
@@ -25,6 +26,15 @@
} }
} }
async function saveSecret() { async function saveSecret() {
const nameValid = nameEl.checkValidity();
const valueValid = valueEl.checkValidity();
if (!nameValid) {
return nameEl.reportValidity();
}
if (!valueValid) {
return valueEl.reportValidity();
}
try { try {
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret }); await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret });
dispatch('refresh'); dispatch('refresh');
@@ -47,7 +57,9 @@
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white"> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<input <input
id="secretName" id="secretName"
bind:this={nameEl}
bind:value={name} bind:value={name}
required
placeholder="EXAMPLE_VARIABLE" placeholder="EXAMPLE_VARIABLE"
class="-mx-2 w-64 border-2 border-transparent" class="-mx-2 w-64 border-2 border-transparent"
readonly={!isNewSecret} readonly={!isNewSecret}
@@ -59,6 +71,8 @@
<input <input
id="secretValue" id="secretValue"
bind:value bind:value
bind:this={valueEl}
required
placeholder="J$#@UIO%HO#$U%H" placeholder="J$#@UIO%HO#$U%H"
class="-mx-2 w-64 border-2 border-transparent" class="-mx-2 w-64 border-2 border-transparent"
class:bg-transparent={!isNewSecret} class:bg-transparent={!isNewSecret}

View File

@@ -1,6 +1,6 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -18,7 +18,7 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -42,7 +42,7 @@ export const post: RequestHandler = async (event) => {
}; };
} }
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
export const del: RequestHandler = async (event) => { export const del: RequestHandler = async (event) => {
@@ -58,6 +58,6 @@ export const del: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -46,22 +46,21 @@
<tr> <tr>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400" class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white">Name</th
>Name</th
> >
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400" class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Value</th >Value</th
> >
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400" class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Need during buildtime?</th >Need during buildtime?</th
> >
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400" class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
/> />
</tr> </tr>
</thead> </thead>

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
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,12 +8,12 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { id } = event.params; const { id } = event.params;
const { debug, previews } = await event.request.json(); const { debug, previews, dualCerts } = await event.request.json();
try { try {
await db.setApplicationSettings({ id, debug, previews }); await db.setApplicationSettings({ id, debug, previews, dualCerts });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getDomain, getUserDetails } from '$lib/common'; import { getDomain, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import { removeProxyConfiguration } from '$lib/haproxy'; import { removeProxyConfiguration } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -16,16 +16,15 @@ export const post: RequestHandler = async (event) => {
id, id,
teamId teamId
}); });
const domain = getDomain(fqdn);
if (destinationDockerId) { if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker }); const docker = dockerInstance({ destinationDocker });
await docker.engine.getContainer(id).stop(); await docker.engine.getContainer(id).stop();
} }
await removeProxyConfiguration({ domain }); await removeProxyConfiguration(fqdn);
return { return {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -16,6 +16,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -20,7 +20,7 @@
</script> </script>
<script lang="ts"> <script lang="ts">
export let applications: Array<Applications>; export let applications: Array<Application>;
import { session } from '$app/stores'; import { session } from '$app/stores';
import Application from './_Application.svelte'; import Application from './_Application.svelte';
</script> </script>

View File

@@ -1,6 +1,6 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -25,7 +25,7 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -7,72 +7,62 @@
<div class="title">CouchDB</div> <div class="title">CouchDB</div>
</div> </div>
<div class="px-10"> <div class="px-10">
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="defaultDatabase">Default Database</label> <label for="defaultDatabase">Default Database</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField required
required readonly={database.defaultDatabase}
readonly={database.defaultDatabase} disabled={database.defaultDatabase}
disabled={database.defaultDatabase} placeholder="eg: mydb"
placeholder="eg: mydb" id="defaultDatabase"
id="defaultDatabase" name="defaultDatabase"
name="defaultDatabase" bind:value={database.defaultDatabase}
bind:value={database.defaultDatabase} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="dbUser">User</label> <label for="dbUser">User</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField readonly
readonly disabled
disabled placeholder="Generated automatically after start"
placeholder="Generated automatically after start" id="dbUser"
id="dbUser" name="dbUser"
name="dbUser" value={database.dbUser}
value={database.dbUser} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="dbUserPassword">Password</label> <label for="dbUserPassword">Password</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField readonly
readonly disabled
disabled placeholder="Generated automatically after start"
placeholder="Generated automatically after start" isPasswordField
isPasswordField id="dbUserPassword"
id="dbUserPassword" name="dbUserPassword"
name="dbUserPassword" value={database.dbUserPassword}
value={database.dbUserPassword} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="rootUser">Root User</label> <label for="rootUser">Root User</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField readonly
readonly disabled
disabled placeholder="Generated automatically after start"
placeholder="Generated automatically after start" id="rootUser"
id="rootUser" name="rootUser"
name="rootUser" value={database.rootUser}
value={database.rootUser} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="rootUserPassword">Root's Password</label> <label for="rootUserPassword">Root's Password</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField readonly
readonly disabled
disabled placeholder="Generated automatically after start"
placeholder="Generated automatically after start" isPasswordField
isPasswordField id="rootUserPassword"
id="rootUserPassword" name="rootUserPassword"
name="rootUserPassword" value={database.rootUserPassword}
value={database.rootUserPassword} />
/>
</div>
</div> </div>
</div> </div>

View File

@@ -36,7 +36,7 @@
function generateUrl() { function generateUrl() {
return browser return browser
? `${database.type}://${ ? `${database.type}://${database.type === 'redis' && ':'}${
databaseDbUser ? databaseDbUser + ':' : '' databaseDbUser ? databaseDbUser + ':' : ''
}${databaseDbUserPassword}@${ }${databaseDbUserPassword}@${
isPublic isPublic
@@ -88,70 +88,60 @@
</div> </div>
<div class="grid grid-flow-row gap-2 px-10"> <div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="name">Name</label> <label for="name">Name</label>
<div class="col-span-2 "> <input
<input readonly={!$session.isAdmin}
readonly={!$session.isAdmin} name="name"
name="name" id="name"
id="name" bind:value={database.name}
bind:value={database.name} required
required />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="destination">Destination</label> <label for="destination">Destination</label>
<div class="col-span-2"> {#if database.destinationDockerId}
{#if database.destinationDockerId} <div class="no-underline">
<div class="no-underline"> <input
<input value={database.destinationDocker.name}
value={database.destinationDocker.name} id="destination"
id="destination" disabled
disabled readonly
readonly class="bg-transparent "
class="bg-transparent " />
/> </div>
</div> {/if}
{/if}
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="version">Version</label> <label for="version">Version</label>
<div class="col-span-2 "> <input value={database.version} readonly disabled class="bg-transparent " />
<input value={database.version} readonly disabled class="bg-transparent " />
</div>
</div> </div>
</div> </div>
<div class="grid grid-flow-row gap-2 px-10"> <div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="host">Host</label> <label for="host">Host</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField placeholder="Generated automatically after start"
placeholder="Generated automatically after start" isPasswordField={false}
isPasswordField={false} readonly
readonly disabled
disabled id="host"
id="host" name="host"
name="host" value={database.id}
value={database.id} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="publicPort">Port</label> <label for="publicPort">Port</label>
<div class="col-span-2"> <CopyPasswordField
<CopyPasswordField placeholder="Generated automatically after start"
placeholder="Generated automatically after start" id="publicPort"
id="publicPort" readonly
readonly disabled
disabled name="publicPort"
name="publicPort" value={isPublic ? database.publicPort : privatePort}
value={isPublic ? database.publicPort : privatePort} />
/>
</div>
</div> </div>
</div> </div>
<div class="grid grid-flow-row gap-2"> <div class="grid grid-flow-row gap-2">
@@ -166,44 +156,42 @@
{:else if database.type === 'couchdb'} {:else if database.type === 'couchdb'}
<CouchDb bind:database /> <CouchDb bind:database />
{/if} {/if}
<div class="grid grid-cols-3 items-center px-10 pb-8"> <div class="grid grid-cols-2 items-center px-10 pb-8">
<label for="url">Connection String</label> <label for="url">Connection String</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField textarea={true}
textarea={true} placeholder="Generated automatically after start"
placeholder="Generated automatically after start" isPasswordField={false}
isPasswordField={false} id="url"
id="url" name="url"
name="url" readonly
readonly disabled
disabled value={databaseUrl}
value={databaseUrl} />
/>
</div>
</div> </div>
</div> </div>
</form> </form>
<div class="flex space-x-1 pb-5 font-bold"> <div class="flex space-x-1 pb-5 font-bold">
<div class="title">Features</div> <div class="title">Features</div>
</div> </div>
<div class="px-4 pb-10 sm:px-6"> <div class="px-10 pb-10">
<ul class="mt-2 divide-y divide-stone-800"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
bind:setting={isPublic} bind:setting={isPublic}
on:click={() => changeSettings('isPublic')} on:click={() => changeSettings('isPublic')}
title="Set it public" title="Set it public"
description="Your database will be reachable over the internet. <br>Take security seriously in this case!" description="Your database will be reachable over the internet. <br>Take security seriously in this case!"
/> />
</ul> </div>
{#if database.type === 'redis'} {#if database.type === 'redis'}
<ul class="mt-2 divide-y divide-stone-800"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
bind:setting={appendOnly} bind:setting={appendOnly}
on:click={() => changeSettings('appendOnly')} on:click={() => changeSettings('appendOnly')}
title="Change append only mode" title="Change append only mode"
description="Useful if you would like to restore redis data from a backup.<br><span class='font-bold text-white'>Database restart is required.</span>" description="Useful if you would like to restore redis data from a backup.<br><span class='font-bold text-white'>Database restart is required.</span>"
/> />
</ul> </div>
{/if} {/if}
</div> </div>
</div> </div>

View File

@@ -7,31 +7,27 @@
<div class="title">MongoDB</div> <div class="title">MongoDB</div>
</div> </div>
<div class="px-10"> <div class="px-10">
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="rootUser">Root User</label> <label for="rootUser">Root User</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField placeholder="Generated automatically after start"
placeholder="Generated automatically after start" id="rootUser"
id="rootUser" readonly
readonly disabled
disabled name="rootUser"
name="rootUser" value={database.rootUser}
value={database.rootUser} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="rootUserPassword">Root's Password</label> <label for="rootUserPassword">Root's Password</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField placeholder="Generated automatically after start"
placeholder="Generated automatically after start" isPasswordField={true}
isPasswordField={true} readonly
readonly disabled
disabled id="rootUserPassword"
id="rootUserPassword" name="rootUserPassword"
name="rootUserPassword" value={database.rootUserPassword}
value={database.rootUserPassword} />
/>
</div>
</div> </div>
</div> </div>

View File

@@ -7,72 +7,62 @@
<div class="title">MySQL</div> <div class="title">MySQL</div>
</div> </div>
<div class=" px-10"> <div class=" px-10">
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="defaultDatabase">Default Database</label> <label for="defaultDatabase">Default Database</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField required
required readonly={database.defaultDatabase}
readonly={database.defaultDatabase} disabled={database.defaultDatabase}
disabled={database.defaultDatabase} placeholder="eg: mydb"
placeholder="eg: mydb" id="defaultDatabase"
id="defaultDatabase" name="defaultDatabase"
name="defaultDatabase" bind:value={database.defaultDatabase}
bind:value={database.defaultDatabase} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="dbUser">User</label> <label for="dbUser">User</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField readonly
readonly disabled
disabled placeholder="Generated automatically after start"
placeholder="Generated automatically after start" id="dbUser"
id="dbUser" name="dbUser"
name="dbUser" value={database.dbUser}
value={database.dbUser} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="dbUserPassword">Password</label> <label for="dbUserPassword">Password</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField readonly
readonly disabled
disabled placeholder="Generated automatically after start"
placeholder="Generated automatically after start" isPasswordField
isPasswordField id="dbUserPassword"
id="dbUserPassword" name="dbUserPassword"
name="dbUserPassword" value={database.dbUserPassword}
value={database.dbUserPassword} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="rootUser">Root User</label> <label for="rootUser">Root User</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField readonly
readonly disabled
disabled placeholder="Generated automatically after start"
placeholder="Generated automatically after start" id="rootUser"
id="rootUser" name="rootUser"
name="rootUser" value={database.rootUser}
value={database.rootUser} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="rootUserPassword">Root's Password</label> <label for="rootUserPassword">Root's Password</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField readonly
readonly disabled
disabled placeholder="Generated automatically after start"
placeholder="Generated automatically after start" isPasswordField
isPasswordField id="rootUserPassword"
id="rootUserPassword" name="rootUserPassword"
name="rootUserPassword" value={database.rootUserPassword}
value={database.rootUserPassword} />
/>
</div>
</div> </div>
</div> </div>

View File

@@ -7,45 +7,39 @@
<div class="title">PostgreSQL</div> <div class="title">PostgreSQL</div>
</div> </div>
<div class="px-10"> <div class="px-10">
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="defaultDatabase">Default Database</label> <label for="defaultDatabase">Default Database</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField required
required readonly={database.defaultDatabase}
readonly={database.defaultDatabase} disabled={database.defaultDatabase}
disabled={database.defaultDatabase} placeholder="eg: mydb"
placeholder="eg: mydb" id="defaultDatabase"
id="defaultDatabase" name="defaultDatabase"
name="defaultDatabase" bind:value={database.defaultDatabase}
bind:value={database.defaultDatabase} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="dbUser">User</label> <label for="dbUser">User</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField readonly
readonly disabled
disabled placeholder="Generated automatically after start"
placeholder="Generated automatically after start" id="dbUser"
id="dbUser" name="dbUser"
name="dbUser" value={database.dbUser}
value={database.dbUser} />
/>
</div>
</div> </div>
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="dbUserPassword">Password</label> <label for="dbUserPassword">Password</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField readonly
readonly disabled
disabled placeholder="Generated automatically after start"
placeholder="Generated automatically after start" isPasswordField
isPasswordField id="dbUserPassword"
id="dbUserPassword" name="dbUserPassword"
name="dbUserPassword" value={database.dbUserPassword}
value={database.dbUserPassword} />
/>
</div>
</div> </div>
</div> </div>

View File

@@ -7,32 +7,17 @@
<div class="title">Redis</div> <div class="title">Redis</div>
</div> </div>
<div class="px-10"> <div class="px-10">
<!-- <div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2 items-center">
<label for="dbUser">User</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
id="dbUser"
name="dbUser"
bind:value={database.dbUser}
/>
</div>
</div> -->
<div class="grid grid-cols-3 items-center">
<label for="dbUserPassword">Password</label> <label for="dbUserPassword">Password</label>
<div class="col-span-2 "> <CopyPasswordField
<CopyPasswordField disabled
disabled readonly
readonly placeholder="Generated automatically after start"
placeholder="Generated automatically after start" isPasswordField
isPasswordField id="dbUserPassword"
id="dbUserPassword" name="dbUserPassword"
name="dbUserPassword" value={database.dbUserPassword}
value={database.dbUserPassword} />
/>
</div>
</div> </div>
<!-- <div class="grid grid-cols-3 items-center"> <!-- <div class="grid grid-cols-3 items-center">
<label for="rootUser">Root User</label> <label for="rootUser">Root User</label>

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
await db.configureDestinationForDatabase({ id, destinationId }); await db.configureDestinationForDatabase({ id, destinationId });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database'; import { ErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -27,6 +27,6 @@ export const post: RequestHandler = async (event) => {
status: 201 status: 201
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database'; import { ErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -31,6 +31,6 @@ export const post: RequestHandler = async (event) => {
status: 201 status: 201
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler, stopDatabase } from '$lib/database'; import { ErrorHandler, stopDatabase } from '$lib/database';
import { deleteProxy } from '$lib/haproxy'; import { deleteProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -17,6 +17,6 @@ export const del: RequestHandler = async (event) => {
await db.removeDatabase({ id }); await db.removeDatabase({ id });
return { status: 200 }; return { status: 200 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common'; import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { generateDatabaseConfiguration, getVersions, PrismaErrorHandler } from '$lib/database'; import { generateDatabaseConfiguration, getVersions, ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -40,7 +40,7 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -64,6 +64,6 @@ export const post: RequestHandler = async (event) => {
}); });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { generateDatabaseConfiguration, PrismaErrorHandler } from '$lib/database'; import { generateDatabaseConfiguration, ErrorHandler } from '$lib/database';
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy'; import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -29,6 +29,6 @@ export const post: RequestHandler = async (event) => {
status: 201 status: 201
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common'; import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { generateDatabaseConfiguration, PrismaErrorHandler } from '$lib/database'; import { generateDatabaseConfiguration, ErrorHandler } 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';
@@ -77,6 +77,6 @@ export const post: RequestHandler = async (event) => {
}; };
} }
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler, stopDatabase } from '$lib/database'; import { ErrorHandler, stopDatabase } from '$lib/database';
import { stopTcpHttpProxy } from '$lib/haproxy'; import { stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -20,6 +20,6 @@ export const post: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -15,6 +15,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -14,6 +14,7 @@
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock'; let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
// let scannedApps = []; // let scannedApps = [];
let loading = false; let loading = false;
let restarting = false;
async function handleSubmit() { async function handleSubmit() {
loading = true; loading = true;
try { try {
@@ -42,6 +43,17 @@
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
} else if (state === true && destination.isCoolifyProxyUsed === false) {
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
try {
await post(`/destinations/${id}/settings.json`, {
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
engine: destination.engine
});
await startProxy();
} catch ({ error }) {
return errorNotification(error);
}
} }
}); });
async function changeProxySetting() { async function changeProxySetting() {
@@ -89,6 +101,25 @@
return errorNotification(error); return errorNotification(error);
} }
} }
async function forceRestartProxy() {
const sure = confirm(
'Are you sure you want to restart the proxy? Everyting will be reconfigured in ~10 sec.'
);
if (sure) {
try {
restarting = true;
toast.push('Coolify Proxy restarting...');
await post(`/destinations/${id}/restart.json`, {
engine: destination.engine,
fqdn: settings.fqdn
});
} catch ({ error }) {
setTimeout(() => {
window.location.reload();
}, 5000);
}
}
}
</script> </script>
<div class="flex justify-center px-6 pb-8"> <div class="flex justify-center px-6 pb-8">
@@ -103,6 +134,12 @@
disabled={loading} disabled={loading}
>{loading ? 'Saving...' : 'Save'} >{loading ? 'Saving...' : 'Save'}
</button> </button>
<button
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
disabled={restarting}
on:click|preventDefault={forceRestartProxy}
>{restarting ? 'Restarting... please wait...' : 'Force restart proxy'}</button
>
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps} <!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
>Scan for applications</button >Scan for applications</button
> --> > -->
@@ -144,21 +181,18 @@
/> />
</div> </div>
</div> </div>
<div class="flex justify-start"> <div class="grid grid-cols-2 items-center">
<ul class="mt-2 divide-y divide-stone-800"> <Setting
<Setting disabled={cannotDisable}
disabled={cannotDisable} bind:setting={destination.isCoolifyProxyUsed}
bind:setting={destination.isCoolifyProxyUsed} on:click={changeProxySetting}
on:click={changeProxySetting} title="Use Coolify Proxy?"
isPadding={false} description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration. Databases will have their own proxy. <br><br>${
title="Use Coolify Proxy?" cannotDisable
description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration. Databases will have their own proxy. <br><br>${ ? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
cannotDisable : ''
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>' }`}
: '' />
}`}
/>
</ul>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getEngine, getTeam, getUserDetails } from '$lib/common'; import { asyncExecShell, getEngine, getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { checkContainer } from '$lib/haproxy'; import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -23,7 +23,7 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -37,7 +37,7 @@ export const post: RequestHandler = async (event) => {
await db.updateDestination({ id, name, engine, network }); await db.updateDestination({ id, name, engine, network });
return { status: 200 }; return { status: 200 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -51,6 +51,6 @@ export const del: RequestHandler = async (event) => {
await db.removeDestination({ id }); await db.removeDestination({ id });
return { status: 200 }; return { status: 200 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -0,0 +1,34 @@
import { getDomain, getUserDetails } from '$lib/common';
import { ErrorHandler } from '$lib/database';
import * as db from '$lib/database';
import {
configureCoolifyProxyOn,
forceSSLOnApplication,
setWwwRedirection,
startCoolifyProxy,
stopCoolifyProxy
} 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 { engine, fqdn } = await event.request.json();
try {
const domain = getDomain(fqdn);
await stopCoolifyProxy(engine);
await startCoolifyProxy(engine);
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
await configureCoolifyProxyOn(fqdn);
await setWwwRedirection(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication(domain);
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
};

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getTeam, getUserDetails } from '$lib/common'; import { asyncExecShell, getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -59,6 +59,6 @@ export const post: RequestHandler = async (event) => {
status: 404 status: 404
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
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,6 @@ export const post: RequestHandler = async (event) => {
await db.setDestinationSettings({ engine, isCoolifyProxyUsed }); await db.setDestinationSettings({ engine, isCoolifyProxyUsed });
return { status: 200 }; return { status: 200 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy'; import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -16,6 +16,6 @@ export const post: RequestHandler = async (event) => {
}; };
} catch (error) { } catch (error) {
await stopCoolifyProxy(engine); await stopCoolifyProxy(engine);
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { stopCoolifyProxy } from '$lib/haproxy'; import { stopCoolifyProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (request) => { export const get: RequestHandler = async (request) => {
@@ -14,6 +14,6 @@ export const get: RequestHandler = async (request) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,7 +1,7 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import type { Load } from '@sveltejs/kit'; import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, session }) => { export const load: Load = async ({ fetch, session }) => {
const url = `/index.json`; const url = `/dashboard.json`;
const res = await fetch(url); const res = await fetch(url);
if (res.ok) { if (res.ok) {

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
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,7 +13,7 @@ export const post: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -28,6 +28,6 @@ export const get: RequestHandler = async (event) => {
await db.getUser({ userId }); await db.getUser({ userId });
return { status: 200 }; return { status: 200 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -9,7 +9,7 @@
let emailEl; let emailEl;
let email, password; let email, password;
if (browser && $session.uid) { if (browser && $session.userId) {
goto('/'); goto('/');
} }
onMount(() => { onMount(() => {
@@ -34,7 +34,7 @@
</script> </script>
<div class="flex h-screen flex-col items-center justify-center"> <div class="flex h-screen flex-col items-center justify-center">
{#if $session.uid} {#if $session.userId}
<div class="flex justify-center px-4 text-xl font-bold">Already logged in...</div> <div class="flex justify-center px-4 text-xl font-bold">Already logged in...</div>
{:else} {:else}
<div class="flex justify-center px-4"> <div class="flex justify-center px-4">
@@ -67,6 +67,7 @@
class:text-stone-600={loading} class:text-stone-600={loading}
class:bg-coollabs={!loading}>{loading ? 'Authenticating...' : 'Login'}</button class:bg-coollabs={!loading}>{loading ? 'Authenticating...' : 'Login'}</button
> >
<button on:click|preventDefault={() => goto('/reset')}>Reset password</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -24,6 +24,6 @@ export const post: RequestHandler = async (event) => {
}); });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails, uniqueName } from '$lib/common'; import { getUserDetails, uniqueName } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
const { id } = await db.newApplication({ name, teamId }); const { id } = await db.newApplication({ name, teamId });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
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,6 @@ export const post: RequestHandler = async (event) => {
const { id } = await db.newDatabase({ name, teamId }); const { id } = await db.newDatabase({ name, teamId });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { isDockerNetworkExists, PrismaErrorHandler } from '$lib/database'; import { isDockerNetworkExists, ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -18,6 +18,6 @@ export const post: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getUserDetails } from '$lib/common'; import { asyncExecShell, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
const id = await db.newDestination({ name, teamId, engine, network, isCoolifyProxyUsed }); const id = await db.newDestination({ name, teamId, engine, network, isCoolifyProxyUsed });
return { status: 200, body: { id } }; return { status: 200, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails, uniqueName } from '$lib/common'; import { getUserDetails, uniqueName } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
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,6 @@ export const post: RequestHandler = async (event) => {
const { id } = await db.newService({ name, teamId }); const { id } = await db.newService({ name, teamId });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -12,6 +12,6 @@ export const post: RequestHandler = async (event) => {
const { id } = await db.newSource({ name, teamId, type, htmlUrl, apiUrl, organization }); const { id } = await db.newSource({ name, teamId, type, htmlUrl, apiUrl, organization });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (e) { } catch (e) {
return PrismaErrorHandler(e); return ErrorHandler(e);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails, uniqueName } from '$lib/common'; import { getUserDetails, uniqueName } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
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,6 @@ export const post: RequestHandler = async (event) => {
const { id } = await db.newTeam({ name, userId }); const { id } = await db.newTeam({ name, userId });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -0,0 +1,26 @@
import type { RequestHandler } from '@sveltejs/kit';
import * as db from '$lib/database';
export const get: RequestHandler = async () => {
const users = await db.prisma.user.findMany({});
return {
status: 200,
body: {
users
}
};
};
export const post: RequestHandler = async (event) => {
const { secretKey } = await event.request.json();
if (secretKey !== process.env.COOLIFY_SECRET_KEY) {
return {
status: 500,
body: {
error: 'Invalid secret key.'
}
};
}
return {
status: 200
};
};

View File

@@ -0,0 +1,96 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { get, post } from '$lib/api';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
let secretKey;
let password = false;
let users = [];
async function handleSubmit() {
try {
await post(`/reset.json`, { secretKey });
password = true;
const data = await get('/reset.json');
users = data.users;
return;
} catch ({ error }) {
return errorNotification(error);
}
}
async function resetPassword(user) {
try {
await post(`/reset/password.json`, { secretKey, user });
toast.push('Password reset done.');
return;
} catch ({ error }) {
return errorNotification(error);
}
}
</script>
<div class="icons fixed top-0 left-0 m-3 cursor-pointer" on:click={() => goto('/')}>
<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" />
<line x1="5" y1="12" x2="19" y2="12" />
<line x1="5" y1="12" x2="11" y2="18" />
<line x1="5" y1="12" x2="11" y2="6" />
</svg>
</div>
<div class="pb-10 pt-24 text-center text-4xl font-bold">Reset Password</div>
<div class="flex items-center justify-center">
{#if password}
<table class="mx-2 text-left">
<thead class="mb-2">
<tr>
<th class="px-2">Email</th>
<th>New password</th>
</tr>
</thead>
<tbody>
{#each users as user}
<tr>
<td class="px-2">{user.email}</td>
<td class="flex space-x-2">
<input
id="newPassword"
name="newPassword"
bind:value={user.newPassword}
placeholder="Super secure new password"
/>
<button
class="mx-auto my-4 w-32 bg-coollabs hover:bg-coollabs-100"
on:click={() => resetPassword(user)}>Reset</button
></td
>
</tr>
{/each}
</tbody>
</table>
{:else}
<form class="flex flex-col" on:submit|preventDefault={handleSubmit}>
<div class="text-center text-2xl py-2 font-bold">Secret Key</div>
<CopyPasswordField
isPasswordField={true}
id="secretKey"
name="secretKey"
bind:value={secretKey}
placeholder="You can find it in ~/coolify/.env (COOLIFY_SECRET_KEY)"
/>
<button type="submit" class="bg-coollabs hover:bg-coollabs-100 mx-auto w-32 my-4"
>Submit</button
>
</form>
{/if}
</div>

Some files were not shown because too many files have changed in this diff Show More