mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-24 20:49:32 +00:00
Compare commits
179 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1adffe260 | ||
|
|
e725887a55 | ||
|
|
5bf79b75b0 | ||
|
|
6926975e40 | ||
|
|
978a01c968 | ||
|
|
f421f5ee84 | ||
|
|
383831c7b8 | ||
|
|
41329facf7 | ||
|
|
7d3c644148 | ||
|
|
7fab9b5930 | ||
|
|
58763ef84c | ||
|
|
0e6abf172b | ||
|
|
9e681ece41 | ||
|
|
28f87a306d | ||
|
|
23e8833208 | ||
|
|
03962663c2 | ||
|
|
cc2ec55c4d | ||
|
|
ff2c38aa16 | ||
|
|
b5a9a2cea8 | ||
|
|
cd3f661f7e | ||
|
|
41bf6b5b86 | ||
|
|
a4e7c85184 | ||
|
|
19aca9ab35 | ||
|
|
08704c289a | ||
|
|
2224c22c6e | ||
|
|
b281889acd | ||
|
|
cfc50a27b0 | ||
|
|
ed5f21da6a | ||
|
|
78f3eb81dd | ||
|
|
6a833934ce | ||
|
|
45bf6f77d1 | ||
|
|
a1b3b7b687 | ||
|
|
7ebcad6abb | ||
|
|
fed6d2bf07 | ||
|
|
bea4943e9f | ||
|
|
1979e431b8 | ||
|
|
9bead1d6b4 | ||
|
|
56c4295e16 | ||
|
|
7c7b5a61e5 | ||
|
|
abaa13fda8 | ||
|
|
042bfeddbb | ||
|
|
f45ab067ce | ||
|
|
97a6f04aaa | ||
|
|
417c01d6e0 | ||
|
|
b2e7435d0f | ||
|
|
73c9cb1d51 | ||
|
|
41c5dd3b53 | ||
|
|
bb0c93dc2f | ||
|
|
7953c1df30 | ||
|
|
c3f4245164 | ||
|
|
369001febb | ||
|
|
7ec296be6b | ||
|
|
d2f5a58f3b | ||
|
|
f4315144af | ||
|
|
e92775887d | ||
|
|
a5f1b4b675 | ||
|
|
157e5fd7aa | ||
|
|
5e7e1c11c7 | ||
|
|
e8516bc831 | ||
|
|
e3f78a1cf9 | ||
|
|
3449e0f8fc | ||
|
|
66af12f9b5 | ||
|
|
13acf09dcc | ||
|
|
ce71dccbc1 | ||
|
|
d9ba1a0b5c | ||
|
|
0b709c93a8 | ||
|
|
1657e5a151 | ||
|
|
a165b21950 | ||
|
|
0d0715a340 | ||
|
|
76754ded79 | ||
|
|
4da27a46a2 | ||
|
|
039953588e | ||
|
|
b8b4f559db | ||
|
|
2b0df270df | ||
|
|
b96c1a23ec | ||
|
|
f779b3bb54 | ||
|
|
6462982d12 | ||
|
|
84b4cc5d54 | ||
|
|
1bd2ccbc16 | ||
|
|
3abe1610bf | ||
|
|
61716738ed | ||
|
|
4e819f6eba | ||
|
|
fedb38f2bc | ||
|
|
aae108032c | ||
|
|
020013683b | ||
|
|
70de2538e2 | ||
|
|
9f581c82a9 | ||
|
|
eb2e07afc5 | ||
|
|
9c47b8495c | ||
|
|
2f8d0ee60c | ||
|
|
5bf14f4639 | ||
|
|
9da08d600b | ||
|
|
4d47eab07c | ||
|
|
f2061c5c25 | ||
|
|
430fc66ed7 | ||
|
|
bcb84b8126 | ||
|
|
dd83e86bc3 | ||
|
|
3e8a8364dc | ||
|
|
be41c0dd02 | ||
|
|
a17b7a564e | ||
|
|
f3cdda29bc | ||
|
|
de37ee9f1c | ||
|
|
8212868b92 | ||
|
|
b44d8578d9 | ||
|
|
0358cf2de2 | ||
|
|
94da008a47 | ||
|
|
456b1b8074 | ||
|
|
78e6a7d1d3 | ||
|
|
76dc7ffb68 | ||
|
|
211aff7170 | ||
|
|
bcacefb841 | ||
|
|
4505ad37d8 | ||
|
|
18cf57f33c | ||
|
|
9f2f5b40c3 | ||
|
|
8a401f50cb | ||
|
|
51a5b3b602 | ||
|
|
68f9bca054 | ||
|
|
e9e92c6e9e | ||
|
|
008cfdba09 | ||
|
|
9973197fa5 | ||
|
|
ec3b94cf96 | ||
|
|
c4cb92c78d | ||
|
|
c390f82246 | ||
|
|
b4f98e24a1 | ||
|
|
e042c5cfde | ||
|
|
faeae8fd6c | ||
|
|
ae4942ba29 | ||
|
|
fd652bfce6 | ||
|
|
3d72167721 | ||
|
|
ba284bef9e | ||
|
|
d18bb9cc74 | ||
|
|
a7ed3e58db | ||
|
|
8405ebd28d | ||
|
|
352bb65125 | ||
|
|
fe2cc5a99a | ||
|
|
7a2f29f6a3 | ||
|
|
9a05bfa899 | ||
|
|
39fa64e20d | ||
|
|
3a835b420e | ||
|
|
82f7633c3a | ||
|
|
9fdac2741a | ||
|
|
8fb5260809 | ||
|
|
e08ec12d26 | ||
|
|
1202e00a21 | ||
|
|
4ba2205af4 | ||
|
|
09841ad4cb | ||
|
|
d2dcd0abc8 | ||
|
|
fe9d0503fb | ||
|
|
8e9e6607e5 | ||
|
|
e1efd9355f | ||
|
|
ca705bbf89 | ||
|
|
b70fe09d17 | ||
|
|
d7d570393f | ||
|
|
41ca265e5a | ||
|
|
03cde08d67 | ||
|
|
5684674bd7 | ||
|
|
4fe919f2ea | ||
|
|
c8c23c53ef | ||
|
|
b1c25e98d7 | ||
|
|
7ab5a4bfcf | ||
|
|
a3ee57995c | ||
|
|
32020fd336 | ||
|
|
f1313b6468 | ||
|
|
3ef093c7e6 | ||
|
|
f5dfaa81d3 | ||
|
|
fcf206a081 | ||
|
|
9790d2b613 | ||
|
|
201fa82efc | ||
|
|
d28433ee64 | ||
|
|
cc348bf0f5 | ||
|
|
b023d65fcf | ||
|
|
1b43976ff0 | ||
|
|
321fb019eb | ||
|
|
f6858a68e0 | ||
|
|
fe17e2eaba | ||
|
|
22ef0b5d29 | ||
|
|
823279fb60 | ||
|
|
f56361c0ca | ||
|
|
4946ca2d91 |
@@ -2,4 +2,5 @@ COOLIFY_APP_ID=
|
|||||||
COOLIFY_SECRET_KEY=12341234123412341234123412341234
|
COOLIFY_SECRET_KEY=12341234123412341234123412341234
|
||||||
COOLIFY_DATABASE_URL=file:../db/dev.db
|
COOLIFY_DATABASE_URL=file:../db/dev.db
|
||||||
COOLIFY_SENTRY_DSN=
|
COOLIFY_SENTRY_DSN=
|
||||||
COOLIFY_IS_ON="docker"
|
COOLIFY_IS_ON="docker"
|
||||||
|
COOLIFY_WHITE_LABELED="false"
|
||||||
@@ -16,7 +16,7 @@ First of all, thank you for considering to contribute to my project! It means a
|
|||||||
- Push to your fork repo
|
- Push to your fork repo
|
||||||
- Create a pull request: https://github.com/coollabsio/compare
|
- Create a pull request: https://github.com/coollabsio/compare
|
||||||
- Write a proper description
|
- Write a proper description
|
||||||
- Click "Change to draft"
|
- Open the pull request to review
|
||||||
|
|
||||||
# How to start after you set up your local fork?
|
# How to start after you set up your local fork?
|
||||||
|
|
||||||
|
|||||||
55
Dockerfile
55
Dockerfile
@@ -1,31 +1,42 @@
|
|||||||
FROM node:16.14.0-alpine
|
FROM node:16.14.2-alpine as install
|
||||||
RUN apk add --no-cache g++ cmake make python3
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package*.json .
|
|
||||||
RUN yarn install
|
|
||||||
COPY . .
|
|
||||||
RUN yarn build
|
|
||||||
|
|
||||||
FROM node:16.14.0-alpine
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
LABEL coolify.managed true
|
RUN apk add --no-cache curl
|
||||||
|
|
||||||
RUN apk add --no-cache git openssh-client curl jq cmake sqlite
|
|
||||||
|
|
||||||
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6
|
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6
|
||||||
RUN pnpm add -g pnpm
|
RUN pnpm add -g pnpm
|
||||||
|
|
||||||
RUN curl -fsSL "https://download.docker.com/linux/static/stable/x86_64/docker-20.10.9.tgz" | tar -xzvf - docker/docker -C . --strip-components 1 && mv docker /usr/bin/docker
|
COPY package*.json .
|
||||||
RUN mkdir -p ~/.docker/cli-plugins/
|
RUN pnpm install
|
||||||
RUN curl -SL https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
|
|
||||||
RUN chmod +x ~/.docker/cli-plugins/docker-compose
|
FROM node:16.14.2-alpine
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV PRISMA_QUERY_ENGINE_BINARY=/app/prisma-engines/query-engine \
|
||||||
|
PRISMA_MIGRATION_ENGINE_BINARY=/app/prisma-engines/migration-engine \
|
||||||
|
PRISMA_INTROSPECTION_ENGINE_BINARY=/app/prisma-engines/introspection-engine \
|
||||||
|
PRISMA_FMT_BINARY=/app/prisma-engines/prisma-fmt \
|
||||||
|
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
|
||||||
|
PRISMA_CLIENT_ENGINE_TYPE=binary
|
||||||
|
|
||||||
|
COPY --from=coollabsio/prisma-engine:latest /prisma-engines/query-engine /prisma-engines/migration-engine /prisma-engines/introspection-engine /prisma-engines/prisma-fmt /app/prisma-engines/
|
||||||
|
|
||||||
|
COPY --from=install /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN apk add --no-cache git git-lfs openssh-client curl jq cmake sqlite openssl
|
||||||
|
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6
|
||||||
|
RUN pnpm add -g pnpm
|
||||||
|
RUN mkdir -p ~/.docker/cli-plugins/
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-20.10.9 -o /usr/bin/docker
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.3.4 -o ~/.docker/cli-plugins/docker-compose
|
||||||
|
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
|
||||||
|
|
||||||
|
RUN pnpm prisma generate
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
|
||||||
COPY --from=0 /app/docker-compose.yaml .
|
|
||||||
COPY --from=0 /app/build .
|
|
||||||
COPY --from=0 /app/package.json .
|
|
||||||
COPY --from=0 /app/node_modules ./node_modules
|
|
||||||
COPY --from=0 /app/prisma ./prisma
|
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD ["pnpm", "start"]
|
CMD ["pnpm", "start"]
|
||||||
1
data/build-prisma-engine.sh
Normal file
1
data/build-prisma-engine.sh
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nohup docker build -t coollabsio/prisma-engine:<arm64/amd64> --push . &
|
||||||
@@ -4,10 +4,10 @@ global
|
|||||||
defaults
|
defaults
|
||||||
mode http
|
mode http
|
||||||
log global
|
log global
|
||||||
timeout http-request 60s
|
timeout http-request 120s
|
||||||
timeout connect 10s
|
timeout connect 20s
|
||||||
timeout client 60s
|
timeout client 120s
|
||||||
timeout server 60s
|
timeout server 120s
|
||||||
|
|
||||||
frontend "${APP}"
|
frontend "${APP}"
|
||||||
mode http
|
mode http
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ global
|
|||||||
defaults
|
defaults
|
||||||
mode http
|
mode http
|
||||||
log global
|
log global
|
||||||
timeout http-request 60s
|
timeout http-request 120s
|
||||||
timeout connect 10s
|
timeout connect 20s
|
||||||
timeout client 60s
|
timeout client 120s
|
||||||
timeout server 60s
|
timeout server 120s
|
||||||
|
|
||||||
userlist haproxy-dataplaneapi
|
userlist haproxy-dataplaneapi
|
||||||
user admin insecure-password "${HAPROXY_PASSWORD}"
|
user admin insecure-password "${HAPROXY_PASSWORD}"
|
||||||
|
|||||||
10
data/prisma-engine.Dockerfile
Normal file
10
data/prisma-engine.Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM rust:1.58.1-alpine3.14 as prisma
|
||||||
|
WORKDIR /prisma
|
||||||
|
ENV RUSTFLAGS="-C target-feature=-crt-static"
|
||||||
|
RUN apk --no-cache add openssl direnv git musl-dev openssl-dev build-base perl protoc
|
||||||
|
RUN git clone --depth=1 --branch=3.11.x https://github.com/prisma/prisma-engines.git /prisma
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
FROM alpine
|
||||||
|
WORKDIR /prisma-engines
|
||||||
|
COPY --from=prisma /prisma/target/release/query-engine /prisma/target/release/migration-engine /prisma/target/release/introspection-engine /prisma/target/release/prisma-fmt /prisma-engines/
|
||||||
41
package.json
41
package.json
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "2.3.0",
|
"version": "2.4.7",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev",
|
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev",
|
||||||
"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 build/index.js",
|
||||||
"build": "svelte-kit build",
|
"build": "svelte-kit build",
|
||||||
"preview": "svelte-kit preview",
|
"preview": "svelte-kit preview",
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
@@ -17,18 +17,18 @@
|
|||||||
"db:push": "prisma db push && prisma generate",
|
"db:push": "prisma db push && prisma generate",
|
||||||
"db:seed": "prisma db seed",
|
"db:seed": "prisma db seed",
|
||||||
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
||||||
"release:staging": "cross-var docker build -t coollabsio/coolify:$npm_package_version . && docker push coollabsio/coolify:$npm_package_version",
|
"release:production:all": "cross-var docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest --push .",
|
||||||
"release:pre": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .",
|
"release:production:amd": "cross-var docker build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest --push .",
|
||||||
"release:coolify": "cross-var yarn release:pre && docker push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
|
"release:staging:all": "cross-var docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify:$npm_package_version --push .",
|
||||||
"release:haproxy": "docker build -f haproxy.Dockerfile -t coollabsio/coolify-haproxy-alpine:1.0.0 -t coollabsio/coolify-haproxy-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-alpine",
|
"release:staging:amd": "cross-var docker build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version --push .",
|
||||||
"release:haproxy:tcp": "docker build -f haproxy-tcp.Dockerfile -t coollabsio/coolify-haproxy-tcp-alpine:1.0.0 -t coollabsio/coolify-haproxy-tcp-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-tcp-alpine",
|
"release:haproxy": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-alpine:latest -t coollabsio/coolify-haproxy-alpine:1.1.0 -f data/haproxy.Dockerfile --push .",
|
||||||
"release:haproxy:http": "docker build -f haproxy-http.Dockerfile -t coollabsio/coolify-haproxy-http-alpine:1.0.0 -t coollabsio/coolify-haproxy-http-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-http-alpine",
|
"release:haproxy:tcp": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-tcp-alpine:latest -t coollabsio/coolify-haproxy-tcp-alpine:1.1.0 -f data/haproxy-tcp.Dockerfile --push .",
|
||||||
|
"release:haproxy:http": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-http-alpine:latest -t coollabsio/coolify-haproxy-http-alpine:1.1.0 -f data/haproxy-http.Dockerfile --push .",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-node": "1.0.0-next.73",
|
"@sveltejs/adapter-node": "1.0.0-next.73",
|
||||||
"@sveltejs/kit": "1.0.0-next.303",
|
"@sveltejs/kit": "1.0.0-next.310",
|
||||||
"@types/bcrypt": "5.0.0",
|
|
||||||
"@types/js-cookie": "3.0.1",
|
"@types/js-cookie": "3.0.1",
|
||||||
"@types/js-yaml": "4.0.5",
|
"@types/js-yaml": "4.0.5",
|
||||||
"@types/node": "17.0.23",
|
"@types/node": "17.0.23",
|
||||||
@@ -45,13 +45,13 @@
|
|||||||
"husky": "7.0.4",
|
"husky": "7.0.4",
|
||||||
"lint-staged": "12.3.7",
|
"lint-staged": "12.3.7",
|
||||||
"postcss": "8.4.12",
|
"postcss": "8.4.12",
|
||||||
"prettier": "2.6.1",
|
"prettier": "2.6.2",
|
||||||
"prettier-plugin-svelte": "2.6.0",
|
"prettier-plugin-svelte": "2.7.0",
|
||||||
"prettier-plugin-tailwindcss": "0.1.8",
|
"prettier-plugin-tailwindcss": "0.1.8",
|
||||||
"prisma": "3.11.1",
|
"prisma": "3.11.1",
|
||||||
"svelte": "3.46.4",
|
"svelte": "3.47.0",
|
||||||
"svelte-check": "2.4.6",
|
"svelte-check": "2.6.0",
|
||||||
"svelte-preprocess": "4.10.4",
|
"svelte-preprocess": "4.10.5",
|
||||||
"svelte-select": "4.4.7",
|
"svelte-select": "4.4.7",
|
||||||
"tailwindcss": "3.0.23",
|
"tailwindcss": "3.0.23",
|
||||||
"ts-node": "10.7.0",
|
"ts-node": "10.7.0",
|
||||||
@@ -62,24 +62,23 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@prisma/client": "3.11.1",
|
"@prisma/client": "3.11.1",
|
||||||
"@sentry/node": "6.19.2",
|
"@sentry/node": "6.19.6",
|
||||||
"bcrypt": "5.0.1",
|
"bcryptjs": "^2.4.3",
|
||||||
"bullmq": "1.78.1",
|
"bullmq": "1.80.0",
|
||||||
"compare-versions": "4.1.3",
|
"compare-versions": "4.1.3",
|
||||||
"cookie": "0.4.2",
|
"cookie": "0.4.2",
|
||||||
"cooltipz-css": "2.1.0",
|
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
"dayjs": "1.11.0",
|
"dayjs": "1.11.0",
|
||||||
"dockerode": "3.3.1",
|
"dockerode": "3.3.1",
|
||||||
"dotenv-extended": "2.9.0",
|
"dotenv-extended": "2.9.0",
|
||||||
"generate-password": "1.7.0",
|
"generate-password": "1.7.0",
|
||||||
"get-port": "6.1.2",
|
"get-port": "6.1.2",
|
||||||
"got": "12.0.2",
|
"got": "12.0.3",
|
||||||
"js-cookie": "3.0.1",
|
"js-cookie": "3.0.1",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
"node-forge": "1.3.0",
|
"node-forge": "1.3.1",
|
||||||
"p-limit": "4.0.0",
|
"p-limit": "4.0.0",
|
||||||
"svelte-kit-cookie-session": "2.1.2",
|
"svelte-kit-cookie-session": "2.1.2",
|
||||||
"tailwindcss-scrollbar": "0.1.0",
|
"tailwindcss-scrollbar": "0.1.0",
|
||||||
|
|||||||
609
pnpm-lock.yaml
generated
609
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Wordpress" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"extraConfig" TEXT,
|
||||||
|
"tablePrefix" TEXT,
|
||||||
|
"mysqlUser" TEXT NOT NULL,
|
||||||
|
"mysqlPassword" TEXT NOT NULL,
|
||||||
|
"mysqlRootUser" TEXT NOT NULL,
|
||||||
|
"mysqlRootUserPassword" TEXT NOT NULL,
|
||||||
|
"mysqlDatabase" TEXT,
|
||||||
|
"mysqlPublicPort" INTEGER,
|
||||||
|
"ftpEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"ftpUser" TEXT,
|
||||||
|
"ftpPassword" TEXT,
|
||||||
|
"ftpPublicPort" INTEGER,
|
||||||
|
"ftpHostKey" TEXT,
|
||||||
|
"ftpHostKeyPrivate" TEXT,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Wordpress_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Wordpress" ("createdAt", "extraConfig", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt") SELECT "createdAt", "extraConfig", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt" FROM "Wordpress";
|
||||||
|
DROP TABLE "Wordpress";
|
||||||
|
ALTER TABLE "new_Wordpress" RENAME TO "Wordpress";
|
||||||
|
CREATE UNIQUE INDEX "Wordpress_serviceId_key" ON "Wordpress"("serviceId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "ApplicationPersistentStorage_path_key";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "ApplicationPersistentStorage_applicationId_key";
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
|
binaryTargets = ["linux-musl"]
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
@@ -117,8 +118,8 @@ model ApplicationSettings {
|
|||||||
model ApplicationPersistentStorage {
|
model ApplicationPersistentStorage {
|
||||||
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
|
||||||
path String @unique
|
path String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@ -332,6 +333,12 @@ model Wordpress {
|
|||||||
mysqlRootUserPassword String
|
mysqlRootUserPassword String
|
||||||
mysqlDatabase String?
|
mysqlDatabase String?
|
||||||
mysqlPublicPort Int?
|
mysqlPublicPort Int?
|
||||||
|
ftpEnabled Boolean @default(false)
|
||||||
|
ftpUser String?
|
||||||
|
ftpPassword String?
|
||||||
|
ftpPublicPort Int?
|
||||||
|
ftpHostKey String?
|
||||||
|
ftpHostKeyPrivate String?
|
||||||
serviceId String @unique
|
serviceId String @unique
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|||||||
6
src/app.d.ts
vendored
6
src/app.d.ts
vendored
@@ -15,18 +15,20 @@ declare namespace App {
|
|||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
source: string;
|
source: string;
|
||||||
settings: string;
|
settings: string;
|
||||||
|
database: Record<string, any>;
|
||||||
|
versions: string;
|
||||||
|
privatePort: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SessionData {
|
interface SessionData {
|
||||||
|
whiteLabeled: boolean;
|
||||||
version?: string;
|
version?: string;
|
||||||
userId?: string | null;
|
userId?: string | null;
|
||||||
teamId?: string | null;
|
teamId?: string | null;
|
||||||
permission?: string;
|
permission?: string;
|
||||||
isAdmin?: boolean;
|
isAdmin?: boolean;
|
||||||
expires?: string | null;
|
expires?: string | null;
|
||||||
gitlabToken?: string | null;
|
|
||||||
ghToken?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DateTimeFormatOptions = {
|
type DateTimeFormatOptions = {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="/favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Coolify</title>
|
<title>Coolify</title>
|
||||||
%svelte.head%
|
%svelte.head%
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { version } from '$lib/common';
|
|||||||
import cookie from 'cookie';
|
import cookie from 'cookie';
|
||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
|
|
||||||
|
const whiteLabeled = process.env['COOLIFY_WHITE_LABELED'] === 'true';
|
||||||
|
|
||||||
export const handle = handleSession(
|
export const handle = handleSession(
|
||||||
{
|
{
|
||||||
secret: process.env['COOLIFY_SECRET_KEY'],
|
secret: process.env['COOLIFY_SECRET_KEY'],
|
||||||
@@ -71,6 +73,7 @@ export const handle = handleSession(
|
|||||||
export const getSession: GetSession = function ({ locals }) {
|
export const getSession: GetSession = function ({ locals }) {
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
|
whiteLabeled,
|
||||||
...locals.session.data
|
...locals.session.data
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
async function send({ method, path, data = {}, headers, timeout = 30000 }) {
|
async function send({
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
data = {},
|
||||||
|
headers,
|
||||||
|
timeout = 120000
|
||||||
|
}): Promise<Record<string, unknown>> {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const id = setTimeout(() => controller.abort(), timeout);
|
const id = setTimeout(() => controller.abort(), timeout);
|
||||||
const opts = { method, headers: {}, body: null, signal: controller.signal };
|
const opts = { method, headers: {}, body: null, signal: controller.signal };
|
||||||
if (Object.keys(data).length > 0) {
|
if (Object.keys(data).length > 0) {
|
||||||
let parsedData = data;
|
const parsedData = data;
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
parsedData[key] = null;
|
parsedData[key] = null;
|
||||||
@@ -43,18 +49,33 @@ async function send({ method, path, data = {}, headers, timeout = 30000 }) {
|
|||||||
return responseData;
|
return responseData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get(path, headers = {}): Promise<any> {
|
export function get(
|
||||||
|
path: string,
|
||||||
|
headers?: Record<string, unknown>
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'GET', path, headers });
|
return send({ method: 'GET', path, headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function del(path, data = {}, headers = {}): Promise<any> {
|
export function del(
|
||||||
|
path: string,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
headers?: Record<string, unknown>
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'DELETE', path, data, headers });
|
return send({ method: 'DELETE', path, data, headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function post(path, data, headers = {}): Promise<any> {
|
export function post(
|
||||||
|
path: string,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
headers?: Record<string, unknown>
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'POST', path, data, headers });
|
return send({ method: 'POST', path, data, headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function put(path, data, headers = {}): Promise<any> {
|
export function put(
|
||||||
|
path: string,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
headers?: Record<string, unknown>
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'PUT', path, data, headers });
|
return send({ method: 'PUT', path, data, headers });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,12 +102,9 @@ export const setDefaultConfiguration = async (data) => {
|
|||||||
else if (buildPack === 'php') port = 80;
|
else if (buildPack === 'php') port = 80;
|
||||||
else if (buildPack === 'python') port = 8000;
|
else if (buildPack === 'python') port = 8000;
|
||||||
}
|
}
|
||||||
if (template) {
|
if (!installCommand) installCommand = template?.installCommand || 'yarn install';
|
||||||
if (!installCommand) installCommand = template?.installCommand || 'yarn install';
|
if (!startCommand) startCommand = template?.startCommand || 'yarn start';
|
||||||
if (!startCommand) startCommand = template?.startCommand || 'yarn start';
|
if (!buildCommand) buildCommand = template?.buildCommand || null;
|
||||||
if (!buildCommand) buildCommand = template?.buildCommand || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
||||||
if (baseDirectory) {
|
if (baseDirectory) {
|
||||||
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
||||||
@@ -127,18 +124,7 @@ export const setDefaultConfiguration = async (data) => {
|
|||||||
|
|
||||||
export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId) {
|
export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId) {
|
||||||
try {
|
try {
|
||||||
// TODO: Write full .dockerignore for all deployments!!
|
|
||||||
if (buildPack === 'php') {
|
if (buildPack === 'php') {
|
||||||
await fs.writeFile(
|
|
||||||
`${workdir}/.htaccess`,
|
|
||||||
`
|
|
||||||
RewriteEngine On
|
|
||||||
RewriteBase /
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
|
||||||
RewriteRule ^(.+)$ index.php [QSA,L]
|
|
||||||
`
|
|
||||||
);
|
|
||||||
await fs.writeFile(`${workdir}/entrypoint.sh`, `chown -R 1000 /app`);
|
await fs.writeFile(`${workdir}/entrypoint.sh`, `chown -R 1000 /app`);
|
||||||
await saveBuildLog({
|
await saveBuildLog({
|
||||||
line: 'Copied default configuration file for PHP.',
|
line: 'Copied default configuration file for PHP.',
|
||||||
|
|||||||
@@ -26,14 +26,17 @@ export default async function ({
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
if (pullmergeRequestId) {
|
if (
|
||||||
if (secret.isPRMRSecret) {
|
(pullmergeRequestId && secret.isPRMRSecret) ||
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
(!pullmergeRequestId && !secret.isPRMRSecret)
|
||||||
}
|
) {
|
||||||
} else {
|
Dockerfile.unshift(`ARG ${secret.name}=${secret.value}`);
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.forEach((line, index) => {
|
||||||
}
|
if (line.startsWith('FROM')) {
|
||||||
|
Dockerfile.splice(index + 1, 0, `ARG ${secret.name}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,23 +1,45 @@
|
|||||||
import { buildImage } from '$lib/docker';
|
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, htaccessFound): Promise<void> => {
|
||||||
const { workdir, baseDirectory } = data;
|
const { workdir, baseDirectory } = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
let composerFound = false;
|
||||||
|
try {
|
||||||
|
await fs.readFile(`${workdir}${baseDirectory || ''}/composer.json`);
|
||||||
|
composerFound = true;
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
Dockerfile.push(`LABEL coolify.image=true`);
|
Dockerfile.push(`LABEL coolify.image=true`);
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`COPY .${baseDirectory || ''} /app`);
|
Dockerfile.push(`COPY .${baseDirectory || ''} /app`);
|
||||||
Dockerfile.push(`COPY /.htaccess .`);
|
if (htaccessFound) {
|
||||||
|
Dockerfile.push(`COPY .${baseDirectory || ''}/.htaccess ./`);
|
||||||
|
}
|
||||||
|
if (composerFound) {
|
||||||
|
Dockerfile.push(`RUN composer install`);
|
||||||
|
}
|
||||||
|
|
||||||
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
|
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
|
||||||
Dockerfile.push(`EXPOSE 80`);
|
Dockerfile.push(`EXPOSE 80`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function (data) {
|
export default async function (data) {
|
||||||
|
const { workdir, baseDirectory } = data;
|
||||||
try {
|
try {
|
||||||
const image = 'webdevops/php-nginx';
|
let htaccessFound = false;
|
||||||
await createDockerfile(data, image);
|
try {
|
||||||
|
await fs.readFile(`${workdir}${baseDirectory || ''}/.htaccess`);
|
||||||
|
htaccessFound = true;
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
const image = htaccessFound
|
||||||
|
? 'webdevops/php-apache:8.0-alpine'
|
||||||
|
: 'webdevops/php-nginx:8.0-alpine';
|
||||||
|
await createDockerfile(data, image, htaccessFound);
|
||||||
await buildImage(data);
|
await buildImage(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import { version as currentVersion } from '../../package.json';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Cookie from 'cookie';
|
import Cookie from 'cookie';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import cuid from 'cuid';
|
import type { RequestEvent } from '@sveltejs/kit/types/internal';
|
||||||
|
import type { Job } from 'bullmq';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
@@ -45,30 +46,32 @@ const customConfig: Config = {
|
|||||||
|
|
||||||
export const version = currentVersion;
|
export const version = currentVersion;
|
||||||
export const asyncExecShell = util.promisify(child.exec);
|
export const asyncExecShell = util.promisify(child.exec);
|
||||||
export const asyncSleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
|
export const asyncSleep = (delay: number): Promise<unknown> =>
|
||||||
|
new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
export const sentry = Sentry;
|
export const sentry = Sentry;
|
||||||
|
|
||||||
export const uniqueName = () => uniqueNamesGenerator(customConfig);
|
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
|
||||||
|
|
||||||
export const saveBuildLog = async ({ line, buildId, applicationId }) => {
|
export const saveBuildLog = async ({
|
||||||
const addTimestamp = `${generateTimestamp()} ${line}`;
|
line,
|
||||||
return await buildLogQueue.add(buildId, { buildId, line: addTimestamp, applicationId });
|
buildId,
|
||||||
};
|
applicationId
|
||||||
|
}: {
|
||||||
export const isTeamIdTokenAvailable = (request) => {
|
line: string;
|
||||||
const cookie = request.headers.cookie
|
buildId: string;
|
||||||
?.split(';')
|
applicationId: string;
|
||||||
.map((s) => s.trim())
|
}): Promise<Job> => {
|
||||||
.find((s) => s.startsWith('teamId='))
|
if (line) {
|
||||||
?.split('=')[1];
|
if (line.includes('ghs_')) {
|
||||||
if (!cookie) {
|
const regex = /ghs_.*@/g;
|
||||||
return getTeam(request);
|
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
||||||
} else {
|
}
|
||||||
return cookie;
|
const addTimestamp = `${generateTimestamp()} ${line}`;
|
||||||
|
return await buildLogQueue.add(buildId, { buildId, line: addTimestamp, applicationId });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTeam = (event) => {
|
export const getTeam = (event: RequestEvent): string | null => {
|
||||||
const 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;
|
||||||
@@ -78,14 +81,28 @@ export const getTeam = (event) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserDetails = async (event, isAdminRequired = true) => {
|
export const getUserDetails = async (
|
||||||
|
event: RequestEvent,
|
||||||
|
isAdminRequired = true
|
||||||
|
): Promise<{
|
||||||
|
teamId: string;
|
||||||
|
userId: string;
|
||||||
|
permission: string;
|
||||||
|
status: number;
|
||||||
|
body: { message: string };
|
||||||
|
}> => {
|
||||||
const teamId = getTeam(event);
|
const teamId = getTeam(event);
|
||||||
const userId = event?.locals?.session?.data?.userId || null;
|
const userId = event?.locals?.session?.data?.userId || null;
|
||||||
const { permission = 'read' } = await db.prisma.permission.findFirst({
|
let permission = 'read';
|
||||||
where: { teamId, userId },
|
if (teamId && userId) {
|
||||||
select: { permission: true },
|
const data = await db.prisma.permission.findFirst({
|
||||||
rejectOnNotFound: true
|
where: { teamId, userId },
|
||||||
});
|
select: { permission: true },
|
||||||
|
rejectOnNotFound: true
|
||||||
|
});
|
||||||
|
if (data.permission) permission = data.permission;
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
teamId,
|
teamId,
|
||||||
userId,
|
userId,
|
||||||
@@ -95,6 +112,7 @@ export const getUserDetails = async (event, isAdminRequired = true) => {
|
|||||||
message: 'OK'
|
message: 'OK'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isAdminRequired && permission !== 'admin' && permission !== 'owner') {
|
if (isAdminRequired && permission !== 'admin' && permission !== 'owner') {
|
||||||
payload.status = 401;
|
payload.status = 401;
|
||||||
payload.body.message =
|
payload.body.message =
|
||||||
@@ -104,11 +122,11 @@ export const getUserDetails = async (event, isAdminRequired = true) => {
|
|||||||
return payload;
|
return payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getEngine(engine) {
|
export function getEngine(engine: string): string {
|
||||||
return engine === '/var/run/docker.sock' ? 'unix:///var/run/docker.sock' : engine;
|
return engine === '/var/run/docker.sock' ? 'unix:///var/run/docker.sock' : engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeContainer(id, engine) {
|
export async function removeContainer(id: string, engine: string): Promise<void> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
try {
|
try {
|
||||||
const { stdout } = await asyncExecShell(
|
const { stdout } = await asyncExecShell(
|
||||||
@@ -124,11 +142,23 @@ export async function removeContainer(id, engine) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removeDestinationDocker = async ({ id, engine }) => {
|
export const removeDestinationDocker = async ({
|
||||||
|
id,
|
||||||
|
engine
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
engine: string;
|
||||||
|
}): Promise<void> => {
|
||||||
return await removeContainer(id, engine);
|
return await removeContainer(id, engine);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createDirectories = async ({ repository, buildId }) => {
|
export const createDirectories = async ({
|
||||||
|
repository,
|
||||||
|
buildId
|
||||||
|
}: {
|
||||||
|
repository: string;
|
||||||
|
buildId: string;
|
||||||
|
}): Promise<{ workdir: string; repodir: string }> => {
|
||||||
const repodir = `/tmp/build-sources/${repository}/`;
|
const repodir = `/tmp/build-sources/${repository}/`;
|
||||||
const workdir = `/tmp/build-sources/${repository}/${buildId}`;
|
const workdir = `/tmp/build-sources/${repository}/${buildId}`;
|
||||||
|
|
||||||
@@ -140,20 +170,10 @@ export const createDirectories = async ({ repository, buildId }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function generateTimestamp() {
|
export function generateTimestamp(): string {
|
||||||
return `${dayjs().format('HH:mm:ss.SSS')} `;
|
return `${dayjs().format('HH:mm:ss.SSS')} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDomain(domain) {
|
export function getDomain(domain: string): string {
|
||||||
return domain?.replace('https://', '').replace('http://', '');
|
return domain?.replace('https://', '').replace('http://', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dashify(str: string, options?: any): string {
|
|
||||||
if (typeof str !== 'string') return str;
|
|
||||||
return str
|
|
||||||
.trim()
|
|
||||||
.replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-'))
|
|
||||||
.replace(/^-+|-+$/g, '')
|
|
||||||
.replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m))
|
|
||||||
.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
export let isCenter = true;
|
export let isCenter = true;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let dataTooltip = null;
|
export let dataTooltip = null;
|
||||||
|
export let loading = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center py-4 pr-8">
|
<div class="flex items-center py-4 pr-8">
|
||||||
@@ -26,9 +27,10 @@
|
|||||||
on:click
|
on:click
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
||||||
class:opacity-50={disabled}
|
class:opacity-50={disabled || loading}
|
||||||
class:bg-green-600={setting}
|
class:bg-green-600={!loading && setting}
|
||||||
class:bg-stone-700={!setting}
|
class:bg-stone-700={!loading && !setting}
|
||||||
|
class:bg-yellow-500={loading}
|
||||||
>
|
>
|
||||||
<span class="sr-only">Use setting</span>
|
<span class="sr-only">Use setting</span>
|
||||||
<span
|
<span
|
||||||
@@ -40,6 +42,7 @@
|
|||||||
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
|
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
|
||||||
class:opacity-0={setting}
|
class:opacity-0={setting}
|
||||||
class:opacity-100={!setting}
|
class:opacity-100={!setting}
|
||||||
|
class:animate-spin={loading}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
|
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
|
||||||
@@ -57,6 +60,7 @@
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class:opacity-100={setting}
|
class:opacity-100={setting}
|
||||||
class:opacity-0={!setting}
|
class:opacity-0={!setting}
|
||||||
|
class:animate-spin={loading}
|
||||||
>
|
>
|
||||||
<svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
|
<svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
|
||||||
<path
|
<path
|
||||||
|
|||||||
@@ -43,3 +43,142 @@ export function changeQueryParams(buildId) {
|
|||||||
queryParams.set('buildId', buildId);
|
queryParams.set('buildId', buildId);
|
||||||
return history.pushState(null, null, '?' + queryParams.toString());
|
return history.pushState(null, null, '?' + queryParams.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const supportedDatabaseTypesAndVersions = [
|
||||||
|
{
|
||||||
|
name: 'mongodb',
|
||||||
|
fancyName: 'MongoDB',
|
||||||
|
baseImage: 'bitnami/mongodb',
|
||||||
|
versions: ['5.0', '4.4', '4.2']
|
||||||
|
},
|
||||||
|
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0', '5.7'] },
|
||||||
|
{
|
||||||
|
name: 'postgresql',
|
||||||
|
fancyName: 'PostgreSQL',
|
||||||
|
baseImage: 'bitnami/postgresql',
|
||||||
|
versions: ['14.2.0', '13.6.0', '12.10.0 ', '11.15.0', '10.20.0']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redis',
|
||||||
|
fancyName: 'Redis',
|
||||||
|
baseImage: 'bitnami/redis',
|
||||||
|
versions: ['6.2', '6.0', '5.0']
|
||||||
|
},
|
||||||
|
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
|
||||||
|
];
|
||||||
|
export const supportedServiceTypesAndVersions = [
|
||||||
|
{
|
||||||
|
name: 'plausibleanalytics',
|
||||||
|
fancyName: 'Plausible Analytics',
|
||||||
|
baseImage: 'plausible/analytics',
|
||||||
|
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
|
||||||
|
versions: ['latest', 'stable'],
|
||||||
|
recommendedVersion: 'stable',
|
||||||
|
ports: {
|
||||||
|
main: 8000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nocodb',
|
||||||
|
fancyName: 'NocoDB',
|
||||||
|
baseImage: 'nocodb/nocodb',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'minio',
|
||||||
|
fancyName: 'MinIO',
|
||||||
|
baseImage: 'minio/minio',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 9001
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vscodeserver',
|
||||||
|
fancyName: 'VSCode Server',
|
||||||
|
baseImage: 'codercom/code-server',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wordpress',
|
||||||
|
fancyName: 'Wordpress',
|
||||||
|
baseImage: 'wordpress',
|
||||||
|
images: ['bitnami/mysql:5.7'],
|
||||||
|
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 80
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vaultwarden',
|
||||||
|
fancyName: 'Vaultwarden',
|
||||||
|
baseImage: 'vaultwarden/server',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 80
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'languagetool',
|
||||||
|
fancyName: 'LanguageTool',
|
||||||
|
baseImage: 'silviof/docker-languagetool',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8010
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'n8n',
|
||||||
|
fancyName: 'n8n',
|
||||||
|
baseImage: 'n8nio/n8n',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 5678
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'uptimekuma',
|
||||||
|
fancyName: 'Uptime Kuma',
|
||||||
|
baseImage: 'louislam/uptime-kuma',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 3001
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ghost',
|
||||||
|
fancyName: 'Ghost',
|
||||||
|
baseImage: 'bitnami/ghost',
|
||||||
|
images: ['bitnami/mariadb'],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 2368
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'meilisearch',
|
||||||
|
fancyName: 'Meilisearch',
|
||||||
|
baseImage: 'getmeili/meilisearch',
|
||||||
|
images: [],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 7700
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
const algorithm = 'aes-256-ctr';
|
const algorithm = 'aes-256-ctr';
|
||||||
|
|
||||||
export const base64Encode = (text: string) => {
|
export const base64Encode = (text: string): string => {
|
||||||
return Buffer.from(text).toString('base64');
|
return Buffer.from(text).toString('base64');
|
||||||
};
|
};
|
||||||
export const base64Decode = (text: string) => {
|
export const base64Decode = (text: string): string => {
|
||||||
return Buffer.from(text, 'base64').toString('ascii');
|
return Buffer.from(text, 'base64').toString('ascii');
|
||||||
};
|
};
|
||||||
export const encrypt = (text: string) => {
|
export const encrypt = (text: string): string => {
|
||||||
if (text) {
|
if (text) {
|
||||||
const iv = crypto.randomBytes(16);
|
const iv = crypto.randomBytes(16);
|
||||||
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv);
|
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv);
|
||||||
@@ -19,7 +19,7 @@ export const encrypt = (text: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decrypt = (hashString: string) => {
|
export const decrypt = (hashString: string): string => {
|
||||||
if (hashString) {
|
if (hashString) {
|
||||||
const hash: Hash = JSON.parse(hashString);
|
const hash: Hash = JSON.parse(hashString);
|
||||||
const decipher = crypto.createDecipheriv(
|
const decipher = crypto.createDecipheriv(
|
||||||
|
|||||||
@@ -1,14 +1,35 @@
|
|||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
import { asyncExecShell, getEngine } from '$lib/common';
|
import { asyncExecShell, getEngine } from '$lib/common';
|
||||||
|
|
||||||
import { getDomain, removeDestinationDocker } from '$lib/common';
|
import { removeDestinationDocker } from '$lib/common';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
|
||||||
export async function listApplications(teamId) {
|
import type {
|
||||||
return await prisma.application.findMany({ where: { teams: { some: { id: teamId } } } });
|
DestinationDocker,
|
||||||
|
GitSource,
|
||||||
|
Secret,
|
||||||
|
ApplicationSettings,
|
||||||
|
Application,
|
||||||
|
ApplicationPersistentStorage
|
||||||
|
} from '@prisma/client';
|
||||||
|
|
||||||
|
export async function listApplications(teamId: string): Promise<Application[]> {
|
||||||
|
if (teamId === '0') {
|
||||||
|
return await prisma.application.findMany({ include: { teams: true } });
|
||||||
|
}
|
||||||
|
return await prisma.application.findMany({
|
||||||
|
where: { teams: { some: { id: teamId } } },
|
||||||
|
include: { teams: true }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function newApplication({ name, teamId }) {
|
export async function newApplication({
|
||||||
|
name,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<Application> {
|
||||||
return await prisma.application.create({
|
return await prisma.application.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
@@ -18,34 +39,17 @@ export async function newApplication({ name, teamId }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function importApplication({
|
export async function removeApplication({
|
||||||
name,
|
id,
|
||||||
teamId,
|
teamId
|
||||||
fqdn,
|
}: {
|
||||||
port,
|
id: string;
|
||||||
buildCommand,
|
teamId: string;
|
||||||
startCommand,
|
}): Promise<void> {
|
||||||
installCommand
|
const { destinationDockerId, destinationDocker } = await prisma.application.findUnique({
|
||||||
}) {
|
|
||||||
return await prisma.application.create({
|
|
||||||
data: {
|
|
||||||
name,
|
|
||||||
fqdn,
|
|
||||||
port,
|
|
||||||
buildCommand,
|
|
||||||
startCommand,
|
|
||||||
installCommand,
|
|
||||||
teams: { connect: { id: teamId } }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function removeApplication({ id, teamId }) {
|
|
||||||
const { fqdn, destinationDockerId, destinationDocker } = await prisma.application.findUnique({
|
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const host = getEngine(destinationDocker.engine);
|
const host = getEngine(destinationDocker.engine);
|
||||||
const { stdout: containers } = await asyncExecShell(
|
const { stdout: containers } = await asyncExecShell(
|
||||||
@@ -56,7 +60,6 @@ export async function removeApplication({ id, teamId }) {
|
|||||||
for (const container of containersArray) {
|
for (const container of containersArray) {
|
||||||
const containerObj = JSON.parse(container);
|
const containerObj = JSON.parse(container);
|
||||||
const id = containerObj.ID;
|
const id = containerObj.ID;
|
||||||
const preview = containerObj.Image.split('-')[1];
|
|
||||||
await removeDestinationDocker({ id, engine: destinationDocker.engine });
|
await removeDestinationDocker({ id, engine: destinationDocker.engine });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,12 +70,30 @@ export async function removeApplication({ id, teamId }) {
|
|||||||
await prisma.build.deleteMany({ where: { applicationId: id } });
|
await prisma.build.deleteMany({ where: { applicationId: id } });
|
||||||
await prisma.secret.deleteMany({ where: { applicationId: id } });
|
await prisma.secret.deleteMany({ where: { applicationId: id } });
|
||||||
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
|
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
|
||||||
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
|
if (teamId === '0') {
|
||||||
|
await prisma.application.deleteMany({ where: { id } });
|
||||||
|
} else {
|
||||||
|
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getApplicationWebhook({ projectId, branch }) {
|
export async function getApplicationWebhook({
|
||||||
|
projectId,
|
||||||
|
branch
|
||||||
|
}: {
|
||||||
|
projectId: number;
|
||||||
|
branch: string;
|
||||||
|
}): Promise<
|
||||||
|
Application & {
|
||||||
|
destinationDocker: DestinationDocker;
|
||||||
|
settings: ApplicationSettings;
|
||||||
|
gitSource: GitSource;
|
||||||
|
secrets: Secret[];
|
||||||
|
persistentStorage: ApplicationPersistentStorage[];
|
||||||
|
}
|
||||||
|
> {
|
||||||
try {
|
try {
|
||||||
let application = await prisma.application.findFirst({
|
const application = await prisma.application.findFirst({
|
||||||
where: { projectId, branch, settings: { autodeploy: true } },
|
where: { projectId, branch, settings: { autodeploy: true } },
|
||||||
include: {
|
include: {
|
||||||
destinationDocker: true,
|
destinationDocker: true,
|
||||||
@@ -121,25 +142,40 @@ export async function getApplicationWebhook({ projectId, branch }) {
|
|||||||
throw { status: 404, body: { message: e.message } };
|
throw { status: 404, body: { message: e.message } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function getApplicationById({ id }) {
|
|
||||||
const body = await prisma.application.findFirst({
|
|
||||||
where: { id },
|
|
||||||
include: { destinationDocker: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
return { ...body };
|
export async function getApplication({ id, teamId }: { id: string; teamId: string }): Promise<
|
||||||
}
|
Application & {
|
||||||
export async function getApplication({ id, teamId }) {
|
destinationDocker: DestinationDocker;
|
||||||
let body = await prisma.application.findFirst({
|
settings: ApplicationSettings;
|
||||||
where: { id, teams: { some: { id: teamId } } },
|
gitSource: GitSource;
|
||||||
include: {
|
secrets: Secret[];
|
||||||
destinationDocker: true,
|
persistentStorage: ApplicationPersistentStorage[];
|
||||||
settings: true,
|
}
|
||||||
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
> {
|
||||||
secrets: true,
|
let body;
|
||||||
persistentStorage: true
|
if (teamId === '0') {
|
||||||
}
|
body = await prisma.application.findFirst({
|
||||||
});
|
where: { id },
|
||||||
|
include: {
|
||||||
|
destinationDocker: true,
|
||||||
|
settings: true,
|
||||||
|
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||||
|
secrets: true,
|
||||||
|
persistentStorage: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
body = await prisma.application.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId } } },
|
||||||
|
include: {
|
||||||
|
destinationDocker: true,
|
||||||
|
settings: true,
|
||||||
|
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||||
|
secrets: true,
|
||||||
|
persistentStorage: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (body?.gitSource?.githubApp?.clientSecret) {
|
if (body?.gitSource?.githubApp?.clientSecret) {
|
||||||
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
|
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
|
||||||
@@ -170,7 +206,14 @@ export async function configureGitRepository({
|
|||||||
projectId,
|
projectId,
|
||||||
webhookToken,
|
webhookToken,
|
||||||
autodeploy
|
autodeploy
|
||||||
}) {
|
}: {
|
||||||
|
id: string;
|
||||||
|
repository: string;
|
||||||
|
branch: string;
|
||||||
|
projectId: number;
|
||||||
|
webhookToken: string;
|
||||||
|
autodeploy: boolean;
|
||||||
|
}): Promise<void> {
|
||||||
if (webhookToken) {
|
if (webhookToken) {
|
||||||
const encryptedWebhookToken = encrypt(webhookToken);
|
const encryptedWebhookToken = encrypt(webhookToken);
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
@@ -200,7 +243,10 @@ export async function configureGitRepository({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureBuildPack({ id, buildPack }) {
|
export async function configureBuildPack({
|
||||||
|
id,
|
||||||
|
buildPack
|
||||||
|
}: Pick<Application, 'id' | 'buildPack'>): Promise<Application> {
|
||||||
return await prisma.application.update({ where: { id }, data: { buildPack } });
|
return await prisma.application.update({ where: { id }, data: { buildPack } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +264,21 @@ export async function configureApplication({
|
|||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable
|
pythonVariable
|
||||||
}) {
|
}: {
|
||||||
|
id: string;
|
||||||
|
buildPack: string;
|
||||||
|
name: string;
|
||||||
|
fqdn: string;
|
||||||
|
port: number;
|
||||||
|
installCommand: string;
|
||||||
|
buildCommand: string;
|
||||||
|
startCommand: string;
|
||||||
|
baseDirectory: string;
|
||||||
|
publishDirectory: string;
|
||||||
|
pythonWSGI: string;
|
||||||
|
pythonModule: string;
|
||||||
|
pythonVariable: string;
|
||||||
|
}): Promise<Application> {
|
||||||
return await prisma.application.update({
|
return await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -238,11 +298,24 @@ export async function configureApplication({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkDoubleBranch(branch, projectId) {
|
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
|
||||||
const applications = await prisma.application.findMany({ where: { branch, projectId } });
|
const applications = await prisma.application.findMany({ where: { branch, projectId } });
|
||||||
return applications.length > 1;
|
return applications.length > 1;
|
||||||
}
|
}
|
||||||
export async function setApplicationSettings({ id, debug, previews, dualCerts, autodeploy }) {
|
|
||||||
|
export async function setApplicationSettings({
|
||||||
|
id,
|
||||||
|
debug,
|
||||||
|
previews,
|
||||||
|
dualCerts,
|
||||||
|
autodeploy
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
debug: boolean;
|
||||||
|
previews: boolean;
|
||||||
|
dualCerts: boolean;
|
||||||
|
autodeploy: boolean;
|
||||||
|
}): Promise<Application & { destinationDocker: DestinationDocker }> {
|
||||||
return await prisma.application.update({
|
return await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
|
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
|
||||||
@@ -250,29 +323,6 @@ export async function setApplicationSettings({ id, debug, previews, dualCerts, a
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createBuild({
|
export async function getPersistentStorage(id: string): Promise<ApplicationPersistentStorage[]> {
|
||||||
id,
|
|
||||||
applicationId,
|
|
||||||
destinationDockerId,
|
|
||||||
gitSourceId,
|
|
||||||
githubAppId,
|
|
||||||
gitlabAppId,
|
|
||||||
type
|
|
||||||
}) {
|
|
||||||
return await prisma.build.create({
|
|
||||||
data: {
|
|
||||||
id,
|
|
||||||
applicationId,
|
|
||||||
destinationDockerId,
|
|
||||||
gitSourceId,
|
|
||||||
githubAppId,
|
|
||||||
gitlabAppId,
|
|
||||||
status: 'running',
|
|
||||||
type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPersistentStorage(id) {
|
|
||||||
return await prisma.applicationPersistentStorage.findMany({ where: { applicationId: id } });
|
return await prisma.applicationPersistentStorage.findMany({ where: { applicationId: id } });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { getDomain } from '$lib/common';
|
import { getDomain } from '$lib/common';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { Application, ServiceSecret, DestinationDocker, Secret } from '@prisma/client';
|
||||||
|
|
||||||
export async function isBranchAlreadyUsed({ repository, branch, id }) {
|
export async function isBranchAlreadyUsed({
|
||||||
|
repository,
|
||||||
|
branch,
|
||||||
|
id
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
repository: string;
|
||||||
|
branch: string;
|
||||||
|
}): Promise<Application> {
|
||||||
const application = await prisma.application.findUnique({
|
const application = await prisma.application.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { gitSource: true }
|
include: { gitSource: true }
|
||||||
@@ -11,18 +20,42 @@ export async function isBranchAlreadyUsed({ repository, branch, id }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isDockerNetworkExists({ network }) {
|
export async function isDockerNetworkExists({
|
||||||
|
network
|
||||||
|
}: {
|
||||||
|
network: string;
|
||||||
|
}): Promise<DestinationDocker> {
|
||||||
return await prisma.destinationDocker.findFirst({ where: { network } });
|
return await prisma.destinationDocker.findFirst({ where: { network } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isServiceSecretExists({ id, name }) {
|
export async function isServiceSecretExists({
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<ServiceSecret> {
|
||||||
return await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
|
return await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
|
||||||
}
|
}
|
||||||
export async function isSecretExists({ id, name, isPRMRSecret }) {
|
export async function isSecretExists({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
isPRMRSecret
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
isPRMRSecret: boolean;
|
||||||
|
}): Promise<Secret> {
|
||||||
return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
|
return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isDomainConfigured({ id, fqdn }) {
|
export async function isDomainConfigured({
|
||||||
|
id,
|
||||||
|
fqdn
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
}): Promise<boolean> {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const nakedDomain = domain.replace('www.', '');
|
const nakedDomain = domain.replace('www.', '');
|
||||||
const foundApp = await prisma.application.findFirst({
|
const foundApp = await prisma.application.findFirst({
|
||||||
@@ -55,6 +88,5 @@ export async function isDomainConfigured({ id, fqdn }) {
|
|||||||
},
|
},
|
||||||
select: { fqdn: true }
|
select: { fqdn: true }
|
||||||
});
|
});
|
||||||
if (foundApp || foundService || coolifyFqdn) return true;
|
return !!(foundApp || foundService || coolifyFqdn);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import { sentry } from '$lib/common';
|
import { sentry } from '$lib/common';
|
||||||
|
import {
|
||||||
|
supportedDatabaseTypesAndVersions,
|
||||||
|
supportedServiceTypesAndVersions
|
||||||
|
} from '$lib/components/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 type { Database, DatabaseSettings } from '@prisma/client';
|
||||||
import generator from 'generate-password';
|
import generator from 'generate-password';
|
||||||
import forge from 'node-forge';
|
import forge from 'node-forge';
|
||||||
|
|
||||||
export function generatePassword(length = 24) {
|
export function generatePassword(length = 24): string {
|
||||||
return generator.generate({
|
return generator.generate({
|
||||||
length,
|
length,
|
||||||
numbers: true,
|
numbers: true,
|
||||||
@@ -26,8 +30,14 @@ export const prisma = new PrismaClient({
|
|||||||
rejectOnNotFound: false
|
rejectOnNotFound: false
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ErrorHandler(e) {
|
export function ErrorHandler(e: {
|
||||||
if (e! instanceof Error) {
|
stdout?;
|
||||||
|
message?: string;
|
||||||
|
status?: number;
|
||||||
|
name?: string;
|
||||||
|
error?: string;
|
||||||
|
}): { status: number; body: { message: string; error: string } } {
|
||||||
|
if (e && e instanceof Error) {
|
||||||
e = new Error(e.toString());
|
e = new Error(e.toString());
|
||||||
}
|
}
|
||||||
let truncatedError = e;
|
let truncatedError = e;
|
||||||
@@ -35,8 +45,7 @@ export function ErrorHandler(e) {
|
|||||||
truncatedError = e.stdout;
|
truncatedError = e.stdout;
|
||||||
}
|
}
|
||||||
if (e.message?.includes('docker run')) {
|
if (e.message?.includes('docker run')) {
|
||||||
let truncatedArray = [];
|
const truncatedArray: string[] = truncatedError.message.split('-').filter((line) => {
|
||||||
truncatedArray = truncatedError.message.split('-').filter((line) => {
|
|
||||||
if (!line.startsWith('e ')) {
|
if (!line.startsWith('e ')) {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
@@ -64,11 +73,11 @@ export function ErrorHandler(e) {
|
|||||||
payload.body.message = 'Already exists. Choose another name.';
|
payload.body.message = 'Already exists. Choose another name.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// console.error(e)
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateSshKeyPair(): Promise<{ publicKey: string; privateKey: string }> {
|
export async function generateSshKeyPair(): Promise<{ publicKey: string; privateKey: string }> {
|
||||||
return await new Promise(async (resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
forge.pki.rsa.generateKeyPair({ bits: 4096, workers: -1 }, function (err, keys) {
|
forge.pki.rsa.generateKeyPair({ bits: 4096, workers: -1 }, function (err, keys) {
|
||||||
if (keys) {
|
if (keys) {
|
||||||
resolve({
|
resolve({
|
||||||
@@ -82,163 +91,93 @@ export async function generateSshKeyPair(): Promise<{ publicKey: string; private
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const supportedDatabaseTypesAndVersions = [
|
export function getVersions(type: string): string[] {
|
||||||
{
|
|
||||||
name: 'mongodb',
|
|
||||||
fancyName: 'MongoDB',
|
|
||||||
baseImage: 'bitnami/mongodb',
|
|
||||||
versions: ['5.0.5', '4.4.11', '4.2.18', '4.0.27']
|
|
||||||
},
|
|
||||||
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0.27', '5.7.36'] },
|
|
||||||
{
|
|
||||||
name: 'postgresql',
|
|
||||||
fancyName: 'PostgreSQL',
|
|
||||||
baseImage: 'bitnami/postgresql',
|
|
||||||
versions: ['14.1.0', '13.5.0', '12.9.0', '11.14.0', '10.19.0', '9.6.24']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'redis',
|
|
||||||
fancyName: 'Redis',
|
|
||||||
baseImage: 'bitnami/redis',
|
|
||||||
versions: ['6.2.6', '6.0.16', '5.0.14']
|
|
||||||
},
|
|
||||||
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
|
|
||||||
];
|
|
||||||
export const supportedServiceTypesAndVersions = [
|
|
||||||
{
|
|
||||||
name: 'plausibleanalytics',
|
|
||||||
fancyName: 'Plausible Analytics',
|
|
||||||
baseImage: 'plausible/analytics',
|
|
||||||
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 8000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'nocodb',
|
|
||||||
fancyName: 'NocoDB',
|
|
||||||
baseImage: 'nocodb/nocodb',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 8080
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'minio',
|
|
||||||
fancyName: 'MinIO',
|
|
||||||
baseImage: 'minio/minio',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 9001
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vscodeserver',
|
|
||||||
fancyName: 'VSCode Server',
|
|
||||||
baseImage: 'codercom/code-server',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 8080
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'wordpress',
|
|
||||||
fancyName: 'Wordpress',
|
|
||||||
baseImage: 'wordpress',
|
|
||||||
images: ['bitnami/mysql:5.7'],
|
|
||||||
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
|
|
||||||
ports: {
|
|
||||||
main: 80
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vaultwarden',
|
|
||||||
fancyName: 'Vaultwarden',
|
|
||||||
baseImage: 'vaultwarden/server',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 80
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'languagetool',
|
|
||||||
fancyName: 'LanguageTool',
|
|
||||||
baseImage: 'silviof/docker-languagetool',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 8010
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'n8n',
|
|
||||||
fancyName: 'n8n',
|
|
||||||
baseImage: 'n8nio/n8n',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 5678
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'uptimekuma',
|
|
||||||
fancyName: 'Uptime Kuma',
|
|
||||||
baseImage: 'louislam/uptime-kuma',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 3001
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ghost',
|
|
||||||
fancyName: 'Ghost',
|
|
||||||
baseImage: 'bitnami/ghost',
|
|
||||||
images: ['bitnami/mariadb'],
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 2368
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'meilisearch',
|
|
||||||
fancyName: 'Meilisearch',
|
|
||||||
baseImage: 'getmeili/meilisearch',
|
|
||||||
images: [],
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 7700
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export function getVersions(type) {
|
|
||||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found.versions;
|
return found.versions;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
export function getDatabaseImage(type) {
|
|
||||||
|
export function getDatabaseImage(type: string): string {
|
||||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found.baseImage;
|
return found.baseImage;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
export function getServiceImage(type) {
|
|
||||||
|
export function getServiceImage(type: string): string {
|
||||||
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
|
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found.baseImage;
|
return found.baseImage;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
export function getServiceImages(type) {
|
|
||||||
|
export function getServiceImages(type: string): string[] {
|
||||||
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
|
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found.images;
|
return found.images;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
export function generateDatabaseConfiguration(database) {
|
|
||||||
|
export function generateDatabaseConfiguration(database: Database & { settings: DatabaseSettings }):
|
||||||
|
| {
|
||||||
|
volume: string;
|
||||||
|
image: string;
|
||||||
|
ulimits: Record<string, unknown>;
|
||||||
|
privatePort: number;
|
||||||
|
environmentVariables: {
|
||||||
|
MYSQL_DATABASE: string;
|
||||||
|
MYSQL_PASSWORD: string;
|
||||||
|
MYSQL_ROOT_USER: string;
|
||||||
|
MYSQL_USER: string;
|
||||||
|
MYSQL_ROOT_PASSWORD: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
volume: string;
|
||||||
|
image: string;
|
||||||
|
ulimits: Record<string, unknown>;
|
||||||
|
privatePort: number;
|
||||||
|
environmentVariables: {
|
||||||
|
MONGODB_ROOT_USER: string;
|
||||||
|
MONGODB_ROOT_PASSWORD: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
volume: string;
|
||||||
|
image: string;
|
||||||
|
ulimits: Record<string, unknown>;
|
||||||
|
privatePort: number;
|
||||||
|
environmentVariables: {
|
||||||
|
POSTGRESQL_USERNAME: string;
|
||||||
|
POSTGRESQL_PASSWORD: string;
|
||||||
|
POSTGRESQL_DATABASE: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
volume: string;
|
||||||
|
image: string;
|
||||||
|
ulimits: Record<string, unknown>;
|
||||||
|
privatePort: number;
|
||||||
|
environmentVariables: {
|
||||||
|
REDIS_AOF_ENABLED: string;
|
||||||
|
REDIS_PASSWORD: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
volume: string;
|
||||||
|
image: string;
|
||||||
|
ulimits: Record<string, unknown>;
|
||||||
|
privatePort: number;
|
||||||
|
environmentVariables: {
|
||||||
|
COUCHDB_PASSWORD: string;
|
||||||
|
COUCHDB_USER: string;
|
||||||
|
};
|
||||||
|
} {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
dbUser,
|
dbUser,
|
||||||
@@ -253,7 +192,6 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
const baseImage = getDatabaseImage(type);
|
const baseImage = getDatabaseImage(type);
|
||||||
if (type === 'mysql') {
|
if (type === 'mysql') {
|
||||||
return {
|
return {
|
||||||
// url: `mysql://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 3306}/${defaultDatabase}`,
|
|
||||||
privatePort: 3306,
|
privatePort: 3306,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
MYSQL_USER: dbUser,
|
MYSQL_USER: dbUser,
|
||||||
@@ -268,7 +206,6 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
};
|
};
|
||||||
} else if (type === 'mongodb') {
|
} else if (type === 'mongodb') {
|
||||||
return {
|
return {
|
||||||
// url: `mongodb://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 27017}/${defaultDatabase}`,
|
|
||||||
privatePort: 27017,
|
privatePort: 27017,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
MONGODB_ROOT_USER: rootUser,
|
MONGODB_ROOT_USER: rootUser,
|
||||||
@@ -280,7 +217,6 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
};
|
};
|
||||||
} else if (type === 'postgresql') {
|
} else if (type === 'postgresql') {
|
||||||
return {
|
return {
|
||||||
// url: `psql://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 5432}/${defaultDatabase}`,
|
|
||||||
privatePort: 5432,
|
privatePort: 5432,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
POSTGRESQL_PASSWORD: dbUserPassword,
|
POSTGRESQL_PASSWORD: dbUserPassword,
|
||||||
@@ -293,7 +229,6 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
};
|
};
|
||||||
} else if (type === 'redis') {
|
} else if (type === 'redis') {
|
||||||
return {
|
return {
|
||||||
// url: `redis://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 6379}/${defaultDatabase}`,
|
|
||||||
privatePort: 6379,
|
privatePort: 6379,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
REDIS_PASSWORD: dbUserPassword,
|
REDIS_PASSWORD: dbUserPassword,
|
||||||
@@ -305,7 +240,6 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
};
|
};
|
||||||
} else if (type === 'couchdb') {
|
} else if (type === 'couchdb') {
|
||||||
return {
|
return {
|
||||||
// url: `couchdb://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 5984}/${defaultDatabase}`,
|
|
||||||
privatePort: 5984,
|
privatePort: 5984,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
COUCHDB_PASSWORD: dbUserPassword,
|
COUCHDB_PASSWORD: dbUserPassword,
|
||||||
@@ -316,18 +250,4 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// } else if (type === 'clickhouse') {
|
|
||||||
// return {
|
|
||||||
// url: `clickhouse://${dbUser}:${dbUserPassword}@${id}:${port}/${defaultDatabase}`,
|
|
||||||
// privatePort: 9000,
|
|
||||||
// image: `bitnami/clickhouse-server:${version}`,
|
|
||||||
// volume: `${id}-${type}-data:/var/lib/clickhouse`,
|
|
||||||
// ulimits: {
|
|
||||||
// nofile: {
|
|
||||||
// soft: 262144,
|
|
||||||
// hard: 262144
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
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 } from './common';
|
||||||
import getPort, { portNumbers } from 'get-port';
|
|
||||||
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
|
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
|
||||||
|
import type { Database, DatabaseSettings, DestinationDocker } from '@prisma/client';
|
||||||
|
|
||||||
export async function listDatabases(teamId) {
|
export async function listDatabases(teamId: string): Promise<Database[]> {
|
||||||
return await prisma.database.findMany({ where: { teams: { some: { id: teamId } } } });
|
if (teamId === '0') {
|
||||||
|
return await prisma.database.findMany({ include: { teams: true } });
|
||||||
|
} else {
|
||||||
|
return await prisma.database.findMany({
|
||||||
|
where: { teams: { some: { id: teamId } } },
|
||||||
|
include: { teams: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export async function newDatabase({ name, teamId }) {
|
|
||||||
|
export async function newDatabase({
|
||||||
|
name,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<Database> {
|
||||||
const dbUser = cuid();
|
const dbUser = cuid();
|
||||||
const dbUserPassword = encrypt(generatePassword());
|
const dbUserPassword = encrypt(generatePassword());
|
||||||
const rootUser = cuid();
|
const rootUser = cuid();
|
||||||
@@ -30,25 +43,44 @@ export async function newDatabase({ name, teamId }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDatabase({ id, teamId }) {
|
export async function getDatabase({
|
||||||
const body = await prisma.database.findFirst({
|
id,
|
||||||
where: { id, teams: { some: { id: teamId } } },
|
teamId
|
||||||
include: { destinationDocker: true, settings: true }
|
}: {
|
||||||
});
|
id: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<Database & { destinationDocker: DestinationDocker; settings: DatabaseSettings }> {
|
||||||
|
let body;
|
||||||
|
if (teamId === '0') {
|
||||||
|
body = await prisma.database.findFirst({
|
||||||
|
where: { id },
|
||||||
|
include: { destinationDocker: true, settings: true }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
body = await prisma.database.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId } } },
|
||||||
|
include: { destinationDocker: true, settings: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
if (body.dbUserPassword) body.dbUserPassword = decrypt(body.dbUserPassword);
|
if (body.dbUserPassword) body.dbUserPassword = decrypt(body.dbUserPassword);
|
||||||
if (body.rootUserPassword) body.rootUserPassword = decrypt(body.rootUserPassword);
|
if (body.rootUserPassword) body.rootUserPassword = decrypt(body.rootUserPassword);
|
||||||
|
|
||||||
return { ...body };
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeDatabase({ id }) {
|
export async function removeDatabase({ id }: { id: string }): Promise<void> {
|
||||||
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
||||||
await prisma.database.delete({ where: { id } });
|
await prisma.database.delete({ where: { id } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureDatabaseType({ id, type }) {
|
export async function configureDatabaseType({
|
||||||
|
id,
|
||||||
|
type
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
}): Promise<Database> {
|
||||||
return await prisma.database.update({
|
return await prisma.database.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { type }
|
data: { type }
|
||||||
@@ -64,7 +96,7 @@ export async function setDatabase({
|
|||||||
version?: string;
|
version?: string;
|
||||||
isPublic?: boolean;
|
isPublic?: boolean;
|
||||||
appendOnly?: boolean;
|
appendOnly?: boolean;
|
||||||
}) {
|
}): Promise<Database> {
|
||||||
return await prisma.database.update({
|
return await prisma.database.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -82,7 +114,16 @@ export async function updateDatabase({
|
|||||||
rootUser,
|
rootUser,
|
||||||
rootUserPassword,
|
rootUserPassword,
|
||||||
version
|
version
|
||||||
}) {
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
defaultDatabase: string;
|
||||||
|
dbUser: string;
|
||||||
|
dbUserPassword: string;
|
||||||
|
rootUser: string;
|
||||||
|
rootUserPassword: string;
|
||||||
|
version: string;
|
||||||
|
}): Promise<Database> {
|
||||||
const encryptedDbUserPassword = dbUserPassword && encrypt(dbUserPassword);
|
const encryptedDbUserPassword = dbUserPassword && encrypt(dbUserPassword);
|
||||||
const encryptedRootUserPassword = rootUserPassword && encrypt(rootUserPassword);
|
const encryptedRootUserPassword = rootUserPassword && encrypt(rootUserPassword);
|
||||||
return await prisma.database.update({
|
return await prisma.database.update({
|
||||||
@@ -99,7 +140,9 @@ export async function updateDatabase({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stopDatabase(database) {
|
export async function stopDatabase(
|
||||||
|
database: Database & { destinationDocker: DestinationDocker }
|
||||||
|
): Promise<boolean> {
|
||||||
let everStarted = false;
|
let everStarted = false;
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
@@ -122,3 +165,43 @@ export async function stopDatabase(database) {
|
|||||||
}
|
}
|
||||||
return everStarted;
|
return everStarted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updatePasswordInDb(database, user, newPassword, isRoot) {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
rootUser,
|
||||||
|
rootUserPassword,
|
||||||
|
dbUser,
|
||||||
|
dbUserPassword,
|
||||||
|
defaultDatabase,
|
||||||
|
destinationDockerId,
|
||||||
|
destinationDocker: { engine }
|
||||||
|
} = database;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
const host = getEngine(engine);
|
||||||
|
if (type === 'mysql') {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker exec ${id} mysql -u ${rootUser} -p${rootUserPassword} -e \"ALTER USER '${user}'@'%' IDENTIFIED WITH caching_sha2_password BY '${newPassword}';\"`
|
||||||
|
);
|
||||||
|
} else if (type === 'postgresql') {
|
||||||
|
if (isRoot) {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker exec ${id} psql postgresql://postgres:${rootUserPassword}@${id}:5432/${defaultDatabase} -c "ALTER role postgres WITH PASSWORD '${newPassword}'"`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker exec ${id} psql postgresql://${dbUser}:${dbUserPassword}@${id}:5432/${defaultDatabase} -c "ALTER role ${user} WITH PASSWORD '${newPassword}'"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (type === 'mongodb') {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker exec ${id} mongo 'mongodb://${rootUser}:${rootUserPassword}@${id}:27017/admin?readPreference=primary&ssl=false' --eval "db.changeUserPassword('${user}','${newPassword}')"`
|
||||||
|
);
|
||||||
|
} else if (type === 'redis') {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker exec ${id} redis-cli -u redis://${dbUserPassword}@${id}:6379 --raw CONFIG SET requirepass ${newPassword}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,27 +1,53 @@
|
|||||||
import { asyncExecShell, getEngine } from '$lib/common';
|
import { asyncExecShell, getEngine } from '$lib/common';
|
||||||
import { decrypt, encrypt } from '$lib/crypto';
|
|
||||||
import { dockerInstance } from '$lib/docker';
|
import { dockerInstance } from '$lib/docker';
|
||||||
import { startCoolifyProxy } from '$lib/haproxy';
|
import { startCoolifyProxy } from '$lib/haproxy';
|
||||||
import { getDatabaseImage } from '.';
|
import { getDatabaseImage } from '.';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { DestinationDocker, Service, Application, Prisma } from '@prisma/client';
|
||||||
|
import type { CreateDockerDestination } from '$lib/types/destinations';
|
||||||
|
|
||||||
export async function listDestinations(teamId) {
|
type DestinationConfigurationObject = {
|
||||||
return await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId } } } });
|
id: string;
|
||||||
|
destinationId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FindDestinationFromTeam = {
|
||||||
|
id: string;
|
||||||
|
teamId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function listDestinations(teamId: string): Promise<DestinationDocker[]> {
|
||||||
|
if (teamId === '0') {
|
||||||
|
return await prisma.destinationDocker.findMany({ include: { teams: true } });
|
||||||
|
}
|
||||||
|
return await prisma.destinationDocker.findMany({
|
||||||
|
where: { teams: { some: { id: teamId } } },
|
||||||
|
include: { teams: true }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureDestinationForService({ id, destinationId }) {
|
export async function configureDestinationForService({
|
||||||
|
id,
|
||||||
|
destinationId
|
||||||
|
}: DestinationConfigurationObject): Promise<Service> {
|
||||||
return await prisma.service.update({
|
return await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { destinationDocker: { connect: { id: destinationId } } }
|
data: { destinationDocker: { connect: { id: destinationId } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function configureDestinationForApplication({ id, destinationId }) {
|
export async function configureDestinationForApplication({
|
||||||
|
id,
|
||||||
|
destinationId
|
||||||
|
}: DestinationConfigurationObject): Promise<Application> {
|
||||||
return await prisma.application.update({
|
return await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { destinationDocker: { connect: { id: destinationId } } }
|
data: { destinationDocker: { connect: { id: destinationId } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function configureDestinationForDatabase({ id, destinationId }) {
|
export async function configureDestinationForDatabase({
|
||||||
|
id,
|
||||||
|
destinationId
|
||||||
|
}: DestinationConfigurationObject): Promise<void> {
|
||||||
await prisma.database.update({
|
await prisma.database.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { destinationDocker: { connect: { id: destinationId } } }
|
data: { destinationDocker: { connect: { id: destinationId } } }
|
||||||
@@ -38,13 +64,16 @@ export async function configureDestinationForDatabase({ id, destinationId }) {
|
|||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
if (type && version) {
|
if (type && version) {
|
||||||
const baseImage = getDatabaseImage(type);
|
const baseImage = getDatabaseImage(type);
|
||||||
asyncExecShell(
|
asyncExecShell(`DOCKER_HOST=${host} docker pull ${baseImage}:${version}`);
|
||||||
`DOCKER_HOST=${host} docker pull ${baseImage}:${version} && echo "FROM ${baseImage}:${version}" | docker build --label coolify.image="true" -t "${baseImage}:${version}" -`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function updateDestination({ id, name, engine, network }) {
|
export async function updateDestination({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
engine,
|
||||||
|
network
|
||||||
|
}: Pick<DestinationDocker, 'id' | 'name' | 'engine' | 'network'>): Promise<DestinationDocker> {
|
||||||
return await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
return await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,13 +83,8 @@ export async function newRemoteDestination({
|
|||||||
engine,
|
engine,
|
||||||
network,
|
network,
|
||||||
isCoolifyProxyUsed,
|
isCoolifyProxyUsed,
|
||||||
remoteEngine,
|
remoteEngine
|
||||||
ipAddress,
|
}: CreateDockerDestination): Promise<string> {
|
||||||
user,
|
|
||||||
port,
|
|
||||||
sshPrivateKey
|
|
||||||
}) {
|
|
||||||
const encryptedPrivateKey = encrypt(sshPrivateKey);
|
|
||||||
const destination = await prisma.destinationDocker.create({
|
const destination = await prisma.destinationDocker.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
@@ -68,16 +92,18 @@ export async function newRemoteDestination({
|
|||||||
engine,
|
engine,
|
||||||
network,
|
network,
|
||||||
isCoolifyProxyUsed,
|
isCoolifyProxyUsed,
|
||||||
remoteEngine,
|
remoteEngine
|
||||||
ipAddress,
|
|
||||||
user,
|
|
||||||
port,
|
|
||||||
sshPrivateKey: encryptedPrivateKey
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return destination.id;
|
return destination.id;
|
||||||
}
|
}
|
||||||
export async function newLocalDestination({ name, teamId, engine, network, isCoolifyProxyUsed }) {
|
export async function newLocalDestination({
|
||||||
|
name,
|
||||||
|
teamId,
|
||||||
|
engine,
|
||||||
|
network,
|
||||||
|
isCoolifyProxyUsed
|
||||||
|
}: CreateDockerDestination): Promise<string> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
const docker = dockerInstance({ destinationDocker: { engine, network } });
|
const docker = dockerInstance({ destinationDocker: { engine, network } });
|
||||||
const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } });
|
const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } });
|
||||||
@@ -95,18 +121,14 @@ export async function newLocalDestination({ name, teamId, engine, network, isCoo
|
|||||||
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
|
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
|
||||||
);
|
);
|
||||||
if (proxyConfigured) {
|
if (proxyConfigured) {
|
||||||
if (proxyConfigured.isCoolifyProxyUsed) {
|
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
|
||||||
isCoolifyProxyUsed = true;
|
|
||||||
} else {
|
|
||||||
isCoolifyProxyUsed = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
||||||
}
|
}
|
||||||
if (isCoolifyProxyUsed) await startCoolifyProxy(engine);
|
if (isCoolifyProxyUsed) await startCoolifyProxy(engine);
|
||||||
return destination.id;
|
return destination.id;
|
||||||
}
|
}
|
||||||
export async function removeDestination({ id }) {
|
export async function removeDestination({ id }: Pick<DestinationDocker, 'id'>): Promise<void> {
|
||||||
const destination = await prisma.destinationDocker.delete({ where: { id } });
|
const destination = await prisma.destinationDocker.delete({ where: { id } });
|
||||||
if (destination.isCoolifyProxyUsed) {
|
if (destination.isCoolifyProxyUsed) {
|
||||||
const host = getEngine(destination.engine);
|
const host = getEngine(destination.engine);
|
||||||
@@ -123,22 +145,39 @@ export async function removeDestination({ id }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDestination({ id, teamId }) {
|
export async function getDestination({
|
||||||
let destination = await prisma.destinationDocker.findFirst({
|
id,
|
||||||
where: { id, teams: { some: { id: teamId } } }
|
teamId
|
||||||
});
|
}: FindDestinationFromTeam): Promise<DestinationDocker & { sshPrivateKey?: string }> {
|
||||||
if (destination.remoteEngine) {
|
let destination;
|
||||||
destination.sshPrivateKey = decrypt(destination.sshPrivateKey);
|
if (teamId === '0') {
|
||||||
|
destination = await prisma.destinationDocker.findFirst({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
destination = await prisma.destinationDocker.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId } } }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
export async function getDestinationByApplicationId({ id, teamId }) {
|
export async function getDestinationByApplicationId({
|
||||||
|
id,
|
||||||
|
teamId
|
||||||
|
}: FindDestinationFromTeam): Promise<DestinationDocker> {
|
||||||
return await prisma.destinationDocker.findFirst({
|
return await prisma.destinationDocker.findFirst({
|
||||||
where: { application: { some: { id } }, teams: { some: { id: teamId } } }
|
where: { application: { some: { id } }, teams: { some: { id: teamId } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setDestinationSettings({ engine, isCoolifyProxyUsed }) {
|
export async function setDestinationSettings({
|
||||||
|
engine,
|
||||||
|
isCoolifyProxyUsed
|
||||||
|
}: {
|
||||||
|
engine: string;
|
||||||
|
isCoolifyProxyUsed: boolean;
|
||||||
|
}): Promise<Prisma.BatchPayload> {
|
||||||
return await prisma.destinationDocker.updateMany({
|
return await prisma.destinationDocker.updateMany({
|
||||||
where: { engine },
|
where: { engine },
|
||||||
data: { isCoolifyProxyUsed }
|
data: { isCoolifyProxyUsed }
|
||||||
|
|||||||
@@ -1,27 +1,36 @@
|
|||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { GithubApp, GitlabApp, GitSource, Prisma, Application } from '@prisma/client';
|
||||||
|
|
||||||
export async function listSources(teamId) {
|
export async function listSources(
|
||||||
|
teamId: string | Prisma.StringFilter
|
||||||
|
): Promise<(GitSource & { githubApp?: GithubApp; gitlabApp?: GitlabApp })[]> {
|
||||||
|
if (teamId === '0') {
|
||||||
|
return await prisma.gitSource.findMany({
|
||||||
|
include: { githubApp: true, gitlabApp: true, teams: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
return await prisma.gitSource.findMany({
|
return await prisma.gitSource.findMany({
|
||||||
where: { teams: { some: { id: teamId } } },
|
where: { teams: { some: { id: teamId } } },
|
||||||
include: { githubApp: true, gitlabApp: true }
|
include: { githubApp: true, gitlabApp: true, teams: true }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function newSource({ name, teamId, type, htmlUrl, apiUrl, organization }) {
|
export async function newSource({
|
||||||
|
name,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<GitSource> {
|
||||||
return await prisma.gitSource.create({
|
return await prisma.gitSource.create({
|
||||||
data: {
|
data: {
|
||||||
teams: { connect: { id: teamId } },
|
|
||||||
name,
|
name,
|
||||||
type,
|
teams: { connect: { id: teamId } }
|
||||||
htmlUrl,
|
|
||||||
apiUrl,
|
|
||||||
organization
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function removeSource({ id }) {
|
export async function removeSource({ id }: { id: string }): Promise<void> {
|
||||||
// TODO: Disconnect application with this sourceId! Maybe not needed?
|
|
||||||
const source = await prisma.gitSource.delete({
|
const source = await prisma.gitSource.delete({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { githubApp: true, gitlabApp: true }
|
include: { githubApp: true, gitlabApp: true }
|
||||||
@@ -30,11 +39,25 @@ export async function removeSource({ id }) {
|
|||||||
if (source.gitlabAppId) await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
|
if (source.gitlabAppId) await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSource({ id, teamId }) {
|
export async function getSource({
|
||||||
let body = await prisma.gitSource.findFirst({
|
id,
|
||||||
where: { id, teams: { some: { id: teamId } } },
|
teamId
|
||||||
include: { githubApp: true, gitlabApp: true }
|
}: {
|
||||||
});
|
id: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<GitSource & { githubApp: GithubApp; gitlabApp: GitlabApp }> {
|
||||||
|
let body;
|
||||||
|
if (teamId === '0') {
|
||||||
|
body = await prisma.gitSource.findFirst({
|
||||||
|
where: { id },
|
||||||
|
include: { githubApp: true, gitlabApp: true }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
body = await prisma.gitSource.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId } } },
|
||||||
|
include: { githubApp: true, gitlabApp: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
if (body?.githubApp?.clientSecret)
|
if (body?.githubApp?.clientSecret)
|
||||||
body.githubApp.clientSecret = decrypt(body.githubApp.clientSecret);
|
body.githubApp.clientSecret = decrypt(body.githubApp.clientSecret);
|
||||||
if (body?.githubApp?.webhookSecret)
|
if (body?.githubApp?.webhookSecret)
|
||||||
@@ -43,29 +66,69 @@ export async function getSource({ id, teamId }) {
|
|||||||
if (body?.gitlabApp?.appSecret) body.gitlabApp.appSecret = decrypt(body.gitlabApp.appSecret);
|
if (body?.gitlabApp?.appSecret) body.gitlabApp.appSecret = decrypt(body.gitlabApp.appSecret);
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
export async function addSource({ id, appId, teamId, oauthId, groupName, appSecret }) {
|
export async function addGitHubSource({ id, teamId, type, name, htmlUrl, apiUrl, organization }) {
|
||||||
|
await prisma.gitSource.update({
|
||||||
|
where: { id },
|
||||||
|
data: { type, name, htmlUrl, apiUrl, organization }
|
||||||
|
});
|
||||||
|
return await prisma.githubApp.create({
|
||||||
|
data: {
|
||||||
|
teams: { connect: { id: teamId } },
|
||||||
|
gitSource: { connect: { id } }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export async function addGitLabSource({
|
||||||
|
id,
|
||||||
|
teamId,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
htmlUrl,
|
||||||
|
apiUrl,
|
||||||
|
oauthId,
|
||||||
|
appId,
|
||||||
|
appSecret,
|
||||||
|
groupName
|
||||||
|
}) {
|
||||||
const encrptedAppSecret = encrypt(appSecret);
|
const encrptedAppSecret = encrypt(appSecret);
|
||||||
|
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name } });
|
||||||
return await prisma.gitlabApp.create({
|
return await prisma.gitlabApp.create({
|
||||||
data: {
|
data: {
|
||||||
teams: { connect: { id: teamId } },
|
teams: { connect: { id: teamId } },
|
||||||
appId,
|
appId,
|
||||||
oauthId,
|
oauthId,
|
||||||
groupName,
|
groupName,
|
||||||
appSecret: encrptedAppSecret,
|
appSecret: encryptedAppSecret,
|
||||||
gitSource: { connect: { id } }
|
gitSource: { connect: { id } }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureGitsource({ id, gitSourceId }) {
|
export async function configureGitsource({
|
||||||
|
id,
|
||||||
|
gitSourceId
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
gitSourceId: string;
|
||||||
|
}): Promise<Application> {
|
||||||
return await prisma.application.update({
|
return await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { gitSource: { connect: { id: gitSourceId } } }
|
data: { gitSource: { connect: { id: gitSourceId } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function updateGitsource({ id, name }) {
|
export async function updateGitsource({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
htmlUrl,
|
||||||
|
apiUrl
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
htmlUrl: string;
|
||||||
|
apiUrl: string;
|
||||||
|
}): Promise<GitSource> {
|
||||||
return await prisma.gitSource.update({
|
return await prisma.gitSource.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { name }
|
data: { name, htmlUrl, apiUrl }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { GithubApp } from '@prisma/client';
|
||||||
|
|
||||||
export async function addInstallation({ gitSourceId, installation_id }) {
|
// TODO: We should change installation_id to be camelCase
|
||||||
|
export async function addInstallation({
|
||||||
|
gitSourceId,
|
||||||
|
installation_id
|
||||||
|
}: {
|
||||||
|
gitSourceId: string;
|
||||||
|
installation_id: string;
|
||||||
|
}): Promise<GithubApp> {
|
||||||
const source = await prisma.gitSource.findUnique({
|
const source = await prisma.gitSource.findUnique({
|
||||||
where: { id: gitSourceId },
|
where: { id: gitSourceId },
|
||||||
include: { githubApp: true }
|
include: { githubApp: true }
|
||||||
@@ -12,8 +20,12 @@ export async function addInstallation({ gitSourceId, installation_id }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUniqueGithubApp({ githubAppId }) {
|
export async function getUniqueGithubApp({
|
||||||
let body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
githubAppId
|
||||||
|
}: {
|
||||||
|
githubAppId: string;
|
||||||
|
}): Promise<GithubApp> {
|
||||||
|
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
||||||
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
|
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
@@ -26,7 +38,15 @@ export async function createGithubApp({
|
|||||||
pem,
|
pem,
|
||||||
webhook_secret,
|
webhook_secret,
|
||||||
state
|
state
|
||||||
}) {
|
}: {
|
||||||
|
id: number;
|
||||||
|
client_id: string;
|
||||||
|
slug: string;
|
||||||
|
client_secret: string;
|
||||||
|
pem: string;
|
||||||
|
webhook_secret: string;
|
||||||
|
state: string;
|
||||||
|
}): Promise<GithubApp> {
|
||||||
const encryptedClientSecret = encrypt(client_secret);
|
const encryptedClientSecret = encrypt(client_secret);
|
||||||
const encryptedWebhookSecret = encrypt(webhook_secret);
|
const encryptedWebhookSecret = encrypt(webhook_secret);
|
||||||
const encryptedPem = encrypt(pem);
|
const encryptedPem = encrypt(pem);
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { encrypt } from '$lib/crypto';
|
import { encrypt } from '$lib/crypto';
|
||||||
import { generateSshKeyPair, prisma } from './common';
|
import { generateSshKeyPair, prisma } from './common';
|
||||||
|
import type { GitlabApp } from '@prisma/client';
|
||||||
|
|
||||||
export async function updateDeployKey({ id, deployKeyId }) {
|
export async function updateDeployKey({
|
||||||
|
id,
|
||||||
|
deployKeyId
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
deployKeyId: number;
|
||||||
|
}): Promise<GitlabApp> {
|
||||||
const application = await prisma.application.findUnique({
|
const application = await prisma.application.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { gitSource: { include: { gitlabApp: true } } }
|
include: { gitSource: { include: { gitlabApp: true } } }
|
||||||
@@ -11,14 +18,24 @@ export async function updateDeployKey({ id, deployKeyId }) {
|
|||||||
data: { deployKeyId }
|
data: { deployKeyId }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function getSshKey({ id }) {
|
export async function getSshKey({
|
||||||
|
id
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
}): Promise<{ status: number; body: { publicKey: string } }> {
|
||||||
const application = await prisma.application.findUnique({
|
const application = await prisma.application.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { gitSource: { include: { gitlabApp: true } } }
|
include: { gitSource: { include: { gitlabApp: true } } }
|
||||||
});
|
});
|
||||||
return { status: 200, body: { publicKey: application.gitSource.gitlabApp.publicSshKey } };
|
return { status: 200, body: { publicKey: application.gitSource.gitlabApp.publicSshKey } };
|
||||||
}
|
}
|
||||||
export async function generateSshKey({ id }) {
|
export async function generateSshKey({
|
||||||
|
id
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
}): Promise<
|
||||||
|
{ status: number; body: { publicKey: string } } | { status: number; body?: undefined }
|
||||||
|
> {
|
||||||
const application = await prisma.application.findUnique({
|
const application = await prisma.application.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { gitSource: { include: { gitlabApp: true } } }
|
include: { gitSource: { include: { gitlabApp: true } } }
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
|
import type { BuildLog } from '@prisma/client';
|
||||||
import { prisma, ErrorHandler } from './common';
|
import { prisma, ErrorHandler } from './common';
|
||||||
|
|
||||||
export async function listLogs({ buildId, last = 0 }) {
|
export async function listLogs({
|
||||||
|
buildId,
|
||||||
|
last = 0
|
||||||
|
}: {
|
||||||
|
buildId: string;
|
||||||
|
last: number;
|
||||||
|
}): Promise<BuildLog[] | { status: number; body: { message: string; error: string } }> {
|
||||||
try {
|
try {
|
||||||
const body = await prisma.buildLog.findMany({
|
const body = await prisma.buildLog.findMany({
|
||||||
where: { buildId, time: { gt: last } },
|
where: { buildId, time: { gt: last } },
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { encrypt, decrypt } from '$lib/crypto';
|
import { encrypt, decrypt } from '$lib/crypto';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { ServiceSecret, Secret, Prisma } from '@prisma/client';
|
||||||
|
|
||||||
export async function listServiceSecrets(serviceId: string) {
|
export async function listServiceSecrets(serviceId: string): Promise<ServiceSecret[]> {
|
||||||
let secrets = await prisma.serviceSecret.findMany({
|
let secrets = await prisma.serviceSecret.findMany({
|
||||||
where: { serviceId },
|
where: { serviceId },
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
@@ -14,7 +15,7 @@ export async function listServiceSecrets(serviceId: string) {
|
|||||||
return secrets;
|
return secrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listSecrets(applicationId: string) {
|
export async function listSecrets(applicationId: string): Promise<Secret[]> {
|
||||||
let secrets = await prisma.secret.findMany({
|
let secrets = await prisma.secret.findMany({
|
||||||
where: { applicationId },
|
where: { applicationId },
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
@@ -27,20 +28,48 @@ export async function listSecrets(applicationId: string) {
|
|||||||
return secrets;
|
return secrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createServiceSecret({ id, name, value }) {
|
export async function createServiceSecret({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}): Promise<ServiceSecret> {
|
||||||
value = encrypt(value);
|
value = encrypt(value);
|
||||||
return await prisma.serviceSecret.create({
|
return await prisma.serviceSecret.create({
|
||||||
data: { name, value, service: { connect: { id } } }
|
data: { name, value, service: { connect: { id } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
export async function createSecret({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
isBuildSecret,
|
||||||
|
isPRMRSecret
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
isBuildSecret: boolean;
|
||||||
|
isPRMRSecret: boolean;
|
||||||
|
}): Promise<Secret> {
|
||||||
value = encrypt(value);
|
value = encrypt(value);
|
||||||
return await prisma.secret.create({
|
return await prisma.secret.create({
|
||||||
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateServiceSecret({ id, name, value }) {
|
export async function updateServiceSecret({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}): Promise<Prisma.BatchPayload | ServiceSecret> {
|
||||||
value = encrypt(value);
|
value = encrypt(value);
|
||||||
const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
|
const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
|
||||||
|
|
||||||
@@ -55,7 +84,19 @@ export async function updateServiceSecret({ id, name, value }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
export async function updateSecret({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
isBuildSecret,
|
||||||
|
isPRMRSecret
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
isBuildSecret: boolean;
|
||||||
|
isPRMRSecret: boolean;
|
||||||
|
}): Promise<Prisma.BatchPayload | Secret> {
|
||||||
value = encrypt(value);
|
value = encrypt(value);
|
||||||
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
||||||
|
|
||||||
@@ -71,10 +112,22 @@ export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecre
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeServiceSecret({ id, name }) {
|
export async function removeServiceSecret({
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Prisma.BatchPayload> {
|
||||||
return await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } });
|
return await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeSecret({ id, name }) {
|
export async function removeSecret({
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Prisma.BatchPayload> {
|
||||||
return await prisma.secret.deleteMany({ where: { applicationId: id, name } });
|
return await prisma.secret.deleteMany({ where: { applicationId: id, name } });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,53 @@
|
|||||||
import { asyncExecShell, getEngine } from '$lib/common';
|
|
||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
|
import type { Minio, Service } from '@prisma/client';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import { generatePassword } from '.';
|
import { generatePassword } from '.';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
|
||||||
export async function listServices(teamId) {
|
export async function listServices(teamId: string): Promise<Service[]> {
|
||||||
return await prisma.service.findMany({ where: { teams: { some: { id: teamId } } } });
|
if (teamId === '0') {
|
||||||
|
return await prisma.service.findMany({ include: { teams: true } });
|
||||||
|
} else {
|
||||||
|
return await prisma.service.findMany({
|
||||||
|
where: { teams: { some: { id: teamId } } },
|
||||||
|
include: { teams: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function newService({ name, teamId }) {
|
export async function newService({
|
||||||
|
name,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.create({ data: { name, teams: { connect: { id: teamId } } } });
|
return await prisma.service.create({ data: { name, teams: { connect: { id: teamId } } } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getService({ id, teamId }) {
|
export async function getService({ id, teamId }: { id: string; teamId: string }): Promise<Service> {
|
||||||
const body = await prisma.service.findFirst({
|
let body;
|
||||||
where: { id, teams: { some: { id: teamId } } },
|
const include = {
|
||||||
include: {
|
destinationDocker: true,
|
||||||
destinationDocker: true,
|
plausibleAnalytics: true,
|
||||||
plausibleAnalytics: true,
|
minio: true,
|
||||||
minio: true,
|
vscodeserver: true,
|
||||||
vscodeserver: true,
|
wordpress: true,
|
||||||
wordpress: true,
|
ghost: true,
|
||||||
ghost: true,
|
serviceSecret: true,
|
||||||
serviceSecret: true,
|
meiliSearch: true
|
||||||
meiliSearch: true
|
};
|
||||||
}
|
if (teamId === '0') {
|
||||||
});
|
body = await prisma.service.findFirst({
|
||||||
|
where: { id },
|
||||||
|
include
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
body = await prisma.service.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId } } },
|
||||||
|
include
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (body.plausibleAnalytics?.postgresqlPassword)
|
if (body.plausibleAnalytics?.postgresqlPassword)
|
||||||
body.plausibleAnalytics.postgresqlPassword = decrypt(
|
body.plausibleAnalytics.postgresqlPassword = decrypt(
|
||||||
@@ -59,11 +81,21 @@ export async function getService({ id, teamId }) {
|
|||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (body.wordpress?.ftpPassword) {
|
||||||
|
body.wordpress.ftpPassword = decrypt(body.wordpress.ftpPassword);
|
||||||
|
}
|
||||||
|
const settings = await prisma.setting.findFirst();
|
||||||
|
|
||||||
return { ...body };
|
return { ...body, settings };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureServiceType({ id, type }) {
|
export async function configureServiceType({
|
||||||
|
id,
|
||||||
|
type
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
}): Promise<void> {
|
||||||
if (type === 'plausibleanalytics') {
|
if (type === 'plausibleanalytics') {
|
||||||
const password = encrypt(generatePassword());
|
const password = encrypt(generatePassword());
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
@@ -145,7 +177,7 @@ export async function configureServiceType({ id, type }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'ghost') {
|
} else if (type === 'ghost') {
|
||||||
const defaultEmail = `${cuid()}@coolify.io`;
|
const defaultEmail = `${cuid()}@example.com`;
|
||||||
const defaultPassword = encrypt(generatePassword());
|
const defaultPassword = encrypt(generatePassword());
|
||||||
const mariadbUser = cuid();
|
const mariadbUser = cuid();
|
||||||
const mariadbPassword = encrypt(generatePassword());
|
const mariadbPassword = encrypt(generatePassword());
|
||||||
@@ -179,56 +211,157 @@ export async function configureServiceType({ id, type }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function setServiceVersion({ id, version }) {
|
|
||||||
|
export async function setServiceVersion({
|
||||||
|
id,
|
||||||
|
version
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
version: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({
|
return await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { version }
|
data: { version }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setServiceSettings({ id, dualCerts }) {
|
export async function setServiceSettings({
|
||||||
|
id,
|
||||||
|
dualCerts
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
dualCerts: boolean;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({
|
return await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { dualCerts }
|
data: { dualCerts }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updatePlausibleAnalyticsService({ id, fqdn, email, username, name }) {
|
export async function updatePlausibleAnalyticsService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
email,
|
||||||
|
username,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
username: string;
|
||||||
|
}): Promise<void> {
|
||||||
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 } });
|
||||||
}
|
}
|
||||||
export async function updateService({ id, fqdn, name }) {
|
|
||||||
|
export async function updateService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||||
}
|
}
|
||||||
export async function updateLanguageToolService({ id, fqdn, name }) {
|
|
||||||
|
export async function updateLanguageToolService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||||
}
|
}
|
||||||
export async function updateMeiliSearchService({ id, fqdn, name }) {
|
|
||||||
|
export async function updateMeiliSearchService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||||
}
|
}
|
||||||
export async function updateVaultWardenService({ id, fqdn, name }) {
|
|
||||||
|
export async function updateVaultWardenService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||||
}
|
}
|
||||||
export async function updateVsCodeServer({ id, fqdn, name }) {
|
|
||||||
|
export async function updateVsCodeServer({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||||
}
|
}
|
||||||
export async function updateWordpress({ id, fqdn, name, mysqlDatabase, extraConfig }) {
|
|
||||||
|
export async function updateWordpress({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name,
|
||||||
|
mysqlDatabase,
|
||||||
|
extraConfig
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
mysqlDatabase: string;
|
||||||
|
extraConfig: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({
|
return await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { fqdn, name, wordpress: { update: { mysqlDatabase, extraConfig } } }
|
data: { fqdn, name, wordpress: { update: { mysqlDatabase, extraConfig } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function updateMinioService({ id, publicPort }) {
|
|
||||||
|
export async function updateMinioService({
|
||||||
|
id,
|
||||||
|
publicPort
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
publicPort: number;
|
||||||
|
}): Promise<Minio> {
|
||||||
return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
|
return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
|
||||||
}
|
}
|
||||||
export async function updateGhostService({ id, fqdn, name, mariadbDatabase }) {
|
|
||||||
|
export async function updateGhostService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name,
|
||||||
|
mariadbDatabase
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
mariadbDatabase: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({
|
return await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { fqdn, name, ghost: { update: { mariadbDatabase } } }
|
data: { fqdn, name, ghost: { update: { mariadbDatabase } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeService({ id }) {
|
export async function removeService({ id }: { id: string }): Promise<void> {
|
||||||
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { decrypt } from '$lib/crypto';
|
import { decrypt } from '$lib/crypto';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { Setting } from '@prisma/client';
|
||||||
|
|
||||||
export async function listSettings() {
|
export async function listSettings(): Promise<Setting> {
|
||||||
let settings = await prisma.setting.findFirst({});
|
const settings = await prisma.setting.findFirst({});
|
||||||
if (settings.proxyPassword) settings.proxyPassword = decrypt(settings.proxyPassword);
|
if (settings.proxyPassword) settings.proxyPassword = decrypt(settings.proxyPassword);
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import type { Team, Permission } from '@prisma/client';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
|
||||||
export async function listTeams() {
|
export async function listTeams(): Promise<Team[]> {
|
||||||
return await prisma.team.findMany();
|
return await prisma.team.findMany();
|
||||||
}
|
}
|
||||||
export async function newTeam({ name, userId }) {
|
export async function newTeam({ name, userId }: { name: string; userId: string }): Promise<Team> {
|
||||||
return await prisma.team.create({
|
return await prisma.team.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
@@ -12,7 +13,11 @@ export async function newTeam({ name, userId }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function getMyTeams({ userId }) {
|
export async function getMyTeams({
|
||||||
|
userId
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
}): Promise<(Permission & { team: Team & { _count: { users: number } } })[]> {
|
||||||
return await prisma.permission.findMany({
|
return await prisma.permission.findMany({
|
||||||
where: { userId },
|
where: { userId },
|
||||||
include: { team: { include: { _count: { select: { users: true } } } } }
|
include: { team: { include: { _count: { select: { users: true } } } } }
|
||||||
|
|||||||
@@ -1,16 +1,30 @@
|
|||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcryptjs';
|
||||||
|
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
import { asyncExecShell, uniqueName } from '$lib/common';
|
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) {
|
import type { User } from '@prisma/client';
|
||||||
|
|
||||||
|
export async function hashPassword(password: string): Promise<string> {
|
||||||
const saltRounds = 15;
|
const saltRounds = 15;
|
||||||
return bcrypt.hash(password, saltRounds);
|
return bcrypt.hash(password, saltRounds);
|
||||||
}
|
}
|
||||||
export async function login({ email, password, isLogin }) {
|
|
||||||
|
export async function login({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
isLogin
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
isLogin: boolean;
|
||||||
|
}): Promise<{
|
||||||
|
status: number;
|
||||||
|
headers: { 'Set-Cookie': string };
|
||||||
|
body: { userId: string; teamId: string; permission: string; isAdmin: boolean };
|
||||||
|
}> {
|
||||||
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 },
|
||||||
@@ -32,26 +46,42 @@ export async function login({ email, password, isLogin }) {
|
|||||||
if (users === 0) {
|
if (users === 0) {
|
||||||
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
|
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
|
||||||
// Create default network & start Coolify Proxy
|
// Create default network & start Coolify Proxy
|
||||||
asyncExecShell(`docker network create --attachable coolify`)
|
await asyncExecShell(`docker network create --attachable coolify`);
|
||||||
.then(() => {
|
await startCoolifyProxy('/var/run/docker.sock');
|
||||||
console.log('Network created');
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log('Network already exists.');
|
|
||||||
});
|
|
||||||
|
|
||||||
startCoolifyProxy('/var/run/docker.sock')
|
|
||||||
.then(() => {
|
|
||||||
console.log('Coolify Proxy started.');
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
uid = '0';
|
uid = '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userFound) {
|
if (userFound) {
|
||||||
if (userFound.type === 'email') {
|
if (userFound.type === 'email') {
|
||||||
|
if (userFound.password === 'RESETME') {
|
||||||
|
const hashedPassword = await hashPassword(password);
|
||||||
|
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { email: userFound.email },
|
||||||
|
data: { password: 'RESETTIMEOUT' }
|
||||||
|
});
|
||||||
|
throw {
|
||||||
|
error: 'Password reset link has expired. Please request a new one.'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { email: userFound.email },
|
||||||
|
data: { password: hashedPassword }
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Set-Cookie': `teamId=${uid}; HttpOnly; Path=/; Max-Age=15778800;`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
userId: userFound.id,
|
||||||
|
teamId: userFound.id,
|
||||||
|
permission: userFound.permission,
|
||||||
|
isAdmin: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
const passwordMatch = await bcrypt.compare(password, userFound.password);
|
const passwordMatch = await bcrypt.compare(password, userFound.password);
|
||||||
if (!passwordMatch) {
|
if (!passwordMatch) {
|
||||||
throw {
|
throw {
|
||||||
@@ -124,6 +154,6 @@ export async function login({ email, password, isLogin }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUser({ userId }) {
|
export async function getUser({ userId }: { userId: string }): Promise<User> {
|
||||||
return await prisma.user.findUnique({ where: { id: userId } });
|
return await prisma.user.findUnique({ where: { id: userId } });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
export function errorNotification(message: string) {
|
|
||||||
|
export function errorNotification(message: string): void {
|
||||||
console.error(message);
|
console.error(message);
|
||||||
if (typeof message !== 'string') {
|
if (typeof message !== 'string') {
|
||||||
toast.push('Ooops, something is not okay, are you okay?');
|
toast.push('Ooops, something is not okay, are you okay?');
|
||||||
@@ -30,7 +31,7 @@ export function enhance(
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
let body = new FormData(form);
|
let body = new FormData(form);
|
||||||
let parsedData = body;
|
const parsedData = body;
|
||||||
|
|
||||||
body.forEach((data, key) => {
|
body.forEach((data, key) => {
|
||||||
if (data === '' || data === null) parsedData.delete(key);
|
if (data === '' || data === null) parsedData.delete(key);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import got from 'got';
|
import got, { type Got } from 'got';
|
||||||
|
import * as db from '$lib/database';
|
||||||
import mustache from 'mustache';
|
import mustache from 'mustache';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
|
||||||
import * as db from '$lib/database';
|
|
||||||
import { checkContainer, checkHAProxy } from '.';
|
import { checkContainer, checkHAProxy } from '.';
|
||||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||||
|
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
||||||
|
|
||||||
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
||||||
|
|
||||||
let template = `program api
|
const template = `program api
|
||||||
command /usr/bin/dataplaneapi -f /usr/local/etc/haproxy/dataplaneapi.hcl --userlist haproxy-dataplaneapi
|
command /usr/bin/dataplaneapi -f /usr/local/etc/haproxy/dataplaneapi.hcl --userlist haproxy-dataplaneapi
|
||||||
no option start-on-reload
|
no option start-on-reload
|
||||||
|
|
||||||
@@ -20,10 +20,10 @@ global
|
|||||||
defaults
|
defaults
|
||||||
mode http
|
mode http
|
||||||
log global
|
log global
|
||||||
timeout http-request 60s
|
timeout http-request 120s
|
||||||
timeout connect 10s
|
timeout connect 10s
|
||||||
timeout client 60s
|
timeout client 120s
|
||||||
timeout server 60s
|
timeout server 120s
|
||||||
|
|
||||||
userlist haproxy-dataplaneapi
|
userlist haproxy-dataplaneapi
|
||||||
user admin insecure-password "\${HAPROXY_PASSWORD}"
|
user admin insecure-password "\${HAPROXY_PASSWORD}"
|
||||||
@@ -127,7 +127,8 @@ backend {{domain}}
|
|||||||
server {{id}} {{id}}:{{port}} check fall 10
|
server {{id}} {{id}}:{{port}} check fall 10
|
||||||
{{/coolify}}
|
{{/coolify}}
|
||||||
`;
|
`;
|
||||||
export async function haproxyInstance() {
|
|
||||||
|
export async function haproxyInstance(): Promise<Got> {
|
||||||
const { proxyPassword } = await db.listSettings();
|
const { proxyPassword } = await db.listSettings();
|
||||||
return got.extend({
|
return got.extend({
|
||||||
prefixUrl: url,
|
prefixUrl: url,
|
||||||
@@ -136,31 +137,97 @@ export async function haproxyInstance() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureHAProxy() {
|
export async function configureHAProxy(): Promise<void> {
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
|
|
||||||
try {
|
const data = {
|
||||||
const data = {
|
applications: [],
|
||||||
applications: [],
|
services: [],
|
||||||
services: [],
|
coolify: []
|
||||||
coolify: []
|
};
|
||||||
};
|
const applications = await db.prisma.application.findMany({
|
||||||
const applications = await db.prisma.application.findMany({
|
include: { destinationDocker: true, settings: true }
|
||||||
include: { destinationDocker: true, settings: true }
|
});
|
||||||
});
|
for (const application of applications) {
|
||||||
for (const application of applications) {
|
const {
|
||||||
const {
|
fqdn,
|
||||||
fqdn,
|
id,
|
||||||
id,
|
port,
|
||||||
port,
|
destinationDocker,
|
||||||
destinationDocker,
|
destinationDockerId,
|
||||||
destinationDockerId,
|
settings: { previews },
|
||||||
settings: { previews },
|
updatedAt
|
||||||
updatedAt
|
} = application;
|
||||||
} = application;
|
if (destinationDockerId) {
|
||||||
if (destinationDockerId) {
|
const { engine, network } = destinationDocker;
|
||||||
const { engine, network } = destinationDocker;
|
const isRunning = await checkContainer(engine, id);
|
||||||
|
if (fqdn) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
const isWWW = fqdn.includes('www.');
|
||||||
|
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||||
|
if (isRunning) {
|
||||||
|
data.applications.push({
|
||||||
|
id,
|
||||||
|
port: port || 3000,
|
||||||
|
domain,
|
||||||
|
isRunning,
|
||||||
|
isHttps,
|
||||||
|
redirectValue,
|
||||||
|
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
|
||||||
|
updatedAt: updatedAt.getTime()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (previews) {
|
||||||
|
const host = getEngine(engine);
|
||||||
|
const { stdout } = await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
||||||
|
);
|
||||||
|
const containers = stdout
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter((a) => a)
|
||||||
|
.map((c) => c.replace(/"/g, ''));
|
||||||
|
if (containers.length > 0) {
|
||||||
|
for (const container of containers) {
|
||||||
|
const previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||||
|
data.applications.push({
|
||||||
|
id: container,
|
||||||
|
port: port || 3000,
|
||||||
|
domain: previewDomain,
|
||||||
|
isRunning,
|
||||||
|
isHttps,
|
||||||
|
redirectValue,
|
||||||
|
redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain,
|
||||||
|
updatedAt: updatedAt.getTime()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const services = await db.prisma.service.findMany({
|
||||||
|
include: {
|
||||||
|
destinationDocker: true,
|
||||||
|
minio: true,
|
||||||
|
plausibleAnalytics: true,
|
||||||
|
vscodeserver: true,
|
||||||
|
wordpress: true,
|
||||||
|
ghost: true,
|
||||||
|
meiliSearch: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const service of services) {
|
||||||
|
const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
const { engine } = destinationDocker;
|
||||||
|
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||||
|
if (found) {
|
||||||
|
const port = found.ports.main;
|
||||||
|
const publicPort = service[type]?.publicPort;
|
||||||
const isRunning = await checkContainer(engine, id);
|
const isRunning = await checkContainer(engine, id);
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
@@ -168,9 +235,10 @@ export async function configureHAProxy() {
|
|||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
data.applications.push({
|
data.services.push({
|
||||||
id,
|
id,
|
||||||
port: port || 3000,
|
port,
|
||||||
|
publicPort,
|
||||||
domain,
|
domain,
|
||||||
isRunning,
|
isRunning,
|
||||||
isHttps,
|
isHttps,
|
||||||
@@ -179,108 +247,38 @@ export async function configureHAProxy() {
|
|||||||
updatedAt: updatedAt.getTime()
|
updatedAt: updatedAt.getTime()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (previews) {
|
|
||||||
const host = getEngine(engine);
|
|
||||||
const { stdout } = await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
|
||||||
);
|
|
||||||
const containers = stdout
|
|
||||||
.trim()
|
|
||||||
.split('\n')
|
|
||||||
.filter((a) => a)
|
|
||||||
.map((c) => c.replace(/"/g, ''));
|
|
||||||
if (containers.length > 0) {
|
|
||||||
for (const container of containers) {
|
|
||||||
let previewDomain = `${container.split('-')[1]}.${domain}`;
|
|
||||||
data.applications.push({
|
|
||||||
id: container,
|
|
||||||
port: port || 3000,
|
|
||||||
domain: previewDomain,
|
|
||||||
isRunning,
|
|
||||||
isHttps,
|
|
||||||
redirectValue,
|
|
||||||
redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain,
|
|
||||||
updatedAt: updatedAt.getTime()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const services = await db.prisma.service.findMany({
|
}
|
||||||
include: {
|
const { fqdn } = await db.prisma.setting.findFirst();
|
||||||
destinationDocker: true,
|
if (fqdn) {
|
||||||
minio: true,
|
const domain = getDomain(fqdn);
|
||||||
plausibleAnalytics: true,
|
const isHttps = fqdn.startsWith('https://');
|
||||||
vscodeserver: true,
|
const isWWW = fqdn.includes('www.');
|
||||||
wordpress: true,
|
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||||
ghost: true
|
data.coolify.push({
|
||||||
|
id: dev ? 'host.docker.internal' : 'coolify',
|
||||||
|
port: 3000,
|
||||||
|
domain,
|
||||||
|
isHttps,
|
||||||
|
redirectValue,
|
||||||
|
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const output = mustache.render(template, data);
|
||||||
|
const newHash = crypto.createHash('md5').update(output).digest('hex');
|
||||||
|
const { proxyHash, id } = await db.listSettings();
|
||||||
|
if (proxyHash !== newHash) {
|
||||||
|
await db.prisma.setting.update({ where: { id }, data: { proxyHash: newHash } });
|
||||||
|
await haproxy.post(`v2/services/haproxy/configuration/raw`, {
|
||||||
|
searchParams: {
|
||||||
|
skip_version: true
|
||||||
|
},
|
||||||
|
body: output,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const service of services) {
|
|
||||||
const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service;
|
|
||||||
if (destinationDockerId) {
|
|
||||||
const { engine } = destinationDocker;
|
|
||||||
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
|
|
||||||
if (found) {
|
|
||||||
const port = found.ports.main;
|
|
||||||
const publicPort = service[type]?.publicPort;
|
|
||||||
const isRunning = await checkContainer(engine, id);
|
|
||||||
if (fqdn) {
|
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
const isHttps = fqdn.startsWith('https://');
|
|
||||||
const isWWW = fqdn.includes('www.');
|
|
||||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
|
||||||
if (isRunning) {
|
|
||||||
data.services.push({
|
|
||||||
id,
|
|
||||||
port,
|
|
||||||
publicPort,
|
|
||||||
domain,
|
|
||||||
isRunning,
|
|
||||||
isHttps,
|
|
||||||
redirectValue,
|
|
||||||
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
|
|
||||||
updatedAt: updatedAt.getTime()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const { fqdn } = await db.prisma.setting.findFirst();
|
|
||||||
if (fqdn) {
|
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
const isHttps = fqdn.startsWith('https://');
|
|
||||||
const isWWW = fqdn.includes('www.');
|
|
||||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
|
||||||
data.coolify.push({
|
|
||||||
id: dev ? 'host.docker.internal' : 'coolify',
|
|
||||||
port: 3000,
|
|
||||||
domain,
|
|
||||||
isHttps,
|
|
||||||
redirectValue,
|
|
||||||
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const output = mustache.render(template, data);
|
|
||||||
const newHash = crypto.createHash('md5').update(output).digest('hex');
|
|
||||||
const { proxyHash, id } = await db.listSettings();
|
|
||||||
if (proxyHash !== newHash) {
|
|
||||||
await db.prisma.setting.update({ where: { id }, data: { proxyHash: newHash } });
|
|
||||||
await haproxy.post(`v2/services/haproxy/configuration/raw`, {
|
|
||||||
searchParams: {
|
|
||||||
skip_version: true
|
|
||||||
},
|
|
||||||
body: output,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import { asyncExecShell, getEngine } from '$lib/common';
|
import { asyncExecShell, getEngine } from '$lib/common';
|
||||||
import got from 'got';
|
import got, { type Got, type Response } from 'got';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
|
import type { DestinationDocker } from '@prisma/client';
|
||||||
|
|
||||||
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ export const defaultProxyImage = `coolify-haproxy-alpine:latest`;
|
|||||||
export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`;
|
export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`;
|
||||||
export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`;
|
export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`;
|
||||||
|
|
||||||
export async function haproxyInstance() {
|
export async function haproxyInstance(): Promise<Got> {
|
||||||
const { proxyPassword } = await db.listSettings();
|
const { proxyPassword } = await db.listSettings();
|
||||||
return got.extend({
|
return got.extend({
|
||||||
prefixUrl: url,
|
prefixUrl: url,
|
||||||
@@ -17,6 +18,7 @@ export async function haproxyInstance() {
|
|||||||
password: proxyPassword
|
password: proxyPassword
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRawConfiguration(): Promise<RawHaproxyConfiguration> {
|
export async function getRawConfiguration(): Promise<RawHaproxyConfiguration> {
|
||||||
return await (await haproxyInstance()).get(`v2/services/haproxy/configuration/raw`).json();
|
return await (await haproxyInstance()).get(`v2/services/haproxy/configuration/raw`).json();
|
||||||
}
|
}
|
||||||
@@ -43,11 +45,12 @@ export async function getNextTransactionId(): Promise<string> {
|
|||||||
return newTransaction.id;
|
return newTransaction.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function completeTransaction(transactionId) {
|
export async function completeTransaction(transactionId: string): Promise<Response<string>> {
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
|
return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
|
||||||
}
|
}
|
||||||
export async function deleteProxy({ id }) {
|
|
||||||
|
export async function deleteProxy({ id }: { id: string }): Promise<void> {
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
|
|
||||||
@@ -77,11 +80,12 @@ export async function deleteProxy({ id }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function reloadHaproxy(engine) {
|
export async function reloadHaproxy(engine: string): Promise<{ stdout: string; stderr: string }> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
|
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
|
||||||
}
|
}
|
||||||
export async function checkHAProxy(haproxy?: any) {
|
|
||||||
|
export async function checkHAProxy(haproxy?: Got): Promise<void> {
|
||||||
if (!haproxy) haproxy = await haproxyInstance();
|
if (!haproxy) haproxy = await haproxyInstance();
|
||||||
try {
|
try {
|
||||||
await haproxy.get('v2/info');
|
await haproxy.get('v2/info');
|
||||||
@@ -93,7 +97,10 @@ export async function checkHAProxy(haproxy?: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stopTcpHttpProxy(destinationDocker, publicPort) {
|
export async function stopTcpHttpProxy(
|
||||||
|
destinationDocker: DestinationDocker,
|
||||||
|
publicPort: number
|
||||||
|
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||||
const { engine } = destinationDocker;
|
const { engine } = destinationDocker;
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
const containerName = `haproxy-for-${publicPort}`;
|
const containerName = `haproxy-for-${publicPort}`;
|
||||||
@@ -108,7 +115,13 @@ export async function stopTcpHttpProxy(destinationDocker, publicPort) {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function startTcpProxy(destinationDocker, id, publicPort, privatePort) {
|
export async function startTcpProxy(
|
||||||
|
destinationDocker: DestinationDocker,
|
||||||
|
id: string,
|
||||||
|
publicPort: number,
|
||||||
|
privatePort: number,
|
||||||
|
volume?: string
|
||||||
|
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||||
const { network, engine } = destinationDocker;
|
const { network, engine } = destinationDocker;
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
|
|
||||||
@@ -123,14 +136,22 @@ export async function startTcpProxy(destinationDocker, id, publicPort, privatePo
|
|||||||
);
|
);
|
||||||
const ip = JSON.parse(Config)[0].Gateway;
|
const ip = JSON.parse(Config)[0].Gateway;
|
||||||
return await asyncExecShell(
|
return await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageTcp}`
|
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} ${
|
||||||
|
volume ? `-v ${volume}` : ''
|
||||||
|
} -d coollabsio/${defaultProxyImageTcp}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function startHttpProxy(destinationDocker, id, publicPort, privatePort) {
|
|
||||||
|
export async function startHttpProxy(
|
||||||
|
destinationDocker: DestinationDocker,
|
||||||
|
id: string,
|
||||||
|
publicPort: number,
|
||||||
|
privatePort: number
|
||||||
|
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||||
const { network, engine } = destinationDocker;
|
const { network, engine } = destinationDocker;
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
|
|
||||||
@@ -152,7 +173,8 @@ export async function startHttpProxy(destinationDocker, id, publicPort, privateP
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function startCoolifyProxy(engine) {
|
|
||||||
|
export async function startCoolifyProxy(engine: string): Promise<void> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
const found = await checkContainer(engine, 'coolify-haproxy');
|
const found = await checkContainer(engine, 'coolify-haproxy');
|
||||||
const { proxyPassword, proxyUser, id } = await db.listSettings();
|
const { proxyPassword, proxyUser, id } = await db.listSettings();
|
||||||
@@ -168,7 +190,8 @@ export async function startCoolifyProxy(engine) {
|
|||||||
}
|
}
|
||||||
await configureNetworkCoolifyProxy(engine);
|
await configureNetworkCoolifyProxy(engine);
|
||||||
}
|
}
|
||||||
export async function checkContainer(engine, container) {
|
|
||||||
|
export async function checkContainer(engine: string, container: string): Promise<boolean> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
let containerFound = false;
|
let containerFound = false;
|
||||||
|
|
||||||
@@ -178,7 +201,7 @@ export async function checkContainer(engine, container) {
|
|||||||
);
|
);
|
||||||
const parsedStdout = JSON.parse(stdout);
|
const parsedStdout = JSON.parse(stdout);
|
||||||
const status = parsedStdout.Status;
|
const status = parsedStdout.Status;
|
||||||
const isRunning = status === 'running' ? true : false;
|
const isRunning = status === 'running';
|
||||||
if (status === 'exited' || status === 'created') {
|
if (status === 'exited' || status === 'created') {
|
||||||
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
|
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
|
||||||
}
|
}
|
||||||
@@ -191,7 +214,9 @@ export async function checkContainer(engine, container) {
|
|||||||
return containerFound;
|
return containerFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stopCoolifyProxy(engine) {
|
export async function stopCoolifyProxy(
|
||||||
|
engine: string
|
||||||
|
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
const found = await checkContainer(engine, 'coolify-haproxy');
|
const found = await checkContainer(engine, 'coolify-haproxy');
|
||||||
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: false });
|
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: false });
|
||||||
@@ -208,16 +233,18 @@ export async function stopCoolifyProxy(engine) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureNetworkCoolifyProxy(engine) {
|
export async function configureNetworkCoolifyProxy(engine: string): Promise<void> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
const destinations = await db.prisma.destinationDocker.findMany({ where: { engine } });
|
const destinations = await db.prisma.destinationDocker.findMany({ where: { engine } });
|
||||||
destinations.forEach(async (destination) => {
|
const { stdout: networks } = await asyncExecShell(
|
||||||
try {
|
`DOCKER_HOST="${host}" docker ps -a --filter name=coolify-haproxy --format '{{json .Networks}}'`
|
||||||
|
);
|
||||||
|
const configuredNetworks = networks.replace(/"/g, '').replace('\n', '').split(',');
|
||||||
|
for (const destination of destinations) {
|
||||||
|
if (!configuredNetworks.includes(destination.network)) {
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-haproxy`
|
`DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-haproxy`
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
// TODO: handle error
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import { asyncExecShell, saveBuildLog } from '$lib/common';
|
|||||||
import got from 'got';
|
import got from 'got';
|
||||||
import jsonwebtoken from 'jsonwebtoken';
|
import jsonwebtoken from 'jsonwebtoken';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler } from '$lib/database';
|
|
||||||
|
|
||||||
export default async function ({
|
export default async function ({
|
||||||
applicationId,
|
applicationId,
|
||||||
debug,
|
|
||||||
workdir,
|
workdir,
|
||||||
githubAppId,
|
githubAppId,
|
||||||
repository,
|
repository,
|
||||||
@@ -14,41 +12,45 @@ export default async function ({
|
|||||||
htmlUrl,
|
htmlUrl,
|
||||||
branch,
|
branch,
|
||||||
buildId
|
buildId
|
||||||
}): Promise<any> {
|
}: {
|
||||||
try {
|
applicationId: string;
|
||||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
workdir: string;
|
||||||
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
githubAppId: string;
|
||||||
const { privateKey, appId, installationId } = await db.getUniqueGithubApp({ githubAppId });
|
repository: string;
|
||||||
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
apiUrl: string;
|
||||||
|
htmlUrl: string;
|
||||||
|
branch: string;
|
||||||
|
buildId: string;
|
||||||
|
}): Promise<string> {
|
||||||
|
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
||||||
|
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
||||||
|
const { privateKey, appId, installationId } = await db.getUniqueGithubApp({ githubAppId });
|
||||||
|
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
iat: Math.round(new Date().getTime() / 1000),
|
iat: Math.round(new Date().getTime() / 1000),
|
||||||
exp: Math.round(new Date().getTime() / 1000 + 60),
|
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||||
iss: appId
|
iss: appId
|
||||||
};
|
};
|
||||||
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
||||||
algorithm: 'RS256'
|
algorithm: 'RS256'
|
||||||
});
|
});
|
||||||
const { token } = await got
|
const { token } = await got
|
||||||
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
|
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${jwtToken}`,
|
Authorization: `Bearer ${jwtToken}`,
|
||||||
Accept: 'application/vnd.github.machine-man-preview+json'
|
Accept: 'application/vnd.github.machine-man-preview+json'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
await saveBuildLog({
|
await saveBuildLog({
|
||||||
line: `Cloning ${repository}:${branch} branch.`,
|
line: `Cloning ${repository}:${branch} branch.`,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId
|
applicationId
|
||||||
});
|
});
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && cd ..`
|
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
);
|
);
|
||||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||||
return commit.replace('\n', '');
|
return commit.replace('\n', '');
|
||||||
} catch (error) {
|
|
||||||
console.log({ error });
|
|
||||||
return ErrorHandler(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,17 @@ export default async function ({
|
|||||||
branch,
|
branch,
|
||||||
buildId,
|
buildId,
|
||||||
privateSshKey
|
privateSshKey
|
||||||
}): Promise<any> {
|
}: {
|
||||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
applicationId: string;
|
||||||
|
workdir: string;
|
||||||
|
repository: string;
|
||||||
|
htmlUrl: string;
|
||||||
|
branch: string;
|
||||||
|
buildId: string;
|
||||||
|
repodir: string;
|
||||||
|
privateSshKey: string;
|
||||||
|
}): Promise<string> {
|
||||||
|
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
||||||
await saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
|
await saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
|
||||||
await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`);
|
await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`);
|
||||||
await asyncExecShell(`chmod 600 ${repodir}/id.rsa`);
|
await asyncExecShell(`chmod 600 ${repodir}/id.rsa`);
|
||||||
@@ -22,7 +31,7 @@ export default async function ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && cd ..`
|
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
);
|
);
|
||||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||||
return commit.replace('\n', '');
|
return commit.replace('\n', '');
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ import { checkContainer, reloadHaproxy } 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 cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
|
import fs from 'fs/promises';
|
||||||
import getPort, { portNumbers } from 'get-port';
|
import getPort, { portNumbers } from 'get-port';
|
||||||
|
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
||||||
|
import { promises as dns } from 'dns';
|
||||||
|
|
||||||
export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
export async function letsEncrypt(domain: string, id?: string, isCoolify = false): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const data = await db.prisma.setting.findFirst();
|
const data = await db.prisma.setting.findFirst();
|
||||||
const { minPort, maxPort } = data;
|
const { minPort, maxPort } = data;
|
||||||
@@ -96,7 +99,7 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateSSLCerts() {
|
export async function generateSSLCerts(): Promise<void> {
|
||||||
const ssls = [];
|
const ssls = [];
|
||||||
const applications = await db.prisma.application.findMany({
|
const applications = await db.prisma.application.findMany({
|
||||||
include: { destinationDocker: true, settings: true },
|
include: { destinationDocker: true, settings: true },
|
||||||
@@ -129,7 +132,7 @@ export async function generateSSLCerts() {
|
|||||||
.map((c) => c.replace(/"/g, ''));
|
.map((c) => c.replace(/"/g, ''));
|
||||||
if (containers.length > 0) {
|
if (containers.length > 0) {
|
||||||
for (const container of containers) {
|
for (const container of containers) {
|
||||||
let previewDomain = `${container.split('-')[1]}.${domain}`;
|
const previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||||
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
|
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +149,8 @@ export async function generateSSLCerts() {
|
|||||||
plausibleAnalytics: true,
|
plausibleAnalytics: true,
|
||||||
vscodeserver: true,
|
vscodeserver: true,
|
||||||
wordpress: true,
|
wordpress: true,
|
||||||
ghost: true
|
ghost: true,
|
||||||
|
meiliSearch: true
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
});
|
});
|
||||||
@@ -160,7 +164,7 @@ export async function generateSSLCerts() {
|
|||||||
type,
|
type,
|
||||||
destinationDocker: { engine }
|
destinationDocker: { engine }
|
||||||
} = service;
|
} = service;
|
||||||
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
|
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
@@ -181,12 +185,89 @@ export async function generateSSLCerts() {
|
|||||||
if (isHttps) ssls.push({ domain, id: 'coolify', isCoolify: true });
|
if (isHttps) ssls.push({ domain, id: 'coolify', isCoolify: true });
|
||||||
}
|
}
|
||||||
if (ssls.length > 0) {
|
if (ssls.length > 0) {
|
||||||
|
const sslDir = dev ? '/tmp/ssl' : '/app/ssl';
|
||||||
|
if (dev) {
|
||||||
|
try {
|
||||||
|
await asyncExecShell(`mkdir -p ${sslDir}`);
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const files = await fs.readdir(sslDir);
|
||||||
|
let certificates = [];
|
||||||
|
if (files.length > 0) {
|
||||||
|
for (const file of files) {
|
||||||
|
file.endsWith('.pem') && certificates.push(file.replace(/\.pem$/, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const resolver = new dns.Resolver({ timeout: 2000 });
|
||||||
|
resolver.setServers(['8.8.8.8', '1.1.1.1']);
|
||||||
|
let ipv4, ipv6;
|
||||||
|
try {
|
||||||
|
ipv4 = await (await asyncExecShell(`curl -4s https://ifconfig.io`)).stdout;
|
||||||
|
} catch (error) {}
|
||||||
|
try {
|
||||||
|
ipv6 = await (await asyncExecShell(`curl -6s https://ifconfig.io`)).stdout;
|
||||||
|
} catch (error) {}
|
||||||
for (const ssl of ssls) {
|
for (const ssl of ssls) {
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
console.log('Checking SSL for', ssl.domain);
|
if (
|
||||||
await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
certificates.includes(ssl.domain) ||
|
||||||
|
certificates.includes(ssl.domain.replace('www.', ''))
|
||||||
|
) {
|
||||||
|
console.log(`Certificate for ${ssl.domain} already exists`);
|
||||||
|
} else {
|
||||||
|
// Checking DNS entry before generating certificate
|
||||||
|
if (ipv4 || ipv6) {
|
||||||
|
let domains4 = [];
|
||||||
|
let domains6 = [];
|
||||||
|
try {
|
||||||
|
domains4 = await resolver.resolve4(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
try {
|
||||||
|
domains6 = await resolver.resolve6(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
if (domains4.length > 0 || domains6.length > 0) {
|
||||||
|
if (
|
||||||
|
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
||||||
|
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
||||||
|
) {
|
||||||
|
console.log('Generating SSL for', ssl.domain, '.');
|
||||||
|
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Checking SSL for', ssl.domain);
|
if (
|
||||||
|
certificates.includes(ssl.domain) ||
|
||||||
|
certificates.includes(ssl.domain.replace('www.', ''))
|
||||||
|
) {
|
||||||
|
console.log(`Certificate for ${ssl.domain} already exists`);
|
||||||
|
} else {
|
||||||
|
// Checking DNS entry before generating certificate
|
||||||
|
if (ipv4 || ipv6) {
|
||||||
|
let domains4 = [];
|
||||||
|
let domains6 = [];
|
||||||
|
try {
|
||||||
|
domains4 = await resolver.resolve4(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
try {
|
||||||
|
domains6 = await resolver.resolve6(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
if (domains4.length > 0 || domains6.length > 0) {
|
||||||
|
if (
|
||||||
|
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
||||||
|
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
||||||
|
) {
|
||||||
|
console.log('Generating SSL for', ssl.domain, '.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,30 +20,22 @@ import {
|
|||||||
setDefaultConfiguration
|
setDefaultConfiguration
|
||||||
} from '$lib/buildPacks/common';
|
} from '$lib/buildPacks/common';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
|
import type { Job } from 'bullmq';
|
||||||
|
import type { BuilderJob } from '$lib/types/builderJob';
|
||||||
|
|
||||||
export default async function (job) {
|
import type { ComposeFile } from '$lib/types/composeFile';
|
||||||
/*
|
|
||||||
Edge cases:
|
export default async function (job: Job<BuilderJob, void, string>): Promise<void> {
|
||||||
1 - Change build pack and redeploy, what should happen?
|
const {
|
||||||
*/
|
|
||||||
let {
|
|
||||||
id: applicationId,
|
id: applicationId,
|
||||||
repository,
|
repository,
|
||||||
branch,
|
|
||||||
buildPack,
|
|
||||||
name,
|
name,
|
||||||
destinationDocker,
|
destinationDocker,
|
||||||
destinationDockerId,
|
destinationDockerId,
|
||||||
gitSource,
|
gitSource,
|
||||||
build_id: buildId,
|
build_id: buildId,
|
||||||
configHash,
|
configHash,
|
||||||
port,
|
|
||||||
installCommand,
|
|
||||||
buildCommand,
|
|
||||||
startCommand,
|
|
||||||
fqdn,
|
fqdn,
|
||||||
baseDirectory,
|
|
||||||
publishDirectory,
|
|
||||||
projectId,
|
projectId,
|
||||||
secrets,
|
secrets,
|
||||||
phpModules,
|
phpModules,
|
||||||
@@ -56,12 +48,22 @@ export default async function (job) {
|
|||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable
|
pythonVariable
|
||||||
} = job.data;
|
} = job.data;
|
||||||
|
let {
|
||||||
|
branch,
|
||||||
|
buildPack,
|
||||||
|
port,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
publishDirectory
|
||||||
|
} = job.data;
|
||||||
const { debug } = settings;
|
const { debug } = settings;
|
||||||
|
|
||||||
await asyncSleep(500);
|
await asyncSleep(500);
|
||||||
await db.prisma.build.updateMany({
|
await db.prisma.build.updateMany({
|
||||||
where: {
|
where: {
|
||||||
status: 'queued',
|
status: { in: ['queued', 'running'] },
|
||||||
id: { not: buildId },
|
id: { not: buildId },
|
||||||
applicationId,
|
applicationId,
|
||||||
createdAt: { lt: new Date(new Date().getTime() - 60 * 60 * 1000) }
|
createdAt: { lt: new Date(new Date().getTime() - 60 * 60 * 1000) }
|
||||||
@@ -70,7 +72,7 @@ export default async function (job) {
|
|||||||
});
|
});
|
||||||
let imageId = applicationId;
|
let imageId = applicationId;
|
||||||
let domain = getDomain(fqdn);
|
let domain = getDomain(fqdn);
|
||||||
let volumes =
|
const volumes =
|
||||||
persistentStorage?.map((storage) => {
|
persistentStorage?.map((storage) => {
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
|
||||||
buildPack !== 'docker' ? '/app' : ''
|
buildPack !== 'docker' ? '/app' : ''
|
||||||
@@ -106,7 +108,7 @@ export default async function (job) {
|
|||||||
publishDirectory = configuration.publishDirectory;
|
publishDirectory = configuration.publishDirectory;
|
||||||
baseDirectory = configuration.baseDirectory;
|
baseDirectory = configuration.baseDirectory;
|
||||||
|
|
||||||
let commit = await importers[gitSource.type]({
|
const commit = await importers[gitSource.type]({
|
||||||
applicationId,
|
applicationId,
|
||||||
debug,
|
debug,
|
||||||
workdir,
|
workdir,
|
||||||
@@ -213,9 +215,7 @@ export default async function (job) {
|
|||||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||||
throw new Error(`Build pack ${buildPack} not found.`);
|
throw new Error(`Build pack ${buildPack} not found.`);
|
||||||
}
|
}
|
||||||
deployNeeded = true;
|
|
||||||
} else {
|
} else {
|
||||||
deployNeeded = false;
|
|
||||||
await saveBuildLog({ line: 'Nothing changed.', buildId, applicationId });
|
await saveBuildLog({ line: 'Nothing changed.', buildId, applicationId });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ export default async function (job) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const compose = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
[imageId]: {
|
[imageId]: {
|
||||||
@@ -283,9 +283,17 @@ export default async function (job) {
|
|||||||
volumes,
|
volumes,
|
||||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||||
networks: [docker.network],
|
networks: [docker.network],
|
||||||
labels: labels,
|
labels,
|
||||||
depends_on: [],
|
depends_on: [],
|
||||||
restart: 'always'
|
restart: 'always',
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -295,7 +303,7 @@ export default async function (job) {
|
|||||||
},
|
},
|
||||||
volumes: Object.assign({}, ...composeVolumes)
|
volumes: Object.assign({}, ...composeVolumes)
|
||||||
};
|
};
|
||||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(compose));
|
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
|
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { dev } from '$app/env';
|
|
||||||
import { asyncExecShell, getEngine, version } from '$lib/common';
|
import { asyncExecShell, getEngine, version } from '$lib/common';
|
||||||
import { prisma } from '$lib/database';
|
import { prisma } from '$lib/database';
|
||||||
import { defaultProxyImageHttp, defaultProxyImageTcp } from '$lib/haproxy';
|
export default async function (): Promise<void> {
|
||||||
export default async function () {
|
|
||||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||||
for (const destinationDocker of destinationDockers) {
|
for (const destinationDocker of destinationDockers) {
|
||||||
const host = getEngine(destinationDocker.engine);
|
const host = getEngine(destinationDocker.engine);
|
||||||
@@ -16,56 +14,23 @@ export default async function () {
|
|||||||
await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
//console.log(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
//console.log(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
//console.log(error);
|
||||||
|
}
|
||||||
|
// Cleanup old images older than a day
|
||||||
|
try {
|
||||||
|
await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=24h" -a -f`);
|
||||||
|
} catch (error) {
|
||||||
|
//console.log(error);
|
||||||
}
|
}
|
||||||
// Tagging images with labels
|
|
||||||
// try {
|
|
||||||
// const images = [
|
|
||||||
// `coollabsio/${defaultProxyImageTcp}`,
|
|
||||||
// `coollabsio/${defaultProxyImageHttp}`,
|
|
||||||
// 'certbot/certbot:latest',
|
|
||||||
// 'node:16.14.0-alpine',
|
|
||||||
// 'alpine:latest',
|
|
||||||
// 'nginx:stable-alpine',
|
|
||||||
// 'node:lts',
|
|
||||||
// 'php:apache',
|
|
||||||
// 'rust:latest'
|
|
||||||
// ];
|
|
||||||
// for (const image of images) {
|
|
||||||
// try {
|
|
||||||
// await asyncExecShell(`DOCKER_HOST=${host} docker image inspect ${image}`);
|
|
||||||
// } catch (error) {
|
|
||||||
// await asyncExecShell(
|
|
||||||
// `DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.image="true" -t "${image}" -`
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } catch (error) {}
|
|
||||||
// if (!dev) {
|
|
||||||
// // Cleanup images that are not managed by coolify
|
|
||||||
// try {
|
|
||||||
// await asyncExecShell(
|
|
||||||
// `DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.image=true' -a -f`
|
|
||||||
// );
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error);
|
|
||||||
// }
|
|
||||||
// // Cleanup old images >3 days
|
|
||||||
// try {
|
|
||||||
// await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as Bullmq from 'bullmq';
|
import * as Bullmq from 'bullmq';
|
||||||
import { default as ProdBullmq, Job, QueueEvents, QueueScheduler } from 'bullmq';
|
import { default as ProdBullmq, QueueScheduler } from 'bullmq';
|
||||||
import cuid from 'cuid';
|
|
||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import { prisma } from '$lib/database';
|
import { prisma } from '$lib/database';
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ const connectionOptions = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const cron = async () => {
|
const cron = async (): Promise<void> => {
|
||||||
new QueueScheduler('proxy', connectionOptions);
|
new QueueScheduler('proxy', connectionOptions);
|
||||||
new QueueScheduler('cleanup', connectionOptions);
|
new QueueScheduler('cleanup', connectionOptions);
|
||||||
new QueueScheduler('ssl', connectionOptions);
|
new QueueScheduler('ssl', connectionOptions);
|
||||||
@@ -89,18 +88,6 @@ const cron = async () => {
|
|||||||
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
|
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
|
||||||
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
|
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
|
||||||
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
||||||
|
|
||||||
const events = {
|
|
||||||
proxy: new QueueEvents('proxy', { ...connectionOptions }),
|
|
||||||
ssl: new QueueEvents('ssl', { ...connectionOptions })
|
|
||||||
};
|
|
||||||
|
|
||||||
events.proxy.on('completed', (data) => {
|
|
||||||
// console.log(data)
|
|
||||||
});
|
|
||||||
events.ssl.on('completed', (data) => {
|
|
||||||
// console.log(data)
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
cron().catch((error) => {
|
cron().catch((error) => {
|
||||||
console.log('cron failed to start');
|
console.log('cron failed to start');
|
||||||
@@ -118,10 +105,14 @@ buildWorker.on('completed', async (job: Bullmq.Job) => {
|
|||||||
try {
|
try {
|
||||||
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } });
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setTimeout(async () => {
|
||||||
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } });
|
||||||
|
}, 1234);
|
||||||
console.log(error);
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
|
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
|
||||||
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
||||||
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
@@ -130,10 +121,14 @@ buildWorker.on('failed', async (job: Bullmq.Job, 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) {
|
||||||
|
setTimeout(async () => {
|
||||||
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } });
|
||||||
|
}, 1234);
|
||||||
console.log(error);
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
const workdir = `/tmp/build-sources/${job.data.repository}`;
|
const workdir = `/tmp/build-sources/${job.data.repository}`;
|
||||||
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
||||||
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } });
|
||||||
}
|
}
|
||||||
await saveBuildLog({
|
await saveBuildLog({
|
||||||
line: 'Failed to deploy!',
|
line: 'Failed to deploy!',
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { prisma } from '$lib/database';
|
import { prisma } from '$lib/database';
|
||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
|
import type { Job } from 'bullmq';
|
||||||
|
|
||||||
export default async function (job) {
|
export default async function (job: Job): Promise<void> {
|
||||||
const { line, applicationId, buildId } = job.data;
|
const { line, applicationId, buildId } = job.data;
|
||||||
if (dev) console.debug(`[${applicationId}] ${line}`);
|
if (dev) console.debug(`[${applicationId}] ${line}`);
|
||||||
await prisma.buildLog.create({ data: { line, buildId, time: Number(job.id), applicationId } });
|
await prisma.buildLog.create({ data: { line, buildId, time: Number(job.id), applicationId } });
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { ErrorHandler } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import { configureHAProxy } from '$lib/haproxy/configuration';
|
import { configureHAProxy } from '$lib/haproxy/configuration';
|
||||||
|
|
||||||
export default async function () {
|
export default async function (): Promise<void | {
|
||||||
|
status: number;
|
||||||
|
body: { message: string; error: string };
|
||||||
|
}> {
|
||||||
try {
|
try {
|
||||||
return await configureHAProxy();
|
return await configureHAProxy();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { generateSSLCerts } from '$lib/letsencrypt';
|
import { generateSSLCerts } from '$lib/letsencrypt';
|
||||||
|
|
||||||
export default async function () {
|
export default async function (): Promise<void> {
|
||||||
try {
|
try {
|
||||||
return await generateSSLCerts();
|
return await generateSSLCerts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import { asyncExecShell } from '$lib/common';
|
import { asyncExecShell } from '$lib/common';
|
||||||
import { reloadHaproxy } from '$lib/haproxy';
|
import { reloadHaproxy } from '$lib/haproxy';
|
||||||
|
|
||||||
export default async function () {
|
export default async function (): Promise<void> {
|
||||||
try {
|
await asyncExecShell(
|
||||||
await asyncExecShell(
|
`docker run --rm --name certbot-renewal -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs renew`
|
||||||
`docker run --rm --name certbot-renewal -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs renew`
|
);
|
||||||
);
|
await reloadHaproxy('unix:///var/run/docker.sock');
|
||||||
await reloadHaproxy('unix:///var/run/docker.sock');
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable, type Writable } from 'svelte/store';
|
||||||
|
|
||||||
export const gitTokens = writable({
|
export const gitTokens: Writable<{ githubToken: string | null; gitlabToken: string | null }> =
|
||||||
githubToken: null,
|
writable({
|
||||||
gitlabToken: null
|
githubToken: null,
|
||||||
});
|
gitlabToken: null
|
||||||
|
});
|
||||||
|
|||||||
51
src/lib/types/builderJob.ts
Normal file
51
src/lib/types/builderJob.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import type { DestinationDocker, GithubApp, GitlabApp, GitSource, Secret } from '@prisma/client';
|
||||||
|
|
||||||
|
export type BuilderJob = {
|
||||||
|
build_id: string;
|
||||||
|
type: BuildType;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
fqdn: string;
|
||||||
|
repository: string;
|
||||||
|
configHash: unknown;
|
||||||
|
branch: string;
|
||||||
|
buildPack: BuildPackName;
|
||||||
|
projectId: number;
|
||||||
|
port: number;
|
||||||
|
installCommand: string;
|
||||||
|
buildCommand?: string;
|
||||||
|
startCommand?: string;
|
||||||
|
baseDirectory: string;
|
||||||
|
publishDirectory: string;
|
||||||
|
phpModules: string;
|
||||||
|
pythonWSGI: string;
|
||||||
|
pythonModule: string;
|
||||||
|
pythonVariable: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
destinationDockerId: string;
|
||||||
|
destinationDocker: DestinationDocker;
|
||||||
|
gitSource: GitSource & { githubApp?: GithubApp; gitlabApp?: GitlabApp };
|
||||||
|
settings: BuilderJobSettings;
|
||||||
|
secrets: Secret[];
|
||||||
|
persistentStorage: { path: string }[];
|
||||||
|
pullmergeRequestId?: unknown;
|
||||||
|
sourceBranch?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Add the other build types
|
||||||
|
export type BuildType = 'manual';
|
||||||
|
|
||||||
|
// TODO: Add the other buildpack names
|
||||||
|
export type BuildPackName = 'node' | 'docker';
|
||||||
|
|
||||||
|
export type BuilderJobSettings = {
|
||||||
|
id: string;
|
||||||
|
applicationId: string;
|
||||||
|
dualCerts: boolean;
|
||||||
|
debug: boolean;
|
||||||
|
previews: boolean;
|
||||||
|
autodeploy: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
61
src/lib/types/composeFile.ts
Normal file
61
src/lib/types/composeFile.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export type ComposeFile = {
|
||||||
|
version: ComposerFileVersion;
|
||||||
|
services: Record<string, ComposeFileService>;
|
||||||
|
networks: Record<string, ComposeFileNetwork>;
|
||||||
|
volumes?: Record<string, ComposeFileVolume>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ComposeFileService = {
|
||||||
|
container_name: string;
|
||||||
|
image?: string;
|
||||||
|
networks: string[];
|
||||||
|
environment?: Record<string, unknown>;
|
||||||
|
volumes?: string[];
|
||||||
|
ulimits?: unknown;
|
||||||
|
labels?: string[];
|
||||||
|
env_file?: string[];
|
||||||
|
extra_hosts?: string[];
|
||||||
|
restart: ComposeFileRestartOption;
|
||||||
|
depends_on?: string[];
|
||||||
|
command?: string;
|
||||||
|
build?: {
|
||||||
|
context: string;
|
||||||
|
dockerfile: string;
|
||||||
|
args?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
deploy?: {
|
||||||
|
restart_policy?: {
|
||||||
|
condition?: string;
|
||||||
|
delay?: string;
|
||||||
|
max_attempts?: number;
|
||||||
|
window?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ComposerFileVersion =
|
||||||
|
| '3.8'
|
||||||
|
| '3.7'
|
||||||
|
| '3.6'
|
||||||
|
| '3.5'
|
||||||
|
| '3.4'
|
||||||
|
| '3.3'
|
||||||
|
| '3.2'
|
||||||
|
| '3.1'
|
||||||
|
| '3.0'
|
||||||
|
| '2.4'
|
||||||
|
| '2.3'
|
||||||
|
| '2.2'
|
||||||
|
| '2.1'
|
||||||
|
| '2.0';
|
||||||
|
|
||||||
|
export type ComposeFileRestartOption = 'no' | 'always' | 'on-failure' | 'unless-stopped';
|
||||||
|
|
||||||
|
export type ComposeFileNetwork = {
|
||||||
|
external: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ComposeFileVolume = {
|
||||||
|
external?: boolean;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
8
src/lib/types/destinations.ts
Normal file
8
src/lib/types/destinations.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export type CreateDockerDestination = {
|
||||||
|
name: string;
|
||||||
|
engine: string;
|
||||||
|
remoteEngine: boolean;
|
||||||
|
network: string;
|
||||||
|
isCoolifyProxyUsed: boolean;
|
||||||
|
teamId: string;
|
||||||
|
};
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
if (!session.userId) {
|
if (!session.userId) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const endpoint = `/teams.json`;
|
const endpoint = `/dashboard.json`;
|
||||||
const res = await fetch(endpoint);
|
const res = await fetch(endpoint);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -134,13 +134,18 @@
|
|||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Coolify</title>
|
<title>Coolify</title>
|
||||||
|
{#if !$session.whiteLabeled}
|
||||||
|
<link rel="icon" href="/favicon.png" />
|
||||||
|
{/if}
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
|
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
|
||||||
{#if $session.userId}
|
{#if $session.userId}
|
||||||
<nav class="nav-main">
|
<nav class="nav-main">
|
||||||
<div class="flex h-screen w-full flex-col items-center transition-all duration-100">
|
<div class="flex h-screen w-full flex-col items-center transition-all duration-100">
|
||||||
<div class="my-4 h-10 w-10"><img src="/favicon.png" alt="coolLabs logo" /></div>
|
{#if !$session.whiteLabeled}
|
||||||
<div class="flex flex-col space-y-4 py-2">
|
<div class="my-4 h-10 w-10"><img src="/favicon.png" alt="coolLabs logo" /></div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col space-y-4 py-2" class:mt-2={$session.whiteLabeled}>
|
||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/"
|
href="/"
|
||||||
@@ -171,7 +176,7 @@
|
|||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/applications"
|
href="/applications"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-green-500"
|
class="icons tooltip-green-500 tooltip-right bg-coolgray-200 hover:text-green-500"
|
||||||
class:text-green-500={$page.url.pathname.startsWith('/applications') ||
|
class:text-green-500={$page.url.pathname.startsWith('/applications') ||
|
||||||
$page.url.pathname.startsWith('/new/application')}
|
$page.url.pathname.startsWith('/new/application')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/applications') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/applications') ||
|
||||||
@@ -199,7 +204,7 @@
|
|||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/sources"
|
href="/sources"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-orange-500"
|
class="icons tooltip-orange-500 tooltip-right bg-coolgray-200 hover:text-orange-500"
|
||||||
class:text-orange-500={$page.url.pathname.startsWith('/sources') ||
|
class:text-orange-500={$page.url.pathname.startsWith('/sources') ||
|
||||||
$page.url.pathname.startsWith('/new/source')}
|
$page.url.pathname.startsWith('/new/source')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/sources') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/sources') ||
|
||||||
@@ -229,7 +234,7 @@
|
|||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/destinations"
|
href="/destinations"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-sky-500"
|
class="icons tooltip-sky-500 tooltip-right bg-coolgray-200 hover:text-sky-500"
|
||||||
class:text-sky-500={$page.url.pathname.startsWith('/destinations') ||
|
class:text-sky-500={$page.url.pathname.startsWith('/destinations') ||
|
||||||
$page.url.pathname.startsWith('/new/destination')}
|
$page.url.pathname.startsWith('/new/destination')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/destinations') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/destinations') ||
|
||||||
@@ -264,7 +269,7 @@
|
|||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/databases"
|
href="/databases"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-purple-500"
|
class="icons tooltip-purple-500 tooltip-right bg-coolgray-200 hover:text-purple-500"
|
||||||
class:text-purple-500={$page.url.pathname.startsWith('/databases') ||
|
class:text-purple-500={$page.url.pathname.startsWith('/databases') ||
|
||||||
$page.url.pathname.startsWith('/new/database')}
|
$page.url.pathname.startsWith('/new/database')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/databases') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/databases') ||
|
||||||
@@ -291,7 +296,7 @@
|
|||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/services"
|
href="/services"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-pink-500"
|
class="icons tooltip-pink-500 tooltip-right bg-coolgray-200 hover:text-pink-500"
|
||||||
class:text-pink-500={$page.url.pathname.startsWith('/services') ||
|
class:text-pink-500={$page.url.pathname.startsWith('/services') ||
|
||||||
$page.url.pathname.startsWith('/new/service')}
|
$page.url.pathname.startsWith('/new/service')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/services') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/services') ||
|
||||||
@@ -312,7 +317,6 @@
|
|||||||
<path d="M7 18a4.6 4.4 0 0 1 0 -9a5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7h-12" />
|
<path d="M7 18a4.6 4.4 0 0 1 0 -9a5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7h-12" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<div class="border-t border-stone-700" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
|
|
||||||
@@ -344,7 +348,7 @@
|
|||||||
{:else if updateStatus.success === null}
|
{:else if updateStatus.success === null}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="w-8 h-9"
|
class="h-9 w-8"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -359,7 +363,7 @@
|
|||||||
<line x1="16" y1="12" x2="12" y2="8" />
|
<line x1="16" y1="12" x2="12" y2="8" />
|
||||||
</svg>
|
</svg>
|
||||||
{:else if updateStatus.success}
|
{:else if updateStatus.success}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="w-8 h-9"
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-9 w-8"
|
||||||
><path
|
><path
|
||||||
fill="#DD2E44"
|
fill="#DD2E44"
|
||||||
d="M11.626 7.488c-.112.112-.197.247-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269 1.562-1.562-.971-6.627-5.656-11.313-4.687-4.686-9.752-7.218-11.315-5.656z"
|
d="M11.626 7.488c-.112.112-.197.247-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269 1.562-1.562-.971-6.627-5.656-11.313-4.687-4.686-9.752-7.218-11.315-5.656z"
|
||||||
@@ -404,7 +408,7 @@
|
|||||||
/></svg
|
/></svg
|
||||||
>
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="w-8 h-9"
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-9 w-8"
|
||||||
><path
|
><path
|
||||||
fill="#FFCC4D"
|
fill="#FFCC4D"
|
||||||
d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18"
|
d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18"
|
||||||
@@ -430,13 +434,12 @@
|
|||||||
<div class="flex flex-col space-y-4 py-2">
|
<div class="flex flex-col space-y-4 py-2">
|
||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/teams"
|
href="/iam"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-cyan-500"
|
class="icons tooltip-fuchsia-500 tooltip-right bg-coolgray-200 hover:text-fuchsia-500"
|
||||||
class:text-cyan-500={$page.url.pathname.startsWith('/teams')}
|
class:text-fuchsia-500={$page.url.pathname.startsWith('/iam')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/teams')}
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/iam')}
|
||||||
data-tooltip="Teams"
|
data-tooltip="IAM"
|
||||||
>
|
><svg
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="h-8 w-8"
|
class="h-8 w-8"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -453,11 +456,12 @@
|
|||||||
<path d="M21 21v-2a4 4 0 0 0 -3 -3.85" />
|
<path d="M21 21v-2a4 4 0 0 0 -3 -3.85" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{#if $session.teamId === '0'}
|
{#if $session.teamId === '0'}
|
||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/settings"
|
href="/settings"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-yellow-500"
|
class="icons tooltip-yellow-500 tooltip-right bg-coolgray-200 hover:text-yellow-500"
|
||||||
class:text-yellow-500={$page.url.pathname.startsWith('/settings')}
|
class:text-yellow-500={$page.url.pathname.startsWith('/settings')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
|
||||||
data-tooltip="Settings"
|
data-tooltip="Settings"
|
||||||
@@ -480,8 +484,9 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-red-500"
|
class="icons tooltip-red-500 tooltip-right bg-coolgray-200 hover:text-red-500"
|
||||||
data-tooltip="Logout"
|
data-tooltip="Logout"
|
||||||
on:click={logout}
|
on:click={logout}
|
||||||
>
|
>
|
||||||
@@ -514,8 +519,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
{#if $session.whiteLabeled}
|
||||||
|
<span class="fixed bottom-0 left-[50px] z-50 m-2 px-4 text-xs text-stone-700"
|
||||||
|
>Powered by <a href="https://coolify.io" target="_blank">Coolify</a></span
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<select
|
<select
|
||||||
class="fixed right-0 bottom-0 z-50 m-2 w-64 bg-opacity-30 p-2 px-4"
|
class="fixed right-0 bottom-0 z-50 m-2 w-64 bg-opacity-30 p-2 px-4 hover:bg-opacity-100"
|
||||||
bind:value={selectedTeamId}
|
bind:value={selectedTeamId}
|
||||||
on:change={switchTeam}
|
on:change={switchTeam}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -36,8 +36,15 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadBranchesByPage(page = 0) {
|
||||||
|
return await get(`${apiUrl}/repos/${selected.repository}/branches?per_page=100&page=${page}`, {
|
||||||
|
Authorization: `token ${$gitTokens.githubToken}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let reposSelectOptions;
|
let reposSelectOptions;
|
||||||
let branchSelectOptions;
|
let branchSelectOptions;
|
||||||
|
|
||||||
async function loadRepositories() {
|
async function loadRepositories() {
|
||||||
let page = 1;
|
let page = 1;
|
||||||
let reposCount = 0;
|
let reposCount = 0;
|
||||||
@@ -58,24 +65,28 @@
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
async function loadBranches(event) {
|
async function loadBranches(event) {
|
||||||
|
branches = [];
|
||||||
selected.repository = event.detail.value;
|
selected.repository = event.detail.value;
|
||||||
loading.branches = true;
|
|
||||||
selected.branch = undefined;
|
|
||||||
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
|
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
|
||||||
try {
|
let page = 1;
|
||||||
branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
|
let branchCount = 0;
|
||||||
Authorization: `token ${$gitTokens.githubToken}`
|
loading.branches = true;
|
||||||
});
|
const loadedBranches = await loadBranchesByPage();
|
||||||
branchSelectOptions = branches.map((branch) => ({
|
branches = branches.concat(loadedBranches);
|
||||||
value: branch.name,
|
branchCount = branches.length;
|
||||||
label: branch.name
|
if (branchCount === 100) {
|
||||||
}));
|
while (branchCount === 100) {
|
||||||
return;
|
page = page + 1;
|
||||||
} catch ({ error }) {
|
const nextBranches = await loadBranchesByPage(page);
|
||||||
return errorNotification(error);
|
branches = branches.concat(nextBranches);
|
||||||
} finally {
|
branchCount = nextBranches.length;
|
||||||
loading.branches = false;
|
}
|
||||||
}
|
}
|
||||||
|
loading.branches = false;
|
||||||
|
branchSelectOptions = branches.map((branch) => ({
|
||||||
|
value: branch.name,
|
||||||
|
label: branch.name
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
async function isBranchAlreadyUsed(event) {
|
async function isBranchAlreadyUsed(event) {
|
||||||
selected.branch = event.detail.value;
|
selected.branch = event.detail.value;
|
||||||
@@ -166,30 +177,36 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
|
<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
|
||||||
<div class="flex-col space-y-3 md:space-y-0 space-x-1">
|
<div class="flex-col space-y-3 md:space-y-0 space-x-1">
|
||||||
<div class="flex gap-4">
|
<div class="flex-col md:flex gap-4">
|
||||||
<div class="custom-select-wrapper">
|
<div class="custom-select-wrapper">
|
||||||
<Select
|
<Select
|
||||||
placeholder={loading.repositories
|
placeholder={loading.repositories
|
||||||
? 'Loading repositories ...'
|
? 'Loading repositories...'
|
||||||
: 'Please select a repository'}
|
: 'Please select a repository'}
|
||||||
id="repository"
|
id="repository"
|
||||||
|
showIndicator={true}
|
||||||
|
isWaiting={loading.repositories}
|
||||||
on:select={loadBranches}
|
on:select={loadBranches}
|
||||||
items={reposSelectOptions}
|
items={reposSelectOptions}
|
||||||
isDisabled={loading.repositories}
|
isDisabled={loading.repositories}
|
||||||
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
||||||
<div class="custom-select-wrapper">
|
<div class="custom-select-wrapper">
|
||||||
<Select
|
<Select
|
||||||
placeholder={loading.branches
|
placeholder={loading.branches
|
||||||
? 'Loading branches ...'
|
? 'Loading branches...'
|
||||||
: !selected.repository
|
: !selected.repository
|
||||||
? 'Please select a repository first'
|
? 'Please select a repository first'
|
||||||
: 'Please select a branch'}
|
: 'Please select a branch'}
|
||||||
id="repository"
|
isWaiting={loading.branches}
|
||||||
|
showIndicator={selected.repository}
|
||||||
|
id="branches"
|
||||||
on:select={isBranchAlreadyUsed}
|
on:select={isBranchAlreadyUsed}
|
||||||
items={branchSelectOptions}
|
items={branchSelectOptions}
|
||||||
isDisabled={loading.branches || !selected.repository}
|
isDisabled={loading.branches || !selected.repository}
|
||||||
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -202,13 +219,6 @@
|
|||||||
class:bg-orange-600={showSave}
|
class:bg-orange-600={showSave}
|
||||||
class:hover:bg-orange-500={showSave}>Save</button
|
class:hover:bg-orange-500={showSave}>Save</button
|
||||||
>
|
>
|
||||||
<!-- <button class="w-40"
|
|
||||||
><a
|
|
||||||
class="no-underline"
|
|
||||||
href="{apiUrl}/apps/{application.gitSource.githubApp.name}/installations/new"
|
|
||||||
>Modify Repositories</a
|
|
||||||
></button
|
|
||||||
> -->
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -81,6 +81,9 @@
|
|||||||
);
|
);
|
||||||
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');
|
||||||
|
const composerPHP = files.find(
|
||||||
|
(file) => file.name === 'composer.json' && file.type === 'blob'
|
||||||
|
);
|
||||||
|
|
||||||
if (yarnLock) packageManager = 'yarn';
|
if (yarnLock) packageManager = 'yarn';
|
||||||
if (pnpmLock) packageManager = 'pnpm';
|
if (pnpmLock) packageManager = 'pnpm';
|
||||||
@@ -103,7 +106,7 @@
|
|||||||
foundConfig = findBuildPack('python');
|
foundConfig = findBuildPack('python');
|
||||||
} else if (indexHtml) {
|
} else if (indexHtml) {
|
||||||
foundConfig = findBuildPack('static', packageManager);
|
foundConfig = findBuildPack('static', packageManager);
|
||||||
} else if (indexPHP) {
|
} else if (indexPHP || composerPHP) {
|
||||||
foundConfig = findBuildPack('php');
|
foundConfig = findBuildPack('php');
|
||||||
} else {
|
} else {
|
||||||
foundConfig = findBuildPack('node', packageManager);
|
foundConfig = findBuildPack('node', packageManager);
|
||||||
@@ -127,6 +130,9 @@
|
|||||||
);
|
);
|
||||||
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');
|
||||||
|
const composerPHP = files.find(
|
||||||
|
(file) => file.name === 'composer.json' && file.type === 'file'
|
||||||
|
);
|
||||||
|
|
||||||
if (yarnLock) packageManager = 'yarn';
|
if (yarnLock) packageManager = 'yarn';
|
||||||
if (pnpmLock) packageManager = 'pnpm';
|
if (pnpmLock) packageManager = 'pnpm';
|
||||||
@@ -146,7 +152,7 @@
|
|||||||
foundConfig = findBuildPack('python');
|
foundConfig = findBuildPack('python');
|
||||||
} else if (indexHtml) {
|
} else if (indexHtml) {
|
||||||
foundConfig = findBuildPack('static', packageManager);
|
foundConfig = findBuildPack('static', packageManager);
|
||||||
} else if (indexPHP) {
|
} else if (indexPHP || composerPHP) {
|
||||||
foundConfig = findBuildPack('php');
|
foundConfig = findBuildPack('php');
|
||||||
} else {
|
} else {
|
||||||
foundConfig = findBuildPack('node', packageManager);
|
foundConfig = findBuildPack('node', packageManager);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type Prisma from '@prisma/client';
|
import type Prisma from '@prisma/client';
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import { 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';
|
||||||
@@ -39,6 +39,16 @@
|
|||||||
|
|
||||||
export let destinations: Prisma.DestinationDocker[];
|
export let destinations: Prisma.DestinationDocker[];
|
||||||
|
|
||||||
|
const ownDestinations = destinations.filter((destination) => {
|
||||||
|
if (destination.teams[0].id === $session.teamId) {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const otherDestinations = destinations.filter((destination) => {
|
||||||
|
if (destination.teams[0].id !== $session.teamId) {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
});
|
||||||
async function handleSubmit(destinationId) {
|
async function handleSubmit(destinationId) {
|
||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/configuration/destination.json`, { destinationId });
|
await post(`/applications/${id}/configuration/destination.json`, { destinationId });
|
||||||
@@ -52,8 +62,8 @@
|
|||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
<div class="mr-4 text-2xl tracking-tight">Configure Destination</div>
|
<div class="mr-4 text-2xl tracking-tight">Configure Destination</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex flex-col justify-center">
|
||||||
{#if !destinations || destinations.length === 0}
|
{#if !destinations || ownDestinations.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="pb-2">No configurable Destination found</div>
|
<div class="pb-2">No configurable Destination found</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
@@ -75,8 +85,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
{#each destinations as destination}
|
{#each ownDestinations as destination}
|
||||||
|
<div class="p-2">
|
||||||
|
<form on:submit|preventDefault={() => handleSubmit(destination.id)}>
|
||||||
|
<button type="submit" class="box-selection hover:bg-sky-700 font-bold">
|
||||||
|
<div class="font-bold text-xl text-center truncate">{destination.name}</div>
|
||||||
|
<div class="text-center truncate">{destination.network}</div>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if otherDestinations.length > 0 && $session.teamId === '0'}
|
||||||
|
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Destinations</div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherDestinations as destination}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<form on:submit|preventDefault={() => handleSubmit(destination.id)}>
|
<form on:submit|preventDefault={() => handleSubmit(destination.id)}>
|
||||||
<button type="submit" class="box-selection hover:bg-sky-700 font-bold">
|
<button type="submit" class="box-selection hover:bg-sky-700 font-bold">
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type Prisma from '@prisma/client';
|
import type Prisma from '@prisma/client';
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import { 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';
|
||||||
@@ -46,6 +46,17 @@
|
|||||||
(source.type === 'github' && source.githubAppId && source.githubApp.installationId) ||
|
(source.type === 'github' && source.githubAppId && source.githubApp.installationId) ||
|
||||||
(source.type === 'gitlab' && source.gitlabAppId)
|
(source.type === 'gitlab' && source.gitlabAppId)
|
||||||
);
|
);
|
||||||
|
const ownSources = filteredSources.filter((source) => {
|
||||||
|
if (source.teams[0].id === $session.teamId) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const otherSources = filteredSources.filter((source) => {
|
||||||
|
if (source.teams[0].id !== $session.teamId) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function handleSubmit(gitSourceId) {
|
async function handleSubmit(gitSourceId) {
|
||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/configuration/source.json`, { gitSourceId });
|
await post(`/applications/${id}/configuration/source.json`, { gitSourceId });
|
||||||
@@ -54,17 +65,21 @@
|
|||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function newSource() {
|
||||||
|
const { id } = await post('/sources/new', {});
|
||||||
|
return await goto(`/sources/${id}`, { replaceState: true });
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
<div class="mr-4 text-2xl tracking-tight">Select a Git Source</div>
|
<div class="mr-4 text-2xl tracking-tight">Select a Git Source</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center">
|
<div class="flex flex-col justify-center">
|
||||||
{#if !filteredSources || filteredSources.length === 0}
|
{#if !filteredSources || ownSources.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="pb-2">No configurable Git Source found</div>
|
<div class="pb-2 text-center">No configurable Git Source found</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<a href="/new/source" sveltekit:prefetch class="add-icon bg-orange-600 hover:bg-orange-500">
|
<button on:click={newSource} class="add-icon bg-orange-600 hover:bg-orange-500">
|
||||||
<svg
|
<svg
|
||||||
class="w-6"
|
class="w-6"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -78,12 +93,39 @@
|
|||||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||||
/></svg
|
/></svg
|
||||||
>
|
>
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
{#each filteredSources as source}
|
{#each ownSources as source}
|
||||||
|
<div class="p-2">
|
||||||
|
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
||||||
|
<button
|
||||||
|
disabled={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
type="submit"
|
||||||
|
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
|
||||||
|
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
>
|
||||||
|
<div class="font-bold text-xl text-center truncate">{source.name}</div>
|
||||||
|
{#if source.gitlabApp && !source.gitlabAppId}
|
||||||
|
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if otherSources.length > 0 && $session.teamId === '0'}
|
||||||
|
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Sources</div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherSources as source}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const del: RequestHandler = async (event) => {
|
|||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
return ErrorHandler(error);
|
return ErrorHandler(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
batchSecretsPairs.map(({ name, value, isNew }) =>
|
batchSecretsPairs.map(({ name, value, isNew }) =>
|
||||||
limit(() => saveSecret({ name, value, applicationId: id, isNew, dispatch }))
|
limit(() => saveSecret({ name, value, applicationId: id, isNew }))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
batchSecrets = '';
|
batchSecrets = '';
|
||||||
@@ -39,8 +39,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h2 class="title my-6 font-bold">Paste .env file</h2>
|
<h2 class="title my-6 font-bold">Paste .env file</h2>
|
||||||
<form on:submit={getValues} class="mb-12 w-full">
|
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
|
||||||
<textarea value={batchSecrets} on:change={setBatchValue} class="mb-2 min-h-[200px] w-full" />
|
<textarea bind:value={batchSecrets} class="mb-2 min-h-[200px] w-full" />
|
||||||
<button
|
<button
|
||||||
class="bg-green-600 hover:bg-green-500 disabled:text-white disabled:opacity-40"
|
class="bg-green-600 hover:bg-green-500 disabled:text-white disabled:opacity-40"
|
||||||
type="submit">Batch add secrets</button
|
type="submit">Batch add secrets</button
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
import { del } from '$lib/api';
|
import { del } from '$lib/api';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
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';
|
||||||
import { saveSecret } from './utils';
|
import { saveSecret } from './utils';
|
||||||
|
|
||||||
@@ -32,21 +33,39 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createSecret(isNew) {
|
async function createSecret(isNew) {
|
||||||
saveSecret({
|
await saveSecret({
|
||||||
isNew,
|
isNew,
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
isBuildSecret,
|
isBuildSecret,
|
||||||
isPRMRSecret,
|
isPRMRSecret,
|
||||||
isNewSecret,
|
isNewSecret,
|
||||||
applicationId: id,
|
applicationId: id
|
||||||
dispatch
|
|
||||||
});
|
});
|
||||||
|
if (isNewSecret) {
|
||||||
|
name = '';
|
||||||
|
value = '';
|
||||||
|
isBuildSecret = false;
|
||||||
|
}
|
||||||
|
dispatch('refresh');
|
||||||
|
toast.push('Secret saved');
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSecretValue() {
|
async function setSecretValue() {
|
||||||
if (isNewSecret) {
|
if (!isPRMRSecret) {
|
||||||
isBuildSecret = !isBuildSecret;
|
isBuildSecret = !isBuildSecret;
|
||||||
|
if (!isNewSecret) {
|
||||||
|
await saveSecret({
|
||||||
|
isNew: isNewSecret,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
isBuildSecret,
|
||||||
|
isPRMRSecret,
|
||||||
|
isNewSecret,
|
||||||
|
applicationId: id
|
||||||
|
});
|
||||||
|
toast.push('Secret saved');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -81,9 +100,9 @@
|
|||||||
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
||||||
class:bg-green-600={isBuildSecret}
|
class:bg-green-600={isBuildSecret}
|
||||||
class:bg-stone-700={!isBuildSecret}
|
class:bg-stone-700={!isBuildSecret}
|
||||||
class:opacity-50={!isNewSecret}
|
class:opacity-50={isPRMRSecret}
|
||||||
class:cursor-not-allowed={!isNewSecret}
|
class:cursor-not-allowed={isPRMRSecret}
|
||||||
class:cursor-pointer={isNewSecret}
|
class:cursor-pointer={!isPRMRSecret}
|
||||||
>
|
>
|
||||||
<span class="sr-only">Use isBuildSecret</span>
|
<span class="sr-only">Use isBuildSecret</span>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -22,18 +22,45 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let secrets;
|
export let secrets;
|
||||||
export let application;
|
export let application;
|
||||||
|
import pLimit from 'p-limit';
|
||||||
import Secret from './_Secret.svelte';
|
import Secret from './_Secret.svelte';
|
||||||
import { getDomain } from '$lib/components/common';
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import BatchSecrets from './_BatchSecrets.svelte';
|
import { saveSecret } from './utils';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
|
|
||||||
|
const limit = pLimit(1);
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
|
let batchSecrets = '';
|
||||||
async function refreshSecrets() {
|
async function refreshSecrets() {
|
||||||
const data = await get(`/applications/${id}/secrets.json`);
|
const data = await get(`/applications/${id}/secrets.json`);
|
||||||
secrets = [...data.secrets];
|
secrets = [...data.secrets];
|
||||||
}
|
}
|
||||||
|
async function getValues(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const eachValuePair = batchSecrets.split('\n');
|
||||||
|
const batchSecretsPairs = eachValuePair
|
||||||
|
.filter((secret) => !secret.startsWith('#') && secret)
|
||||||
|
.map((secret) => {
|
||||||
|
const [name, value] = secret.split('=');
|
||||||
|
const cleanValue = value?.replaceAll('"', '') || '';
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
value: cleanValue,
|
||||||
|
isNew: !secrets.find((secret) => name === secret.name)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
batchSecretsPairs.map(({ name, value, isNew }) =>
|
||||||
|
limit(() => saveSecret({ name, value, applicationId: id, isNew }))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
batchSecrets = '';
|
||||||
|
await refreshSecrets();
|
||||||
|
toast.push('Secrets saved');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
||||||
@@ -134,6 +161,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<h2 class="title my-6 font-bold">Paste .env file</h2>
|
||||||
<BatchSecrets {secrets} {id} {refreshSecrets} />
|
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
|
||||||
|
<textarea bind:value={batchSecrets} class="mb-2 min-h-[200px] w-full" />
|
||||||
|
<button
|
||||||
|
class="bg-green-600 hover:bg-green-500 disabled:text-white disabled:opacity-40"
|
||||||
|
type="submit">Batch add secrets</button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ type Props = {
|
|||||||
isPRMRSecret?: boolean;
|
isPRMRSecret?: boolean;
|
||||||
isNewSecret?: boolean;
|
isNewSecret?: boolean;
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
dispatch: (name: string) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function saveSecret({
|
export async function saveSecret({
|
||||||
@@ -20,8 +19,7 @@ export async function saveSecret({
|
|||||||
isBuildSecret,
|
isBuildSecret,
|
||||||
isPRMRSecret,
|
isPRMRSecret,
|
||||||
isNewSecret,
|
isNewSecret,
|
||||||
applicationId,
|
applicationId
|
||||||
dispatch
|
|
||||||
}: Props): Promise<void> {
|
}: Props): Promise<void> {
|
||||||
if (!name) return errorNotification('Name is required.');
|
if (!name) return errorNotification('Name is required.');
|
||||||
if (!value) return errorNotification('Value is required.');
|
if (!value) return errorNotification('Value is required.');
|
||||||
@@ -33,13 +31,11 @@ export async function saveSecret({
|
|||||||
isPRMRSecret,
|
isPRMRSecret,
|
||||||
isNew: isNew || false
|
isNew: isNew || false
|
||||||
});
|
});
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let application;
|
|
||||||
import Rust from '$lib/components/svg/applications/Rust.svelte';
|
|
||||||
import Nodejs from '$lib/components/svg/applications/Nodejs.svelte';
|
|
||||||
import React from '$lib/components/svg/applications/React.svelte';
|
|
||||||
import Svelte from '$lib/components/svg/applications/Svelte.svelte';
|
|
||||||
import Vuejs from '$lib/components/svg/applications/Vuejs.svelte';
|
|
||||||
import PHP from '$lib/components/svg/applications/PHP.svelte';
|
|
||||||
import Python from '$lib/components/svg/applications/Python.svelte';
|
|
||||||
import Static from '$lib/components/svg/applications/Static.svelte';
|
|
||||||
import Nestjs from '$lib/components/svg/applications/Nestjs.svelte';
|
|
||||||
import Nuxtjs from '$lib/components/svg/applications/Nuxtjs.svelte';
|
|
||||||
import Nextjs from '$lib/components/svg/applications/Nextjs.svelte';
|
|
||||||
import Gatsby from '$lib/components/svg/applications/Gatsby.svelte';
|
|
||||||
import Docker from '$lib/components/svg/applications/Docker.svelte';
|
|
||||||
import Astro from '$lib/components/svg/applications/Astro.svelte';
|
|
||||||
import Eleventy from '$lib/components/svg/applications/Eleventy.svelte';
|
|
||||||
|
|
||||||
const buildPack = application?.buildPack?.toLowerCase();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<a href="/applications/{application.id}" class="w-96 p-2 no-underline">
|
|
||||||
<div class="box-selection group relative hover:bg-green-600">
|
|
||||||
{#if buildPack === 'rust'}
|
|
||||||
<Rust />
|
|
||||||
{:else if buildPack === 'node'}
|
|
||||||
<Nodejs />
|
|
||||||
{:else if buildPack === 'react'}
|
|
||||||
<React />
|
|
||||||
{:else if buildPack === 'svelte'}
|
|
||||||
<Svelte />
|
|
||||||
{:else if buildPack === 'vuejs'}
|
|
||||||
<Vuejs />
|
|
||||||
{:else if buildPack === 'php'}
|
|
||||||
<PHP />
|
|
||||||
{:else if buildPack === 'python'}
|
|
||||||
<Python />
|
|
||||||
{:else if buildPack === 'static'}
|
|
||||||
<Static />
|
|
||||||
{:else if buildPack === 'nestjs'}
|
|
||||||
<Nestjs />
|
|
||||||
{:else if buildPack === 'nuxtjs'}
|
|
||||||
<Nuxtjs />
|
|
||||||
{:else if buildPack === 'nextjs'}
|
|
||||||
<Nextjs />
|
|
||||||
{:else if buildPack === 'gatsby'}
|
|
||||||
<Gatsby />
|
|
||||||
{:else if buildPack === 'docker'}
|
|
||||||
<Docker />
|
|
||||||
{:else if buildPack === 'astro'}
|
|
||||||
<Astro />
|
|
||||||
{:else if buildPack === 'eleventy'}
|
|
||||||
<Eleventy />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="truncate text-center text-xl font-bold">{application.name}</div>
|
|
||||||
{#if application.fqdn}
|
|
||||||
<div class="truncate text-center">{application.fqdn}</div>
|
|
||||||
{/if}
|
|
||||||
{#if !application.gitSourceId || !application.destinationDockerId}
|
|
||||||
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
|
||||||
Configuration missing
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
@@ -1,13 +1,40 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let applications: Array<Application>;
|
export let applications: Array<Application>;
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
import Application from './_Application.svelte';
|
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import Rust from '$lib/components/svg/applications/Rust.svelte';
|
||||||
|
import Nodejs from '$lib/components/svg/applications/Nodejs.svelte';
|
||||||
|
import React from '$lib/components/svg/applications/React.svelte';
|
||||||
|
import Svelte from '$lib/components/svg/applications/Svelte.svelte';
|
||||||
|
import Vuejs from '$lib/components/svg/applications/Vuejs.svelte';
|
||||||
|
import PHP from '$lib/components/svg/applications/PHP.svelte';
|
||||||
|
import Python from '$lib/components/svg/applications/Python.svelte';
|
||||||
|
import Static from '$lib/components/svg/applications/Static.svelte';
|
||||||
|
import Nestjs from '$lib/components/svg/applications/Nestjs.svelte';
|
||||||
|
import Nuxtjs from '$lib/components/svg/applications/Nuxtjs.svelte';
|
||||||
|
import Nextjs from '$lib/components/svg/applications/Nextjs.svelte';
|
||||||
|
import Gatsby from '$lib/components/svg/applications/Gatsby.svelte';
|
||||||
|
import Docker from '$lib/components/svg/applications/Docker.svelte';
|
||||||
|
import Astro from '$lib/components/svg/applications/Astro.svelte';
|
||||||
|
import Eleventy from '$lib/components/svg/applications/Eleventy.svelte';
|
||||||
|
import { getDomain } from '$lib/components/common';
|
||||||
|
|
||||||
async function newApplication() {
|
async function newApplication() {
|
||||||
const { id } = await post('/applications/new', {});
|
const { id } = await post('/applications/new', {});
|
||||||
return await goto(`/applications/${id}`, { replaceState: true });
|
return await goto(`/applications/${id}`, { replaceState: true });
|
||||||
}
|
}
|
||||||
|
const ownApplications = applications.filter((application) => {
|
||||||
|
if (application.teams[0].id === $session.teamId) {
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const otherApplications = applications.filter((application) => {
|
||||||
|
if (application.teams[0].id !== $session.teamId) {
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
@@ -30,14 +57,125 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-col flex-wrap justify-center">
|
||||||
{#if !applications || applications.length === 0}
|
{#if !applications || ownApplications.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">No applications found</div>
|
<div class="text-center text-xl font-bold">No applications found</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{/if}
|
||||||
{#each applications as application}
|
{#if ownApplications.length > 0 || otherApplications.length > 0}
|
||||||
<Application {application} />
|
<div class="flex flex-col">
|
||||||
{/each}
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each ownApplications as application}
|
||||||
|
<a href="/applications/{application.id}" class="w-96 p-2 no-underline">
|
||||||
|
<div class="box-selection group relative hover:bg-green-600">
|
||||||
|
{#if application.buildPack}
|
||||||
|
{#if application.buildPack.toLowerCase() === 'rust'}
|
||||||
|
<Rust />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'node'}
|
||||||
|
<Nodejs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'react'}
|
||||||
|
<React />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'svelte'}
|
||||||
|
<Svelte />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'vuejs'}
|
||||||
|
<Vuejs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'php'}
|
||||||
|
<PHP />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'python'}
|
||||||
|
<Python />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'static'}
|
||||||
|
<Static />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nestjs'}
|
||||||
|
<Nestjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nuxtjs'}
|
||||||
|
<Nuxtjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nextjs'}
|
||||||
|
<Nextjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'gatsby'}
|
||||||
|
<Gatsby />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'docker'}
|
||||||
|
<Docker />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'astro'}
|
||||||
|
<Astro />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'eleventy'}
|
||||||
|
<Eleventy />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="truncate text-center text-xl font-bold">{application.name}</div>
|
||||||
|
{#if $session.teamId === '0' && otherApplications.length > 0}
|
||||||
|
<div class="truncate text-center">Team {application.teams[0].name}</div>
|
||||||
|
{/if}
|
||||||
|
{#if application.fqdn}
|
||||||
|
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
|
||||||
|
{/if}
|
||||||
|
{#if !application.gitSourceId || !application.destinationDockerId || !application.fqdn}
|
||||||
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if otherApplications.length > 0 && $session.teamId === '0'}
|
||||||
|
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Applications</div>
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherApplications as application}
|
||||||
|
<a href="/applications/{application.id}" class="w-96 p-2 no-underline">
|
||||||
|
<div class="box-selection group relative hover:bg-green-600">
|
||||||
|
{#if application.buildPack}
|
||||||
|
{#if application.buildPack.toLowerCase() === 'rust'}
|
||||||
|
<Rust />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'node'}
|
||||||
|
<Nodejs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'react'}
|
||||||
|
<React />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'svelte'}
|
||||||
|
<Svelte />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'vuejs'}
|
||||||
|
<Vuejs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'php'}
|
||||||
|
<PHP />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'python'}
|
||||||
|
<Python />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'static'}
|
||||||
|
<Static />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nestjs'}
|
||||||
|
<Nestjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nuxtjs'}
|
||||||
|
<Nuxtjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nextjs'}
|
||||||
|
<Nextjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'gatsby'}
|
||||||
|
<Gatsby />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'docker'}
|
||||||
|
<Docker />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'astro'}
|
||||||
|
<Astro />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'eleventy'}
|
||||||
|
<Eleventy />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="truncate text-center text-xl font-bold">{application.name}</div>
|
||||||
|
{#if $session.teamId === '0'}
|
||||||
|
<div class="truncate text-center">Team {application.teams[0].name}</div>
|
||||||
|
{/if}
|
||||||
|
{#if application.fqdn}
|
||||||
|
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
|
||||||
|
{/if}
|
||||||
|
{#if !application.gitSourceId || !application.destinationDockerId || !application.fqdn}
|
||||||
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getTeam, getUserDetails } from '$lib/common';
|
import { getUserDetails } from '$lib/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
@@ -9,23 +9,28 @@ export const get: RequestHandler = async (event) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const applicationsCount = await db.prisma.application.count({
|
const applicationsCount = await db.prisma.application.count({
|
||||||
where: { teams: { some: { id: teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
});
|
});
|
||||||
const sourcesCount = await db.prisma.gitSource.count({
|
const sourcesCount = await db.prisma.gitSource.count({
|
||||||
where: { teams: { some: { id: teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
});
|
});
|
||||||
const destinationsCount = await db.prisma.destinationDocker.count({
|
const destinationsCount = await db.prisma.destinationDocker.count({
|
||||||
where: { teams: { some: { id: teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
});
|
});
|
||||||
const teamsCount = await db.prisma.permission.count({ where: { userId } });
|
const teamsCount = await db.prisma.permission.count({ where: { userId } });
|
||||||
const databasesCount = await db.prisma.database.count({
|
const databasesCount = await db.prisma.database.count({
|
||||||
where: { teams: { some: { id: teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
});
|
});
|
||||||
const servicesCount = await db.prisma.service.count({
|
const servicesCount = await db.prisma.service.count({
|
||||||
where: { teams: { some: { id: teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
|
});
|
||||||
|
const teams = await db.prisma.permission.findMany({
|
||||||
|
where: { userId },
|
||||||
|
include: { team: { include: { _count: { select: { users: true } } } } }
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
body: {
|
body: {
|
||||||
|
teams,
|
||||||
applicationsCount,
|
applicationsCount,
|
||||||
sourcesCount,
|
sourcesCount,
|
||||||
destinationsCount,
|
destinationsCount,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
export let database;
|
export let database;
|
||||||
export let privatePort;
|
export let privatePort;
|
||||||
export let settings;
|
export let settings;
|
||||||
|
export let isRunning;
|
||||||
|
|
||||||
import { page, session } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
@@ -15,27 +17,39 @@
|
|||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
let publicLoading = false;
|
||||||
|
|
||||||
let isPublic = database.settings.isPublic || false;
|
let isPublic = database.settings.isPublic || false;
|
||||||
let appendOnly = database.settings.appendOnly;
|
let appendOnly = database.settings.appendOnly;
|
||||||
|
|
||||||
let databaseDefault = database.defaultDatabase;
|
let databaseDefault;
|
||||||
let databaseDbUser = database.dbUser;
|
let databaseDbUser;
|
||||||
let databaseDbUserPassword = database.dbUserPassword;
|
let databaseDbUserPassword;
|
||||||
if (database.type === 'mongodb') {
|
|
||||||
databaseDefault = '?readPreference=primary&ssl=false';
|
generateDbDetails();
|
||||||
databaseDbUser = database.rootUser;
|
|
||||||
databaseDbUserPassword = database.rootUserPassword;
|
function generateDbDetails() {
|
||||||
} else if (database.type === 'redis') {
|
databaseDefault = database.defaultDatabase;
|
||||||
databaseDefault = '';
|
databaseDbUser = database.dbUser;
|
||||||
databaseDbUser = '';
|
databaseDbUserPassword = database.dbUserPassword;
|
||||||
|
if (database.type === 'mongodb') {
|
||||||
|
databaseDefault = '?readPreference=primary&ssl=false';
|
||||||
|
databaseDbUser = database.rootUser;
|
||||||
|
databaseDbUserPassword = database.rootUserPassword;
|
||||||
|
} else if (database.type === 'redis') {
|
||||||
|
databaseDefault = '';
|
||||||
|
databaseDbUser = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let databaseUrl = generateUrl();
|
$: databaseUrl = generateUrl();
|
||||||
|
|
||||||
function generateUrl() {
|
function generateUrl() {
|
||||||
return browser
|
return (databaseUrl = browser
|
||||||
? `${database.type}://${
|
? `${database.type}://${
|
||||||
databaseDbUser ? databaseDbUser + ':' : ''
|
databaseDbUser ? databaseDbUser + ':' : ''
|
||||||
}${databaseDbUserPassword}@${
|
}${databaseDbUserPassword}@${
|
||||||
@@ -45,32 +59,50 @@
|
|||||||
: window.location.hostname
|
: window.location.hostname
|
||||||
: database.id
|
: database.id
|
||||||
}:${isPublic ? database.publicPort : privatePort}/${databaseDefault}`
|
}:${isPublic ? database.publicPort : privatePort}/${databaseDefault}`
|
||||||
: 'Loading...';
|
: 'Loading...');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changeSettings(name) {
|
async function changeSettings(name) {
|
||||||
|
if (publicLoading || !isRunning) return;
|
||||||
|
publicLoading = true;
|
||||||
|
let data = {
|
||||||
|
isPublic,
|
||||||
|
appendOnly
|
||||||
|
};
|
||||||
if (name === 'isPublic') {
|
if (name === 'isPublic') {
|
||||||
isPublic = !isPublic;
|
data.isPublic = !isPublic;
|
||||||
}
|
}
|
||||||
if (name === 'appendOnly') {
|
if (name === 'appendOnly') {
|
||||||
appendOnly = !appendOnly;
|
data.appendOnly = !appendOnly;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { publicPort } = await post(`/databases/${id}/settings.json`, { isPublic, appendOnly });
|
const { publicPort } = await post(`/databases/${id}/settings.json`, {
|
||||||
|
isPublic: data.isPublic,
|
||||||
|
appendOnly: data.appendOnly
|
||||||
|
});
|
||||||
|
isPublic = data.isPublic;
|
||||||
|
appendOnly = data.appendOnly;
|
||||||
|
databaseUrl = generateUrl();
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
database.publicPort = publicPort;
|
database.publicPort = publicPort;
|
||||||
}
|
}
|
||||||
databaseUrl = generateUrl();
|
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
publicLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
try {
|
try {
|
||||||
await post(`/databases/${id}.json`, { ...database });
|
loading = true;
|
||||||
return window.location.reload();
|
await post(`/databases/${id}.json`, { ...database, isRunning });
|
||||||
|
generateDbDetails();
|
||||||
|
databaseUrl = generateUrl();
|
||||||
|
toast.push('Settings saved.');
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -142,21 +174,21 @@
|
|||||||
readonly
|
readonly
|
||||||
disabled
|
disabled
|
||||||
name="publicPort"
|
name="publicPort"
|
||||||
value={isPublic ? database.publicPort : privatePort}
|
value={publicLoading ? 'Loading...' : isPublic ? database.publicPort : privatePort}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2">
|
<div class="grid grid-flow-row gap-2">
|
||||||
{#if database.type === 'mysql'}
|
{#if database.type === 'mysql'}
|
||||||
<MySql bind:database />
|
<MySql bind:database {isRunning} />
|
||||||
{:else if database.type === 'postgresql'}
|
{:else if database.type === 'postgresql'}
|
||||||
<PostgreSql bind:database />
|
<PostgreSql bind:database {isRunning} />
|
||||||
{:else if database.type === 'mongodb'}
|
{:else if database.type === 'mongodb'}
|
||||||
<MongoDb {database} />
|
<MongoDb bind:database {isRunning} />
|
||||||
{:else if database.type === 'redis'}
|
{:else if database.type === 'redis'}
|
||||||
<Redis {database} />
|
<Redis bind:database {isRunning} />
|
||||||
{:else if database.type === 'couchdb'}
|
{:else if database.type === 'couchdb'}
|
||||||
<CouchDb bind:database />
|
<CouchDb {database} />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-center px-10 pb-8">
|
<div class="grid grid-cols-2 items-center px-10 pb-8">
|
||||||
<label for="url" class="text-base font-bold text-stone-100">Connection String</label>
|
<label for="url" class="text-base font-bold text-stone-100">Connection String</label>
|
||||||
@@ -168,7 +200,7 @@
|
|||||||
name="url"
|
name="url"
|
||||||
readonly
|
readonly
|
||||||
disabled
|
disabled
|
||||||
value={databaseUrl}
|
value={publicLoading || loading ? 'Loading...' : generateUrl()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -179,10 +211,12 @@
|
|||||||
<div class="px-10 pb-10">
|
<div class="px-10 pb-10">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
loading={publicLoading}
|
||||||
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!"
|
||||||
|
disabled={!isRunning}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if database.type === 'redis'}
|
{#if database.type === 'redis'}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
export let database;
|
export let database;
|
||||||
|
export let isRunning;
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
@@ -21,13 +23,14 @@
|
|||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
|
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
|
disabled={!isRunning}
|
||||||
|
readonly={!isRunning}
|
||||||
placeholder="Generated automatically after start"
|
placeholder="Generated automatically after start"
|
||||||
isPasswordField={true}
|
isPasswordField={true}
|
||||||
readonly
|
|
||||||
disabled
|
|
||||||
id="rootUserPassword"
|
id="rootUserPassword"
|
||||||
name="rootUserPassword"
|
name="rootUserPassword"
|
||||||
value={database.rootUserPassword}
|
bind:value={database.rootUserPassword}
|
||||||
/>
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
export let database;
|
export let database;
|
||||||
|
export let isRunning;
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
@@ -33,14 +35,15 @@
|
|||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
readonly
|
disabled={!isRunning}
|
||||||
disabled
|
readonly={!isRunning}
|
||||||
placeholder="Generated automatically after start"
|
placeholder="Generated automatically after start"
|
||||||
isPasswordField
|
isPasswordField
|
||||||
id="dbUserPassword"
|
id="dbUserPassword"
|
||||||
name="dbUserPassword"
|
name="dbUserPassword"
|
||||||
value={database.dbUserPassword}
|
bind:value={database.dbUserPassword}
|
||||||
/>
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
|
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
|
||||||
@@ -56,13 +59,14 @@
|
|||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
|
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
readonly
|
disabled={!isRunning}
|
||||||
disabled
|
readonly={!isRunning}
|
||||||
placeholder="Generated automatically after start"
|
placeholder="Generated automatically after start"
|
||||||
isPasswordField
|
isPasswordField
|
||||||
id="rootUserPassword"
|
id="rootUserPassword"
|
||||||
name="rootUserPassword"
|
name="rootUserPassword"
|
||||||
value={database.rootUserPassword}
|
bind:value={database.rootUserPassword}
|
||||||
/>
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
export let database;
|
export let database;
|
||||||
|
export let isRunning;
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
@@ -19,6 +21,19 @@
|
|||||||
bind:value={database.defaultDatabase}
|
bind:value={database.defaultDatabase}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="rootUser" class="text-base font-bold text-stone-100"
|
||||||
|
>Root (postgres) User Password</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
disabled={!isRunning}
|
||||||
|
readonly={!isRunning}
|
||||||
|
placeholder="Generated automatically after start"
|
||||||
|
id="rootUserPassword"
|
||||||
|
name="rootUserPassword"
|
||||||
|
bind:value={database.rootUserPassword}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
|
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
@@ -33,13 +48,14 @@
|
|||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
readonly
|
disabled={!isRunning}
|
||||||
disabled
|
readonly={!isRunning}
|
||||||
placeholder="Generated automatically after start"
|
placeholder="Generated automatically after start"
|
||||||
isPasswordField
|
isPasswordField
|
||||||
id="dbUserPassword"
|
id="dbUserPassword"
|
||||||
name="dbUserPassword"
|
name="dbUserPassword"
|
||||||
value={database.dbUserPassword}
|
bind:value={database.dbUserPassword}
|
||||||
/>
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
export let database;
|
export let database;
|
||||||
|
export let isRunning;
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
@@ -10,40 +12,14 @@
|
|||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
disabled
|
disabled={!isRunning}
|
||||||
readonly
|
readonly={!isRunning}
|
||||||
placeholder="Generated automatically after start"
|
placeholder="Generated automatically after start"
|
||||||
isPasswordField
|
isPasswordField
|
||||||
id="dbUserPassword"
|
id="dbUserPassword"
|
||||||
name="dbUserPassword"
|
name="dbUserPassword"
|
||||||
value={database.dbUserPassword}
|
bind:value={database.dbUserPassword}
|
||||||
/>
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="grid grid-cols-3 items-center">
|
|
||||||
<label for="rootUser">Root User</label>
|
|
||||||
<div class="col-span-2 ">
|
|
||||||
<CopyPasswordField
|
|
||||||
disabled
|
|
||||||
readonly
|
|
||||||
placeholder="Generated automatically after start"
|
|
||||||
id="rootUser"
|
|
||||||
name="rootUser"
|
|
||||||
value={database.rootUser}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
|
||||||
<label for="rootUserPassword">Root's Password</label>
|
|
||||||
<div class="col-span-2 ">
|
|
||||||
<CopyPasswordField
|
|
||||||
disabled
|
|
||||||
readonly
|
|
||||||
placeholder="Generated automatically after start"
|
|
||||||
isPasswordField
|
|
||||||
id="rootUserPassword"
|
|
||||||
name="rootUserPassword"
|
|
||||||
value={database.rootUserPassword}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
const endpoint = `/databases/${params.id}.json`;
|
const endpoint = `/databases/${params.id}.json`;
|
||||||
const res = await fetch(endpoint);
|
const res = await fetch(endpoint);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const { database, state, versions, privatePort, settings } = await res.json();
|
const { database, isRunning, versions, privatePort, settings } = await res.json();
|
||||||
if (!database || Object.entries(database).length === 0) {
|
if (!database || Object.entries(database).length === 0) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
@@ -35,13 +35,13 @@
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
database,
|
database,
|
||||||
state,
|
isRunning,
|
||||||
versions,
|
versions,
|
||||||
privatePort
|
privatePort
|
||||||
},
|
},
|
||||||
stuff: {
|
stuff: {
|
||||||
database,
|
database,
|
||||||
state,
|
isRunning,
|
||||||
versions,
|
versions,
|
||||||
privatePort,
|
privatePort,
|
||||||
settings
|
settings
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
export let database;
|
export let database;
|
||||||
export let state;
|
export let isRunning;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
|
||||||
async function deleteDatabase() {
|
async function deleteDatabase() {
|
||||||
@@ -91,8 +91,6 @@
|
|||||||
return window.location.reload();
|
return window.location.reload();
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,8 +101,6 @@
|
|||||||
return window.location.reload();
|
return window.location.reload();
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -114,7 +110,7 @@
|
|||||||
<Loading fullscreen cover />
|
<Loading fullscreen cover />
|
||||||
{:else}
|
{:else}
|
||||||
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
|
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
|
||||||
{#if state === 'running'}
|
{#if isRunning}
|
||||||
<button
|
<button
|
||||||
on:click={stopDatabase}
|
on:click={stopDatabase}
|
||||||
title="Stop database"
|
title="Stop database"
|
||||||
@@ -140,7 +136,7 @@
|
|||||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{:else if state === 'not started'}
|
{:else}
|
||||||
<button
|
<button
|
||||||
on:click={startDatabase}
|
on:click={startDatabase}
|
||||||
title="Start database"
|
title="Start database"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getUserDetails } from '$lib/common';
|
import { getUserDetails } from '$lib/common';
|
||||||
|
import { supportedDatabaseTypesAndVersions } from '$lib/components/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const get: RequestHandler = async (event) => {
|
export const get: RequestHandler = async (event) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getUserDetails } from '$lib/common';
|
import { getUserDetails } from '$lib/common';
|
||||||
|
import { supportedDatabaseTypesAndVersions } from '$lib/components/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const get: RequestHandler = async (event) => {
|
export const get: RequestHandler = async (event) => {
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
|
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { generateDatabaseConfiguration, getVersions, ErrorHandler } from '$lib/database';
|
import {
|
||||||
|
generateDatabaseConfiguration,
|
||||||
|
getVersions,
|
||||||
|
ErrorHandler,
|
||||||
|
updatePasswordInDb
|
||||||
|
} from '$lib/database';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const get: RequestHandler = async (event) => {
|
export const get: RequestHandler = async (event) => {
|
||||||
@@ -12,7 +17,7 @@ export const get: RequestHandler = async (event) => {
|
|||||||
const database = await db.getDatabase({ id, teamId });
|
const database = await db.getDatabase({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker } = database;
|
const { destinationDockerId, destinationDocker } = database;
|
||||||
|
|
||||||
let state = 'not started';
|
let isRunning = false;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const host = getEngine(destinationDocker.engine);
|
const host = getEngine(destinationDocker.engine);
|
||||||
|
|
||||||
@@ -22,7 +27,7 @@ export const get: RequestHandler = async (event) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (JSON.parse(stdout).Running) {
|
if (JSON.parse(stdout).Running) {
|
||||||
state = 'running';
|
isRunning = true;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
@@ -34,7 +39,7 @@ export const get: RequestHandler = async (event) => {
|
|||||||
body: {
|
body: {
|
||||||
privatePort: configuration?.privatePort,
|
privatePort: configuration?.privatePort,
|
||||||
database,
|
database,
|
||||||
state,
|
isRunning,
|
||||||
versions: getVersions(database.type),
|
versions: getVersions(database.type),
|
||||||
settings
|
settings
|
||||||
}
|
}
|
||||||
@@ -48,10 +53,26 @@ export const post: 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;
|
const { id } = event.params;
|
||||||
const { name, defaultDatabase, dbUser, dbUserPassword, rootUser, rootUserPassword, version } =
|
const {
|
||||||
await event.request.json();
|
name,
|
||||||
|
defaultDatabase,
|
||||||
|
dbUser,
|
||||||
|
dbUserPassword,
|
||||||
|
rootUser,
|
||||||
|
rootUserPassword,
|
||||||
|
version,
|
||||||
|
isRunning
|
||||||
|
} = await event.request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const database = await db.getDatabase({ id, teamId });
|
||||||
|
if (isRunning) {
|
||||||
|
if (database.dbUserPassword !== dbUserPassword) {
|
||||||
|
await updatePasswordInDb(database, dbUser, dbUserPassword, false);
|
||||||
|
} else if (database.rootUserPassword !== rootUserPassword) {
|
||||||
|
await updatePasswordInDb(database, rootUser, rootUserPassword, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
await db.updateDatabase({
|
await db.updateDatabase({
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
database: stuff.database,
|
database: stuff.database,
|
||||||
versions: stuff.versions,
|
versions: stuff.versions,
|
||||||
privatePort: stuff.privatePort,
|
privatePort: stuff.privatePort,
|
||||||
settings: stuff.settings
|
settings: stuff.settings,
|
||||||
|
isRunning: stuff.isRunning
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
export let database;
|
export let database;
|
||||||
export let settings;
|
export let settings;
|
||||||
export let privatePort;
|
export let privatePort;
|
||||||
|
export let isRunning;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center space-x-2 p-6 text-2xl font-bold">
|
<div class="flex items-center space-x-2 p-6 text-2xl font-bold">
|
||||||
@@ -47,4 +49,4 @@
|
|||||||
<DatabaseLinks {database} />
|
<DatabaseLinks {database} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Databases bind:database {privatePort} {settings} />
|
<Databases bind:database {privatePort} {settings} {isRunning} />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import yaml from 'js-yaml';
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { makeLabelForStandaloneDatabase } from '$lib/buildPacks/common';
|
import { makeLabelForStandaloneDatabase } from '$lib/buildPacks/common';
|
||||||
import { startTcpProxy } from '$lib/haproxy';
|
import { startTcpProxy } from '$lib/haproxy';
|
||||||
|
import type { ComposeFile } from '$lib/types/composeFile';
|
||||||
|
|
||||||
export const post: RequestHandler = async (event) => {
|
export const post: RequestHandler = async (event) => {
|
||||||
const { teamId, status, body } = await getUserDetails(event);
|
const { teamId, status, body } = await getUserDetails(event);
|
||||||
@@ -33,7 +34,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
|
|
||||||
const composeFile = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
[id]: {
|
[id]: {
|
||||||
@@ -44,7 +45,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
volumes: [volume],
|
volumes: [volume],
|
||||||
ulimits,
|
ulimits,
|
||||||
labels,
|
labels,
|
||||||
restart: 'always'
|
restart: 'always',
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -8,11 +8,22 @@
|
|||||||
import Redis from '$lib/components/svg/databases/Redis.svelte';
|
import Redis from '$lib/components/svg/databases/Redis.svelte';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { session } from '$app/stores';
|
||||||
|
|
||||||
async function newDatabase() {
|
async function newDatabase() {
|
||||||
const { id } = await post('/databases/new', {});
|
const { id } = await post('/databases/new', {});
|
||||||
return await goto(`/databases/${id}`, { replaceState: true });
|
return await goto(`/databases/${id}`, { replaceState: true });
|
||||||
}
|
}
|
||||||
|
const ownDatabases = databases.filter((database) => {
|
||||||
|
if (database.teams[0].id === $session.teamId) {
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const otherDatabases = databases.filter((database) => {
|
||||||
|
if (database.teams[0].id !== $session.teamId) {
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
@@ -34,40 +45,83 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-col flex-wrap justify-center">
|
||||||
{#if !databases || databases.length === 0}
|
{#if !databases || ownDatabases.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">No databases found</div>
|
<div class="text-center text-xl font-bold">No databases found</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{/if}
|
||||||
{#each databases as database}
|
{#if ownDatabases.length > 0 || otherDatabases.length > 0}
|
||||||
<a href="/databases/{database.id}" class="no-underline p-2 w-96">
|
<div class="flex flex-col">
|
||||||
<div class="box-selection relative hover:bg-purple-600 group">
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
{#if database.type === 'clickhouse'}
|
{#each ownDatabases as database}
|
||||||
<Clickhouse isAbsolute />
|
<a href="/databases/{database.id}" class="w-96 p-2 no-underline">
|
||||||
{:else if database.type === 'couchdb'}
|
<div class="box-selection group relative hover:bg-purple-600">
|
||||||
<CouchDB isAbsolute />
|
{#if database.type === 'clickhouse'}
|
||||||
{:else if database.type === 'mongodb'}
|
<Clickhouse isAbsolute />
|
||||||
<MongoDB isAbsolute />
|
{:else if database.type === 'couchdb'}
|
||||||
{:else if database.type === 'mysql'}
|
<CouchDB isAbsolute />
|
||||||
<MySQL isAbsolute />
|
{:else if database.type === 'mongodb'}
|
||||||
{:else if database.type === 'postgresql'}
|
<MongoDB isAbsolute />
|
||||||
<PostgreSQL isAbsolute />
|
{:else if database.type === 'mysql'}
|
||||||
{:else if database.type === 'redis'}
|
<MySQL isAbsolute />
|
||||||
<Redis isAbsolute />
|
{:else if database.type === 'postgresql'}
|
||||||
{/if}
|
<PostgreSQL isAbsolute />
|
||||||
<div class="font-bold text-xl text-center truncate">
|
{:else if database.type === 'redis'}
|
||||||
{database.name}
|
<Redis isAbsolute />
|
||||||
</div>
|
{/if}
|
||||||
{#if !database.type}
|
<div class="truncate text-center text-xl font-bold">
|
||||||
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
{database.name}
|
||||||
Configuration missing
|
</div>
|
||||||
|
{#if $session.teamId === '0' && otherDatabases.length > 0}
|
||||||
|
<div class="truncate text-center">{database.teams[0].name}</div>
|
||||||
|
{/if}
|
||||||
|
{#if !database.type}
|
||||||
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
</a>
|
||||||
<div class="text-center truncate">{database.type}</div>
|
{/each}
|
||||||
{/if}
|
</div>
|
||||||
|
{#if otherDatabases.length > 0 && $session.teamId === '0'}
|
||||||
|
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Databases</div>
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherDatabases as database}
|
||||||
|
<a href="/databases/{database.id}" class="w-96 p-2 no-underline">
|
||||||
|
<div class="box-selection group relative hover:bg-purple-600">
|
||||||
|
{#if database.type === 'clickhouse'}
|
||||||
|
<Clickhouse isAbsolute />
|
||||||
|
{:else if database.type === 'couchdb'}
|
||||||
|
<CouchDB isAbsolute />
|
||||||
|
{:else if database.type === 'mongodb'}
|
||||||
|
<MongoDB isAbsolute />
|
||||||
|
{:else if database.type === 'mysql'}
|
||||||
|
<MySQL isAbsolute />
|
||||||
|
{:else if database.type === 'postgresql'}
|
||||||
|
<PostgreSQL isAbsolute />
|
||||||
|
{:else if database.type === 'redis'}
|
||||||
|
<Redis isAbsolute />
|
||||||
|
{/if}
|
||||||
|
<div class="truncate text-center text-xl font-bold">
|
||||||
|
{database.name}
|
||||||
|
</div>
|
||||||
|
{#if $session.teamId === '0'}
|
||||||
|
<div class="truncate text-center">{database.teams[0].name}</div>
|
||||||
|
{/if}
|
||||||
|
{#if !database.type}
|
||||||
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="text-center truncate">{database.type}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
{/if}
|
||||||
{/each}
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
|
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
|
||||||
// let scannedApps = [];
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
let loadingProxy = false;
|
||||||
let restarting = false;
|
let restarting = false;
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
loading = true;
|
loading = true;
|
||||||
@@ -25,12 +25,6 @@
|
|||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// async function scanApps() {
|
|
||||||
// scannedApps = [];
|
|
||||||
// const data = await fetch(`/destinations/${id}/scan.json`);
|
|
||||||
// const { containers } = await data.json();
|
|
||||||
// scannedApps = containers;
|
|
||||||
// }
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (state === false && destination.isCoolifyProxyUsed === true) {
|
if (state === false && destination.isCoolifyProxyUsed === true) {
|
||||||
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
||||||
@@ -71,6 +65,7 @@
|
|||||||
}
|
}
|
||||||
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
||||||
try {
|
try {
|
||||||
|
loadingProxy = true;
|
||||||
await post(`/destinations/${id}/settings.json`, {
|
await post(`/destinations/${id}/settings.json`, {
|
||||||
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
|
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
|
||||||
engine: destination.engine
|
engine: destination.engine
|
||||||
@@ -82,6 +77,8 @@
|
|||||||
}
|
}
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loadingProxy = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,41 +181,20 @@
|
|||||||
value={destination.network}
|
value={destination.network}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
{#if $session.teamId === '0'}
|
||||||
<Setting
|
<div class="grid grid-cols-2 items-center">
|
||||||
disabled={cannotDisable}
|
<Setting
|
||||||
bind:setting={destination.isCoolifyProxyUsed}
|
loading={loadingProxy}
|
||||||
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
|
title="Use Coolify Proxy?"
|
||||||
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
|
description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration. Databases will have their own proxy. <br><br>${
|
||||||
: ''
|
cannotDisable
|
||||||
}`}
|
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
|
||||||
/>
|
: ''
|
||||||
</div>
|
}`}
|
||||||
</form>
|
/>
|
||||||
<!-- <div class="flex justify-center">
|
</div>
|
||||||
{#if payload.isCoolifyProxyUsed}
|
|
||||||
{#if state}
|
|
||||||
<button on:click={stopProxy}>Stop proxy</button>
|
|
||||||
{:else}
|
|
||||||
<button on:click={startProxy}>Start proxy</button>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
</div> -->
|
</form>
|
||||||
|
|
||||||
<!-- {#if scannedApps.length > 0}
|
|
||||||
<div class="flex justify-center px-6 pb-10">
|
|
||||||
<div class="flex space-x-2 h-8 items-center">
|
|
||||||
<div class="font-bold text-xl text-white">Found applications</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="max-w-4xl mx-auto px-6">
|
|
||||||
<div class="flex space-x-2 justify-center">
|
|
||||||
{#each scannedApps as app}
|
|
||||||
<FoundApp {app} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if} -->
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import type { RequestHandler } from '@sveltejs/kit';
|
|||||||
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 { id } = event.params;
|
const { id } = event.params;
|
||||||
try {
|
try {
|
||||||
const destination = await db.getDestination({ id, teamId });
|
const destination = await db.getDestination({ id, teamId });
|
||||||
|
|||||||
@@ -24,6 +24,16 @@
|
|||||||
|
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
export let destinations: Prisma.DestinationDocker[];
|
export let destinations: Prisma.DestinationDocker[];
|
||||||
|
const ownDestinations = destinations.filter((destination) => {
|
||||||
|
if (destination.teams[0].id === $session.teamId) {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const otherDestinations = destinations.filter((destination) => {
|
||||||
|
if (destination.teams[0].id !== $session.teamId) {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
@@ -47,20 +57,43 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
{#if !destinations || destinations.length === 0}
|
{#if !destinations || ownDestinations.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">No destination found</div>
|
<div class="text-center text-xl font-bold">No destination found</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{/if}
|
||||||
<div class="flex flex-wrap justify-center">
|
{#if ownDestinations.length > 0 || otherDestinations.length > 0}
|
||||||
{#each destinations as destination}
|
<div class="flex flex-col">
|
||||||
<a href="/destinations/{destination.id}" class="no-underline p-2 w-96">
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
<div class="box-selection hover:bg-sky-600">
|
{#each ownDestinations as destination}
|
||||||
<div class="font-bold text-xl text-center truncate">{destination.name}</div>
|
<a href="/destinations/{destination.id}" class="w-96 p-2 no-underline">
|
||||||
<div class="text-center truncate">{destination.network}</div>
|
<div class="box-selection hover:bg-sky-600">
|
||||||
</div>
|
<div class="truncate text-center text-xl font-bold">{destination.name}</div>
|
||||||
</a>
|
{#if $session.teamId === '0' && otherDestinations.length > 0}
|
||||||
{/each}
|
<div class="truncate text-center">{destination.teams[0].name}</div>
|
||||||
|
{/if}
|
||||||
|
<div class="truncate text-center">{destination.network}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if otherDestinations.length > 0 && $session.teamId === '0'}
|
||||||
|
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Destinations</div>
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherDestinations as destination}
|
||||||
|
<a href="/destinations/{destination.id}" class="w-96 p-2 no-underline">
|
||||||
|
<div class="box-selection hover:bg-sky-600">
|
||||||
|
<div class="truncate text-center text-xl font-bold">{destination.name}</div>
|
||||||
|
{#if $session.teamId === '0'}
|
||||||
|
<div class="truncate text-center">{destination.teams[0].name}</div>
|
||||||
|
{/if}
|
||||||
|
<div class="truncate text-center">{destination.network}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
130
src/routes/iam/index.json.ts
Normal file
130
src/routes/iam/index.json.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { getUserDetails } from '$lib/common';
|
||||||
|
import * as db from '$lib/database';
|
||||||
|
import { ErrorHandler } from '$lib/database';
|
||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const get: RequestHandler = async (event) => {
|
||||||
|
const { teamId, userId, status, body } = await getUserDetails(event);
|
||||||
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const account = await db.prisma.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
select: { id: true, email: true, teams: true }
|
||||||
|
});
|
||||||
|
let accounts = [];
|
||||||
|
if (teamId === '0') {
|
||||||
|
accounts = await db.prisma.user.findMany({ select: { id: true, email: true, teams: true } });
|
||||||
|
}
|
||||||
|
|
||||||
|
const teams = await db.prisma.permission.findMany({
|
||||||
|
where: { userId: teamId === '0' ? undefined : userId },
|
||||||
|
include: { team: { include: { _count: { select: { users: true } } } } }
|
||||||
|
});
|
||||||
|
|
||||||
|
const invitations = await db.prisma.teamInvitation.findMany({ where: { uid: userId } });
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
teams,
|
||||||
|
invitations,
|
||||||
|
account,
|
||||||
|
accounts
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorHandler(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const post: RequestHandler = async (event) => {
|
||||||
|
const { teamId, userId, status, body } = await getUserDetails(event);
|
||||||
|
if (status === 401) return { status, body };
|
||||||
|
if (teamId !== '0')
|
||||||
|
return { status: 401, body: { message: 'You are not authorized to perform this action' } };
|
||||||
|
|
||||||
|
const { id } = await event.request.json();
|
||||||
|
try {
|
||||||
|
const aloneInTeams = await db.prisma.team.findMany({ where: { users: { every: { id } } } });
|
||||||
|
if (aloneInTeams.length > 0) {
|
||||||
|
for (const team of aloneInTeams) {
|
||||||
|
const applications = await db.prisma.application.findMany({
|
||||||
|
where: { teams: { every: { id: team.id } } }
|
||||||
|
});
|
||||||
|
if (applications.length > 0) {
|
||||||
|
for (const application of applications) {
|
||||||
|
await db.prisma.application.update({
|
||||||
|
where: { id: application.id },
|
||||||
|
data: { teams: { connect: { id: '0' } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const services = await db.prisma.service.findMany({
|
||||||
|
where: { teams: { every: { id: team.id } } }
|
||||||
|
});
|
||||||
|
if (services.length > 0) {
|
||||||
|
for (const service of services) {
|
||||||
|
await db.prisma.service.update({
|
||||||
|
where: { id: service.id },
|
||||||
|
data: { teams: { connect: { id: '0' } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const databases = await db.prisma.database.findMany({
|
||||||
|
where: { teams: { every: { id: team.id } } }
|
||||||
|
});
|
||||||
|
if (databases.length > 0) {
|
||||||
|
for (const database of databases) {
|
||||||
|
await db.prisma.database.update({
|
||||||
|
where: { id: database.id },
|
||||||
|
data: { teams: { connect: { id: '0' } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sources = await db.prisma.gitSource.findMany({
|
||||||
|
where: { teams: { every: { id: team.id } } }
|
||||||
|
});
|
||||||
|
if (sources.length > 0) {
|
||||||
|
for (const source of sources) {
|
||||||
|
await db.prisma.gitSource.update({
|
||||||
|
where: { id: source.id },
|
||||||
|
data: { teams: { connect: { id: '0' } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const destinations = await db.prisma.destinationDocker.findMany({
|
||||||
|
where: { teams: { every: { id: team.id } } }
|
||||||
|
});
|
||||||
|
if (destinations.length > 0) {
|
||||||
|
for (const destination of destinations) {
|
||||||
|
await db.prisma.destinationDocker.update({
|
||||||
|
where: { id: destination.id },
|
||||||
|
data: { teams: { connect: { id: '0' } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await db.prisma.teamInvitation.deleteMany({ where: { teamId: team.id } });
|
||||||
|
await db.prisma.permission.deleteMany({ where: { teamId: team.id } });
|
||||||
|
await db.prisma.user.delete({ where: { id } });
|
||||||
|
await db.prisma.team.delete({ where: { id: team.id } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notAloneInTeams = await db.prisma.team.findMany({ where: { users: { some: { id } } } });
|
||||||
|
if (notAloneInTeams.length > 0) {
|
||||||
|
for (const team of notAloneInTeams) {
|
||||||
|
await db.prisma.team.update({
|
||||||
|
where: { id: team.id },
|
||||||
|
data: { users: { disconnect: { id } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: 201
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
status: 500
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
215
src/routes/iam/index.svelte
Normal file
215
src/routes/iam/index.svelte
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
<script context="module" lang="ts">
|
||||||
|
import type { Load } from '@sveltejs/kit';
|
||||||
|
export const load: Load = async ({ fetch }) => {
|
||||||
|
const url = `/iam.json`;
|
||||||
|
const res = await fetch(url);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...(await res.json())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (res.status === 401) {
|
||||||
|
return {
|
||||||
|
status: 302,
|
||||||
|
redirect: '/'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: res.status,
|
||||||
|
error: new Error(`Could not load ${url}`)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { session } from '$app/stores';
|
||||||
|
import { get, post } from '$lib/api';
|
||||||
|
import { errorNotification } from '$lib/form';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
|
|
||||||
|
export let account;
|
||||||
|
export let accounts;
|
||||||
|
export let invitations;
|
||||||
|
if (accounts.length === 0) {
|
||||||
|
accounts.push(account);
|
||||||
|
}
|
||||||
|
export let teams;
|
||||||
|
|
||||||
|
const ownTeams = teams.filter((team) => {
|
||||||
|
if (team.team.id === $session.teamId) {
|
||||||
|
return team;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const otherTeams = teams.filter((team) => {
|
||||||
|
if (team.team.id !== $session.teamId) {
|
||||||
|
return team;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function resetPassword(id) {
|
||||||
|
const sure = window.confirm('Are you sure you want to reset the password?');
|
||||||
|
if (!sure) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await post(`/iam/password.json`, { id });
|
||||||
|
toast.push('Password reset successfully. Please relogin to reset it.');
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function deleteUser(id) {
|
||||||
|
const sure = window.confirm('Are you sure you want to delete this user?');
|
||||||
|
if (!sure) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await post(`/iam.json`, { id });
|
||||||
|
toast.push('Account deleted.');
|
||||||
|
const data = await get('/iam.json');
|
||||||
|
accounts = data.accounts;
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function acceptInvitation(id, teamId) {
|
||||||
|
try {
|
||||||
|
await post(`/iam/team/${teamId}/invitation/accept.json`, { id });
|
||||||
|
return window.location.reload();
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function revokeInvitation(id, teamId) {
|
||||||
|
try {
|
||||||
|
await post(`/iam/team/${teamId}/invitation/revoke.json`, { id });
|
||||||
|
return window.location.reload();
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
|
<div class="mr-4 text-2xl tracking-tight">Identity and Access Management</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if invitations.length > 0}
|
||||||
|
<div class="mx-auto max-w-4xl px-6 py-4">
|
||||||
|
<div class="title font-bold">Pending invitations</div>
|
||||||
|
<div class="pt-10 text-center">
|
||||||
|
{#each invitations as invitation}
|
||||||
|
<div class="flex justify-center space-x-2">
|
||||||
|
<div>
|
||||||
|
Invited to <span class="font-bold text-pink-600">{invitation.teamName}</span> with
|
||||||
|
<span class="font-bold text-rose-600">{invitation.permission}</span> permission.
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="hover:bg-green-500"
|
||||||
|
on:click={() => acceptInvitation(invitation.id, invitation.teamId)}>Accept</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="hover:bg-red-600"
|
||||||
|
on:click={() => revokeInvitation(invitation.id, invitation.teamId)}>Delete</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="mx-auto max-w-4xl px-6 py-4">
|
||||||
|
{#if $session.teamId === '0' && accounts.length > 0}
|
||||||
|
<div class="title font-bold">Accounts</div>
|
||||||
|
{:else}
|
||||||
|
<div class="title font-bold">Account</div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex items-center justify-center pt-10">
|
||||||
|
<table class="mx-2 text-left">
|
||||||
|
<thead class="mb-2">
|
||||||
|
<tr>
|
||||||
|
{#if accounts.length > 1}
|
||||||
|
<th class="px-2">Email</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
{/if}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{#each accounts as account}
|
||||||
|
<tr>
|
||||||
|
<td class="px-2">{account.email}</td>
|
||||||
|
<td class="flex space-x-2">
|
||||||
|
<form on:submit|preventDefault={() => resetPassword(account.id)}>
|
||||||
|
<button
|
||||||
|
class="mx-auto my-4 w-32 bg-coollabs hover:bg-coollabs-100 disabled:bg-coolgray-200"
|
||||||
|
>Reset Password</button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
<form on:submit|preventDefault={() => deleteUser(account.id)}>
|
||||||
|
<button
|
||||||
|
disabled={account.id === $session.userId}
|
||||||
|
class="mx-auto my-4 w-32 bg-coollabs hover:bg-coollabs-100 disabled:bg-coolgray-200"
|
||||||
|
type="submit">Delete User</button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
|
<div class="title font-bold">Teams</div>
|
||||||
|
<div class="flex items-center justify-center pt-10">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 pb-10 md:flex-row">
|
||||||
|
{#each ownTeams as team}
|
||||||
|
<a href="/iam/team/{team.teamId}" class="w-96 p-2 no-underline">
|
||||||
|
<div
|
||||||
|
class="box-selection relative"
|
||||||
|
class:hover:bg-cyan-600={team.team?.id !== '0'}
|
||||||
|
class:hover:bg-red-500={team.team?.id === '0'}
|
||||||
|
>
|
||||||
|
<div class="truncate text-center text-xl font-bold">
|
||||||
|
{team.team.name}
|
||||||
|
</div>
|
||||||
|
<div class="truncate text-center font-bold">
|
||||||
|
{team.team?.id === '0' ? 'root team' : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-1 text-center">{team.team._count.users} member(s)</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if $session.teamId === '0' && otherTeams.length > 0}
|
||||||
|
<div class="pb-5 pt-10 text-xl font-bold">Other Teams</div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherTeams as team}
|
||||||
|
<a href="/iam/team/{team.teamId}" class="w-96 p-2 no-underline">
|
||||||
|
<div
|
||||||
|
class="box-selection relative"
|
||||||
|
class:hover:bg-cyan-600={team.team?.id !== '0'}
|
||||||
|
class:hover:bg-red-500={team.team?.id === '0'}
|
||||||
|
>
|
||||||
|
<div class="truncate text-center text-xl font-bold">
|
||||||
|
{team.team.name}
|
||||||
|
</div>
|
||||||
|
<div class="truncate text-center font-bold">
|
||||||
|
{team.team?.id === '0' ? 'root team' : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-1 text-center">{team.team._count.users} member(s)</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
22
src/routes/iam/password.json.ts
Normal file
22
src/routes/iam/password.json.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
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, userId, status, body } = await getUserDetails(event);
|
||||||
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
|
const { id } = await event.request.json();
|
||||||
|
try {
|
||||||
|
await db.prisma.user.update({ where: { id }, data: { password: 'RESETME' } });
|
||||||
|
return {
|
||||||
|
status: 201
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return {
|
||||||
|
status: 500
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
<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 }) => {
|
export const load: Load = async ({ fetch, params }) => {
|
||||||
const url = `/teams/${params.id}.json`;
|
const url = `/iam/team/${params.id}.json`;
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!data.permissions || Object.entries(data.permissions).length === 0) {
|
if (!data.permissions || Object.entries(data.permissions).length === 0) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
redirect: '/teams'
|
redirect: '/iam'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
redirect: '/teams'
|
redirect: '/iam'
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -4,14 +4,14 @@ import { ErrorHandler } from '$lib/database';
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const get: RequestHandler = async (event) => {
|
export const get: RequestHandler = async (event) => {
|
||||||
const { userId, status, body } = await getUserDetails(event, false);
|
const { teamId, userId, status, body } = await getUserDetails(event, false);
|
||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await db.prisma.user.findFirst({
|
const user = await db.prisma.user.findFirst({
|
||||||
where: { id: userId, teams: { some: { id } } },
|
where: { id: userId, teams: teamId === '0' ? undefined : { some: { id } } },
|
||||||
include: { permission: true }
|
include: { permission: true }
|
||||||
});
|
});
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -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 }) => {
|
export const load: Load = async ({ fetch, params }) => {
|
||||||
const url = `/teams/${params.id}.json`;
|
const url = `/iam/team/${params.id}.json`;
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
async function sendInvitation() {
|
async function sendInvitation() {
|
||||||
try {
|
try {
|
||||||
await post(`/teams/${id}/invitation/invite.json`, {
|
await post(`/iam/team/${id}/invitation/invite.json`, {
|
||||||
teamId: team.id,
|
teamId: team.id,
|
||||||
teamName: invitation.teamName,
|
teamName: invitation.teamName,
|
||||||
email: invitation.email.toLowerCase(),
|
email: invitation.email.toLowerCase(),
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
}
|
}
|
||||||
async function revokeInvitation(id: string) {
|
async function revokeInvitation(id: string) {
|
||||||
try {
|
try {
|
||||||
await post(`/teams/${id}/invitation/revoke.json`, { id });
|
await post(`/iam/team/${id}/invitation/revoke.json`, { id });
|
||||||
return window.location.reload();
|
return window.location.reload();
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
}
|
}
|
||||||
async function removeFromTeam(uid: string) {
|
async function removeFromTeam(uid: string) {
|
||||||
try {
|
try {
|
||||||
await post(`/teams/${id}/remove/user.json`, { teamId: team.id, uid });
|
await post(`/iam/team/${id}/remove/user.json`, { teamId: team.id, uid });
|
||||||
return window.location.reload();
|
return window.location.reload();
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
newPermission = 'admin';
|
newPermission = 'admin';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await post(`/teams/${id}/permission/change.json`, { userId, newPermission, permissionId });
|
await post(`/iam/team/${id}/permission/change.json`, { userId, newPermission, permissionId });
|
||||||
return window.location.reload();
|
return window.location.reload();
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
}
|
}
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
try {
|
try {
|
||||||
await post(`/teams/${id}.json`, { ...team });
|
await post(`/iam/team/${id}.json`, { ...team });
|
||||||
return window.location.reload();
|
return window.location.reload();
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="/teams"
|
href="/iam"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
class="flex cursor-pointer flex-col rounded p-6 text-center text-cyan-500 no-underline transition duration-150 hover:bg-cyan-500 hover:text-white"
|
class="flex cursor-pointer flex-col rounded p-6 text-center text-cyan-500 no-underline transition duration-150 hover:bg-cyan-500 hover:text-white"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { session } from '$app/stores';
|
||||||
|
|
||||||
export let payload;
|
export let payload;
|
||||||
|
|
||||||
@@ -56,13 +57,15 @@
|
|||||||
<label for="network" class="text-base font-bold text-stone-100">Network</label>
|
<label for="network" class="text-base font-bold text-stone-100">Network</label>
|
||||||
<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 class="grid grid-cols-2 items-center">
|
{#if $session.teamId === '0'}
|
||||||
<Setting
|
<div class="grid grid-cols-2 items-center">
|
||||||
bind:setting={payload.isCoolifyProxyUsed}
|
<Setting
|
||||||
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
|
bind:setting={payload.isCoolifyProxyUsed}
|
||||||
title="Use Coolify Proxy?"
|
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
|
||||||
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).<br><br>Databases will have their own proxy."
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,43 +1,22 @@
|
|||||||
import { asyncExecShell, getUserDetails } from '$lib/common';
|
import { getUserDetails } from '$lib/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import { dockerInstance } from '$lib/docker';
|
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import type { CreateDockerDestination } from '$lib/types/destinations';
|
||||||
|
|
||||||
export const post: RequestHandler = async (event) => {
|
export const post: RequestHandler = async (event) => {
|
||||||
const { teamId, status, body } = await getUserDetails(event);
|
const { teamId, status, body } = await getUserDetails(event);
|
||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const {
|
const dockerDestinationProps = {
|
||||||
name,
|
...((await event.request.json()) as Omit<CreateDockerDestination, 'teamId'>),
|
||||||
engine,
|
teamId
|
||||||
network,
|
};
|
||||||
isCoolifyProxyUsed,
|
|
||||||
remoteEngine,
|
|
||||||
ipAddress,
|
|
||||||
user,
|
|
||||||
port,
|
|
||||||
sshPrivateKey
|
|
||||||
} = await event.request.json();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let id = null;
|
const id = dockerDestinationProps.remoteEngine
|
||||||
if (remoteEngine) {
|
? await db.newRemoteDestination(dockerDestinationProps)
|
||||||
id = await db.newRemoteDestination({
|
: await db.newLocalDestination(dockerDestinationProps);
|
||||||
name,
|
|
||||||
teamId,
|
|
||||||
engine,
|
|
||||||
network,
|
|
||||||
isCoolifyProxyUsed,
|
|
||||||
remoteEngine,
|
|
||||||
ipAddress,
|
|
||||||
user,
|
|
||||||
port,
|
|
||||||
sshPrivateKey
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
id = await db.newLocalDestination({ name, teamId, engine, network, isCoolifyProxyUsed });
|
|
||||||
}
|
|
||||||
return { status: 200, body: { id } };
|
return { status: 200, body: { id } };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return ErrorHandler(error);
|
return ErrorHandler(error);
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let gitSource;
|
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import { post } from '$lib/api';
|
|
||||||
|
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
|
|
||||||
import { errorNotification } from '$lib/form';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
let nameEl;
|
|
||||||
let organizationEl;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
nameEl.focus();
|
|
||||||
});
|
|
||||||
async function handleSubmit() {
|
|
||||||
try {
|
|
||||||
const { id } = await post(`/new/source.json`, { ...gitSource });
|
|
||||||
return await goto(`/sources/${id}/`);
|
|
||||||
} catch ({ error }) {
|
|
||||||
return errorNotification(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
|
||||||
<div class="flex justify-center pb-8">
|
|
||||||
<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="text-xl font-bold text-white">Configuration</div>
|
|
||||||
<button type="submit" class="bg-orange-600 hover:bg-orange-500">Save</button>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<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}>
|
|
||||||
<option value="github">GitHub</option>
|
|
||||||
<option value="gitlab">GitLab</option>
|
|
||||||
<option value="bitbucket">BitBucket</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
|
||||||
<input
|
|
||||||
name="name"
|
|
||||||
id="name"
|
|
||||||
placeholder="GitHub.com"
|
|
||||||
required
|
|
||||||
bind:this={nameEl}
|
|
||||||
bind:value={gitSource.name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
|
|
||||||
<input
|
|
||||||
type="url"
|
|
||||||
name="htmlUrl"
|
|
||||||
id="htmlUrl"
|
|
||||||
placeholder="eg: https://github.com"
|
|
||||||
required
|
|
||||||
bind:value={gitSource.htmlUrl}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
|
||||||
<input
|
|
||||||
name="apiUrl"
|
|
||||||
type="url"
|
|
||||||
id="apiUrl"
|
|
||||||
placeholder="eg: https://api.github.com"
|
|
||||||
required
|
|
||||||
bind:value={gitSource.apiUrl}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 px-10">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
|
|
||||||
>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
|
|
||||||
name="organization"
|
|
||||||
id="organization"
|
|
||||||
placeholder="eg: coollabsio"
|
|
||||||
bind:value={gitSource.organization}
|
|
||||||
bind:this={organizationEl}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user