Merge branch 'arm' into improve-typing
This commit is contained in:
commit
45bf6f77d1
@ -16,7 +16,7 @@ # Recommended Pull Request Guideline
|
|||||||
- 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 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 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 . &
|
6
data/haproxy-http.Dockerfile
Normal file
6
data/haproxy-http.Dockerfile
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
FROM haproxytech/haproxy-alpine:2.5
|
||||||
|
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
||||||
|
|
||||||
|
COPY haproxy/haproxy.cfg-http.template /usr/local/etc/haproxy/haproxy.cfg
|
||||||
|
COPY haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
||||||
|
COPY haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
6
data/haproxy-tcp.Dockerfile
Normal file
6
data/haproxy-tcp.Dockerfile
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
FROM haproxytech/haproxy-alpine:2.5
|
||||||
|
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
||||||
|
|
||||||
|
COPY haproxy/haproxy.cfg-tcp.template /usr/local/etc/haproxy/haproxy.cfg
|
||||||
|
COPY haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
||||||
|
COPY haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
6
data/haproxy.Dockerfile
Normal file
6
data/haproxy.Dockerfile
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
FROM haproxytech/haproxy-alpine:2.5
|
||||||
|
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
||||||
|
|
||||||
|
COPY haproxy/haproxy.cfg.template /usr/local/etc/haproxy/haproxy.cfg
|
||||||
|
COPY haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
||||||
|
COPY haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
@ -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/
|
@ -1,6 +0,0 @@
|
|||||||
FROM haproxytech/haproxy-alpine:2.5
|
|
||||||
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
|
||||||
|
|
||||||
COPY data/haproxy/haproxy.cfg-http.template /usr/local/etc/haproxy/haproxy.cfg
|
|
||||||
COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
|
||||||
COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
|
@ -1,6 +0,0 @@
|
|||||||
FROM haproxytech/haproxy-alpine:2.5
|
|
||||||
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
|
||||||
|
|
||||||
COPY data/haproxy/haproxy.cfg-tcp.template /usr/local/etc/haproxy/haproxy.cfg
|
|
||||||
COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
|
||||||
COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
|
@ -1,6 +0,0 @@
|
|||||||
FROM haproxytech/haproxy-alpine:2.5
|
|
||||||
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
|
||||||
|
|
||||||
COPY data/haproxy/haproxy.cfg.template /usr/local/etc/haproxy/haproxy.cfg
|
|
||||||
COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
|
||||||
COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
|
40
package.json
40
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.4.0",
|
"version": "2.4.3",
|
||||||
"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,17 @@
|
|||||||
"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:staging:all": "cross-var docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify:$npm_package_version --push .",
|
||||||
"release:coolify": "cross-var yarn release:pre && docker push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
|
"release:staging:amd": "cross-var docker build --platform linux/amd64 -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:haproxy": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-alpine:latest -t coollabsio/coolify-haproxy-alpine:1.1.0 -f haproxy.Dockerfile --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: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 haproxy-tcp.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: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 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 +44,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 +61,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.79.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",
|
||||||
|
621
pnpm-lock.yaml
621
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -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
|
||||||
|
|
||||||
|
3
src/app.d.ts
vendored
3
src/app.d.ts
vendored
@ -15,6 +15,9 @@ declare namespace App {
|
|||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
source: string;
|
source: string;
|
||||||
settings: string;
|
settings: string;
|
||||||
|
database: Record<string, any>;
|
||||||
|
versions: string;
|
||||||
|
privatePort: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -61,12 +61,14 @@ export const saveBuildLog = async ({
|
|||||||
buildId: string;
|
buildId: string;
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}): Promise<Job> => {
|
}): Promise<Job> => {
|
||||||
if (line.includes('ghs_')) {
|
if (line) {
|
||||||
const regex = /ghs_.*@/g;
|
if (line.includes('ghs_')) {
|
||||||
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
const regex = /ghs_.*@/g;
|
||||||
|
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
||||||
|
}
|
||||||
|
const addTimestamp = `${generateTimestamp()} ${line}`;
|
||||||
|
return await buildLogQueue.add(buildId, { buildId, line: addTimestamp, applicationId });
|
||||||
}
|
}
|
||||||
const addTimestamp = `${generateTimestamp()} ${line}`;
|
|
||||||
return await buildLogQueue.add(buildId, { buildId, line: addTimestamp, applicationId });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTeam = (event: RequestEvent): string | null => {
|
export const getTeam = (event: RequestEvent): string | null => {
|
||||||
@ -105,6 +107,7 @@ export const getUserDetails = async (
|
|||||||
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 =
|
||||||
|
@ -56,7 +56,7 @@ export const supportedDatabaseTypesAndVersions = [
|
|||||||
name: 'postgresql',
|
name: 'postgresql',
|
||||||
fancyName: 'PostgreSQL',
|
fancyName: 'PostgreSQL',
|
||||||
baseImage: 'bitnami/postgresql',
|
baseImage: 'bitnami/postgresql',
|
||||||
versions: ['14.2', '13.6', '12.10', '11.15', '10.20']
|
versions: ['14.2.0', '13.6.0', '12.10.0 ', '11.15.0', '10.20.0']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'redis',
|
name: 'redis',
|
||||||
|
@ -70,7 +70,11 @@ export async function removeApplication({
|
|||||||
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({
|
export async function getApplicationWebhook({
|
||||||
|
@ -219,6 +219,7 @@ export function generateDatabaseConfiguration(database: Database & { settings: D
|
|||||||
return {
|
return {
|
||||||
privatePort: 5432,
|
privatePort: 5432,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
|
POSTGRESQL_POSTGRES_PASSWORD: rootUserPassword,
|
||||||
POSTGRESQL_PASSWORD: dbUserPassword,
|
POSTGRESQL_PASSWORD: dbUserPassword,
|
||||||
POSTGRESQL_USERNAME: dbUser,
|
POSTGRESQL_USERNAME: dbUser,
|
||||||
POSTGRESQL_DATABASE: defaultDatabase
|
POSTGRESQL_DATABASE: defaultDatabase
|
||||||
|
@ -165,3 +165,43 @@ export async function stopDatabase(
|
|||||||
}
|
}
|
||||||
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}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -64,9 +64,7 @@ export async function configureDestinationForDatabase({
|
|||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
if (type && version) {
|
if (type && version) {
|
||||||
const baseImage = getDatabaseImage(type);
|
const baseImage = getDatabaseImage(type);
|
||||||
await 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}" -`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,17 +33,12 @@ export async function newSource({
|
|||||||
}): Promise<GitSource> {
|
}): 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 }: { id: string }): Promise<void> {
|
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 }
|
||||||
@ -79,22 +74,29 @@ export async function getSource({
|
|||||||
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({
|
export async function addGitHubSource({ id, teamId, type, name, htmlUrl, apiUrl }) {
|
||||||
|
await prisma.gitSource.update({ where: { id }, data: { type, name, htmlUrl, apiUrl } });
|
||||||
|
return await prisma.githubApp.create({
|
||||||
|
data: {
|
||||||
|
teams: { connect: { id: teamId } },
|
||||||
|
gitSource: { connect: { id } }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export async function addGitLabSource({
|
||||||
id,
|
id,
|
||||||
appId,
|
|
||||||
teamId,
|
teamId,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
htmlUrl,
|
||||||
|
apiUrl,
|
||||||
oauthId,
|
oauthId,
|
||||||
groupName,
|
appId,
|
||||||
appSecret
|
appSecret,
|
||||||
}: {
|
groupName
|
||||||
id: string;
|
}) {
|
||||||
appId: string;
|
const encrptedAppSecret = encrypt(appSecret);
|
||||||
teamId: string;
|
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name } });
|
||||||
oauthId: number;
|
|
||||||
groupName: string;
|
|
||||||
appSecret: string;
|
|
||||||
}): Promise<GitlabApp> {
|
|
||||||
const encryptedAppSecret = encrypt(appSecret);
|
|
||||||
return await prisma.gitlabApp.create({
|
return await prisma.gitlabApp.create({
|
||||||
data: {
|
data: {
|
||||||
teams: { connect: { id: teamId } },
|
teams: { connect: { id: teamId } },
|
||||||
@ -128,6 +130,6 @@ export async function updateGitsource({
|
|||||||
}): Promise<GitSource> {
|
}): Promise<GitSource> {
|
||||||
return await prisma.gitSource.update({
|
return await prisma.gitSource.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { name }
|
data: { name, htmlUrl, apiUrl }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -177,7 +177,7 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} 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());
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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';
|
||||||
@ -45,27 +46,43 @@ export async function login({
|
|||||||
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') {
|
||||||
const passwordMatch = bcrypt.compare(password, userFound.password);
|
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);
|
||||||
if (!passwordMatch) {
|
if (!passwordMatch) {
|
||||||
throw {
|
throw {
|
||||||
error: 'Wrong password or email address.'
|
error: 'Wrong password or email address.'
|
||||||
|
@ -19,7 +19,7 @@ export default async function ({
|
|||||||
repodir: string;
|
repodir: string;
|
||||||
privateSshKey: string;
|
privateSshKey: string;
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
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`);
|
||||||
|
@ -3,6 +3,7 @@ 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 { supportedServiceTypesAndVersions } from '$lib/components/common';
|
||||||
|
|
||||||
@ -182,12 +183,41 @@ export async function generateSSLCerts(): Promise<void> {
|
|||||||
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$/, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
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 {
|
||||||
|
console.log('Generating SSL for', ssl.domain);
|
||||||
|
await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
||||||
|
}
|
||||||
} 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 {
|
||||||
|
console.log('Generating SSL for', ssl.domain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
@ -434,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-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"
|
||||||
@ -457,6 +456,7 @@
|
|||||||
<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
|
||||||
@ -484,6 +484,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-red-500"
|
class="icons tooltip-right bg-coolgray-200 hover:text-red-500"
|
||||||
data-tooltip="Logout"
|
data-tooltip="Logout"
|
||||||
@ -525,7 +526,7 @@
|
|||||||
{/if}
|
{/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}
|
||||||
>
|
>
|
||||||
|
@ -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 text-center">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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -61,9 +61,6 @@
|
|||||||
await refreshSecrets();
|
await refreshSecrets();
|
||||||
toast.push('Secrets saved');
|
toast.push('Secrets saved');
|
||||||
}
|
}
|
||||||
function asd() {
|
|
||||||
console.log(secrets);
|
|
||||||
}
|
|
||||||
</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">
|
||||||
@ -164,7 +161,6 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<button on:click={asd}>Save</button>
|
|
||||||
<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|preventDefault={getValues} class="mb-12 w-full">
|
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
|
||||||
<textarea bind:value={batchSecrets} class="mb-2 min-h-[200px] w-full" />
|
<textarea bind:value={batchSecrets} class="mb-2 min-h-[200px] w-full" />
|
||||||
|
@ -1,70 +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';
|
|
||||||
import { session } from '$app/stores';
|
|
||||||
|
|
||||||
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 $session.teamId === '0'}
|
|
||||||
<div class="truncate text-center">Team {application.teams[0].name}</div>
|
|
||||||
{/if}
|
|
||||||
{#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>
|
||||||
|
@ -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,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} />
|
||||||
|
@ -14,6 +14,16 @@
|
|||||||
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">
|
||||||
@ -35,43 +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 $session.teamId === '0'}
|
<div class="truncate text-center text-xl font-bold">
|
||||||
<div class="text-center truncate">Team {database.teams[0].name}</div>
|
{database.name}
|
||||||
{/if}
|
</div>
|
||||||
{#if !database.type}
|
{#if $session.teamId === '0' && otherDatabases.length > 0}
|
||||||
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
<div class="truncate text-center">{database.teams[0].name}</div>
|
||||||
Configuration missing
|
{/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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,6 +184,7 @@
|
|||||||
{#if $session.teamId === '0'}
|
{#if $session.teamId === '0'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
loading={loadingProxy}
|
||||||
disabled={cannotDisable}
|
disabled={cannotDisable}
|
||||||
bind:setting={destination.isCoolifyProxyUsed}
|
bind:setting={destination.isCoolifyProxyUsed}
|
||||||
on:click={changeProxySetting}
|
on:click={changeProxySetting}
|
||||||
|
@ -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,23 +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">
|
||||||
{#if $session.teamId === '0'}
|
<div class="box-selection hover:bg-sky-600">
|
||||||
<div class="text-center truncate">Team {destination.teams[0].name}</div>
|
<div class="truncate text-center text-xl font-bold">{destination.name}</div>
|
||||||
{/if}
|
{#if $session.teamId === '0' && otherDestinations.length > 0}
|
||||||
<div class="text-center truncate">{destination.network}</div>
|
<div class="truncate text-center">{destination.teams[0].name}</div>
|
||||||
</div>
|
{/if}
|
||||||
</a>
|
<div class="truncate text-center">{destination.network}</div>
|
||||||
{/each}
|
</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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
175
src/routes/iam/index.svelte
Normal file
175
src/routes/iam/index.svelte
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<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;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<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>
|
@ -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,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>
|
|
@ -1,73 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let gitSource;
|
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import { post } from '$lib/api';
|
|
||||||
|
|
||||||
import { errorNotification } from '$lib/form';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
let nameEl;
|
|
||||||
|
|
||||||
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="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>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
@ -1,66 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Github from './_Github.svelte';
|
|
||||||
import Gitlab from './_Gitlab.svelte';
|
|
||||||
let gitSource = {
|
|
||||||
name: undefined,
|
|
||||||
type: 'github',
|
|
||||||
htmlUrl: undefined,
|
|
||||||
apiUrl: undefined,
|
|
||||||
organization: undefined
|
|
||||||
};
|
|
||||||
function setPredefined(type) {
|
|
||||||
switch (type) {
|
|
||||||
case 'github':
|
|
||||||
gitSource = {
|
|
||||||
name: 'GitHub.com',
|
|
||||||
type,
|
|
||||||
htmlUrl: 'https://github.com',
|
|
||||||
apiUrl: 'https://api.github.com',
|
|
||||||
organization: undefined
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'gitlab':
|
|
||||||
gitSource = {
|
|
||||||
name: 'GitLab.com',
|
|
||||||
type,
|
|
||||||
htmlUrl: 'https://gitlab.com',
|
|
||||||
apiUrl: 'https://gitlab.com/api',
|
|
||||||
organization: undefined
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'bitbucket':
|
|
||||||
gitSource = {
|
|
||||||
name: 'BitBucket.com',
|
|
||||||
type,
|
|
||||||
htmlUrl: 'https://bitbucket.com',
|
|
||||||
apiUrl: 'https://bitbucket.com',
|
|
||||||
organization: undefined
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
|
||||||
<div class="mr-4 text-2xl tracking-tight">Add New Git Source</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-col space-y-2 pb-10 text-center">
|
|
||||||
<div class="text-xl font-bold text-white">Official providers</div>
|
|
||||||
<div class="flex justify-center space-x-2">
|
|
||||||
<button class="w-32" on:click={() => setPredefined('github')}>GitHub.com</button>
|
|
||||||
<button class="w-32" on:click={() => setPredefined('gitlab')}>GitLab.com</button>
|
|
||||||
<button class="w-32" on:click={() => setPredefined('bitbucket')}>Bitbucket.com</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="px-6">
|
|
||||||
{#if gitSource.type === 'github'}
|
|
||||||
<Github {gitSource} />
|
|
||||||
{:else if gitSource.type === 'gitlab'}
|
|
||||||
<Gitlab {gitSource} />
|
|
||||||
{:else if gitSource.type === 'bitbucket'}
|
|
||||||
<div class="text-center font-bold text-4xl py-10">Not implemented yet</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
@ -93,7 +93,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="buildPack" class="text-base font-bold text-stone-100">Version / Tag</label>
|
<label for="version" class="text-base font-bold text-stone-100">Version / Tag</label>
|
||||||
<a
|
<a
|
||||||
href={$session.isAdmin
|
href={$session.isAdmin
|
||||||
? `/services/${id}/configuration/version?from=/services/${id}`
|
? `/services/${id}/configuration/version?from=/services/${id}`
|
||||||
|
@ -44,12 +44,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
const image = getServiceImage(type);
|
const image = getServiceImage(type);
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const config = {
|
const config = {
|
||||||
ghost: {
|
ghost: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
volume: `${id}-ghost:/bitnami/ghost`,
|
volume: `${id}-ghost:/bitnami/ghost`,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
|
url: fqdn,
|
||||||
GHOST_HOST: domain,
|
GHOST_HOST: domain,
|
||||||
|
GHOST_ENABLE_HTTPS: isHttps ? 'yes' : 'no',
|
||||||
GHOST_EMAIL: defaultEmail,
|
GHOST_EMAIL: defaultEmail,
|
||||||
GHOST_PASSWORD: defaultPassword,
|
GHOST_PASSWORD: defaultPassword,
|
||||||
GHOST_DATABASE_HOST: `${id}-mariadb`,
|
GHOST_DATABASE_HOST: `${id}-mariadb`,
|
||||||
|
@ -13,12 +13,23 @@
|
|||||||
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
||||||
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
|
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
|
import { getDomain } from '$lib/components/common';
|
||||||
|
|
||||||
export let services;
|
export let services;
|
||||||
async function newService() {
|
async function newService() {
|
||||||
const { id } = await post('/services/new', {});
|
const { id } = await post('/services/new', {});
|
||||||
return await goto(`/services/${id}`, { replaceState: true });
|
return await goto(`/services/${id}`, { replaceState: true });
|
||||||
}
|
}
|
||||||
|
const ownServices = services.filter((service) => {
|
||||||
|
if (service.teams[0].id === $session.teamId) {
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const otherServices = services.filter((service) => {
|
||||||
|
if (service.teams[0].id !== $session.teamId) {
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
@ -40,53 +51,109 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-col flex-wrap justify-center">
|
||||||
{#if !services || services.length === 0}
|
{#if !services || ownServices.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">No services found</div>
|
<div class="text-center text-xl font-bold">No services found</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{/if}
|
||||||
{#each services as service}
|
{#if ownServices.length > 0 || otherServices.length > 0}
|
||||||
<a href="/services/{service.id}" class="no-underline p-2 w-96">
|
<div class="flex flex-col">
|
||||||
<div class="box-selection relative hover:bg-pink-600 group">
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
{#if service.type === 'plausibleanalytics'}
|
{#each ownServices as service}
|
||||||
<PlausibleAnalytics isAbsolute />
|
<a href="/services/{service.id}" class="w-96 p-2 no-underline">
|
||||||
{:else if service.type === 'nocodb'}
|
<div class="box-selection group relative hover:bg-pink-600">
|
||||||
<NocoDb isAbsolute />
|
{#if service.type === 'plausibleanalytics'}
|
||||||
{:else if service.type === 'minio'}
|
<PlausibleAnalytics isAbsolute />
|
||||||
<MinIo isAbsolute />
|
{:else if service.type === 'nocodb'}
|
||||||
{:else if service.type === 'vscodeserver'}
|
<NocoDb isAbsolute />
|
||||||
<VsCodeServer isAbsolute />
|
{:else if service.type === 'minio'}
|
||||||
{:else if service.type === 'wordpress'}
|
<MinIo isAbsolute />
|
||||||
<Wordpress isAbsolute />
|
{:else if service.type === 'vscodeserver'}
|
||||||
{:else if service.type === 'vaultwarden'}
|
<VsCodeServer isAbsolute />
|
||||||
<VaultWarden isAbsolute />
|
{:else if service.type === 'wordpress'}
|
||||||
{:else if service.type === 'languagetool'}
|
<Wordpress isAbsolute />
|
||||||
<LanguageTool isAbsolute />
|
{:else if service.type === 'vaultwarden'}
|
||||||
{:else if service.type === 'n8n'}
|
<VaultWarden isAbsolute />
|
||||||
<N8n isAbsolute />
|
{:else if service.type === 'languagetool'}
|
||||||
{:else if service.type === 'uptimekuma'}
|
<LanguageTool isAbsolute />
|
||||||
<UptimeKuma isAbsolute />
|
{:else if service.type === 'n8n'}
|
||||||
{:else if service.type === 'ghost'}
|
<N8n isAbsolute />
|
||||||
<Ghost isAbsolute />
|
{:else if service.type === 'uptimekuma'}
|
||||||
{:else if service.type === 'meilisearch'}
|
<UptimeKuma isAbsolute />
|
||||||
<MeiliSearch isAbsolute />
|
{:else if service.type === 'ghost'}
|
||||||
{/if}
|
<Ghost isAbsolute />
|
||||||
<div class="font-bold text-xl text-center truncate">
|
{:else if service.type === 'meilisearch'}
|
||||||
{service.name}
|
<MeiliSearch isAbsolute />
|
||||||
</div>
|
{/if}
|
||||||
{#if $session.teamId === '0'}
|
<div class="truncate text-center text-xl font-bold">
|
||||||
<div class="text-center truncate">Team {service.teams[0].name}</div>
|
{service.name}
|
||||||
{/if}
|
</div>
|
||||||
{#if !service.type || !service.fqdn}
|
{#if $session.teamId === '0' && otherServices.length > 0}
|
||||||
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
<div class="truncate text-center">{service.teams[0].name}</div>
|
||||||
Configuration missing
|
{/if}
|
||||||
|
{#if service.fqdn}
|
||||||
|
<div class="truncate text-center">{getDomain(service.fqdn) || ''}</div>
|
||||||
|
{/if}
|
||||||
|
{#if !service.type || !service.fqdn}
|
||||||
|
<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">{service.type}</div>
|
{/each}
|
||||||
{/if}
|
</div>
|
||||||
|
{#if otherServices.length > 0 && $session.teamId === '0'}
|
||||||
|
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Services</div>
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherServices as service}
|
||||||
|
<a href="/services/{service.id}" class="w-96 p-2 no-underline">
|
||||||
|
<div class="box-selection group relative hover:bg-pink-600">
|
||||||
|
{#if service.type === 'plausibleanalytics'}
|
||||||
|
<PlausibleAnalytics isAbsolute />
|
||||||
|
{:else if service.type === 'nocodb'}
|
||||||
|
<NocoDb isAbsolute />
|
||||||
|
{:else if service.type === 'minio'}
|
||||||
|
<MinIo isAbsolute />
|
||||||
|
{:else if service.type === 'vscodeserver'}
|
||||||
|
<VsCodeServer isAbsolute />
|
||||||
|
{:else if service.type === 'wordpress'}
|
||||||
|
<Wordpress isAbsolute />
|
||||||
|
{:else if service.type === 'vaultwarden'}
|
||||||
|
<VaultWarden isAbsolute />
|
||||||
|
{:else if service.type === 'languagetool'}
|
||||||
|
<LanguageTool isAbsolute />
|
||||||
|
{:else if service.type === 'n8n'}
|
||||||
|
<N8n isAbsolute />
|
||||||
|
{:else if service.type === 'uptimekuma'}
|
||||||
|
<UptimeKuma isAbsolute />
|
||||||
|
{:else if service.type === 'ghost'}
|
||||||
|
<Ghost isAbsolute />
|
||||||
|
{:else if service.type === 'meilisearch'}
|
||||||
|
<MeiliSearch isAbsolute />
|
||||||
|
{/if}
|
||||||
|
<div class="truncate text-center text-xl font-bold">
|
||||||
|
{service.name}
|
||||||
|
</div>
|
||||||
|
{#if $session.teamId === '0'}
|
||||||
|
<div class="truncate text-center">{service.teams[0].name}</div>
|
||||||
|
{/if}
|
||||||
|
{#if service.fqdn}
|
||||||
|
<div class="truncate text-center">{getDomain(service.fqdn) || ''}</div>
|
||||||
|
{/if}
|
||||||
|
{#if !service.type || !service.fqdn}
|
||||||
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="text-center truncate">{service.type}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
{/if}
|
||||||
{/each}
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,9 +5,9 @@ import type { RequestHandler } from '@sveltejs/kit';
|
|||||||
import { promises as dns } from 'dns';
|
import { promises as dns } from 'dns';
|
||||||
|
|
||||||
export const get: RequestHandler = async (event) => {
|
export const get: RequestHandler = async (event) => {
|
||||||
const { status, body } = await getUserDetails(event);
|
const { teamId, status, body } = await getUserDetails(event);
|
||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
if (teamId !== '0') return { status: 401, body: { message: 'You are not an admin.' } };
|
||||||
try {
|
try {
|
||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
return {
|
return {
|
||||||
|
@ -11,7 +11,12 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (res.status === 401) {
|
||||||
|
return {
|
||||||
|
status: 302,
|
||||||
|
redirect: '/databases'
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
status: res.status,
|
status: res.status,
|
||||||
error: new Error(`Could not load ${url}`)
|
error: new Error(`Could not load ${url}`)
|
||||||
|
@ -3,13 +3,19 @@
|
|||||||
import { page, session } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
return await post(`/sources/${id}.json`, { name: source.name });
|
await post(`/sources/${id}.json`, {
|
||||||
|
name: source.name,
|
||||||
|
htmlUrl: source.htmlUrl.replace(/\/$/, ''),
|
||||||
|
apiUrl: source.apiUrl.replace(/\/$/, '')
|
||||||
|
});
|
||||||
|
toast.push('Settings saved.');
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -38,7 +44,18 @@
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
function newGithubApp() {
|
async function newGithubApp() {
|
||||||
|
loading = true;
|
||||||
|
try {
|
||||||
|
await post(`/sources/${id}/github.json`, {
|
||||||
|
type: 'github',
|
||||||
|
name: source.name,
|
||||||
|
htmlUrl: source.htmlUrl.replace(/\/$/, ''),
|
||||||
|
apiUrl: source.apiUrl.replace(/\/$/, '')
|
||||||
|
});
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
const left = screen.width / 2 - 1020 / 2;
|
const left = screen.width / 2 - 1020 / 2;
|
||||||
const top = screen.height / 2 - 618 / 2;
|
const top = screen.height / 2 - 618 / 2;
|
||||||
const newWindow = open(
|
const newWindow = open(
|
||||||
@ -59,31 +76,72 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !source.githubAppId}
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
<button on:click={newGithubApp}>Create new GitHub App</button>
|
{#if !source.githubAppId}
|
||||||
{:else if source.githubApp?.installationId}
|
<form on:submit|preventDefault={newGithubApp} class="py-4">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
<div class="flex space-x-1 pb-5 font-bold">
|
||||||
<div class="flex space-x-1 pb-5 font-bold">
|
<div class="title">General</div>
|
||||||
<div class="title">General</div>
|
|
||||||
{#if $session.isAdmin}
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class:bg-orange-600={!loading}
|
|
||||||
class:hover:bg-orange-500={!loading}
|
|
||||||
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
|
|
||||||
>
|
|
||||||
<button on:click|preventDefault={() => installRepositories(source)}
|
|
||||||
>Change GitHub App Settings</button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
|
||||||
<div class="grid grid-cols-2 items-center mt-2">
|
|
||||||
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
|
||||||
<input name="name" id="name" required bind:value={source.name} />
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
|
<div class="grid grid-flow-row gap-2">
|
||||||
|
<div class="mt-2 grid grid-cols-2 items-center">
|
||||||
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
|
<input name="name" id="name" required bind:value={source.name} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
|
||||||
|
<input name="htmlUrl" id="htmlUrl" required bind:value={source.htmlUrl} />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
||||||
|
<input name="apiUrl" id="apiUrl" required bind:value={source.apiUrl} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if source.apiUrl && source.htmlUrl && source.name}
|
||||||
|
<div class="text-center">
|
||||||
|
<button class=" mt-8 bg-orange-600" type="submit">Create new GitHub App</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</form>
|
||||||
|
{:else if source.githubApp?.installationId}
|
||||||
|
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||||
|
<div class="flex space-x-1 pb-5 font-bold">
|
||||||
|
<div class="title">General</div>
|
||||||
|
{#if $session.isAdmin}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class:bg-orange-600={!loading}
|
||||||
|
class:hover:bg-orange-500={!loading}
|
||||||
|
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
|
||||||
|
>
|
||||||
|
<button on:click|preventDefault={() => installRepositories(source)}
|
||||||
|
>Change GitHub App Settings</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
|
<div class="grid grid-flow-row gap-2">
|
||||||
|
<div class="mt-2 grid grid-cols-2 items-center">
|
||||||
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
|
<input name="name" id="name" required bind:value={source.name} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
|
||||||
|
<input name="htmlUrl" id="htmlUrl" required bind:value={source.htmlUrl} />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
||||||
|
<input name="apiUrl" id="apiUrl" required bind:value={source.apiUrl} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{:else}
|
||||||
|
<div class="text-center">
|
||||||
|
<button class=" bg-orange-600 mt-8" on:click={() => installRepositories(source)}
|
||||||
|
>Install Repositories</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
{/if}
|
||||||
{:else}
|
</div>
|
||||||
<button on:click={() => installRepositories(source)}>Install Repositories</button>
|
|
||||||
{/if}
|
|
||||||
|
@ -1,35 +1,73 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let source;
|
export let source;
|
||||||
|
export let settings;
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
import { enhance, errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
import { page, session } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
let url = browser ? (settings.fqdn ? settings.fqdn : window.location.origin) : '';
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
|
||||||
let oauthIdEl;
|
let oauthIdEl;
|
||||||
let payload = {
|
let applicationType;
|
||||||
oauthId: undefined,
|
if (!source.gitlabAppId) {
|
||||||
groupName: undefined,
|
source.gitlabApp = {
|
||||||
appId: undefined,
|
oauthId: null,
|
||||||
appSecret: undefined,
|
groupName: null,
|
||||||
applicationType: 'user'
|
appId: null,
|
||||||
};
|
appSecret: null
|
||||||
|
};
|
||||||
|
}
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
oauthIdEl && oauthIdEl.focus();
|
oauthIdEl && oauthIdEl.focus();
|
||||||
});
|
});
|
||||||
async function handleSubmitSave() {
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (loading) return;
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
if (!source.gitlabAppId) {
|
||||||
return await post(`/sources/${id}.json`, { name: source.name });
|
// New GitLab App
|
||||||
} catch ({ error }) {
|
try {
|
||||||
return errorNotification(error);
|
await post(`/sources/${id}/gitlab.json`, {
|
||||||
} finally {
|
type: 'gitlab',
|
||||||
loading = false;
|
name: source.name,
|
||||||
|
htmlUrl: source.htmlUrl.replace(/\/$/, ''),
|
||||||
|
apiUrl: source.apiUrl.replace(/\/$/, ''),
|
||||||
|
oauthId: source.gitlabApp.oauthId,
|
||||||
|
appId: source.gitlabApp.appId,
|
||||||
|
appSecret: source.gitlabApp.appSecret,
|
||||||
|
groupName: source.gitlabApp.groupName
|
||||||
|
});
|
||||||
|
return window.location.reload();
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Update GitLab App
|
||||||
|
try {
|
||||||
|
await post(`/sources/${id}.json`, {
|
||||||
|
name: source.name,
|
||||||
|
htmlUrl: source.htmlUrl.replace(/\/$/, ''),
|
||||||
|
apiUrl: source.apiUrl.replace(/\/$/, '')
|
||||||
|
});
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
toast.push('Settings saved.');
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changeSettings() {
|
async function changeSettings() {
|
||||||
const {
|
const {
|
||||||
htmlUrl,
|
htmlUrl,
|
||||||
@ -53,23 +91,27 @@
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
async function checkOauthId() {
|
async function checkOauthId() {
|
||||||
if (payload.oauthId) {
|
if (source.gitlabApp?.oauthId) {
|
||||||
try {
|
try {
|
||||||
await post(`/sources/${id}/check.json`, { oauthId: payload.oauthId });
|
await post(`/sources/${id}/check.json`, {
|
||||||
|
oauthId: source.gitlabApp?.oauthId
|
||||||
|
});
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
payload.oauthId = null;
|
source.gitlabApp.oauthId = null;
|
||||||
oauthIdEl.focus();
|
oauthIdEl.focus();
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function newApp() {
|
function newApp() {
|
||||||
switch (payload.applicationType) {
|
switch (applicationType) {
|
||||||
case 'user':
|
case 'user':
|
||||||
window.open(`${source.htmlUrl}/-/profile/applications`);
|
window.open(`${source.htmlUrl}/-/profile/applications`);
|
||||||
break;
|
break;
|
||||||
case 'group':
|
case 'group':
|
||||||
window.open(`${source.htmlUrl}/groups/${payload.groupName}/-/settings/applications`);
|
window.open(
|
||||||
|
`${source.htmlUrl}/groups/${source.gitlabApp.groupName}/-/settings/applications`
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'instance':
|
case 'instance':
|
||||||
break;
|
break;
|
||||||
@ -77,130 +119,136 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function handleSubmit() {
|
|
||||||
loading = true;
|
|
||||||
try {
|
|
||||||
await post(`/sources/${id}/gitlab.json`, { ...payload });
|
|
||||||
return window.location.reload();
|
|
||||||
} catch ({ error }) {
|
|
||||||
return errorNotification(error);
|
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !source.gitlabApp?.appId}
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
<div>
|
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||||
<form class="grid grid-flow-row gap-2 py-4" on:submit|preventDefault={newApp}>
|
<div class="flex space-x-1 pb-5 font-bold">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="title">General</div>
|
||||||
<label for="type">GitLab Application Type</label>
|
{#if $session.isAdmin}
|
||||||
<select name="type" id="type" class="w-96" bind:value={payload.applicationType}>
|
|
||||||
<option value="user">User owned application</option>
|
|
||||||
<option value="group">Group owned application</option>
|
|
||||||
{#if source.htmlUrl !== 'https://gitlab.com'}
|
|
||||||
<option value="instance">Instance-wide application (self-hosted)</option>
|
|
||||||
{/if}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
{#if payload.applicationType === 'group'}
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<label for="groupName">Group Name</label>
|
|
||||||
<input name="groupName" id="groupName" required bind:value={payload.groupName} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="w-full pt-10 text-center">
|
|
||||||
<button class="w-96 bg-orange-600 hover:bg-orange-500" type="submit"
|
|
||||||
>Register new OAuth application on GitLab</button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Explainer
|
|
||||||
customClass="w-full"
|
|
||||||
text="<span class='font-bold text-base text-white'>Scopes required:</span>
|
|
||||||
<br>- <span class='text-orange-500 font-bold'>api</span> (Access the authenticated user's API)
|
|
||||||
<br>- <span class='text-orange-500 font-bold'>read_repository</span> (Allows read-only access to the repository)
|
|
||||||
<br>- <span class='text-orange-500 font-bold'>email</span> (Allows read-only access to the user's primary email address using OpenID Connect)
|
|
||||||
<br>
|
|
||||||
<br>For extra security, you can set Expire access tokens!
|
|
||||||
<br><br>Webhook URL: <span class='text-orange-500 font-bold'>{browser
|
|
||||||
? window.location.origin
|
|
||||||
: ''}/webhooks/gitlab</span>
|
|
||||||
<br>But if you will set a custom domain name for Coolify, use that instead."
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4 pt-10">
|
|
||||||
<div class="flex h-8 items-center space-x-2">
|
|
||||||
<div class="text-xl font-bold text-white">Configuration</div>
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class:bg-orange-600={!loading}
|
class:bg-orange-600={!loading}
|
||||||
class:hover:bg-orange-500={!loading}
|
class:hover:bg-orange-500={!loading}
|
||||||
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
|
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
|
||||||
>
|
>
|
||||||
</div>
|
{#if source.gitlabAppId}
|
||||||
|
<button on:click|preventDefault={changeSettings}>Change GitLab App Settings</button>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
|
{#if !source.gitlabAppId}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="type" class="text-base font-bold text-stone-100"
|
||||||
|
>GitLab Application Type</label
|
||||||
|
>
|
||||||
|
<select name="type" id="type" class="w-96" bind:value={applicationType}>
|
||||||
|
<option value="user">User owned application</option>
|
||||||
|
<option value="group">Group owned application</option>
|
||||||
|
{#if source.htmlUrl !== 'https://gitlab.com'}
|
||||||
|
<option value="instance">Instance-wide application (self-hosted)</option>
|
||||||
|
{/if}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-start">
|
{#if applicationType === 'group'}
|
||||||
<div class="flex-col">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="oauthId" class="pt-2">OAuth ID</label>
|
<label for="groupName" class="text-base font-bold text-stone-100">Group Name</label>
|
||||||
<Explainer
|
<input
|
||||||
text="The OAuth ID is the unique identifier of the GitLab application. <br>You can find it <span class='font-bold text-orange-600' >in the URL</span> of your GitLab OAuth Application."
|
name="groupName"
|
||||||
|
id="groupName"
|
||||||
|
required
|
||||||
|
bind:value={source.gitlabApp.groupName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="grid grid-flow-row gap-2">
|
||||||
|
<div class="mt-2 grid grid-cols-2 items-center">
|
||||||
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
|
<input name="name" id="name" required bind:value={source.name} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if source.gitlabApp.groupName}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="groupName" class="text-base font-bold text-stone-100">Group Name</label>
|
||||||
|
<input
|
||||||
|
name="groupName"
|
||||||
|
id="groupName"
|
||||||
|
disabled={source.gitlabAppId}
|
||||||
|
readonly={source.gitlabAppId}
|
||||||
|
required
|
||||||
|
bind:value={source.gitlabApp.groupName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
|
||||||
|
<input name="htmlUrl" id="htmlUrl" required bind:value={source.htmlUrl} />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
||||||
|
<input name="apiUrl" id="apiUrl" required bind:value={source.apiUrl} />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-start">
|
||||||
|
<div class="flex-col">
|
||||||
|
<label for="oauthId" class="pt-2 text-base font-bold text-stone-100">OAuth ID</label>
|
||||||
|
{#if !source.gitlabAppId}
|
||||||
|
<Explainer
|
||||||
|
text="The OAuth ID is the unique identifier of the GitLab application. <br>You can find it <span class='font-bold text-orange-600' >in the URL</span> of your GitLab OAuth Application."
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
|
disabled={source.gitlabAppId}
|
||||||
|
readonly={source.gitlabAppId}
|
||||||
on:change={checkOauthId}
|
on:change={checkOauthId}
|
||||||
bind:this={oauthIdEl}
|
bind:this={oauthIdEl}
|
||||||
name="oauthId"
|
name="oauthId"
|
||||||
id="oauthId"
|
id="oauthId"
|
||||||
type="number"
|
type="number"
|
||||||
required
|
required
|
||||||
bind:value={payload.oauthId}
|
bind:value={source.gitlabApp.oauthId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if payload.applicationType === 'group'}
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<label for="groupName">Group Name</label>
|
|
||||||
<input name="groupName" id="groupName" required bind:value={payload.groupName} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="appId">Application ID</label>
|
<label for="appId" class="text-base font-bold text-stone-100">Application ID</label>
|
||||||
<input name="appId" id="appId" required bind:value={payload.appId} />
|
<input
|
||||||
|
name="appId"
|
||||||
|
id="appId"
|
||||||
|
disabled={source.gitlabAppId}
|
||||||
|
readonly={source.gitlabAppId}
|
||||||
|
required
|
||||||
|
bind:value={source.gitlabApp.appId}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="appSecret">Secret</label>
|
<label for="appSecret" class="text-base font-bold text-stone-100">Secret</label>
|
||||||
<input
|
<CopyPasswordField
|
||||||
|
disabled={source.gitlabAppId}
|
||||||
|
readonly={source.gitlabAppId}
|
||||||
|
isPasswordField={true}
|
||||||
name="appSecret"
|
name="appSecret"
|
||||||
id="appSecret"
|
id="appSecret"
|
||||||
type="password"
|
|
||||||
required
|
required
|
||||||
bind:value={payload.appSecret}
|
bind:value={source.gitlabApp.appSecret}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
{:else}
|
{#if !source.gitlabAppId}
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
<Explainer
|
||||||
<form on:submit|preventDefault={handleSubmitSave} class="py-4">
|
customClass="w-full"
|
||||||
<div class="flex space-x-1 pb-5 font-bold">
|
text="<span class='font-bold text-base text-white'>Scopes required:</span>
|
||||||
<div class="title">General</div>
|
<br>- <span class='text-orange-500 font-bold'>api</span> (Access the authenticated user's API)
|
||||||
{#if $session.isAdmin}
|
<br>- <span class='text-orange-500 font-bold'>read_repository</span> (Allows read-only access to the repository)
|
||||||
<button
|
<br>- <span class='text-orange-500 font-bold'>email</span> (Allows read-only access to the user's primary email address using OpenID Connect)
|
||||||
type="submit"
|
<br>
|
||||||
class:bg-orange-600={!loading}
|
<br>For extra security, you can set <span class='text-orange-500 font-bold'>Expire Access Tokens</span>
|
||||||
class:hover:bg-orange-500={!loading}
|
<br><br>Webhook URL: <span class='text-orange-500 font-bold'>{url}/webhooks/gitlab</span>"
|
||||||
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
|
/>
|
||||||
>
|
{/if}
|
||||||
<button on:click|preventDefault={changeSettings}>Change GitLab App Settings</button>
|
</div>
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
|
||||||
<div class="mt-2 grid grid-cols-2 items-center">
|
|
||||||
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
|
||||||
<input name="name" id="name" required bind:value={source.name} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
18
src/routes/sources/[id]/github.json.ts
Normal file
18
src/routes/sources/[id]/github.json.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { getUserDetails } from '$lib/common';
|
||||||
|
import * as db from '$lib/database';
|
||||||
|
import { ErrorHandler } from '$lib/database';
|
||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const post: RequestHandler = async (event) => {
|
||||||
|
const { teamId, status, body } = await getUserDetails(event);
|
||||||
|
if (status === 401) return { status, body };
|
||||||
|
const { id } = event.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let { type, name, htmlUrl, apiUrl } = await event.request.json();
|
||||||
|
await db.addGitHubSource({ id, teamId, type, name, htmlUrl, apiUrl });
|
||||||
|
return { status: 201 };
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorHandler(error);
|
||||||
|
}
|
||||||
|
};
|
@ -9,11 +9,23 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let { oauthId, groupName, appId, appSecret } = await event.request.json();
|
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName } =
|
||||||
|
await event.request.json();
|
||||||
|
|
||||||
oauthId = Number(oauthId);
|
oauthId = Number(oauthId);
|
||||||
|
|
||||||
await db.addSource({ id, teamId, oauthId, groupName, appId, appSecret });
|
await db.addGitLabSource({
|
||||||
|
id,
|
||||||
|
teamId,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
htmlUrl,
|
||||||
|
apiUrl,
|
||||||
|
oauthId,
|
||||||
|
appId,
|
||||||
|
appSecret,
|
||||||
|
groupName
|
||||||
|
});
|
||||||
return { status: 201 };
|
return { status: 201 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return ErrorHandler(error);
|
return ErrorHandler(error);
|
||||||
|
@ -43,10 +43,10 @@ export const post: RequestHandler = async (event) => {
|
|||||||
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
|
|
||||||
const { name } = await event.request.json();
|
const { name, htmlUrl, apiUrl } = await event.request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.updateGitsource({ id, name });
|
await db.updateGitsource({ id, name, htmlUrl, apiUrl });
|
||||||
return { status: 201 };
|
return { status: 201 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return ErrorHandler(error);
|
return ErrorHandler(error);
|
||||||
|
@ -29,9 +29,41 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let source: Prisma.GitSource;
|
export let source: Prisma.GitSource;
|
||||||
|
export let settings;
|
||||||
import type Prisma from '@prisma/client';
|
import type Prisma from '@prisma/client';
|
||||||
import Github from './_Github.svelte';
|
import Github from './_Github.svelte';
|
||||||
import Gitlab from './_Gitlab.svelte';
|
import Gitlab from './_Gitlab.svelte';
|
||||||
|
|
||||||
|
function setPredefined(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'github':
|
||||||
|
source.name = 'Github.com';
|
||||||
|
source.type = 'github';
|
||||||
|
source.htmlUrl = 'https://github.com';
|
||||||
|
source.apiUrl = 'https://api.github.com';
|
||||||
|
source.organization = undefined;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'gitlab':
|
||||||
|
source.name = 'Gitlab.com';
|
||||||
|
source.type = 'gitlab';
|
||||||
|
source.htmlUrl = 'https://gitlab.com';
|
||||||
|
source.apiUrl = 'https://gitlab.com/api';
|
||||||
|
source.organization = undefined;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'bitbucket':
|
||||||
|
source.name = 'Bitbucket.com';
|
||||||
|
source.type = 'bitbucket';
|
||||||
|
source.htmlUrl = 'https://bitbucket.com';
|
||||||
|
source.apiUrl = 'https://api.bitbucket.org';
|
||||||
|
source.organization = undefined;
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 px-6 text-2xl font-bold">
|
<div class="flex space-x-1 p-6 px-6 text-2xl font-bold">
|
||||||
@ -40,10 +72,21 @@
|
|||||||
<span class="pr-2">{source.name}</span>
|
<span class="pr-2">{source.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center px-6 pb-8">
|
<div class="flex flex-col justify-center">
|
||||||
{#if source.type === 'github'}
|
{#if !source.gitlabAppId && !source.githubAppId}
|
||||||
<Github bind:source />
|
<div class="flex-col space-y-2 pb-10 text-center">
|
||||||
{:else if source.type === 'gitlab'}
|
<div class="text-xl font-bold text-white">Select a provider</div>
|
||||||
<Gitlab bind:source />
|
<div class="flex justify-center space-x-2">
|
||||||
|
<button class="w-32" on:click={() => setPredefined('github')}>GitHub.com</button>
|
||||||
|
<button class="w-32" on:click={() => setPredefined('gitlab')}>GitLab.com</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div>
|
||||||
|
{#if source.type === 'github'}
|
||||||
|
<Github bind:source />
|
||||||
|
{:else if source.type === 'gitlab'}
|
||||||
|
<Gitlab bind:source {settings} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
export let settings;
|
export let settings;
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const { organization, id, htmlUrl } = source;
|
const { organization, id, htmlUrl } = source;
|
||||||
|
console.log(source);
|
||||||
const { fqdn } = settings;
|
const { fqdn } = settings;
|
||||||
const host = dev
|
const host = dev
|
||||||
? 'http://localhost:3000'
|
? 'http://localhost:3000'
|
||||||
|
@ -22,12 +22,29 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let sources;
|
export let sources;
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
|
import { post } from '$lib/api';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { getDomain } from '$lib/components/common';
|
||||||
|
const ownSources = sources.filter((source) => {
|
||||||
|
if (source.teams[0].id === $session.teamId) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const otherSources = sources.filter((source) => {
|
||||||
|
if (source.teams[0].id !== $session.teamId) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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">Git Sources</div>
|
<div class="mr-4 text-2xl tracking-tight">Git Sources</div>
|
||||||
{#if $session.isAdmin}
|
{#if $session.isAdmin}
|
||||||
<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"
|
||||||
@ -41,38 +58,70 @@
|
|||||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||||
/></svg
|
/></svg
|
||||||
>
|
>
|
||||||
</a>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex flex-col flex-wrap justify-center">
|
||||||
{#if !sources || sources.length === 0}
|
{#if !sources || ownSources.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">No git sources found</div>
|
<div class="text-center text-xl font-bold">No git sources found</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{/if}
|
||||||
<div class="flex flex-wrap justify-center">
|
{#if ownSources.length > 0 || otherSources.length > 0}
|
||||||
{#each sources as source}
|
<div class="flex flex-col">
|
||||||
<a href="/sources/{source.id}" class="no-underline p-2 w-96">
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
<div
|
{#each ownSources as source}
|
||||||
class="box-selection hover:bg-orange-600 group"
|
<a href="/sources/{source.id}" class="w-96 p-2 no-underline">
|
||||||
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
<div
|
||||||
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
class="box-selection group hover:bg-orange-600"
|
||||||
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
||||||
>
|
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
||||||
<div class="font-bold text-xl text-center truncate">{source.name}</div>
|
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
||||||
{#if $session.teamId === '0'}
|
>
|
||||||
<div class="text-center truncate">Team {source.teams[0].name}</div>
|
<div class="truncate text-center text-xl font-bold">{source.name}</div>
|
||||||
{/if}
|
{#if $session.teamId === '0' && otherSources.length > 0}
|
||||||
{#if (source.type === 'gitlab' && !source.gitlabAppId) || (source.type === 'github' && !source.githubAppId && !source.githubApp?.installationId)}
|
<div class="truncate text-center">{source.teams[0].name}</div>
|
||||||
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
{/if}
|
||||||
Configuration missing
|
|
||||||
|
{#if (source.type === 'gitlab' && !source.gitlabAppId) || (source.type === 'github' && source.githubApp?.installationId === null)}
|
||||||
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="truncate text-center">{getDomain(source.htmlUrl) || ''}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if otherSources.length > 0 && $session.teamId === '0'}
|
||||||
|
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Sources</div>
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherSources as source}
|
||||||
|
<a href="/sources/{source.id}" class="w-96 p-2 no-underline">
|
||||||
|
<div
|
||||||
|
class="box-selection group hover:bg-orange-600"
|
||||||
|
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="truncate text-center text-xl font-bold">{source.name}</div>
|
||||||
|
{#if $session.teamId === '0'}
|
||||||
|
<div class="truncate text-center">{source.teams[0].name}</div>
|
||||||
|
{/if}
|
||||||
|
{#if (source.type === 'gitlab' && !source.gitlabAppId) || (source.type === 'github' && source.githubApp?.installationId === null)}
|
||||||
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="truncate text-center">{source.htmlUrl}</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
</a>
|
||||||
<div class="truncate text-center">{source.htmlUrl}</div>
|
{/each}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getUserDetails } from '$lib/common';
|
import { getUserDetails, uniqueName } 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';
|
||||||
@ -7,9 +7,9 @@ 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 { name, type, htmlUrl, apiUrl, organization } = await event.request.json();
|
const name = uniqueName();
|
||||||
try {
|
try {
|
||||||
const { id } = await db.newSource({ name, teamId, type, htmlUrl, apiUrl, organization });
|
const { id } = await db.newSource({ teamId, name });
|
||||||
return { status: 201, body: { id } };
|
return { status: 201, body: { id } };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return ErrorHandler(e);
|
return ErrorHandler(e);
|
@ -1,27 +0,0 @@
|
|||||||
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, false);
|
|
||||||
if (status === 401) return { status, body };
|
|
||||||
|
|
||||||
try {
|
|
||||||
const teams = await db.prisma.permission.findMany({
|
|
||||||
where: { userId: teamId === '0' ? undefined : teamId },
|
|
||||||
include: { team: { include: { _count: { select: { users: true } } } } }
|
|
||||||
});
|
|
||||||
|
|
||||||
const invitations = await db.prisma.teamInvitation.findMany({ where: { uid: userId } });
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
teams,
|
|
||||||
invitations
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorHandler(error);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,111 +0,0 @@
|
|||||||
<script context="module" lang="ts">
|
|
||||||
import type { Load } from '@sveltejs/kit';
|
|
||||||
export const load: Load = async ({ fetch }) => {
|
|
||||||
const url = `/teams.json`;
|
|
||||||
const res = await fetch(url);
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
...(await res.json())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: res.status,
|
|
||||||
error: new Error(`Could not load ${url}`)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { errorNotification } from '$lib/form';
|
|
||||||
import { session } from '$app/stores';
|
|
||||||
import { post } from '$lib/api';
|
|
||||||
|
|
||||||
export let teams;
|
|
||||||
export let invitations;
|
|
||||||
|
|
||||||
async function acceptInvitation(id, teamId) {
|
|
||||||
try {
|
|
||||||
await post(`/teams/${teamId}/invitation/accept.json`, { id });
|
|
||||||
return window.location.reload();
|
|
||||||
} catch ({ error }) {
|
|
||||||
return errorNotification(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function revokeInvitation(id, teamId) {
|
|
||||||
try {
|
|
||||||
await post(`/teams/${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">Teams</div>
|
|
||||||
{#if $session.isAdmin}
|
|
||||||
<a href="/new/team" class="add-icon bg-cyan-600 hover:bg-cyan-500">
|
|
||||||
<svg
|
|
||||||
class="w-6"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
><path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
|
||||||
/></svg
|
|
||||||
>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if invitations.length > 0}
|
|
||||||
<div class="mx-auto max-w-2xl pb-10">
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
|
||||||
<div class="title">Pending invitations</div>
|
|
||||||
</div>
|
|
||||||
<div class="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="flex flex-wrap justify-center">
|
|
||||||
{#each teams as team}
|
|
||||||
<a href="/teams/{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}
|
|
||||||
{team.team?.id === '0' ? '(root)' : ''}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-1 text-center">{team.team._count.users} member(s)</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
@ -22,6 +22,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const allowedActions = ['opened', 'reopen', 'close', 'open', 'update'];
|
const allowedActions = ['opened', 'reopen', 'close', 'open', 'update'];
|
||||||
const body = await event.request.json();
|
const body = await event.request.json();
|
||||||
const buildId = cuid();
|
const buildId = cuid();
|
||||||
|
const webhookToken = event.request.headers.get('x-gitlab-token');
|
||||||
|
if (!webhookToken) {
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
body: {
|
||||||
|
message: 'Ooops, something is not okay, are you okay?'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const { object_kind: objectKind } = body;
|
const { object_kind: objectKind } = body;
|
||||||
if (objectKind === 'push') {
|
if (objectKind === 'push') {
|
||||||
@ -77,16 +86,6 @@ export const post: RequestHandler = async (event) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (objectKind === 'merge_request') {
|
} else if (objectKind === 'merge_request') {
|
||||||
const webhookToken = event.request.headers.get('x-gitlab-token');
|
|
||||||
if (!webhookToken) {
|
|
||||||
return {
|
|
||||||
status: 500,
|
|
||||||
body: {
|
|
||||||
message: 'Ooops, something is not okay, are you okay?'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDraft = body.object_attributes.work_in_progress;
|
const isDraft = body.object_attributes.work_in_progress;
|
||||||
const action = body.object_attributes.action;
|
const action = body.object_attributes.action;
|
||||||
const projectId = Number(body.project.id);
|
const projectId = Number(body.project.id);
|
||||||
|
Loading…
Reference in New Issue
Block a user