mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dfcf9b1e6 | ||
|
|
08f57ac5bc | ||
|
|
7095e781e9 | ||
|
|
df18b93809 | ||
|
|
0c2e028b38 | ||
|
|
80cb1bc129 | ||
|
|
74c1cb51f6 | ||
|
|
2e864bddf9 | ||
|
|
e60ae91b5d | ||
|
|
d606cd86a0 | ||
|
|
bc463c37f4 | ||
|
|
76c1480903 | ||
|
|
6f312caf8b | ||
|
|
980d8d374f | ||
|
|
c49b34942f | ||
|
|
fcfa8717a5 | ||
|
|
954a265965 | ||
|
|
69845a020a | ||
|
|
22200fd8a7 | ||
|
|
add441675d | ||
|
|
d3d9754277 | ||
|
|
aa5e2edbc5 | ||
|
|
310b099ecf | ||
|
|
1cfaef911c | ||
|
|
b931c5f638 | ||
|
|
7c683668eb | ||
|
|
cab7ac7d58 | ||
|
|
15e69c538a | ||
|
|
31ee938b66 | ||
|
|
e51a8d43d9 | ||
|
|
64cd5b6e4b | ||
|
|
6c9ef34905 | ||
|
|
aa89019236 | ||
|
|
df58fcee16 | ||
|
|
ea3ffc429f | ||
|
|
2efca7a2b5 | ||
|
|
9db448a5e2 | ||
|
|
feee90beef | ||
|
|
906a63b6b5 | ||
|
|
2ce64ac213 | ||
|
|
4d8bf57135 | ||
|
|
c5348ce4b3 | ||
|
|
7f87c03f97 | ||
|
|
9469f148ff | ||
|
|
ffb7dc4ec2 | ||
|
|
242b8fa746 | ||
|
|
50cae5ac3b | ||
|
|
6a71233eb2 | ||
|
|
1aff8933c9 | ||
|
|
0ed87a5dfc | ||
|
|
24a6bcbd1e | ||
|
|
ca7f3da19d | ||
|
|
bf047e2a3c | ||
|
|
4454287be9 | ||
|
|
8b20761e8b | ||
|
|
655d0b5d5f | ||
|
|
91849cdd3a | ||
|
|
df25a694c3 | ||
|
|
eabaca145e | ||
|
|
2f0e458765 | ||
|
|
ff8037f231 |
27
package.json
27
package.json
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "2.0.13",
|
"version": "2.0.18",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
|
"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
|
||||||
"dev:stop": "docker compose -f docker-compose-dev.yaml down",
|
"dev:stop": "docker-compose -f docker-compose-dev.yaml down",
|
||||||
"dev:logs": "docker compose -f docker-compose-dev.yaml logs -f --tail 10",
|
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
|
||||||
"studio": "npx prisma studio",
|
"studio": "npx prisma studio",
|
||||||
"start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js",
|
"start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js",
|
||||||
"build": "svelte-kit build",
|
"build": "svelte-kit build",
|
||||||
@@ -25,9 +25,9 @@
|
|||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-node": "1.0.0-next.67",
|
"@sveltejs/adapter-node": "1.0.0-next.68",
|
||||||
"@sveltejs/adapter-static": "1.0.0-next.27",
|
"@sveltejs/adapter-static": "1.0.0-next.28",
|
||||||
"@sveltejs/kit": "1.0.0-next.259",
|
"@sveltejs/kit": "1.0.0-next.278",
|
||||||
"@types/bcrypt": "5.0.0",
|
"@types/bcrypt": "5.0.0",
|
||||||
"@types/js-cookie": "3.0.1",
|
"@types/js-cookie": "3.0.1",
|
||||||
"@types/node": "17.0.18",
|
"@types/node": "17.0.18",
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
"svelte": "3.46.4",
|
"svelte": "3.46.4",
|
||||||
"svelte-check": "2.4.3",
|
"svelte-check": "2.4.3",
|
||||||
"svelte-preprocess": "4.10.3",
|
"svelte-preprocess": "4.10.3",
|
||||||
"tailwindcss": "3.0.22",
|
"tailwindcss": "3.0.23",
|
||||||
"ts-node": "10.5.0",
|
"ts-node": "10.5.0",
|
||||||
"tslib": "2.3.1",
|
"tslib": "2.3.1",
|
||||||
"typescript": "4.5.5"
|
"typescript": "4.5.5"
|
||||||
@@ -59,9 +59,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@prisma/client": "3.9.2",
|
"@prisma/client": "3.9.2",
|
||||||
"@sentry/node": "6.17.8",
|
"@sentry/node": "6.17.9",
|
||||||
"bcrypt": "5.0.1",
|
"bcrypt": "5.0.1",
|
||||||
"bullmq": "1.72.0",
|
"bullmq": "1.73.0",
|
||||||
"compare-versions": "4.1.3",
|
"compare-versions": "4.1.3",
|
||||||
"cookie": "0.4.2",
|
"cookie": "0.4.2",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
@@ -69,14 +69,15 @@
|
|||||||
"dockerode": "3.3.1",
|
"dockerode": "3.3.1",
|
||||||
"dotenv-extended": "2.9.0",
|
"dotenv-extended": "2.9.0",
|
||||||
"generate-password": "1.7.0",
|
"generate-password": "1.7.0",
|
||||||
"get-port": "6.0.0",
|
"get-port": "6.1.0",
|
||||||
"got": "12.0.1",
|
"got": "12.0.1",
|
||||||
"js-cookie": "3.0.1",
|
"js-cookie": "3.0.1",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"node-forge": "1.2.1",
|
"node-forge": "1.2.1",
|
||||||
"svelte-kit-cookie-session": "2.0.2",
|
"svelte-kit-cookie-session": "2.1.2",
|
||||||
"unique-names-generator": "4.6.0"
|
"tailwindcss-scrollbar": "^0.1.0",
|
||||||
|
"unique-names-generator": "4.7.1"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "node prisma/seed.cjs"
|
"seed": "node prisma/seed.cjs"
|
||||||
|
|||||||
145
pnpm-lock.yaml
generated
145
pnpm-lock.yaml
generated
@@ -3,10 +3,10 @@ lockfileVersion: 5.3
|
|||||||
specifiers:
|
specifiers:
|
||||||
'@iarna/toml': 2.2.5
|
'@iarna/toml': 2.2.5
|
||||||
'@prisma/client': 3.9.2
|
'@prisma/client': 3.9.2
|
||||||
'@sentry/node': 6.17.8
|
'@sentry/node': 6.17.9
|
||||||
'@sveltejs/adapter-node': 1.0.0-next.67
|
'@sveltejs/adapter-node': 1.0.0-next.68
|
||||||
'@sveltejs/adapter-static': 1.0.0-next.27
|
'@sveltejs/adapter-static': 1.0.0-next.28
|
||||||
'@sveltejs/kit': 1.0.0-next.259
|
'@sveltejs/kit': 1.0.0-next.278
|
||||||
'@types/bcrypt': 5.0.0
|
'@types/bcrypt': 5.0.0
|
||||||
'@types/js-cookie': 3.0.1
|
'@types/js-cookie': 3.0.1
|
||||||
'@types/node': 17.0.18
|
'@types/node': 17.0.18
|
||||||
@@ -16,7 +16,7 @@ specifiers:
|
|||||||
'@zerodevx/svelte-toast': 0.6.3
|
'@zerodevx/svelte-toast': 0.6.3
|
||||||
autoprefixer: 10.4.2
|
autoprefixer: 10.4.2
|
||||||
bcrypt: 5.0.1
|
bcrypt: 5.0.1
|
||||||
bullmq: 1.72.0
|
bullmq: 1.73.0
|
||||||
compare-versions: 4.1.3
|
compare-versions: 4.1.3
|
||||||
cookie: 0.4.2
|
cookie: 0.4.2
|
||||||
cross-var: 1.1.0
|
cross-var: 1.1.0
|
||||||
@@ -28,7 +28,7 @@ specifiers:
|
|||||||
eslint-config-prettier: 8.3.0
|
eslint-config-prettier: 8.3.0
|
||||||
eslint-plugin-svelte3: 3.2.1
|
eslint-plugin-svelte3: 3.2.1
|
||||||
generate-password: 1.7.0
|
generate-password: 1.7.0
|
||||||
get-port: 6.0.0
|
get-port: 6.1.0
|
||||||
got: 12.0.1
|
got: 12.0.1
|
||||||
husky: 7.0.4
|
husky: 7.0.4
|
||||||
js-cookie: 3.0.1
|
js-cookie: 3.0.1
|
||||||
@@ -43,20 +43,21 @@ specifiers:
|
|||||||
prisma: 3.9.2
|
prisma: 3.9.2
|
||||||
svelte: 3.46.4
|
svelte: 3.46.4
|
||||||
svelte-check: 2.4.3
|
svelte-check: 2.4.3
|
||||||
svelte-kit-cookie-session: 2.0.5
|
svelte-kit-cookie-session: 2.1.2
|
||||||
svelte-preprocess: 4.10.3
|
svelte-preprocess: 4.10.3
|
||||||
tailwindcss: 3.0.22
|
tailwindcss: 3.0.23
|
||||||
|
tailwindcss-scrollbar: ^0.1.0
|
||||||
ts-node: 10.5.0
|
ts-node: 10.5.0
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.5.5
|
||||||
unique-names-generator: 4.6.0
|
unique-names-generator: 4.7.1
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@iarna/toml': 2.2.5
|
'@iarna/toml': 2.2.5
|
||||||
'@prisma/client': 3.9.2_prisma@3.9.2
|
'@prisma/client': 3.9.2_prisma@3.9.2
|
||||||
'@sentry/node': 6.17.8
|
'@sentry/node': 6.17.9
|
||||||
bcrypt: 5.0.1
|
bcrypt: 5.0.1
|
||||||
bullmq: 1.72.0
|
bullmq: 1.73.0
|
||||||
compare-versions: 4.1.3
|
compare-versions: 4.1.3
|
||||||
cookie: 0.4.2
|
cookie: 0.4.2
|
||||||
cuid: 2.1.8
|
cuid: 2.1.8
|
||||||
@@ -64,19 +65,20 @@ dependencies:
|
|||||||
dockerode: 3.3.1
|
dockerode: 3.3.1
|
||||||
dotenv-extended: 2.9.0
|
dotenv-extended: 2.9.0
|
||||||
generate-password: 1.7.0
|
generate-password: 1.7.0
|
||||||
get-port: 6.0.0
|
get-port: 6.1.0
|
||||||
got: 12.0.1
|
got: 12.0.1
|
||||||
js-cookie: 3.0.1
|
js-cookie: 3.0.1
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
jsonwebtoken: 8.5.1
|
jsonwebtoken: 8.5.1
|
||||||
node-forge: 1.2.1
|
node-forge: 1.2.1
|
||||||
svelte-kit-cookie-session: 2.0.5
|
svelte-kit-cookie-session: 2.1.2
|
||||||
unique-names-generator: 4.6.0
|
tailwindcss-scrollbar: 0.1.0_tailwindcss@3.0.23
|
||||||
|
unique-names-generator: 4.7.1
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@sveltejs/adapter-node': 1.0.0-next.67
|
'@sveltejs/adapter-node': 1.0.0-next.68
|
||||||
'@sveltejs/adapter-static': 1.0.0-next.27
|
'@sveltejs/adapter-static': 1.0.0-next.28
|
||||||
'@sveltejs/kit': 1.0.0-next.259_svelte@3.46.4
|
'@sveltejs/kit': 1.0.0-next.278_svelte@3.46.4
|
||||||
'@types/bcrypt': 5.0.0
|
'@types/bcrypt': 5.0.0
|
||||||
'@types/js-cookie': 3.0.1
|
'@types/js-cookie': 3.0.1
|
||||||
'@types/node': 17.0.18
|
'@types/node': 17.0.18
|
||||||
@@ -99,7 +101,7 @@ devDependencies:
|
|||||||
svelte: 3.46.4
|
svelte: 3.46.4
|
||||||
svelte-check: 2.4.3_postcss@8.4.6+svelte@3.46.4
|
svelte-check: 2.4.3_postcss@8.4.6+svelte@3.46.4
|
||||||
svelte-preprocess: 4.10.3_88b359da5cac6d8f6ee1bbb7080a3fa9
|
svelte-preprocess: 4.10.3_88b359da5cac6d8f6ee1bbb7080a3fa9
|
||||||
tailwindcss: 3.0.22_c940fbabf228b85b1c73d314b43e31f1
|
tailwindcss: 3.0.23_c940fbabf228b85b1c73d314b43e31f1
|
||||||
ts-node: 10.5.0_f3bd4037939c2ed2942ba074291f8ef2
|
ts-node: 10.5.0_f3bd4037939c2ed2942ba074291f8ef2
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.5.5
|
typescript: 4.5.5
|
||||||
@@ -293,56 +295,56 @@ packages:
|
|||||||
picomatch: 2.3.0
|
picomatch: 2.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@sentry/core/6.17.8:
|
/@sentry/core/6.17.9:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-4WTjgQom75Rvgn6XYy6e7vMIbWlj8utau1wWvr7kjqFKuuuuycRvPgVzAdVr4B3WDHHCInAZpUchsOLs2qwIEA==
|
integrity: sha512-14KalmTholGUtgdh9TklO+jUpyQ/D3OGkhlH1rnGQGoJgFy2eYm+s+MnUEMxFdGIUCz5kOteuNqYZxaDmFagpQ==
|
||||||
}
|
}
|
||||||
engines: { node: '>=6' }
|
engines: { node: '>=6' }
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sentry/hub': 6.17.8
|
'@sentry/hub': 6.17.9
|
||||||
'@sentry/minimal': 6.17.8
|
'@sentry/minimal': 6.17.9
|
||||||
'@sentry/types': 6.17.8
|
'@sentry/types': 6.17.9
|
||||||
'@sentry/utils': 6.17.8
|
'@sentry/utils': 6.17.9
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@sentry/hub/6.17.8:
|
/@sentry/hub/6.17.9:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-GW0XYpkoQu/kSJaTLfsF4extHDOBPNRnT0qKr/YO20Z5wGxYp8LsdnAuU3njcFHcAV2F/QDTj2BPq1U385/4+A==
|
integrity: sha512-34EdrweWDbBV9EzEFIXcO+JeoyQmKzQVJxpTKZoJA6PUwf2NrndaUdjlkDEtBEzjuLUTxhLxtOzEsYs1O6RVcg==
|
||||||
}
|
}
|
||||||
engines: { node: '>=6' }
|
engines: { node: '>=6' }
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sentry/types': 6.17.8
|
'@sentry/types': 6.17.9
|
||||||
'@sentry/utils': 6.17.8
|
'@sentry/utils': 6.17.9
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@sentry/minimal/6.17.8:
|
/@sentry/minimal/6.17.9:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-VJXFZBO/O8SViK0fdzodxpNr+pbpgczNgLpz/MNuSooV6EBesgCMVjXtxDUp1Ie1odc0GUprN/ZMLYBmYdIrKQ==
|
integrity: sha512-T3PMCHcKk6lkZq6zKgANrYJJxXBXKOe+ousV1Fas1rVBMv7dtKfsa4itqQHszcW9shusPDiaQKIJ4zRLE5LKmg==
|
||||||
}
|
}
|
||||||
engines: { node: '>=6' }
|
engines: { node: '>=6' }
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sentry/hub': 6.17.8
|
'@sentry/hub': 6.17.9
|
||||||
'@sentry/types': 6.17.8
|
'@sentry/types': 6.17.9
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@sentry/node/6.17.8:
|
/@sentry/node/6.17.9:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-b3zg1XjKtxp7o821ENORO1CCzMM4QzKP01rzztMwyMcj28dmUq36QXoQAnwdKn7jEYkJdLnMeniIBR6U6NUJrQ==
|
integrity: sha512-jbn+q7qPGOh6D7nYoYGaAlmuvMDpQmyMwBtUVYybuZp2AALe43O3Z4LtoJ+1+F31XowpsIPZx1mwNs4ZrILskA==
|
||||||
}
|
}
|
||||||
engines: { node: '>=6' }
|
engines: { node: '>=6' }
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sentry/core': 6.17.8
|
'@sentry/core': 6.17.9
|
||||||
'@sentry/hub': 6.17.8
|
'@sentry/hub': 6.17.9
|
||||||
'@sentry/tracing': 6.17.8
|
'@sentry/tracing': 6.17.9
|
||||||
'@sentry/types': 6.17.8
|
'@sentry/types': 6.17.9
|
||||||
'@sentry/utils': 6.17.8
|
'@sentry/utils': 6.17.9
|
||||||
cookie: 0.4.2
|
cookie: 0.4.2
|
||||||
https-proxy-agent: 5.0.0
|
https-proxy-agent: 5.0.0
|
||||||
lru_map: 0.3.3
|
lru_map: 0.3.3
|
||||||
@@ -351,36 +353,36 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@sentry/tracing/6.17.8:
|
/@sentry/tracing/6.17.9:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-WJ3W8O6iPI3w7MrzTnYcw3s5PGBNFqT4b9oBCl5Ndjexs8DsGlQOxjrsipo36z6TpnRHpAE4FEbOETb2R8JRJQ==
|
integrity: sha512-5Rb/OS4ryNJLvz2nv6wyjwhifjy6veqaF9ffLrwFYij/WDy7m62ASBblxgeiI3fbPLX0aBRFWIJAq1vko26+AQ==
|
||||||
}
|
}
|
||||||
engines: { node: '>=6' }
|
engines: { node: '>=6' }
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sentry/hub': 6.17.8
|
'@sentry/hub': 6.17.9
|
||||||
'@sentry/minimal': 6.17.8
|
'@sentry/minimal': 6.17.9
|
||||||
'@sentry/types': 6.17.8
|
'@sentry/types': 6.17.9
|
||||||
'@sentry/utils': 6.17.8
|
'@sentry/utils': 6.17.9
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@sentry/types/6.17.8:
|
/@sentry/types/6.17.9:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-0i0f+dpvV62Pm5QMVBHNfEsTGIXoXRGQbeN2LGL4XbhzrzUmIrBPzrnZHv9c/JYtSJnI6A0B9OG7Bdlh3aku+Q==
|
integrity: sha512-xuulX6qUCL14ayEOh/h6FUIvZtsi1Bx34dSOaWDrjXUOJHJAM7214uiqW1GZxPJ13YuaUIubjTSfDmSQ9CBzTw==
|
||||||
}
|
}
|
||||||
engines: { node: '>=6' }
|
engines: { node: '>=6' }
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@sentry/utils/6.17.8:
|
/@sentry/utils/6.17.9:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-cAOM53A5FHv95hpDuXKJU8rI4B1XdZ6qe3Yo+/nDS9QDpOgzvyjcItgXPvKW1wUjdHCcnwu7VBfBxB7teYOW9g==
|
integrity: sha512-4eo9Z3JlJCGlGrQRbtZWL+L9NnlUXgTbfK3Lk7oO8D1ev8R5b5+iE6tZHTvU5rQRcq6zu+POT+tK5u9oxc/rnQ==
|
||||||
}
|
}
|
||||||
engines: { node: '>=6' }
|
engines: { node: '>=6' }
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sentry/types': 6.17.8
|
'@sentry/types': 6.17.9
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -392,28 +394,28 @@ packages:
|
|||||||
engines: { node: '>=10' }
|
engines: { node: '>=10' }
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@sveltejs/adapter-node/1.0.0-next.67:
|
/@sveltejs/adapter-node/1.0.0-next.68:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-+LuLn91xARZsRANiQNIIDpMMncUTnP2pJc8tyL+FdpVvs5UtlvkYJpeCBPFqjjseRpIIbi8Slu89GCdrRXBDUg==
|
integrity: sha512-MiEjtl15Aupm6bjirVlq0kkc9AL8qDXz/blsh4jYMsaiidmcEHeDgfZQFM5YiXy95DbxV30MAkhwCQiYK/J8Kw==
|
||||||
}
|
}
|
||||||
dependencies:
|
dependencies:
|
||||||
tiny-glob: 0.2.9
|
tiny-glob: 0.2.9
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@sveltejs/adapter-static/1.0.0-next.27:
|
/@sveltejs/adapter-static/1.0.0-next.28:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-dcN1p1D7ZY/a9SClfN14mgm9pyWbLxdwM9gzPMZG6xXOoqMtwI03aZOFgGGumHPdv+XcGRZM96vUSRoDm6vBJQ==
|
integrity: sha512-c4xLyeSwnbGQxe4f1SLpHTbxZDm3TEr43scR3tOlVgQN+mnAL9aDdl3nTtdzWmrUDmDEmY4GriAwLyFLZuINLw==
|
||||||
}
|
}
|
||||||
dependencies:
|
dependencies:
|
||||||
tiny-glob: 0.2.9
|
tiny-glob: 0.2.9
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@sveltejs/kit/1.0.0-next.259_svelte@3.46.4:
|
/@sveltejs/kit/1.0.0-next.278_svelte@3.46.4:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-+Tss6cQXmpi4Jno/ZP0zJ3INBLMED+WeW4UI81tmexheC76Y2p+cbInneKO/REx/8QFo1iroYrWAUkZPsOg8Ew==
|
integrity: sha512-WT93Wnu05X9WG9BMMk/dj0gy6R7iXm9aXRDVgmIl9z8jT2ukejgmkhi5IwBYrK0OMIUALRVfukn+iy+srPc91Q==
|
||||||
}
|
}
|
||||||
engines: { node: '>=14.13' }
|
engines: { node: '>=14.13' }
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -1746,10 +1748,10 @@ packages:
|
|||||||
ieee754: 1.2.1
|
ieee754: 1.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/bullmq/1.72.0:
|
/bullmq/1.73.0:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-Q0pk6GphHyYsacpjZZFhjp/+TY+2g2FDsJS3qwIyskQL4j7vZaa1iYX3gKDEBn4C5eZMP1EOl9GWkm2bhdB0Wg==
|
integrity: sha512-+BF7yeGagYD/iMkM3FA8Wvb3j3MyKE/OdXv404+nQjUsKXfL7PbqX5NSA9lBtFzOdyFx9ZWyKRnBwuGQsLfM0w==
|
||||||
}
|
}
|
||||||
dependencies:
|
dependencies:
|
||||||
cron-parser: 2.18.0
|
cron-parser: 2.18.0
|
||||||
@@ -3114,10 +3116,10 @@ packages:
|
|||||||
engines: { node: '>=8' }
|
engines: { node: '>=8' }
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/get-port/6.0.0:
|
/get-port/6.1.0:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-qSVkVF6Eq1GdL/cBNiFuP4nUHMF7OEMTqEjC6alR2N90u8BFOoO0PFhNTX2QtAUoGrz8NnrSWj85TZ8YXZ6LOA==
|
integrity: sha512-JKnPFW/G2ZRirH/25sLK1aLBQktJfQLixzMMuMBP8A2G/ivSaIwdTnlJeO7PWeyhyIGVorezNf6+CXZU9i0cIQ==
|
||||||
}
|
}
|
||||||
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
|
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
|
||||||
dev: false
|
dev: false
|
||||||
@@ -5203,10 +5205,10 @@ packages:
|
|||||||
svelte: 3.46.4
|
svelte: 3.46.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/svelte-kit-cookie-session/2.0.5:
|
/svelte-kit-cookie-session/2.1.2:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-IX1IXtn42UTz/isem1LqH0SAZdCx6Z6Iu2V4Q83V2EScFbXZWfeFY08Azl8ZrPKdIDhSNHBLAAumRjA6TBxCvQ==
|
integrity: sha512-PfxIWDhiyYWu7iKlL0GHpmwDrdFh+rX/WmBzOuvctF25UqngIo9MCiegWBSBLE1RBwNs5UqaIeI8+vligmY07g==
|
||||||
}
|
}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -5288,10 +5290,21 @@ packages:
|
|||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/tailwindcss/3.0.22_c940fbabf228b85b1c73d314b43e31f1:
|
/tailwindcss-scrollbar/0.1.0_tailwindcss@3.0.23:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-F8lt74RlNZirnkaSk310+vGQta7c0/hgx7/bqxruM4wS9lp8oqV93lzavajC3VT0Lp4UUtUVIt8ifKcmGzkr0A==
|
integrity: sha512-egipxw4ooQDh94x02XQpPck0P0sfwazwoUGfA9SedPATIuYDR+6qe8d31Gl7YsSMRiOKDkkqfI0kBvEw9lT/Hg==
|
||||||
|
}
|
||||||
|
peerDependencies:
|
||||||
|
tailwindcss: '>= 2.x.x'
|
||||||
|
dependencies:
|
||||||
|
tailwindcss: 3.0.23_c940fbabf228b85b1c73d314b43e31f1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/tailwindcss/3.0.23_c940fbabf228b85b1c73d314b43e31f1:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-+OZOV9ubyQ6oI2BXEhzw4HrqvgcARY38xv3zKcjnWtMIZstEsXdI9xftd1iB7+RbOnj2HOEzkA0OyB5BaSxPQA==
|
||||||
}
|
}
|
||||||
engines: { node: '>=12.13.0' }
|
engines: { node: '>=12.13.0' }
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -5515,10 +5528,10 @@ packages:
|
|||||||
function.name: 1.0.13
|
function.name: 1.0.13
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/unique-names-generator/4.6.0:
|
/unique-names-generator/4.7.1:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-m0fke1emBeT96UYn2psPQYwljooDWRTKt9oUZ5vlt88ZFMBGxqwPyLHXwCfkbgdm8jzioCp7oIpo6KdM+fnUlQ==
|
integrity: sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==
|
||||||
}
|
}
|
||||||
engines: { node: '>=8' }
|
engines: { node: '>=8' }
|
||||||
dev: false
|
dev: false
|
||||||
|
|||||||
47
prisma/migrations/20220217211304_dualcerts/migration.sql
Normal file
47
prisma/migrations/20220217211304_dualcerts/migration.sql
Normal 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;
|
||||||
19
prisma/migrations/20220219231255_prmr_secrets/migration.sql
Normal file
19
prisma/migrations/20220219231255_prmr_secrets/migration.sql
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Secret" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"value" TEXT NOT NULL,
|
||||||
|
"isPRMRSecret" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isBuildSecret" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"applicationId" TEXT NOT NULL,
|
||||||
|
CONSTRAINT "Secret_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Secret" ("applicationId", "createdAt", "id", "isBuildSecret", "name", "updatedAt", "value") SELECT "applicationId", "createdAt", "id", "isBuildSecret", "name", "updatedAt", "value" FROM "Secret";
|
||||||
|
DROP TABLE "Secret";
|
||||||
|
ALTER TABLE "new_Secret" RENAME TO "Secret";
|
||||||
|
CREATE UNIQUE INDEX "Secret_name_applicationId_isPRMRSecret_key" ON "Secret"("name", "applicationId", "isPRMRSecret");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Setting" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||||
|
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||||
|
"proxyPassword" TEXT NOT NULL,
|
||||||
|
"proxyUser" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Setting" ("createdAt", "dualCerts", "fqdn", "id", "isRegistrationEnabled", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "dualCerts", "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");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -11,6 +11,9 @@ model Setting {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
fqdn String? @unique
|
fqdn String? @unique
|
||||||
isRegistrationEnabled Boolean @default(false)
|
isRegistrationEnabled Boolean @default(false)
|
||||||
|
dualCerts Boolean @default(false)
|
||||||
|
minPort Int @default(9000)
|
||||||
|
maxPort Int @default(9100)
|
||||||
proxyPassword String
|
proxyPassword String
|
||||||
proxyUser String
|
proxyUser String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -97,6 +100,7 @@ model ApplicationSettings {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
applicationId String @unique
|
applicationId String @unique
|
||||||
|
dualCerts Boolean @default(false)
|
||||||
debug Boolean @default(false)
|
debug Boolean @default(false)
|
||||||
previews Boolean @default(false)
|
previews Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -105,15 +109,16 @@ model ApplicationSettings {
|
|||||||
|
|
||||||
model Secret {
|
model Secret {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
value String
|
value String
|
||||||
|
isPRMRSecret Boolean @default(false)
|
||||||
isBuildSecret Boolean @default(false)
|
isBuildSecret Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
applicationId String
|
applicationId String
|
||||||
|
|
||||||
@@unique([name, applicationId])
|
@@unique([name, applicationId, isPRMRSecret])
|
||||||
}
|
}
|
||||||
|
|
||||||
model BuildLog {
|
model BuildLog {
|
||||||
@@ -234,6 +239,7 @@ model Service {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
fqdn String?
|
fqdn String?
|
||||||
|
dualCerts Boolean @default(false)
|
||||||
type String?
|
type String?
|
||||||
version String?
|
version String?
|
||||||
teams Team[]
|
teams Team[]
|
||||||
|
|||||||
105
src/global.d.ts → src/app.d.ts
vendored
105
src/global.d.ts → src/app.d.ts
vendored
@@ -1,74 +1,26 @@
|
|||||||
/// <reference types="@sveltejs/kit" />
|
/// <reference types="@sveltejs/kit" />
|
||||||
interface Cookies {
|
|
||||||
teamId?: string;
|
declare namespace App {
|
||||||
gitlabToken?: string;
|
interface Locals {
|
||||||
'kit.session'?: string;
|
session: import('svelte-kit-cookie-session').Session<SessionData>;
|
||||||
}
|
cookies: Record<string, string>;
|
||||||
interface Locals {
|
}
|
||||||
gitlabToken?: string;
|
interface Platform {}
|
||||||
user: {
|
interface Session extends SessionData {}
|
||||||
teamId: string;
|
interface Stuff {}
|
||||||
permission: string;
|
|
||||||
isAdmin: boolean;
|
|
||||||
};
|
|
||||||
session: {
|
|
||||||
data: {
|
|
||||||
uid?: string;
|
|
||||||
teams?: string[];
|
|
||||||
expires?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Applications = {
|
interface SessionData {
|
||||||
name: string;
|
version?: string;
|
||||||
domain: string;
|
userId?: string | null;
|
||||||
};
|
teamId?: string | null;
|
||||||
|
permission?: string;
|
||||||
interface Hash {
|
isAdmin?: boolean;
|
||||||
iv: string;
|
expires?: string | null;
|
||||||
content: string;
|
gitlabToken?: string | null;
|
||||||
|
ghToken?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BuildPack {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Not used, not working what?!
|
|
||||||
enum GitSource {
|
|
||||||
Github = 'github',
|
|
||||||
Gitlab = 'gitlab',
|
|
||||||
Bitbucket = 'bitbucket'
|
|
||||||
}
|
|
||||||
|
|
||||||
type RawHaproxyConfiguration = {
|
|
||||||
_version: number;
|
|
||||||
data: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type NewTransaction = {
|
|
||||||
_version: number;
|
|
||||||
id: string;
|
|
||||||
status: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type HttpRequestRuleForceSSL = {
|
|
||||||
return_hdrs: null;
|
|
||||||
cond: string;
|
|
||||||
cond_test: string;
|
|
||||||
index: number;
|
|
||||||
redir_code: number;
|
|
||||||
redir_type: string;
|
|
||||||
redir_value: string;
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: No any please
|
|
||||||
type HttpRequestRule = {
|
|
||||||
_version: number;
|
|
||||||
data: Array<any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DateTimeFormatOptions = {
|
type DateTimeFormatOptions = {
|
||||||
localeMatcher?: 'lookup' | 'best fit';
|
localeMatcher?: 'lookup' | 'best fit';
|
||||||
weekday?: 'long' | 'short' | 'narrow';
|
weekday?: 'long' | 'short' | 'narrow';
|
||||||
@@ -84,3 +36,24 @@ type DateTimeFormatOptions = {
|
|||||||
hour12?: boolean;
|
hour12?: boolean;
|
||||||
timeZone?: string;
|
timeZone?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface Hash {
|
||||||
|
iv: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RawHaproxyConfiguration = {
|
||||||
|
_version: number;
|
||||||
|
data: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NewTransaction = {
|
||||||
|
_version: number;
|
||||||
|
id: string;
|
||||||
|
status: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Application = {
|
||||||
|
name: string;
|
||||||
|
domain: string;
|
||||||
|
};
|
||||||
43
src/hooks.ts
43
src/hooks.ts
@@ -2,7 +2,7 @@ import dotEnvExtended from 'dotenv-extended';
|
|||||||
dotEnvExtended.load();
|
dotEnvExtended.load();
|
||||||
import type { GetSession } from '@sveltejs/kit';
|
import type { GetSession } from '@sveltejs/kit';
|
||||||
import { handleSession } from 'svelte-kit-cookie-session';
|
import { handleSession } from 'svelte-kit-cookie-session';
|
||||||
import { getUserDetails, isTeamIdTokenAvailable, sentry } from '$lib/common';
|
import { getUserDetails, sentry } from '$lib/common';
|
||||||
import { version } from '$lib/common';
|
import { version } from '$lib/common';
|
||||||
import cookie from 'cookie';
|
import cookie from 'cookie';
|
||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
@@ -10,27 +10,34 @@ import { dev } from '$app/env';
|
|||||||
export const handle = handleSession(
|
export const handle = handleSession(
|
||||||
{
|
{
|
||||||
secret: process.env['COOLIFY_SECRET_KEY'],
|
secret: process.env['COOLIFY_SECRET_KEY'],
|
||||||
expires: 30
|
expires: 30,
|
||||||
|
cookie: { secure: false }
|
||||||
},
|
},
|
||||||
async function ({ event, resolve }) {
|
async function ({ event, resolve }) {
|
||||||
let response;
|
let response;
|
||||||
try {
|
try {
|
||||||
const cookies: Cookies = cookie.parse(event.request.headers.get('cookie') || '');
|
if (event.locals.cookies) {
|
||||||
if (cookies['kit.session']) {
|
if (event.locals.cookies['kit.session']) {
|
||||||
const { permission, teamId } = await getUserDetails(event, false);
|
const { permission, teamId, userId } = await getUserDetails(event, false);
|
||||||
event.locals.user = {
|
const newSession = {
|
||||||
teamId,
|
userId,
|
||||||
permission,
|
teamId,
|
||||||
isAdmin: permission === 'admin' || permission === 'owner'
|
permission,
|
||||||
};
|
isAdmin: permission === 'admin' || permission === 'owner',
|
||||||
}
|
expires: event.locals.session.data.expires
|
||||||
if (cookies.gitlabToken) {
|
};
|
||||||
event.locals.gitlabToken = cookies.gitlabToken;
|
|
||||||
|
if (JSON.stringify(event.locals.session.data) !== JSON.stringify(newSession)) {
|
||||||
|
event.locals.session.data = { ...newSession };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response = await resolve(event, {
|
response = await resolve(event, {
|
||||||
ssr: !event.url.pathname.startsWith('/webhooks/success')
|
ssr: !event.url.pathname.startsWith('/webhooks/success')
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
response = await resolve(event, {
|
response = await resolve(event, {
|
||||||
ssr: !event.url.pathname.startsWith('/webhooks/success')
|
ssr: !event.url.pathname.startsWith('/webhooks/success')
|
||||||
});
|
});
|
||||||
@@ -61,17 +68,13 @@ export const handle = handleSession(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getSession: GetSession = function (request) {
|
export const getSession: GetSession = function ({ locals }) {
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
gitlabToken: request.locals?.gitlabToken || null,
|
...locals.session.data
|
||||||
uid: request.locals.session.data?.uid || null,
|
|
||||||
teamId: request.locals.user?.teamId || null,
|
|
||||||
permission: request.locals.user?.permission,
|
|
||||||
isAdmin: request.locals.user?.isAdmin || false
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function handleError({ error, event }) {
|
export async function handleError({ error, event }) {
|
||||||
if (!dev) sentry.captureException(error, { event });
|
if (!dev) sentry.captureException(error, event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ export default async function ({
|
|||||||
docker,
|
docker,
|
||||||
buildId,
|
buildId,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
secrets
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
let file = `${workdir}/Dockerfile`;
|
let file = `${workdir}/Dockerfile`;
|
||||||
@@ -24,7 +25,15 @@ export default async function ({
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } =
|
const {
|
||||||
data;
|
workdir,
|
||||||
|
port,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
|
} = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
@@ -11,7 +19,15 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } =
|
const {
|
||||||
data;
|
workdir,
|
||||||
|
port,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
|
} = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
@@ -11,7 +19,15 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } =
|
const {
|
||||||
data;
|
workdir,
|
||||||
|
port,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
|
} = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
@@ -11,7 +19,15 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,16 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
const { applicationId, tag, workdir, buildCommand, baseDirectory, publishDirectory, secrets } =
|
const {
|
||||||
data;
|
applicationId,
|
||||||
|
tag,
|
||||||
|
workdir,
|
||||||
|
buildCommand,
|
||||||
|
baseDirectory,
|
||||||
|
publishDirectory,
|
||||||
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
|
} = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
@@ -11,7 +19,15 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const isTeamIdTokenAvailable = (request) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getTeam = (event) => {
|
export const getTeam = (event) => {
|
||||||
const cookies: Cookies = Cookie.parse(event.request.headers.get('cookie'));
|
const cookies = Cookie.parse(event.request.headers.get('cookie'));
|
||||||
if (cookies.teamId) {
|
if (cookies.teamId) {
|
||||||
return cookies.teamId;
|
return cookies.teamId;
|
||||||
} else if (event.locals.session.data.teamId) {
|
} else if (event.locals.session.data.teamId) {
|
||||||
@@ -78,7 +78,7 @@ export const getTeam = (event) => {
|
|||||||
|
|
||||||
export const getUserDetails = async (event, isAdminRequired = true) => {
|
export const getUserDetails = async (event, isAdminRequired = true) => {
|
||||||
const teamId = getTeam(event);
|
const teamId = getTeam(event);
|
||||||
const userId = event.locals.session.data.uid || null;
|
const userId = event.locals.session.data.userId || null;
|
||||||
const { permission = 'read' } = await db.prisma.permission.findFirst({
|
const { permission = 'read' } = await db.prisma.permission.findFirst({
|
||||||
where: { teamId, userId },
|
where: { teamId, userId },
|
||||||
select: { permission: true },
|
select: { permission: true },
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
export let value;
|
|
||||||
let showPassword = false;
|
let showPassword = false;
|
||||||
|
|
||||||
|
export let value;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let isPasswordField = false;
|
export let isPasswordField = false;
|
||||||
export let readonly = false;
|
export let readonly = false;
|
||||||
@@ -14,30 +14,22 @@
|
|||||||
export let name;
|
export let name;
|
||||||
export let placeholder = '';
|
export let placeholder = '';
|
||||||
|
|
||||||
let disabledClass = 'bg-coolback disabled:bg-coolblack select-all';
|
let disabledClass = 'bg-coolback disabled:bg-coolblack';
|
||||||
let actionsShow = false;
|
|
||||||
let isHttps = browser && window.location.protocol === 'https:';
|
let isHttps = browser && window.location.protocol === 'https:';
|
||||||
|
|
||||||
function showActions(value) {
|
|
||||||
actionsShow = value;
|
|
||||||
}
|
|
||||||
function copyToClipboard() {
|
function copyToClipboard() {
|
||||||
if (isHttps && navigator.clipboard) {
|
if (isHttps && navigator.clipboard) {
|
||||||
navigator.clipboard.writeText(value);
|
navigator.clipboard.writeText(value);
|
||||||
toast.push('Copied to clipboard');
|
toast.push('Copied to clipboard.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span
|
<div class="relative">
|
||||||
class="relative"
|
|
||||||
on:mouseenter={() => showActions(true)}
|
|
||||||
on:mouseleave={() => showActions(false)}
|
|
||||||
>
|
|
||||||
{#if !isPasswordField || showPassword}
|
{#if !isPasswordField || showPassword}
|
||||||
{#if textarea}
|
{#if textarea}
|
||||||
<textarea
|
<textarea
|
||||||
rows="3"
|
rows="5"
|
||||||
class={disabledClass}
|
class={disabledClass}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
type="text"
|
type="text"
|
||||||
@@ -77,69 +69,67 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if actionsShow}
|
<div class="absolute top-0 right-0 m-3 cursor-pointer text-warmGray-600 hover:text-white">
|
||||||
<div class="absolute top-0 right-0 mx-2 cursor-pointer text-warmGray-600 hover:text-white">
|
<div class="flex space-x-2">
|
||||||
<div class="flex space-x-2">
|
{#if isPasswordField}
|
||||||
{#if isPasswordField}
|
<div on:click={() => (showPassword = !showPassword)}>
|
||||||
<div on:click={() => (showPassword = !showPassword)}>
|
{#if showPassword}
|
||||||
{#if showPassword}
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{:else}
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if value && isHttps}
|
|
||||||
<div on:click={copyToClipboard}>
|
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="h-6 w-6"
|
class="h-6 w-6"
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke-linecap="round"
|
viewBox="0 0 24 24"
|
||||||
stroke-linejoin="round"
|
stroke="currentColor"
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path
|
||||||
<rect x="8" y="8" width="12" height="12" rx="2" />
|
stroke-linecap="round"
|
||||||
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
{:else}
|
||||||
{/if}
|
<svg
|
||||||
</div>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if value && isHttps}
|
||||||
|
<div on:click={copyToClipboard}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<rect x="8" y="8" width="12" height="12" rx="2" />
|
||||||
|
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
</span>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
export let text;
|
export let text;
|
||||||
export let maxWidthClass = 'max-w-[24rem]';
|
export let customClass = 'max-w-[24rem]';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="py-1 text-xs text-stone-400 {maxWidthClass}">{@html text}</div>
|
<div class="p-2 text-xs text-stone-400 {customClass}">{@html text}</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<span class="loader" />
|
<span class="loader" />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class=" main h-64 py-24 left-0 top-0 flex flex-wrap content-center mx-auto">
|
<div class="main h-64 py-24 left-0 top-0 flex flex-wrap content-center mx-auto">
|
||||||
<span class="loader" />
|
<span class="loader" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -4,15 +4,23 @@
|
|||||||
export let setting;
|
export let setting;
|
||||||
export let title;
|
export let title;
|
||||||
export let description;
|
export let description;
|
||||||
export let isPadding = true;
|
export let isCenter = true;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
export let dataTooltip = null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li class="flex items-center py-4">
|
<div class="flex items-center py-4 pr-8">
|
||||||
<div class="flex w-96 flex-col" class:px-4={isPadding} class:pr-32={!isPadding}>
|
<div class="flex w-96 flex-col">
|
||||||
<p class="text-xs font-bold text-stone-100 md:text-base">{title}</p>
|
<div class="text-xs font-bold text-stone-100 md:text-base">{title}</div>
|
||||||
<Explainer text={description} />
|
<Explainer text={description} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class:tooltip={dataTooltip}
|
||||||
|
class:text-center={isCenter}
|
||||||
|
data-tooltip={dataTooltip}
|
||||||
|
class="flex justify-center"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
type="button"
|
type="button"
|
||||||
on:click
|
on:click
|
||||||
@@ -58,5 +66,4 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- {/if} -->
|
</div>
|
||||||
</li>
|
|
||||||
|
|||||||
@@ -9,22 +9,6 @@ export const dateOptions: DateTimeFormatOptions = {
|
|||||||
hour12: false
|
hour12: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getGithubToken({ apiUrl, application, githubToken }): Promise<void> {
|
|
||||||
const response = await fetch(
|
|
||||||
`${apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${githubToken}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Git Source not configured.');
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
return data.token;
|
|
||||||
}
|
|
||||||
export const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby', 'php'];
|
export const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby', 'php'];
|
||||||
export const notNodeDeployments = ['php', 'docker', 'rust'];
|
export const notNodeDeployments = ['php', 'docker', 'rust'];
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,146 @@
|
|||||||
const defaultBuildAndDeploy = {
|
function defaultBuildAndDeploy(packageManager) {
|
||||||
installCommand: 'yarn install',
|
return {
|
||||||
buildCommand: 'yarn build',
|
installCommand:
|
||||||
startCommand: 'yarn start'
|
packageManager === 'npm' ? `${packageManager} run install` : `${packageManager} install`,
|
||||||
};
|
buildCommand:
|
||||||
export const buildPacks = [
|
packageManager === 'npm' ? `${packageManager} run build` : `${packageManager} build`,
|
||||||
{
|
startCommand:
|
||||||
|
packageManager === 'npm' ? `${packageManager} run start` : `${packageManager} start`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function findBuildPack(pack, packageManager = 'npm') {
|
||||||
|
const metaData = buildPacks.find((b) => b.name === pack);
|
||||||
|
if (pack === 'node') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
installCommand: null,
|
||||||
|
buildCommand: null,
|
||||||
|
startCommand: null,
|
||||||
|
publishDirectory: null,
|
||||||
|
port: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'static') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
installCommand: null,
|
||||||
|
buildCommand: null,
|
||||||
|
startCommand: null,
|
||||||
|
publishDirectory: null,
|
||||||
|
port: 80
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'docker') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
installCommand: null,
|
||||||
|
buildCommand: null,
|
||||||
|
startCommand: null,
|
||||||
|
publishDirectory: null,
|
||||||
|
port: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'svelte') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
...defaultBuildAndDeploy(packageManager),
|
||||||
|
publishDirectory: 'public',
|
||||||
|
port: 80
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'nestjs') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
...defaultBuildAndDeploy(packageManager),
|
||||||
|
startCommand:
|
||||||
|
packageManager === 'npm' ? 'npm run start:prod' : `${packageManager} run start:prod`,
|
||||||
|
publishDirectory: null,
|
||||||
|
port: 3000
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'react') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
...defaultBuildAndDeploy(packageManager),
|
||||||
|
publishDirectory: 'build',
|
||||||
|
port: 80
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'nextjs') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
...defaultBuildAndDeploy(packageManager),
|
||||||
|
publishDirectory: null,
|
||||||
|
port: 3000
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'gatsby') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
...defaultBuildAndDeploy(packageManager),
|
||||||
|
publishDirectory: 'public',
|
||||||
|
port: 80
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'vuejs') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
...defaultBuildAndDeploy(packageManager),
|
||||||
|
publishDirectory: 'dist',
|
||||||
|
port: 80
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'nuxtjs') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
...defaultBuildAndDeploy(packageManager),
|
||||||
|
publishDirectory: null,
|
||||||
|
port: 3000
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'preact') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
...defaultBuildAndDeploy(packageManager),
|
||||||
|
publishDirectory: 'build',
|
||||||
|
port: 80
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'php') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
installCommand: null,
|
||||||
|
buildCommand: null,
|
||||||
|
startCommand: null,
|
||||||
|
publishDirectory: null,
|
||||||
|
port: 80
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pack === 'rust') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
installCommand: null,
|
||||||
|
buildCommand: null,
|
||||||
|
startCommand: null,
|
||||||
|
publishDirectory: null,
|
||||||
|
port: 3000
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
name: 'node',
|
name: 'node',
|
||||||
|
fancyName: 'Node.js',
|
||||||
|
hoverColor: 'hover:bg-green-700',
|
||||||
|
color: 'bg-green-700',
|
||||||
installCommand: null,
|
installCommand: null,
|
||||||
buildCommand: null,
|
buildCommand: null,
|
||||||
startCommand: null,
|
startCommand: null,
|
||||||
publishDirectory: null,
|
publishDirectory: null,
|
||||||
port: null,
|
port: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export const buildPacks = [
|
||||||
|
{
|
||||||
|
name: 'node',
|
||||||
fancyName: 'Node.js',
|
fancyName: 'Node.js',
|
||||||
hoverColor: 'hover:bg-green-700',
|
hoverColor: 'hover:bg-green-700',
|
||||||
color: 'bg-green-700'
|
color: 'bg-green-700'
|
||||||
@@ -18,104 +148,72 @@ export const buildPacks = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
name: 'static',
|
name: 'static',
|
||||||
...defaultBuildAndDeploy,
|
|
||||||
publishDirectory: 'dist',
|
|
||||||
port: 80,
|
|
||||||
fancyName: 'Static',
|
fancyName: 'Static',
|
||||||
hoverColor: 'hover:bg-orange-700',
|
hoverColor: 'hover:bg-orange-700',
|
||||||
color: 'bg-orange-700'
|
color: 'bg-orange-700'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'docker',
|
name: 'docker',
|
||||||
installCommand: null,
|
|
||||||
buildCommand: null,
|
|
||||||
startCommand: null,
|
|
||||||
publishDirectory: null,
|
|
||||||
port: null,
|
|
||||||
fancyName: 'Docker',
|
fancyName: 'Docker',
|
||||||
hoverColor: 'hover:bg-sky-700',
|
hoverColor: 'hover:bg-sky-700',
|
||||||
color: 'bg-sky-700'
|
color: 'bg-sky-700'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'svelte',
|
name: 'svelte',
|
||||||
...defaultBuildAndDeploy,
|
|
||||||
publishDirectory: 'public',
|
|
||||||
port: 80,
|
|
||||||
fancyName: 'Svelte',
|
fancyName: 'Svelte',
|
||||||
hoverColor: 'hover:bg-orange-700',
|
hoverColor: 'hover:bg-orange-700',
|
||||||
color: 'bg-orange-700'
|
color: 'bg-orange-700'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'nestjs',
|
name: 'nestjs',
|
||||||
...defaultBuildAndDeploy,
|
|
||||||
startCommand: 'yarn start:prod',
|
|
||||||
port: 3000,
|
|
||||||
fancyName: 'NestJS',
|
fancyName: 'NestJS',
|
||||||
hoverColor: 'hover:bg-red-700',
|
hoverColor: 'hover:bg-red-700',
|
||||||
color: 'bg-red-700'
|
color: 'bg-red-700'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'react',
|
name: 'react',
|
||||||
...defaultBuildAndDeploy,
|
|
||||||
publishDirectory: 'build',
|
|
||||||
port: 80,
|
|
||||||
fancyName: 'React',
|
fancyName: 'React',
|
||||||
hoverColor: 'hover:bg-blue-700',
|
hoverColor: 'hover:bg-blue-700',
|
||||||
color: 'bg-blue-700'
|
color: 'bg-blue-700'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'nextjs',
|
name: 'nextjs',
|
||||||
...defaultBuildAndDeploy,
|
|
||||||
port: 3000,
|
|
||||||
fancyName: 'NextJS',
|
fancyName: 'NextJS',
|
||||||
hoverColor: 'hover:bg-blue-700',
|
hoverColor: 'hover:bg-blue-700',
|
||||||
color: 'bg-blue-700'
|
color: 'bg-blue-700'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'gatsby',
|
name: 'gatsby',
|
||||||
...defaultBuildAndDeploy,
|
|
||||||
publishDirectory: 'public',
|
|
||||||
port: 80,
|
|
||||||
fancyName: 'Gatsby',
|
fancyName: 'Gatsby',
|
||||||
hoverColor: 'hover:bg-blue-700',
|
hoverColor: 'hover:bg-blue-700',
|
||||||
color: 'bg-blue-700'
|
color: 'bg-blue-700'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'vuejs',
|
name: 'vuejs',
|
||||||
...defaultBuildAndDeploy,
|
|
||||||
publishDirectory: 'dist',
|
|
||||||
port: 80,
|
|
||||||
fancyName: 'VueJS',
|
fancyName: 'VueJS',
|
||||||
hoverColor: 'hover:bg-green-700',
|
hoverColor: 'hover:bg-green-700',
|
||||||
color: 'bg-green-700'
|
color: 'bg-green-700'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'nuxtjs',
|
name: 'nuxtjs',
|
||||||
...defaultBuildAndDeploy,
|
|
||||||
port: 3000,
|
|
||||||
fancyName: 'NuxtJS',
|
fancyName: 'NuxtJS',
|
||||||
hoverColor: 'hover:bg-green-700',
|
hoverColor: 'hover:bg-green-700',
|
||||||
color: 'bg-green-700'
|
color: 'bg-green-700'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'preact',
|
name: 'preact',
|
||||||
...defaultBuildAndDeploy,
|
|
||||||
publishDirectory: 'build',
|
|
||||||
port: 80,
|
|
||||||
fancyName: 'Preact',
|
fancyName: 'Preact',
|
||||||
hoverColor: 'hover:bg-blue-700',
|
hoverColor: 'hover:bg-blue-700',
|
||||||
color: 'bg-blue-700'
|
color: 'bg-blue-700'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'php',
|
name: 'php',
|
||||||
port: 80,
|
|
||||||
fancyName: 'PHP',
|
fancyName: 'PHP',
|
||||||
hoverColor: 'hover:bg-indigo-700',
|
hoverColor: 'hover:bg-indigo-700',
|
||||||
color: 'bg-indigo-700'
|
color: 'bg-indigo-700'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'rust',
|
name: 'rust',
|
||||||
port: 3000,
|
|
||||||
fancyName: 'Rust',
|
fancyName: 'Rust',
|
||||||
hoverColor: 'hover:bg-pink-700',
|
hoverColor: 'hover:bg-pink-700',
|
||||||
color: 'bg-pink-700'
|
color: 'bg-pink-700'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
import { removeProxyConfiguration, removeWwwRedirection } from '$lib/haproxy';
|
import { removeProxyConfiguration } from '$lib/haproxy';
|
||||||
import { asyncExecShell, getEngine } from '$lib/common';
|
import { asyncExecShell, getEngine } from '$lib/common';
|
||||||
|
|
||||||
import { getDomain, removeDestinationDocker } from '$lib/common';
|
import { getDomain, removeDestinationDocker } from '$lib/common';
|
||||||
@@ -209,10 +209,10 @@ export async function configureApplication({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setApplicationSettings({ id, debug, previews }) {
|
export async function setApplicationSettings({ id, debug, previews, dualCerts }) {
|
||||||
return await prisma.application.update({
|
return await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { settings: { update: { debug, previews } } },
|
data: { settings: { update: { debug, previews, dualCerts } } },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,22 +15,41 @@ export async function isDockerNetworkExists({ network }) {
|
|||||||
return await prisma.destinationDocker.findFirst({ where: { network } });
|
return await prisma.destinationDocker.findFirst({ where: { network } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isSecretExists({ id, name }) {
|
export async function isSecretExists({ id, name, isPRMRSecret }) {
|
||||||
return await prisma.secret.findFirst({ where: { name, applicationId: id } });
|
return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isDomainConfigured({ id, fqdn }) {
|
export async function isDomainConfigured({ id, fqdn }) {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
|
const nakedDomain = domain.replace('www.', '');
|
||||||
const foundApp = await prisma.application.findFirst({
|
const foundApp = await prisma.application.findFirst({
|
||||||
where: { fqdn: { endsWith: `//${domain}` }, id: { not: id } },
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ fqdn: { endsWith: `//${nakedDomain}` } },
|
||||||
|
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
|
||||||
|
],
|
||||||
|
id: { not: id }
|
||||||
|
},
|
||||||
select: { fqdn: true }
|
select: { fqdn: true }
|
||||||
});
|
});
|
||||||
const foundService = await prisma.service.findFirst({
|
const foundService = await prisma.service.findFirst({
|
||||||
where: { fqdn: { endsWith: `//${domain}` }, id: { not: id } },
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ fqdn: { endsWith: `//${nakedDomain}` } },
|
||||||
|
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
|
||||||
|
],
|
||||||
|
id: { not: id }
|
||||||
|
},
|
||||||
select: { fqdn: true }
|
select: { fqdn: true }
|
||||||
});
|
});
|
||||||
const coolifyFqdn = await prisma.setting.findFirst({
|
const coolifyFqdn = await prisma.setting.findFirst({
|
||||||
where: { fqdn: { endsWith: `//${domain}` }, id: { not: id } },
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ fqdn: { endsWith: `//${nakedDomain}` } },
|
||||||
|
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
|
||||||
|
],
|
||||||
|
id: { not: id }
|
||||||
|
},
|
||||||
select: { fqdn: true }
|
select: { fqdn: true }
|
||||||
});
|
});
|
||||||
if (foundApp || foundService || coolifyFqdn) return true;
|
if (foundApp || foundService || coolifyFqdn) return true;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { dev } from '$app/env';
|
|||||||
import { sentry } from '$lib/common';
|
import { sentry } from '$lib/common';
|
||||||
import * as Prisma from '@prisma/client';
|
import * as Prisma from '@prisma/client';
|
||||||
import { default as ProdPrisma } from '@prisma/client';
|
import { default as ProdPrisma } from '@prisma/client';
|
||||||
|
import type { PrismaClientOptions } from '@prisma/client/runtime';
|
||||||
import generator from 'generate-password';
|
import generator from 'generate-password';
|
||||||
import forge from 'node-forge';
|
import forge from 'node-forge';
|
||||||
|
|
||||||
@@ -19,28 +20,20 @@ if (!dev) {
|
|||||||
PrismaClient = ProdPrisma.PrismaClient;
|
PrismaClient = ProdPrisma.PrismaClient;
|
||||||
P = ProdPrisma.Prisma;
|
P = ProdPrisma.Prisma;
|
||||||
}
|
}
|
||||||
let prismaOptions = {
|
|
||||||
|
export const prisma = new PrismaClient({
|
||||||
|
errorFormat: 'pretty',
|
||||||
rejectOnNotFound: false
|
rejectOnNotFound: false
|
||||||
};
|
});
|
||||||
if (dev) {
|
|
||||||
prismaOptions = {
|
|
||||||
errorFormat: 'pretty',
|
|
||||||
rejectOnNotFound: false,
|
|
||||||
log: [
|
|
||||||
{
|
|
||||||
emit: 'event',
|
|
||||||
level: 'query'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export const prisma = new PrismaClient(prismaOptions);
|
|
||||||
|
|
||||||
export function ErrorHandler(e) {
|
export function ErrorHandler(e) {
|
||||||
if (e! instanceof Error) {
|
if (e! instanceof Error) {
|
||||||
e = new Error(e.toString());
|
e = new Error(e.toString());
|
||||||
}
|
}
|
||||||
let truncatedError = e;
|
let truncatedError = e;
|
||||||
|
if (e.stdout) {
|
||||||
|
truncatedError = e.stdout;
|
||||||
|
}
|
||||||
if (e.message?.includes('docker run')) {
|
if (e.message?.includes('docker run')) {
|
||||||
let truncatedArray = [];
|
let truncatedArray = [];
|
||||||
truncatedArray = truncatedError.message.split('-').filter((line) => {
|
truncatedArray = truncatedError.message.split('-').filter((line) => {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
import { dockerInstance } from '$lib/docker';
|
import * as db from '$lib/database';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import { generatePassword } from '.';
|
import { generatePassword } from '.';
|
||||||
import { prisma, ErrorHandler } from './common';
|
import { prisma, ErrorHandler } from './common';
|
||||||
import getPort from 'get-port';
|
import getPort, { portNumbers } from 'get-port';
|
||||||
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
|
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
|
||||||
|
|
||||||
export async function listDatabases(teamId) {
|
export async function listDatabases(teamId) {
|
||||||
@@ -16,24 +16,9 @@ export async function newDatabase({ name, teamId }) {
|
|||||||
const rootUserPassword = encrypt(generatePassword());
|
const rootUserPassword = encrypt(generatePassword());
|
||||||
const defaultDatabase = cuid();
|
const defaultDatabase = cuid();
|
||||||
|
|
||||||
let publicPort = await getPort();
|
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
const usedPorts = await prisma.database.findMany({ where: { publicPort } });
|
|
||||||
if (usedPorts.length === 0) break;
|
|
||||||
publicPort = await getPort();
|
|
||||||
i++;
|
|
||||||
} while (i < 10);
|
|
||||||
if (i === 9) {
|
|
||||||
throw {
|
|
||||||
error: 'No free port found!? Is it possible?'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return await prisma.database.create({
|
return await prisma.database.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
publicPort,
|
|
||||||
defaultDatabase,
|
defaultDatabase,
|
||||||
dbUser,
|
dbUser,
|
||||||
dbUserPassword,
|
dbUserPassword,
|
||||||
|
|||||||
@@ -1,19 +1,41 @@
|
|||||||
import { encrypt } from '$lib/crypto';
|
import { encrypt, decrypt } from '$lib/crypto';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
|
||||||
export async function listSecrets({ applicationId }) {
|
export async function listSecrets(applicationId: string) {
|
||||||
return await prisma.secret.findMany({
|
let secrets = await prisma.secret.findMany({
|
||||||
where: { applicationId },
|
where: { applicationId },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' }
|
||||||
select: { id: true, createdAt: true, name: true, isBuildSecret: true }
|
});
|
||||||
|
secrets = secrets.map((secret) => {
|
||||||
|
secret.value = decrypt(secret.value);
|
||||||
|
return secret;
|
||||||
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
||||||
|
value = encrypt(value);
|
||||||
|
return await prisma.secret.create({
|
||||||
|
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createSecret({ id, name, value, isBuildSecret }) {
|
export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
||||||
value = encrypt(value);
|
value = encrypt(value);
|
||||||
return await prisma.secret.create({
|
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
||||||
data: { name, value, isBuildSecret, application: { connect: { id } } }
|
console.log(found);
|
||||||
});
|
|
||||||
|
if (found) {
|
||||||
|
return await prisma.secret.updateMany({
|
||||||
|
where: { applicationId: id, name, isPRMRSecret },
|
||||||
|
data: { value, isBuildSecret, isPRMRSecret }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return await prisma.secret.create({
|
||||||
|
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeSecret({ id, name }) {
|
export async function removeSecret({ id, name }) {
|
||||||
|
|||||||
@@ -107,13 +107,20 @@ export async function configureServiceType({ id, type }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function setService({ id, version }) {
|
export async function setServiceVersion({ id, version }) {
|
||||||
return await prisma.service.update({
|
return await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { version }
|
data: { version }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setServiceSettings({ id, dualCerts }) {
|
||||||
|
return await prisma.service.update({
|
||||||
|
where: { id },
|
||||||
|
data: { dualCerts }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function updatePlausibleAnalyticsService({ id, fqdn, email, username, name }) {
|
export async function updatePlausibleAnalyticsService({ id, fqdn, email, username, name }) {
|
||||||
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
|
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
|
||||||
await prisma.service.update({ where: { id }, data: { name, fqdn } });
|
await prisma.service.update({ where: { id }, data: { name, fqdn } });
|
||||||
|
|||||||
@@ -6,19 +6,23 @@ import { asyncExecShell, uniqueName } from '$lib/common';
|
|||||||
|
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { startCoolifyProxy } from '$lib/haproxy';
|
import { startCoolifyProxy } from '$lib/haproxy';
|
||||||
|
export async function hashPassword(password: string) {
|
||||||
export async function login({ email, password }) {
|
|
||||||
const saltRounds = 15;
|
const saltRounds = 15;
|
||||||
|
return bcrypt.hash(password, saltRounds);
|
||||||
|
}
|
||||||
|
export async function login({ email, password }) {
|
||||||
const users = await prisma.user.count();
|
const users = await prisma.user.count();
|
||||||
const userFound = await prisma.user.findUnique({
|
const userFound = await prisma.user.findUnique({
|
||||||
where: { email },
|
where: { email },
|
||||||
include: { teams: true },
|
include: { teams: true, permission: true },
|
||||||
rejectOnNotFound: false
|
rejectOnNotFound: false
|
||||||
});
|
});
|
||||||
// Registration disabled if database is not seeded properly
|
// Registration disabled if database is not seeded properly
|
||||||
const { isRegistrationEnabled, id } = await db.listSettings();
|
const { isRegistrationEnabled, id } = await db.listSettings();
|
||||||
|
|
||||||
let uid = cuid();
|
let uid = cuid();
|
||||||
|
let permission = 'read';
|
||||||
|
let isAdmin = false;
|
||||||
// Disable registration if we are registering the first user.
|
// Disable registration if we are registering the first user.
|
||||||
if (users === 0) {
|
if (users === 0) {
|
||||||
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
|
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
|
||||||
@@ -50,6 +54,8 @@ export async function login({ email, password }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
uid = userFound.id;
|
uid = userFound.id;
|
||||||
|
// permission = userFound.permission;
|
||||||
|
isAdmin = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If registration disabled, return 403
|
// If registration disabled, return 403
|
||||||
@@ -59,8 +65,10 @@ export async function login({ email, password }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
const hashedPassword = await hashPassword(password);
|
||||||
if (users === 0) {
|
if (users === 0) {
|
||||||
|
permission = 'owner';
|
||||||
|
isAdmin = true;
|
||||||
await prisma.user.create({
|
await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
id: uid,
|
id: uid,
|
||||||
@@ -103,8 +111,10 @@ export async function login({ email, password }) {
|
|||||||
'Set-Cookie': `teamId=${uid}; HttpOnly; Path=/; Max-Age=15778800;`
|
'Set-Cookie': `teamId=${uid}; HttpOnly; Path=/; Max-Age=15778800;`
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
uid,
|
userId: uid,
|
||||||
teamId: uid
|
teamId: uid,
|
||||||
|
permission,
|
||||||
|
isAdmin
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,15 +13,24 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
|||||||
installCommand,
|
installCommand,
|
||||||
buildCommand,
|
buildCommand,
|
||||||
debug,
|
debug,
|
||||||
secrets
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
} = data;
|
} = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||||
Dockerfile.push('WORKDIR /usr/src/app');
|
Dockerfile.push('WORKDIR /usr/src/app');
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (!secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { dev } from '$app/env';
|
|||||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { letsEncrypt } from '$lib/letsencrypt';
|
|
||||||
|
|
||||||
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
||||||
|
|
||||||
@@ -49,7 +48,8 @@ export async function completeTransaction(transactionId) {
|
|||||||
return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
|
return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeProxyConfiguration({ domain }) {
|
export async function removeProxyConfiguration(fqdn) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
const backendFound = await haproxy
|
const backendFound = await haproxy
|
||||||
.get(`v2/services/haproxy/configuration/backends/${domain}`)
|
.get(`v2/services/haproxy/configuration/backends/${domain}`)
|
||||||
@@ -65,97 +65,92 @@ export async function removeProxyConfiguration({ domain }) {
|
|||||||
.json();
|
.json();
|
||||||
await completeTransaction(transactionId);
|
await completeTransaction(transactionId);
|
||||||
}
|
}
|
||||||
await removeWwwRedirection(domain);
|
await forceSSLOffApplication(domain);
|
||||||
|
await removeWwwRedirection(fqdn);
|
||||||
}
|
}
|
||||||
export async function forceSSLOffApplication({ domain }) {
|
export async function forceSSLOffApplication(domain) {
|
||||||
if (!dev) {
|
const haproxy = await haproxyInstance();
|
||||||
const haproxy = await haproxyInstance();
|
await checkHAProxy(haproxy);
|
||||||
await checkHAProxy(haproxy);
|
let transactionId;
|
||||||
let transactionId;
|
try {
|
||||||
try {
|
const rules: any = await haproxy
|
||||||
const rules: any = await haproxy
|
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
searchParams: {
|
||||||
searchParams: {
|
parent_name: 'http',
|
||||||
parent_name: 'http',
|
parent_type: 'frontend'
|
||||||
parent_type: 'frontend'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.json();
|
|
||||||
if (rules.data.length > 0) {
|
|
||||||
const rule = rules.data.find((rule) => rule.cond_test.includes(`-i ${domain}`));
|
|
||||||
if (rule) {
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.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 {
|
|
||||||
if (transactionId) await completeTransaction(transactionId);
|
|
||||||
}
|
}
|
||||||
} else {
|
} catch (error) {
|
||||||
console.log(`[DEBUG] Removing ssl for ${domain}`);
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
if (transactionId) await completeTransaction(transactionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function forceSSLOnApplication({ domain }) {
|
export async function forceSSLOnApplication(domain) {
|
||||||
if (!dev) {
|
const haproxy = await haproxyInstance();
|
||||||
const haproxy = await haproxyInstance();
|
await checkHAProxy(haproxy);
|
||||||
await checkHAProxy(haproxy);
|
let transactionId;
|
||||||
let transactionId;
|
try {
|
||||||
try {
|
const rules: any = await haproxy
|
||||||
const rules: any = await haproxy
|
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
searchParams: {
|
||||||
searchParams: {
|
parent_name: 'http',
|
||||||
parent_name: 'http',
|
parent_type: 'frontend'
|
||||||
parent_type: 'frontend'
|
}
|
||||||
}
|
})
|
||||||
})
|
.json();
|
||||||
.json();
|
let nextRule = 0;
|
||||||
let nextRule = 0;
|
if (rules.data.length > 0) {
|
||||||
if (rules.data.length > 0) {
|
const rule = rules.data.find((rule) =>
|
||||||
const rule = rules.data.find((rule) =>
|
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
|
||||||
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
|
);
|
||||||
);
|
if (rule) return;
|
||||||
if (rule) return;
|
nextRule = rules.data[rules.data.length - 1].index + 1;
|
||||||
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: `{ 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 {
|
|
||||||
if (transactionId) await completeTransaction(transactionId);
|
|
||||||
}
|
}
|
||||||
} else {
|
transactionId = await getNextTransactionId();
|
||||||
console.log(`[DEBUG] Adding ssl for ${domain}`);
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,6 +269,7 @@ export async function configureProxyForApplication({ domain, imageId, applicatio
|
|||||||
|
|
||||||
export async function configureCoolifyProxyOff(fqdn) {
|
export async function configureCoolifyProxyOff(fqdn) {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
|
|
||||||
@@ -288,10 +284,8 @@ export async function configureCoolifyProxyOff(fqdn) {
|
|||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
await completeTransaction(transactionId);
|
await completeTransaction(transactionId);
|
||||||
if (!dev) {
|
if (isHttps) await forceSSLOffApplication(domain);
|
||||||
await forceSSLOffApplication({ domain });
|
await removeWwwRedirection(fqdn);
|
||||||
}
|
|
||||||
await setWwwRedirection(fqdn);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error?.response?.body || error;
|
throw error?.response?.body || error;
|
||||||
}
|
}
|
||||||
@@ -565,7 +559,8 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
|
|||||||
await completeTransaction(transactionId);
|
await completeTransaction(transactionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureSimpleServiceProxyOff({ domain }) {
|
export async function configureSimpleServiceProxyOff(fqdn) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
try {
|
try {
|
||||||
@@ -580,11 +575,16 @@ export async function configureSimpleServiceProxyOff({ domain }) {
|
|||||||
.json();
|
.json();
|
||||||
await completeTransaction(transactionId);
|
await completeTransaction(transactionId);
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
await removeWwwRedirection(domain);
|
await forceSSLOffApplication(domain);
|
||||||
|
await removeWwwRedirection(fqdn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeWwwRedirection(domain) {
|
export async function removeWwwRedirection(fqdn) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||||
|
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy();
|
await checkHAProxy();
|
||||||
const rules: any = await haproxy
|
const rules: any = await haproxy
|
||||||
@@ -596,9 +596,7 @@ export async function removeWwwRedirection(domain) {
|
|||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
if (rules.data.length > 0) {
|
if (rules.data.length > 0) {
|
||||||
const rule = rules.data.find((rule) =>
|
const rule = rules.data.find((rule) => rule.redir_value.includes(redirectValue));
|
||||||
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
|
|
||||||
);
|
|
||||||
if (rule) {
|
if (rule) {
|
||||||
const transactionId = await getNextTransactionId();
|
const transactionId = await getNextTransactionId();
|
||||||
await haproxy
|
await haproxy
|
||||||
@@ -623,6 +621,7 @@ export async function setWwwRedirection(fqdn) {
|
|||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
|
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||||
const contTest = `{ req.hdr(host) -i ${isWWW ? domain.replace('www.', '') : `www.${domain}`} }`;
|
const contTest = `{ req.hdr(host) -i ${isWWW ? domain.replace('www.', '') : `www.${domain}`} }`;
|
||||||
const rules: any = await haproxy
|
const rules: any = await haproxy
|
||||||
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
@@ -634,13 +633,11 @@ export async function setWwwRedirection(fqdn) {
|
|||||||
.json();
|
.json();
|
||||||
let nextRule = 0;
|
let nextRule = 0;
|
||||||
if (rules.data.length > 0) {
|
if (rules.data.length > 0) {
|
||||||
const rule = rules.data.find((rule) =>
|
const rule = rules.data.find((rule) => rule.redir_value.includes(redirectValue));
|
||||||
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
|
|
||||||
);
|
|
||||||
if (rule) return;
|
if (rule) return;
|
||||||
nextRule = rules.data[rules.data.length - 1].index + 1;
|
nextRule = rules.data[rules.data.length - 1].index + 1;
|
||||||
}
|
}
|
||||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
|
||||||
transactionId = await getNextTransactionId();
|
transactionId = await getNextTransactionId();
|
||||||
await haproxy
|
await haproxy
|
||||||
.post(`v2/services/haproxy/configuration/http_request_rules`, {
|
.post(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
|
|||||||
@@ -1,50 +1,79 @@
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import { forceSSLOffApplication, forceSSLOnApplication, getNextTransactionId } from '$lib/haproxy';
|
import { forceSSLOffApplication, forceSSLOnApplication } from '$lib/haproxy';
|
||||||
import { asyncExecShell, getEngine } from './common';
|
import { asyncExecShell, getEngine } from './common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
|
import getPort, { portNumbers } from 'get-port';
|
||||||
|
|
||||||
export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
|
export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
|
||||||
try {
|
try {
|
||||||
const randomCuid = cuid();
|
const data = await db.prisma.setting.findFirst();
|
||||||
if (dev) {
|
const { minPort, maxPort } = data;
|
||||||
return await forceSSLOnApplication({ domain });
|
|
||||||
} else {
|
|
||||||
if (isCoolify) {
|
|
||||||
await asyncExecShell(
|
|
||||||
`docker run --rm --name certbot-${randomCuid} -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(
|
const nakedDomain = domain.replace('www.', '');
|
||||||
`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`
|
const wwwDomain = `www.${nakedDomain}`;
|
||||||
);
|
const randomCuid = cuid();
|
||||||
if (stderr) throw new Error(stderr);
|
const randomPort = await getPort({ port: portNumbers(minPort, maxPort) });
|
||||||
return;
|
|
||||||
|
let host;
|
||||||
|
let dualCerts = false;
|
||||||
|
if (isCoolify) {
|
||||||
|
dualCerts = data.dualCerts;
|
||||||
|
host = 'unix:///var/run/docker.sock';
|
||||||
|
} else {
|
||||||
|
const applicationData = await db.prisma.application.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: { destinationDocker: true, settings: true }
|
||||||
|
});
|
||||||
|
if (applicationData) {
|
||||||
|
if (applicationData?.destinationDockerId && applicationData?.destinationDocker) {
|
||||||
|
host = getEngine(applicationData.destinationDocker.engine);
|
||||||
|
}
|
||||||
|
if (applicationData?.settings?.dualCerts) {
|
||||||
|
dualCerts = applicationData.settings.dualCerts;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let data: any = await db.prisma.application.findUnique({
|
// Check Service
|
||||||
|
const serviceData = await db.prisma.service.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
if (!data) {
|
if (serviceData) {
|
||||||
data = await db.prisma.service.findUnique({
|
if (serviceData?.destinationDockerId && serviceData?.destinationDocker) {
|
||||||
where: { id },
|
host = getEngine(serviceData.destinationDocker.engine);
|
||||||
include: { destinationDocker: true }
|
}
|
||||||
});
|
if (serviceData?.dualCerts) {
|
||||||
}
|
dualCerts = serviceData.dualCerts;
|
||||||
// Set SSL with Let's encrypt
|
}
|
||||||
if (data.destinationDockerId && data.destinationDocker) {
|
|
||||||
const host = getEngine(data.destinationDocker.engine);
|
|
||||||
await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -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-${randomCuid} -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
|
|
||||||
);
|
|
||||||
if (stderr) throw new Error(stderr);
|
|
||||||
await forceSSLOnApplication({ domain });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await forceSSLOffApplication(domain);
|
||||||
|
if (dualCerts) {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||||
|
dev ? '--test-cert' : ''
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "test -d /etc/letsencrypt/live/${nakedDomain}/ && cat /etc/letsencrypt/live/${nakedDomain}/fullchain.pem /etc/letsencrypt/live/${nakedDomain}/privkey.pem > /app/ssl/${nakedDomain}.pem || cat /etc/letsencrypt/live/${wwwDomain}/fullchain.pem /etc/letsencrypt/live/${wwwDomain}/privkey.pem > /app/ssl/${wwwDomain}.pem"`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${domain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||||
|
dev ? '--test-cert' : ''
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem"`
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
if (error.code !== 0) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (!isCoolify) {
|
||||||
|
await forceSSLOnApplication(domain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ export default async function (job) {
|
|||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
destinationType = 'docker';
|
destinationType = 'docker';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destinationType === 'docker') {
|
if (destinationType === 'docker') {
|
||||||
const docker = dockerInstance({ destinationDocker });
|
const docker = dockerInstance({ destinationDocker });
|
||||||
const host = getEngine(destinationDocker.engine);
|
const host = getEngine(destinationDocker.engine);
|
||||||
@@ -205,7 +204,15 @@ export default async function (job) {
|
|||||||
const envs = [];
|
const envs = [];
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||||
@@ -239,6 +246,8 @@ export default async function (job) {
|
|||||||
if (stderr) console.log(stderr);
|
if (stderr) console.log(stderr);
|
||||||
saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
saveBuildLog({ line: error, buildId, applicationId });
|
||||||
|
sentry.captureException(error);
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -257,7 +266,9 @@ export default async function (job) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
saveBuildLog({ line: error.stdout || error, buildId, applicationId });
|
||||||
sentry.captureException(error);
|
sentry.captureException(error);
|
||||||
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ buildWorker.on('completed', async (job: Bullmq.Job) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
|
buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
|
||||||
console.log(failedReason);
|
|
||||||
try {
|
try {
|
||||||
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } });
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -136,7 +135,11 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
|
|||||||
const workdir = `/tmp/build-sources/${job.data.repository}`;
|
const workdir = `/tmp/build-sources/${job.data.repository}`;
|
||||||
await asyncExecShell(`rm -fr ${workdir}`);
|
await asyncExecShell(`rm -fr ${workdir}`);
|
||||||
}
|
}
|
||||||
saveBuildLog({ line: 'Failed build!', buildId: job.data.build_id, applicationId: job.data.id });
|
saveBuildLog({
|
||||||
|
line: 'Failed to deploy!',
|
||||||
|
buildId: job.data.build_id,
|
||||||
|
applicationId: job.data.id
|
||||||
|
});
|
||||||
saveBuildLog({
|
saveBuildLog({
|
||||||
line: `Reason: ${failedReason.toString()}`,
|
line: `Reason: ${failedReason.toString()}`,
|
||||||
buildId: job.data.build_id,
|
buildId: job.data.build_id,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default async function () {
|
|||||||
port
|
port
|
||||||
});
|
});
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
if (isHttps) await forceSSLOnApplication({ domain });
|
if (isHttps) await forceSSLOnApplication(domain);
|
||||||
await setWwwRedirection(fqdn);
|
await setWwwRedirection(fqdn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,10 +98,9 @@ export default async function () {
|
|||||||
await configureCoolifyProxyOn(fqdn);
|
await configureCoolifyProxyOn(fqdn);
|
||||||
await setWwwRedirection(fqdn);
|
await setWwwRedirection(fqdn);
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
if (isHttps) await forceSSLOnApplication({ domain });
|
if (isHttps) await forceSSLOnApplication(domain);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,16 @@ import { dockerInstance } from '$lib/docker';
|
|||||||
import { forceSSLOnApplication } from '$lib/haproxy';
|
import { forceSSLOnApplication } from '$lib/haproxy';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
|
import getPort, { portNumbers } from 'get-port';
|
||||||
|
import cuid from 'cuid';
|
||||||
|
|
||||||
export default async function () {
|
export default async function () {
|
||||||
try {
|
try {
|
||||||
|
const data = await db.prisma.setting.findFirst();
|
||||||
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
|
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
|
||||||
|
const randomCuid = cuid();
|
||||||
const destinationDockers = await prisma.destinationDocker.findMany({});
|
const destinationDockers = await prisma.destinationDocker.findMany({});
|
||||||
for (const destination of destinationDockers) {
|
for (const destination of destinationDockers) {
|
||||||
if (destination.isCoolifyProxyUsed) {
|
if (destination.isCoolifyProxyUsed) {
|
||||||
@@ -30,10 +37,10 @@ export default async function () {
|
|||||||
} else {
|
} else {
|
||||||
const host = getEngine(destination.engine);
|
const host = getEngine(destination.engine);
|
||||||
await asyncExecShell(
|
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`
|
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${publicPort} -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 ${publicPort} -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
|
||||||
);
|
);
|
||||||
const { stderr } = await asyncExecShell(
|
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`
|
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
|
||||||
);
|
);
|
||||||
if (stderr) throw new Error(stderr);
|
if (stderr) throw new Error(stderr);
|
||||||
}
|
}
|
||||||
@@ -52,7 +59,7 @@ export default async function () {
|
|||||||
console.log('DEV MODE: SSL is enabled');
|
console.log('DEV MODE: SSL is enabled');
|
||||||
} else {
|
} else {
|
||||||
await asyncExecShell(
|
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`
|
`docker run --rm --name certbot-${randomCuid} -p 9080:${publicPort} -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 ${publicPort} -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
|
||||||
);
|
);
|
||||||
|
|
||||||
const { stderr } = await asyncExecShell(
|
const { stderr } = await asyncExecShell(
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
export const publicPaths = [
|
export const publicPaths = [
|
||||||
'/login',
|
'/login',
|
||||||
|
'/reset',
|
||||||
|
'/reset/password',
|
||||||
'/webhooks/success',
|
'/webhooks/success',
|
||||||
'/webhooks/github',
|
'/webhooks/github',
|
||||||
'/webhooks/github/install',
|
'/webhooks/github/install',
|
||||||
|
|||||||
6
src/lib/store.ts
Normal file
6
src/lib/store.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export const gitTokens = writable({
|
||||||
|
githubToken: null,
|
||||||
|
gitlabToken: null
|
||||||
|
});
|
||||||
@@ -2,14 +2,14 @@
|
|||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
import { publicPaths } from '$lib/settings';
|
import { publicPaths } from '$lib/settings';
|
||||||
|
|
||||||
export const load: Load = async ({ fetch, url, params, session }) => {
|
export const load: Load = async ({ fetch, url, session }) => {
|
||||||
if (!session.uid && !publicPaths.includes(url.pathname)) {
|
if (!session.userId && !publicPaths.includes(url.pathname)) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
redirect: '/login'
|
redirect: '/login'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!session.uid) {
|
if (!session.userId) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const endpoint = `/teams.json`;
|
const endpoint = `/teams.json`;
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
};
|
};
|
||||||
let latestVersion = 'latest';
|
let latestVersion = 'latest';
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($session.uid) {
|
if ($session.userId) {
|
||||||
const overrideVersion = browser && window.localStorage.getItem('latestVersion');
|
const overrideVersion = browser && window.localStorage.getItem('latestVersion');
|
||||||
try {
|
try {
|
||||||
await get(`/login.json`);
|
await get(`/login.json`);
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
async function switchTeam() {
|
async function switchTeam() {
|
||||||
try {
|
try {
|
||||||
await post(`/index.json?from=${$page.url.pathname}`, {
|
await post(`/dashboard.json?from=${$page.url.pathname}`, {
|
||||||
cookie: 'teamId',
|
cookie: 'teamId',
|
||||||
value: selectedTeamId
|
value: selectedTeamId
|
||||||
});
|
});
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
updateStatus.loading = true;
|
updateStatus.loading = true;
|
||||||
try {
|
try {
|
||||||
await post(`/update.json`, { type: 'update', latestVersion });
|
await post(`/update.json`, { type: 'update', latestVersion });
|
||||||
toast.push('Update completed.<br>Waiting for the new version to start...');
|
toast.push('Update completed.<br><br>Waiting for the new version to start...');
|
||||||
let reachable = false;
|
let reachable = false;
|
||||||
let tries = 0;
|
let tries = 0;
|
||||||
do {
|
do {
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
<title>Coolify</title>
|
<title>Coolify</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
|
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
|
||||||
{#if $session.uid}
|
{#if $session.userId}
|
||||||
<nav class="nav-main">
|
<nav class="nav-main">
|
||||||
<div class="flex h-screen w-full flex-col items-center transition-all duration-100">
|
<div class="flex h-screen w-full flex-col items-center transition-all duration-100">
|
||||||
<div class="my-4 h-10 w-10"><img src="/favicon.png" alt="coolLabs logo" /></div>
|
<div class="my-4 h-10 w-10"><img src="/favicon.png" alt="coolLabs logo" /></div>
|
||||||
@@ -444,7 +444,8 @@
|
|||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col space-y-4 py-2">
|
||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/teams"
|
href="/teams"
|
||||||
@@ -519,20 +520,20 @@
|
|||||||
<path d="M7 12h14l-3 -3m0 6l3 -3" />
|
<path d="M7 12h14l-3 -3m0 6l3 -3" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div
|
||||||
<div
|
class="w-full text-center font-bold text-stone-400 hover:bg-coolgray-200 hover:text-white"
|
||||||
class="w-full text-center font-bold text-stone-400 hover:bg-coolgray-200 hover:text-white"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
class="text-[10px] no-underline"
|
|
||||||
href={`https://github.com/coollabsio/coolify/releases/tag/v${$session.version}`}
|
|
||||||
target="_blank">v{$session.version}</a
|
|
||||||
>
|
>
|
||||||
|
<a
|
||||||
|
class="text-[10px] no-underline"
|
||||||
|
href={`https://github.com/coollabsio/coolify/releases/tag/v${$session.version}`}
|
||||||
|
target="_blank">v{$session.version}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<select
|
<select
|
||||||
class="fixed right-0 bottom-0 z-50 m-2 p-2 px-4"
|
class="fixed right-0 bottom-0 z-50 m-2 w-64 bg-opacity-30 p-2 px-4"
|
||||||
bind:value={selectedTeamId}
|
bind:value={selectedTeamId}
|
||||||
on:change={switchTeam}
|
on:change={switchTeam}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -17,13 +17,20 @@
|
|||||||
const endpoint = `/applications/${params.id}.json`;
|
const endpoint = `/applications/${params.id}.json`;
|
||||||
const res = await fetch(endpoint);
|
const res = await fetch(endpoint);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const { application, githubToken, ghToken, isRunning, appId } = await res.json();
|
let { application, isRunning, appId, githubToken, gitlabToken } = await res.json();
|
||||||
if (!application || Object.entries(application).length === 0) {
|
if (!application || Object.entries(application).length === 0) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
redirect: '/applications'
|
redirect: '/applications'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (application.gitSource?.githubAppId && !githubToken) {
|
||||||
|
const response = await fetch(`/applications/${params.id}/configuration/githubToken.json`);
|
||||||
|
if (response.ok) {
|
||||||
|
const { token } = await response.json();
|
||||||
|
githubToken = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
const configurationPhase = checkConfiguration(application);
|
const configurationPhase = checkConfiguration(application);
|
||||||
if (
|
if (
|
||||||
configurationPhase &&
|
configurationPhase &&
|
||||||
@@ -38,12 +45,12 @@
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
application,
|
application,
|
||||||
isRunning
|
isRunning,
|
||||||
|
githubToken,
|
||||||
|
gitlabToken
|
||||||
},
|
},
|
||||||
stuff: {
|
stuff: {
|
||||||
isRunning,
|
isRunning,
|
||||||
ghToken,
|
|
||||||
githubToken,
|
|
||||||
application,
|
application,
|
||||||
appId
|
appId
|
||||||
}
|
}
|
||||||
@@ -60,12 +67,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let application;
|
export let application;
|
||||||
export let isRunning;
|
export let isRunning;
|
||||||
|
export let githubToken;
|
||||||
|
export let gitlabToken;
|
||||||
import { page, session } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||||
import Loading from '$lib/components/Loading.svelte';
|
import Loading from '$lib/components/Loading.svelte';
|
||||||
import { del, post } from '$lib/api';
|
import { del, post } from '$lib/api';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { gitTokens } from '$lib/store';
|
||||||
|
|
||||||
|
if (githubToken) $gitTokens.githubToken = githubToken;
|
||||||
|
if (gitlabToken) $gitTokens.gitlabToken = gitlabToken;
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const found = await db.isDomainConfigured({ id, fqdn });
|
const found = await db.isDomainConfigured({ id, fqdn });
|
||||||
if (found) {
|
if (found) {
|
||||||
throw {
|
throw {
|
||||||
message: `Domain ${getDomain(fqdn)} is already configured.`
|
message: `Domain ${getDomain(fqdn).replace('www.', '')} is already used.`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
|
import { findBuildPack } from '$lib/components/templates';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
@@ -11,10 +12,13 @@
|
|||||||
export let buildPack;
|
export let buildPack;
|
||||||
export let foundConfig;
|
export let foundConfig;
|
||||||
export let scanning;
|
export let scanning;
|
||||||
|
export let packageManager;
|
||||||
|
|
||||||
async function handleSubmit(name) {
|
async function handleSubmit(name) {
|
||||||
try {
|
try {
|
||||||
const tempBuildPack = JSON.parse(JSON.stringify(buildPack));
|
const tempBuildPack = JSON.parse(
|
||||||
|
JSON.stringify(findBuildPack(buildPack.name, packageManager))
|
||||||
|
);
|
||||||
delete tempBuildPack.name;
|
delete tempBuildPack.name;
|
||||||
delete tempBuildPack.fancyName;
|
delete tempBuildPack.fancyName;
|
||||||
delete tempBuildPack.color;
|
delete tempBuildPack.color;
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
|
|
||||||
export let githubToken;
|
|
||||||
export let application;
|
export let application;
|
||||||
|
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { getGithubToken } from '$lib/components/common';
|
import { errorNotification } from '$lib/form';
|
||||||
import { enhance, errorNotification } from '$lib/form';
|
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { gitTokens } from '$lib/store';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
const from = $page.url.searchParams.get('from');
|
const from = $page.url.searchParams.get('from');
|
||||||
@@ -30,19 +28,12 @@
|
|||||||
branch: undefined
|
branch: undefined
|
||||||
};
|
};
|
||||||
let showSave = false;
|
let showSave = false;
|
||||||
let token = null;
|
|
||||||
|
|
||||||
async function loadRepositoriesByPage(page = 0) {
|
async function loadRepositoriesByPage(page = 0) {
|
||||||
try {
|
return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, {
|
||||||
return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, {
|
Authorization: `token ${$gitTokens.githubToken}`
|
||||||
Authorization: `token ${token}`
|
});
|
||||||
});
|
|
||||||
} catch ({ error }) {
|
|
||||||
return errorNotification(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
async function loadRepositories() {
|
async function loadRepositories() {
|
||||||
token = await getGithubToken({ apiUrl, githubToken, application });
|
|
||||||
let page = 1;
|
let page = 1;
|
||||||
let reposCount = 0;
|
let reposCount = 0;
|
||||||
const loadedRepos = await loadRepositoriesByPage();
|
const loadedRepos = await loadRepositoriesByPage();
|
||||||
@@ -63,7 +54,7 @@
|
|||||||
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
|
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
|
||||||
try {
|
try {
|
||||||
branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
|
branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
|
||||||
Authorization: `token ${token}`
|
Authorization: `token ${$gitTokens.githubToken}`
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
@@ -90,7 +81,47 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await loadRepositories();
|
try {
|
||||||
|
if (!$gitTokens.githubToken) {
|
||||||
|
const { token } = await get(`/applications/${id}/configuration/githubToken.json`);
|
||||||
|
$gitTokens.githubToken = token;
|
||||||
|
}
|
||||||
|
await loadRepositories();
|
||||||
|
} catch (error) {
|
||||||
|
if (
|
||||||
|
error.error === 'invalid_token' ||
|
||||||
|
error.error_description ===
|
||||||
|
'Token is expired. You can either do re-authorization or token refresh.' ||
|
||||||
|
error.message === '401 Unauthorized'
|
||||||
|
) {
|
||||||
|
if (application.gitSource.gitlabAppId) {
|
||||||
|
let htmlUrl = application.gitSource.htmlUrl;
|
||||||
|
const left = screen.width / 2 - 1020 / 2;
|
||||||
|
const top = screen.height / 2 - 618 / 2;
|
||||||
|
const newWindow = open(
|
||||||
|
`${htmlUrl}/oauth/authorize?client_id=${application.gitSource.gitlabApp.appId}&redirect_uri=${window.location.origin}/webhooks/gitlab&response_type=code&scope=api+email+read_repository&state=${$page.params.id}`,
|
||||||
|
'GitLab',
|
||||||
|
'resizable=1, scrollbars=1, fullscreen=0, height=618, width=1020,top=' +
|
||||||
|
top +
|
||||||
|
', left=' +
|
||||||
|
left +
|
||||||
|
', toolbar=0, menubar=0, status=0'
|
||||||
|
);
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (newWindow?.closed) {
|
||||||
|
clearInterval(timer);
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (error.message === 'Bad credentials') {
|
||||||
|
const { token } = await get(`/applications/${id}/configuration/githubToken.json`);
|
||||||
|
$gitTokens.githubToken = token;
|
||||||
|
return await loadRepositories();
|
||||||
|
}
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { del, get, post, put } from '$lib/api';
|
import { del, get, post, put } from '$lib/api';
|
||||||
|
import { gitTokens } from '$lib/store';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
const from = $page.url.searchParams.get('from');
|
const from = $page.url.searchParams.get('from');
|
||||||
@@ -36,13 +37,13 @@
|
|||||||
branch: undefined
|
branch: undefined
|
||||||
};
|
};
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (!$session.gitlabToken) {
|
if (!$gitTokens.gitlabToken) {
|
||||||
getGitlabToken();
|
getGitlabToken();
|
||||||
} else {
|
} else {
|
||||||
loading.base = true;
|
loading.base = true;
|
||||||
try {
|
try {
|
||||||
const user = await get(`${apiUrl}/v4/user`, {
|
const user = await get(`${apiUrl}/v4/user`, {
|
||||||
Authorization: `Bearer ${$session.gitlabToken}`
|
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||||
});
|
});
|
||||||
username = user.username;
|
username = user.username;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
groups = await get(`${apiUrl}/v4/groups?per_page=5000`, {
|
groups = await get(`${apiUrl}/v4/groups?per_page=5000`, {
|
||||||
Authorization: `Bearer ${$session.gitlabToken}`
|
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorNotification(error);
|
errorNotification(error);
|
||||||
@@ -88,7 +89,7 @@
|
|||||||
projects = await get(
|
projects = await get(
|
||||||
`${apiUrl}/v4/users/${selected.group.name}/projects?min_access_level=40&page=1&per_page=25&archived=false`,
|
`${apiUrl}/v4/users/${selected.group.name}/projects?min_access_level=40&page=1&per_page=25&archived=false`,
|
||||||
{
|
{
|
||||||
Authorization: `Bearer ${$session.gitlabToken}`
|
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -102,7 +103,7 @@
|
|||||||
projects = await get(
|
projects = await get(
|
||||||
`${apiUrl}/v4/groups/${selected.group.id}/projects?page=1&per_page=25&archived=false`,
|
`${apiUrl}/v4/groups/${selected.group.id}/projects?page=1&per_page=25&archived=false`,
|
||||||
{
|
{
|
||||||
Authorization: `Bearer ${$session.gitlabToken}`
|
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -120,7 +121,7 @@
|
|||||||
branches = await get(
|
branches = await get(
|
||||||
`${apiUrl}/v4/projects/${selected.project.id}/repository/branches?per_page=100&page=1`,
|
`${apiUrl}/v4/projects/${selected.project.id}/repository/branches?per_page=100&page=1`,
|
||||||
{
|
{
|
||||||
Authorization: `Bearer ${$session.gitlabToken}`
|
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -170,7 +171,7 @@
|
|||||||
merge_requests_events: true
|
merge_requests_events: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Authorization: `Bearer ${$session.gitlabToken}`
|
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -194,7 +195,7 @@
|
|||||||
publicSshKey = publicKey;
|
publicSshKey = publicKey;
|
||||||
}
|
}
|
||||||
const deployKeys = await get(deployKeyUrl, {
|
const deployKeys = await get(deployKeyUrl, {
|
||||||
Authorization: `Bearer ${$session.gitlabToken}`
|
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||||
});
|
});
|
||||||
const deployKeyFound = deployKeys.filter((dk) => dk.title === `${appId}-coolify-deploy-key`);
|
const deployKeyFound = deployKeys.filter((dk) => dk.title === `${appId}-coolify-deploy-key`);
|
||||||
if (deployKeyFound.length > 0) {
|
if (deployKeyFound.length > 0) {
|
||||||
@@ -203,7 +204,7 @@
|
|||||||
`${deployKeyUrl}/${deployKey.id}`,
|
`${deployKeyUrl}/${deployKey.id}`,
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
Authorization: `Bearer ${$session.gitlabToken}`
|
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -216,7 +217,7 @@
|
|||||||
can_push: false
|
can_push: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Authorization: `Bearer ${$session.gitlabToken}`
|
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
await post(updateDeployKeyIdUrl, { deployKeyId: id });
|
await post(updateDeployKeyIdUrl, { deployKeyId: id });
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
||||||
const { application, ghToken } = stuff;
|
const { application } = stuff;
|
||||||
if (application?.buildPack && !url.searchParams.get('from')) {
|
if (application?.buildPack && !url.searchParams.get('from')) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
@@ -14,8 +14,7 @@
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
...(await res.json()),
|
...(await res.json()),
|
||||||
application,
|
application
|
||||||
ghToken
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -30,30 +29,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
import { buildPacks, scanningTemplates } from '$lib/components/templates';
|
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/components/templates';
|
||||||
import BuildPack from './_BuildPack.svelte';
|
import BuildPack from './_BuildPack.svelte';
|
||||||
import { page, session } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
|
import { gitTokens } from '$lib/store';
|
||||||
|
|
||||||
let scanning = true;
|
let scanning = true;
|
||||||
let foundConfig = null;
|
let foundConfig = null;
|
||||||
|
let packageManager = 'npm';
|
||||||
|
|
||||||
export let apiUrl;
|
export let apiUrl;
|
||||||
export let projectId;
|
export let projectId;
|
||||||
export let repository;
|
export let repository;
|
||||||
export let branch;
|
export let branch;
|
||||||
export let ghToken;
|
|
||||||
export let type;
|
export let type;
|
||||||
export let application;
|
export let application;
|
||||||
|
|
||||||
function checkPackageJSONContents({ key, json }) {
|
function checkPackageJSONContents({ key, json }) {
|
||||||
return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key);
|
return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key);
|
||||||
}
|
}
|
||||||
function checkTemplates({ json }) {
|
function checkTemplates({ json, packageManager }) {
|
||||||
for (const [key, value] of Object.entries(scanningTemplates)) {
|
for (const [key, value] of Object.entries(scanningTemplates)) {
|
||||||
if (checkPackageJSONContents({ key, json })) {
|
if (checkPackageJSONContents({ key, json })) {
|
||||||
return buildPacks.find((bp) => bp.name === value.buildPack);
|
foundConfig = findBuildPack(value.buildPack, packageManager);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,11 +62,15 @@
|
|||||||
try {
|
try {
|
||||||
if (type === 'gitlab') {
|
if (type === 'gitlab') {
|
||||||
const files = await get(`${apiUrl}/v4/projects/${projectId}/repository/tree`, {
|
const files = await get(`${apiUrl}/v4/projects/${projectId}/repository/tree`, {
|
||||||
Authorization: `Bearer ${$session.gitlabToken}`
|
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||||
});
|
});
|
||||||
const packageJson = files.find(
|
const packageJson = files.find(
|
||||||
(file) => file.name === 'package.json' && file.type === 'blob'
|
(file) => file.name === 'package.json' && file.type === 'blob'
|
||||||
);
|
);
|
||||||
|
const yarnLock = files.find((file) => file.name === 'yarn.lock' && file.type === 'blob');
|
||||||
|
const pnpmLock = files.find(
|
||||||
|
(file) => file.name === 'pnpm-lock.yaml' && file.type === 'blob'
|
||||||
|
);
|
||||||
const dockerfile = files.find((file) => file.name === 'Dockerfile' && file.type === 'blob');
|
const dockerfile = files.find((file) => file.name === 'Dockerfile' && file.type === 'blob');
|
||||||
const cargoToml = files.find((file) => file.name === 'Cargo.toml' && file.type === 'blob');
|
const cargoToml = files.find((file) => file.name === 'Cargo.toml' && file.type === 'blob');
|
||||||
const requirementsTxt = files.find(
|
const requirementsTxt = files.find(
|
||||||
@@ -73,6 +78,10 @@
|
|||||||
);
|
);
|
||||||
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'blob');
|
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'blob');
|
||||||
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'blob');
|
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'blob');
|
||||||
|
|
||||||
|
if (yarnLock) packageManager = 'yarn';
|
||||||
|
if (pnpmLock) packageManager = 'pnpm';
|
||||||
|
|
||||||
if (dockerfile) {
|
if (dockerfile) {
|
||||||
foundConfig.buildPack = 'docker';
|
foundConfig.buildPack = 'docker';
|
||||||
} else if (packageJson) {
|
} else if (packageJson) {
|
||||||
@@ -80,28 +89,32 @@
|
|||||||
const data = await get(
|
const data = await get(
|
||||||
`${apiUrl}/v4/projects/${projectId}/repository/files/${path}/raw?ref=${branch}`,
|
`${apiUrl}/v4/projects/${projectId}/repository/files/${path}/raw?ref=${branch}`,
|
||||||
{
|
{
|
||||||
Authorization: `Bearer ${$session.gitlabToken}`
|
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const json = JSON.parse(data) || {};
|
const json = JSON.parse(data) || {};
|
||||||
foundConfig = checkTemplates({ json });
|
checkTemplates({ json, packageManager });
|
||||||
} else if (cargoToml) {
|
} else if (cargoToml) {
|
||||||
foundConfig = buildPacks.find((bp) => bp.name === 'rust');
|
foundConfig = findBuildPack('rust');
|
||||||
} else if (requirementsTxt) {
|
} else if (requirementsTxt) {
|
||||||
foundConfig = buildPacks.find((bp) => bp.name === 'python');
|
foundConfig = findBuildPack('python');
|
||||||
} else if (indexHtml) {
|
} else if (indexHtml) {
|
||||||
foundConfig = buildPacks.find((bp) => bp.name === 'static');
|
foundConfig = findBuildPack('static', packageManager);
|
||||||
} else if (indexPHP) {
|
} else if (indexPHP) {
|
||||||
foundConfig = buildPacks.find((bp) => bp.name === 'php');
|
foundConfig = findBuildPack('php');
|
||||||
}
|
}
|
||||||
} else if (type === 'github') {
|
} else if (type === 'github') {
|
||||||
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
|
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
|
||||||
Authorization: `Bearer ${ghToken}`,
|
Authorization: `Bearer ${$gitTokens.githubToken}`,
|
||||||
Accept: 'application/vnd.github.v2.json'
|
Accept: 'application/vnd.github.v2.json'
|
||||||
});
|
});
|
||||||
const packageJson = files.find(
|
const packageJson = files.find(
|
||||||
(file) => file.name === 'package.json' && file.type === 'file'
|
(file) => file.name === 'package.json' && file.type === 'file'
|
||||||
);
|
);
|
||||||
|
const yarnLock = files.find((file) => file.name === 'yarn.lock' && file.type === 'file');
|
||||||
|
const pnpmLock = files.find(
|
||||||
|
(file) => file.name === 'pnpm-lock.yaml' && file.type === 'file'
|
||||||
|
);
|
||||||
const dockerfile = files.find((file) => file.name === 'Dockerfile' && file.type === 'file');
|
const dockerfile = files.find((file) => file.name === 'Dockerfile' && file.type === 'file');
|
||||||
const cargoToml = files.find((file) => file.name === 'Cargo.toml' && file.type === 'file');
|
const cargoToml = files.find((file) => file.name === 'Cargo.toml' && file.type === 'file');
|
||||||
const requirementsTxt = files.find(
|
const requirementsTxt = files.find(
|
||||||
@@ -109,26 +122,31 @@
|
|||||||
);
|
);
|
||||||
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'file');
|
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'file');
|
||||||
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'file');
|
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'file');
|
||||||
|
|
||||||
|
if (yarnLock) packageManager = 'yarn';
|
||||||
|
if (pnpmLock) packageManager = 'pnpm';
|
||||||
|
|
||||||
if (dockerfile) {
|
if (dockerfile) {
|
||||||
foundConfig.buildPack = 'docker';
|
foundConfig.buildPack = 'docker';
|
||||||
} else if (packageJson) {
|
} else if (packageJson) {
|
||||||
const data = await get(`${packageJson.git_url}`, {
|
const data = await get(`${packageJson.git_url}`, {
|
||||||
Authorization: `Bearer ${ghToken}`,
|
Authorization: `Bearer ${$gitTokens.githubToken}`,
|
||||||
Accept: 'application/vnd.github.v2.raw'
|
Accept: 'application/vnd.github.v2.raw'
|
||||||
});
|
});
|
||||||
const json = JSON.parse(data) || {};
|
const json = JSON.parse(data) || {};
|
||||||
foundConfig = checkTemplates({ json });
|
checkTemplates({ json, packageManager });
|
||||||
} else if (cargoToml) {
|
} else if (cargoToml) {
|
||||||
foundConfig = buildPacks.find((bp) => bp.name === 'rust');
|
foundConfig = findBuildPack('rust');
|
||||||
} else if (requirementsTxt) {
|
} else if (requirementsTxt) {
|
||||||
foundConfig = buildPacks.find((bp) => bp.name === 'python');
|
foundConfig = findBuildPack('python');
|
||||||
} else if (indexHtml) {
|
} else if (indexHtml) {
|
||||||
foundConfig = buildPacks.find((bp) => bp.name === 'static');
|
foundConfig = findBuildPack('static', packageManager);
|
||||||
} else if (indexPHP) {
|
} else if (indexPHP) {
|
||||||
foundConfig = buildPacks.find((bp) => bp.name === 'php');
|
foundConfig = findBuildPack('php');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
scanning = true;
|
||||||
if (
|
if (
|
||||||
error.error === 'invalid_token' ||
|
error.error === 'invalid_token' ||
|
||||||
error.error_description ===
|
error.error_description ===
|
||||||
@@ -156,11 +174,13 @@
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (error.message === 'Bad credentials') {
|
||||||
|
browser && window.location.reload();
|
||||||
|
}
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
|
||||||
if (!foundConfig) foundConfig = buildPacks.find((bp) => bp.name === 'node');
|
|
||||||
scanning = false;
|
|
||||||
}
|
}
|
||||||
|
if (!foundConfig) foundConfig = findBuildPack('node', packageManager);
|
||||||
|
scanning = false;
|
||||||
}
|
}
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await scanRepository();
|
await scanRepository();
|
||||||
@@ -176,10 +196,16 @@
|
|||||||
<div class="text-xl tracking-tight">Scanning repository to suggest a build pack for you...</div>
|
<div class="text-xl tracking-tight">Scanning repository to suggest a build pack for you...</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
{#if packageManager === 'yarn' || packageManager === 'pnpm'}
|
||||||
|
<div class="flex justify-center p-6">
|
||||||
|
Found lock file for <span class="font-bold text-orange-500 pl-1">{packageManager}</span>.
|
||||||
|
Using it for predefined commands commands.
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">
|
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">
|
||||||
{#each buildPacks as buildPack}
|
{#each buildPacks as buildPack}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<BuildPack {buildPack} {scanning} bind:foundConfig />
|
<BuildPack {buildPack} {scanning} {packageManager} bind:foundConfig />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { getUserDetails } from '$lib/common';
|
||||||
|
import * as db from '$lib/database';
|
||||||
|
import { ErrorHandler } from '$lib/database';
|
||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import jsonwebtoken from 'jsonwebtoken';
|
||||||
|
|
||||||
|
export const get: RequestHandler = async (event) => {
|
||||||
|
const { teamId, status, body } = await getUserDetails(event);
|
||||||
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
|
const { id } = event.params;
|
||||||
|
try {
|
||||||
|
const application = await db.getApplication({ id, teamId });
|
||||||
|
const payload = {
|
||||||
|
iat: Math.round(new Date().getTime() / 1000),
|
||||||
|
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||||
|
iss: application.gitSource.githubApp.appId
|
||||||
|
};
|
||||||
|
const githubToken = jsonwebtoken.sign(payload, application.gitSource.githubApp.privateKey, {
|
||||||
|
algorithm: 'RS256'
|
||||||
|
});
|
||||||
|
const response = await fetch(
|
||||||
|
`${application.gitSource.apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${githubToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
status: 201,
|
||||||
|
body: { token: data.token },
|
||||||
|
headers: {
|
||||||
|
'Set-Cookie': `githubToken=${data.token}; Path=/; HttpOnly; Max-Age=15778800;`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorHandler(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
export const load: Load = async ({ params, url, stuff }) => {
|
export const load: Load = async ({ params, url, stuff }) => {
|
||||||
const { application, githubToken, appId } = stuff;
|
const { application, appId } = stuff;
|
||||||
if (application?.branch && application?.repository && !url.searchParams.get('from')) {
|
if (application?.branch && application?.repository && !url.searchParams.get('from')) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
githubToken,
|
|
||||||
application,
|
application,
|
||||||
appId
|
appId
|
||||||
}
|
}
|
||||||
@@ -20,8 +19,8 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let application;
|
export let application;
|
||||||
export let githubToken;
|
|
||||||
export let appId;
|
export let appId;
|
||||||
|
|
||||||
import GithubRepositories from './_GithubRepositories.svelte';
|
import GithubRepositories from './_GithubRepositories.svelte';
|
||||||
import GitlabRepositories from './_GitlabRepositories.svelte';
|
import GitlabRepositories from './_GitlabRepositories.svelte';
|
||||||
</script>
|
</script>
|
||||||
@@ -31,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-wrap justify-center">
|
||||||
{#if application.gitSource.type === 'github'}
|
{#if application.gitSource.type === 'github'}
|
||||||
<GithubRepositories {application} {githubToken} />
|
<GithubRepositories {application} />
|
||||||
{:else if application.gitSource.type === 'gitlab'}
|
{:else if application.gitSource.type === 'gitlab'}
|
||||||
<GitlabRepositories {application} {appId} />
|
<GitlabRepositories {application} {appId} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
import type Prisma from '@prisma/client';
|
import type Prisma from '@prisma/client';
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { enhance, errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +1,36 @@
|
|||||||
import { getTeam, getUserDetails } from '$lib/common';
|
import { getUserDetails } from '$lib/common';
|
||||||
import { getGithubToken } from '$lib/components/common';
|
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import { checkContainer } from '$lib/haproxy';
|
import { checkContainer } from '$lib/haproxy';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import jsonwebtoken from 'jsonwebtoken';
|
import jsonwebtoken from 'jsonwebtoken';
|
||||||
|
import { get as getRequest } from '$lib/api';
|
||||||
|
|
||||||
export const get: RequestHandler = async (event) => {
|
export const get: RequestHandler = async (event) => {
|
||||||
const { teamId, status, body } = await getUserDetails(event);
|
const { teamId, status, body } = await getUserDetails(event);
|
||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const appId = process.env['COOLIFY_APP_ID'];
|
|
||||||
let githubToken = null;
|
|
||||||
let ghToken = null;
|
|
||||||
let isRunning = false;
|
|
||||||
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
|
|
||||||
|
const appId = process.env['COOLIFY_APP_ID'];
|
||||||
|
let isRunning = false;
|
||||||
|
let githubToken = event.locals.cookies?.githubToken || null;
|
||||||
|
let gitlabToken = event.locals.cookies?.gitlabToken || null;
|
||||||
try {
|
try {
|
||||||
const application = await db.getApplication({ id, teamId });
|
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 (application.destinationDockerId) {
|
if (application.destinationDockerId) {
|
||||||
isRunning = await checkContainer(application.destinationDocker.engine, id);
|
isRunning = await checkContainer(application.destinationDocker.engine, id);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
isRunning,
|
isRunning,
|
||||||
ghToken,
|
|
||||||
githubToken,
|
|
||||||
application,
|
application,
|
||||||
appId
|
appId,
|
||||||
}
|
githubToken,
|
||||||
|
gitlabToken
|
||||||
|
},
|
||||||
|
headers: {}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
import type Prisma from '@prisma/client';
|
import type Prisma from '@prisma/client';
|
||||||
import { getDomain, notNodeDeployments, staticDeployments } from '$lib/components/common';
|
import { notNodeDeployments, staticDeployments } from '$lib/components/common';
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
@@ -52,6 +52,7 @@
|
|||||||
let loading = false;
|
let loading = false;
|
||||||
let debug = application.settings.debug;
|
let debug = application.settings.debug;
|
||||||
let previews = application.settings.previews;
|
let previews = application.settings.previews;
|
||||||
|
let dualCerts = application.settings.dualCerts;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
domainEl.focus();
|
domainEl.focus();
|
||||||
@@ -64,8 +65,11 @@
|
|||||||
if (name === 'previews') {
|
if (name === 'previews') {
|
||||||
previews = !previews;
|
previews = !previews;
|
||||||
}
|
}
|
||||||
|
if (name === 'dualCerts') {
|
||||||
|
dualCerts = !dualCerts;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/settings.json`, { previews, debug });
|
await post(`/applications/${id}/settings.json`, { previews, debug, dualCerts });
|
||||||
return toast.push('Settings saved.');
|
return toast.push('Settings saved.');
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@@ -169,191 +173,191 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
<div class="mt-2 grid grid-cols-3 items-center">
|
<div class="mt-2 grid grid-cols-2 items-center">
|
||||||
<label for="name">Name</label>
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
<div class="col-span-2 ">
|
<input
|
||||||
|
readonly={!$session.isAdmin}
|
||||||
|
name="name"
|
||||||
|
id="name"
|
||||||
|
bind:value={application.name}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="gitSource" class="text-base font-bold text-stone-100">Git Source</label>
|
||||||
|
<a
|
||||||
|
href={$session.isAdmin
|
||||||
|
? `/applications/${id}/configuration/source?from=/applications/${id}`
|
||||||
|
: ''}
|
||||||
|
class="no-underline"
|
||||||
|
><input
|
||||||
|
value={application.gitSource.name}
|
||||||
|
id="gitSource"
|
||||||
|
disabled
|
||||||
|
class="cursor-pointer hover:bg-coolgray-500"
|
||||||
|
/></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="repository" class="text-base font-bold text-stone-100">Git Repository</label>
|
||||||
|
<a
|
||||||
|
href={$session.isAdmin
|
||||||
|
? `/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`
|
||||||
|
: ''}
|
||||||
|
class="no-underline"
|
||||||
|
><input
|
||||||
|
value="{application.repository}/{application.branch}"
|
||||||
|
id="repository"
|
||||||
|
disabled
|
||||||
|
class="cursor-pointer hover:bg-coolgray-500"
|
||||||
|
/></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="buildPack" class="text-base font-bold text-stone-100">Build Pack</label>
|
||||||
|
<a
|
||||||
|
href={$session.isAdmin
|
||||||
|
? `/applications/${id}/configuration/buildpack?from=/applications/${id}`
|
||||||
|
: ''}
|
||||||
|
class="no-underline "
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
readonly={!$session.isAdmin}
|
value={application.buildPack}
|
||||||
name="name"
|
id="buildPack"
|
||||||
id="name"
|
disabled
|
||||||
bind:value={application.name}
|
class="cursor-pointer hover:bg-coolgray-500"
|
||||||
required
|
/></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center pb-8">
|
||||||
|
<label for="destination" class="text-base font-bold text-stone-100">Destination</label>
|
||||||
|
<div class="no-underline">
|
||||||
|
<input
|
||||||
|
value={application.destinationDocker.name}
|
||||||
|
id="destination"
|
||||||
|
disabled
|
||||||
|
class="bg-transparent "
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
|
||||||
<label for="gitSource">Git Source</label>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<a
|
|
||||||
href={$session.isAdmin
|
|
||||||
? `/applications/${id}/configuration/source?from=/applications/${id}`
|
|
||||||
: ''}
|
|
||||||
class="no-underline"
|
|
||||||
><input
|
|
||||||
value={application.gitSource.name}
|
|
||||||
id="gitSource"
|
|
||||||
disabled
|
|
||||||
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500"
|
|
||||||
/></a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
|
||||||
<label for="repository">Git Repository</label>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<a
|
|
||||||
href={$session.isAdmin
|
|
||||||
? `/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`
|
|
||||||
: ''}
|
|
||||||
class="no-underline"
|
|
||||||
><input
|
|
||||||
value="{application.repository}/{application.branch}"
|
|
||||||
id="repository"
|
|
||||||
disabled
|
|
||||||
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500"
|
|
||||||
/></a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
|
||||||
<label for="buildPack">Build Pack</label>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<a
|
|
||||||
href={$session.isAdmin
|
|
||||||
? `/applications/${id}/configuration/buildpack?from=/applications/${id}`
|
|
||||||
: ''}
|
|
||||||
class="no-underline "
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
value={application.buildPack}
|
|
||||||
id="buildPack"
|
|
||||||
disabled
|
|
||||||
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500"
|
|
||||||
/></a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center pb-8">
|
|
||||||
<label for="destination">Destination</label>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<div class="no-underline">
|
|
||||||
<input
|
|
||||||
value={application.destinationDocker.name}
|
|
||||||
id="destination"
|
|
||||||
disabled
|
|
||||||
class="bg-transparent "
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">Application</div>
|
<div class="title">Application</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
<div class="grid grid-cols-3">
|
<div class="grid grid-cols-2">
|
||||||
<label for="fqdn" class="pt-2">Domain (FQDN)</label>
|
<div class="flex-col">
|
||||||
<div class="col-span-2">
|
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</label>
|
||||||
<input
|
|
||||||
readonly={!$session.isAdmin || isRunning}
|
|
||||||
disabled={!$session.isAdmin || isRunning}
|
|
||||||
bind:this={domainEl}
|
|
||||||
name="fqdn"
|
|
||||||
id="fqdn"
|
|
||||||
bind:value={application.fqdn}
|
|
||||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
|
||||||
placeholder="eg: https://coollabs.io"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<Explainer
|
<Explainer
|
||||||
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
|
text="If you specify <span class='text-green-500 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-500 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<input
|
||||||
|
readonly={!$session.isAdmin || isRunning}
|
||||||
|
disabled={!$session.isAdmin || isRunning}
|
||||||
|
bind:this={domainEl}
|
||||||
|
name="fqdn"
|
||||||
|
id="fqdn"
|
||||||
|
bind:value={application.fqdn}
|
||||||
|
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||||
|
placeholder="eg: https://coollabs.io"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
{#if !staticDeployments.includes(application.buildPack)}
|
{#if !staticDeployments.includes(application.buildPack)}
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="port">Port</label>
|
<label for="port" class="text-base font-bold text-stone-100">Port</label>
|
||||||
<div class="col-span-2">
|
|
||||||
<input
|
|
||||||
readonly={!$session.isAdmin}
|
|
||||||
name="port"
|
|
||||||
id="port"
|
|
||||||
bind:value={application.port}
|
|
||||||
placeholder="default: 3000"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
|
||||||
<label for="installCommand">Install Command</label>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<input
|
|
||||||
readonly={!$session.isAdmin}
|
|
||||||
name="installCommand"
|
|
||||||
id="installCommand"
|
|
||||||
bind:value={application.installCommand}
|
|
||||||
placeholder="default: yarn install"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
|
||||||
<label for="buildCommand">Build Command</label>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<input
|
|
||||||
readonly={!$session.isAdmin}
|
|
||||||
name="buildCommand"
|
|
||||||
id="buildCommand"
|
|
||||||
bind:value={application.buildCommand}
|
|
||||||
placeholder="default: yarn build"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
|
||||||
<label for="startCommand" class="">Start Command</label>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<input
|
|
||||||
readonly={!$session.isAdmin}
|
|
||||||
name="startCommand"
|
|
||||||
id="startCommand"
|
|
||||||
bind:value={application.startCommand}
|
|
||||||
placeholder="default: yarn start"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="grid grid-cols-3">
|
|
||||||
<label for="baseDirectory" class="pt-2">Base Directory</label>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<input
|
<input
|
||||||
readonly={!$session.isAdmin}
|
readonly={!$session.isAdmin}
|
||||||
name="baseDirectory"
|
name="port"
|
||||||
id="baseDirectory"
|
id="port"
|
||||||
bind:value={application.baseDirectory}
|
bind:value={application.port}
|
||||||
placeholder="default: /"
|
placeholder="default: 3000"
|
||||||
/>
|
|
||||||
<Explainer
|
|
||||||
text="Directory to use as the base of all commands. <br> Could be useful with monorepos."
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="installCommand" class="text-base font-bold text-stone-100"
|
||||||
|
>Install Command</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
readonly={!$session.isAdmin}
|
||||||
|
name="installCommand"
|
||||||
|
id="installCommand"
|
||||||
|
bind:value={application.installCommand}
|
||||||
|
placeholder="default: yarn install"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="buildCommand" class="text-base font-bold text-stone-100">Build Command</label>
|
||||||
|
<input
|
||||||
|
readonly={!$session.isAdmin}
|
||||||
|
name="buildCommand"
|
||||||
|
id="buildCommand"
|
||||||
|
bind:value={application.buildCommand}
|
||||||
|
placeholder="default: yarn build"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="startCommand" class="text-base font-bold text-stone-100">Start Command</label>
|
||||||
|
<input
|
||||||
|
readonly={!$session.isAdmin}
|
||||||
|
name="startCommand"
|
||||||
|
id="startCommand"
|
||||||
|
bind:value={application.startCommand}
|
||||||
|
placeholder="default: yarn start"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<div class="flex-col">
|
||||||
|
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
|
||||||
|
>Base Directory</label
|
||||||
|
>
|
||||||
|
<Explainer
|
||||||
|
text="Directory to use as the base for all commands.<br>Could be useful with <span class='text-green-500 font-bold'>monorepos</span>."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
readonly={!$session.isAdmin}
|
||||||
|
name="baseDirectory"
|
||||||
|
id="baseDirectory"
|
||||||
|
bind:value={application.baseDirectory}
|
||||||
|
placeholder="default: /"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||||
<div class="grid grid-cols-3">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="publishDirectory" class="pt-2">Publish Directory</label>
|
<div class="flex-col">
|
||||||
<div class="col-span-2">
|
<label for="publishDirectory" class="pt-2 text-base font-bold text-stone-100"
|
||||||
<input
|
>Publish Directory</label
|
||||||
readonly={!$session.isAdmin}
|
>
|
||||||
name="publishDirectory"
|
|
||||||
id="publishDirectory"
|
|
||||||
bind:value={application.publishDirectory}
|
|
||||||
placeholder=" default: /"
|
|
||||||
/>
|
|
||||||
<Explainer
|
<Explainer
|
||||||
text="Directory containing all the assets for deployment. <br> For example: <span class='text-green-600 font-bold'>dist</span>,<span class='text-green-600 font-bold'>_site</span> or <span class='text-green-600 font-bold'>public</span>."
|
text="Directory containing all the assets for deployment. <br> For example: <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> or <span class='text-green-500 font-bold'>public</span>."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
readonly={!$session.isAdmin}
|
||||||
|
name="publishDirectory"
|
||||||
|
id="publishDirectory"
|
||||||
|
bind:value={application.publishDirectory}
|
||||||
|
placeholder=" default: /"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -361,8 +365,7 @@
|
|||||||
<div class="flex space-x-1 pb-5 font-bold">
|
<div class="flex space-x-1 pb-5 font-bold">
|
||||||
<div class="title">Features</div>
|
<div class="title">Features</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 pb-10 sm:px-6">
|
<!-- <ul class="mt-2 divide-y divide-stone-800">
|
||||||
<!-- <ul class="mt-2 divide-y divide-stone-800">
|
|
||||||
<Setting
|
<Setting
|
||||||
bind:setting={forceSSL}
|
bind:setting={forceSSL}
|
||||||
on:click={() => changeSettings('forceSSL')}
|
on:click={() => changeSettings('forceSSL')}
|
||||||
@@ -370,21 +373,24 @@
|
|||||||
description="Creates a https redirect for all requests from http and also generates a https certificate for the domain through Let's Encrypt."
|
description="Creates a https redirect for all requests from http and also generates a https certificate for the domain through Let's Encrypt."
|
||||||
/>
|
/>
|
||||||
</ul> -->
|
</ul> -->
|
||||||
<ul class="mt-2 divide-y divide-stone-800">
|
<div class="px-10 pb-10">
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
isCenter={false}
|
||||||
bind:setting={previews}
|
bind:setting={previews}
|
||||||
on:click={() => changeSettings('previews')}
|
on:click={() => changeSettings('previews')}
|
||||||
title="Enable MR/PR Previews"
|
title="Enable MR/PR Previews"
|
||||||
description="Creates previews from pull and merge requests."
|
description="Creates previews from pull and merge requests."
|
||||||
/>
|
/>
|
||||||
</ul>
|
</div>
|
||||||
<ul class="mt-2 divide-y divide-stone-800">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
isCenter={false}
|
||||||
bind:setting={debug}
|
bind:setting={debug}
|
||||||
on:click={() => changeSettings('debug')}
|
on:click={() => changeSettings('debug')}
|
||||||
title="Debug Logs"
|
title="Debug Logs"
|
||||||
description="Enable debug logs during build phase. <br>(<span class='text-red-500'>sensitive information</span> could be visible in logs)"
|
description="Enable debug logs during build phase.<br><span class='text-red-500 font-bold'>Sensitive information</span> could be visible and saved in logs."
|
||||||
/>
|
/>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
<div class="flex justify-end sticky top-0 p-2">
|
<div class="flex justify-end sticky top-0 p-2">
|
||||||
<button
|
<button
|
||||||
on:click={followBuild}
|
on:click={followBuild}
|
||||||
|
class="bg-transparent"
|
||||||
data-tooltip="Follow logs"
|
data-tooltip="Follow logs"
|
||||||
class:text-green-500={followingBuild}
|
class:text-green-500={followingBuild}
|
||||||
>
|
>
|
||||||
@@ -108,7 +109,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12"
|
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}
|
bind:this={logsEl}
|
||||||
>
|
>
|
||||||
{#each logs as log}
|
{#each logs as log}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
}
|
}
|
||||||
async function loadBuild(build) {
|
async function loadBuild(build) {
|
||||||
buildId = build;
|
buildId = build;
|
||||||
goto(`/applications/${id}/logs/build?buildId=${buildId}`);
|
await goto(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -94,17 +94,19 @@
|
|||||||
<div class="block flex-row justify-start space-x-2 px-5 pt-6 sm:px-10 md:flex">
|
<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="mb-4 min-w-[16rem] space-y-2 md:mb-0 ">
|
||||||
<div class="top-4 md:sticky">
|
<div class="top-4 md:sticky">
|
||||||
{#each builds as build (build.id)}
|
{#each builds as build, index (build.id)}
|
||||||
<div
|
<div
|
||||||
data-tooltip={new Intl.DateTimeFormat('default', dateOptions).format(
|
data-tooltip={new Intl.DateTimeFormat('default', dateOptions).format(
|
||||||
new Date(build.createdAt)
|
new Date(build.createdAt)
|
||||||
) + `\n${build.status}`}
|
) + `\n${build.status}`}
|
||||||
on:click={() => loadBuild(build.id)}
|
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: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:bg-coolgray-400={buildId === build.id}
|
||||||
class:border-red-500={build.status === 'failed'}
|
class:border-red-500={build.status === 'failed'}
|
||||||
class:border-green-500={build.status === 'success'}
|
class:border-green-500={build.status === 'success'}
|
||||||
class:border-yellow-500={build.status === 'inprogress'}
|
class:border-yellow-500={build.status === 'running'}
|
||||||
>
|
>
|
||||||
<div class="flex-col px-2">
|
<div class="flex-col px-2">
|
||||||
<div class="text-sm font-bold">
|
<div class="text-sm font-bold">
|
||||||
|
|||||||
@@ -77,11 +77,12 @@
|
|||||||
{#if logs.length === 0}
|
{#if logs.length === 0}
|
||||||
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
|
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="relative w-full">
|
<div class="relative">
|
||||||
<LoadingLogs />
|
<LoadingLogs />
|
||||||
<div class="flex justify-end sticky top-0 p-2">
|
<div class="flex justify-end sticky top-0 p-2">
|
||||||
<button
|
<button
|
||||||
on:click={followBuild}
|
on:click={followBuild}
|
||||||
|
class="bg-transparent"
|
||||||
data-tooltip="Follow logs"
|
data-tooltip="Follow logs"
|
||||||
class:text-green-500={followingBuild}
|
class:text-green-500={followingBuild}
|
||||||
>
|
>
|
||||||
@@ -104,12 +105,14 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 p-6 whitespace-pre-wrap break-words w-full mb-10 -mt-12"
|
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}
|
bind:this={logsEl}
|
||||||
>
|
>
|
||||||
{#each logs as log}
|
<div class="px-2">
|
||||||
{log + '\n'}
|
{#each logs as log}
|
||||||
{/each}
|
{log + '\n'}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ export const get: RequestHandler = async (event) => {
|
|||||||
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
try {
|
try {
|
||||||
|
const secrets = await db.listSecrets(id);
|
||||||
|
const applicationSecrets = secrets.filter((secret) => !secret.isPRMRSecret);
|
||||||
|
const PRMRSecrets = secrets.filter((secret) => secret.isPRMRSecret);
|
||||||
const destinationDocker = await db.getDestinationByApplicationId({ id, teamId });
|
const destinationDocker = await db.getDestinationByApplicationId({ id, teamId });
|
||||||
const docker = dockerInstance({ destinationDocker });
|
const docker = dockerInstance({ destinationDocker });
|
||||||
const listContainers = await docker.engine.listContainers({
|
const listContainers = await docker.engine.listContainers({
|
||||||
@@ -35,7 +38,13 @@ export const get: RequestHandler = async (event) => {
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
body: {
|
body: {
|
||||||
containers: jsonContainers
|
containers: jsonContainers,
|
||||||
|
applicationSecrets: applicationSecrets.sort((a, b) => {
|
||||||
|
return ('' + a.name).localeCompare(b.name);
|
||||||
|
}),
|
||||||
|
PRMRSecrets: PRMRSecrets.sort((a, b) => {
|
||||||
|
return ('' + a.name).localeCompare(b.name);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -22,8 +22,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let containers;
|
export let containers;
|
||||||
export let application;
|
export let application;
|
||||||
|
export let PRMRSecrets;
|
||||||
|
export let applicationSecrets;
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
|
import Secret from '../secrets/_Secret.svelte';
|
||||||
|
import { get } from '$lib/api';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
|
||||||
|
const { id } = $page.params;
|
||||||
|
async function refreshSecrets() {
|
||||||
|
const data = await get(`/applications/${id}/secrets.json`);
|
||||||
|
PRMRSecrets = [...data.secrets];
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
@@ -32,7 +43,57 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
<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 applicationSecrets as secret}
|
||||||
|
{#key secret.id}
|
||||||
|
<tr class="h-20 transition duration-100 hover:bg-coolgray-400">
|
||||||
|
<Secret
|
||||||
|
PRMRSecret={PRMRSecrets.find((s) => s.name === secret.name)}
|
||||||
|
isPRMRSecret
|
||||||
|
name={secret.name}
|
||||||
|
value={secret.value}
|
||||||
|
isBuildSecret={secret.isBuildSecret}
|
||||||
|
on:refresh={refreshSecrets}
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
{/key}
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center py-4 text-center">
|
||||||
|
<Explainer
|
||||||
|
customClass="w-full"
|
||||||
|
text={applicationSecrets.length === 0
|
||||||
|
? "<span class='font-bold text-white text-xl'>Please add secrets to the application first.</span> <br><br>These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
|
||||||
|
: "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto max-w-4xl py-10">
|
||||||
<div class="flex flex-wrap justify-center space-x-2">
|
<div class="flex flex-wrap justify-center space-x-2">
|
||||||
{#if containers.length > 0}
|
{#if containers.length > 0}
|
||||||
{#each containers as container}
|
{#each containers as container}
|
||||||
|
|||||||
@@ -3,14 +3,19 @@
|
|||||||
export let value = '';
|
export let value = '';
|
||||||
export let isBuildSecret = false;
|
export let isBuildSecret = false;
|
||||||
export let isNewSecret = false;
|
export let isNewSecret = false;
|
||||||
|
export let isPRMRSecret = false;
|
||||||
|
export let PRMRSecret = {};
|
||||||
|
|
||||||
|
if (isPRMRSecret) value = PRMRSecret.value;
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { del, post } from '$lib/api';
|
import { del, post } from '$lib/api';
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let nameEl;
|
|
||||||
let valueEl;
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
async function removeSecret() {
|
async function removeSecret() {
|
||||||
try {
|
try {
|
||||||
@@ -25,24 +30,24 @@
|
|||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function saveSecret() {
|
async function saveSecret(isNew = false) {
|
||||||
const nameValid = nameEl.checkValidity();
|
if (!name) return errorNotification('Name is required.');
|
||||||
const valueValid = valueEl.checkValidity();
|
if (!value) return errorNotification('Value is required.');
|
||||||
if (!nameValid) {
|
|
||||||
return nameEl.reportValidity();
|
|
||||||
}
|
|
||||||
if (!valueValid) {
|
|
||||||
return valueEl.reportValidity();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret });
|
await post(`/applications/${id}/secrets.json`, {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
isBuildSecret,
|
||||||
|
isPRMRSecret,
|
||||||
|
isNew
|
||||||
|
});
|
||||||
dispatch('refresh');
|
dispatch('refresh');
|
||||||
if (isNewSecret) {
|
if (isNewSecret) {
|
||||||
name = '';
|
name = '';
|
||||||
value = '';
|
value = '';
|
||||||
isBuildSecret = false;
|
isBuildSecret = false;
|
||||||
}
|
}
|
||||||
|
toast.push('Secret saved.');
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
@@ -54,33 +59,29 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
<td>
|
||||||
<input
|
<input
|
||||||
id="secretName"
|
id={isNewSecret ? 'secretName' : 'secretNameNew'}
|
||||||
bind:this={nameEl}
|
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
required
|
required
|
||||||
placeholder="EXAMPLE_VARIABLE"
|
placeholder="EXAMPLE_VARIABLE"
|
||||||
class="-mx-2 w-64 border-2 border-transparent"
|
class=" border border-dashed border-coolgray-300"
|
||||||
readonly={!isNewSecret}
|
readonly={!isNewSecret}
|
||||||
class:bg-transparent={!isNewSecret}
|
class:bg-transparent={!isNewSecret}
|
||||||
class:cursor-not-allowed={!isNewSecret}
|
class:cursor-not-allowed={!isNewSecret}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
<td>
|
||||||
<input
|
<CopyPasswordField
|
||||||
id="secretValue"
|
id={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
||||||
|
name={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
||||||
|
isPasswordField={true}
|
||||||
bind:value
|
bind:value
|
||||||
bind:this={valueEl}
|
|
||||||
required
|
required
|
||||||
placeholder="J$#@UIO%HO#$U%H"
|
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>
|
||||||
<td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
|
<td class="text-center">
|
||||||
<div
|
<div
|
||||||
type="button"
|
type="button"
|
||||||
on:click={setSecretValue}
|
on:click={setSecretValue}
|
||||||
@@ -129,14 +130,21 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
<td>
|
||||||
{#if isNewSecret}
|
{#if isNewSecret}
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<button class="w-24 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
|
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveSecret(true)}>Add</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex justify-center items-end">
|
<div class="flex flex-row justify-center space-x-2">
|
||||||
<button class="w-24 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
|
<div class="flex items-center justify-center">
|
||||||
|
<button class="" on:click={() => saveSecret(false)}>Set</button>
|
||||||
|
</div>
|
||||||
|
{#if !isPRMRSecret}
|
||||||
|
<div class="flex justify-center items-end">
|
||||||
|
<button class="bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ export const get: RequestHandler = async (event) => {
|
|||||||
const { teamId, status, body } = await getUserDetails(event);
|
const { teamId, status, body } = await getUserDetails(event);
|
||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
|
const { id } = event.params;
|
||||||
try {
|
try {
|
||||||
const secrets = await db.listSecrets({ applicationId: event.params.id });
|
const secrets = await (await db.listSecrets(id)).filter((secret) => !secret.isPRMRSecret);
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
@@ -27,16 +28,22 @@ export const post: RequestHandler = async (event) => {
|
|||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
const { name, value, isBuildSecret } = await event.request.json();
|
const { name, value, isBuildSecret, isPRMRSecret, isNew } = await event.request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = await db.isSecretExists({ id, name });
|
if (isNew) {
|
||||||
if (found) {
|
const found = await db.isSecretExists({ id, name, isPRMRSecret });
|
||||||
throw {
|
if (found) {
|
||||||
error: `Secret ${name} already exists.`
|
throw {
|
||||||
};
|
error: `Secret ${name} already exists.`
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
await db.createSecret({ id, name, value, isBuildSecret, isPRMRSecret });
|
||||||
|
return {
|
||||||
|
status: 201
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await db.createSecret({ id, name, value, isBuildSecret });
|
await db.updateSecret({ id, name, value, isBuildSecret, isPRMRSecret });
|
||||||
return {
|
return {
|
||||||
status: 201
|
status: 201
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,37 +41,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||||
<table class="mx-auto">
|
<table class="mx-auto border-separate text-left">
|
||||||
<thead class=" rounded-xl border-b border-coolgray-500">
|
<thead>
|
||||||
<tr>
|
<tr class="h-12">
|
||||||
<th
|
<th scope="col">Name</th>
|
||||||
scope="col"
|
<th scope="col">Value</th>
|
||||||
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
|
<th scope="col" class="w-64 text-center">Need during buildtime?</th>
|
||||||
>Name</th
|
<th scope="col" class="w-96 text-center">Action</th>
|
||||||
>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
|
|
||||||
>Value</th
|
|
||||||
>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
|
|
||||||
>Need during buildtime?</th
|
|
||||||
>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
|
|
||||||
/>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="">
|
<tbody>
|
||||||
{#each secrets as secret}
|
{#each secrets as secret}
|
||||||
{#key secret.id}
|
{#key secret.id}
|
||||||
<tr class="hover:bg-coolgray-200">
|
<tr>
|
||||||
<Secret
|
<Secret
|
||||||
name={secret.name}
|
name={secret.name}
|
||||||
value={secret.value ? secret.value : 'ENCRYPTED'}
|
value={secret.value}
|
||||||
isBuildSecret={secret.isBuildSecret}
|
isBuildSecret={secret.isBuildSecret}
|
||||||
on:refresh={refreshSecrets}
|
on:refresh={refreshSecrets}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ export const post: RequestHandler = async (event) => {
|
|||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
const { debug, previews } = await event.request.json();
|
const { debug, previews, dualCerts } = await event.request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.setApplicationSettings({ id, debug, previews });
|
await db.setApplicationSettings({ id, debug, previews, dualCerts });
|
||||||
return { status: 201 };
|
return { status: 201 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return ErrorHandler(error);
|
return ErrorHandler(error);
|
||||||
|
|||||||
@@ -16,12 +16,11 @@ export const post: RequestHandler = async (event) => {
|
|||||||
id,
|
id,
|
||||||
teamId
|
teamId
|
||||||
});
|
});
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const docker = dockerInstance({ destinationDocker });
|
const docker = dockerInstance({ destinationDocker });
|
||||||
await docker.engine.getContainer(id).stop();
|
await docker.engine.getContainer(id).stop();
|
||||||
}
|
}
|
||||||
await removeProxyConfiguration({ domain });
|
await removeProxyConfiguration(fqdn);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let applications: Array<Applications>;
|
export let applications: Array<Application>;
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
import Application from './_Application.svelte';
|
import Application from './_Application.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,73 +6,63 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">CouchDB</div>
|
<div class="title">CouchDB</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-10">
|
<div class="space-y-2 px-10">
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="defaultDatabase">Default Database</label>
|
<label for="defaultDatabase" class="text-base font-bold text-stone-100">Default Database</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
required
|
||||||
required
|
readonly={database.defaultDatabase}
|
||||||
readonly={database.defaultDatabase}
|
disabled={database.defaultDatabase}
|
||||||
disabled={database.defaultDatabase}
|
placeholder="eg: mydb"
|
||||||
placeholder="eg: mydb"
|
id="defaultDatabase"
|
||||||
id="defaultDatabase"
|
name="defaultDatabase"
|
||||||
name="defaultDatabase"
|
bind:value={database.defaultDatabase}
|
||||||
bind:value={database.defaultDatabase}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUser">User</label>
|
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
id="dbUser"
|
||||||
id="dbUser"
|
name="dbUser"
|
||||||
name="dbUser"
|
value={database.dbUser}
|
||||||
value={database.dbUser}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUserPassword">Password</label>
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
isPasswordField
|
||||||
isPasswordField
|
id="dbUserPassword"
|
||||||
id="dbUserPassword"
|
name="dbUserPassword"
|
||||||
name="dbUserPassword"
|
value={database.dbUserPassword}
|
||||||
value={database.dbUserPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUser">Root User</label>
|
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
id="rootUser"
|
||||||
id="rootUser"
|
name="rootUser"
|
||||||
name="rootUser"
|
value={database.rootUser}
|
||||||
value={database.rootUser}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUserPassword">Root's Password</label>
|
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
isPasswordField
|
||||||
isPasswordField
|
id="rootUserPassword"
|
||||||
id="rootUserPassword"
|
name="rootUserPassword"
|
||||||
name="rootUserPassword"
|
value={database.rootUserPassword}
|
||||||
value={database.rootUserPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -56,9 +56,11 @@
|
|||||||
appendOnly = !appendOnly;
|
appendOnly = !appendOnly;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await post(`/databases/${id}/settings.json`, { isPublic, appendOnly });
|
const { publicPort } = await post(`/databases/${id}/settings.json`, { isPublic, appendOnly });
|
||||||
|
if (isPublic) {
|
||||||
|
database.publicPort = publicPort;
|
||||||
|
}
|
||||||
databaseUrl = generateUrl();
|
databaseUrl = generateUrl();
|
||||||
return;
|
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
@@ -88,70 +90,60 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="name">Name</label>
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
<div class="col-span-2 ">
|
<input
|
||||||
<input
|
readonly={!$session.isAdmin}
|
||||||
readonly={!$session.isAdmin}
|
name="name"
|
||||||
name="name"
|
id="name"
|
||||||
id="name"
|
bind:value={database.name}
|
||||||
bind:value={database.name}
|
required
|
||||||
required
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="destination">Destination</label>
|
<label for="destination" class="text-base font-bold text-stone-100">Destination</label>
|
||||||
<div class="col-span-2">
|
{#if database.destinationDockerId}
|
||||||
{#if database.destinationDockerId}
|
<div class="no-underline">
|
||||||
<div class="no-underline">
|
<input
|
||||||
<input
|
value={database.destinationDocker.name}
|
||||||
value={database.destinationDocker.name}
|
id="destination"
|
||||||
id="destination"
|
disabled
|
||||||
disabled
|
readonly
|
||||||
readonly
|
class="bg-transparent "
|
||||||
class="bg-transparent "
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="version">Version</label>
|
<label for="version" class="text-base font-bold text-stone-100">Version</label>
|
||||||
<div class="col-span-2 ">
|
<input value={database.version} readonly disabled class="bg-transparent " />
|
||||||
<input value={database.version} readonly disabled class="bg-transparent " />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10 pt-2">
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="host">Host</label>
|
<label for="host" class="text-base font-bold text-stone-100">Host</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
isPasswordField={false}
|
||||||
isPasswordField={false}
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
id="host"
|
||||||
id="host"
|
name="host"
|
||||||
name="host"
|
value={database.id}
|
||||||
value={database.id}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="publicPort">Port</label>
|
<label for="publicPort" class="text-base font-bold text-stone-100">Port</label>
|
||||||
<div class="col-span-2">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
placeholder="Generated automatically after set to public"
|
||||||
placeholder="Generated automatically after start"
|
id="publicPort"
|
||||||
id="publicPort"
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
name="publicPort"
|
||||||
name="publicPort"
|
value={isPublic ? database.publicPort : privatePort}
|
||||||
value={isPublic ? database.publicPort : privatePort}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2">
|
<div class="grid grid-flow-row gap-2">
|
||||||
@@ -166,44 +158,42 @@
|
|||||||
{:else if database.type === 'couchdb'}
|
{:else if database.type === 'couchdb'}
|
||||||
<CouchDb bind:database />
|
<CouchDb bind:database />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-3 items-center px-10 pb-8">
|
<div class="grid grid-cols-2 items-center px-10 pb-8">
|
||||||
<label for="url">Connection String</label>
|
<label for="url" class="text-base font-bold text-stone-100">Connection String</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
textarea={true}
|
||||||
textarea={true}
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
isPasswordField={false}
|
||||||
isPasswordField={false}
|
id="url"
|
||||||
id="url"
|
name="url"
|
||||||
name="url"
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
value={databaseUrl}
|
||||||
value={databaseUrl}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="flex space-x-1 pb-5 font-bold">
|
<div class="flex space-x-1 pb-5 font-bold">
|
||||||
<div class="title">Features</div>
|
<div class="title">Features</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 pb-10 sm:px-6">
|
<div class="px-10 pb-10">
|
||||||
<ul class="mt-2 divide-y divide-stone-800">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
bind:setting={isPublic}
|
bind:setting={isPublic}
|
||||||
on:click={() => changeSettings('isPublic')}
|
on:click={() => changeSettings('isPublic')}
|
||||||
title="Set it public"
|
title="Set it public"
|
||||||
description="Your database will be reachable over the internet. <br>Take security seriously in this case!"
|
description="Your database will be reachable over the internet. <br>Take security seriously in this case!"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</div>
|
||||||
{#if database.type === 'redis'}
|
{#if database.type === 'redis'}
|
||||||
<ul class="mt-2 divide-y divide-stone-800">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
bind:setting={appendOnly}
|
bind:setting={appendOnly}
|
||||||
on:click={() => changeSettings('appendOnly')}
|
on:click={() => changeSettings('appendOnly')}
|
||||||
title="Change append only mode"
|
title="Change append only mode"
|
||||||
description="Useful if you would like to restore redis data from a backup.<br><span class='font-bold text-white'>Database restart is required.</span>"
|
description="Useful if you would like to restore redis data from a backup.<br><span class='font-bold text-white'>Database restart is required.</span>"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,32 +6,28 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">MongoDB</div>
|
<div class="title">MongoDB</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-10">
|
<div class="space-y-2 px-10">
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUser">Root User</label>
|
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
id="rootUser"
|
||||||
id="rootUser"
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
name="rootUser"
|
||||||
name="rootUser"
|
value={database.rootUser}
|
||||||
value={database.rootUser}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUserPassword">Root's Password</label>
|
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
isPasswordField={true}
|
||||||
isPasswordField={true}
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
id="rootUserPassword"
|
||||||
id="rootUserPassword"
|
name="rootUserPassword"
|
||||||
name="rootUserPassword"
|
value={database.rootUserPassword}
|
||||||
value={database.rootUserPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,73 +6,63 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">MySQL</div>
|
<div class="title">MySQL</div>
|
||||||
</div>
|
</div>
|
||||||
<div class=" px-10">
|
<div class="space-y-2 px-10">
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="defaultDatabase">Default Database</label>
|
<label for="defaultDatabase" class="text-base font-bold text-stone-100">Default Database</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
required
|
||||||
required
|
readonly={database.defaultDatabase}
|
||||||
readonly={database.defaultDatabase}
|
disabled={database.defaultDatabase}
|
||||||
disabled={database.defaultDatabase}
|
placeholder="eg: mydb"
|
||||||
placeholder="eg: mydb"
|
id="defaultDatabase"
|
||||||
id="defaultDatabase"
|
name="defaultDatabase"
|
||||||
name="defaultDatabase"
|
bind:value={database.defaultDatabase}
|
||||||
bind:value={database.defaultDatabase}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUser">User</label>
|
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
id="dbUser"
|
||||||
id="dbUser"
|
name="dbUser"
|
||||||
name="dbUser"
|
value={database.dbUser}
|
||||||
value={database.dbUser}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUserPassword">Password</label>
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
isPasswordField
|
||||||
isPasswordField
|
id="dbUserPassword"
|
||||||
id="dbUserPassword"
|
name="dbUserPassword"
|
||||||
name="dbUserPassword"
|
value={database.dbUserPassword}
|
||||||
value={database.dbUserPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUser">Root User</label>
|
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
id="rootUser"
|
||||||
id="rootUser"
|
name="rootUser"
|
||||||
name="rootUser"
|
value={database.rootUser}
|
||||||
value={database.rootUser}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUserPassword">Root's Password</label>
|
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
isPasswordField
|
||||||
isPasswordField
|
id="rootUserPassword"
|
||||||
id="rootUserPassword"
|
name="rootUserPassword"
|
||||||
name="rootUserPassword"
|
value={database.rootUserPassword}
|
||||||
value={database.rootUserPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,46 +6,40 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">PostgreSQL</div>
|
<div class="title">PostgreSQL</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-10">
|
<div class="space-y-2 px-10">
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="defaultDatabase">Default Database</label>
|
<label for="defaultDatabase" class="text-base font-bold text-stone-100">Default Database</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
required
|
||||||
required
|
readonly={database.defaultDatabase}
|
||||||
readonly={database.defaultDatabase}
|
disabled={database.defaultDatabase}
|
||||||
disabled={database.defaultDatabase}
|
placeholder="eg: mydb"
|
||||||
placeholder="eg: mydb"
|
id="defaultDatabase"
|
||||||
id="defaultDatabase"
|
name="defaultDatabase"
|
||||||
name="defaultDatabase"
|
bind:value={database.defaultDatabase}
|
||||||
bind:value={database.defaultDatabase}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUser">User</label>
|
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
id="dbUser"
|
||||||
id="dbUser"
|
name="dbUser"
|
||||||
name="dbUser"
|
value={database.dbUser}
|
||||||
value={database.dbUser}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUserPassword">Password</label>
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
isPasswordField
|
||||||
isPasswordField
|
id="dbUserPassword"
|
||||||
id="dbUserPassword"
|
name="dbUserPassword"
|
||||||
name="dbUserPassword"
|
value={database.dbUserPassword}
|
||||||
value={database.dbUserPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,33 +6,18 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">Redis</div>
|
<div class="title">Redis</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-10">
|
<div class="space-y-2 px-10">
|
||||||
<!-- <div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUser">User</label>
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
disabled
|
||||||
readonly
|
readonly
|
||||||
disabled
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
isPasswordField
|
||||||
id="dbUser"
|
id="dbUserPassword"
|
||||||
name="dbUser"
|
name="dbUserPassword"
|
||||||
bind:value={database.dbUser}
|
value={database.dbUserPassword}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
<div class="grid grid-cols-3 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>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="grid grid-cols-3 items-center">
|
<!-- <div class="grid grid-cols-3 items-center">
|
||||||
<label for="rootUser">Root User</label>
|
<label for="rootUser">Root User</label>
|
||||||
|
|||||||
@@ -3,30 +3,39 @@ import * as db from '$lib/database';
|
|||||||
import { generateDatabaseConfiguration, ErrorHandler } from '$lib/database';
|
import { generateDatabaseConfiguration, ErrorHandler } from '$lib/database';
|
||||||
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import getPort, { portNumbers } from 'get-port';
|
||||||
|
|
||||||
export const post: RequestHandler = async (event) => {
|
export const post: RequestHandler = async (event) => {
|
||||||
const { status, body, teamId } = await getUserDetails(event);
|
const { status, body, teamId } = await getUserDetails(event);
|
||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
|
const data = await db.prisma.setting.findFirst();
|
||||||
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
const { isPublic, appendOnly = true } = await event.request.json();
|
const { isPublic, appendOnly = true } = await event.request.json();
|
||||||
|
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.setDatabase({ id, isPublic, appendOnly });
|
await db.setDatabase({ id, isPublic, appendOnly });
|
||||||
const database = await db.getDatabase({ id, teamId });
|
const database = await db.getDatabase({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, publicPort } = database;
|
const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database;
|
||||||
const { privatePort } = generateDatabaseConfiguration(database);
|
const { privatePort } = generateDatabaseConfiguration(database);
|
||||||
|
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
|
await db.prisma.database.update({ where: { id }, data: { publicPort } });
|
||||||
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||||
} else {
|
} else {
|
||||||
await stopTcpHttpProxy(destinationDocker, publicPort);
|
await db.prisma.database.update({ where: { id }, data: { publicPort: null } });
|
||||||
|
await stopTcpHttpProxy(destinationDocker, oldPublicPort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 201
|
status: 201,
|
||||||
|
body: {
|
||||||
|
publicPort
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return ErrorHandler(error);
|
return ErrorHandler(error);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const everStarted = await stopDatabase(database);
|
const everStarted = await stopDatabase(database);
|
||||||
if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort);
|
if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort);
|
||||||
await db.setDatabase({ id, isPublic: false });
|
await db.setDatabase({ id, isPublic: false });
|
||||||
|
await db.prisma.database.update({ where: { id }, data: { publicPort: null } });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -61,12 +61,12 @@
|
|||||||
<div class="w-full text-center font-bold">Loading...</div>
|
<div class="w-full text-center font-bold">Loading...</div>
|
||||||
{:else if app.foundByDomain}
|
{:else if app.foundByDomain}
|
||||||
<div class="w-full bg-coolgray-200 text-xs">
|
<div class="w-full bg-coolgray-200 text-xs">
|
||||||
<span class="text-red-500">Domain</span> already configured for
|
<span class="text-red-500">Domain</span> already used for
|
||||||
<span class="text-red-500">{app.foundName}</span>
|
<span class="text-red-500">{app.foundName}</span>
|
||||||
</div>
|
</div>
|
||||||
{:else if app.foundByRepository}
|
{:else if app.foundByRepository}
|
||||||
<div class="w-full bg-coolgray-200 text-xs">
|
<div class="w-full bg-coolgray-200 text-xs">
|
||||||
<span class="text-red-500">Repository</span> already configured for
|
<span class="text-red-500">Repository</span> already used for
|
||||||
<span class="text-red-500">{app.foundName}</span>
|
<span class="text-red-500">{app.foundName}</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -122,83 +122,72 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-center px-6 pb-8">
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
<div class="flex space-x-1 pb-5">
|
||||||
<div class="flex h-8 items-center space-x-2">
|
<div class="title font-bold">Configuration</div>
|
||||||
<div class="text-xl font-bold text-white">Configuration</div>
|
<button
|
||||||
<button
|
type="submit"
|
||||||
type="submit"
|
class="bg-sky-600 hover:bg-sky-500"
|
||||||
class="bg-sky-600 hover:bg-sky-500"
|
class:bg-sky-600={!loading}
|
||||||
class:bg-sky-600={!loading}
|
class:hover:bg-sky-500={!loading}
|
||||||
class:hover:bg-sky-500={!loading}
|
disabled={loading}
|
||||||
disabled={loading}
|
>{loading ? 'Saving...' : 'Save'}
|
||||||
>{loading ? 'Saving...' : 'Save'}
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
|
||||||
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
|
disabled={restarting}
|
||||||
disabled={restarting}
|
on:click|preventDefault={forceRestartProxy}
|
||||||
on:click|preventDefault={forceRestartProxy}
|
>{restarting ? 'Restarting... please wait...' : 'Force restart proxy'}</button
|
||||||
>{restarting ? 'Restarting... please wait...' : 'Force restart proxy'}</button
|
>
|
||||||
>
|
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
|
||||||
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
|
|
||||||
>Scan for applications</button
|
>Scan for applications</button
|
||||||
> -->
|
> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10 ">
|
||||||
<label for="name">Name</label>
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
<div class="col-span-2">
|
<input name="name" placeholder="name" bind:value={destination.name} />
|
||||||
<input name="name" placeholder="name" bind:value={destination.name} />
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="engine">Engine</label>
|
<label for="engine" class="text-base font-bold text-stone-100">Engine</label>
|
||||||
<div class="col-span-2">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
id="engine"
|
||||||
id="engine"
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
name="engine"
|
||||||
name="engine"
|
placeholder="eg: /var/run/docker.sock"
|
||||||
placeholder="eg: /var/run/docker.sock"
|
value={destination.engine}
|
||||||
value={destination.engine}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<!-- <div class="flex items-center">
|
||||||
</div>
|
|
||||||
<!-- <div class="flex items-center">
|
|
||||||
<label for="remoteEngine">Remote Engine?</label>
|
<label for="remoteEngine">Remote Engine?</label>
|
||||||
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
|
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="network">Network</label>
|
<label for="network" class="text-base font-bold text-stone-100">Network</label>
|
||||||
<div class="col-span-2">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
id="network"
|
||||||
id="network"
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
name="network"
|
||||||
name="network"
|
placeholder="default: coolify"
|
||||||
placeholder="default: coolify"
|
value={destination.network}
|
||||||
value={destination.network}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-2 items-center">
|
||||||
</div>
|
<Setting
|
||||||
<div class="flex justify-start">
|
disabled={cannotDisable}
|
||||||
<ul class="mt-2 divide-y divide-stone-800">
|
bind:setting={destination.isCoolifyProxyUsed}
|
||||||
<Setting
|
on:click={changeProxySetting}
|
||||||
disabled={cannotDisable}
|
title="Use Coolify Proxy?"
|
||||||
bind:setting={destination.isCoolifyProxyUsed}
|
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>${
|
||||||
on:click={changeProxySetting}
|
cannotDisable
|
||||||
isPadding={false}
|
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
|
||||||
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>
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="flex justify-center">
|
<!-- <div class="flex justify-center">
|
||||||
{#if payload.isCoolifyProxyUsed}
|
{#if payload.isCoolifyProxyUsed}
|
||||||
{#if state}
|
{#if state}
|
||||||
|
|||||||
@@ -42,5 +42,6 @@
|
|||||||
<span class="arrow-right-applications px-1">></span>
|
<span class="arrow-right-applications px-1">></span>
|
||||||
<span class="pr-2">{destination.name}</span>
|
<span class="pr-2">{destination.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
<LocalDocker bind:destination {settings} {state} />
|
<LocalDocker bind:destination {settings} {state} />
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
await configureCoolifyProxyOn(fqdn);
|
await configureCoolifyProxyOn(fqdn);
|
||||||
await setWwwRedirection(fqdn);
|
await setWwwRedirection(fqdn);
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
if (isHttps) await forceSSLOnApplication({ domain });
|
if (isHttps) await forceSSLOnApplication(domain);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
export const load: Load = async ({ fetch, session }) => {
|
export const load: Load = async ({ fetch, session }) => {
|
||||||
const url = `/index.json`;
|
const url = `/dashboard.json`;
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
let emailEl;
|
let emailEl;
|
||||||
let email, password;
|
let email, password;
|
||||||
|
|
||||||
if (browser && $session.uid) {
|
if (browser && $session.userId) {
|
||||||
goto('/');
|
goto('/');
|
||||||
}
|
}
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
const { teamId } = await post(`/login.json`, { email, password });
|
const { teamId } = await post(`/login.json`, { email: email.toLowerCase(), password });
|
||||||
if (teamId === '0') {
|
if (teamId === '0') {
|
||||||
window.location.replace('/settings');
|
window.location.replace('/settings');
|
||||||
} else {
|
} else {
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-screen flex-col items-center justify-center">
|
<div class="flex h-screen flex-col items-center justify-center">
|
||||||
{#if $session.uid}
|
{#if $session.userId}
|
||||||
<div class="flex justify-center px-4 text-xl font-bold">Already logged in...</div>
|
<div class="flex justify-center px-4 text-xl font-bold">Already logged in...</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex justify-center px-4">
|
<div class="flex justify-center px-4">
|
||||||
@@ -67,6 +67,7 @@
|
|||||||
class:text-stone-600={loading}
|
class:text-stone-600={loading}
|
||||||
class:bg-coollabs={!loading}>{loading ? 'Authenticating...' : 'Login'}</button
|
class:bg-coollabs={!loading}>{loading ? 'Authenticating...' : 'Login'}</button
|
||||||
>
|
>
|
||||||
|
<button on:click|preventDefault={() => goto('/reset')}>Reset password</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
import { enhance, errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
|
|
||||||
<div class="flex justify-center px-6 pb-8">
|
<div class="flex justify-center px-6 pb-8">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
<div class="flex h-8 items-center space-x-2">
|
<div class="flex items-center space-x-2 pb-5">
|
||||||
<div class="text-xl font-bold text-white">Configuration</div>
|
<div class="title font-bold">Configuration</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class:bg-sky-600={!loading}
|
class:bg-sky-600={!loading}
|
||||||
@@ -38,24 +38,20 @@
|
|||||||
: 'Save'}</button
|
: 'Save'}</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="mt-2 grid grid-cols-2 items-center px-10">
|
||||||
<label for="name">Name</label>
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
<div class="col-span-2">
|
<input required name="name" placeholder="name" bind:value={payload.name} />
|
||||||
<input required name="name" placeholder="name" bind:value={payload.name} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="engine">Engine</label>
|
<label for="engine" class="text-base font-bold text-stone-100">Engine</label>
|
||||||
<div class="col-span-2">
|
<input
|
||||||
<input
|
required
|
||||||
required
|
name="engine"
|
||||||
name="engine"
|
placeholder="eg: /var/run/docker.sock"
|
||||||
placeholder="eg: /var/run/docker.sock"
|
bind:value={payload.engine}
|
||||||
bind:value={payload.engine}
|
/>
|
||||||
/>
|
<!-- <Explainer text="You can use remote Docker Engine with over SSH." /> -->
|
||||||
<!-- <Explainer text="You can use remote Docker Engine with over SSH." /> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="flex items-center">
|
<!-- <div class="flex items-center">
|
||||||
<label for="remoteEngine">Remote Docker Engine?</label>
|
<label for="remoteEngine">Remote Docker Engine?</label>
|
||||||
@@ -75,27 +71,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if} -->
|
{/if} -->
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="network">Network</label>
|
<label for="network" class="text-base font-bold text-stone-100">Network</label>
|
||||||
<div class="col-span-2">
|
<input required name="network" placeholder="default: coolify" bind:value={payload.network} />
|
||||||
<input
|
|
||||||
required
|
|
||||||
name="network"
|
|
||||||
placeholder="default: coolify"
|
|
||||||
bind:value={payload.network}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-start">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<ul class="mt-2 divide-y divide-stone-800">
|
<Setting
|
||||||
<Setting
|
bind:setting={payload.isCoolifyProxyUsed}
|
||||||
bind:setting={payload.isCoolifyProxyUsed}
|
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
|
||||||
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
|
title="Use Coolify Proxy?"
|
||||||
isPadding={false}
|
description="This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker).<br><br>Databases will have their own proxy."
|
||||||
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 (recommended for Docker). Databases will have their own proxy."
|
|
||||||
/>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,26 +24,24 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-center pb-8">
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
<div class="flex justify-center pb-8">
|
||||||
<div class="flex h-8 items-center space-x-2">
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
<div class="text-xl font-bold text-white">Configuration</div>
|
<div class="flex h-8 items-center space-x-2">
|
||||||
<button type="submit" class="bg-orange-600 hover:bg-orange-500">Save</button>
|
<div class="text-xl font-bold text-white">Configuration</div>
|
||||||
</div>
|
<button type="submit" class="bg-orange-600 hover:bg-orange-500">Save</button>
|
||||||
<div class="grid grid-cols-3 items-center">
|
</div>
|
||||||
<label for="type">Type</label>
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="type" class="text-base font-bold text-stone-100">Type</label>
|
||||||
|
|
||||||
<div class="col-span-2">
|
|
||||||
<select name="type" id="type" class="w-96" bind:value={gitSource.type}>
|
<select name="type" id="type" class="w-96" bind:value={gitSource.type}>
|
||||||
<option value="github">GitHub</option>
|
<option value="github">GitHub</option>
|
||||||
<option value="gitlab">GitLab</option>
|
<option value="gitlab">GitLab</option>
|
||||||
<option value="bitbucket">BitBucket</option>
|
<option value="bitbucket">BitBucket</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<div class="grid grid-cols-3 items-center">
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
<label for="name">Name</label>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<input
|
<input
|
||||||
name="name"
|
name="name"
|
||||||
id="name"
|
id="name"
|
||||||
@@ -53,11 +51,9 @@
|
|||||||
bind:value={gitSource.name}
|
bind:value={gitSource.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="htmlUrl">HTML URL</label>
|
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
|
||||||
<div class="col-span-2">
|
|
||||||
<input
|
<input
|
||||||
type="url"
|
type="url"
|
||||||
name="htmlUrl"
|
name="htmlUrl"
|
||||||
@@ -67,10 +63,8 @@
|
|||||||
bind:value={gitSource.htmlUrl}
|
bind:value={gitSource.htmlUrl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<div class="grid grid-cols-3 items-center">
|
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
||||||
<label for="apiUrl">API URL</label>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<input
|
<input
|
||||||
name="apiUrl"
|
name="apiUrl"
|
||||||
type="url"
|
type="url"
|
||||||
@@ -80,10 +74,15 @@
|
|||||||
bind:value={gitSource.apiUrl}
|
bind:value={gitSource.apiUrl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-2 px-10">
|
||||||
<div class="grid grid-cols-3">
|
<div class="flex flex-col">
|
||||||
<label for="organization" class="pt-2">Organization</label>
|
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
|
||||||
<div class="col-span-2">
|
>Organization</label
|
||||||
|
>
|
||||||
|
<Explainer
|
||||||
|
text="Fill it if you would like to use an organization's as your Git Source. Otherwise your user will be used."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
name="organization"
|
name="organization"
|
||||||
id="organization"
|
id="organization"
|
||||||
@@ -91,11 +90,7 @@
|
|||||||
bind:value={gitSource.organization}
|
bind:value={gitSource.organization}
|
||||||
bind:this={organizationEl}
|
bind:this={organizationEl}
|
||||||
/>
|
/>
|
||||||
<Explainer
|
|
||||||
text="Fill it if you would like to use an organization's as your Git Source. Otherwise your
|
|
||||||
user will be used."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,56 +27,47 @@
|
|||||||
<div class="text-xl font-bold text-white">Configuration</div>
|
<div class="text-xl font-bold text-white">Configuration</div>
|
||||||
<button type="submit" class="bg-orange-600 hover:bg-orange-500">Save</button>
|
<button type="submit" class="bg-orange-600 hover:bg-orange-500">Save</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="type">Type</label>
|
<label for="type" class="text-base font-bold text-stone-100">Type</label>
|
||||||
|
<select name="type" id="type" class="w-96" bind:value={gitSource.type}>
|
||||||
<div class="col-span-2">
|
<option value="github">GitHub</option>
|
||||||
<select name="type" id="type" class="w-96" bind:value={gitSource.type}>
|
<option value="gitlab">GitLab</option>
|
||||||
<option value="github">GitHub</option>
|
<option value="bitbucket">BitBucket</option>
|
||||||
<option value="gitlab">GitLab</option>
|
</select>
|
||||||
<option value="bitbucket">BitBucket</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="name">Name</label>
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
<div class="col-span-2">
|
<input
|
||||||
<input
|
name="name"
|
||||||
name="name"
|
id="name"
|
||||||
id="name"
|
placeholder="GitHub.com"
|
||||||
placeholder="GitHub.com"
|
required
|
||||||
required
|
bind:this={nameEl}
|
||||||
bind:this={nameEl}
|
bind:value={gitSource.name}
|
||||||
bind:value={gitSource.name}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="htmlUrl">HTML URL</label>
|
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
|
||||||
<div class="col-span-2">
|
<input
|
||||||
<input
|
type="url"
|
||||||
type="url"
|
name="htmlUrl"
|
||||||
name="htmlUrl"
|
id="htmlUrl"
|
||||||
id="htmlUrl"
|
placeholder="eg: https://github.com"
|
||||||
placeholder="eg: https://github.com"
|
required
|
||||||
required
|
bind:value={gitSource.htmlUrl}
|
||||||
bind:value={gitSource.htmlUrl}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="apiUrl">API URL</label>
|
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
||||||
<div class="col-span-2">
|
<input
|
||||||
<input
|
name="apiUrl"
|
||||||
name="apiUrl"
|
type="url"
|
||||||
type="url"
|
id="apiUrl"
|
||||||
id="apiUrl"
|
placeholder="eg: https://api.github.com"
|
||||||
placeholder="eg: https://api.github.com"
|
required
|
||||||
required
|
bind:value={gitSource.apiUrl}
|
||||||
bind:value={gitSource.apiUrl}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
26
src/routes/reset/index.json.ts
Normal file
26
src/routes/reset/index.json.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import * as db from '$lib/database';
|
||||||
|
|
||||||
|
export const get: RequestHandler = async () => {
|
||||||
|
const users = await db.prisma.user.findMany({});
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
users
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const post: RequestHandler = async (event) => {
|
||||||
|
const { secretKey } = await event.request.json();
|
||||||
|
if (secretKey !== process.env.COOLIFY_SECRET_KEY) {
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
body: {
|
||||||
|
error: 'Invalid secret key.'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: 200
|
||||||
|
};
|
||||||
|
};
|
||||||
96
src/routes/reset/index.svelte
Normal file
96
src/routes/reset/index.svelte
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { get, post } from '$lib/api';
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import { errorNotification } from '$lib/form';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
|
|
||||||
|
let secretKey;
|
||||||
|
let password = false;
|
||||||
|
let users = [];
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
try {
|
||||||
|
await post(`/reset.json`, { secretKey });
|
||||||
|
password = true;
|
||||||
|
const data = await get('/reset.json');
|
||||||
|
users = data.users;
|
||||||
|
return;
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function resetPassword(user) {
|
||||||
|
try {
|
||||||
|
await post(`/reset/password.json`, { secretKey, user });
|
||||||
|
toast.push('Password reset done.');
|
||||||
|
return;
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="icons fixed top-0 left-0 m-3 cursor-pointer" on:click={() => goto('/')}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<line x1="5" y1="12" x2="19" y2="12" />
|
||||||
|
<line x1="5" y1="12" x2="11" y2="18" />
|
||||||
|
<line x1="5" y1="12" x2="11" y2="6" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="pb-10 pt-24 text-center text-4xl font-bold">Reset Password</div>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
{#if password}
|
||||||
|
<table class="mx-2 text-left">
|
||||||
|
<thead class="mb-2">
|
||||||
|
<tr>
|
||||||
|
<th class="px-2">Email</th>
|
||||||
|
<th>New password</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each users as user}
|
||||||
|
<tr>
|
||||||
|
<td class="px-2">{user.email}</td>
|
||||||
|
<td class="flex space-x-2">
|
||||||
|
<input
|
||||||
|
id="newPassword"
|
||||||
|
name="newPassword"
|
||||||
|
bind:value={user.newPassword}
|
||||||
|
placeholder="Super secure new password"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="mx-auto my-4 w-32 bg-coollabs hover:bg-coollabs-100"
|
||||||
|
on:click={() => resetPassword(user)}>Reset</button
|
||||||
|
></td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{:else}
|
||||||
|
<form class="flex flex-col" on:submit|preventDefault={handleSubmit}>
|
||||||
|
<div class="text-center text-2xl py-2 font-bold">Secret Key</div>
|
||||||
|
<CopyPasswordField
|
||||||
|
isPasswordField={true}
|
||||||
|
id="secretKey"
|
||||||
|
name="secretKey"
|
||||||
|
bind:value={secretKey}
|
||||||
|
placeholder="You can find it in ~/coolify/.env (COOLIFY_SECRET_KEY)"
|
||||||
|
/>
|
||||||
|
<button type="submit" class="bg-coollabs hover:bg-coollabs-100 mx-auto w-32 my-4"
|
||||||
|
>Submit</button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
27
src/routes/reset/password.json.ts
Normal file
27
src/routes/reset/password.json.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import * as db from '$lib/database';
|
||||||
|
import { ErrorHandler, hashPassword } from '$lib/database';
|
||||||
|
|
||||||
|
export const post: RequestHandler = async (event) => {
|
||||||
|
const { secretKey, user } = await event.request.json();
|
||||||
|
if (secretKey !== process.env.COOLIFY_SECRET_KEY) {
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
body: {
|
||||||
|
error: 'Invalid secret key.'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const hashedPassword = await hashPassword(user.newPassword);
|
||||||
|
await db.prisma.user.update({
|
||||||
|
where: { email: user.email },
|
||||||
|
data: { password: hashedPassword }
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
status: 200
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorHandler(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -7,42 +7,36 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">MinIO Server</div>
|
<div class="title">MinIO Server</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="rootUser">Root User</label>
|
<label for="rootUser">Root User</label>
|
||||||
<div class="col-span-2 ">
|
<input
|
||||||
<input
|
name="rootUser"
|
||||||
name="rootUser"
|
id="rootUser"
|
||||||
id="rootUser"
|
placeholder="User to login"
|
||||||
placeholder="User to login"
|
value={service.minio.rootUser}
|
||||||
value={service.minio.rootUser}
|
disabled
|
||||||
disabled
|
readonly
|
||||||
readonly
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="rootUserPassword">Root's Password</label>
|
<label for="rootUserPassword">Root's Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
id="rootUserPassword"
|
||||||
id="rootUserPassword"
|
isPasswordField
|
||||||
isPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
name="rootUserPassword"
|
||||||
name="rootUserPassword"
|
value={service.minio.rootUserPassword}
|
||||||
value={service.minio.rootUserPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="publicPort">API Port</label>
|
<label for="publicPort">API Port</label>
|
||||||
<div class="col-span-2 ">
|
<input
|
||||||
<input
|
name="publicPort"
|
||||||
name="publicPort"
|
id="publicPort"
|
||||||
id="publicPort"
|
value={service.minio.publicPort}
|
||||||
value={service.minio.publicPort}
|
disabled
|
||||||
disabled
|
readonly
|
||||||
readonly
|
placeholder="Generated automatically after start"
|
||||||
placeholder="Generated automatically after start"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,86 +7,74 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">Plausible Analytics</div>
|
<div class="title">Plausible Analytics</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="email">Email Address</label>
|
<label for="email">Email Address</label>
|
||||||
<div class="col-span-2">
|
<input
|
||||||
<input
|
name="email"
|
||||||
name="email"
|
id="email"
|
||||||
id="email"
|
disabled={readOnly}
|
||||||
disabled={readOnly}
|
readonly={readOnly}
|
||||||
readonly={readOnly}
|
placeholder="Email address"
|
||||||
placeholder="Email address"
|
bind:value={service.plausibleAnalytics.email}
|
||||||
bind:value={service.plausibleAnalytics.email}
|
required
|
||||||
required
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="username">Username</label>
|
<label for="username">Username</label>
|
||||||
<div class="col-span-2">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
name="username"
|
||||||
name="username"
|
id="username"
|
||||||
id="username"
|
disabled={readOnly}
|
||||||
disabled={readOnly}
|
readonly={readOnly}
|
||||||
readonly={readOnly}
|
placeholder="User to login"
|
||||||
placeholder="User to login"
|
bind:value={service.plausibleAnalytics.username}
|
||||||
bind:value={service.plausibleAnalytics.username}
|
required
|
||||||
required
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
id="password"
|
||||||
id="password"
|
isPasswordField
|
||||||
isPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
name="password"
|
||||||
name="password"
|
value={service.plausibleAnalytics.password}
|
||||||
value={service.plausibleAnalytics.password}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">PostgreSQL</div>
|
<div class="title">PostgreSQL</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="postgresqlUser">Username</label>
|
<label for="postgresqlUser">Username</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
name="postgresqlUser"
|
||||||
name="postgresqlUser"
|
id="postgresqlUser"
|
||||||
id="postgresqlUser"
|
value={service.plausibleAnalytics.postgresqlUser}
|
||||||
value={service.plausibleAnalytics.postgresqlUser}
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="postgresqlPassword">Password</label>
|
<label for="postgresqlPassword">Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
id="postgresqlPassword"
|
||||||
id="postgresqlPassword"
|
isPasswordField
|
||||||
isPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
name="postgresqlPassword"
|
||||||
name="postgresqlPassword"
|
value={service.plausibleAnalytics.postgresqlPassword}
|
||||||
value={service.plausibleAnalytics.postgresqlPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="postgresqlDatabase">Database</label>
|
<label for="postgresqlDatabase">Database</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
name="postgresqlDatabase"
|
||||||
name="postgresqlDatabase"
|
id="postgresqlDatabase"
|
||||||
id="postgresqlDatabase"
|
value={service.plausibleAnalytics.postgresqlDatabase}
|
||||||
value={service.plausibleAnalytics.postgresqlDatabase}
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="grid grid-cols-3 items-center">
|
<!-- <div class="grid grid-cols-3 items-center">
|
||||||
<label for="postgresqlPublicPort">Public Port</label>
|
<label for="postgresqlPublicPort">Public Port</label>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
import MinIo from './_MinIO.svelte';
|
import MinIo from './_MinIO.svelte';
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let loadingVerification = false;
|
let loadingVerification = false;
|
||||||
|
let dualCerts = service.dualCerts;
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
loading = true;
|
loading = true;
|
||||||
@@ -42,9 +44,20 @@
|
|||||||
loadingVerification = false;
|
loadingVerification = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function changeSettings(name) {
|
||||||
|
try {
|
||||||
|
if (name === 'dualCerts') {
|
||||||
|
dualCerts = !dualCerts;
|
||||||
|
}
|
||||||
|
await post(`/services/${id}/settings.json`, { dualCerts });
|
||||||
|
return toast.push('Settings saved.');
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
<div class="mx-auto max-w-4xl px-6 pb-12">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||||
<div class="flex space-x-1 pb-5 font-bold">
|
<div class="flex space-x-1 pb-5 font-bold">
|
||||||
<div class="title">General</div>
|
<div class="title">General</div>
|
||||||
@@ -57,20 +70,16 @@
|
|||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if service.type === 'plausibleanalytics' && isRunning}
|
{#if service.type === 'plausibleanalytics' && isRunning}
|
||||||
<button
|
<button on:click|preventDefault={setEmailsToVerified} disabled={loadingVerification}
|
||||||
on:click|preventDefault={setEmailsToVerified}
|
|
||||||
class:bg-pink-600={!loadingVerification}
|
|
||||||
class:hover:bg-pink-500={!loadingVerification}
|
|
||||||
disabled={loadingVerification}
|
|
||||||
>{loadingVerification ? 'Verifying' : 'Verify emails without SMTP'}</button
|
>{loadingVerification ? 'Verifying' : 'Verify emails without SMTP'}</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2">
|
||||||
<div class="mt-2 grid grid-cols-3 items-center">
|
<div class="mt-2 grid grid-cols-2 items-center px-10">
|
||||||
<label for="name">Name</label>
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
<div class="col-span-2 ">
|
<div>
|
||||||
<input
|
<input
|
||||||
readonly={!$session.isAdmin}
|
readonly={!$session.isAdmin}
|
||||||
name="name"
|
name="name"
|
||||||
@@ -81,9 +90,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="destination">Destination</label>
|
<label for="destination" class="text-base font-bold text-stone-100">Destination</label>
|
||||||
<div class="col-span-2">
|
<div>
|
||||||
{#if service.destinationDockerId}
|
{#if service.destinationDockerId}
|
||||||
<div class="no-underline">
|
<div class="no-underline">
|
||||||
<input
|
<input
|
||||||
@@ -96,23 +105,34 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3">
|
<div class="grid grid-cols-2 px-10">
|
||||||
<label for="fqdn" class="pt-2">Domain (FQDN)</label>
|
<div class="flex-col ">
|
||||||
<div class="col-span-2 ">
|
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</label>
|
||||||
<CopyPasswordField
|
|
||||||
placeholder="eg: https://analytics.coollabs.io"
|
|
||||||
readonly={!$session.isAdmin && !isRunning}
|
|
||||||
disabled={!$session.isAdmin || isRunning}
|
|
||||||
name="fqdn"
|
|
||||||
id="fqdn"
|
|
||||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
|
||||||
bind:value={service.fqdn}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<Explainer
|
<Explainer
|
||||||
text="If you specify <span class='text-pink-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-pink-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
|
text="If you specify <span class='text-pink-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-pink-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<CopyPasswordField
|
||||||
|
placeholder="eg: https://analytics.coollabs.io"
|
||||||
|
readonly={!$session.isAdmin && !isRunning}
|
||||||
|
disabled={!$session.isAdmin || isRunning}
|
||||||
|
name="fqdn"
|
||||||
|
id="fqdn"
|
||||||
|
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||||
|
bind:value={service.fqdn}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<Setting
|
||||||
|
disabled={isRunning}
|
||||||
|
dataTooltip="Must be stopped to modify."
|
||||||
|
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-pink-600'>both DNS entries</span> set in advance.<br><br>Service needs to be restarted."
|
||||||
|
on:click={() => !isRunning && changeSettings('dualCerts')}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if service.type === 'plausibleanalytics'}
|
{#if service.type === 'plausibleanalytics'}
|
||||||
<PlausibleAnalytics bind:service {readOnly} />
|
<PlausibleAnalytics bind:service {readOnly} />
|
||||||
|
|||||||
@@ -7,16 +7,14 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">VSCode Server</div>
|
<div class="title">VSCode Server</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
id="password"
|
||||||
id="password"
|
isPasswordField
|
||||||
isPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
name="password"
|
||||||
name="password"
|
value={service.vscodeserver.password}
|
||||||
value={service.vscodeserver.password}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,85 +10,73 @@
|
|||||||
<div class="title">Wordpress</div>
|
<div class="title">Wordpress</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="extraConfig">Extra Config</label>
|
<label for="extraConfig">Extra Config</label>
|
||||||
<div class="col-span-2 ">
|
<textarea
|
||||||
<textarea
|
disabled={isRunning}
|
||||||
disabled={isRunning}
|
readonly={isRunning}
|
||||||
readonly={isRunning}
|
class:resize-none={isRunning}
|
||||||
class:resize-none={isRunning}
|
rows={isRunning ? 1 : 5}
|
||||||
rows={isRunning ? 1 : 5}
|
name="extraConfig"
|
||||||
name="extraConfig"
|
id="extraConfig"
|
||||||
id="extraConfig"
|
placeholder={!isRunning
|
||||||
placeholder={!isRunning
|
? `eg:
|
||||||
? `eg:
|
|
||||||
|
|
||||||
define('WP_ALLOW_MULTISITE', true);
|
define('WP_ALLOW_MULTISITE', true);
|
||||||
define('MULTISITE', true);
|
define('MULTISITE', true);
|
||||||
define('SUBDOMAIN_INSTALL', false);`
|
define('SUBDOMAIN_INSTALL', false);`
|
||||||
: null}>{service.wordpress.extraConfig}</textarea
|
: null}>{service.wordpress.extraConfig || 'N/A'}</textarea
|
||||||
>
|
>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">MySQL</div>
|
<div class="title">MySQL</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="mysqlDatabase">Database</label>
|
<label for="mysqlDatabase">Database</label>
|
||||||
<div class="col-span-2 ">
|
<input
|
||||||
<input
|
name="mysqlDatabase"
|
||||||
name="mysqlDatabase"
|
id="mysqlDatabase"
|
||||||
id="mysqlDatabase"
|
required
|
||||||
required
|
readonly={readOnly}
|
||||||
readonly={readOnly}
|
disabled={readOnly}
|
||||||
disabled={readOnly}
|
bind:value={service.wordpress.mysqlDatabase}
|
||||||
bind:value={service.wordpress.mysqlDatabase}
|
placeholder="eg: wordpress_db"
|
||||||
placeholder="eg: wordpress_db"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="mysqlRootUser">Root User</label>
|
<label for="mysqlRootUser">Root User</label>
|
||||||
<div class="col-span-2 ">
|
<input
|
||||||
<input
|
name="mysqlRootUser"
|
||||||
name="mysqlRootUser"
|
id="mysqlRootUser"
|
||||||
id="mysqlRootUser"
|
placeholder="MySQL Root User"
|
||||||
placeholder="MySQL Root User"
|
value={service.wordpress.mysqlRootUser}
|
||||||
value={service.wordpress.mysqlRootUser}
|
disabled
|
||||||
disabled
|
readonly
|
||||||
readonly
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="mysqlRootUserPassword">Root's Password</label>
|
<label for="mysqlRootUserPassword">Root's Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
id="mysqlRootUserPassword"
|
||||||
id="mysqlRootUserPassword"
|
isPasswordField
|
||||||
isPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
name="mysqlRootUserPassword"
|
||||||
name="mysqlRootUserPassword"
|
value={service.wordpress.mysqlRootUserPassword}
|
||||||
value={service.wordpress.mysqlRootUserPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="mysqlUser">User</label>
|
<label for="mysqlUser">User</label>
|
||||||
<div class="col-span-2 ">
|
<input name="mysqlUser" id="mysqlUser" value={service.wordpress.mysqlUser} disabled readonly />
|
||||||
<input name="mysqlUser" id="mysqlUser" value={service.wordpress.mysqlUser} disabled readonly />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="mysqlPassword">Password</label>
|
<label for="mysqlPassword">Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
id="mysqlPassword"
|
||||||
id="mysqlPassword"
|
isPasswordField
|
||||||
isPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
name="mysqlPassword"
|
||||||
name="mysqlPassword"
|
value={service.wordpress.mysqlPassword}
|
||||||
value={service.wordpress.mysqlPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
return {
|
return {
|
||||||
status: found ? 500 : 200,
|
status: found ? 500 : 200,
|
||||||
body: {
|
body: {
|
||||||
error: found && `Domain ${getDomain(fqdn)} is already configured`
|
error: found && `Domain ${getDomain(fqdn).replace('www.', '')} is already used.`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const { version } = await event.request.json();
|
const { version } = await event.request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.setService({ id, version });
|
await db.setServiceVersion({ id, version });
|
||||||
return {
|
return {
|
||||||
status: 201
|
status: 201
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import {
|
|||||||
configureSimpleServiceProxyOn,
|
configureSimpleServiceProxyOn,
|
||||||
reloadHaproxy,
|
reloadHaproxy,
|
||||||
setWwwRedirection,
|
setWwwRedirection,
|
||||||
startHttpProxy,
|
startHttpProxy
|
||||||
startTcpProxy
|
|
||||||
} from '$lib/haproxy';
|
} from '$lib/haproxy';
|
||||||
import getPort from 'get-port';
|
import getPort, { portNumbers } from 'get-port';
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
import { ErrorHandler } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||||
@@ -35,14 +34,20 @@ export const post: RequestHandler = async (event) => {
|
|||||||
minio: { rootUser, rootUserPassword }
|
minio: { rootUser, rootUserPassword }
|
||||||
} = service;
|
} = service;
|
||||||
|
|
||||||
|
const data = await db.prisma.setting.findFirst();
|
||||||
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
const host = getEngine(destinationDocker.engine);
|
||||||
const publicPort = await getPort();
|
|
||||||
|
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
|
||||||
|
|
||||||
const consolePort = 9001;
|
const consolePort = 9001;
|
||||||
const apiPort = 9000;
|
const apiPort = 9000;
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await stopTcpHttpProxy(destinationDocker, publicPort);
|
await stopTcpHttpProxy(destinationDocker, publicPort);
|
||||||
await configureSimpleServiceProxyOff({ domain });
|
await configureSimpleServiceProxyOff(fqdn);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await configureSimpleServiceProxyOff({ domain });
|
await configureSimpleServiceProxyOff(fqdn);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
postgresql: {
|
postgresql: {
|
||||||
volume: `${plausibleDbId}-postgresql-data:/var/lib/postgresql/data`,
|
volume: `${plausibleDbId}-postgresql-data:/bitnami/postgresql/`,
|
||||||
image: 'bitnami/postgresql:13.2.0',
|
image: 'bitnami/postgresql:13.2.0',
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
POSTGRESQL_PASSWORD: postgresqlPassword,
|
POSTGRESQL_PASSWORD: postgresqlPassword,
|
||||||
@@ -136,7 +136,6 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
|||||||
'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run"',
|
'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run"',
|
||||||
networks: [network],
|
networks: [network],
|
||||||
environment: config.plausibleAnalytics.environmentVariables,
|
environment: config.plausibleAnalytics.environmentVariables,
|
||||||
volumes: [config.postgresql.volume],
|
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
depends_on: [`${id}-postgresql`, `${id}-clickhouse`],
|
depends_on: [`${id}-postgresql`, `${id}-clickhouse`],
|
||||||
labels: makeLabelForServices('plausibleAnalytics')
|
labels: makeLabelForServices('plausibleAnalytics')
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await configureSimpleServiceProxyOff({ domain });
|
await configureSimpleServiceProxyOff(fqdn);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/routes/services/[id]/settings.json.ts
Normal file
19
src/routes/services/[id]/settings.json.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { getUserDetails } from '$lib/common';
|
||||||
|
import * as db from '$lib/database';
|
||||||
|
import { ErrorHandler } from '$lib/database';
|
||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const post: RequestHandler = async (event) => {
|
||||||
|
const { teamId, status, body } = await getUserDetails(event);
|
||||||
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
|
const { id } = event.params;
|
||||||
|
const { dualCerts } = await event.request.json();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.setServiceSettings({ id, dualCerts });
|
||||||
|
return { status: 201 };
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorHandler(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -28,7 +28,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await configureSimpleServiceProxyOff({ domain });
|
await configureSimpleServiceProxyOff(fqdn);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await configureSimpleServiceProxyOff({ domain });
|
await configureSimpleServiceProxyOff(fqdn);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await configureSimpleServiceProxyOff({ domain });
|
await configureSimpleServiceProxyOff(fqdn);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
return {
|
return {
|
||||||
status: found ? 500 : 200,
|
status: found ? 500 : 200,
|
||||||
body: {
|
body: {
|
||||||
error: found && `Domain ${fqdn} is already configured`
|
error: found && `Domain ${fqdn.replace('www.', '')} is already used.`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ import { getDomain, getUserDetails } from '$lib/common';
|
|||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { listSettings, ErrorHandler } from '$lib/database';
|
import { listSettings, ErrorHandler } from '$lib/database';
|
||||||
import {
|
import {
|
||||||
checkContainer,
|
|
||||||
configureCoolifyProxyOff,
|
configureCoolifyProxyOff,
|
||||||
configureCoolifyProxyOn,
|
configureCoolifyProxyOn,
|
||||||
forceSSLOffApplication,
|
|
||||||
forceSSLOnApplication,
|
forceSSLOnApplication,
|
||||||
reloadHaproxy,
|
reloadHaproxy,
|
||||||
removeWwwRedirection,
|
removeWwwRedirection,
|
||||||
@@ -15,6 +13,7 @@ import {
|
|||||||
} from '$lib/haproxy';
|
} from '$lib/haproxy';
|
||||||
import { letsEncrypt } from '$lib/letsencrypt';
|
import { letsEncrypt } from '$lib/letsencrypt';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import { promises as dns } from 'dns';
|
||||||
|
|
||||||
export const get: RequestHandler = async (event) => {
|
export const get: RequestHandler = async (event) => {
|
||||||
const { status, body } = await getUserDetails(event);
|
const { status, body } = await getUserDetails(event);
|
||||||
@@ -45,14 +44,24 @@ export const del: RequestHandler = async (event) => {
|
|||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const { fqdn } = await event.request.json();
|
const { fqdn } = await event.request.json();
|
||||||
|
let ip;
|
||||||
|
console.log(fqdn);
|
||||||
|
try {
|
||||||
|
ip = await dns.resolve(fqdn);
|
||||||
|
} catch (error) {
|
||||||
|
// Do not care.
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
||||||
await configureCoolifyProxyOff(fqdn);
|
await configureCoolifyProxyOff(fqdn);
|
||||||
await removeWwwRedirection(domain);
|
await removeWwwRedirection(domain);
|
||||||
return {
|
return {
|
||||||
status: 201
|
status: 200,
|
||||||
|
body: {
|
||||||
|
message: 'Domain removed',
|
||||||
|
redirect: ip ? `http://${ip[0]}:3000/settings` : undefined
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return ErrorHandler(error);
|
return ErrorHandler(error);
|
||||||
@@ -69,16 +78,20 @@ export const post: RequestHandler = async (event) => {
|
|||||||
};
|
};
|
||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const { fqdn, isRegistrationEnabled } = await event.request.json();
|
const { fqdn, isRegistrationEnabled, dualCerts, minPort, maxPort } = await event.request.json();
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
fqdn: oldFqdn,
|
fqdn: oldFqdn,
|
||||||
isRegistrationEnabled: oldIsRegistrationEnabled
|
isRegistrationEnabled: oldIsRegistrationEnabled,
|
||||||
|
dualCerts: oldDualCerts
|
||||||
} = await db.listSettings();
|
} = await db.listSettings();
|
||||||
if (oldIsRegistrationEnabled !== isRegistrationEnabled) {
|
if (oldIsRegistrationEnabled !== isRegistrationEnabled) {
|
||||||
await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled } });
|
await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled } });
|
||||||
}
|
}
|
||||||
|
if (oldDualCerts !== dualCerts) {
|
||||||
|
await db.prisma.setting.update({ where: { id }, data: { dualCerts } });
|
||||||
|
}
|
||||||
if (oldFqdn && oldFqdn !== fqdn) {
|
if (oldFqdn && oldFqdn !== fqdn) {
|
||||||
if (oldFqdn) {
|
if (oldFqdn) {
|
||||||
const oldDomain = getDomain(oldFqdn);
|
const oldDomain = getDomain(oldFqdn);
|
||||||
@@ -93,9 +106,9 @@ export const post: RequestHandler = async (event) => {
|
|||||||
if (domain) {
|
if (domain) {
|
||||||
await configureCoolifyProxyOn(fqdn);
|
await configureCoolifyProxyOn(fqdn);
|
||||||
await setWwwRedirection(fqdn);
|
await setWwwRedirection(fqdn);
|
||||||
if (isHttps && !dev) {
|
if (isHttps) {
|
||||||
await letsEncrypt({ domain, isCoolify: true });
|
await letsEncrypt({ domain, isCoolify: true });
|
||||||
await forceSSLOnApplication({ domain });
|
await forceSSLOnApplication(domain);
|
||||||
await reloadHaproxy('/var/run/docker.sock');
|
await reloadHaproxy('/var/run/docker.sock');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +119,9 @@ export const post: RequestHandler = async (event) => {
|
|||||||
data: { isCoolifyProxyUsed: true }
|
data: { isCoolifyProxyUsed: true }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (minPort && maxPort) {
|
||||||
|
await db.prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 201
|
status: 201
|
||||||
|
|||||||
@@ -30,10 +30,16 @@
|
|||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
|
|
||||||
let isRegistrationEnabled = settings.isRegistrationEnabled;
|
let isRegistrationEnabled = settings.isRegistrationEnabled;
|
||||||
|
let dualCerts = settings.dualCerts;
|
||||||
|
|
||||||
|
let minPort = settings.minPort;
|
||||||
|
let maxPort = settings.maxPort;
|
||||||
|
|
||||||
let fqdn = settings.fqdn;
|
let fqdn = settings.fqdn;
|
||||||
let isFqdnSet = settings.fqdn;
|
let isFqdnSet = !!settings.fqdn;
|
||||||
let loading = {
|
let loading = {
|
||||||
save: false,
|
save: false,
|
||||||
remove: false
|
remove: false
|
||||||
@@ -43,8 +49,8 @@
|
|||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
loading.remove = true;
|
loading.remove = true;
|
||||||
try {
|
try {
|
||||||
await del(`/settings.json`, { fqdn });
|
const { redirect } = await del(`/settings.json`, { fqdn });
|
||||||
return window.location.reload();
|
return redirect ? window.location.replace(redirect) : window.location.reload();
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -57,7 +63,11 @@
|
|||||||
if (name === 'isRegistrationEnabled') {
|
if (name === 'isRegistrationEnabled') {
|
||||||
isRegistrationEnabled = !isRegistrationEnabled;
|
isRegistrationEnabled = !isRegistrationEnabled;
|
||||||
}
|
}
|
||||||
return await post(`/settings.json`, { isRegistrationEnabled });
|
if (name === 'dualCerts') {
|
||||||
|
dualCerts = !dualCerts;
|
||||||
|
}
|
||||||
|
await post(`/settings.json`, { isRegistrationEnabled, dualCerts });
|
||||||
|
return toast.push('Settings saved.');
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
@@ -68,7 +78,11 @@
|
|||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
await post(`/settings/check.json`, { fqdn });
|
await post(`/settings/check.json`, { fqdn });
|
||||||
await post(`/settings.json`, { fqdn });
|
await post(`/settings.json`, { fqdn });
|
||||||
return window.location.reload();
|
}
|
||||||
|
if (minPort !== settings.minPort || maxPort !== settings.maxPort) {
|
||||||
|
await post(`/settings.json`, { minPort, maxPort });
|
||||||
|
settings.minPort = minPort;
|
||||||
|
settings.maxPort = maxPort;
|
||||||
}
|
}
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@@ -82,15 +96,15 @@
|
|||||||
<div class="mr-4 text-2xl tracking-tight">Settings</div>
|
<div class="mr-4 text-2xl tracking-tight">Settings</div>
|
||||||
</div>
|
</div>
|
||||||
{#if $session.teamId === '0'}
|
{#if $session.teamId === '0'}
|
||||||
<div class="mx-auto max-w-2xl">
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
<form on:submit|preventDefault={handleSubmit}>
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 py-6">
|
||||||
<div class="title">Global Settings</div>
|
<div class="title font-bold">Global Settings</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading.save}
|
disabled={loading.save}
|
||||||
class:bg-green-600={!loading.save}
|
class:bg-yellow-500={!loading.save}
|
||||||
class:hover:bg-green-500={!loading.save}
|
class:hover:bg-yellow-400={!loading.save}
|
||||||
class="mx-2 ">{loading.save ? 'Saving...' : 'Save'}</button
|
class="mx-2 ">{loading.save ? 'Saving...' : 'Save'}</button
|
||||||
>
|
>
|
||||||
{#if isFqdnSet}
|
{#if isFqdnSet}
|
||||||
@@ -103,10 +117,15 @@
|
|||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 sm:px-6">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
<div class="flex space-x-4 py-4 px-4">
|
<div class="grid grid-cols-2 items-start">
|
||||||
<p class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</p>
|
<div class="flex-col">
|
||||||
<div class="justify-center">
|
<div class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</div>
|
||||||
|
<Explainer
|
||||||
|
text="If you specify <span class='text-yellow-500 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-yellow-500 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="justify-start text-left">
|
||||||
<input
|
<input
|
||||||
bind:value={fqdn}
|
bind:value={fqdn}
|
||||||
readonly={!$session.isAdmin || isFqdnSet}
|
readonly={!$session.isAdmin || isFqdnSet}
|
||||||
@@ -115,60 +134,85 @@
|
|||||||
id="fqdn"
|
id="fqdn"
|
||||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||||
placeholder="eg: https://coolify.io"
|
placeholder="eg: https://coolify.io"
|
||||||
required
|
|
||||||
/>
|
|
||||||
<Explainer
|
|
||||||
text="If you specify <span class='text-green-600 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-600 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa."
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="mt-2 divide-y divide-stone-800">
|
<div class="grid grid-cols-2 items-start py-6">
|
||||||
|
<div class="flex-col">
|
||||||
|
<div class="pt-2 text-base font-bold text-stone-100">Public Port Range</div>
|
||||||
|
<Explainer
|
||||||
|
text="Ports used to expose databases/services/internal services.<br> Add them to your firewall (if applicable).<br><br>You can specify a range of ports, eg: <span class='text-yellow-500 font-bold'>9000-9100</span>"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto flex-row items-center justify-center space-y-2">
|
||||||
|
<input
|
||||||
|
class="h-8 w-20 px-2"
|
||||||
|
type="number"
|
||||||
|
bind:value={minPort}
|
||||||
|
min="1024"
|
||||||
|
max={maxPort}
|
||||||
|
/>
|
||||||
|
-
|
||||||
|
<input
|
||||||
|
class="h-8 w-20 px-2"
|
||||||
|
type="number"
|
||||||
|
bind:value={maxPort}
|
||||||
|
min={minPort}
|
||||||
|
max="65543"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<Setting
|
||||||
|
dataTooltip="Must remove the domain before you can change this setting."
|
||||||
|
disabled={isFqdnSet}
|
||||||
|
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-yellow-500'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both."
|
||||||
|
on:click={() => !isFqdnSet && changeSettings('dualCerts')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
bind:setting={isRegistrationEnabled}
|
bind:setting={isRegistrationEnabled}
|
||||||
title="Registration allowed?"
|
title="Registration allowed?"
|
||||||
description="Allow further registrations to the application. <br>It's turned off after the first registration. "
|
description="Allow further registrations to the application. <br>It's turned off after the first registration. "
|
||||||
on:click={() => changeSettings('isRegistrationEnabled')}
|
on:click={() => changeSettings('isRegistrationEnabled')}
|
||||||
/>
|
/>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
<div class="flex space-x-1 pt-6 font-bold">
|
||||||
<div class="flex space-x-1 pt-5 font-bold">
|
<div class="title">Coolify Proxy Settings</div>
|
||||||
<div class="title">HAProxy Settings</div>
|
</div>
|
||||||
</div>
|
<Explainer
|
||||||
<Explainer
|
text={`Credentials for <a class="text-white font-bold" href=${
|
||||||
text={`Credentials for <a class="text-white font-bold" href=${
|
fqdn
|
||||||
fqdn
|
? 'http://' + getDomain(fqdn) + ':8404'
|
||||||
? 'http://' + getDomain(fqdn) + ':8404'
|
: browser && 'http://' + window.location.hostname + ':8404'
|
||||||
: browser && 'http://' + window.location.hostname + ':8404'
|
} target="_blank">stats</a> page.`}
|
||||||
} target="_blank">stats</a> page.`}
|
/>
|
||||||
/>
|
<div class="space-y-2 px-10 py-5">
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="grid grid-cols-3 items-center px-4 pt-5">
|
|
||||||
<label for="proxyUser">User</label>
|
<label for="proxyUser">User</label>
|
||||||
|
<CopyPasswordField
|
||||||
<div class="col-span-2 ">
|
readonly
|
||||||
<CopyPasswordField
|
disabled
|
||||||
readonly
|
id="proxyUser"
|
||||||
disabled
|
name="proxyUser"
|
||||||
id="proxyUser"
|
value={settings.proxyUser}
|
||||||
name="proxyUser"
|
/>
|
||||||
value={settings.proxyUser}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 items-center px-4">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="proxyPassword">Password</label>
|
<label for="proxyPassword">Password</label>
|
||||||
<div class="col-span-2 ">
|
<CopyPasswordField
|
||||||
<CopyPasswordField
|
readonly
|
||||||
readonly
|
disabled
|
||||||
disabled
|
id="proxyPassword"
|
||||||
id="proxyPassword"
|
name="proxyPassword"
|
||||||
name="proxyPassword"
|
isPasswordField
|
||||||
isPasswordField
|
value={settings.proxyPassword}
|
||||||
value={settings.proxyPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -62,32 +62,28 @@
|
|||||||
{#if !source.githubAppId}
|
{#if !source.githubAppId}
|
||||||
<button on:click={newGithubApp}>Create new GitHub App</button>
|
<button on:click={newGithubApp}>Create new GitHub App</button>
|
||||||
{:else if source.githubApp?.installationId}
|
{:else if source.githubApp?.installationId}
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
<div class="flex space-x-1 pb-5 font-bold">
|
||||||
<div class="flex space-x-1 pb-5 font-bold">
|
<div class="title">General</div>
|
||||||
<div class="title">General</div>
|
{#if $session.isAdmin}
|
||||||
{#if $session.isAdmin}
|
<button
|
||||||
<button
|
type="submit"
|
||||||
type="submit"
|
class:bg-orange-600={!loading}
|
||||||
class:bg-orange-600={!loading}
|
class:hover:bg-orange-500={!loading}
|
||||||
class:hover:bg-orange-500={!loading}
|
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
|
||||||
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
|
>
|
||||||
>
|
<button on:click|preventDefault={() => installRepositories(source)}
|
||||||
<button on:click|preventDefault={() => installRepositories(source)}
|
>Change GitHub App Settings</button
|
||||||
>Change GitHub App Settings</button
|
>
|
||||||
>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
|
<div class="grid grid-cols-2 items-center mt-2">
|
||||||
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
|
<input name="name" id="name" required bind:value={source.name} />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
</div>
|
||||||
<div class="mt-2 grid grid-cols-3 items-center">
|
</form>
|
||||||
<label for="name">Name</label>
|
|
||||||
<div class="col-span-2 ">
|
|
||||||
<input name="name" id="name" required bind:value={source.name} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{:else}
|
{:else}
|
||||||
<button on:click={() => installRepositories(source)}>Install Repositories</button>
|
<button on:click={() => installRepositories(source)}>Install Repositories</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user