Compare commits

...

112 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
Andras Bacsai
88aa620cb4 Merge pull request #121 from coollabsio/next
v2.0.7
2022-02-13 23:45:49 +01:00
Andras Bacsai
70d3448110 fix: New secret should have default values 2022-02-13 23:31:52 +01:00
Andras Bacsai
09a1a406a6 Bump version 2022-02-13 23:19:08 +01:00
Andras Bacsai
40939d0b7f fix: Build secrets should be visible in runtime 2022-02-13 23:17:50 +01:00
Andras Bacsai
aec1d184c8 feat: www <-> non-www redirection 2022-02-13 22:56:37 +01:00
Andras Bacsai
69d3cb5dd8 feat: www <-> non-www redirection for apps 2022-02-13 15:04:00 +01:00
Andras Bacsai
3deff162bb code cleanup 2022-02-13 13:46:51 +01:00
Andras Bacsai
4ed54568d3 fix: package.json 2022-02-12 15:34:55 +01:00
Andras Bacsai
004724da55 Merge pull request #119 from coollabsio/next
Unique secrets by applications
2022-02-12 15:31:53 +01:00
Andras Bacsai
97e9b5ffe3 Merge branch 'main' into next 2022-02-12 15:31:45 +01:00
Andras Bacsai
45f920f802 fix: unique secret by application
css: redesign secret page
2022-02-12 15:23:58 +01:00
Andras Bacsai
2b31532d19 Update README.md 2022-02-11 23:04:50 +01:00
Andras Bacsai
e7a6ecf95b fix: Typo 2022-02-11 21:50:47 +01:00
Andras Bacsai
545c98cee0 Merge pull request #117 from coollabsio/next
Update README.md
2022-02-11 21:50:19 +01:00
Andras Bacsai
d29ccbfe37 Update README.md 2022-02-11 21:49:30 +01:00
Andras Bacsai
d0807862e6 Merge pull request #116 from coollabsio/next
v2.0.5
2022-02-11 21:37:01 +01:00
Andras Bacsai
b92616dc14 chore: Version 2022-02-11 21:29:35 +01:00
Andras Bacsai
a1a436300d fix: Check sentry 2022-02-11 21:27:28 +01:00
Andras Bacsai
16a5aeb1ba chore: Version 2022-02-11 21:23:16 +01:00
Andras Bacsai
872095ff7a fix: Local static assets 2022-02-11 21:22:41 +01:00
Andras Bacsai
d88f2ea4c3 fix: More error handling in proxy configuration + cleanups 2022-02-11 21:14:47 +01:00
Andras Bacsai
02e0385ab8 Preserve build image as well 2022-02-11 21:07:31 +01:00
Andras Bacsai
c9751d4cd9 More images to tag 2022-02-11 21:06:48 +01:00
Andras Bacsai
162b637992 fix: Cleanup images 2022-02-11 21:04:48 +01:00
Andras Bacsai
a10ddd4063 fix: Delete all build files 2022-02-11 20:28:56 +01:00
Andras Bacsai
f46ccc63a7 Update staging release (final, I promise) 2022-02-11 15:46:17 +01:00
Andras Bacsai
fc04a45744 Update staging script (again) 2022-02-11 15:35:01 +01:00
Andras Bacsai
90c2b59a51 Update staging script 2022-02-11 15:34:45 +01:00
Andras Bacsai
d6bee99c1b update releast script 2022-02-11 15:31:53 +01:00
Andras Bacsai
0871d47568 feat: VaultWarden service 2022-02-11 15:31:25 +01:00
Andras Bacsai
5c646c1898 fix: Haproxy check should not throw error 2022-02-11 15:11:10 +01:00
Andras Bacsai
8974de165f fix: PreventDefault on a button, thats all 2022-02-11 13:43:45 +01:00
Andras Bacsai
e622294b87 feat: New update process (#115) 2022-02-11 11:46:47 +01:00
Andras Bacsai
cf9d32b556 Merge pull request #114 from coollabsio/next
fix: Docker Engine bugs
2022-02-11 09:22:28 +01:00
Andras Bacsai
e2d6b5bf64 fix: version 2022-02-11 09:13:51 +01:00
Andras Bacsai
dec58fd6d1 feat: Use tags in update 2022-02-11 09:00:45 +01:00
Andras Bacsai
dbb2241213 fix: Docker Engine bug related to live-restore and IPs 2022-02-11 08:42:47 +01:00
181 changed files with 2699 additions and 1842 deletions

1
.gitignore vendored
View File

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

View File

@@ -8,6 +8,8 @@ RUN yarn build
FROM node:16.14.0-alpine
WORKDIR /app
LABEL coolify.managed true
RUN apk add --no-cache git openssh-client curl jq cmake sqlite
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6

View File

@@ -2,6 +2,66 @@
An open-source & self-hostable Heroku / Netlify alternative.
## Installation
Installation is automated with the following command:
```bash
/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"
```
## Migration from v1
A fresh installation is necessary. v2 is not compatible with v1.
## Features
### Git Sources
You can use the official ones or your self hosted version!
- Github
- GitLab
- Bitbucket (WIP)
### Destinations
- Local Docker Engine
- Remote Docker Engine (WIP)
- Kubernetes (WIP)
### Applications
- Static sites
- NodeJS
- VueJS
- NuxtJS
- NextJS
- React/Preact
- NextJS
- Gatsby
- Svelte
- PHP
- Rust
- Dockerfile (you can provide it)
### Databases
- MongoDB
- MySQL
- PostgreSQL
- CouchDB
- Redis
### One-click services
- [WordPress](https://wordpress.org)
- [Plausible Analytics](https://plausible.io)
- [NocoDB](https://nocodb.com)
- [VSCode Server](https://github.com/cdr/code-server)
- [MinIO](https://min.io)
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
## Support
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
@@ -11,7 +71,7 @@ An open-source & self-hostable Heroku / Netlify alternative.
## 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)
## License

View File

@@ -2,7 +2,7 @@ version: '3.8'
services:
coolify:
image: coollabsio/coolify:latest
image: coollabsio/coolify:${TAG:-latest}
restart: always
container_name: coolify
ports:

View File

@@ -1,12 +1,12 @@
{
"name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "2.0.3",
"version": "2.0.14",
"license": "AGPL-3.0",
"scripts": {
"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
"dev:stop": "docker-compose -f docker-compose-dev.yaml down",
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
"dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
"dev:stop": "docker compose -f docker-compose-dev.yaml down",
"dev:logs": "docker compose -f docker-compose-dev.yaml logs -f --tail 10",
"studio": "npx prisma studio",
"start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js",
"build": "svelte-kit build",
@@ -16,20 +16,21 @@
"db:generate": "prisma generate",
"db:push": "prisma db push && prisma generate",
"db:seed": "prisma db seed",
"prerelease": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .",
"release:coolify": "cross-var yarn prerelease && docker push coollabsio/coolify:$npm_package_version && docker image push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
"release:staging": "cross-var docker build -t coollabsio/coolify:$npm_package_version . && docker push coollabsio/coolify:$npm_package_version",
"release:pre": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .",
"release:coolify": "cross-var yarn release:pre && docker push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
"release:haproxy": "docker build -f haproxy.Dockerfile -t coollabsio/coolify-haproxy-alpine:1.0.0 -t coollabsio/coolify-haproxy-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-alpine",
"release:haproxy:tcp": "docker build -f haproxy-tcp.Dockerfile -t coollabsio/coolify-haproxy-tcp-alpine:1.0.0 -t coollabsio/coolify-haproxy-tcp-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-tcp-alpine",
"release:haproxy:http": "docker build -f haproxy-http.Dockerfile -t coollabsio/coolify-haproxy-http-alpine:1.0.0 -t coollabsio/coolify-haproxy-http-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-http-alpine",
"prepare": "husky install"
},
"devDependencies": {
"@sveltejs/adapter-node": "1.0.0-next.67",
"@sveltejs/adapter-static": "1.0.0-next.27",
"@sveltejs/kit": "1.0.0-next.259",
"@sveltejs/adapter-node": "1.0.0-next.68",
"@sveltejs/adapter-static": "1.0.0-next.28",
"@sveltejs/kit": "1.0.0-next.278",
"@types/bcrypt": "5.0.0",
"@types/js-cookie": "3.0.1",
"@types/node": "17.0.16",
"@types/node": "17.0.18",
"@types/node-forge": "1.0.0",
"@typescript-eslint/eslint-plugin": "4.31.1",
"@typescript-eslint/parser": "4.31.1",
@@ -40,16 +41,16 @@
"eslint-config-prettier": "8.3.0",
"eslint-plugin-svelte3": "3.2.1",
"husky": "7.0.4",
"lint-staged": "12.3.3",
"lint-staged": "12.3.4",
"postcss": "8.4.6",
"prettier": "2.5.1",
"prettier-plugin-svelte": "2.6.0",
"prettier-plugin-tailwindcss": "0.1.7",
"prisma": "3.9.1",
"prisma": "3.9.2",
"svelte": "3.46.4",
"svelte-check": "2.4.3",
"svelte-preprocess": "4.10.3",
"tailwindcss": "3.0.19",
"tailwindcss": "3.0.23",
"ts-node": "10.5.0",
"tslib": "2.3.1",
"typescript": "4.5.5"
@@ -57,10 +58,10 @@
"type": "module",
"dependencies": {
"@iarna/toml": "2.2.5",
"@prisma/client": "3.9.1",
"@sentry/node": "6.17.6",
"@prisma/client": "3.9.2",
"@sentry/node": "6.17.9",
"bcrypt": "5.0.1",
"bullmq": "1.69.0",
"bullmq": "1.73.0",
"compare-versions": "4.1.3",
"cookie": "0.4.2",
"cuid": "2.1.8",
@@ -68,14 +69,15 @@
"dockerode": "3.3.1",
"dotenv-extended": "2.9.0",
"generate-password": "1.7.0",
"get-port": "6.0.0",
"get-port": "6.1.0",
"got": "12.0.1",
"js-cookie": "3.0.1",
"js-yaml": "4.1.0",
"jsonwebtoken": "8.5.1",
"node-forge": "1.2.1",
"svelte-kit-cookie-session": "2.0.3",
"unique-names-generator": "4.6.0"
"svelte-kit-cookie-session": "2.1.2",
"tailwindcss-scrollbar": "^0.1.0",
"unique-names-generator": "4.7.1"
},
"prisma": {
"seed": "node prisma/seed.cjs"

200
pnpm-lock.yaml generated
View File

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

View File

@@ -0,0 +1,11 @@
/*
Warnings:
- A unique constraint covering the columns `[name,applicationId]` on the table `Secret` will be added. If there are existing duplicate values, this will fail.
*/
-- DropIndex
DROP INDEX "Secret_name_key";
-- CreateIndex
CREATE UNIQUE INDEX "Secret_name_applicationId_key" ON "Secret"("name", "applicationId");

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())
fqdn String? @unique
isRegistrationEnabled Boolean @default(false)
dualCerts Boolean @default(false)
proxyPassword String
proxyUser String
createdAt DateTime @default(now())
@@ -97,6 +98,7 @@ model ApplicationSettings {
id String @id @default(cuid())
application Application @relation(fields: [applicationId], references: [id])
applicationId String @unique
dualCerts Boolean @default(false)
debug Boolean @default(false)
previews Boolean @default(false)
createdAt DateTime @default(now())
@@ -105,13 +107,15 @@ model ApplicationSettings {
model Secret {
id String @id @default(cuid())
name String @unique
name String
value String
isBuildSecret Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])
applicationId String
@@unique([name, applicationId])
}
model BuildLog {
@@ -232,6 +236,7 @@ model Service {
id String @id @default(cuid())
name String
fqdn String?
dualCerts Boolean @default(false)
type String?
version String?
teams Team[]

View File

@@ -1,74 +1,26 @@
/// <reference types="@sveltejs/kit" />
interface Cookies {
teamId?: string;
gitlabToken?: string;
'kit.session'?: string;
}
interface Locals {
gitlabToken?: string;
user: {
teamId: string;
permission: string;
isAdmin: boolean;
};
session: {
data: {
uid?: string;
teams?: string[];
expires?: string;
};
};
declare namespace App {
interface Locals {
session: import('svelte-kit-cookie-session').Session<SessionData>;
cookies: Record<string, string>;
}
interface Platform {}
interface Session extends SessionData {}
interface Stuff {}
}
type Applications = {
name: string;
domain: string;
};
interface Hash {
iv: string;
content: string;
interface SessionData {
version?: string;
userId?: string | null;
teamId?: string | null;
permission?: string;
isAdmin?: boolean;
expires?: string | null;
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 = {
localeMatcher?: 'lookup' | 'best fit';
weekday?: 'long' | 'short' | 'narrow';
@@ -84,3 +36,24 @@ type DateTimeFormatOptions = {
hour12?: boolean;
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

@@ -4,9 +4,6 @@
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="dns-prefetch" href="https://cdn.coollabs.io/" />
<link rel="preconnect" href="https://cdn.coollabs.io/" crossorigin="" />
<link rel="stylesheet" href="https://cdn.coollabs.io/fonts/poppins/poppins.css" />
<title>Coolify</title>
%svelte.head%
</head>

View File

@@ -2,7 +2,7 @@ import dotEnvExtended from 'dotenv-extended';
dotEnvExtended.load();
import type { GetSession } from '@sveltejs/kit';
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 cookie from 'cookie';
import { dev } from '$app/env';
@@ -10,27 +10,38 @@ import { dev } from '$app/env';
export const handle = handleSession(
{
secret: process.env['COOLIFY_SECRET_KEY'],
expires: 30
expires: 30,
cookie: { secure: false }
},
async function ({ event, resolve }) {
let response;
try {
const cookies: Cookies = cookie.parse(event.request.headers.get('cookie') || '');
if (cookies['kit.session']) {
const { permission, teamId } = await getUserDetails(event, false);
event.locals.user = {
teamId,
permission,
isAdmin: permission === 'admin' || permission === 'owner'
};
}
if (cookies.gitlabToken) {
event.locals.gitlabToken = cookies.gitlabToken;
if (event.locals.cookies) {
let gitlabToken = event.locals.cookies.gitlabToken || null;
let ghToken = event.locals.cookies.ghToken;
if (event.locals.cookies['kit.session']) {
const { permission, teamId, userId } = await getUserDetails(event, false);
const newSession = {
userId,
teamId,
permission,
isAdmin: permission === 'admin' || permission === 'owner',
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, {
ssr: !event.url.pathname.startsWith('/webhooks/success')
});
} catch (error) {
console.log(error);
response = await resolve(event, {
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 {
version,
gitlabToken: request.locals?.gitlabToken || null,
uid: request.locals.session.data?.uid || null,
teamId: request.locals.user?.teamId || null,
permission: request.locals.user?.permission,
isAdmin: request.locals.user?.isAdmin || false
...locals.session.data
};
};
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 }) {
const service = await db.prisma.service.findFirst({
where: { id },
include: { plausibleAnalytics: true }
});
delete service.destinationDockerId;
delete service.createdAt;
delete service.updatedAt;
export function makeLabelForServices(type) {
return [
'coolify.managed=true',
`coolify.version=${version}`,
`coolify.type=service-plausibleanalytics`,
`coolify.configuration=${base64Encode(
JSON.stringify({
version,
images,
volume,
...service
})
)}`
`coolify.type=service`,
`coolify.service.type=${type}`
];
}

View File

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

View File

@@ -8,16 +8,26 @@ import * as db from '$lib/database';
import { buildLogQueue } from './queues';
import { version as currentVersion } from '../../package.json';
import { dockerInstance } from './docker';
import dayjs from 'dayjs';
import Cookie from 'cookie';
import os from 'os';
try {
if (!dev) {
Sentry.init({
dsn: process.env['COOLIFY_SENTRY_DSN'],
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) {
@@ -57,7 +67,7 @@ export const isTeamIdTokenAvailable = (request) => {
};
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) {
return cookies.teamId;
} else if (event.locals.session.data.teamId) {
@@ -67,9 +77,8 @@ export const getTeam = (event) => {
};
export const getUserDetails = async (event, isAdminRequired = true) => {
// try {
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({
where: { teamId, userId },
select: { permission: true },
@@ -91,18 +100,6 @@ export const getUserDetails = async (event, isAdminRequired = true) => {
}
return payload;
// } catch (err) {
// console.log(err);
// return {
// teamId: null,
// userId: null,
// permission: 'read',
// status: 401,
// body: {
// message: 'You do not have permission to do this. \nAsk an admin to modify your permissions.'
// }
// };
// }
};
export function getEngine(engine) {

View File

@@ -20,9 +20,6 @@
function showActions(value) {
actionsShow = value;
// if (value === false) {
// showPassword = false;
// }
}
function copyToClipboard() {
if (isHttps && navigator.clipboard) {
@@ -32,7 +29,7 @@
}
</script>
<span
<div
class="relative"
on:mouseenter={() => showActions(true)}
on:mouseleave={() => showActions(false)}
@@ -81,7 +78,7 @@
{/if}
{#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">
{#if isPasswordField}
<div on:click={() => (showPassword = !showPassword)}>
@@ -145,4 +142,4 @@
</div>
</div>
{/if}
</span>
</div>

View File

@@ -1,6 +1,6 @@
<script>
export let text;
export let maxWidthClass = 'max-w-[24rem]';
export let customClass = 'max-w-[24rem]';
</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" />
</div>
{: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" />
</div>
{/if}

View File

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

View File

@@ -5,5 +5,5 @@
<img
alt="minio logo"
class={isAbsolute ? 'w-7 absolute top-0 left-0 -m-3 -mt-5' : 'w-4 mx-auto'}
src="https://cdn.coollabs.io/assets/coolify/services/minio/MINIO_Bird.png"
src="/minio.png"
/>

View File

@@ -5,5 +5,5 @@
<img
alt="nocodb logo"
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
src="https://cdn.coollabs.io/assets/coolify/services/nocodb/nocodb.png"
src="/nocodb.png"
/>

View File

@@ -5,5 +5,5 @@
<img
alt="plausible logo"
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-6 mx-auto'}
src="https://cdn.coollabs.io/assets/coolify/services/plausible/logo_sm.png"
src="/plausible.png"
/>

View File

@@ -0,0 +1,35 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
id="Icon"
x="0px"
y="0px"
viewBox="0 0 1024 1024"
style="enable-background:new 0 0 1024 1024;"
xml:space="preserve"
>
<style type="text/css">
.st0 {
fill: #175ddc;
}
.st1 {
fill: #ffffff;
}
</style>
<path
id="Background"
class="st0"
d="M1024,864c0,88.4-71.6,160-160,160H160C71.6,1024,0,952.4,0,864V160C0,71.6,71.6,0,160,0h704 c88.4,0,160,71.6,160,160V864z"
/>
<path
id="Identity"
class="st1"
d="M829.8,128.6c-6.5-6.5-14.2-9.7-23-9.7H217.2c-8.9,0-16.5,3.2-23,9.7c-6.5,6.5-9.7,14.2-9.7,23 v393.1c0,29.3,5.7,58.4,17.1,87.3c11.4,28.8,25.6,54.4,42.5,76.8c16.9,22.3,37,44.1,60.4,65.3c23.4,21.2,45,38.7,64.7,52.7 c19.8,14,40.4,27.2,61.9,39.7c21.5,12.5,36.8,20.9,45.8,25.3c9,4.4,16.3,7.9,21.7,10.2c4.1,2,8.5,3.1,13.3,3.1c4.8,0,9.2-1,13.3-3.1 c5.5-2.4,12.7-5.8,21.8-10.2c9-4.4,24.3-12.9,45.8-25.3c21.5-12.5,42.1-25.7,61.9-39.7c19.8-14,41.4-31.6,64.8-52.7 c23.4-21.2,43.5-42.9,60.4-65.3c16.9-22.4,31-47.9,42.5-76.8c11.4-28.8,17.1-57.9,17.1-87.3V151.7 C839.6,142.8,836.3,135.1,829.8,128.6z M753.8,548.4c0,142.3-241.8,264.9-241.8,264.9V203.1h241.8 C753.8,203.1,753.8,406.1,753.8,548.4z"
/>
</svg>

View File

@@ -1,6 +1,6 @@
import { decrypt, encrypt } from '$lib/crypto';
import { removeProxyConfiguration } from '$lib/haproxy';
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
import { asyncExecShell, getEngine } from '$lib/common';
import { getDomain, removeDestinationDocker } from '$lib/common';
import { prisma } from './common';
@@ -59,10 +59,14 @@ export async function removeApplication({ id, teamId }) {
const id = containerObj.ID;
const preview = containerObj.Image.split('-')[1];
await removeDestinationDocker({ id, engine: destinationDocker.engine });
if (preview) {
await removeProxyConfiguration({ domain: `${preview}.${domain}` });
} else {
await removeProxyConfiguration({ domain });
try {
if (preview) {
await removeProxyConfiguration({ domain: `${preview}.${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 } };
}
}
export async function getApplicationById({ id }) {
const body = await prisma.application.findFirst({
where: { id }
});
return { ...body };
}
export async function getApplication({ id, teamId }) {
let body = await prisma.application.findFirst({
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({
where: { id },
data: { settings: { update: { debug, previews } } },
data: { settings: { update: { debug, previews, dualCerts } } },
include: { destinationDocker: true }
});
}

View File

@@ -1,5 +1,5 @@
import { getDomain } from '$lib/common';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function isBranchAlreadyUsed({ repository, branch, id }) {
const application = await prisma.application.findUnique({
@@ -21,16 +21,35 @@ export async function isSecretExists({ id, name }) {
export async function isDomainConfigured({ id, fqdn }) {
const domain = getDomain(fqdn);
const nakedDomain = domain.replace('www.', '');
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 }
});
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 }
});
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 }
});
if (foundApp || foundService || coolifyFqdn) return true;

View File

@@ -2,6 +2,7 @@ import { dev } from '$app/env';
import { sentry } from '$lib/common';
import * as Prisma from '@prisma/client';
import { default as ProdPrisma } from '@prisma/client';
import type { PrismaClientOptions } from '@prisma/client/runtime';
import generator from 'generate-password';
import forge from 'node-forge';
@@ -19,40 +20,45 @@ if (!dev) {
PrismaClient = ProdPrisma.PrismaClient;
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) {
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 = {
status: e.status || 500,
status: truncatedError.status || 500,
body: {
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;
}
if (e instanceof P.PrismaClientKnownRequestError) {
if (e.code === 'P2002') {
if (truncatedError instanceof P.PrismaClientKnownRequestError) {
if (truncatedError?.code === 'P2002') {
payload.body.message = 'Already exists. Choose another name.';
}
}
@@ -101,21 +107,55 @@ export const supportedServiceTypesAndVersions = [
name: 'plausibleanalytics',
fancyName: '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',
fancyName: 'VSCode Server',
baseImage: 'codercom/code-server',
versions: ['latest']
versions: ['latest'],
ports: {
main: 8080
}
},
{
name: 'wordpress',
fancyName: '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',
fancyName: 'Vaultwarden',
baseImage: 'vaultwarden/server',
versions: ['latest'],
ports: {
main: 80
}
}
];

View File

@@ -2,7 +2,7 @@ import { decrypt, encrypt } from '$lib/crypto';
import { dockerInstance } from '$lib/docker';
import cuid from 'cuid';
import { generatePassword } from '.';
import { prisma, PrismaErrorHandler } from './common';
import { prisma, ErrorHandler } from './common';
import getPort from 'get-port';
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
@@ -114,15 +114,6 @@ export async function updateDatabase({
});
}
// export async function setDatabaseSettings({ id, isPublic }) {
// try {
// await prisma.databaseSettings.update({ where: { databaseId: id }, data: { isPublic } })
// return { status: 201 }
// } catch (e) {
// throw PrismaErrorHandler(e)
// }
// }
export async function stopDatabase(database) {
let everStarted = false;
const {

View File

@@ -1,8 +1,8 @@
import { asyncExecShell, getEngine } from '$lib/common';
import { dockerInstance } from '$lib/docker';
import { defaultProxyImageHttp, defaultProxyImageTcp, startCoolifyProxy } from '$lib/haproxy';
import { startCoolifyProxy } from '$lib/haproxy';
import { getDatabaseImage } from '.';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function listDestinations(teamId) {
return await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId } } } });
@@ -37,11 +37,9 @@ export async function configureDestinationForDatabase({ id, destinationId }) {
const host = getEngine(engine);
if (type && version) {
const baseImage = getDatabaseImage(type);
asyncExecShell(`DOCKER_HOST=${host} docker pull ${baseImage}:${version}`);
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageTcp}`);
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageHttp}`);
asyncExecShell(`DOCKER_HOST=${host} docker pull certbot/certbot:latest`);
asyncExecShell(`DOCKER_HOST=${host} docker pull alpine:latest`);
asyncExecShell(
`DOCKER_HOST=${host} docker pull ${baseImage}:${version} && echo "FROM ${baseImage}:${version}" | docker build --label coolify.image="true" -t "${baseImage}:${version}" -`
);
}
}
}
@@ -111,22 +109,4 @@ export async function setDestinationSettings({ engine, isCoolifyProxyUsed }) {
where: { engine },
data: { isCoolifyProxyUsed }
});
// if (isCoolifyProxyUsed) {
// await installCoolifyProxy(engine)
// await configureNetworkCoolifyProxy(engine)
// } else {
// // TODO: must check if other destination is using the proxy??? or not?
// const domain = await prisma.setting.findUnique({ where: { name: 'domain' }, rejectOnNotFound: false })
// if (!domain) {
// await uninstallCoolifyProxy(engine)
// } else {
// return {
// stastus: 500,
// body: {
// message: 'You can not disable the Coolify proxy while the domain is set for Coolify itself.'
// }
// }
// }
// }
}

View File

@@ -1,5 +1,5 @@
import { decrypt, encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function listSources(teamId) {
return await prisma.gitSource.findMany({

View File

@@ -1,5 +1,5 @@
import { decrypt, encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function addInstallation({ gitSourceId, installation_id }) {
const source = await prisma.gitSource.findUnique({

View File

@@ -1,5 +1,5 @@
import { encrypt } from '$lib/crypto';
import { generateSshKeyPair, prisma, PrismaErrorHandler } from './common';
import { generateSshKeyPair, prisma } from './common';
export async function updateDeployKey({ id, deployKeyId }) {
const application = await prisma.application.findUnique({

View File

@@ -1,4 +1,4 @@
import { prisma, PrismaErrorHandler } from './common';
import { prisma, ErrorHandler } from './common';
export async function listLogs({ buildId, last = 0 }) {
try {
@@ -7,7 +7,7 @@ export async function listLogs({ buildId, last = 0 }) {
orderBy: { time: 'asc' }
});
return [...body];
} catch (e) {
throw PrismaErrorHandler(e);
} catch (error) {
return ErrorHandler(error);
}
}

View File

@@ -1,5 +1,5 @@
import { encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function listSecrets({ applicationId }) {
return await prisma.secret.findMany({

View File

@@ -1,8 +1,7 @@
import { decrypt, encrypt } from '$lib/crypto';
import { dockerInstance } from '$lib/docker';
import cuid from 'cuid';
import { generatePassword } from '.';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function listServices(teamId) {
return await prisma.service.findMany({ where: { teams: { some: { id: teamId } } } });
@@ -99,15 +98,29 @@ export async function configureServiceType({ id, type }) {
wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } }
}
});
} else if (type === 'vaultwarden') {
await prisma.service.update({
where: { id },
data: {
type
}
});
}
}
export async function setService({ id, version }) {
export async function setServiceVersion({ id, version }) {
return await prisma.service.update({
where: { id },
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 }) {
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
await prisma.service.update({ where: { id }, data: { name, fqdn } });
@@ -115,6 +128,9 @@ export async function updatePlausibleAnalyticsService({ id, fqdn, email, usernam
export async function updateNocoDbOrMinioService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateVaultWardenService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateVsCodeServer({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}

View File

@@ -1,4 +1,4 @@
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function listTeams() {
return await prisma.team.findMany();

View File

@@ -1,24 +1,28 @@
import cuid from 'cuid';
import bcrypt from 'bcrypt';
import { prisma, PrismaErrorHandler } from './common';
import { asyncExecShell, removeContainer, uniqueName } from '$lib/common';
import { prisma } from './common';
import { asyncExecShell, uniqueName } from '$lib/common';
import * as db from '$lib/database';
import { startCoolifyProxy } from '$lib/haproxy';
export async function login({ email, password }) {
export async function hashPassword(password: string) {
const saltRounds = 15;
return bcrypt.hash(password, saltRounds);
}
export async function login({ email, password }) {
const users = await prisma.user.count();
const userFound = await prisma.user.findUnique({
where: { email },
include: { teams: true },
include: { teams: true, permission: true },
rejectOnNotFound: false
});
// Registration disabled if database is not seeded properly
const { isRegistrationEnabled, id } = await db.listSettings();
let uid = cuid();
let permission = 'read';
let isAdmin = false;
// Disable registration if we are registering the first user.
if (users === 0) {
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
@@ -28,12 +32,12 @@ export async function login({ email, password }) {
console.log('Network created');
})
.catch(() => {
console.log('Network already exists');
console.log('Network already exists.');
});
startCoolifyProxy('/var/run/docker.sock')
.then(() => {
console.log('Coolify Proxy Started');
console.log('Coolify Proxy started.');
})
.catch((err) => {
console.log(err);
@@ -50,6 +54,8 @@ export async function login({ email, password }) {
};
}
uid = userFound.id;
// permission = userFound.permission;
isAdmin = true;
}
} else {
// 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) {
permission = 'owner';
isAdmin = true;
await prisma.user.create({
data: {
id: uid,
@@ -97,25 +105,16 @@ export async function login({ email, password }) {
});
}
}
// const token = jsonwebtoken.sign({}, secretKey, {
// expiresIn: 15778800,
// algorithm: 'HS256',
// audience: 'coolify',
// issuer: 'coolify',
// jwtid: uid,
// subject: `User:${uid}`,
// notBefore: -1000
// });
return {
status: 200,
headers: {
'Set-Cookie': `teamId=${uid}; HttpOnly; Path=/; Max-Age=15778800;`
},
body: {
uid,
teamId: uid
userId: uid,
teamId: uid,
permission,
isAdmin
}
};
}

View File

@@ -1,8 +1,7 @@
import { dev } from '$app/env';
import { asyncExecShell, getEngine } from '$lib/common';
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import got from 'got';
import * as db from '$lib/database';
import { letsEncrypt } from '$lib/letsencrypt';
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
@@ -49,13 +48,14 @@ export async function completeTransaction(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 transactionId = await getNextTransactionId();
const backendFound = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`)
.json();
if (backendFound) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: {
@@ -65,104 +65,102 @@ export async function removeProxyConfiguration({ domain }) {
.json();
await completeTransaction(transactionId);
}
await forceSSLOffApplication(domain);
await removeWwwRedirection(fqdn);
}
export async function forceSSLOffApplication({ domain }) {
if (!dev) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
const transactionId = await getNextTransactionId();
try {
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
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();
export async function forceSSLOffApplication(domain) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let transactionId;
try {
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.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 {
console.log(`[DEBUG] Removing ssl for ${domain}`);
} catch (error) {
console.log(error);
} finally {
if (transactionId) await completeTransaction(transactionId);
}
}
export async function forceSSLOnApplication({ domain }) {
if (!dev) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
const transactionId = await getNextTransactionId();
try {
const rules: any = await haproxy
.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(`-i ${domain}`));
if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1;
}
await haproxy
.post(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
},
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);
export async function forceSSLOnApplication(domain) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let transactionId;
try {
const rules: any = await haproxy
.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;
}
} else {
console.log(`[DEBUG] Adding ssl for ${domain}`);
transactionId = await getNextTransactionId();
await haproxy
.post(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
},
json: {
index: nextRule,
cond: 'if',
cond_test: `{ hdr(host) -i ${domain} } !{ ssl_fc }`,
type: 'redirect',
redir_type: 'scheme',
redir_value: 'https',
redir_code: dev ? 302 : 301
}
})
.json();
} catch (error) {
console.log(error);
throw error;
} finally {
if (transactionId) await completeTransaction(transactionId);
}
}
export async function deleteProxy({ id }) {
const haproxy = await haproxyInstance();
try {
await checkHAProxy(haproxy);
} catch (error) {
return;
}
const transactionId = await getNextTransactionId();
await checkHAProxy(haproxy);
let transactionId;
try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json();
transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${id}`, {
searchParams: {
@@ -181,97 +179,10 @@ export async function deleteProxy({ id }) {
} catch (error) {
console.log(error.response.body);
} finally {
await completeTransaction(transactionId);
if (transactionId) await completeTransaction(transactionId);
}
}
// export async function configureProxyForDatabase({ id, port, isPublic, privatePort }) {
// const haproxy = await haproxyInstance()
// try {
// await checkHAProxy()
// } catch (error) {
// return
// }
// let alreadyConfigured = false
// try {
// const backend: any = await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json()
// const server: any = await haproxy.get(`v2/services/haproxy/configuration/servers/${id}`, {
// searchParams: {
// backend: id
// },
// }).json()
// if (backend.data.name === id) {
// if (server.data.port === privatePort) {
// if (server.data.check === 'enabled') {
// if (server.data.address === id) {
// alreadyConfigured = true
// }
// }
// }
// }
// } catch (error) {
// console.log('error getting backend or server', error.response.body)
// }
// if (alreadyConfigured) return
// const transactionId = await getNextTransactionId()
// try {
// await haproxy.post('v2/services/haproxy/configuration/backends', {
// searchParams: {
// transaction_id: transactionId
// },
// json: {
// "init-addr": "last,libc,none",
// "mode": "tcp",
// "name": id
// }
// })
// await haproxy.post('v2/services/haproxy/configuration/servers', {
// searchParams: {
// transaction_id: transactionId,
// backend: id
// },
// json: {
// "address": id,
// "check": "enabled",
// "name": id,
// "port": privatePort
// }
// })
// await haproxy.post('v2/services/haproxy/configuration/frontends', {
// searchParams: {
// transaction_id: transactionId,
// backend: id
// },
// json: {
// "default_backend": id,
// "mode": "tcp",
// "name": id
// }
// })
// await haproxy.post('v2/services/haproxy/configuration/binds', {
// searchParams: {
// transaction_id: transactionId,
// frontend: id
// },
// json: {
// "address": "*",
// "name": id,
// "port": port
// }
// })
// } catch (error) {
// console.log(error.response.body)
// throw error.response.body
// } finally {
// try {
// await completeTransaction(transactionId)
// } catch (error) {
// console.log(error.response.body)
// }
// }
// await configureDatabaseVisibility({ id, isPublic })
// }
export async function reloadHaproxy(engine) {
const host = getEngine(engine);
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
@@ -300,7 +211,7 @@ export async function configureProxyForApplication({ domain, imageId, applicatio
if (backendAvailable.data.forwardfor.enabled === 'enabled') {
if (backendAvailable.data.name === domain) {
if (server.data.check === 'enabled') {
if (server.data.address === applicationId) {
if (server.data.address === imageId) {
if (server.data.port === port) {
serverConfigured = true;
}
@@ -356,13 +267,15 @@ export async function configureProxyForApplication({ domain, imageId, applicatio
}
}
export async function configureCoolifyProxyOff({ domain }) {
export async function configureCoolifyProxyOff(fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
const transactionId = await getNextTransactionId();
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: {
@@ -371,22 +284,25 @@ export async function configureCoolifyProxyOff({ domain }) {
})
.json();
await completeTransaction(transactionId);
if (!dev) {
await forceSSLOffApplication({ domain });
}
if (isHttps) await forceSSLOffApplication(domain);
await removeWwwRedirection(fqdn);
} catch (error) {
throw error?.response?.body || error;
}
}
export async function checkHAProxy(haproxy) {
export async function checkHAProxy(haproxy?: any) {
if (!haproxy) haproxy = await haproxyInstance();
try {
await haproxy.get('v2/info');
} 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({ domain }) {
export async function configureCoolifyProxyOn(fqdn) {
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let serverConfigured = false;
@@ -481,8 +397,12 @@ export async function startTcpProxy(destinationDocker, id, publicPort, privatePo
try {
if (foundDB && !found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
const ip = JSON.parse(Config)[0].Gateway;
return await asyncExecShell(
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageTcp}`
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageTcp}`
);
}
} catch (error) {
@@ -499,8 +419,12 @@ export async function startHttpProxy(destinationDocker, id, publicPort, privateP
try {
if (foundDB && !found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
const ip = JSON.parse(Config)[0].Gateway;
return await asyncExecShell(
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}`
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}`
);
}
} catch (error) {
@@ -512,8 +436,12 @@ export async function startCoolifyProxy(engine) {
const found = await checkContainer(engine, 'coolify-haproxy');
const { proxyPassword, proxyUser } = await db.listSettings();
if (!found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
const ip = JSON.parse(Config)[0].Gateway;
await asyncExecShell(
`DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}`
`DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}`
);
}
await configureNetworkCoolifyProxy(engine);
@@ -574,53 +502,70 @@ export async function configureNetworkCoolifyProxy(engine) {
export async function configureSimpleServiceProxyOn({ id, domain, port }) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let serverConfigured = false;
let backendAvailable: any = null;
try {
await checkHAProxy(haproxy);
} catch (error) {
return;
}
try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
return;
backendAvailable = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`)
.json();
const server: any = await haproxy
.get(`v2/services/haproxy/configuration/servers/${id}`, {
searchParams: {
backend: domain
}
})
.json();
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) {}
try {
const transactionId = await getNextTransactionId();
await haproxy.post('v2/services/haproxy/configuration/backends', {
searchParams: {
transaction_id: transactionId
},
json: {
'init-addr': 'last,libc,none',
forwardfor: { enabled: 'enabled' },
name: domain
}
});
await haproxy.post('v2/services/haproxy/configuration/servers', {
searchParams: {
transaction_id: transactionId,
backend: domain
},
json: {
address: id,
check: 'enabled',
name: id,
port: port
}
});
await completeTransaction(transactionId);
} catch (error) {
console.log(error);
}
if (serverConfigured) return;
const transactionId = await getNextTransactionId();
await haproxy.post('v2/services/haproxy/configuration/backends', {
searchParams: {
transaction_id: transactionId
},
json: {
'init-addr': 'last,libc,none',
forwardfor: { enabled: 'enabled' },
name: domain
}
});
await haproxy.post('v2/services/haproxy/configuration/servers', {
searchParams: {
transaction_id: transactionId,
backend: domain
},
json: {
address: id,
check: 'enabled',
name: id,
port: port
}
});
await completeTransaction(transactionId);
}
export async function configureSimpleServiceProxyOff({ domain }) {
export async function configureSimpleServiceProxyOff(fqdn) {
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: {
@@ -630,5 +575,92 @@ export async function configureSimpleServiceProxyOff({ domain }) {
.json();
await completeTransaction(transactionId);
} catch (error) {}
await forceSSLOffApplication(domain);
await removeWwwRedirection(fqdn);
return;
}
export async function removeWwwRedirection(fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
const haproxy = await haproxyInstance();
await checkHAProxy();
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
if (rules.data.length > 0) {
const rule = rules.data.find((rule) => rule.redir_value.includes(redirectValue));
if (rule) {
const 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();
await completeTransaction(transactionId);
}
}
}
export async function setWwwRedirection(fqdn) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let transactionId;
try {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
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 rules: any = await haproxy
.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.redir_value.includes(redirectValue));
if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1;
}
transactionId = await getNextTransactionId();
await haproxy
.post(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
},
json: {
index: nextRule,
cond: 'if',
cond_test: contTest,
type: 'redirect',
redir_type: 'location',
redir_value: redirectValue,
redir_code: dev ? 302 : 301
}
})
.json();
} catch (error) {
console.log(error);
throw error;
} finally {
if (transactionId) await completeTransaction(transactionId);
}
}

View File

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

View File

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

View File

@@ -1,48 +1,78 @@
import { dev } from '$app/env';
import { forceSSLOffApplication, forceSSLOnApplication, getNextTransactionId } from '$lib/haproxy';
import { forceSSLOffApplication, forceSSLOnApplication } from '$lib/haproxy';
import { asyncExecShell, getEngine } from './common';
import * as db from '$lib/database';
import cuid from 'cuid';
import getPort from 'get-port';
export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
try {
if (dev) {
return await forceSSLOnApplication({ domain });
} else {
if (isCoolify) {
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 nakedDomain = domain.replace('www.', '');
const wwwDomain = `www.${nakedDomain}`;
const randomCuid = cuid();
const randomPort = 9080;
const { stderr } = await asyncExecShell(
`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`
);
if (stderr) throw new Error(stderr);
return;
let host;
let dualCerts = false;
if (isCoolify) {
const data = await db.prisma.setting.findFirst();
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 },
include: { destinationDocker: true }
});
if (!data) {
data = await db.prisma.service.findUnique({
where: { id },
include: { destinationDocker: true }
});
}
// 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 });
if (serviceData) {
if (serviceData?.destinationDockerId && serviceData?.destinationDocker) {
host = getEngine(serviceData.destinationDocker.engine);
}
if (serviceData?.dualCerts) {
dualCerts = serviceData.dualCerts;
}
}
}
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) {
throw error;
if (error.code !== 0) {
throw error;
}
} finally {
if (!isCoolify) {
await forceSSLOnApplication(domain);
}
}
}

View File

@@ -4,9 +4,10 @@ import * as buildpacks from '../buildPacks';
import * as importers from '../importers';
import { dockerInstance } from '../docker';
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common';
import { configureProxyForApplication, reloadHaproxy } from '../haproxy';
import { configureProxyForApplication, reloadHaproxy, setWwwRedirection } from '../haproxy';
import * as db from '$lib/database';
import { decrypt } from '$lib/crypto';
import { sentry } from '$lib/common';
import {
copyBaseConfigurationFiles,
makeLabelForStandaloneApplication,
@@ -63,10 +64,6 @@ export default async function (job) {
if (destinationDockerId) {
destinationType = 'docker';
}
// Not implemented yet
// if (destinationKubernetesId) {
// destinationType = 'kubernetes'
// }
if (destinationType === 'docker') {
const docker = dockerInstance({ destinationDocker });
@@ -208,9 +205,7 @@ export default async function (job) {
const envs = [];
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (!secret.isBuildSecret) {
envs.push(`${secret.name}=${secret.value}`);
}
envs.push(`${secret.name}=${secret.value}`);
});
}
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
@@ -244,21 +239,29 @@ export default async function (job) {
if (stderr) console.log(stderr);
saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
} catch (error) {
saveBuildLog({ line: error, buildId, applicationId });
sentry.captureException(error);
throw new Error(error);
}
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
await configureProxyForApplication({ domain, imageId, applicationId, port });
if (isHttps) await letsEncrypt({ domain, id: applicationId });
await reloadHaproxy(destinationDocker.engine);
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
} else {
saveBuildLog({
line: 'Coolify Proxy is not configured for this destination. Nothing else to do.',
buildId,
applicationId
});
try {
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
await configureProxyForApplication({ domain, imageId, applicationId, port });
if (isHttps) await letsEncrypt({ domain, id: applicationId });
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
} else {
saveBuildLog({
line: 'Coolify Proxy is not configured for this destination. Nothing else to do.',
buildId,
applicationId
});
}
} catch (error) {
saveBuildLog({ line: error.stdout || error, buildId, applicationId });
sentry.captureException(error);
throw new Error(error);
}
}
}

View File

@@ -1,22 +1,49 @@
import { dev } from '$app/env';
import { asyncExecShell, getEngine } from '$lib/common';
import { prisma } from '$lib/database';
import { defaultProxyImageHttp, defaultProxyImageTcp } from '$lib/haproxy';
export default async function () {
if (!dev) {
const destinationDockers = await prisma.destinationDocker.findMany();
for (const destinationDocker of destinationDockers) {
const host = getEngine(destinationDocker.engine);
// Tagging images with labels
try {
const images = [
`coollabsio/${defaultProxyImageTcp}`,
`coollabsio/${defaultProxyImageHttp}`,
'certbot/certbot:latest',
'node:16.14.0-alpine',
'alpine:latest',
'nginx:stable-alpine',
'node:lts',
'php:apache',
'rust:latest'
];
for (const image of images) {
await asyncExecShell(
`DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.image="true" -t "${image}" -`
);
}
} catch (error) {}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
} catch (error) {
//
console.log(error);
}
// Cleanup images that are not managed by coolify
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.image=true' -a -f`
);
} catch (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.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 } });
const events = {
@@ -107,7 +107,7 @@ cron().catch((error) => {
console.log(error);
});
const buildQueueName = dev ? cuid() : 'build_queue';
const buildQueueName = 'build_queue';
const buildQueue = new Queue(buildQueueName, connectionOptions);
const buildWorker = new Worker(buildQueueName, async (job) => await builder(job), {
concurrency: 2,
@@ -120,29 +120,26 @@ buildWorker.on('completed', async (job: Bullmq.Job) => {
} catch (err) {
console.log(err);
} finally {
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
const workdir = `/tmp/build-sources/${job.data.repository}/`;
await asyncExecShell(`rm -fr ${workdir}`);
await asyncExecShell(
`test -f /tmp/build-sources/${job.data.repository}/id.rsa && rm /tmp/build-sources/${job.data.repository}/id.rsa`
);
}
return;
});
buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
console.log(failedReason);
try {
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } });
} catch (error) {
console.log(error);
} finally {
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
const workdir = `/tmp/build-sources/${job.data.repository}`;
await asyncExecShell(`rm -fr ${workdir}`);
await asyncExecShell(
`test -f /tmp/build-sources/${job.data.repository}/id.rsa && rm /tmp/build-sources/${job.data.repository}/id.rsa`
);
}
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({
line: `Reason: ${failedReason.toString()}`,
buildId: job.data.build_id,
@@ -150,29 +147,7 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
});
});
// const letsEncryptQueueName = dev ? cuid() : 'letsencrypt_queue'
// const letsEncryptQueue = new Queue(letsEncryptQueueName, connectionOptions)
// const letsEncryptWorker = new Worker(letsEncryptQueueName, async (job) => await letsencrypt(job), {
// concurrency: 1,
// ...connectionOptions
// })
// letsEncryptWorker.on('completed', async () => {
// // TODO: Save letsencrypt logs as build logs!
// console.log('[DEBUG] Lets Encrypt job completed')
// })
// letsEncryptWorker.on('failed', async (job: Job, failedReason: string) => {
// try {
// await prisma.applicationSettings.updateMany({ where: { applicationId: job.data.id }, data: { forceSSL: false } })
// } catch (error) {
// console.log(error)
// }
// console.log('[DEBUG] Lets Encrypt job failed')
// console.log(failedReason)
// })
const buildLogQueueName = dev ? cuid() : 'log_queue';
const buildLogQueueName = 'log_queue';
const buildLogQueue = new Queue(buildLogQueueName, connectionOptions);
const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), {
concurrency: 1,

View File

@@ -1,13 +1,16 @@
import { getDomain } from '$lib/common';
import { prisma } from '$lib/database';
import { getApplicationById, prisma, supportedServiceTypesAndVersions } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import {
checkContainer,
configureCoolifyProxyOn,
configureProxyForApplication,
configureSimpleServiceProxyOn,
forceSSLOnApplication,
reloadHaproxy,
startCoolifyProxy
setWwwRedirection,
startCoolifyProxy,
startHttpProxy
} from '$lib/haproxy';
import * as db from '$lib/database';
@@ -23,42 +26,82 @@ export default async function () {
(container) => container.Labels['coolify.managed']
);
for (const configuration of configurations) {
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 (fqdn) {
const domain = getDomain(fqdn);
await configureProxyForApplication({
domain,
imageId: pullmergeRequestId
? `${applicationId}-${pullmergeRequestId}`
: applicationId,
applicationId,
port
});
const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication({ domain });
if (configuration.Labels['coolify.configuration']) {
const parsedConfiguration = JSON.parse(
Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString()
);
if (
parsedConfiguration &&
configuration.Labels['coolify.type'] === 'standalone-application'
) {
const { fqdn, applicationId, port, pullmergeRequestId } = parsedConfiguration;
if (fqdn) {
const found = await getApplicationById({ id: applicationId });
if (found) {
const domain = getDomain(fqdn);
await configureProxyForApplication({
domain,
imageId: pullmergeRequestId
? `${applicationId}-${pullmergeRequestId}`
: 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
const { fqdn } = await db.listSettings();
if (fqdn) {
const domain = getDomain(fqdn);
const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy');
if (!found) await startCoolifyProxy('/var/run/docker.sock');
await configureCoolifyProxyOn({ domain });
await startCoolifyProxy('/var/run/docker.sock');
await configureCoolifyProxyOn(fqdn);
await setWwwRedirection(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication({ domain });
if (isHttps) await forceSSLOnApplication(domain);
}
} catch (error) {
console.log(error);
throw error;
} finally {
// await reloadHaproxy('/var/run/docker.sock');
}
}

View File

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

View File

@@ -2,14 +2,14 @@
import type { Load } from '@sveltejs/kit';
import { publicPaths } from '$lib/settings';
export const load: Load = async ({ fetch, url, params, session }) => {
if (!session.uid && !publicPaths.includes(url.pathname)) {
export const load: Load = async ({ fetch, url, session }) => {
if (!session.userId && !publicPaths.includes(url.pathname)) {
return {
status: 302,
redirect: '/login'
};
}
if (!session.uid) {
if (!session.userId) {
return {};
}
const endpoint = `/teams.json`;
@@ -39,8 +39,7 @@
import { errorNotification } from '$lib/form';
import { asyncSleep } from '$lib/components/common';
import { del, get, post } from '$lib/api';
import { dev } from '$app/env';
import Loading from '$lib/components/Loading.svelte';
import { browser } from '$app/env';
let isUpdateAvailable = false;
let updateStatus = {
@@ -48,9 +47,10 @@
checking: false,
success: null
};
let latestVersion = 'latest';
onMount(async () => {
if ($session.uid) {
if ($session.userId) {
const overrideVersion = browser && window.localStorage.getItem('latestVersion');
try {
await get(`/login.json`);
} catch ({ error }) {
@@ -62,10 +62,11 @@
updateStatus.checking = true;
try {
const data = await get(`/update.json`);
if (data?.isUpdateAvailable) {
await post(`/update.json`, { type: 'pull' });
if (overrideVersion || data?.isUpdateAvailable) {
latestVersion = overrideVersion || data.latestVersion;
isUpdateAvailable = overrideVersion ? true : data?.isUpdateAvailable;
await post(`/update.json`, { type: 'pull', latestVersion });
}
isUpdateAvailable = data?.isUpdateAvailable;
} catch (error) {
} finally {
updateStatus.checking = false;
@@ -83,7 +84,7 @@
}
async function switchTeam() {
try {
await post(`/index.json?from=${$page.url.pathname}`, {
await post(`/dashboard.json?from=${$page.url.pathname}`, {
cookie: 'teamId',
value: selectedTeamId
});
@@ -95,53 +96,31 @@
async function update() {
updateStatus.loading = true;
if (!dev) {
try {
await post(`/update.json`, { type: 'update' });
toast.push('Update completed. Waiting for the new version to start...');
let reachable = false;
let tries = 0;
do {
await asyncSleep(4000);
try {
await get(`/undead.json`);
reachable = true;
} catch (error) {
reachable = false;
}
if (reachable) break;
tries++;
} while (!reachable || tries < 120);
toast.push('New version reachable. Reloading...');
updateStatus.loading = false;
updateStatus.success = true;
await asyncSleep(3000);
return window.location.reload();
} catch ({ error }) {
return errorNotification(error);
} finally {
updateStatus.success = false;
updateStatus.loading = false;
}
} else {
try {
await post(`/update.json`, { type: 'update', latestVersion });
toast.push('Update completed.<br>Waiting for the new version to start...');
let reachable = false;
let tries = 0;
do {
await asyncSleep(1000);
await asyncSleep(4000);
try {
await get(`/undead.json`);
reachable = true;
} catch (error) {
console.log(error);
reachable = false;
}
console.log(reachable);
if (reachable) break;
tries++;
} while (!reachable || tries < 120);
toast.push('New version reachable. Reloading...');
await asyncSleep(2000);
window.location.reload();
updateStatus.loading = false;
updateStatus.success = true;
await asyncSleep(3000);
return window.location.reload();
} catch ({ error }) {
updateStatus.success = false;
updateStatus.loading = false;
return errorNotification(error);
}
}
</script>
@@ -150,7 +129,7 @@
<title>Coolify</title>
</svelte:head>
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
{#if $session.uid}
{#if $session.userId}
<nav class="nav-main">
<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>

View File

@@ -17,7 +17,7 @@
const endpoint = `/applications/${params.id}.json`;
const res = await fetch(endpoint);
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) {
return {
status: 302,
@@ -42,8 +42,6 @@
},
stuff: {
isRunning,
ghToken,
githubToken,
application,
appId
}

View File

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

View File

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

View File

@@ -8,7 +8,6 @@
import cuid from 'cuid';
import { goto } from '$app/navigation';
import { del, get, post, put } from '$lib/api';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@@ -132,24 +131,20 @@
}
async function isBranchAlreadyUsed() {
const url = `/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`;
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;
} catch (error) {
showSave = false;
return errorNotification('Branch already configured');
} catch ({ error }) {
return errorNotification(error);
}
}
// async function saveDeployKey(deployKeyId: number) {
// try {
// await post(updateDeployKeyIdUrl, { deployKeyId });
// } catch (error) {
// errorNotification(error);
// throw new Error(error);
// }
// }
async function checkSSHKey(sshkeyUrl) {
try {
return await post(sshkeyUrl, {});
@@ -203,7 +198,6 @@
const deployKeyFound = deployKeys.filter((dk) => dk.title === `${appId}-coolify-deploy-key`);
if (deployKeyFound.length > 0) {
for (const deployKey of deployKeyFound) {
console.log(`${deployKeyUrl}/${deployKey.id}`);
await del(
`${deployKeyUrl}/${deployKey.id}`,
{},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
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')) {
return {
status: 302,
@@ -10,7 +10,6 @@
}
return {
props: {
githubToken,
application,
appId
}
@@ -20,7 +19,6 @@
<script lang="ts">
export let application;
export let githubToken;
export let appId;
import GithubRepositories from './_GithubRepositories.svelte';
import GitlabRepositories from './_GitlabRepositories.svelte';
@@ -31,7 +29,7 @@
</div>
<div class="flex flex-wrap justify-center">
{#if application.gitSource.type === 'github'}
<GithubRepositories {application} {githubToken} />
<GithubRepositories {application} />
{:else if application.gitSource.type === 'gitlab'}
<GitlabRepositories {application} {appId} />
{/if}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -42,7 +42,7 @@
import Explainer from '$lib/components/Explainer.svelte';
import Setting from '$lib/components/Setting.svelte';
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 { post } from '$lib/api';
const { id } = $page.params;
@@ -52,6 +52,7 @@
let loading = false;
let debug = application.settings.debug;
let previews = application.settings.previews;
let dualCerts = application.settings.dualCerts;
onMount(() => {
domainEl.focus();
@@ -64,8 +65,11 @@
if (name === 'previews') {
previews = !previews;
}
if (name === 'dualCerts') {
dualCerts = !dualCerts;
}
try {
await post(`/applications/${id}/settings.json`, { previews, debug });
await post(`/applications/${id}/settings.json`, { previews, debug, dualCerts });
return toast.push('Settings saved.');
} catch ({ error }) {
return errorNotification(error);
@@ -252,7 +256,7 @@
</div>
<div class="grid grid-flow-row gap-2 px-10">
<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">
<input
readonly={!$session.isAdmin || isRunning}
@@ -266,11 +270,21 @@
required
/>
<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>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 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)}
<div class="grid grid-cols-3 items-center">
<label for="port">Port</label>
@@ -285,6 +299,7 @@
</div>
</div>
{/if}
{#if !notNodeDeployments.includes(application.buildPack)}
<div class="grid grid-cols-3 items-center">
<label for="installCommand">Install Command</label>
@@ -361,8 +376,7 @@
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">Features</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
bind:setting={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."
/>
</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
isCenter={false}
bind:setting={previews}
on:click={() => changeSettings('previews')}
title="Enable MR/PR Previews"
description="Creates previews from pull and merge requests."
/>
</ul>
<ul class="mt-2 divide-y divide-stone-800">
</div>
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={debug}
on:click={() => changeSettings('debug')}
title="Debug 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>

View File

@@ -15,16 +15,32 @@
let loading = true;
let currentStatus;
let streamInterval;
let followingBuild;
let followingInterval;
let logsEl;
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) {
try {
let { logs: responseLogs, status } = await get(
`/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${sequence}`
);
currentStatus = status;
logs = logs.concat(responseLogs);
logs = logs.concat(responseLogs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
loading = false;
streamInterval = setInterval(async () => {
if (status !== 'running') {
@@ -38,18 +54,21 @@
);
status = data.status;
currentStatus = status;
logs = logs.concat(data.logs);
logs = logs.concat(data.logs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
dispatch('updateBuildStatus', { status });
} catch ({ error }) {
return errorNotification(error);
}
}, 1000);
} catch ({ error }) {
console.log(error);
return errorNotification(error);
}
}
onDestroy(() => {
clearInterval(streamInterval);
clearInterval(followingInterval);
});
onMount(async () => {
window.scrollTo(0, 0);
@@ -60,12 +79,38 @@
{#if loading}
<Loading />
{:else}
<div class="relative">
<div class="relative ">
{#if currentStatus === 'running'}
<LoadingLogs />
{/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
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}
<div>{log.line + '\n'}</div>

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,19 +27,23 @@
import { getDomain } from '$lib/components/common';
import { get } from '$lib/api';
import { errorNotification } from '$lib/form';
let loadLogsInterval = null;
let logs = [];
let followingBuild;
let followingInterval;
let logsEl;
const { id } = $page.params;
onMount(async () => {
loadLogs();
loadLogsInterval = setInterval(() => {
loadLogs();
}, 3000);
}, 1000);
});
onDestroy(() => {
clearInterval(loadLogsInterval);
clearInterval(followingInterval);
});
async function loadLogs() {
try {
@@ -50,6 +54,18 @@
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>
<div class="flex space-x-1 p-6 font-bold">
@@ -61,14 +77,42 @@
{#if logs.length === 0}
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
{:else}
<div class="relative w-full">
<div class="relative">
<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
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}
{log + '\n'}
{/each}
<div class="px-2">
{#each logs as log}
{log + '\n'}
{/each}
</div>
</div>
</div>
{/if}

View File

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

View File

@@ -6,22 +6,43 @@
import { page } from '$app/stores';
import { del, post } from '$lib/api';
import { errorNotification } from '$lib/form';
import { createEventDispatcher } from 'svelte';
if (name) value = 'ENCRYPTED';
const dispatch = createEventDispatcher();
let nameEl;
let valueEl;
const { id } = $page.params;
async function removeSecret() {
try {
await del(`/applications/${id}/secrets.json`, { name });
return window.location.reload();
dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
isBuildSecret = false;
}
} catch ({ error }) {
return errorNotification(error);
}
}
async function saveSecret() {
const nameValid = nameEl.checkValidity();
const valueValid = valueEl.checkValidity();
if (!nameValid) {
return nameEl.reportValidity();
}
if (!valueValid) {
return valueEl.reportValidity();
}
try {
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret });
return window.location.reload();
dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
isBuildSecret = false;
}
} catch ({ error }) {
return errorNotification(error);
}
@@ -33,101 +54,89 @@
}
</script>
<div class="mx-auto max-w-3xl pt-4">
<div class="flex space-x-2">
<div class="grid grid-flow-row">
<label for="secretName">Name</label>
<input
id="secretName"
bind:value={name}
placeholder="EXAMPLE_VARIABLE"
class="w-64 border-2 border-transparent"
readonly={!isNewSecret}
class:hover:bg-coolgray-200={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
/>
</div>
<div class="grid grid-flow-row">
<label for="secretValue">Value (will be encrypted)</label>
<input
id="secretValue"
bind:value
placeholder="J$#@UIO%HO#$U%H"
class="w-64 border-2 border-transparent"
class:hover:bg-coolgray-200={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
readonly={!isNewSecret}
/>
</div>
<div class="w-32 px-2 text-center">
<div class="text-xs">Is build variable?</div>
<div class="mt-2">
<ul class="divide-y divide-stone-800">
<li>
<div
type="button"
on:click={setSecretValue}
aria-pressed="false"
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
class:bg-green-600={isBuildSecret}
class:bg-stone-700={!isBuildSecret}
class:cursor-not-allowed={!isNewSecret}
class:cursor-pointer={isNewSecret}
>
<span class="sr-only">Use isBuildSecret</span>
<span
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out"
class:translate-x-5={isBuildSecret}
class:translate-x-0={!isBuildSecret}
>
<span
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
class:opacity-0={isBuildSecret}
class:opacity-100={!isBuildSecret}
aria-hidden="true"
>
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
<path
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
<span
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out"
aria-hidden="true"
class:opacity-100={isBuildSecret}
class:opacity-0={!isBuildSecret}
>
<svg
class="h-3 w-3 bg-white text-green-600"
fill="currentColor"
viewBox="0 0 12 12"
>
<path
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
/>
</svg>
</span>
</span>
</div>
</li>
</ul>
</div>
</div>
{#if isNewSecret}
<div class="mt-6">
<button class="w-20 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
</div>
{:else}
<div class="mt-6">
<button class="w-20 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
{/if}
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<input
id="secretName"
bind:this={nameEl}
bind:value={name}
required
placeholder="EXAMPLE_VARIABLE"
class="-mx-2 w-64 border-2 border-transparent"
readonly={!isNewSecret}
class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
/>
</td>
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<input
id="secretValue"
bind:value
bind:this={valueEl}
required
placeholder="J$#@UIO%HO#$U%H"
class="-mx-2 w-64 border-2 border-transparent"
class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
readonly={!isNewSecret}
/>
</td>
<td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
<div
type="button"
on:click={setSecretValue}
aria-pressed="false"
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
class:bg-green-600={isBuildSecret}
class:bg-stone-700={!isBuildSecret}
class:opacity-50={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
class:cursor-pointer={isNewSecret}
>
<span class="sr-only">Use isBuildSecret</span>
<span
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out"
class:translate-x-5={isBuildSecret}
class:translate-x-0={!isBuildSecret}
>
<span
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
class:opacity-0={isBuildSecret}
class:opacity-100={!isBuildSecret}
aria-hidden="true"
>
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
<path
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
<span
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out"
aria-hidden="true"
class:opacity-100={isBuildSecret}
class:opacity-0={!isBuildSecret}
>
<svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
<path
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
/>
</svg>
</span>
</span>
</div>
</div>
</td>
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
{#if isNewSecret}
<div class="flex items-center justify-center">
<button class="w-24 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
</div>
{:else}
<div class="flex justify-center items-end">
<button class="w-24 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
{/if}
</td>

View File

@@ -1,6 +1,6 @@
import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database';
import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
@@ -18,7 +18,7 @@ export const get: RequestHandler = async (event) => {
}
};
} catch (error) {
return PrismaErrorHandler(error);
return ErrorHandler(error);
}
};
@@ -33,7 +33,7 @@ export const post: RequestHandler = async (event) => {
const found = await db.isSecretExists({ id, name });
if (found) {
throw {
error: `Secret ${name} already exists`
error: `Secret ${name} already exists.`
};
} else {
await db.createSecret({ id, name, value, isBuildSecret });
@@ -42,7 +42,7 @@ export const post: RequestHandler = async (event) => {
};
}
} catch (error) {
return PrismaErrorHandler(error);
return ErrorHandler(error);
}
};
export const del: RequestHandler = async (event) => {
@@ -58,6 +58,6 @@ export const del: RequestHandler = async (event) => {
status: 200
};
} catch (error) {
return PrismaErrorHandler(error);
return ErrorHandler(error);
}
};

View File

@@ -24,6 +24,15 @@
export let application;
import Secret from './_Secret.svelte';
import { getDomain } from '$lib/components/common';
import { page } from '$app/stores';
import { get } from '$lib/api';
const { id } = $page.params;
async function refreshSecrets() {
const data = await get(`/applications/${id}/secrets.json`);
secrets = [...data.secrets];
}
</script>
<div class="flex space-x-1 p-6 font-bold">
@@ -31,11 +40,46 @@
Secrets for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
</div>
</div>
<div class="mx-auto max-w-4xl px-6">
<div class="flex-col justify-start space-y-1">
{#each secrets as secret}
<Secret name={secret.name} value={secret.value} isBuildSecret={secret.isBuildSecret} />
{/each}
<Secret isNewSecret />
</div>
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<table class="mx-auto">
<thead class=" rounded-xl border-b border-coolgray-500">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white">Name</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Value</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Need during buildtime?</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
/>
</tr>
</thead>
<tbody class="">
{#each secrets as secret}
{#key secret.id}
<tr class="hover:bg-coolgray-200">
<Secret
name={secret.name}
value={secret.value ? secret.value : 'ENCRYPTED'}
isBuildSecret={secret.isBuildSecret}
on:refresh={refreshSecrets}
/>
</tr>
{/key}
{/each}
<tr>
<Secret isNewSecret on:refresh={refreshSecrets} />
</tr>
</tbody>
</table>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
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';
export const get: RequestHandler = async (event) => {
@@ -25,9 +25,7 @@ export const get: RequestHandler = async (event) => {
state = 'running';
}
} catch (error) {
// if (!error.stderr.includes('No such object')) {
// console.log(error)
// }
//
}
}
const configuration = generateDatabaseConfiguration(database);
@@ -42,7 +40,7 @@ export const get: RequestHandler = async (event) => {
}
};
} catch (error) {
return PrismaErrorHandler(error);
return ErrorHandler(error);
}
};
@@ -66,6 +64,6 @@ export const post: RequestHandler = async (event) => {
});
return { status: 201 };
} catch (error) {
return PrismaErrorHandler(error);
return ErrorHandler(error);
}
};

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common';
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 type { RequestHandler } from '@sveltejs/kit';
@@ -29,6 +29,6 @@ export const post: RequestHandler = async (event) => {
status: 201
};
} catch (error) {
return PrismaErrorHandler(error);
return ErrorHandler(error);
}
};

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database';
import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
@@ -15,6 +15,6 @@ export const get: RequestHandler = async (event) => {
}
};
} 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 scannedApps = [];
let loading = false;
let restarting = false;
async function handleSubmit() {
loading = true;
try {
@@ -42,6 +43,17 @@
} catch ({ 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() {
@@ -89,6 +101,25 @@
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>
<div class="flex justify-center px-6 pb-8">
@@ -103,6 +134,12 @@
disabled={loading}
>{loading ? 'Saving...' : 'Save'}
</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}
>Scan for applications</button
> -->
@@ -144,21 +181,18 @@
/>
</div>
</div>
<div class="flex justify-start">
<ul class="mt-2 divide-y divide-stone-800">
<Setting
disabled={cannotDisable}
bind:setting={destination.isCoolifyProxyUsed}
on:click={changeProxySetting}
isPadding={false}
title="Use Coolify Proxy?"
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>${
cannotDisable
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
: ''
}`}
/>
</ul>
<div class="grid grid-cols-2 items-center">
<Setting
disabled={cannotDisable}
bind:setting={destination.isCoolifyProxyUsed}
on:click={changeProxySetting}
title="Use Coolify Proxy?"
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>${
cannotDisable
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
: ''
}`}
/>
</div>
</form>
</div>

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getEngine, getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
@@ -12,7 +12,8 @@ export const get: RequestHandler = async (event) => {
try {
const destination = await db.getDestination({ id, teamId });
const settings = await db.listSettings();
const state = await checkContainer(destination.engine, 'coolify-haproxy');
const state =
destination?.engine && (await checkContainer(destination.engine, 'coolify-haproxy'));
return {
status: 200,
body: {
@@ -22,7 +23,7 @@ export const get: RequestHandler = async (event) => {
}
};
} catch (error) {
return PrismaErrorHandler(error);
return ErrorHandler(error);
}
};
export const post: RequestHandler = async (event) => {
@@ -36,7 +37,7 @@ export const post: RequestHandler = async (event) => {
await db.updateDestination({ id, name, engine, network });
return { status: 200 };
} catch (error) {
return PrismaErrorHandler(error);
return ErrorHandler(error);
}
};
@@ -50,6 +51,6 @@ export const del: RequestHandler = async (event) => {
await db.removeDestination({ id });
return { status: 200 };
} 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 * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit';
@@ -59,6 +59,6 @@ export const post: RequestHandler = async (event) => {
status: 404
};
} catch (error) {
return PrismaErrorHandler(error);
return ErrorHandler(error);
}
};

View File

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

View File

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

View File

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

View File

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

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