mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-30 20:59:22 +00:00
Compare commits
50 Commits
v3.8.7
...
v3.9.0-rc.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4159804052 | ||
|
|
adb27cf143 | ||
|
|
a4879d854f | ||
|
|
8b92dfb889 | ||
|
|
eb4868cb6e | ||
|
|
e06e6e05ae | ||
|
|
4fce4f81c7 | ||
|
|
ae11283574 | ||
|
|
15fc9aa483 | ||
|
|
2ebfb8e6a9 | ||
|
|
d098ea675f | ||
|
|
8ad152e5fc | ||
|
|
14077fcf51 | ||
|
|
b427573e19 | ||
|
|
46268f0dcf | ||
|
|
006c178eb1 | ||
|
|
d63b20dabb | ||
|
|
fcf0a391ed | ||
|
|
263b9c4b3e | ||
|
|
1dc7355952 | ||
|
|
67e4a72a28 | ||
|
|
e6ea07f9b7 | ||
|
|
44a691ae29 | ||
|
|
290dbc43cb | ||
|
|
219f1f9f3f | ||
|
|
582170f26e | ||
|
|
4e2dad7720 | ||
|
|
d002ec72ad | ||
|
|
f6bb14f7c4 | ||
|
|
e1697848a5 | ||
|
|
4d48bba350 | ||
|
|
a690cc5564 | ||
|
|
be16f76034 | ||
|
|
ae4cf44728 | ||
|
|
4ac0df71b1 | ||
|
|
dbd948867c | ||
|
|
a9b5cd6c31 | ||
|
|
92f513d514 | ||
|
|
b239d21961 | ||
|
|
40e8dd4a8d | ||
|
|
a667435ef2 | ||
|
|
042b4e7587 | ||
|
|
c46a1b4a59 | ||
|
|
e6035d5479 | ||
|
|
008d090093 | ||
|
|
6ff080c36b | ||
|
|
086ca30323 | ||
|
|
17f3ecbbcb | ||
|
|
1f2c8c4ad2 | ||
|
|
bc03331c66 |
2
.github/workflows/production-release.yml
vendored
2
.github/workflows/production-release.yml
vendored
@@ -2,7 +2,7 @@ name: production-release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [released]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
making-something-cool:
|
making-something-cool:
|
||||||
|
|||||||
39
.github/workflows/release-candidate.yml
vendored
Normal file
39
.github/workflows/release-candidate.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: release-candidate
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [prereleased]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
making-something-cool:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: 'next'
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Get current package version
|
||||||
|
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||||
|
id: package-version
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: coollabsio/coolify:${{github.event.release.name}}
|
||||||
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc
|
||||||
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc,mode=max
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
@@ -125,7 +125,7 @@ If you are finished with the Prisma schema, you should update the database schem
|
|||||||
|
|
||||||
Supported versions are hardcoded into Coolify (for now).
|
Supported versions are hardcoded into Coolify (for now).
|
||||||
|
|
||||||
You need to update `supportedServiceTypesAndVersions` function at [src/apps/api/src/lib/supportedVersions.ts](src/apps/api/src/lib/supportedVersions.ts). Example JSON:
|
You need to update `supportedServiceTypesAndVersions` function at [apps/api/src/lib/services/supportedVersions.ts](apps/api/src/lib/services/supportedVersions.ts). Example JSON:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
@@ -208,22 +208,22 @@ export const umami = [{
|
|||||||
|
|
||||||
4. Add service deletion query to `removeService` function in [apps/api/src/lib/services/common.ts](apps/api/src/lib/services/common.ts)
|
4. Add service deletion query to `removeService` function in [apps/api/src/lib/services/common.ts](apps/api/src/lib/services/common.ts)
|
||||||
|
|
||||||
|
5. Add start process for the new service in [apps/api/src/lib/services/handlers.ts](apps/api/src/lib/services/handlers.ts)
|
||||||
5. You need to add start process for the new service in [apps/api/src/routes/api/v1/services/handlers.ts](apps/api/src/routes/api/v1/services/handlers.ts)
|
|
||||||
|
|
||||||
> See startUmamiService() function as example.
|
> See startUmamiService() function as example.
|
||||||
|
|
||||||
|
6. Add the newly added start process to `startService` in [apps/api/src/routes/api/v1/services/handlers.ts](apps/api/src/routes/api/v1/services/handlers.ts)
|
||||||
|
|
||||||
6. You need to add a custom logo at [apps/ui/src/lib/components/svg/services](apps/ui/src/lib/components/svg/services) as a svelte component and export it in [apps/ui/src/lib/components/svg/services/index.ts](apps/ui/src/lib/components/svg/services/index.ts)
|
7. You need to add a custom logo at [apps/ui/src/lib/components/svg/services](apps/ui/src/lib/components/svg/services) as a svelte component and export it in [apps/ui/src/lib/components/svg/services/index.ts](apps/ui/src/lib/components/svg/services/index.ts)
|
||||||
|
|
||||||
SVG is recommended, but you can use PNG as well. It should have the `isAbsolute` variable with the suitable CSS classes, primarily for sizing and positioning.
|
SVG is recommended, but you can use PNG as well. It should have the `isAbsolute` variable with the suitable CSS classes, primarily for sizing and positioning.
|
||||||
|
|
||||||
7. You need to include it the logo at:
|
8. You need to include it the logo at:
|
||||||
|
|
||||||
- [apps/ui/src/lib/components/svg/services/ServiceIcons.svelte](apps/ui/src/lib/components/svg/services/ServiceIcons.svelte) with `isAbsolute`.
|
- [apps/ui/src/lib/components/svg/services/ServiceIcons.svelte](apps/ui/src/lib/components/svg/services/ServiceIcons.svelte) with `isAbsolute`.
|
||||||
- [apps/ui/src/routes/services/[id]/_ServiceLinks.svelte](apps/ui/src/routes/services/[id]/_ServiceLinks.svelte) with the link to the docs/main site of the service
|
- [apps/ui/src/routes/services/[id]/_ServiceLinks.svelte](apps/ui/src/routes/services/[id]/_ServiceLinks.svelte) with the link to the docs/main site of the service
|
||||||
|
|
||||||
8. By default the URL and the name frontend forms are included in [apps/ui/src/routes/services/[id]/_Services/_Services.svelte](apps/ui/src/routes/services/[id]/_Services/_Services.svelte).
|
9. By default the URL and the name frontend forms are included in [apps/ui/src/routes/services/[id]/_Services/_Services.svelte](apps/ui/src/routes/services/[id]/_Services/_Services.svelte).
|
||||||
|
|
||||||
If you need to show more details on the frontend, such as users/passwords, you need to add Svelte component to [apps/ui/src/routes/services/[id]/_Services](apps/ui/src/routes/services/[id]/_Services) with an underscore.
|
If you need to show more details on the frontend, such as users/passwords, you need to add Svelte component to [apps/ui/src/routes/services/[id]/_Services](apps/ui/src/routes/services/[id]/_Services) with an underscore.
|
||||||
|
|
||||||
|
|||||||
14
Dockerfile
14
Dockerfile
@@ -14,14 +14,14 @@ WORKDIR /app
|
|||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
ENV PRISMA_QUERY_ENGINE_BINARY=/app/prisma-engines/query-engine \
|
# ENV PRISMA_QUERY_ENGINE_BINARY=/app/prisma-engines/query-engine \
|
||||||
PRISMA_MIGRATION_ENGINE_BINARY=/app/prisma-engines/migration-engine \
|
# PRISMA_MIGRATION_ENGINE_BINARY=/app/prisma-engines/migration-engine \
|
||||||
PRISMA_INTROSPECTION_ENGINE_BINARY=/app/prisma-engines/introspection-engine \
|
# PRISMA_INTROSPECTION_ENGINE_BINARY=/app/prisma-engines/introspection-engine \
|
||||||
PRISMA_FMT_BINARY=/app/prisma-engines/prisma-fmt \
|
# PRISMA_FMT_BINARY=/app/prisma-engines/prisma-fmt \
|
||||||
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
|
# PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
|
||||||
PRISMA_CLIENT_ENGINE_TYPE=binary
|
# PRISMA_CLIENT_ENGINE_TYPE=binary
|
||||||
|
|
||||||
COPY --from=coollabsio/prisma-engine:4.2.0 /prisma-engines/query-engine /prisma-engines/migration-engine /prisma-engines/introspection-engine /prisma-engines/prisma-fmt /app/prisma-engines/
|
# COPY --from=coollabsio/prisma-engine:3.15 /prisma-engines/query-engine /prisma-engines/migration-engine /prisma-engines/introspection-engine /prisma-engines/prisma-fmt /app/prisma-engines/
|
||||||
|
|
||||||
RUN apk add --no-cache git git-lfs openssh-client curl jq cmake sqlite openssl psmisc
|
RUN apk add --no-cache git git-lfs openssh-client curl jq cmake sqlite openssl psmisc
|
||||||
RUN curl -sL https://unpkg.com/@pnpm/self-installer | node
|
RUN curl -sL https://unpkg.com/@pnpm/self-installer | node
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"@fastify/static": "6.5.0",
|
"@fastify/static": "6.5.0",
|
||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@ladjs/graceful": "3.0.2",
|
"@ladjs/graceful": "3.0.2",
|
||||||
"@prisma/client": "4.2.1",
|
"@prisma/client": "3.15.2",
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bree": "9.1.2",
|
"bree": "9.1.2",
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
"eslint-plugin-prettier": "4.2.1",
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"nodemon": "2.0.19",
|
"nodemon": "2.0.19",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.7.1",
|
||||||
"prisma": "4.2.1",
|
"prisma": "3.15.2",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"tsconfig-paths": "4.1.0",
|
"tsconfig-paths": "4.1.0",
|
||||||
"typescript": "4.7.4"
|
"typescript": "4.7.4"
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Weblate" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"adminPassword" TEXT NOT NULL,
|
||||||
|
"postgresqlHost" TEXT NOT NULL,
|
||||||
|
"postgresqlPort" INTEGER NOT NULL,
|
||||||
|
"postgresqlUser" TEXT NOT NULL,
|
||||||
|
"postgresqlPassword" TEXT NOT NULL,
|
||||||
|
"postgresqlDatabase" TEXT NOT NULL,
|
||||||
|
"postgresqlPublicPort" INTEGER,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Weblate_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Weblate_serviceId_key" ON "Weblate"("serviceId");
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Taiga" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"secretKey" TEXT NOT NULL,
|
||||||
|
"erlangSecret" TEXT NOT NULL,
|
||||||
|
"djangoAdminPassword" TEXT NOT NULL,
|
||||||
|
"djangoAdminUser" TEXT NOT NULL,
|
||||||
|
"rabbitMQUser" TEXT NOT NULL,
|
||||||
|
"rabbitMQPassword" TEXT NOT NULL,
|
||||||
|
"postgresqlHost" TEXT NOT NULL,
|
||||||
|
"postgresqlPort" INTEGER NOT NULL,
|
||||||
|
"postgresqlUser" TEXT NOT NULL,
|
||||||
|
"postgresqlPassword" TEXT NOT NULL,
|
||||||
|
"postgresqlDatabase" TEXT NOT NULL,
|
||||||
|
"postgresqlPublicPort" INTEGER,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Taiga_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Taiga_serviceId_key" ON "Taiga"("serviceId");
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
binaryTargets = ["native", "linux-musl"]
|
binaryTargets = ["native"]
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
@@ -200,7 +200,7 @@ model Build {
|
|||||||
commit String?
|
commit String?
|
||||||
pullmergeRequestId String?
|
pullmergeRequestId String?
|
||||||
forceRebuild Boolean @default(false)
|
forceRebuild Boolean @default(false)
|
||||||
sourceBranch String?
|
sourceBranch String?
|
||||||
branch String?
|
branch String?
|
||||||
status String? @default("queued")
|
status String? @default("queued")
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -348,6 +348,8 @@ model Service {
|
|||||||
wordpress Wordpress?
|
wordpress Wordpress?
|
||||||
appwrite Appwrite?
|
appwrite Appwrite?
|
||||||
searxng Searxng?
|
searxng Searxng?
|
||||||
|
weblate Weblate?
|
||||||
|
taiga Taiga?
|
||||||
}
|
}
|
||||||
|
|
||||||
model PlausibleAnalytics {
|
model PlausibleAnalytics {
|
||||||
@@ -559,3 +561,38 @@ model Searxng {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Weblate {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
adminPassword String
|
||||||
|
postgresqlHost String
|
||||||
|
postgresqlPort Int
|
||||||
|
postgresqlUser String
|
||||||
|
postgresqlPassword String
|
||||||
|
postgresqlDatabase String
|
||||||
|
postgresqlPublicPort Int?
|
||||||
|
serviceId String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Taiga {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
secretKey String
|
||||||
|
erlangSecret String
|
||||||
|
djangoAdminPassword String
|
||||||
|
djangoAdminUser String
|
||||||
|
rabbitMQUser String
|
||||||
|
rabbitMQPassword String
|
||||||
|
postgresqlHost String
|
||||||
|
postgresqlPort Int
|
||||||
|
postgresqlUser String
|
||||||
|
postgresqlPassword String
|
||||||
|
postgresqlDatabase String
|
||||||
|
postgresqlPublicPort Int?
|
||||||
|
serviceId String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import axios from 'axios';
|
|||||||
import { compareVersions } from 'compare-versions';
|
import { compareVersions } from 'compare-versions';
|
||||||
import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, listSettings, version } from '../lib/common';
|
import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, listSettings, version } from '../lib/common';
|
||||||
|
|
||||||
async function disconnect() {
|
|
||||||
await prisma.$disconnect();
|
|
||||||
}
|
|
||||||
async function autoUpdater() {
|
async function autoUpdater() {
|
||||||
try {
|
try {
|
||||||
const currentVersion = version;
|
const currentVersion = version;
|
||||||
@@ -27,6 +24,9 @@ async function autoUpdater() {
|
|||||||
console.log(`Updating Coolify to ${latestVersion}.`);
|
console.log(`Updating Coolify to ${latestVersion}.`);
|
||||||
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
|
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
|
||||||
await asyncExecShell(`env | grep COOLIFY > .env`);
|
await asyncExecShell(`env | grep COOLIFY > .env`);
|
||||||
|
await asyncExecShell(
|
||||||
|
`sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=true' .env`
|
||||||
|
);
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify && docker rm coolify && docker compose up -d --force-recreate"`
|
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify && docker rm coolify && docker compose up -d --force-recreate"`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { scheduler } from './scheduler';
|
|||||||
import { supportedServiceTypesAndVersions } from './services/supportedVersions';
|
import { supportedServiceTypesAndVersions } from './services/supportedVersions';
|
||||||
import { includeServices } from './services/common';
|
import { includeServices } from './services/common';
|
||||||
|
|
||||||
export const version = '3.8.7';
|
export const version = '3.9.0-rc.1';
|
||||||
export const isDev = process.env.NODE_ENV === 'development';
|
export const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
const algorithm = 'aes-256-ctr';
|
const algorithm = 'aes-256-ctr';
|
||||||
@@ -139,10 +139,10 @@ export const prisma = new PrismaClient({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// prisma.$on('query', (e) => {
|
// prisma.$on('query', (e) => {
|
||||||
// console.log({e})
|
// console.log({e})
|
||||||
// console.log('Query: ' + e.query)
|
// console.log('Query: ' + e.query)
|
||||||
// console.log('Params: ' + e.params)
|
// console.log('Params: ' + e.params)
|
||||||
// console.log('Duration: ' + e.duration + 'ms')
|
// console.log('Duration: ' + e.duration + 'ms')
|
||||||
// })
|
// })
|
||||||
export const base64Encode = (text: string): string => {
|
export const base64Encode = (text: string): string => {
|
||||||
return Buffer.from(text).toString('base64');
|
return Buffer.from(text).toString('base64');
|
||||||
@@ -1089,7 +1089,6 @@ export async function checkExposedPort({ id, configuredPort, exposePort, dockerI
|
|||||||
if (exposePort < 1024 || exposePort > 65535) {
|
if (exposePort < 1024 || exposePort > 65535) {
|
||||||
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configuredPort) {
|
if (configuredPort) {
|
||||||
if (configuredPort !== exposePort) {
|
if (configuredPort !== exposePort) {
|
||||||
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
||||||
@@ -1309,6 +1308,9 @@ export function saveUpdateableFields(type: string, data: any) {
|
|||||||
temp = Boolean(temp)
|
temp = Boolean(temp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (k.isNumber && temp === '') {
|
||||||
|
temp = null
|
||||||
|
}
|
||||||
update[k.name] = temp
|
update[k.name] = temp
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1351,9 +1353,9 @@ export const getServiceMainPort = (service: string) => {
|
|||||||
export function makeLabelForServices(type) {
|
export function makeLabelForServices(type) {
|
||||||
return [
|
return [
|
||||||
'coolify.managed=true',
|
'coolify.managed=true',
|
||||||
`coolify.version = ${version}`,
|
`coolify.version=${version}`,
|
||||||
`coolify.type = service`,
|
`coolify.type=service`,
|
||||||
`coolify.service.type = ${type}`
|
`coolify.service.type=${type}`
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
export function errorHandler({ status = 500, message = 'Unknown error.' }: { status: number, message: string | any }) {
|
export function errorHandler({ status = 500, message = 'Unknown error.' }: { status: number, message: string | any }) {
|
||||||
@@ -1473,14 +1475,25 @@ export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function persistentVolumes(id, persistentStorage, config) {
|
export function persistentVolumes(id, persistentStorage, config) {
|
||||||
|
let volumeSet = new Set();
|
||||||
|
if (Object.keys(config).length > 0) {
|
||||||
|
for (const [key, value] of Object.entries(config)) {
|
||||||
|
if (value.volumes) {
|
||||||
|
for (const volume of value.volumes) {
|
||||||
|
volumeSet.add(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const volumesArray = Array.from(volumeSet);
|
||||||
const persistentVolume =
|
const persistentVolume =
|
||||||
persistentStorage?.map((storage) => {
|
persistentStorage?.map((storage) => {
|
||||||
return `${id}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
return `${id}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
let volumes = [...persistentVolume]
|
let volumes = [...persistentVolume]
|
||||||
if (config.volume) volumes = [config.volume, ...volumes]
|
if (volumesArray) volumes = [...volumesArray, ...volumes]
|
||||||
|
|
||||||
const composeVolumes = volumes.length > 0 && volumes.map((volume) => {
|
const composeVolumes = volumes.length > 0 && volumes.map((volume) => {
|
||||||
return {
|
return {
|
||||||
[`${volume.split(':')[0]}`]: {
|
[`${volume.split(':')[0]}`]: {
|
||||||
@@ -1489,16 +1502,11 @@ export function persistentVolumes(id, persistentStorage, config) {
|
|||||||
};
|
};
|
||||||
}) || []
|
}) || []
|
||||||
|
|
||||||
const volumeMounts = config.volume && Object.assign(
|
const volumeMounts = Object.assign(
|
||||||
{},
|
{},
|
||||||
{
|
|
||||||
[config.volume.split(':')[0]]: {
|
|
||||||
name: config.volume.split(':')[0]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
...composeVolumes
|
...composeVolumes
|
||||||
) || {}
|
) || {}
|
||||||
return { volumes, volumeMounts }
|
return { volumeMounts }
|
||||||
}
|
}
|
||||||
export function defaultComposeConfiguration(network: string): any {
|
export function defaultComposeConfiguration(network: string): any {
|
||||||
return {
|
return {
|
||||||
@@ -1515,26 +1523,26 @@ export function defaultComposeConfiguration(network: string): any {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export function decryptApplication(application: any) {
|
export function decryptApplication(application: any) {
|
||||||
if (application) {
|
if (application) {
|
||||||
if (application?.gitSource?.githubApp?.clientSecret) {
|
if (application?.gitSource?.githubApp?.clientSecret) {
|
||||||
application.gitSource.githubApp.clientSecret = decrypt(application.gitSource.githubApp.clientSecret) || null;
|
application.gitSource.githubApp.clientSecret = decrypt(application.gitSource.githubApp.clientSecret) || null;
|
||||||
}
|
}
|
||||||
if (application?.gitSource?.githubApp?.webhookSecret) {
|
if (application?.gitSource?.githubApp?.webhookSecret) {
|
||||||
application.gitSource.githubApp.webhookSecret = decrypt(application.gitSource.githubApp.webhookSecret) || null;
|
application.gitSource.githubApp.webhookSecret = decrypt(application.gitSource.githubApp.webhookSecret) || null;
|
||||||
}
|
}
|
||||||
if (application?.gitSource?.githubApp?.privateKey) {
|
if (application?.gitSource?.githubApp?.privateKey) {
|
||||||
application.gitSource.githubApp.privateKey = decrypt(application.gitSource.githubApp.privateKey) || null;
|
application.gitSource.githubApp.privateKey = decrypt(application.gitSource.githubApp.privateKey) || null;
|
||||||
}
|
}
|
||||||
if (application?.gitSource?.gitlabApp?.appSecret) {
|
if (application?.gitSource?.gitlabApp?.appSecret) {
|
||||||
application.gitSource.gitlabApp.appSecret = decrypt(application.gitSource.gitlabApp.appSecret) || null;
|
application.gitSource.gitlabApp.appSecret = decrypt(application.gitSource.gitlabApp.appSecret) || null;
|
||||||
}
|
}
|
||||||
if (application?.secrets.length > 0) {
|
if (application?.secrets.length > 0) {
|
||||||
application.secrets = application.secrets.map((s: any) => {
|
application.secrets = application.secrets.map((s: any) => {
|
||||||
s.value = decrypt(s.value) || null
|
s.value = decrypt(s.value) || null
|
||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return application;
|
return application;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,7 @@
|
|||||||
import { exec } from 'node:child_process'
|
|
||||||
import util from 'util';
|
|
||||||
import fs from 'fs/promises';
|
|
||||||
import yaml from 'js-yaml';
|
|
||||||
import forge from 'node-forge';
|
|
||||||
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
|
|
||||||
import type { Config } from 'unique-names-generator';
|
|
||||||
import generator from 'generate-password';
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import { promises as dns } from 'dns';
|
|
||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import os from 'os';
|
|
||||||
import sshConfig from 'ssh-config'
|
|
||||||
import { encrypt, generatePassword, prisma } from '../common';
|
import { encrypt, generatePassword, prisma } from '../common';
|
||||||
|
|
||||||
|
|
||||||
export const version = '3.8.2';
|
|
||||||
export const isDev = process.env.NODE_ENV === 'development';
|
|
||||||
|
|
||||||
export const includeServices: any = {
|
export const includeServices: any = {
|
||||||
destinationDocker: true,
|
destinationDocker: true,
|
||||||
persistentStorage: true,
|
persistentStorage: true,
|
||||||
@@ -34,7 +18,9 @@ export const includeServices: any = {
|
|||||||
moodle: true,
|
moodle: true,
|
||||||
appwrite: true,
|
appwrite: true,
|
||||||
glitchTip: true,
|
glitchTip: true,
|
||||||
searxng: true
|
searxng: true,
|
||||||
|
weblate: true,
|
||||||
|
taiga: true
|
||||||
};
|
};
|
||||||
export async function configureServiceType({
|
export async function configureServiceType({
|
||||||
id,
|
id,
|
||||||
@@ -312,6 +298,58 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (type === 'weblate') {
|
||||||
|
const adminPassword = encrypt(generatePassword({}))
|
||||||
|
const postgresqlUser = cuid();
|
||||||
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
|
const postgresqlDatabase = 'weblate';
|
||||||
|
await prisma.service.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
type,
|
||||||
|
weblate: {
|
||||||
|
create: {
|
||||||
|
adminPassword,
|
||||||
|
postgresqlHost: `${id}-postgresql`,
|
||||||
|
postgresqlPort: 5432,
|
||||||
|
postgresqlUser,
|
||||||
|
postgresqlPassword,
|
||||||
|
postgresqlDatabase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (type === 'taiga') {
|
||||||
|
const secretKey = encrypt(generatePassword({}))
|
||||||
|
const erlangSecret = encrypt(generatePassword({}))
|
||||||
|
const rabbitMQUser = cuid();
|
||||||
|
const djangoAdminUser = cuid();
|
||||||
|
const djangoAdminPassword = encrypt(generatePassword({}))
|
||||||
|
const rabbitMQPassword = encrypt(generatePassword({}))
|
||||||
|
const postgresqlUser = cuid();
|
||||||
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
|
const postgresqlDatabase = 'taiga';
|
||||||
|
await prisma.service.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
type,
|
||||||
|
taiga: {
|
||||||
|
create: {
|
||||||
|
secretKey,
|
||||||
|
erlangSecret,
|
||||||
|
djangoAdminUser,
|
||||||
|
djangoAdminPassword,
|
||||||
|
rabbitMQUser,
|
||||||
|
rabbitMQPassword,
|
||||||
|
postgresqlHost: `${id}-postgresql`,
|
||||||
|
postgresqlPort: 5432,
|
||||||
|
postgresqlUser,
|
||||||
|
postgresqlPassword,
|
||||||
|
postgresqlDatabase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -338,7 +376,8 @@ export async function removeService({ id }: { id: string }): Promise<void> {
|
|||||||
await prisma.moodle.deleteMany({ where: { serviceId: id } });
|
await prisma.moodle.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.searxng.deleteMany({ where: { serviceId: id } });
|
await prisma.searxng.deleteMany({ where: { serviceId: id } });
|
||||||
|
await prisma.weblate.deleteMany({ where: { serviceId: id } });
|
||||||
|
await prisma.taiga.deleteMany({ where: { serviceId: id } });
|
||||||
|
|
||||||
await prisma.service.delete({ where: { id } });
|
await prisma.service.delete({ where: { id } });
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -599,6 +599,54 @@ export const glitchTip = [{
|
|||||||
isBoolean: false,
|
isBoolean: false,
|
||||||
isEncrypted: true
|
isEncrypted: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'emailSmtpHost',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailSmtpPassword',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailSmtpUseSsl',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: true,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailSmtpUseSsl',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: true,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailSmtpPort',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: true,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailSmtpUser',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'defaultEmail',
|
name: 'defaultEmail',
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
@@ -624,7 +672,7 @@ export const glitchTip = [{
|
|||||||
isEncrypted: true
|
isEncrypted: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'defaultFromEmail',
|
name: 'defaultEmailFrom',
|
||||||
isEditable: true,
|
isEditable: true,
|
||||||
isLowerCase: false,
|
isLowerCase: false,
|
||||||
isNumber: false,
|
isNumber: false,
|
||||||
@@ -687,4 +735,133 @@ export const searxng = [{
|
|||||||
isNumber: false,
|
isNumber: false,
|
||||||
isBoolean: false,
|
isBoolean: false,
|
||||||
isEncrypted: true
|
isEncrypted: true
|
||||||
|
}]
|
||||||
|
|
||||||
|
export const weblate = [{
|
||||||
|
name: 'adminPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlHost',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlPort',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlUser',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlDatabase',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
}]
|
||||||
|
export const taiga = [{
|
||||||
|
name: 'secretKey',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'djangoAdminUser',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'djangoAdminPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rabbitMQUser',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rabbitMQPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlHost',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlPort',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlUser',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlDatabase',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
}]
|
}]
|
||||||
@@ -190,4 +190,26 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
main: 8080
|
main: 8080
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'weblate',
|
||||||
|
fancyName: 'Weblate',
|
||||||
|
baseImage: 'weblate/weblate',
|
||||||
|
images: ['postgres:14-alpine', 'redis:6-alpine'],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: 'taiga',
|
||||||
|
// fancyName: 'Taiga',
|
||||||
|
// baseImage: 'taigaio/taiga-front',
|
||||||
|
// images: ['postgres:12.3', 'rabbitmq:3.8-management-alpine', 'taigaio/taiga-back', 'taigaio/taiga-events', 'taigaio/taiga-protected'],
|
||||||
|
// versions: ['latest'],
|
||||||
|
// recommendedVersion: 'latest',
|
||||||
|
// ports: {
|
||||||
|
// main: 80
|
||||||
|
// }
|
||||||
|
// },
|
||||||
];
|
];
|
||||||
@@ -3,16 +3,23 @@ import crypto from 'node:crypto'
|
|||||||
import jsonwebtoken from 'jsonwebtoken';
|
import jsonwebtoken from 'jsonwebtoken';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { FastifyReply } from 'fastify';
|
import { FastifyReply } from 'fastify';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
import { day } from '../../../../lib/dayjs';
|
import { day } from '../../../../lib/dayjs';
|
||||||
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
import { makeLabelForStandaloneApplication, setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
||||||
import { checkDomainsIsValidInDNS, checkDoubleBranch, checkExposedPort, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
import { checkDomainsIsValidInDNS, checkDoubleBranch, checkExposedPort, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
||||||
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
|
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
|
||||||
import { scheduler } from '../../../../lib/scheduler';
|
|
||||||
|
|
||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication } from './types';
|
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication } from './types';
|
||||||
import { OnlyId } from '../../../../types';
|
import { OnlyId } from '../../../../types';
|
||||||
|
|
||||||
|
function filterObject(obj, callback) {
|
||||||
|
return Object.fromEntries(Object.entries(obj).
|
||||||
|
filter(([key, val]) => callback(val, key)));
|
||||||
|
}
|
||||||
|
|
||||||
export async function listApplications(request: FastifyRequest) {
|
export async function listApplications(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
@@ -176,7 +183,7 @@ export async function getApplicationFromDB(id: string, teamId: string) {
|
|||||||
}
|
}
|
||||||
export async function getApplicationFromDBWebhook(projectId: number, branch: string) {
|
export async function getApplicationFromDBWebhook(projectId: number, branch: string) {
|
||||||
try {
|
try {
|
||||||
let application = await prisma.application.findFirst({
|
let applications = await prisma.application.findMany({
|
||||||
where: { projectId, branch, settings: { autodeploy: true } },
|
where: { projectId, branch, settings: { autodeploy: true } },
|
||||||
include: {
|
include: {
|
||||||
destinationDocker: true,
|
destinationDocker: true,
|
||||||
@@ -186,22 +193,28 @@ export async function getApplicationFromDBWebhook(projectId: number, branch: str
|
|||||||
persistentStorage: true
|
persistentStorage: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!application) {
|
if (applications.length === 0) {
|
||||||
throw { status: 500, message: 'Application not configured.' }
|
throw { status: 500, message: 'Application not configured.' }
|
||||||
}
|
}
|
||||||
application = decryptApplication(application);
|
applications = applications.map((application: any) => {
|
||||||
const { baseImage, baseBuildImage, baseBuildImages, baseImages } = setDefaultBaseImage(
|
application = decryptApplication(application);
|
||||||
application.buildPack
|
const { baseImage, baseBuildImage, baseBuildImages, baseImages } = setDefaultBaseImage(
|
||||||
);
|
application.buildPack
|
||||||
|
);
|
||||||
|
|
||||||
// Set default build images
|
// Set default build images
|
||||||
if (!application.baseImage) {
|
if (!application.baseImage) {
|
||||||
application.baseImage = baseImage;
|
application.baseImage = baseImage;
|
||||||
}
|
}
|
||||||
if (!application.baseBuildImage) {
|
if (!application.baseBuildImage) {
|
||||||
application.baseBuildImage = baseBuildImage;
|
application.baseBuildImage = baseBuildImage;
|
||||||
}
|
}
|
||||||
return { ...application, baseBuildImages, baseImages };
|
application.baseBuildImages = baseBuildImages;
|
||||||
|
application.baseImages = baseImages;
|
||||||
|
return application
|
||||||
|
})
|
||||||
|
|
||||||
|
return applications;
|
||||||
|
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -236,8 +249,8 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
exposePort = Number(exposePort);
|
exposePort = Number(exposePort);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { destinationDocker: { id: dockerId, remoteIpAddress } } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
const { destinationDocker: { id: dockerId, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
if (exposePort) await checkExposedPort({ id, exposePort, dockerId, remoteIpAddress })
|
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
|
||||||
if (denoOptions) denoOptions = denoOptions.trim();
|
if (denoOptions) denoOptions = denoOptions.trim();
|
||||||
const defaultConfiguration = await setDefaultConfiguration({
|
const defaultConfiguration = await setDefaultConfiguration({
|
||||||
buildPack,
|
buildPack,
|
||||||
@@ -277,11 +290,11 @@ export async function saveApplicationSettings(request: FastifyRequest<SaveApplic
|
|||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot } = request.body
|
const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot } = request.body
|
||||||
const isDouble = await checkDoubleBranch(branch, projectId);
|
// const isDouble = await checkDoubleBranch(branch, projectId);
|
||||||
if (isDouble && autodeploy) {
|
// if (isDouble && autodeploy) {
|
||||||
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
|
// await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
|
||||||
throw { status: 500, message: 'Cannot activate automatic deployments until only one application is defined for this repository / branch.' }
|
// throw { status: 500, message: 'Cannot activate automatic deployments until only one application is defined for this repository / branch.' }
|
||||||
}
|
// }
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot } } },
|
data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot } } },
|
||||||
@@ -312,6 +325,113 @@ export async function stopPreviewApplication(request: FastifyRequest<StopPreview
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function restartApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params
|
||||||
|
const { teamId } = request.user
|
||||||
|
let application: any = await getApplicationFromDB(id, teamId);
|
||||||
|
if (application?.destinationDockerId) {
|
||||||
|
const buildId = cuid();
|
||||||
|
const { id: dockerId, network } = application.destinationDocker;
|
||||||
|
const { secrets, pullmergeRequestId, port, repository, persistentStorage, id: applicationId, buildPack, exposePort } = application;
|
||||||
|
|
||||||
|
const envs = [
|
||||||
|
`PORT=${port}`
|
||||||
|
];
|
||||||
|
if (secrets.length > 0) {
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { workdir } = await createDirectories({ repository, buildId });
|
||||||
|
const labels = []
|
||||||
|
let image = null
|
||||||
|
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` })
|
||||||
|
const containersArray = container.trim().split('\n');
|
||||||
|
for (const container of containersArray) {
|
||||||
|
const containerObj = formatLabelsOnDocker(container);
|
||||||
|
image = containerObj[0].Image
|
||||||
|
Object.keys(containerObj[0].Labels).forEach(function (key) {
|
||||||
|
if (key.startsWith('coolify')) {
|
||||||
|
labels.push(`${key}=${containerObj[0].Labels[key]}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let imageFound = false;
|
||||||
|
try {
|
||||||
|
await executeDockerCmd({
|
||||||
|
dockerId,
|
||||||
|
command: `docker image inspect ${image}`
|
||||||
|
})
|
||||||
|
imageFound = true;
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
if (!imageFound) {
|
||||||
|
throw { status: 500, message: 'Image not found, cannot restart application.' }
|
||||||
|
}
|
||||||
|
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||||
|
|
||||||
|
let envFound = false;
|
||||||
|
try {
|
||||||
|
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
const volumes =
|
||||||
|
persistentStorage?.map((storage) => {
|
||||||
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
||||||
|
}${storage.path}`;
|
||||||
|
}) || [];
|
||||||
|
const composeVolumes = volumes.map((volume) => {
|
||||||
|
return {
|
||||||
|
[`${volume.split(':')[0]}`]: {
|
||||||
|
name: volume.split(':')[0]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const composeFile = {
|
||||||
|
version: '3.8',
|
||||||
|
services: {
|
||||||
|
[applicationId]: {
|
||||||
|
image,
|
||||||
|
container_name: applicationId,
|
||||||
|
volumes,
|
||||||
|
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||||
|
labels,
|
||||||
|
depends_on: [],
|
||||||
|
expose: [port],
|
||||||
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
networks: {
|
||||||
|
[network]: {
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
volumes: Object.assign({}, ...composeVolumes)
|
||||||
|
};
|
||||||
|
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||||
|
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
||||||
|
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
||||||
|
await executeDockerCmd({ dockerId, command: `docker compose --project-directory ${workdir} up -d` })
|
||||||
|
return reply.code(201).send();
|
||||||
|
}
|
||||||
|
throw { status: 500, message: 'Application cannot be restarted.' }
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function stopApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
export async function stopApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
@@ -332,12 +452,14 @@ export async function stopApplication(request: FastifyRequest<OnlyId>, reply: Fa
|
|||||||
export async function deleteApplication(request: FastifyRequest<DeleteApplication>, reply: FastifyReply) {
|
export async function deleteApplication(request: FastifyRequest<DeleteApplication>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
|
const { force } = request.body
|
||||||
|
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
const application = await prisma.application.findUnique({
|
const application = await prisma.application.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
if (application?.destinationDockerId && application.destinationDocker?.network) {
|
if (!force && application?.destinationDockerId && application.destinationDocker?.network) {
|
||||||
const { stdout: containers } = await executeDockerCmd({
|
const { stdout: containers } = await executeDockerCmd({
|
||||||
dockerId: application.destinationDocker.id,
|
dockerId: application.destinationDocker.id,
|
||||||
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
||||||
@@ -558,12 +680,12 @@ export async function saveRepository(request, reply) {
|
|||||||
data: { repository, branch, projectId, settings: { update: { autodeploy, isPublicRepository } } }
|
data: { repository, branch, projectId, settings: { update: { autodeploy, isPublicRepository } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!isPublicRepository) {
|
// if (!isPublicRepository) {
|
||||||
const isDouble = await checkDoubleBranch(branch, projectId);
|
// const isDouble = await checkDoubleBranch(branch, projectId);
|
||||||
if (isDouble) {
|
// if (isDouble) {
|
||||||
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false, isPublicRepository } })
|
// await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false, isPublicRepository } })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -860,9 +982,12 @@ export async function getBuildIdLogs(request: FastifyRequest<GetBuildIdLogs>) {
|
|||||||
where: { buildId, time: { gt: sequence } },
|
where: { buildId, time: { gt: sequence } },
|
||||||
orderBy: { time: 'asc' }
|
orderBy: { time: 'asc' }
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await prisma.build.findFirst({ where: { id: buildId } });
|
const data = await prisma.build.findFirst({ where: { id: buildId } });
|
||||||
|
const createdAt = day(data.createdAt).utc();
|
||||||
return {
|
return {
|
||||||
logs,
|
logs,
|
||||||
|
took: day().diff(createdAt) / 1000,
|
||||||
status: data?.status || 'queued'
|
status: data?.status || 'queued'
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { OnlyId } from '../../../../types';
|
import { OnlyId } from '../../../../types';
|
||||||
import { cancelDeployment, checkDNS, checkDomain, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication } from './handlers';
|
import { cancelDeployment, checkDNS, checkDomain, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, restartApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication } from './handlers';
|
||||||
|
|
||||||
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
|
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
|
|
||||||
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
|
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
|
||||||
|
|
||||||
|
fastify.post<OnlyId>('/:id/restart', async (request, reply) => await restartApplication(request, reply));
|
||||||
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
|
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
|
||||||
fastify.post<StopPreviewApplication>('/:id/stop/preview', async (request, reply) => await stopPreviewApplication(request, reply));
|
fastify.post<StopPreviewApplication>('/:id/stop/preview', async (request, reply) => await stopPreviewApplication(request, reply));
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export interface SaveApplicationSettings extends OnlyId {
|
|||||||
}
|
}
|
||||||
export interface DeleteApplication extends OnlyId {
|
export interface DeleteApplication extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string; };
|
||||||
|
Body: { force: boolean }
|
||||||
}
|
}
|
||||||
export interface CheckDomain extends OnlyId {
|
export interface CheckDomain extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string; };
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, execute
|
|||||||
import { day } from '../../../../lib/dayjs';
|
import { day } from '../../../../lib/dayjs';
|
||||||
|
|
||||||
import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
|
import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
|
||||||
import { SaveDatabaseType } from './types';
|
import { DeleteDatabase, SaveDatabaseType } from './types';
|
||||||
|
|
||||||
export async function listDatabases(request: FastifyRequest) {
|
export async function listDatabases(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
@@ -167,6 +167,7 @@ export async function saveDatabaseDestination(request: FastifyRequest<SaveDataba
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { destinationId } = request.body;
|
const { destinationId } = request.body;
|
||||||
|
|
||||||
|
const { arch } = await listSettings();
|
||||||
await prisma.database.update({
|
await prisma.database.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { destinationDocker: { connect: { id: destinationId } } }
|
data: { destinationDocker: { connect: { id: destinationId } } }
|
||||||
@@ -181,7 +182,7 @@ export async function saveDatabaseDestination(request: FastifyRequest<SaveDataba
|
|||||||
|
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
if (type && version) {
|
if (type && version) {
|
||||||
const baseImage = getDatabaseImage(type);
|
const baseImage = getDatabaseImage(type, arch);
|
||||||
executeDockerCmd({ dockerId, command: `docker pull ${baseImage}:${version}` })
|
executeDockerCmd({ dockerId, command: `docker pull ${baseImage}:${version}` })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,19 +361,22 @@ export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>)
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function deleteDatabase(request: FastifyRequest<OnlyId>) {
|
export async function deleteDatabase(request: FastifyRequest<DeleteDatabase>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
|
const { force } = request.body;
|
||||||
const database = await prisma.database.findFirst({
|
const database = await prisma.database.findFirst({
|
||||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { destinationDocker: true, settings: true }
|
include: { destinationDocker: true, settings: true }
|
||||||
});
|
});
|
||||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
if (!force) {
|
||||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||||
if (database.destinationDockerId) {
|
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||||
const everStarted = await stopDatabaseContainer(database);
|
if (database.destinationDockerId) {
|
||||||
if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
const everStarted = await stopDatabaseContainer(database);
|
||||||
|
if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
||||||
await prisma.database.delete({ where: { id } });
|
await prisma.database.delete({ where: { id } });
|
||||||
@@ -436,7 +440,7 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
|
|||||||
let publicPort = null
|
let publicPort = null
|
||||||
|
|
||||||
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
|
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
|
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
publicPort = await getFreePublicPort(id, dockerId);
|
publicPort = await getFreePublicPort(id, dockerId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { deleteDatabase, getDatabase, getDatabaseLogs, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
import { deleteDatabase, getDatabase, getDatabaseLogs, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
||||||
|
|
||||||
import type { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
|
import type { DeleteDatabase, GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
|
||||||
import type { SaveDatabaseType } from './types';
|
import type { SaveDatabaseType } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
@@ -13,7 +13,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
|
|
||||||
fastify.get<OnlyId>('/:id', async (request) => await getDatabase(request));
|
fastify.get<OnlyId>('/:id', async (request) => await getDatabase(request));
|
||||||
fastify.post<SaveDatabase>('/:id', async (request, reply) => await saveDatabase(request, reply));
|
fastify.post<SaveDatabase>('/:id', async (request, reply) => await saveDatabase(request, reply));
|
||||||
fastify.delete<OnlyId>('/:id', async (request) => await deleteDatabase(request));
|
fastify.delete<DeleteDatabase>('/:id', async (request) => await deleteDatabase(request));
|
||||||
|
|
||||||
fastify.get<OnlyId>('/:id/status', async (request) => await getDatabaseStatus(request));
|
fastify.get<OnlyId>('/:id/status', async (request) => await getDatabaseStatus(request));
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,7 @@ import type { OnlyId } from "../../../../types";
|
|||||||
|
|
||||||
export interface SaveDatabaseType extends OnlyId {
|
export interface SaveDatabaseType extends OnlyId {
|
||||||
Body: { type: string }
|
Body: { type: string }
|
||||||
|
}
|
||||||
|
export interface DeleteDatabase extends OnlyId {
|
||||||
|
Body: { force: string }
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import bcrypt from 'bcryptjs';
|
import { prisma, uniqueName, asyncExecShell, getServiceFromDB, getContainerUsage, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, ComposeFile, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, executeDockerCmd, checkDomainsIsValidInDNS, checkExposedPort } from '../../../../lib/common';
|
||||||
import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceFromDB, getContainerUsage,isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, executeDockerCmd, checkDomainsIsValidInDNS, persistentVolumes, asyncSleep, isARM, defaultComposeConfiguration, checkExposedPort } from '../../../../lib/common';
|
|
||||||
import { day } from '../../../../lib/dayjs';
|
import { day } from '../../../../lib/dayjs';
|
||||||
import { checkContainer, isContainerExited, removeContainer } from '../../../../lib/docker';
|
import { checkContainer, isContainerExited } from '../../../../lib/docker';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
|
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetGlitchTipSettings, SetWordpressSettings } from './types';
|
||||||
import { defaultServiceConfigurations } from '../../../../lib/services';
|
|
||||||
import { supportedServiceTypesAndVersions } from '../../../../lib/services/supportedVersions';
|
import { supportedServiceTypesAndVersions } from '../../../../lib/services/supportedVersions';
|
||||||
import { configureServiceType, removeService } from '../../../../lib/services/common';
|
import { configureServiceType, removeService } from '../../../../lib/services/common';
|
||||||
|
|
||||||
@@ -269,7 +267,6 @@ export async function saveService(request: FastifyRequest<SaveService>, reply: F
|
|||||||
if (exposePort) exposePort = Number(exposePort);
|
if (exposePort) exposePort = Number(exposePort);
|
||||||
|
|
||||||
type = fixType(type)
|
type = fixType(type)
|
||||||
|
|
||||||
const update = saveUpdateableFields(type, request.body[type])
|
const update = saveUpdateableFields(type, request.body[type])
|
||||||
const data = {
|
const data = {
|
||||||
fqdn,
|
fqdn,
|
||||||
@@ -400,17 +397,33 @@ export async function deleteServiceStorage(request: FastifyRequest<DeleteService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setSettingsService(request: FastifyRequest<ServiceStartStop & SetWordpressSettings>, reply: FastifyReply) {
|
export async function setSettingsService(request: FastifyRequest<ServiceStartStop & SetWordpressSettings & SetGlitchTipSettings>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { type } = request.params
|
const { type } = request.params
|
||||||
if (type === 'wordpress') {
|
if (type === 'wordpress') {
|
||||||
return await setWordpressSettings(request, reply)
|
return await setWordpressSettings(request, reply)
|
||||||
}
|
}
|
||||||
|
if (type === 'glitchtip') {
|
||||||
|
return await setGlitchTipSettings(request, reply)
|
||||||
|
}
|
||||||
throw `Service type ${type} not supported.`
|
throw `Service type ${type} not supported.`
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function setGlitchTipSettings(request: FastifyRequest<SetGlitchTipSettings>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params
|
||||||
|
const { enableOpenUserRegistration, emailSmtpUseSsl, emailSmtpUseTls } = request.body
|
||||||
|
await prisma.glitchTip.update({
|
||||||
|
where: { serviceId: id },
|
||||||
|
data: { enableOpenUserRegistration, emailSmtpUseSsl, emailSmtpUseTls }
|
||||||
|
});
|
||||||
|
return reply.code(201).send()
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
async function setWordpressSettings(request: FastifyRequest<ServiceStartStop & SetWordpressSettings>, reply: FastifyReply) {
|
async function setWordpressSettings(request: FastifyRequest<ServiceStartStop & SetWordpressSettings>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
} from './handlers';
|
} from './handlers';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
|
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetGlitchTipSettings, SetWordpressSettings } from './types';
|
||||||
import { startService, stopService } from '../../../../lib/services/handlers';
|
import { startService, stopService } from '../../../../lib/services/handlers';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
@@ -71,7 +71,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
|
|
||||||
fastify.post<ServiceStartStop>('/:id/:type/start', async (request) => await startService(request));
|
fastify.post<ServiceStartStop>('/:id/:type/start', async (request) => await startService(request));
|
||||||
fastify.post<ServiceStartStop>('/:id/:type/stop', async (request) => await stopService(request));
|
fastify.post<ServiceStartStop>('/:id/:type/stop', async (request) => await stopService(request));
|
||||||
fastify.post<ServiceStartStop & SetWordpressSettings>('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply));
|
fastify.post<ServiceStartStop & SetWordpressSettings & SetGlitchTipSettings>('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply));
|
||||||
|
|
||||||
fastify.post<OnlyId>('/:id/plausibleanalytics/activate', async (request, reply) => await activatePlausibleUsers(request, reply));
|
fastify.post<OnlyId>('/:id/plausibleanalytics/activate', async (request, reply) => await activatePlausibleUsers(request, reply));
|
||||||
fastify.post<OnlyId>('/:id/plausibleanalytics/cleanup', async (request, reply) => await cleanupPlausibleLogs(request, reply));
|
fastify.post<OnlyId>('/:id/plausibleanalytics/cleanup', async (request, reply) => await cleanupPlausibleLogs(request, reply));
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ export interface ActivateWordpressFtp extends OnlyId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SetGlitchTipSettings extends OnlyId {
|
||||||
|
Body: {
|
||||||
|
enableOpenUserRegistration: boolean,
|
||||||
|
emailSmtpUseSsl: boolean,
|
||||||
|
emailSmtpUseTls: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export async function configureGitHubApp(request, reply) {
|
|||||||
}
|
}
|
||||||
export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promise<any> {
|
export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const buildId = cuid();
|
|
||||||
const allowedGithubEvents = ['push', 'pull_request'];
|
const allowedGithubEvents = ['push', 'pull_request'];
|
||||||
const allowedActions = ['opened', 'reopened', 'synchronize', 'closed'];
|
const allowedActions = ['opened', 'reopened', 'synchronize', 'closed'];
|
||||||
const githubEvent = request.headers['x-github-event']?.toString().toLowerCase();
|
const githubEvent = request.headers['x-github-event']?.toString().toLowerCase();
|
||||||
@@ -87,126 +87,124 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
if (!projectId || !branch) {
|
if (!projectId || !branch) {
|
||||||
throw { status: 500, message: 'Cannot parse projectId or branch from the webhook?!' }
|
throw { status: 500, message: 'Cannot parse projectId or branch from the webhook?!' }
|
||||||
}
|
}
|
||||||
const applicationFound = await getApplicationFromDBWebhook(projectId, branch);
|
const applicationsFound = await getApplicationFromDBWebhook(projectId, branch);
|
||||||
if (applicationFound) {
|
if (applicationsFound && applicationsFound.length > 0) {
|
||||||
const webhookSecret = applicationFound.gitSource.githubApp.webhookSecret || null;
|
for (const application of applicationsFound) {
|
||||||
//@ts-ignore
|
const buildId = cuid();
|
||||||
const hmac = crypto.createHmac('sha256', webhookSecret);
|
const webhookSecret = application.gitSource.githubApp.webhookSecret || null;
|
||||||
const digest = Buffer.from(
|
|
||||||
'sha256=' + hmac.update(JSON.stringify(body)).digest('hex'),
|
|
||||||
'utf8'
|
|
||||||
);
|
|
||||||
if (!isDev) {
|
|
||||||
const checksum = Buffer.from(githubSignature, 'utf8');
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) {
|
const hmac = crypto.createHmac('sha256', webhookSecret);
|
||||||
throw { status: 500, message: 'SHA256 checksum failed. Are you doing something fishy?' }
|
const digest = Buffer.from(
|
||||||
};
|
'sha256=' + hmac.update(JSON.stringify(body)).digest('hex'),
|
||||||
}
|
'utf8'
|
||||||
|
);
|
||||||
|
if (!isDev) {
|
||||||
if (githubEvent === 'push') {
|
const checksum = Buffer.from(githubSignature, 'utf8');
|
||||||
if (!applicationFound.configHash) {
|
//@ts-ignore
|
||||||
const configHash = crypto
|
if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) {
|
||||||
//@ts-ignore
|
console.log('SHA256 checksum failed. Are you doing something fishy?')
|
||||||
.createHash('sha256')
|
// throw { status: 500, message: 'SHA256 checksum failed. Are you doing something fishy?'
|
||||||
.update(
|
};
|
||||||
JSON.stringify({
|
|
||||||
buildPack: applicationFound.buildPack,
|
|
||||||
port: applicationFound.port,
|
|
||||||
exposePort: applicationFound.exposePort,
|
|
||||||
installCommand: applicationFound.installCommand,
|
|
||||||
buildCommand: applicationFound.buildCommand,
|
|
||||||
startCommand: applicationFound.startCommand
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.digest('hex');
|
|
||||||
await prisma.application.updateMany({
|
|
||||||
where: { branch, projectId },
|
|
||||||
data: { configHash }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await prisma.application.update({
|
|
||||||
where: { id: applicationFound.id },
|
|
||||||
data: { updatedAt: new Date() }
|
|
||||||
});
|
|
||||||
await prisma.build.create({
|
|
||||||
data: {
|
|
||||||
id: buildId,
|
|
||||||
applicationId: applicationFound.id,
|
|
||||||
destinationDockerId: applicationFound.destinationDocker.id,
|
|
||||||
gitSourceId: applicationFound.gitSource.id,
|
|
||||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
|
||||||
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
|
|
||||||
status: 'queued',
|
|
||||||
type: 'webhook_commit'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
message: 'Queued. Thank you!'
|
|
||||||
};
|
|
||||||
} else if (githubEvent === 'pull_request') {
|
|
||||||
const pullmergeRequestId = body.number.toString();
|
|
||||||
const pullmergeRequestAction = body.action;
|
|
||||||
const sourceBranch = body.pull_request.head.ref.includes('/') ? body.pull_request.head.ref.split('/')[2] : body.pull_request.head.ref;
|
|
||||||
if (!allowedActions.includes(pullmergeRequestAction)) {
|
|
||||||
throw { status: 500, message: 'Action not allowed.' }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applicationFound.settings.previews) {
|
if (githubEvent === 'push') {
|
||||||
if (applicationFound.destinationDockerId) {
|
if (!application.configHash) {
|
||||||
const isRunning = await checkContainer(
|
const configHash = crypto
|
||||||
{
|
//@ts-ignore
|
||||||
dockerId: applicationFound.destinationDocker.id,
|
.createHash('sha256')
|
||||||
container: applicationFound.id
|
.update(
|
||||||
}
|
JSON.stringify({
|
||||||
);
|
buildPack: application.buildPack,
|
||||||
if (!isRunning) {
|
port: application.port,
|
||||||
throw { status: 500, message: 'Application not running.' }
|
exposePort: application.exposePort,
|
||||||
}
|
installCommand: application.installCommand,
|
||||||
}
|
buildCommand: application.buildCommand,
|
||||||
if (
|
startCommand: application.startCommand
|
||||||
pullmergeRequestAction === 'opened' ||
|
})
|
||||||
pullmergeRequestAction === 'reopened' ||
|
)
|
||||||
pullmergeRequestAction === 'synchronize'
|
.digest('hex');
|
||||||
) {
|
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id: applicationFound.id },
|
where: { id: application.id },
|
||||||
data: { updatedAt: new Date() }
|
data: { configHash }
|
||||||
});
|
});
|
||||||
await prisma.build.create({
|
|
||||||
data: {
|
|
||||||
id: buildId,
|
|
||||||
pullmergeRequestId,
|
|
||||||
sourceBranch,
|
|
||||||
applicationId: applicationFound.id,
|
|
||||||
destinationDockerId: applicationFound.destinationDocker.id,
|
|
||||||
gitSourceId: applicationFound.gitSource.id,
|
|
||||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
|
||||||
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
|
|
||||||
status: 'queued',
|
|
||||||
type: 'webhook_pr'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
message: 'Queued. Thank you!'
|
|
||||||
};
|
|
||||||
} else if (pullmergeRequestAction === 'closed') {
|
|
||||||
if (applicationFound.destinationDockerId) {
|
|
||||||
const id = `${applicationFound.id}-${pullmergeRequestId}`;
|
|
||||||
await removeContainer({ id, dockerId: applicationFound.destinationDocker.id });
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
message: 'Removed preview. Thank you!'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
throw { status: 500, message: 'Pull request previews are not enabled.' }
|
await prisma.application.update({
|
||||||
|
where: { id: application.id },
|
||||||
|
data: { updatedAt: new Date() }
|
||||||
|
});
|
||||||
|
console.log(application.id)
|
||||||
|
|
||||||
|
await prisma.build.create({
|
||||||
|
data: {
|
||||||
|
id: buildId,
|
||||||
|
applicationId: application.id,
|
||||||
|
destinationDockerId: application.destinationDocker.id,
|
||||||
|
gitSourceId: application.gitSource.id,
|
||||||
|
githubAppId: application.gitSource.githubApp?.id,
|
||||||
|
gitlabAppId: application.gitSource.gitlabApp?.id,
|
||||||
|
status: 'queued',
|
||||||
|
type: 'webhook_commit'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`Webhook for ${application.name} queued.`)
|
||||||
|
|
||||||
|
} else if (githubEvent === 'pull_request') {
|
||||||
|
const pullmergeRequestId = body.number.toString();
|
||||||
|
const pullmergeRequestAction = body.action;
|
||||||
|
const sourceBranch = body.pull_request.head.ref.includes('/') ? body.pull_request.head.ref.split('/')[2] : body.pull_request.head.ref;
|
||||||
|
if (!allowedActions.includes(pullmergeRequestAction)) {
|
||||||
|
throw { status: 500, message: 'Action not allowed.' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (application.settings.previews) {
|
||||||
|
if (application.destinationDockerId) {
|
||||||
|
const isRunning = await checkContainer(
|
||||||
|
{
|
||||||
|
dockerId: application.destinationDocker.id,
|
||||||
|
container: application.id
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!isRunning) {
|
||||||
|
throw { status: 500, message: 'Application not running.' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
pullmergeRequestAction === 'opened' ||
|
||||||
|
pullmergeRequestAction === 'reopened' ||
|
||||||
|
pullmergeRequestAction === 'synchronize'
|
||||||
|
) {
|
||||||
|
await prisma.application.update({
|
||||||
|
where: { id: application.id },
|
||||||
|
data: { updatedAt: new Date() }
|
||||||
|
});
|
||||||
|
await prisma.build.create({
|
||||||
|
data: {
|
||||||
|
id: buildId,
|
||||||
|
pullmergeRequestId,
|
||||||
|
sourceBranch,
|
||||||
|
applicationId: application.id,
|
||||||
|
destinationDockerId: application.destinationDocker.id,
|
||||||
|
gitSourceId: application.gitSource.id,
|
||||||
|
githubAppId: application.gitSource.githubApp?.id,
|
||||||
|
gitlabAppId: application.gitSource.gitlabApp?.id,
|
||||||
|
status: 'queued',
|
||||||
|
type: 'webhook_pr'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
} else if (pullmergeRequestAction === 'closed') {
|
||||||
|
if (application.destinationDockerId) {
|
||||||
|
const id = `${application.id}-${pullmergeRequestId}`;
|
||||||
|
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw { status: 500, message: 'Not handled event.' }
|
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import axios from "axios";
|
|||||||
import cuid from "cuid";
|
import cuid from "cuid";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import { errorHandler, getAPIUrl, isDev, listSettings, prisma } from "../../../lib/common";
|
import { errorHandler, getAPIUrl, getUIUrl, isDev, listSettings, prisma } from "../../../lib/common";
|
||||||
import { checkContainer, removeContainer } from "../../../lib/docker";
|
import { checkContainer, removeContainer } from "../../../lib/docker";
|
||||||
import { scheduler } from "../../../lib/scheduler";
|
|
||||||
import { getApplicationFromDB, getApplicationFromDBWebhook } from "../../api/v1/applications/handlers";
|
import { getApplicationFromDB, getApplicationFromDBWebhook } from "../../api/v1/applications/handlers";
|
||||||
|
|
||||||
import type { ConfigureGitLabApp, GitLabEvents } from "./types";
|
import type { ConfigureGitLabApp, GitLabEvents } from "./types";
|
||||||
@@ -30,7 +29,7 @@ export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLab
|
|||||||
});
|
});
|
||||||
const { data } = await axios.post(`${htmlUrl}/oauth/token`, params)
|
const { data } = await axios.post(`${htmlUrl}/oauth/token`, params)
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
return reply.redirect(`${getAPIUrl()}/webhooks/success?token=${data.access_token}`)
|
return reply.redirect(`${getUIUrl()}/webhooks/success?token=${data.access_token}`)
|
||||||
}
|
}
|
||||||
return reply.redirect(`/webhooks/success?token=${data.access_token}`)
|
return reply.redirect(`/webhooks/success?token=${data.access_token}`)
|
||||||
} catch ({ status, message, ...other }) {
|
} catch ({ status, message, ...other }) {
|
||||||
@@ -40,59 +39,56 @@ export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLab
|
|||||||
export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
||||||
const { object_kind: objectKind, ref, project_id } = request.body
|
const { object_kind: objectKind, ref, project_id } = request.body
|
||||||
try {
|
try {
|
||||||
const buildId = cuid();
|
|
||||||
|
|
||||||
const allowedActions = ['opened', 'reopen', 'close', 'open', 'update'];
|
const allowedActions = ['opened', 'reopen', 'close', 'open', 'update'];
|
||||||
|
|
||||||
const webhookToken = request.headers['x-gitlab-token'];
|
const webhookToken = request.headers['x-gitlab-token'];
|
||||||
if (!webhookToken) {
|
if (!webhookToken && !isDev) {
|
||||||
throw { status: 500, message: 'Invalid webhookToken.' }
|
throw { status: 500, message: 'Invalid webhookToken.' }
|
||||||
}
|
}
|
||||||
if (objectKind === 'push') {
|
if (objectKind === 'push') {
|
||||||
const projectId = Number(project_id);
|
const projectId = Number(project_id);
|
||||||
const branch = ref.split('/')[2];
|
const branch = ref.split('/')[2];
|
||||||
const applicationFound = await getApplicationFromDBWebhook(projectId, branch);
|
const applicationsFound = await getApplicationFromDBWebhook(projectId, branch);
|
||||||
if (applicationFound) {
|
if (applicationsFound && applicationsFound.length > 0) {
|
||||||
if (!applicationFound.configHash) {
|
for (const application of applicationsFound) {
|
||||||
const configHash = crypto
|
const buildId = cuid();
|
||||||
.createHash('sha256')
|
if (!application.configHash) {
|
||||||
.update(
|
const configHash = crypto
|
||||||
JSON.stringify({
|
.createHash('sha256')
|
||||||
buildPack: applicationFound.buildPack,
|
.update(
|
||||||
port: applicationFound.port,
|
JSON.stringify({
|
||||||
exposePort: applicationFound.exposePort,
|
buildPack: application.buildPack,
|
||||||
installCommand: applicationFound.installCommand,
|
port: application.port,
|
||||||
buildCommand: applicationFound.buildCommand,
|
exposePort: application.exposePort,
|
||||||
startCommand: applicationFound.startCommand
|
installCommand: application.installCommand,
|
||||||
})
|
buildCommand: application.buildCommand,
|
||||||
)
|
startCommand: application.startCommand
|
||||||
.digest('hex');
|
})
|
||||||
await prisma.application.updateMany({
|
)
|
||||||
where: { branch, projectId },
|
.digest('hex');
|
||||||
data: { configHash }
|
await prisma.application.update({
|
||||||
|
where: { id: application.id },
|
||||||
|
data: { configHash }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await prisma.application.update({
|
||||||
|
where: { id: application.id },
|
||||||
|
data: { updatedAt: new Date() }
|
||||||
|
});
|
||||||
|
await prisma.build.create({
|
||||||
|
data: {
|
||||||
|
id: buildId,
|
||||||
|
applicationId: application.id,
|
||||||
|
destinationDockerId: application.destinationDocker.id,
|
||||||
|
gitSourceId: application.gitSource.id,
|
||||||
|
githubAppId: application.gitSource.githubApp?.id,
|
||||||
|
gitlabAppId: application.gitSource.gitlabApp?.id,
|
||||||
|
status: 'queued',
|
||||||
|
type: 'webhook_commit'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await prisma.application.update({
|
|
||||||
where: { id: applicationFound.id },
|
|
||||||
data: { updatedAt: new Date() }
|
|
||||||
});
|
|
||||||
await prisma.build.create({
|
|
||||||
data: {
|
|
||||||
id: buildId,
|
|
||||||
applicationId: applicationFound.id,
|
|
||||||
destinationDockerId: applicationFound.destinationDocker.id,
|
|
||||||
gitSourceId: applicationFound.gitSource.id,
|
|
||||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
|
||||||
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
|
|
||||||
status: 'queued',
|
|
||||||
type: 'webhook_commit'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
message: 'Queued. Thank you!'
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if (objectKind === 'merge_request') {
|
} else if (objectKind === 'merge_request') {
|
||||||
const { object_attributes: { work_in_progress: isDraft, action, source_branch: sourceBranch, target_branch: targetBranch, iid: pullmergeRequestId }, project: { id } } = request.body
|
const { object_attributes: { work_in_progress: isDraft, action, source_branch: sourceBranch, target_branch: targetBranch, iid: pullmergeRequestId }, project: { id } } = request.body
|
||||||
@@ -105,64 +101,63 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
throw { status: 500, message: 'Draft MR, do nothing.' }
|
throw { status: 500, message: 'Draft MR, do nothing.' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const applicationFound = await getApplicationFromDBWebhook(projectId, targetBranch);
|
const applicationsFound = await getApplicationFromDBWebhook(projectId, targetBranch);
|
||||||
if (applicationFound) {
|
if (applicationsFound && applicationsFound.length > 0) {
|
||||||
if (applicationFound.settings.previews) {
|
for (const application of applicationsFound) {
|
||||||
if (applicationFound.destinationDockerId) {
|
const buildId = cuid();
|
||||||
const isRunning = await checkContainer(
|
if (application.settings.previews) {
|
||||||
{
|
if (application.destinationDockerId) {
|
||||||
dockerId: applicationFound.destinationDocker.id,
|
const isRunning = await checkContainer(
|
||||||
container: applicationFound.id
|
{
|
||||||
|
dockerId: application.destinationDocker.id,
|
||||||
|
container: application.id
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!isRunning) {
|
||||||
|
throw { status: 500, message: 'Application not running.' }
|
||||||
}
|
}
|
||||||
);
|
|
||||||
if (!isRunning) {
|
|
||||||
throw { status: 500, message: 'Application not running.' }
|
|
||||||
}
|
}
|
||||||
}
|
if (!isDev && application.gitSource.gitlabApp.webhookToken !== webhookToken) {
|
||||||
if (!isDev && applicationFound.gitSource.gitlabApp.webhookToken !== webhookToken) {
|
throw { status: 500, message: 'Invalid webhookToken. Are you doing something nasty?!' }
|
||||||
throw { status: 500, message: 'Invalid webhookToken. Are you doing something nasty?!' }
|
}
|
||||||
}
|
if (
|
||||||
if (
|
action === 'opened' ||
|
||||||
action === 'opened' ||
|
action === 'reopen' ||
|
||||||
action === 'reopen' ||
|
action === 'open' ||
|
||||||
action === 'open' ||
|
action === 'update'
|
||||||
action === 'update'
|
) {
|
||||||
) {
|
await prisma.application.update({
|
||||||
await prisma.application.update({
|
where: { id: application.id },
|
||||||
where: { id: applicationFound.id },
|
data: { updatedAt: new Date() }
|
||||||
data: { updatedAt: new Date() }
|
});
|
||||||
});
|
await prisma.build.create({
|
||||||
await prisma.build.create({
|
data: {
|
||||||
data: {
|
id: buildId,
|
||||||
id: buildId,
|
pullmergeRequestId,
|
||||||
pullmergeRequestId,
|
sourceBranch,
|
||||||
sourceBranch,
|
applicationId: application.id,
|
||||||
applicationId: applicationFound.id,
|
destinationDockerId: application.destinationDocker.id,
|
||||||
destinationDockerId: applicationFound.destinationDocker.id,
|
gitSourceId: application.gitSource.id,
|
||||||
gitSourceId: applicationFound.gitSource.id,
|
githubAppId: application.gitSource.githubApp?.id,
|
||||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
gitlabAppId: application.gitSource.gitlabApp?.id,
|
||||||
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
|
status: 'queued',
|
||||||
status: 'queued',
|
type: 'webhook_mr'
|
||||||
type: 'webhook_mr'
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
message: 'Queued. Thank you!'
|
||||||
|
};
|
||||||
|
} else if (action === 'close') {
|
||||||
|
if (application.destinationDockerId) {
|
||||||
|
const id = `${application.id}-${pullmergeRequestId}`;
|
||||||
|
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
||||||
}
|
}
|
||||||
});
|
|
||||||
return {
|
|
||||||
message: 'Queued. Thank you!'
|
|
||||||
};
|
|
||||||
} else if (action === 'close') {
|
|
||||||
if (applicationFound.destinationDockerId) {
|
|
||||||
const id = `${applicationFound.id}-${pullmergeRequestId}`;
|
|
||||||
await removeContainer({ id, dockerId: applicationFound.destinationDocker.id });
|
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
message: 'Removed preview. Thank you!'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw { status: 500, message: 'Merge request previews are not enabled.' }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw { status: 500, message: 'Not handled event.' }
|
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { errorHandler, getDomain, isDev, prisma, executeDockerCmd } from "../../
|
|||||||
import { supportedServiceTypesAndVersions } from "../../../lib/services/supportedVersions";
|
import { supportedServiceTypesAndVersions } from "../../../lib/services/supportedVersions";
|
||||||
import { includeServices } from "../../../lib/services/common";
|
import { includeServices } from "../../../lib/services/common";
|
||||||
import { TraefikOtherConfiguration } from "./types";
|
import { TraefikOtherConfiguration } from "./types";
|
||||||
|
import { OnlyId } from "../../../types";
|
||||||
|
|
||||||
function configureMiddleware(
|
function configureMiddleware(
|
||||||
{ id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type },
|
{ id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type },
|
||||||
@@ -25,7 +26,30 @@ function configureMiddleware(
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (type === 'appwrite') {
|
||||||
|
traefik.http.routers[`${id}-realtime`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/v1/realtime\`)`,
|
||||||
|
service: `${`${id}-realtime`}`,
|
||||||
|
tls: {
|
||||||
|
domains: {
|
||||||
|
main: `${domain}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
middlewares: []
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
traefik.http.services[`${id}-realtime`] = {
|
||||||
|
loadbalancer: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `http://${container}-realtime:${port}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
if (isDualCerts) {
|
if (isDualCerts) {
|
||||||
traefik.http.routers[`${id}-secure`] = {
|
traefik.http.routers[`${id}-secure`] = {
|
||||||
entrypoints: ['websecure'],
|
entrypoints: ['websecure'],
|
||||||
@@ -112,6 +136,23 @@ function configureMiddleware(
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (type === 'appwrite') {
|
||||||
|
traefik.http.routers[`${id}-realtime`] = {
|
||||||
|
entrypoints: ['web'],
|
||||||
|
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/v1/realtime\`)`,
|
||||||
|
service: `${id}-realtime`,
|
||||||
|
middlewares: []
|
||||||
|
};
|
||||||
|
traefik.http.services[`${id}-realtime`] = {
|
||||||
|
loadbalancer: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `http://${container}-realtime:${port}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!isDualCerts) {
|
if (!isDualCerts) {
|
||||||
if (isWWW) {
|
if (isWWW) {
|
||||||
@@ -490,7 +531,7 @@ export async function traefikOtherConfiguration(request: FastifyRequest<TraefikO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remoteTraefikConfiguration(request: FastifyRequest) {
|
export async function remoteTraefikConfiguration(request: FastifyRequest<OnlyId>) {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
try {
|
try {
|
||||||
const traefik = {
|
const traefik = {
|
||||||
|
|||||||
@@ -36,4 +36,3 @@ export interface SaveDatabaseSettings extends OnlyId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
4
apps/i18n/.env.example
Normal file
4
apps/i18n/.env.example
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
WEBLATE_INSTANCE_URL=http://localhost
|
||||||
|
WEBLATE_COMPONENT_NAME=coolify
|
||||||
|
WEBLATE_TOKEN=
|
||||||
|
TRANSLATION_DIR=
|
||||||
1
apps/i18n/.gitignore
vendored
Normal file
1
apps/i18n/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
locales/*
|
||||||
63
apps/i18n/index.mjs
Normal file
63
apps/i18n/index.mjs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import dotenv from 'dotenv';
|
||||||
|
dotenv.config()
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import Gettext from 'node-gettext'
|
||||||
|
import { po } from 'gettext-parser'
|
||||||
|
import got from 'got';
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const weblateInstanceURL = process.env.WEBLATE_INSTANCE_URL;
|
||||||
|
const weblateComponentName = process.env.WEBLATE_COMPONENT_NAME
|
||||||
|
const token = process.env.WEBLATE_TOKEN;
|
||||||
|
|
||||||
|
const translationsDir = process.env.TRANSLATION_DIR;
|
||||||
|
const translationsPODir = './locales';
|
||||||
|
const locales = []
|
||||||
|
const domain = 'locale'
|
||||||
|
|
||||||
|
const translations = await got(`${weblateInstanceURL}/api/components/${weblateComponentName}/glossary/translations/?format=json`, {
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Token ${token}`
|
||||||
|
}
|
||||||
|
}).json()
|
||||||
|
for (const translation of translations.results) {
|
||||||
|
const code = translation.language_code
|
||||||
|
locales.push(code)
|
||||||
|
|
||||||
|
const fileUrl = translation.file_url.replace('=json', '=po')
|
||||||
|
const file = await got(fileUrl, {
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Token ${token}`
|
||||||
|
}
|
||||||
|
}).text()
|
||||||
|
fs.writeFileSync(path.join(__dirname, translationsPODir, domain + '-' + code + '.po'), file)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const gt = new Gettext()
|
||||||
|
|
||||||
|
locales.forEach((locale) => {
|
||||||
|
let json = {}
|
||||||
|
const fileName = `${domain}-${locale}.po`
|
||||||
|
const translationsFilePath = path.join(translationsPODir, fileName)
|
||||||
|
const translationsContent = fs.readFileSync(translationsFilePath)
|
||||||
|
|
||||||
|
const parsedTranslations = po.parse(translationsContent)
|
||||||
|
const a = gt.gettext(parsedTranslations)
|
||||||
|
for (const [key, value] of Object.entries(a)) {
|
||||||
|
if (key === 'translations') {
|
||||||
|
for (const [key1, value1] of Object.entries(value)) {
|
||||||
|
if (key1 !== '') {
|
||||||
|
for (const [key2, value2] of Object.entries(value1)) {
|
||||||
|
json[value2.msgctxt] = value2.msgstr[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.writeFileSync(`${translationsDir}/${locale}.json`, JSON.stringify(json))
|
||||||
|
})
|
||||||
15
apps/i18n/package.json
Normal file
15
apps/i18n/package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "i18n-converter",
|
||||||
|
"description": "Convert Weblate translations to sveltekit-i18n",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"scripts": {
|
||||||
|
"translate": "node index.mjs"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"node-gettext": "3.0.0",
|
||||||
|
"gettext-parser": "6.0.0",
|
||||||
|
"got": "12.3.1",
|
||||||
|
"dotenv": "16.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,15 +14,20 @@
|
|||||||
"format": "prettier --write --plugin-search-dir=. ."
|
"format": "prettier --write --plugin-search-dir=. ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@floating-ui/dom": "1.0.1",
|
||||||
"@playwright/test": "1.25.1",
|
"@playwright/test": "1.25.1",
|
||||||
|
"@popperjs/core": "2.11.6",
|
||||||
"@sveltejs/kit": "1.0.0-next.405",
|
"@sveltejs/kit": "1.0.0-next.405",
|
||||||
"@types/js-cookie": "3.0.2",
|
"@types/js-cookie": "3.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "5.35.1",
|
"@typescript-eslint/eslint-plugin": "5.35.1",
|
||||||
"@typescript-eslint/parser": "5.35.1",
|
"@typescript-eslint/parser": "5.35.1",
|
||||||
"autoprefixer": "10.4.8",
|
"autoprefixer": "10.4.8",
|
||||||
|
"classnames": "2.3.1",
|
||||||
"eslint": "8.22.0",
|
"eslint": "8.22.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
"eslint-plugin-svelte3": "4.0.0",
|
"eslint-plugin-svelte3": "4.0.0",
|
||||||
|
"flowbite": "1.5.2",
|
||||||
|
"flowbite-svelte": "0.26.2",
|
||||||
"postcss": "8.4.16",
|
"postcss": "8.4.16",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.7.1",
|
||||||
"prettier-plugin-svelte": "2.7.0",
|
"prettier-plugin-svelte": "2.7.0",
|
||||||
|
|||||||
32
apps/ui/src/lib/components/DocLink.svelte
Normal file
32
apps/ui/src/lib/components/DocLink.svelte
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Tooltip from './Tooltip.svelte';
|
||||||
|
export let url = 'https://docs.coollabs.io';
|
||||||
|
let id =
|
||||||
|
'cool-' +
|
||||||
|
url
|
||||||
|
.split('')
|
||||||
|
.map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))
|
||||||
|
.join('')
|
||||||
|
.slice(-16);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a {id} href={url} target="_blank" class="icons inline-block text-pink-500 cursor-pointer text-xs">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M6 4h11a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-11a1 1 0 0 1 -1 -1v-14a1 1 0 0 1 1 -1m3 0v18"
|
||||||
|
/>
|
||||||
|
<line x1="13" y1="8" x2="15" y2="8" />
|
||||||
|
<line x1="13" y1="12" x2="15" y2="12" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<Tooltip triggeredBy={`#${id}`}>See details in the documentation</Tooltip>
|
||||||
@@ -1,6 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let text: string;
|
import { onMount } from 'svelte';
|
||||||
export let customClass = 'max-w-[24rem]';
|
|
||||||
|
import Tooltip from './Tooltip.svelte';
|
||||||
|
export let explanation = '';
|
||||||
|
let id: any;
|
||||||
|
let self: any;
|
||||||
|
onMount(() => {
|
||||||
|
id = `info-${self.offsetLeft}-${self.offsetTop}`;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-2 text-xs text-stone-400 {customClass}">{@html text}</div>
|
<div {id} class="inline-block mx-2 text-pink-500 cursor-pointer" bind:this={self}>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="18"
|
||||||
|
shape-rendering="geometricPrecision"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="18"
|
||||||
|
><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z" /><path
|
||||||
|
d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3"
|
||||||
|
/><circle cx="12" cy="17" r=".5" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{#if id}
|
||||||
|
<Tooltip triggeredBy={`#${id}`}>{@html explanation}</Tooltip>
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import Explaner from './Explainer.svelte';
|
||||||
|
import Tooltip from './Tooltip.svelte';
|
||||||
|
|
||||||
|
export let id: any;
|
||||||
export let setting: any;
|
export let setting: any;
|
||||||
export let title: any;
|
export let title: any;
|
||||||
export let description: any;
|
export let description: any;
|
||||||
@@ -8,22 +10,17 @@
|
|||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let dataTooltip: any = null;
|
export let dataTooltip: any = null;
|
||||||
export let loading = false;
|
export let loading = false;
|
||||||
|
let triggeredBy = `#${id}`;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center py-4 pr-8">
|
<div class="flex items-center py-4 pr-8">
|
||||||
<div class="flex w-96 flex-col">
|
<div class="flex w-96 flex-col">
|
||||||
<div class="text-xs font-bold text-stone-100 md:text-base">{title}</div>
|
<div class="text-xs font-bold text-stone-100 md:text-base">
|
||||||
<Explainer text={description} />
|
{title}<Explaner explanation={description} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class:text-center={isCenter} class="flex justify-center">
|
||||||
class:tooltip-right={dataTooltip}
|
|
||||||
class:tooltip-primary={dataTooltip}
|
|
||||||
class:tooltip={dataTooltip}
|
|
||||||
class:text-center={isCenter}
|
|
||||||
data-tip={dataTooltip}
|
|
||||||
class="flex justify-center"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
on:click
|
on:click
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
@@ -32,6 +29,7 @@
|
|||||||
class:bg-green-600={!loading && setting}
|
class:bg-green-600={!loading && setting}
|
||||||
class:bg-stone-700={!loading && !setting}
|
class:bg-stone-700={!loading && !setting}
|
||||||
class:bg-yellow-500={loading}
|
class:bg-yellow-500={loading}
|
||||||
|
{id}
|
||||||
>
|
>
|
||||||
<span class="sr-only">Use setting</span>
|
<span class="sr-only">Use setting</span>
|
||||||
<span
|
<span
|
||||||
@@ -72,3 +70,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if dataTooltip}
|
||||||
|
<Tooltip {triggeredBy} placement="top">{dataTooltip}</Tooltip>
|
||||||
|
{/if}
|
||||||
|
|||||||
6
apps/ui/src/lib/components/SimpleExplainer.svelte
Normal file
6
apps/ui/src/lib/components/SimpleExplainer.svelte
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let text: string;
|
||||||
|
export let customClass = 'max-w-[24rem]';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="p-2 text-xs text-stone-400 {customClass}">{@html text}</div>
|
||||||
8
apps/ui/src/lib/components/Tooltip.svelte
Normal file
8
apps/ui/src/lib/components/Tooltip.svelte
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Tooltip } from 'flowbite-svelte';
|
||||||
|
export let placement = 'bottom';
|
||||||
|
export let color = 'bg-coollabs text-left';
|
||||||
|
export let triggeredBy = '#tooltip-default';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Tooltip {triggeredBy} {placement} arrow={false} {color} style="custom"><slot /></Tooltip>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 127 74"
|
viewBox="0 0 127 74"
|
||||||
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 h-8mx-auto'}
|
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 h-8 mx-auto'}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
><path
|
><path
|
||||||
d="M.825 73.993l23.244-59.47A21.85 21.85 0 0144.42.625h14.014L35.19 60.096a21.85 21.85 0 01-20.352 13.897H.825z"
|
d="M.825 73.993l23.244-59.47A21.85 21.85 0 0144.42.625h14.014L35.19 60.096a21.85 21.85 0 01-20.352 13.897H.825z"
|
||||||
|
|||||||
@@ -40,4 +40,6 @@
|
|||||||
<Icons.GlitchTip {isAbsolute} />
|
<Icons.GlitchTip {isAbsolute} />
|
||||||
{:else if type === 'searxng'}
|
{:else if type === 'searxng'}
|
||||||
<Icons.Searxng {isAbsolute} />
|
<Icons.Searxng {isAbsolute} />
|
||||||
|
{:else if type === 'weblate'}
|
||||||
|
<Icons.Weblate {isAbsolute} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
61
apps/ui/src/lib/components/svg/services/Weblate.svelte
Normal file
61
apps/ui/src/lib/components/svg/services/Weblate.svelte
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class={isAbsolute ? 'w-16 h-16 absolute top-0 left-0 -m-7' : 'w-12 h-12 mx-auto'}
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 300 300"
|
||||||
|
><linearGradient
|
||||||
|
id="a"
|
||||||
|
x1=".3965"
|
||||||
|
x2="98.808"
|
||||||
|
y1="55.253"
|
||||||
|
y2="55.253"
|
||||||
|
gradientTransform="scale(.98308 1.0172)"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
><stop stop-color="#00d2e6" offset="0" /><stop
|
||||||
|
stop-color="#2eccaa"
|
||||||
|
offset="1"
|
||||||
|
/></linearGradient
|
||||||
|
><linearGradient
|
||||||
|
id="b"
|
||||||
|
x1="49.017"
|
||||||
|
x2="99.793"
|
||||||
|
y1="137.89"
|
||||||
|
y2="113.96"
|
||||||
|
gradientTransform="scale(1.1631 .8598)"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
><stop stop-opacity="0" offset="0" /><stop offset=".51413" /><stop
|
||||||
|
stop-opacity="0"
|
||||||
|
offset="1"
|
||||||
|
/></linearGradient
|
||||||
|
><linearGradient
|
||||||
|
id="c"
|
||||||
|
x1="201.82"
|
||||||
|
x2="103.58"
|
||||||
|
y1="57.649"
|
||||||
|
y2="57.649"
|
||||||
|
gradientTransform="scale(.98308 1.0172)"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
><stop stop-color="#1fa385" offset="0" /><stop
|
||||||
|
stop-color="#2eccaa"
|
||||||
|
offset="1"
|
||||||
|
/></linearGradient
|
||||||
|
><g transform="translate(50,76)" fill-rule="evenodd"
|
||||||
|
><path
|
||||||
|
d="m127.25 111.61c-2.8884-0.0145-5.7666-0.6024-8.4797-1.7847-6.1117-2.6626-11.493-7.6912-15.872-14.495 1.2486-2.2193 2.3738-4.5173 3.3784-6.8535 4.4051-10.243 6.5-21.46 6.6607-32.593-0.0233-0.22082-0.0416-0.44244-0.0552-0.66483l-0.0121-0.57132c-0.01-4.3654-0.67459-8.7898-2.1767-12.909-1.7304-4.7458-4.4887-9.4955-8.865-11.348-0.79519-0.33595-1.6316-0.47701-2.4642-0.45737-5.5049-10.289-5.6799-20.149 0-29.537 0.10115 0 0.20619 3.9293e-4 0.30734 0.001179 6.7012 0.07387 13.34 2.1418 19.021 5.7536 15.469 9.835 23.182 29.001 23.352 47.818 2e-3 0.22083-3.9e-4 0.44126-7e-3 0.66169h0.0868c-0.0226 19.887-4.8049 40.054-14.875 56.979zm-34.3 31.216c-14.448 5.9425-31.228 5.6236-45.549-1.025-16.476-7.6476-29.065-22.512-36.818-39.479-13.262-29.022-13.566-63.715-0.98815-93.182 9.4458 3.7788 17.845-2.2397 17.845-2.2397s-0.01945 9.2605 8.9478 13.905c-9.2007 21.556-8.979 47.167 0.2412 68.173 4.4389 10.107 11.22 19.519 20.619 24.842 3.3547 1.8996 7.041 3.126 10.833 3.5862 0.01404 0.0219 0.02808 0.0439 0.04214 0.0658 6.6965 10.449 15.132 19.157 24.828 25.354z"
|
||||||
|
fill="url(#a)"
|
||||||
|
fill-rule="nonzero"
|
||||||
|
/><path
|
||||||
|
d="m127.24 111.61c-2.8869-0.0151-5.7636-0.60296-8.4755-1.7846-6.1127-2.663-11.495-7.6928-15.874-14.498 1.2494-2.2205 2.3754-4.5198 3.3806-6.8572 1.3282-3.0884 2.4463-6.2648 3.3644-9.501 2.128-7.4978 30.382 2.0181 26.072 14.371-2.2239 6.373-5.0394 12.509-8.4675 18.27zm-34.302 31.212c-14.446 5.9396-31.224 5.6198-45.543-1.0278-16.476-7.6476 0.44739-33.303 9.8465-27.981 3.3533 1.8988 7.0378 3.125 10.828 3.5856 0.01567 0.0245 0.03135 0.049 0.04704 0.0735 6.695 10.447 15.128 19.153 24.821 25.349z"
|
||||||
|
fill="url(#b)"
|
||||||
|
opacity=".3"
|
||||||
|
/><path
|
||||||
|
d="m56.762 54.628c-0.0066-0.22043-0.0093-0.44086-7e-3 -0.66169 0.17001-18.817 7.8827-37.983 23.352-47.818 5.6811-3.6118 12.32-5.6798 19.021-5.7536 0.10115-7.8585e-4 0.20619-0.001179 0.30734-0.001179v29.537c-0.83254-0.01965-1.669 0.12141-2.4642 0.45737-4.3763 1.8523-7.1345 6.602-8.865 11.348-1.5021 4.1191-2.1669 8.5434-2.1767 12.909l-0.01206 0.57132c-0.01362 0.2224-0.0319 0.44401-0.05524 0.66483 0.16067 11.134 2.2556 22.35 6.6607 32.593 4.9334 11.472 12.775 22.025 23.847 26.849 8.3526 3.6397 17.612 2.7811 25.182-1.5057 9.3991-5.3226 16.18-14.734 20.619-24.842 9.2202-21.006 9.4419-46.617 0.24121-68.173 8.9673-4.6444 8.9478-13.905 8.9478-13.905s8.3993 6.0185 17.845 2.2397c12.578 29.466 12.274 64.16-0.98815 93.182-7.7535 16.967-20.343 31.831-36.818 39.479-14.667 6.809-31.913 6.9792-46.591 0.58389-13.19-5.7489-23.918-16.106-31.637-28.15-11.179-17.443-16.472-38.678-16.496-59.604z"
|
||||||
|
fill="url(#c)"
|
||||||
|
fill-rule="nonzero"
|
||||||
|
/></g
|
||||||
|
></svg
|
||||||
|
>
|
||||||
@@ -16,4 +16,5 @@ export { default as Fider } from './Fider.svelte';
|
|||||||
export { default as Appwrite } from './Appwrite.svelte';
|
export { default as Appwrite } from './Appwrite.svelte';
|
||||||
export { default as Moodle } from './Moodle.svelte';
|
export { default as Moodle } from './Moodle.svelte';
|
||||||
export { default as GlitchTip } from './GlitchTip.svelte';
|
export { default as GlitchTip } from './GlitchTip.svelte';
|
||||||
export { default as Searxng } from './Searxng.svelte';
|
export { default as Searxng } from './Searxng.svelte';
|
||||||
|
export { default as Weblate } from './Weblate.svelte';
|
||||||
@@ -209,7 +209,7 @@
|
|||||||
"expose_a_port": "Expose a port",
|
"expose_a_port": "Expose a port",
|
||||||
"enable_preview_deploy_mr_pr_requests": "Enable preview deployments from pull or merge requests.",
|
"enable_preview_deploy_mr_pr_requests": "Enable preview deployments from pull or merge requests.",
|
||||||
"debug_logs": "Debug Logs",
|
"debug_logs": "Debug Logs",
|
||||||
"enable_debug_log_during_build": "Enable debug logs during build phase.<br><span class='text-red-500 font-bold'>Sensitive information</span> could be visible and saved in logs.",
|
"enable_debug_log_during_build": "Enable debug logs during build phase.<br><span class='text-settings font-bold'>Sensitive information</span> could be visible and saved in logs.",
|
||||||
"cant_activate_auto_deploy_without_repo": "Cannot activate automatic deployments until only one application is defined for this repository / branch.",
|
"cant_activate_auto_deploy_without_repo": "Cannot activate automatic deployments until only one application is defined for this repository / branch.",
|
||||||
"no_applications_found": "No applications found",
|
"no_applications_found": "No applications found",
|
||||||
"secret__batch_dot_env": "Paste .env file",
|
"secret__batch_dot_env": "Paste .env file",
|
||||||
@@ -275,7 +275,7 @@
|
|||||||
"application_id": "Application ID",
|
"application_id": "Application ID",
|
||||||
"group_name": "Group Name",
|
"group_name": "Group Name",
|
||||||
"oauth_id": "OAuth ID",
|
"oauth_id": "OAuth ID",
|
||||||
"oauth_id_explainer": "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.",
|
"oauth_id_explainer": "The OAuth ID is the unique identifier of the GitLab application. <br>You can find it <span class='font-bold text-settings' >in the URL</span> of your GitLab OAuth Application.",
|
||||||
"register_oauth_gitlab": "Register new OAuth application on GitLab",
|
"register_oauth_gitlab": "Register new OAuth application on GitLab",
|
||||||
"gitlab": {
|
"gitlab": {
|
||||||
"self_hosted": "Instance-wide application (self-hosted)",
|
"self_hosted": "Instance-wide application (self-hosted)",
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
$appSession.version = baseSettings.version;
|
$appSession.version = baseSettings.version;
|
||||||
$appSession.whiteLabeled = baseSettings.whiteLabeled;
|
$appSession.whiteLabeled = baseSettings.whiteLabeled;
|
||||||
$appSession.whiteLabeledDetails.icon = baseSettings.whiteLabeledIcon;
|
$appSession.whiteLabeledDetails.icon = baseSettings.whiteLabeledIcon;
|
||||||
$appSession.supportedServiceTypesAndVersions = supportedServiceTypesAndVersions
|
$appSession.supportedServiceTypesAndVersions = supportedServiceTypesAndVersions;
|
||||||
|
|
||||||
export let userId: string;
|
export let userId: string;
|
||||||
export let teamId: string;
|
export let teamId: string;
|
||||||
@@ -88,6 +88,7 @@
|
|||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { appSession } from '$lib/store';
|
import { appSession } from '$lib/store';
|
||||||
import Toasts from '$lib/components/Toasts.svelte';
|
import Toasts from '$lib/components/Toasts.svelte';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
|
||||||
if (userId) $appSession.userId = userId;
|
if (userId) $appSession.userId = userId;
|
||||||
if (teamId) $appSession.teamId = teamId;
|
if (teamId) $appSession.teamId = teamId;
|
||||||
@@ -132,12 +133,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="flex flex-col space-y-2 py-2" class:mt-2={$appSession.whiteLabeled}>
|
<div class="flex flex-col space-y-2 py-2" class:mt-2={$appSession.whiteLabeled}>
|
||||||
<a
|
<a
|
||||||
|
id="dashboard"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/"
|
href="/"
|
||||||
class="icons tooltip tooltip-primary tooltip-right bg-coolgray-200 hover:text-white"
|
class="icons bg-coolgray-200 hover:text-white"
|
||||||
class:text-white={$page.url.pathname === '/'}
|
class:text-white={$page.url.pathname === '/'}
|
||||||
class:bg-coolgray-500={$page.url.pathname === '/'}
|
class:bg-coolgray-500={$page.url.pathname === '/'}
|
||||||
data-tip="Dashboard"
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -156,12 +157,13 @@
|
|||||||
<path d="M16 15c-2.21 1.333 -5.792 1.333 -8 0" />
|
<path d="M16 15c-2.21 1.333 -5.792 1.333 -8 0" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<div class="border-t border-stone-700" />
|
|
||||||
|
|
||||||
|
<div class="border-t border-stone-700" />
|
||||||
<a
|
<a
|
||||||
|
id="applications"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/applications"
|
href="/applications"
|
||||||
class="icons tooltip tooltip-primary tooltip-right bg-coolgray-200"
|
class="icons bg-coolgray-200"
|
||||||
class:text-applications={$page.url.pathname.startsWith('/applications') ||
|
class:text-applications={$page.url.pathname.startsWith('/applications') ||
|
||||||
$page.url.pathname.startsWith('/new/application')}
|
$page.url.pathname.startsWith('/new/application')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/applications') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/applications') ||
|
||||||
@@ -186,10 +188,12 @@
|
|||||||
<line x1="17" y1="4" x2="17" y2="10" />
|
<line x1="17" y1="4" x2="17" y2="10" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
|
id="sources"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/sources"
|
href="/sources"
|
||||||
class="icons tooltip tooltip-primary tooltip-right bg-coolgray-200"
|
class="icons bg-coolgray-200"
|
||||||
class:text-sources={$page.url.pathname.startsWith('/sources') ||
|
class:text-sources={$page.url.pathname.startsWith('/sources') ||
|
||||||
$page.url.pathname.startsWith('/new/source')}
|
$page.url.pathname.startsWith('/new/source')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/sources') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/sources') ||
|
||||||
@@ -216,9 +220,10 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
|
id="destinations"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/destinations"
|
href="/destinations"
|
||||||
class="icons tooltip tooltip-primary tooltip-right bg-coolgray-200"
|
class="icons bg-coolgray-200"
|
||||||
class:text-destinations={$page.url.pathname.startsWith('/destinations') ||
|
class:text-destinations={$page.url.pathname.startsWith('/destinations') ||
|
||||||
$page.url.pathname.startsWith('/new/destination')}
|
$page.url.pathname.startsWith('/new/destination')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/destinations') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/destinations') ||
|
||||||
@@ -251,9 +256,10 @@
|
|||||||
</a>
|
</a>
|
||||||
<div class="border-t border-stone-700" />
|
<div class="border-t border-stone-700" />
|
||||||
<a
|
<a
|
||||||
|
id="databases"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/databases"
|
href="/databases"
|
||||||
class="icons tooltip tooltip-primary tooltip-right bg-coolgray-200"
|
class="icons bg-coolgray-200"
|
||||||
class:text-databases={$page.url.pathname.startsWith('/databases') ||
|
class:text-databases={$page.url.pathname.startsWith('/databases') ||
|
||||||
$page.url.pathname.startsWith('/new/database')}
|
$page.url.pathname.startsWith('/new/database')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/databases') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/databases') ||
|
||||||
@@ -277,9 +283,10 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
|
id="services"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/services"
|
href="/services"
|
||||||
class="icons tooltip tooltip-primary tooltip-right bg-coolgray-200"
|
class="icons bg-coolgray-200"
|
||||||
class:text-services={$page.url.pathname.startsWith('/services') ||
|
class:text-services={$page.url.pathname.startsWith('/services') ||
|
||||||
$page.url.pathname.startsWith('/new/service')}
|
$page.url.pathname.startsWith('/new/service')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/services') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/services') ||
|
||||||
@@ -306,9 +313,10 @@
|
|||||||
<UpdateAvailable />
|
<UpdateAvailable />
|
||||||
<div class="flex flex-col space-y-2 py-2">
|
<div class="flex flex-col space-y-2 py-2">
|
||||||
<a
|
<a
|
||||||
|
id="iam"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/iam"
|
href="/iam"
|
||||||
class="icons tooltip tooltip-primary tooltip-right bg-coolgray-200"
|
class="icons bg-coolgray-200"
|
||||||
class:text-iam={$page.url.pathname.startsWith('/iam')}
|
class:text-iam={$page.url.pathname.startsWith('/iam')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/iam')}
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/iam')}
|
||||||
data-tip="IAM"
|
data-tip="IAM"
|
||||||
@@ -330,9 +338,10 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
|
id="settings"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href={$appSession.teamId === '0' ? '/settings/global' : '/settings/ssh-keys'}
|
href={$appSession.teamId === '0' ? '/settings/global' : '/settings/ssh-keys'}
|
||||||
class="icons tooltip tooltip-primary tooltip-right bg-coolgray-200"
|
class="icons bg-coolgray-200"
|
||||||
class:text-settings={$page.url.pathname.startsWith('/settings')}
|
class:text-settings={$page.url.pathname.startsWith('/settings')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
|
||||||
data-tip="Settings"
|
data-tip="Settings"
|
||||||
@@ -356,7 +365,8 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="icons tooltip tooltip-primary tooltip-right bg-coolgray-200 hover:text-error"
|
id="logout"
|
||||||
|
class="icons bg-coolgray-200 hover:text-error"
|
||||||
data-tip="Logout"
|
data-tip="Logout"
|
||||||
on:click={logout}
|
on:click={logout}
|
||||||
>
|
>
|
||||||
@@ -396,7 +406,20 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<main>
|
<main>
|
||||||
<div class="px-20">
|
<div class="pl-14 lg:px-20">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<Tooltip triggeredBy="#dashboard" placement="right">Dashboard</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#applications" placement="right" color="bg-applications">Applications</Tooltip
|
||||||
|
>
|
||||||
|
<Tooltip triggeredBy="#sources" placement="right" color="bg-sources">Git Sources</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#destinations" placement="right" color="bg-destinations">Destinations</Tooltip
|
||||||
|
>
|
||||||
|
<Tooltip triggeredBy="#databases" placement="right" color="bg-databases">Databases</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#services" placement="right" color="bg-services">Services</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#iam" placement="right" color="bg-iam">IAM</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#settings" placement="right" color="bg-settings text-black">Settings</Tooltip
|
||||||
|
>
|
||||||
|
<Tooltip triggeredBy="#logout" placement="right" color="bg-red-600">Logout</Tooltip>
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
|
|
||||||
export let setting: any;
|
|
||||||
export let title: any;
|
|
||||||
export let description: any;
|
|
||||||
export let isCenter = true;
|
|
||||||
export let disabled = false;
|
|
||||||
export let dataTooltip: any = null;
|
|
||||||
export let loading = false;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex items-center py-4 pr-8">
|
|
||||||
<div class="flex w-96 flex-col">
|
|
||||||
<div class="text-xs font-bold text-stone-100 md:text-base">{title}</div>
|
|
||||||
<Explainer text={description} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class:tooltip={dataTooltip}
|
|
||||||
class:text-center={isCenter}
|
|
||||||
data-tip={dataTooltip}
|
|
||||||
class="flex justify-center"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
on:click
|
|
||||||
aria-pressed="false"
|
|
||||||
class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
|
||||||
class:opacity-50={disabled || loading}
|
|
||||||
class:bg-green-600={!loading && setting}
|
|
||||||
class:bg-stone-700={!loading && !setting}
|
|
||||||
class:bg-yellow-500={loading}
|
|
||||||
>
|
|
||||||
<span class="sr-only">Use setting</span>
|
|
||||||
<span
|
|
||||||
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out"
|
|
||||||
class:translate-x-5={setting}
|
|
||||||
class:translate-x-0={!setting}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
|
|
||||||
class:opacity-0={setting}
|
|
||||||
class:opacity-100={!setting}
|
|
||||||
class:animate-spin={loading}
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
|
|
||||||
<path
|
|
||||||
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out"
|
|
||||||
aria-hidden="true"
|
|
||||||
class:opacity-100={setting}
|
|
||||||
class:opacity-0={!setting}
|
|
||||||
class:animate-spin={loading}
|
|
||||||
>
|
|
||||||
<svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
|
|
||||||
<path
|
|
||||||
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -62,10 +62,10 @@
|
|||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { appSession, disabledButton, status, location, setLocation, addToast } from '$lib/store';
|
import { appSession, disabledButton, status, location, setLocation, addToast } from '$lib/store';
|
||||||
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
||||||
import Loading from '$lib/components/Loading.svelte';
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
|
||||||
let loading = false;
|
|
||||||
let statusInterval: any;
|
let statusInterval: any;
|
||||||
|
let forceDelete = false;
|
||||||
$disabledButton =
|
$disabledButton =
|
||||||
!$appSession.isAdmin ||
|
!$appSession.isAdmin ||
|
||||||
(!application.fqdn && !application.settings.isBot) ||
|
(!application.fqdn && !application.settings.isBot) ||
|
||||||
@@ -78,7 +78,10 @@
|
|||||||
|
|
||||||
async function handleDeploySubmit(forceRebuild = false) {
|
async function handleDeploySubmit(forceRebuild = false) {
|
||||||
try {
|
try {
|
||||||
const { buildId } = await post(`/applications/${id}/deploy`, { ...application, forceRebuild });
|
const { buildId } = await post(`/applications/${id}/deploy`, {
|
||||||
|
...application,
|
||||||
|
forceRebuild
|
||||||
|
});
|
||||||
addToast({
|
addToast({
|
||||||
message: $t('application.deployment_queued'),
|
message: $t('application.deployment_queued'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
@@ -95,25 +98,51 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteApplication(name: string) {
|
async function deleteApplication(name: string, force: boolean) {
|
||||||
const sure = confirm($t('application.confirm_to_delete', { name }));
|
const sure = confirm($t('application.confirm_to_delete', { name }));
|
||||||
if (sure) {
|
if (sure) {
|
||||||
loading = true;
|
$status.application.initialLoading = true;
|
||||||
try {
|
try {
|
||||||
await del(`/applications/${id}`, { id });
|
await del(`/applications/${id}`, { id, force });
|
||||||
return await goto(`/applications`);
|
return await goto(`/applications`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.message.startsWith(`Command failed: SSH_AUTH_SOCK=/tmp/ssh-agent.pid`)) {
|
||||||
|
forceDelete = true;
|
||||||
|
}
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
$status.application.initialLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function restartApplication() {
|
||||||
|
try {
|
||||||
|
$status.application.initialLoading = true;
|
||||||
|
$status.application.loading = true;
|
||||||
|
await post(`/applications/${id}/restart`, {});
|
||||||
|
addToast({
|
||||||
|
type: 'success',
|
||||||
|
message: 'Restart successful.'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
$status.application.initialLoading = false;
|
||||||
|
$status.application.loading = false;
|
||||||
|
await getStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
async function stopApplication() {
|
async function stopApplication() {
|
||||||
try {
|
try {
|
||||||
loading = true;
|
$status.application.initialLoading = true;
|
||||||
|
// $status.application.loading = true;
|
||||||
await post(`/applications/${id}/stop`, {});
|
await post(`/applications/${id}/stop`, {});
|
||||||
return window.location.reload();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
$status.application.initialLoading = false;
|
||||||
|
// $status.application.loading = false;
|
||||||
|
await getStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getStatus() {
|
async function getStatus() {
|
||||||
@@ -128,6 +157,9 @@
|
|||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
$status.application.initialLoading = true;
|
$status.application.initialLoading = true;
|
||||||
|
$status.application.isRunning = false;
|
||||||
|
$status.application.isExited = false;
|
||||||
|
$status.application.loading = false;
|
||||||
$location = null;
|
$location = null;
|
||||||
clearInterval(statusInterval);
|
clearInterval(statusInterval);
|
||||||
});
|
});
|
||||||
@@ -152,371 +184,396 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="nav-side">
|
<nav class="nav-side">
|
||||||
{#if loading}
|
{#if $location}
|
||||||
<Loading fullscreen cover />
|
<a
|
||||||
{:else}
|
id="open"
|
||||||
{#if $location}
|
href={$location}
|
||||||
<a
|
target="_blank"
|
||||||
href={$location}
|
class="icons flex items-center bg-transparent text-sm"
|
||||||
target="_blank"
|
><svg
|
||||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
><svg
|
class="h-6 w-6"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
viewBox="0 0 24 24"
|
||||||
class="h-6 w-6"
|
stroke-width="1.5"
|
||||||
viewBox="0 0 24 24"
|
stroke="currentColor"
|
||||||
stroke-width="1.5"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke-linecap="round"
|
||||||
fill="none"
|
stroke-linejoin="round"
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
|
||||||
<line x1="10" y1="14" x2="20" y2="4" />
|
|
||||||
<polyline points="15 4 20 4 20 9" />
|
|
||||||
</svg></a
|
|
||||||
>
|
>
|
||||||
<div class="border border-coolgray-500 h-8" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||||
{/if}
|
<line x1="10" y1="14" x2="20" y2="4" />
|
||||||
|
<polyline points="15 4 20 4 20 9" />
|
||||||
{#if $status.application.isExited}
|
</svg></a
|
||||||
<a
|
>
|
||||||
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
<Tooltip triggeredBy="#open">Open</Tooltip>
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center text-error"
|
|
||||||
data-tip="Application exited with an error!"
|
|
||||||
sveltekit:prefetch
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentcolor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path
|
|
||||||
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
|
|
||||||
/>
|
|
||||||
<line x1="12" y1="8" x2="12" y2="12" />
|
|
||||||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
{#if $status.application.initialLoading}
|
|
||||||
<button
|
|
||||||
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
|
||||||
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
|
||||||
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
|
||||||
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
|
||||||
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
|
||||||
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{:else if $status.application.isRunning}
|
|
||||||
<button
|
|
||||||
on:click={stopApplication}
|
|
||||||
type="submit"
|
|
||||||
disabled={$disabledButton}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-error"
|
|
||||||
data-tip={$appSession.isAdmin
|
|
||||||
? $t('application.stop_application')
|
|
||||||
: $t('application.permission_denied_stop_application')}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<rect x="6" y="5" width="4" height="14" rx="1" />
|
|
||||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<form on:submit|preventDefault={() => handleDeploySubmit(true)}>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={$disabledButton}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2"
|
|
||||||
data-tip={$appSession.isAdmin
|
|
||||||
? 'Force Rebuild Application'
|
|
||||||
: 'You do not have permission to rebuild application.'}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path
|
|
||||||
d="M16.3 5h.7a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h5l-2.82 -2.82m0 5.64l2.82 -2.82"
|
|
||||||
transform="rotate(-45 12 12)"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{:else}
|
|
||||||
<form on:submit|preventDefault={() => handleDeploySubmit(false)}>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={$disabledButton}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-success"
|
|
||||||
data-tip={$appSession.isAdmin
|
|
||||||
? 'Deploy'
|
|
||||||
: 'You do not have permission to deploy application.'}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M7 4v16l13 -8z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="border border-coolgray-500 h-8" />
|
<div class="border border-coolgray-500 h-8" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $status.application.isExited}
|
||||||
<a
|
<a
|
||||||
href={!$disabledButton ? `/applications/${id}` : null}
|
id="applicationerror"
|
||||||
|
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
||||||
|
class="icons bg-transparent text-sm flex items-center text-error"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
class="hover:text-yellow-500 rounded"
|
|
||||||
class:text-yellow-500={$page.url.pathname === `/applications/${id}`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<svg
|
||||||
disabled={$disabledButton}
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
class="w-6 h-6"
|
||||||
data-tip="Configurations"
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentcolor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<svg
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path
|
||||||
class="h-6 w-6"
|
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
|
||||||
viewBox="0 0 24 24"
|
/>
|
||||||
stroke-width="1.5"
|
<line x1="12" y1="8" x2="12" y2="12" />
|
||||||
stroke="currentColor"
|
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||||
fill="none"
|
</svg>
|
||||||
stroke-linecap="round"
|
</a>
|
||||||
stroke-linejoin="round"
|
<Tooltip triggeredBy="#applicationerror">Application exited with an error!</Tooltip>
|
||||||
>
|
{/if}
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
{#if $status.application.initialLoading}
|
||||||
<rect x="4" y="8" width="4" height="4" />
|
<button
|
||||||
<line x1="6" y1="4" x2="6" y2="8" />
|
class="icons flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
||||||
<line x1="6" y1="12" x2="6" y2="20" />
|
|
||||||
<rect x="10" y="14" width="4" height="4" />
|
|
||||||
<line x1="12" y1="4" x2="12" y2="14" />
|
|
||||||
<line x1="12" y1="18" x2="12" y2="20" />
|
|
||||||
<rect x="16" y="5" width="4" height="4" />
|
|
||||||
<line x1="18" y1="4" x2="18" y2="5" />
|
|
||||||
<line x1="18" y1="9" x2="18" y2="20" />
|
|
||||||
</svg></button
|
|
||||||
></a
|
|
||||||
>
|
>
|
||||||
<a
|
<svg
|
||||||
href={!$disabledButton ? `/applications/${id}/secrets` : null}
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
sveltekit:prefetch
|
class="h-6 w-6"
|
||||||
class="hover:text-pink-500 rounded"
|
viewBox="0 0 24 24"
|
||||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/secrets`}
|
stroke-width="1.5"
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`}
|
stroke="currentColor"
|
||||||
>
|
fill="none"
|
||||||
<button
|
stroke-linecap="round"
|
||||||
disabled={$disabledButton}
|
stroke-linejoin="round"
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
|
||||||
data-tip="Secrets"
|
|
||||||
>
|
>
|
||||||
<svg
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
||||||
class="w-6 h-6"
|
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
||||||
viewBox="0 0 24 24"
|
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
||||||
stroke-width="1.5"
|
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
||||||
stroke="currentColor"
|
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
||||||
fill="none"
|
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
||||||
stroke-linecap="round"
|
</svg>
|
||||||
stroke-linejoin="round"
|
</button>
|
||||||
>
|
{:else if $status.application.isRunning}
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<button
|
||||||
<path
|
id="stop"
|
||||||
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
|
on:click={stopApplication}
|
||||||
/>
|
type="submit"
|
||||||
<circle cx="12" cy="11" r="1" />
|
disabled={$disabledButton}
|
||||||
<line x1="12" y1="12" x2="12" y2="14.5" />
|
class="icons bg-transparent text-sm flex items-center space-x-2 text-error"
|
||||||
</svg></button
|
|
||||||
></a
|
|
||||||
>
|
>
|
||||||
<a
|
<svg
|
||||||
href={!$disabledButton ? `/applications/${id}/storages` : null}
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
sveltekit:prefetch
|
class="w-6 h-6"
|
||||||
class="hover:text-pink-500 rounded"
|
viewBox="0 0 24 24"
|
||||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/storages`}
|
stroke-width="1.5"
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storages`}
|
stroke="currentColor"
|
||||||
>
|
fill="none"
|
||||||
<button
|
stroke-linecap="round"
|
||||||
disabled={$disabledButton}
|
stroke-linejoin="round"
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
|
||||||
data-tip="Persistent Storages"
|
|
||||||
>
|
>
|
||||||
<svg
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<rect x="6" y="5" width="4" height="14" rx="1" />
|
||||||
class="w-6 h-6"
|
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||||
viewBox="0 0 24 24"
|
</svg>
|
||||||
stroke-width="1.5"
|
</button>
|
||||||
stroke="currentColor"
|
<Tooltip triggeredBy="#stop">Stop</Tooltip>
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
|
||||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
|
|
||||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
|
||||||
</svg>
|
|
||||||
</button></a
|
|
||||||
>
|
|
||||||
{#if !application.settings.isBot}
|
|
||||||
<a
|
|
||||||
href={!$disabledButton ? `/applications/${id}/previews` : null}
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-orange-500 rounded"
|
|
||||||
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
disabled={$disabledButton}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
|
||||||
data-tip="Previews"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<circle cx="7" cy="18" r="2" />
|
|
||||||
<circle cx="7" cy="6" r="2" />
|
|
||||||
<circle cx="17" cy="12" r="2" />
|
|
||||||
<line x1="7" y1="8" x2="7" y2="16" />
|
|
||||||
<path d="M7 8a4 4 0 0 0 4 4h4" />
|
|
||||||
</svg></button
|
|
||||||
></a
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
<div class="border border-coolgray-500 h-8" />
|
|
||||||
<a
|
|
||||||
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-sky-500 rounded"
|
|
||||||
class:text-sky-500={$page.url.pathname === `/applications/${id}/logs`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
disabled={$disabledButton || !$status.application.isRunning}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
|
||||||
data-tip={$t('application.logs')}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
|
||||||
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
|
||||||
<line x1="3" y1="6" x2="3" y2="19" />
|
|
||||||
<line x1="12" y1="6" x2="12" y2="19" />
|
|
||||||
<line x1="21" y1="6" x2="21" y2="19" />
|
|
||||||
</svg>
|
|
||||||
</button></a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href={!$disabledButton ? `/applications/${id}/logs/build` : null}
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-red-500 rounded"
|
|
||||||
class:text-red-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
disabled={$disabledButton}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
|
||||||
data-tip="Build Logs"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<circle cx="19" cy="13" r="2" />
|
|
||||||
<circle cx="4" cy="17" r="2" />
|
|
||||||
<circle cx="13" cy="17" r="2" />
|
|
||||||
<line x1="13" y1="19" x2="4" y2="19" />
|
|
||||||
<line x1="4" y1="15" x2="13" y2="15" />
|
|
||||||
<path d="M8 12v-5h2a3 3 0 0 1 3 3v5" />
|
|
||||||
<path d="M5 15v-2a1 1 0 0 1 1 -1h7" />
|
|
||||||
<path d="M19 11v-7l-6 7" />
|
|
||||||
</svg>
|
|
||||||
</button></a
|
|
||||||
>
|
|
||||||
<div class="border border-coolgray-500 h-8" />
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
on:click={() => deleteApplication(application.name)}
|
id="restart"
|
||||||
|
on:click={restartApplication}
|
||||||
|
type="submit"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent text-sm flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" />
|
||||||
|
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<Tooltip triggeredBy="#restart">Restart (useful to change secrets)</Tooltip>
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={() => handleDeploySubmit(true)}>
|
||||||
|
<button
|
||||||
|
id="forceredeploy"
|
||||||
|
type="submit"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent text-sm flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M16.3 5h.7a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h5l-2.82 -2.82m0 5.64l2.82 -2.82"
|
||||||
|
transform="rotate(-45 12 12)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<Tooltip triggeredBy="#forceredeploy">Force redeploy (without cache)</Tooltip>
|
||||||
|
</form>
|
||||||
|
{:else}
|
||||||
|
<form on:submit|preventDefault={() => handleDeploySubmit(false)}>
|
||||||
|
<button
|
||||||
|
id="deploy"
|
||||||
|
type="submit"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent text-sm flex items-center space-x-2 text-success"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M7 4v16l13 -8z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<Tooltip triggeredBy="#deploy">Deploy</Tooltip>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="border border-coolgray-500 h-8" />
|
||||||
|
<a
|
||||||
|
id="configurations"
|
||||||
|
href={!$disabledButton ? `/applications/${id}` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-yellow-500 rounded"
|
||||||
|
class:text-yellow-500={$page.url.pathname === `/applications/${id}`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}`}
|
||||||
|
>
|
||||||
|
<button disabled={$disabledButton} class="icons bg-transparent text-sm">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<rect x="4" y="8" width="4" height="4" />
|
||||||
|
<line x1="6" y1="4" x2="6" y2="8" />
|
||||||
|
<line x1="6" y1="12" x2="6" y2="20" />
|
||||||
|
<rect x="10" y="14" width="4" height="4" />
|
||||||
|
<line x1="12" y1="4" x2="12" y2="14" />
|
||||||
|
<line x1="12" y1="18" x2="12" y2="20" />
|
||||||
|
<rect x="16" y="5" width="4" height="4" />
|
||||||
|
<line x1="18" y1="4" x2="18" y2="5" />
|
||||||
|
<line x1="18" y1="9" x2="18" y2="20" />
|
||||||
|
</svg></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
id="secrets"
|
||||||
|
href={!$disabledButton ? `/applications/${id}/secrets` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-pink-500 rounded"
|
||||||
|
class:text-pink-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||||
|
>
|
||||||
|
<button disabled={$disabledButton} class="icons bg-transparent text-sm">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
|
||||||
|
/>
|
||||||
|
<circle cx="12" cy="11" r="1" />
|
||||||
|
<line x1="12" y1="12" x2="12" y2="14.5" />
|
||||||
|
</svg></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
id="persistentstorages"
|
||||||
|
href={!$disabledButton ? `/applications/${id}/storages` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-pink-500 rounded"
|
||||||
|
class:text-pink-500={$page.url.pathname === `/applications/${id}/storages`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storages`}
|
||||||
|
>
|
||||||
|
<button disabled={$disabledButton} class="icons bg-transparent text-sm">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
||||||
|
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
|
||||||
|
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
||||||
|
</svg>
|
||||||
|
</button></a
|
||||||
|
>
|
||||||
|
{#if !application.settings.isBot}
|
||||||
|
<a
|
||||||
|
id="previews"
|
||||||
|
href={!$disabledButton ? `/applications/${id}/previews` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-orange-500 rounded"
|
||||||
|
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||||
|
>
|
||||||
|
<button disabled={$disabledButton} class="icons bg-transparent text-sm">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<circle cx="7" cy="18" r="2" />
|
||||||
|
<circle cx="7" cy="6" r="2" />
|
||||||
|
<circle cx="17" cy="12" r="2" />
|
||||||
|
<line x1="7" y1="8" x2="7" y2="16" />
|
||||||
|
<path d="M7 8a4 4 0 0 0 4 4h4" />
|
||||||
|
</svg></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
<div class="border border-coolgray-500 h-8" />
|
||||||
|
<a
|
||||||
|
id="applicationlogs"
|
||||||
|
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-sky-500 rounded"
|
||||||
|
class:text-sky-500={$page.url.pathname === `/applications/${id}/logs`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
disabled={$disabledButton || !$status.application.isRunning}
|
||||||
|
class="icons bg-transparent text-sm"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
||||||
|
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
||||||
|
<line x1="3" y1="6" x2="3" y2="19" />
|
||||||
|
<line x1="12" y1="6" x2="12" y2="19" />
|
||||||
|
<line x1="21" y1="6" x2="21" y2="19" />
|
||||||
|
</svg>
|
||||||
|
</button></a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
id="buildlogs"
|
||||||
|
href={!$disabledButton ? `/applications/${id}/logs/build` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-red-500 rounded"
|
||||||
|
class:text-red-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
||||||
|
>
|
||||||
|
<button disabled={$disabledButton} class="icons bg-transparent text-sm">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<circle cx="19" cy="13" r="2" />
|
||||||
|
<circle cx="4" cy="17" r="2" />
|
||||||
|
<circle cx="13" cy="17" r="2" />
|
||||||
|
<line x1="13" y1="19" x2="4" y2="19" />
|
||||||
|
<line x1="4" y1="15" x2="13" y2="15" />
|
||||||
|
<path d="M8 12v-5h2a3 3 0 0 1 3 3v5" />
|
||||||
|
<path d="M5 15v-2a1 1 0 0 1 1 -1h7" />
|
||||||
|
<path d="M19 11v-7l-6 7" />
|
||||||
|
</svg>
|
||||||
|
</button></a
|
||||||
|
>
|
||||||
|
<div class="border border-coolgray-500 h-8" />
|
||||||
|
|
||||||
|
{#if forceDelete}
|
||||||
|
<button
|
||||||
|
on:click={() => deleteApplication(application.name, true)}
|
||||||
|
type="submit"
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
|
class:bg-red-600={$appSession.isAdmin}
|
||||||
|
class:hover:bg-red-500={$appSession.isAdmin}
|
||||||
|
class="icons bg-transparent text-sm"
|
||||||
|
>
|
||||||
|
Force Delete
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
id="delete"
|
||||||
|
on:click={() => deleteApplication(application.name, false)}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!$appSession.isAdmin}
|
disabled={!$appSession.isAdmin}
|
||||||
class:hover:text-red-500={$appSession.isAdmin}
|
class:hover:text-red-500={$appSession.isAdmin}
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
class="icons bg-transparent text-sm"
|
||||||
data-tip={$appSession.isAdmin
|
|
||||||
? $t('application.delete_application')
|
|
||||||
: $t('application.permission_denied_delete_application')}
|
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</nav>
|
</nav>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
|
<Tooltip triggeredBy="#configurations">Configurations</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#secrets">Secrets</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#persistentstorages">Persistent Storages</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#previews">Previews</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#applicationlogs">Application Logs</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#buildlogs">Build Logs</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#delete">Delete</Tooltip>
|
||||||
|
|||||||
@@ -95,19 +95,19 @@
|
|||||||
async function isBranchAlreadyUsed(event: any) {
|
async function isBranchAlreadyUsed(event: any) {
|
||||||
selected.branch = event.detail.value;
|
selected.branch = event.detail.value;
|
||||||
try {
|
try {
|
||||||
const data = await get(
|
// const data = await get(
|
||||||
`/applications/${id}/configuration/repository?repository=${selected.repository}&branch=${selected.branch}`
|
// `/applications/${id}/configuration/repository?repository=${selected.repository}&branch=${selected.branch}`
|
||||||
);
|
// );
|
||||||
if (data.used) {
|
// if (data.used) {
|
||||||
const sure = confirm($t('application.configuration.branch_already_in_use'));
|
// const sure = confirm($t('application.configuration.branch_already_in_use'));
|
||||||
if (sure) {
|
// if (sure) {
|
||||||
selected.autodeploy = false;
|
// selected.autodeploy = false;
|
||||||
showSave = true;
|
// showSave = true;
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
showSave = false;
|
// showSave = false;
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
showSave = true;
|
showSave = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showSave = false;
|
showSave = false;
|
||||||
|
|||||||
@@ -169,10 +169,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function selectBranch(event: any) {
|
|
||||||
selected.branch = event.detail;
|
|
||||||
isBranchAlreadyUsed();
|
|
||||||
}
|
|
||||||
async function loadBranches(page: number = 1) {
|
async function loadBranches(page: number = 1) {
|
||||||
let perPage = 100;
|
let perPage = 100;
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@@ -199,21 +195,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isBranchAlreadyUsed() {
|
async function isBranchAlreadyUsed(event) {
|
||||||
|
selected.branch = event.detail;
|
||||||
try {
|
try {
|
||||||
const data = await get(
|
// const data = await get(
|
||||||
`/applications/${id}/configuration/repository?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
|
// `/applications/${id}/configuration/repository?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
|
||||||
);
|
// );
|
||||||
if (data.used) {
|
// if (data.used) {
|
||||||
const sure = confirm($t('application.configuration.branch_already_in_use'));
|
// const sure = confirm($t('application.configuration.branch_already_in_use'));
|
||||||
if (sure) {
|
// if (sure) {
|
||||||
autodeploy = false;
|
// autodeploy = false;
|
||||||
showSave = true;
|
// showSave = true;
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
showSave = false;
|
// showSave = false;
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
showSave = true;
|
showSave = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@@ -227,9 +224,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function setWebhook(url: any, webhookToken: any) {
|
async function setWebhook(url: any, webhookToken: any) {
|
||||||
const host = dev
|
const host = dev ? getWebhookUrl('gitlab') : `${window.location.origin}/webhooks/gitlab/events`;
|
||||||
? getWebhookUrl('gitlab')
|
|
||||||
: `${window.location.origin}/webhooks/gitlab/events`;
|
|
||||||
try {
|
try {
|
||||||
await post(
|
await post(
|
||||||
url,
|
url,
|
||||||
@@ -294,17 +289,15 @@
|
|||||||
);
|
);
|
||||||
await post(updateDeployKeyIdUrl, { deployKeyId: id });
|
await post(updateDeployKeyIdUrl, { deployKeyId: id });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
|
||||||
} finally {
|
|
||||||
loading.save = false;
|
loading.save = false;
|
||||||
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await setWebhook(webhookUrl, webhookToken);
|
await setWebhook(webhookUrl, webhookToken);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
|
||||||
} finally {
|
|
||||||
loading.save = false;
|
loading.save = false;
|
||||||
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `/applications/${id}/configuration/repository`;
|
const url = `/applications/${id}/configuration/repository`;
|
||||||
@@ -317,11 +310,11 @@
|
|||||||
autodeploy,
|
autodeploy,
|
||||||
webhookToken
|
webhookToken
|
||||||
});
|
});
|
||||||
|
loading.save = false;
|
||||||
return await goto(from || `/applications/${id}/configuration/buildpack`);
|
return await goto(from || `/applications/${id}/configuration/buildpack`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
|
||||||
} finally {
|
|
||||||
loading.save = false;
|
loading.save = false;
|
||||||
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
@@ -396,7 +389,7 @@
|
|||||||
showIndicator={!loading.branches}
|
showIndicator={!loading.branches}
|
||||||
isWaiting={loading.branches}
|
isWaiting={loading.branches}
|
||||||
isDisabled={loading.branches || !selected.project}
|
isDisabled={loading.branches || !selected.project}
|
||||||
on:select={selectBranch}
|
on:select={isBranchAlreadyUsed}
|
||||||
on:clear={() => {
|
on:clear={() => {
|
||||||
showSave = false;
|
showSave = false;
|
||||||
selected.branch = null;
|
selected.branch = null;
|
||||||
@@ -425,7 +418,7 @@
|
|||||||
configuration <a href={`/sources/${application.gitSource.id}`}>here.</a>
|
configuration <a href={`/sources/${application.gitSource.id}`}>here.</a>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="w-40 bg-green-600"
|
class="btn btn-sm w-40 bg-green-600"
|
||||||
on:click|stopPropagation|preventDefault={() => window.location.reload()}
|
on:click|stopPropagation|preventDefault={() => window.location.reload()}
|
||||||
>
|
>
|
||||||
Try again
|
Try again
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
import Select from 'svelte-select';
|
import Select from 'svelte-select';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
|
|
||||||
@@ -164,7 +163,6 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<input
|
<input
|
||||||
placeholder="eg: https://github.com/coollabsio/nodejs-example/tree/main"
|
placeholder="eg: https://github.com/coollabsio/nodejs-example/tree/main"
|
||||||
class="text-xs"
|
|
||||||
bind:value={publicRepositoryLink}
|
bind:value={publicRepositoryLink}
|
||||||
/>
|
/>
|
||||||
{#if branchSelectOptions.length > 0}
|
{#if branchSelectOptions.length > 0}
|
||||||
@@ -193,7 +191,5 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Explainer
|
|
||||||
text="Examples:<br><br>https://github.com/coollabsio/nodejs-example<br>https://github.com/coollabsio/nodejs-example/tree/main<br>https://gitlab.com/aleveha/fastify-example<br>https://gitlab.com/aleveha/fastify-example/-/tree/master<br><br>Only works with Github.com and Gitlab.com."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { appSession } from '$lib/store';
|
import { appSession } from '$lib/store';
|
||||||
import PublicRepository from './_PublicRepository.svelte';
|
import PublicRepository from './_PublicRepository.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import DocLink from '$lib/components/DocLink.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
const from = $page.url.searchParams.get('from');
|
const from = $page.url.searchParams.get('from');
|
||||||
@@ -192,7 +192,9 @@ import Explainer from '$lib/components/Explainer.svelte';
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="title py-4">Public Repository</div>
|
<div class="flex items-center">
|
||||||
|
<div class="title py-4">Public Repository</div>
|
||||||
<PublicRepository />
|
<DocLink url="https://docs.coollabs.io/coolify/applications/#public-repository" />
|
||||||
|
</div>
|
||||||
|
<PublicRepository />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,14 +32,15 @@
|
|||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import Select from 'svelte-select';
|
import Select from 'svelte-select';
|
||||||
|
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import { browser } from '$app/env';
|
|
||||||
import { addToast, appSession, disabledButton, setLocation, status } from '$lib/store';
|
import { addToast, appSession, disabledButton, setLocation, status } from '$lib/store';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
|
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
|
||||||
import Setting from './_Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
$: isDisabled =
|
$: isDisabled =
|
||||||
@@ -281,6 +282,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if application.gitSource?.htmlUrl && application.repository && application.branch}
|
{#if application.gitSource?.htmlUrl && application.repository && application.branch}
|
||||||
<a
|
<a
|
||||||
|
id="git"
|
||||||
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
|
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="w-10"
|
class="w-10"
|
||||||
@@ -321,6 +323,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
|
<Tooltip triggeredBy="#git">Open on Git</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -426,7 +429,7 @@
|
|||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center pb-8">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="destination" class="text-base font-bold text-stone-100"
|
<label for="destination" class="text-base font-bold text-stone-100"
|
||||||
>{$t('application.destination')}</label
|
>{$t('application.destination')}</label
|
||||||
>
|
>
|
||||||
@@ -440,10 +443,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'}
|
{#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'}
|
||||||
<div class="grid grid-cols-2 items-center pb-8">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="baseBuildImage" class="text-base font-bold text-stone-100"
|
<label for="baseBuildImage" class="text-base font-bold text-stone-100"
|
||||||
>{$t('application.base_build_image')}</label
|
>{$t('application.base_build_image')}
|
||||||
>
|
<Explainer
|
||||||
|
explanation={application.buildPack === 'laravel'
|
||||||
|
? 'For building frontend assets with webpack.'
|
||||||
|
: 'Image that will be used during the build process.'}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div class="custom-select-wrapper">
|
<div class="custom-select-wrapper">
|
||||||
<Select
|
<Select
|
||||||
@@ -457,17 +465,13 @@
|
|||||||
isClearable={false}
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if application.buildPack === 'laravel'}
|
|
||||||
<Explainer text="For building frontend assets with webpack." />
|
|
||||||
{:else}
|
|
||||||
<Explainer text={$t('application.base_build_image_explainer')} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if application.buildPack !== 'docker'}
|
{#if application.buildPack !== 'docker'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="baseImage" class="text-base font-bold text-stone-100"
|
<label for="baseImage" class="text-base font-bold text-stone-100"
|
||||||
>{$t('application.base_image')}</label
|
>{$t('application.base_image')}
|
||||||
|
<Explainer explanation={'Image that will be used for the deployment.'} /></label
|
||||||
>
|
>
|
||||||
<div class="custom-select-wrapper">
|
<div class="custom-select-wrapper">
|
||||||
<Select
|
<Select
|
||||||
@@ -481,13 +485,15 @@
|
|||||||
isClearable={false}
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Explainer text={$t('application.base_image_explainer')} />
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if application.buildPack !== 'docker' && (application.buildPack === 'nextjs' || application.buildPack === 'nuxtjs')}
|
{#if application.buildPack !== 'docker' && (application.buildPack === 'nextjs' || application.buildPack === 'nuxtjs')}
|
||||||
<div class="grid grid-cols-2 items-center pb-8">
|
<div class="grid grid-cols-2 items-center pb-8">
|
||||||
<label for="deploymentType" class="text-base font-bold text-stone-100"
|
<label for="deploymentType" class="text-base font-bold text-stone-100"
|
||||||
>Deployment Type</label
|
>Deployment Type
|
||||||
|
<Explainer
|
||||||
|
explanation={"Defines how to deploy your application. <br><br><span class='text-green-500 font-bold'>Static</span> is for static websites, <span class='text-green-500 font-bold'>node</span> is for server-side applications."}
|
||||||
|
/></label
|
||||||
>
|
>
|
||||||
<div class="custom-select-wrapper">
|
<div class="custom-select-wrapper">
|
||||||
<Select
|
<Select
|
||||||
@@ -501,9 +507,6 @@
|
|||||||
isClearable={false}
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Explainer
|
|
||||||
text="Defines how to deploy your application. <br><br><span class='text-green-500 font-bold'>Static</span> is for static websites, <span class='text-green-500 font-bold'>node</span> is for server-side applications."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -513,32 +516,39 @@
|
|||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="isBot"
|
||||||
isCenter={false}
|
isCenter={false}
|
||||||
bind:setting={isBot}
|
bind:setting={isBot}
|
||||||
on:click={() => changeSettings('isBot')}
|
on:click={() => changeSettings('isBot')}
|
||||||
title="Is your application a bot?"
|
title="Is your application a bot?"
|
||||||
description="You can deploy applications without domains. <br>You can also make them to listen on <span class='text-green-500 font-bold'>IP:EXPOSEDPORT</span> as well.<br></Setting><br>Useful to host <span class='text-green-500 font-bold'>Twitch bots, regular jobs, or anything that does not require an incoming connection.</span>"
|
description="You can deploy applications without domains or make them to listen on the <span class='text-settings font-bold'>Exposed Port</span>.<br></Setting><br>Useful to host <span class='text-settings font-bold'>Twitch bots, regular jobs, or anything that does not require an incoming HTTP connection.</span>"
|
||||||
disabled={$status.application.isRunning}
|
disabled={$status.application.isRunning}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<Setting
|
||||||
|
id="dualCerts"
|
||||||
|
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||||
|
disabled={$status.application.isRunning}
|
||||||
|
isCenter={false}
|
||||||
|
bind:setting={dualCerts}
|
||||||
|
title={$t('application.ssl_www_and_non_www')}
|
||||||
|
description="It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-settings'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both."
|
||||||
|
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{#if !isBot}
|
{#if !isBot}
|
||||||
<div class="grid grid-cols-2">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="flex-col">
|
<label for="fqdn" class="text-base font-bold text-stone-100"
|
||||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
>{$t('application.url_fqdn')}
|
||||||
>{$t('application.url_fqdn')}</label
|
<Explainer
|
||||||
>
|
explanation={"If you specify <span class='text-settings font-bold'>https</span>, the application will be accessible only over https.<br>SSL certificate will be generated automatically.<br><br>If you specify <span class='text-settings font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-settings font-bold'>You must set your DNS to point to the server IP in advance.</span>"}
|
||||||
{#if browser && window.location.hostname === 'demo.coolify.io'}
|
/>
|
||||||
<Explainer
|
</label>
|
||||||
text="<span class='text-white font-bold'>You can use the predefined random url name or enter your own domain name.</span>"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<Explainer text={$t('application.https_explainer')} />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
readonly={isDisabled}
|
readonly={isDisabled}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
bind:this={domainEl}
|
|
||||||
name="fqdn"
|
name="fqdn"
|
||||||
id="fqdn"
|
id="fqdn"
|
||||||
required
|
required
|
||||||
@@ -582,17 +592,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center pb-8">
|
|
||||||
<Setting
|
|
||||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
|
||||||
disabled={$status.application.isRunning}
|
|
||||||
isCenter={false}
|
|
||||||
bind:setting={dualCerts}
|
|
||||||
title={$t('application.ssl_www_and_non_www')}
|
|
||||||
description={$t('application.ssl_explainer')}
|
|
||||||
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if application.buildPack === 'python'}
|
{#if application.buildPack === 'python'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
@@ -645,7 +644,10 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if !staticDeployments.includes(application.buildPack)}
|
{#if !staticDeployments.includes(application.buildPack)}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="port" class="text-base font-bold text-stone-100">{$t('forms.port')}</label>
|
<label for="port" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.port')}
|
||||||
|
<Explainer explanation={'The port your application listens on.'} /></label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
readonly={!$appSession.isAdmin}
|
readonly={!$appSession.isAdmin}
|
||||||
@@ -654,11 +656,14 @@
|
|||||||
bind:value={application.port}
|
bind:value={application.port}
|
||||||
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
|
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
|
||||||
/>
|
/>
|
||||||
<Explainer text={'The port your application listens on.'} />
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
|
<label for="exposePort" class="text-base font-bold text-stone-100"
|
||||||
|
>Exposed Port <Explainer
|
||||||
|
explanation={'You can expose your application to a port on the host system.<br><br>Useful if you would like to use your own reverse proxy or tunnel and also in development mode. Otherwise leave empty.'}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
readonly={!$appSession.isAdmin && !$status.application.isRunning}
|
readonly={!$appSession.isAdmin && !$status.application.isRunning}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
@@ -667,12 +672,9 @@
|
|||||||
bind:value={application.exposePort}
|
bind:value={application.exposePort}
|
||||||
placeholder="12345"
|
placeholder="12345"
|
||||||
/>
|
/>
|
||||||
<Explainer
|
|
||||||
text={'You can expose your application to a port on the host system.<br><br>Useful if you would like to use your own reverse proxy or tunnel and also in development mode. Otherwise leave empty.'}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||||
<div class="grid grid-cols-2 items-center pt-4">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="installCommand" class="text-base font-bold text-stone-100"
|
<label for="installCommand" class="text-base font-bold text-stone-100"
|
||||||
>{$t('application.install_command')}</label
|
>{$t('application.install_command')}</label
|
||||||
>
|
>
|
||||||
@@ -715,7 +717,9 @@
|
|||||||
{#if application.buildPack === 'docker'}
|
{#if application.buildPack === 'docker'}
|
||||||
<div class="grid grid-cols-2 items-center pt-4">
|
<div class="grid grid-cols-2 items-center pt-4">
|
||||||
<label for="dockerFileLocation" class="text-base font-bold text-stone-100"
|
<label for="dockerFileLocation" class="text-base font-bold text-stone-100"
|
||||||
>Dockerfile Location</label
|
>Dockerfile Location <Explainer
|
||||||
|
explanation={"Should be absolute path, like <span class='text-settings font-bold'>/data/Dockerfile</span> or <span class='text-settings font-bold'>/Dockerfile.</span>"}
|
||||||
|
/></label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
@@ -725,9 +729,6 @@
|
|||||||
bind:value={application.dockerFileLocation}
|
bind:value={application.dockerFileLocation}
|
||||||
placeholder="default: /Dockerfile"
|
placeholder="default: /Dockerfile"
|
||||||
/>
|
/>
|
||||||
<Explainer
|
|
||||||
text="Should be absolute path, like <span class='text-green-500 font-bold'>/data/Dockerfile</span> or <span class='text-green-500 font-bold'>/Dockerfile.</span>"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if application.buildPack === 'deno'}
|
{#if application.buildPack === 'deno'}
|
||||||
@@ -743,7 +744,11 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="denoOptions" class="text-base font-bold text-stone-100">Arguments</label>
|
<label for="denoOptions" class="text-base font-bold text-stone-100"
|
||||||
|
>Arguments <Explainer
|
||||||
|
explanation={"List of arguments to pass to <span class='text-settings font-bold'>deno run</span> command. Could include permissions, configurations files, etc."}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
readonly={!$appSession.isAdmin}
|
readonly={!$appSession.isAdmin}
|
||||||
@@ -752,18 +757,17 @@
|
|||||||
bind:value={application.denoOptions}
|
bind:value={application.denoOptions}
|
||||||
placeholder="eg: --allow-net --allow-hrtime --config path/to/file.json"
|
placeholder="eg: --allow-net --allow-hrtime --config path/to/file.json"
|
||||||
/>
|
/>
|
||||||
<Explainer
|
|
||||||
text="List of arguments to pass to <span class='text-green-500 font-bold'>deno run</span> command. Could include permissions, configurations files, etc."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if application.buildPack !== 'laravel' && application.buildPack !== 'heroku'}
|
{#if application.buildPack !== 'laravel' && application.buildPack !== 'heroku'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
|
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
|
||||||
>{$t('forms.base_directory')}</label
|
>{$t('forms.base_directory')}
|
||||||
|
<Explainer
|
||||||
|
explanation={"Directory to use as the base for all commands.<br>Could be useful with <span class='text-settings font-bold'>monorepos</span>."}
|
||||||
|
/></label
|
||||||
>
|
>
|
||||||
<Explainer text={$t('application.directory_to_use_explainer')} />
|
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
@@ -779,9 +783,11 @@
|
|||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<label for="publishDirectory" class="pt-2 text-base font-bold text-stone-100"
|
<label for="publishDirectory" class="pt-2 text-base font-bold text-stone-100"
|
||||||
>{$t('forms.publish_directory')}</label
|
>{$t('forms.publish_directory')}
|
||||||
|
<Explainer
|
||||||
|
explanation={"Directory containing all the assets for deployment. <br> For example: <span class='text-settings font-bold'>dist</span>,<span class='text-settings font-bold'>_site</span> or <span class='text-settings font-bold'>public</span>."}
|
||||||
|
/></label
|
||||||
>
|
>
|
||||||
<Explainer text={$t('application.publish_directory_explainer')} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
@@ -804,6 +810,7 @@
|
|||||||
{#if !application.settings.isPublicRepository}
|
{#if !application.settings.isPublicRepository}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="autodeploy"
|
||||||
isCenter={false}
|
isCenter={false}
|
||||||
bind:setting={autodeploy}
|
bind:setting={autodeploy}
|
||||||
on:click={() => changeSettings('autodeploy')}
|
on:click={() => changeSettings('autodeploy')}
|
||||||
@@ -812,9 +819,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !application.settings.isBot}
|
{#if !application.settings.isBot && !application.settings.isPublicRepository}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="previews"
|
||||||
isCenter={false}
|
isCenter={false}
|
||||||
bind:setting={previews}
|
bind:setting={previews}
|
||||||
on:click={() => changeSettings('previews')}
|
on:click={() => changeSettings('previews')}
|
||||||
@@ -825,6 +833,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="debug"
|
||||||
isCenter={false}
|
isCenter={false}
|
||||||
bind:setting={debug}
|
bind:setting={debug}
|
||||||
on:click={() => changeSettings('debug')}
|
on:click={() => changeSettings('debug')}
|
||||||
|
|||||||
@@ -6,14 +6,13 @@
|
|||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
import Loading from '$lib/components/Loading.svelte';
|
|
||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import LoadingLogs from '$lib/components/LoadingLogs.svelte';
|
import LoadingLogs from '$lib/components/LoadingLogs.svelte';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
|
||||||
let logs: any = [];
|
let logs: any = [];
|
||||||
let loading = true;
|
|
||||||
let currentStatus: any;
|
let currentStatus: any;
|
||||||
let streamInterval: any;
|
let streamInterval: any;
|
||||||
let followingBuild: any;
|
let followingBuild: any;
|
||||||
@@ -46,7 +45,6 @@
|
|||||||
logs = logs.concat(
|
logs = logs.concat(
|
||||||
responseLogs.map((log: any) => ({ ...log, line: cleanAnsiCodes(log.line) }))
|
responseLogs.map((log: any) => ({ ...log, line: cleanAnsiCodes(log.line) }))
|
||||||
);
|
);
|
||||||
loading = false;
|
|
||||||
streamInterval = setInterval(async () => {
|
streamInterval = setInterval(async () => {
|
||||||
if (status !== 'running' && status !== 'queued') {
|
if (status !== 'running' && status !== 'queued') {
|
||||||
clearInterval(streamInterval);
|
clearInterval(streamInterval);
|
||||||
@@ -63,7 +61,7 @@
|
|||||||
logs = logs.concat(
|
logs = logs.concat(
|
||||||
data.logs.map((log: any) => ({ ...log, line: cleanAnsiCodes(log.line) }))
|
data.logs.map((log: any) => ({ ...log, line: cleanAnsiCodes(log.line) }))
|
||||||
);
|
);
|
||||||
dispatch('updateBuildStatus', { status });
|
dispatch('updateBuildStatus', { status, took: data.took });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
@@ -96,84 +94,82 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loading}
|
<div class="relative ">
|
||||||
<Loading />
|
{#if currentStatus === 'running'}
|
||||||
{:else}
|
<LoadingLogs />
|
||||||
<div class="relative ">
|
{/if}
|
||||||
{#if currentStatus === 'running'}
|
{#if currentStatus === 'queued'}
|
||||||
<LoadingLogs />
|
<div class="text-center font-bold text-xl">{$t('application.build.queued_waiting_exec')}</div>
|
||||||
{/if}
|
{:else}
|
||||||
{#if currentStatus === 'queued'}
|
<div class="flex justify-end sticky top-0 p-2 mx-1">
|
||||||
<div class="text-center font-bold text-xl">{$t('application.build.queued_waiting_exec')}</div>
|
<button
|
||||||
{:else}
|
id="follow"
|
||||||
<div class="flex justify-end sticky top-0 p-2 mx-1">
|
on:click={followBuild}
|
||||||
|
class="bg-transparent btn btn-sm btn-linkhover:text-green-500 hover:bg-coolgray-500"
|
||||||
|
class:text-green-500={followingBuild}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<circle cx="12" cy="12" r="9" />
|
||||||
|
<line x1="8" y1="12" x2="12" y2="16" />
|
||||||
|
<line x1="12" y1="8" x2="12" y2="16" />
|
||||||
|
<line x1="16" y1="12" x2="12" y2="16" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<Tooltip triggeredBy="#follow">Follow Logs</Tooltip>
|
||||||
|
{#if currentStatus === 'running'}
|
||||||
<button
|
<button
|
||||||
on:click={followBuild}
|
id="cancel"
|
||||||
class="bg-transparent btn btn-sm btn-link tooltip tooltip-primary tooltip-bottom hover:text-green-500 hover:bg-coolgray-500"
|
on:click={cancelBuild}
|
||||||
data-tip="Follow logs"
|
class:animation-spin={cancelInprogress}
|
||||||
class:text-green-500={followingBuild}
|
class="bg-transparent btn btn-sm btn-link hover:text-red-500 hover:bg-coolgray-500"
|
||||||
>
|
>
|
||||||
<svg
|
{#if cancelInprogress}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
Cancelling...
|
||||||
class="w-6 h-6"
|
{:else}
|
||||||
viewBox="0 0 24 24"
|
<svg
|
||||||
stroke-width="1.5"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
stroke="currentColor"
|
class="w-6 h-6"
|
||||||
fill="none"
|
viewBox="0 0 24 24"
|
||||||
stroke-linecap="round"
|
stroke-width="1.5"
|
||||||
stroke-linejoin="round"
|
stroke="currentColor"
|
||||||
>
|
fill="none"
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
stroke-linecap="round"
|
||||||
<circle cx="12" cy="12" r="9" />
|
stroke-linejoin="round"
|
||||||
<line x1="8" y1="12" x2="12" y2="16" />
|
>
|
||||||
<line x1="12" y1="8" x2="12" y2="16" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<line x1="16" y1="12" x2="12" y2="16" />
|
<circle cx="12" cy="12" r="9" />
|
||||||
</svg>
|
<path d="M10 10l4 4m0 -4l-4 4" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{#if currentStatus === 'running'}
|
<Tooltip triggeredBy="#cancel">Cancel build</Tooltip>
|
||||||
<button
|
|
||||||
on:click={cancelBuild}
|
|
||||||
class:animation-spin={cancelInprogress}
|
|
||||||
class="bg-transparent btn btn-sm btn-link hover:text-red-500 hover:bg-coolgray-500 tooltip tooltip-primary tooltip-bottom"
|
|
||||||
data-tip="Cancel build"
|
|
||||||
>
|
|
||||||
{#if cancelInprogress}
|
|
||||||
Cancelling...
|
|
||||||
{:else}
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<circle cx="12" cy="12" r="9" />
|
|
||||||
<path d="M10 10l4 4m0 -4l-4 4" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if logs.length > 0}
|
|
||||||
<div
|
|
||||||
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
|
||||||
bind:this={logsEl}
|
|
||||||
>
|
|
||||||
{#each logs as log}
|
|
||||||
<div>{log.line + '\n'}</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div
|
|
||||||
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
|
||||||
>
|
|
||||||
No logs found.
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if logs.length > 0}
|
||||||
|
<div
|
||||||
|
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
||||||
|
bind:this={logsEl}
|
||||||
|
>
|
||||||
|
{#each logs as log}
|
||||||
|
<div>{log.line + '\n'}</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
||||||
|
>
|
||||||
|
No logs found.
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
|
|||||||
@@ -28,17 +28,20 @@
|
|||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { changeQueryParams, dateOptions, errorNotification } from '$lib/common';
|
import { changeQueryParams, dateOptions, errorNotification } from '$lib/common';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
|
||||||
let buildId: any;
|
let buildId: any;
|
||||||
|
|
||||||
let skip = 0;
|
let skip = 0;
|
||||||
let noMoreBuilds = buildCount < 5 || buildCount <= skip;
|
let noMoreBuilds = buildCount < 5 || buildCount <= skip;
|
||||||
|
|
||||||
|
let buildTook = 0;
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
let preselectedBuildId = $page.url.searchParams.get('buildId');
|
let preselectedBuildId = $page.url.searchParams.get('buildId');
|
||||||
if (preselectedBuildId) buildId = preselectedBuildId;
|
if (preselectedBuildId) buildId = preselectedBuildId;
|
||||||
|
|
||||||
async function updateBuildStatus({ detail }: { detail: any }) {
|
async function updateBuildStatus({ detail }: { detail: any }) {
|
||||||
const { status } = detail;
|
const { status, took } = detail;
|
||||||
if (status !== 'running') {
|
if (status !== 'running') {
|
||||||
try {
|
try {
|
||||||
const data = await get(`/applications/${id}/logs/build?buildId=${buildId}`);
|
const data = await get(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||||
@@ -58,6 +61,7 @@
|
|||||||
if (build.id === buildId) build.status = status;
|
if (build.id === buildId) build.status = status;
|
||||||
return build;
|
return build;
|
||||||
});
|
});
|
||||||
|
buildTook = took;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function loadMoreBuilds() {
|
async function loadMoreBuilds() {
|
||||||
@@ -137,20 +141,18 @@
|
|||||||
<div class="top-4 md:sticky">
|
<div class="top-4 md:sticky">
|
||||||
{#each builds as build, index (build.id)}
|
{#each builds as build, index (build.id)}
|
||||||
<div
|
<div
|
||||||
data-tip={new Intl.DateTimeFormat('default', dateOptions).format(
|
id={`building-${build.id}`}
|
||||||
new Date(build.createdAt)
|
|
||||||
) + `\n${build.status}`}
|
|
||||||
on:click={() => loadBuild(build.id)}
|
on:click={() => loadBuild(build.id)}
|
||||||
class:rounded-tr={index === 0}
|
class:rounded-tr={index === 0}
|
||||||
class:rounded-br={index === builds.length - 1}
|
class:rounded-br={index === builds.length - 1}
|
||||||
class="tooltip tooltip-primary tooltip-top flex cursor-pointer items-center justify-center border-l-2 py-4 no-underline transition-all duration-100 hover:bg-coolgray-400 hover:shadow-xl"
|
class="flex cursor-pointer items-center justify-center border-l-2 py-4 no-underline transition-all duration-100 hover:bg-coolgray-400 hover:shadow-xl"
|
||||||
class:bg-coolgray-400={buildId === build.id}
|
class:bg-coolgray-400={buildId === build.id}
|
||||||
class:border-red-500={build.status === 'failed'}
|
class:border-red-500={build.status === 'failed'}
|
||||||
class:border-orange-500={build.status === 'canceled'}
|
class:border-orange-500={build.status === 'canceled'}
|
||||||
class:border-green-500={build.status === 'success'}
|
class:border-green-500={build.status === 'success'}
|
||||||
class:border-yellow-500={build.status === 'running'}
|
class:border-yellow-500={build.status === 'running'}
|
||||||
>
|
>
|
||||||
<div class="flex-col px-2">
|
<div class="flex-col px-2 text-center min-w-[10rem]">
|
||||||
<div class="text-sm font-bold">
|
<div class="text-sm font-bold">
|
||||||
{build.branch || application.branch}
|
{build.branch || application.branch}
|
||||||
</div>
|
</div>
|
||||||
@@ -162,6 +164,10 @@
|
|||||||
<div class="w-48 text-center text-xs">
|
<div class="w-48 text-center text-xs">
|
||||||
{#if build.status === 'running'}
|
{#if build.status === 'running'}
|
||||||
<div class="font-bold">{$t('application.build.running')}</div>
|
<div class="font-bold">{$t('application.build.running')}</div>
|
||||||
|
<div>
|
||||||
|
Elapsed
|
||||||
|
<span class="font-bold">{buildTook}s</span>
|
||||||
|
</div>
|
||||||
{:else if build.status === 'queued'}
|
{:else if build.status === 'queued'}
|
||||||
<div class="font-bold">{$t('application.build.queued')}</div>
|
<div class="font-bold">{$t('application.build.queued')}</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -172,6 +178,10 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Tooltip triggeredBy={`#building-${build.id}`}
|
||||||
|
>{new Intl.DateTimeFormat('default', dateOptions).format(new Date(build.createdAt)) +
|
||||||
|
`\n${build.status}`}</Tooltip
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if !noMoreBuilds}
|
{#if !noMoreBuilds}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import LoadingLogs from '$lib/components/LoadingLogs.svelte';
|
import LoadingLogs from '$lib/components/LoadingLogs.svelte';
|
||||||
import { onMount, onDestroy } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
|
||||||
let application: any = {};
|
let application: any = {};
|
||||||
let logsLoading = false;
|
let logsLoading = false;
|
||||||
@@ -146,9 +147,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="flex justify-end sticky top-0 p-1 mx-1">
|
<div class="flex justify-end sticky top-0 p-1 mx-1">
|
||||||
<button
|
<button
|
||||||
|
id="follow"
|
||||||
on:click={followBuild}
|
on:click={followBuild}
|
||||||
class="bg-transparent btn btn-sm btn-link tooltip tooltip-primary tooltip-bottom"
|
class="bg-transparent btn btn-sm btn-link"
|
||||||
data-tip="Follow logs"
|
|
||||||
class:text-green-500={followingLogs}
|
class:text-green-500={followingLogs}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -168,6 +169,7 @@
|
|||||||
<line x1="16" y1="12" x2="12" y2="16" />
|
<line x1="16" y1="12" x2="12" y2="16" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
<Tooltip triggeredBy="#follow">Follow Logs</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="font-mono w-full leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
class="font-mono w-full leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
||||||
|
|||||||
@@ -21,13 +21,13 @@
|
|||||||
import Secret from './_Secret.svelte';
|
import Secret from './_Secret.svelte';
|
||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { errorNotification, getDomain } from '$lib/common';
|
import { errorNotification, getDomain } from '$lib/common';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Loading from '$lib/components/Loading.svelte';
|
import Loading from '$lib/components/Loading.svelte';
|
||||||
import { addToast } from '$lib/store';
|
import { addToast } from '$lib/store';
|
||||||
|
import SimpleExplainer from '$lib/components/SimpleExplainer.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="mx-auto max-w-6xl px-6 pt-4">
|
<div class="mx-auto max-w-6xl px-6 pt-4">
|
||||||
<div class="flex justify-center py-4 text-center">
|
<div class="flex justify-center py-4 text-center">
|
||||||
<Explainer
|
<SimpleExplainer
|
||||||
customClass="w-full"
|
customClass="w-full"
|
||||||
text={applicationSecrets.length === 0
|
text={applicationSecrets.length === 0
|
||||||
? "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
|
? "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
|
||||||
@@ -194,8 +194,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<button class="btn btn-sm bg-coollabs hover:bg-coollabs-100" on:click={() => redeploy(container)}
|
<button
|
||||||
>{$t('application.preview.redeploy')}</button
|
class="btn btn-sm bg-coollabs hover:bg-coollabs-100"
|
||||||
|
on:click={() => redeploy(container)}>{$t('application.preview.redeploy')}</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import Storage from './_Storage.svelte';
|
import Storage from './_Storage.svelte';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import SimpleExplainer from '$lib/components/SimpleExplainer.svelte';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
@@ -87,7 +87,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||||
|
<div class="flex justify-center py-4 text-center">
|
||||||
|
<SimpleExplainer customClass="w-full" text={$t('application.storage.persistent_storage_explainer')} />
|
||||||
|
</div>
|
||||||
<table class="mx-auto border-separate text-left">
|
<table class="mx-auto border-separate text-left">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="h-12">
|
<tr class="h-12">
|
||||||
@@ -107,7 +109,4 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="flex justify-center py-4 text-center">
|
|
||||||
<Explainer customClass="w-full" text={$t('application.storage.persistent_storage_explainer')} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -209,13 +209,13 @@
|
|||||||
<div class="grid grid-cols-2 items-center px-10 pb-8">
|
<div class="grid grid-cols-2 items-center px-10 pb-8">
|
||||||
<div>
|
<div>
|
||||||
<label for="url" class="text-base font-bold text-stone-100"
|
<label for="url" class="text-base font-bold text-stone-100"
|
||||||
>{$t('database.connection_string')}</label
|
>{$t('database.connection_string')}
|
||||||
|
{#if !isPublic && database.destinationDocker.remoteEngine}
|
||||||
|
<Explainer
|
||||||
|
explanation="You can only access the database with this URL if your application is deployed to the same Destination."
|
||||||
|
/>
|
||||||
|
{/if}</label
|
||||||
>
|
>
|
||||||
{#if !isPublic && database.destinationDocker.remoteEngine}
|
|
||||||
<Explainer
|
|
||||||
text="You can only access the database with this URL if your application is deployed to the same Destination."
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
textarea={true}
|
textarea={true}
|
||||||
@@ -236,6 +236,7 @@
|
|||||||
<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
|
||||||
|
id="isPublic"
|
||||||
loading={publicLoading}
|
loading={publicLoading}
|
||||||
bind:setting={isPublic}
|
bind:setting={isPublic}
|
||||||
on:click={() => changeSettings('isPublic')}
|
on:click={() => changeSettings('isPublic')}
|
||||||
@@ -247,6 +248,7 @@
|
|||||||
{#if database.type === 'redis'}
|
{#if database.type === 'redis'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="appendOnly"
|
||||||
loading={publicLoading}
|
loading={publicLoading}
|
||||||
bind:setting={appendOnly}
|
bind:setting={appendOnly}
|
||||||
on:click={() => changeSettings('appendOnly')}
|
on:click={() => changeSettings('appendOnly')}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
export let database: any;
|
export let database: any;
|
||||||
import { status } from '$lib/store';
|
import { status } from '$lib/store';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
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">
|
||||||
@@ -37,7 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<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"
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
|
||||||
>{$t('forms.password')}</label
|
>{$t('forms.password')}
|
||||||
|
<Explainer explanation="Could be changed while the database is running." /></label
|
||||||
>
|
>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
disabled={!$status.database.isRunning}
|
disabled={!$status.database.isRunning}
|
||||||
@@ -48,7 +49,6 @@
|
|||||||
name="dbUserPassword"
|
name="dbUserPassword"
|
||||||
bind: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">{$t('forms.root_user')}</label>
|
<label for="rootUser" class="text-base font-bold text-stone-100">{$t('forms.root_user')}</label>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<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"
|
<label for="rootUserPassword" class="text-base font-bold text-stone-100"
|
||||||
>{$t('forms.roots_password')}</label
|
>{$t('forms.roots_password')} <Explainer explanation="Could be changed while the database is running." /></label
|
||||||
>
|
>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
disabled={!$status.database.isRunning}
|
disabled={!$status.database.isRunning}
|
||||||
@@ -74,6 +74,5 @@
|
|||||||
name="rootUserPassword"
|
name="rootUserPassword"
|
||||||
bind:value={database.rootUserPassword}
|
bind:value={database.rootUserPassword}
|
||||||
/>
|
/>
|
||||||
<Explainer text="Could be changed while the database is running." />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
export let database: any;
|
export let database: any;
|
||||||
import { status } from '$lib/store';
|
import { status } from '$lib/store';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
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">
|
||||||
@@ -23,7 +23,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<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"
|
<label for="rootUserPassword" class="text-base font-bold text-stone-100"
|
||||||
>{$t('forms.roots_password')}</label
|
>{$t('forms.roots_password')}
|
||||||
|
<Explainer explanation="Could be changed while the database is running." /></label
|
||||||
>
|
>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
disabled={!$status.database.isRunning}
|
disabled={!$status.database.isRunning}
|
||||||
@@ -34,6 +35,5 @@
|
|||||||
name="rootUserPassword"
|
name="rootUserPassword"
|
||||||
bind:value={database.rootUserPassword}
|
bind:value={database.rootUserPassword}
|
||||||
/>
|
/>
|
||||||
<Explainer text="Could be changed while the database is running." />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
export let database: any;
|
export let database: any;
|
||||||
import { status } from '$lib/store';
|
import { status } from '$lib/store';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
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">
|
||||||
@@ -37,7 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<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"
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
|
||||||
>{$t('forms.password')}</label
|
>{$t('forms.password')}
|
||||||
|
<Explainer explanation="Could be changed while the database is running." /></label
|
||||||
>
|
>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
disabled={!$status.database.isRunning}
|
disabled={!$status.database.isRunning}
|
||||||
@@ -48,7 +49,6 @@
|
|||||||
name="dbUserPassword"
|
name="dbUserPassword"
|
||||||
bind: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">{$t('forms.root_user')}</label>
|
<label for="rootUser" class="text-base font-bold text-stone-100">{$t('forms.root_user')}</label>
|
||||||
@@ -63,7 +63,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<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"
|
<label for="rootUserPassword" class="text-base font-bold text-stone-100"
|
||||||
>{$t('forms.roots_password')}</label
|
>{$t('forms.roots_password')}
|
||||||
|
<Explainer explanation="Could be changed while the database is running." /></label
|
||||||
>
|
>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
disabled={!$status.database.isRunning}
|
disabled={!$status.database.isRunning}
|
||||||
@@ -74,6 +75,5 @@
|
|||||||
name="rootUserPassword"
|
name="rootUserPassword"
|
||||||
bind:value={database.rootUserPassword}
|
bind:value={database.rootUserPassword}
|
||||||
/>
|
/>
|
||||||
<Explainer text="Could be changed while the database is running." />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
export let database: any;
|
export let database: any;
|
||||||
import { status } from '$lib/store';
|
import { status } from '$lib/store';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
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">
|
||||||
@@ -26,7 +26,9 @@
|
|||||||
</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"
|
<label for="rootUser" class="text-base font-bold text-stone-100"
|
||||||
>Root (postgres) User Password</label
|
>Postgres User Password <Explainer
|
||||||
|
explanation="Could be changed while the database is running."
|
||||||
|
/></label
|
||||||
>
|
>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
disabled={!$status.database.isRunning}
|
disabled={!$status.database.isRunning}
|
||||||
@@ -37,7 +39,6 @@
|
|||||||
name="rootUserPassword"
|
name="rootUserPassword"
|
||||||
bind:value={database.rootUserPassword}
|
bind:value={database.rootUserPassword}
|
||||||
/>
|
/>
|
||||||
<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="dbUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
|
<label for="dbUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
|
||||||
@@ -52,7 +53,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<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"
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
|
||||||
>{$t('forms.password')}</label
|
>{$t('forms.password')}
|
||||||
|
<Explainer explanation="Could be changed while the database is running." /></label
|
||||||
>
|
>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
disabled={!$status.database.isRunning}
|
disabled={!$status.database.isRunning}
|
||||||
@@ -63,6 +65,5 @@
|
|||||||
name="dbUserPassword"
|
name="dbUserPassword"
|
||||||
bind:value={database.dbUserPassword}
|
bind:value={database.dbUserPassword}
|
||||||
/>
|
/>
|
||||||
<Explainer text="Could be changed while the database is running." />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
export let database: any;
|
export let database: any;
|
||||||
import { status } from '$lib/store';
|
import { status } from '$lib/store';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
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">
|
||||||
@@ -12,7 +12,8 @@
|
|||||||
<div class="space-y-2 px-10">
|
<div class="space-y-2 px-10">
|
||||||
<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"
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
|
||||||
>{$t('forms.password')}</label
|
>{$t('forms.password')}
|
||||||
|
<Explainer explanation="Could be changed while the database is running." /></label
|
||||||
>
|
>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
disabled={!$status.database.isRunning}
|
disabled={!$status.database.isRunning}
|
||||||
@@ -23,6 +24,5 @@
|
|||||||
name="dbUserPassword"
|
name="dbUserPassword"
|
||||||
bind:value={database.dbUserPassword}
|
bind:value={database.dbUserPassword}
|
||||||
/>
|
/>
|
||||||
<Explainer text="Could be changed while the database is running." />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -60,48 +60,53 @@
|
|||||||
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
||||||
import { appSession, status, disabledButton } from '$lib/store';
|
import { appSession, status, disabledButton } from '$lib/store';
|
||||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||||
import Loading from '$lib/components/Loading.svelte';
|
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
let loading = false;
|
|
||||||
let statusInterval: any = false;
|
let statusInterval: any = false;
|
||||||
|
let forceDelete = false;
|
||||||
|
|
||||||
$disabledButton = !$appSession.isAdmin;
|
$disabledButton = !$appSession.isAdmin;
|
||||||
|
|
||||||
async function deleteDatabase() {
|
async function deleteDatabase(force: boolean) {
|
||||||
const sure = confirm(`Are you sure you would like to delete '${database.name}'?`);
|
const sure = confirm(`Are you sure you would like to delete '${database.name}'?`);
|
||||||
if (sure) {
|
if (sure) {
|
||||||
loading = true;
|
$status.database.initialLoading = true;
|
||||||
try {
|
try {
|
||||||
await del(`/databases/${database.id}`, { id: database.id });
|
await del(`/databases/${database.id}`, { id: database.id, force });
|
||||||
return await goto('/databases');
|
return await goto('/databases');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
$status.database.initialLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function stopDatabase() {
|
async function stopDatabase() {
|
||||||
const sure = confirm($t('database.confirm_stop', { name: database.name }));
|
const sure = confirm($t('database.confirm_stop', { name: database.name }));
|
||||||
if (sure) {
|
if (sure) {
|
||||||
loading = true;
|
$status.database.initialLoading = true;
|
||||||
try {
|
try {
|
||||||
await post(`/databases/${database.id}/stop`, {});
|
await post(`/databases/${database.id}/stop`, {});
|
||||||
return window.location.reload();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
$status.database.initialLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function startDatabase() {
|
async function startDatabase() {
|
||||||
loading = true;
|
$status.database.initialLoading = true;
|
||||||
|
$status.database.loading = true;
|
||||||
try {
|
try {
|
||||||
await post(`/databases/${database.id}/start`, {});
|
await post(`/databases/${database.id}/start`, {});
|
||||||
return window.location.reload();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
$status.database.initialLoading = false;
|
||||||
|
$status.database.loading = false;
|
||||||
|
await getStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getStatus() {
|
async function getStatus() {
|
||||||
@@ -114,6 +119,9 @@
|
|||||||
}
|
}
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
$status.database.initialLoading = true;
|
$status.database.initialLoading = true;
|
||||||
|
$status.database.isRunning = false;
|
||||||
|
$status.database.isExited = false;
|
||||||
|
$status.database.loading = false;
|
||||||
clearInterval(statusInterval);
|
clearInterval(statusInterval);
|
||||||
});
|
});
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
@@ -137,120 +145,37 @@
|
|||||||
|
|
||||||
{#if id !== 'new'}
|
{#if id !== 'new'}
|
||||||
<nav class="nav-side">
|
<nav class="nav-side">
|
||||||
{#if loading}
|
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
|
||||||
<Loading fullscreen cover />
|
{#if $status.database.isExited}
|
||||||
{:else}
|
<a
|
||||||
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
|
id="exited"
|
||||||
{#if $status.database.isExited}
|
href={!$disabledButton ? `/databases/${id}/logs` : null}
|
||||||
<a
|
class="icons bg-transparent text-sm flex items-center text-red-500 tooltip-error"
|
||||||
href={!$disabledButton ? `/databases/${id}/logs` : null}
|
sveltekit:prefetch
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center text-red-500 tooltip-error"
|
>
|
||||||
data-tip="Service exited with an error!"
|
<svg
|
||||||
sveltekit:prefetch
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentcolor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<svg
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path
|
||||||
class="w-6 h-6"
|
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
|
||||||
viewBox="0 0 24 24"
|
/>
|
||||||
stroke-width="1.5"
|
<line x1="12" y1="8" x2="12" y2="12" />
|
||||||
stroke="currentcolor"
|
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||||
fill="none"
|
</svg>
|
||||||
stroke-linecap="round"
|
</a>
|
||||||
stroke-linejoin="round"
|
<Tooltip triggeredBy="#exited">{'Service exited with an error!'}</Tooltip>
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path
|
|
||||||
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
|
|
||||||
/>
|
|
||||||
<line x1="12" y1="8" x2="12" y2="12" />
|
|
||||||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
{#if $status.database.initialLoading}
|
|
||||||
<button
|
|
||||||
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
|
||||||
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
|
||||||
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
|
||||||
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
|
||||||
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
|
||||||
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{:else if $status.database.isRunning}
|
|
||||||
<button
|
|
||||||
on:click={stopDatabase}
|
|
||||||
type="submit"
|
|
||||||
disabled={!$appSession.isAdmin}
|
|
||||||
class="icons bg-transparent tooltip tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
|
|
||||||
data-tip={$appSession.isAdmin
|
|
||||||
? $t('database.stop_database')
|
|
||||||
: $t('database.permission_denied_stop_database')}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<rect x="6" y="5" width="4" height="14" rx="1" />
|
|
||||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
on:click={startDatabase}
|
|
||||||
type="submit"
|
|
||||||
disabled={!$appSession.isAdmin}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
|
|
||||||
data-tip={$appSession.isAdmin
|
|
||||||
? $t('database.start_database')
|
|
||||||
: $t('database.permission_denied_start_database')}
|
|
||||||
><svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M7 4v16l13 -8z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
<div class="border border-stone-700 h-8" />
|
{#if $status.database.initialLoading}
|
||||||
<a
|
|
||||||
href="/databases/{id}"
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-yellow-500 rounded"
|
|
||||||
class:text-yellow-500={$page.url.pathname === `/databases/${id}`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/databases/${id}`}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm disabled:text-red-500"
|
class="icons flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
||||||
data-tip={$t('application.configurations')}
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -263,34 +188,25 @@
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<rect x="4" y="8" width="4" height="4" />
|
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
||||||
<line x1="6" y1="4" x2="6" y2="8" />
|
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
||||||
<line x1="6" y1="12" x2="6" y2="20" />
|
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
||||||
<rect x="10" y="14" width="4" height="4" />
|
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
||||||
<line x1="12" y1="4" x2="12" y2="14" />
|
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
||||||
<line x1="12" y1="18" x2="12" y2="20" />
|
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
||||||
<rect x="16" y="5" width="4" height="4" />
|
</svg>
|
||||||
<line x1="18" y1="4" x2="18" y2="5" />
|
</button>
|
||||||
<line x1="18" y1="9" x2="18" y2="20" />
|
{:else if $status.database.isRunning}
|
||||||
</svg></button
|
|
||||||
></a
|
|
||||||
>
|
|
||||||
<div class="border border-stone-700 h-8" />
|
|
||||||
<a
|
|
||||||
href={$status.database.isRunning ? `/databases/${id}/logs` : null}
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-pink-500 rounded"
|
|
||||||
class:text-pink-500={$page.url.pathname === `/databases/${id}/logs`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/databases/${id}/logs`}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
disabled={!$status.database.isRunning}
|
id="stop"
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
on:click={stopDatabase}
|
||||||
data-tip={$t('database.logs')}
|
type="submit"
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
|
class="icons bg-transparent text-sm flex items-center space-x-2 text-red-500"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="h-6 w-6"
|
class="w-6 h-6"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -299,25 +215,120 @@
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
<rect x="6" y="5" width="4" height="14" rx="1" />
|
||||||
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||||
<line x1="3" y1="6" x2="3" y2="19" />
|
</svg>
|
||||||
<line x1="12" y1="6" x2="12" y2="19" />
|
</button>
|
||||||
<line x1="21" y1="6" x2="21" y2="19" />
|
<Tooltip triggeredBy="#stop">{'Stop'}</Tooltip>
|
||||||
</svg></button
|
{:else}
|
||||||
></a
|
<button
|
||||||
>
|
id="start"
|
||||||
|
on:click={startDatabase}
|
||||||
|
type="submit"
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
|
class="icons bg-transparent text-sm flex items-center space-x-2 text-green-500"
|
||||||
|
><svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M7 4v16l13 -8z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<Tooltip triggeredBy="#start">{'Start'}</Tooltip>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
<div class="border border-stone-700 h-8" />
|
||||||
|
<a
|
||||||
|
id="configuration"
|
||||||
|
href="/databases/{id}"
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-yellow-500 rounded"
|
||||||
|
class:text-yellow-500={$page.url.pathname === `/databases/${id}`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/databases/${id}`}
|
||||||
|
>
|
||||||
|
<button class="icons bg-transparent m text-sm disabled:text-red-500">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<rect x="4" y="8" width="4" height="4" />
|
||||||
|
<line x1="6" y1="4" x2="6" y2="8" />
|
||||||
|
<line x1="6" y1="12" x2="6" y2="20" />
|
||||||
|
<rect x="10" y="14" width="4" height="4" />
|
||||||
|
<line x1="12" y1="4" x2="12" y2="14" />
|
||||||
|
<line x1="12" y1="18" x2="12" y2="20" />
|
||||||
|
<rect x="16" y="5" width="4" height="4" />
|
||||||
|
<line x1="18" y1="4" x2="18" y2="5" />
|
||||||
|
<line x1="18" y1="9" x2="18" y2="20" />
|
||||||
|
</svg></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
<Tooltip triggeredBy="#configuration">{'Configuration'}</Tooltip>
|
||||||
|
<div class="border border-stone-700 h-8" />
|
||||||
|
<a
|
||||||
|
id="databaselogs"
|
||||||
|
href={$status.database.isRunning ? `/databases/${id}/logs` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-pink-500 rounded"
|
||||||
|
class:text-pink-500={$page.url.pathname === `/databases/${id}/logs`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/databases/${id}/logs`}
|
||||||
|
>
|
||||||
|
<button disabled={!$status.database.isRunning} class="icons bg-transparent text-sm">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
||||||
|
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
||||||
|
<line x1="3" y1="6" x2="3" y2="19" />
|
||||||
|
<line x1="12" y1="6" x2="12" y2="19" />
|
||||||
|
<line x1="21" y1="6" x2="21" y2="19" />
|
||||||
|
</svg></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
<Tooltip triggeredBy="#databaselogs">{'Logs'}</Tooltip>
|
||||||
|
{#if forceDelete}
|
||||||
<button
|
<button
|
||||||
on:click={deleteDatabase}
|
on:click={() => deleteDatabase(true)}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!$appSession.isAdmin}
|
disabled={!$appSession.isAdmin}
|
||||||
class:hover:text-red-500={$appSession.isAdmin}
|
class:hover:text-red-500={$appSession.isAdmin}
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
class="icons bg-transparent text-sm"
|
||||||
data-tip={$appSession.isAdmin
|
>
|
||||||
? $t('database.delete_database')
|
Force Delete</button
|
||||||
: $t('database.permission_denied_delete_database')}><DeleteIcon /></button
|
>{:else}
|
||||||
|
<button
|
||||||
|
id="delete"
|
||||||
|
on:click={() => deleteDatabase(false)}
|
||||||
|
type="submit"
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
|
class:hover:text-red-500={$appSession.isAdmin}
|
||||||
|
class="icons bg-transparent text-sm"><DeleteIcon /></button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<Tooltip triggeredBy="#delete">{'Delete'}</Tooltip>
|
||||||
</nav>
|
</nav>
|
||||||
{/if}
|
{/if}
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
@@ -17,9 +18,13 @@
|
|||||||
let logsEl: any;
|
let logsEl: any;
|
||||||
let position = 0;
|
let position = 0;
|
||||||
let loadingLogs = false;
|
let loadingLogs = false;
|
||||||
let database: any = {};
|
let database = {
|
||||||
|
name: null
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
const response = await get(`/databases/${id}`);
|
||||||
|
database = response.database;
|
||||||
const { logs: firstLogs } = await get(`/databases/${id}/logs`);
|
const { logs: firstLogs } = await get(`/databases/${id}/logs`);
|
||||||
logs = firstLogs;
|
logs = firstLogs;
|
||||||
loadAllLogs();
|
loadAllLogs();
|
||||||
@@ -94,29 +99,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="text-xs">{database.name}</span>
|
<span class="text-xs">{database.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if database.fqdn}
|
|
||||||
<a
|
|
||||||
href={database.fqdn}
|
|
||||||
target="_blank"
|
|
||||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
|
||||||
><svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
|
||||||
<line x1="10" y1="14" x2="20" y2="4" />
|
|
||||||
<polyline points="15 4 20 4 20 9" />
|
|
||||||
</svg></a
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row justify-center space-x-2 px-10 pt-6">
|
<div class="flex flex-row justify-center space-x-2 px-10 pt-6">
|
||||||
{#if logs.length === 0}
|
{#if logs.length === 0}
|
||||||
@@ -129,9 +111,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="flex justify-end sticky top-0 p-1 mx-1">
|
<div class="flex justify-end sticky top-0 p-1 mx-1">
|
||||||
<button
|
<button
|
||||||
|
id="follow"
|
||||||
on:click={followBuild}
|
on:click={followBuild}
|
||||||
class="bg-transparent btn btn-sm tooltip tooltip-primary tooltip-bottom"
|
class="bg-transparent btn btn-sm"
|
||||||
data-tip="Follow logs"
|
|
||||||
class:text-green-500={followingLogs}
|
class:text-green-500={followingLogs}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -151,6 +133,7 @@
|
|||||||
<line x1="16" y1="12" x2="12" y2="16" />
|
<line x1="16" y1="12" x2="12" y2="16" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
<Tooltip triggeredBy="#follow">Follow Logs</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="font-mono w-full leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
class="font-mono w-full leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
||||||
|
|||||||
@@ -142,8 +142,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
<div class="flex space-x-1 pb-5">
|
<div class="flex md:flex-row space-y-2 md:space-y-0 space-x-0 md:space-x-2 flex-col pb-5">
|
||||||
<div class="title font-bold">{$t('forms.configuration')}</div>
|
<div class="title">{$t('forms.configuration')}</div>
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10 ">
|
<div class="grid lg:grid-cols-2 items-center px-10 ">
|
||||||
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
||||||
<input
|
<input
|
||||||
name="name"
|
name="name"
|
||||||
@@ -173,7 +173,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid lg:grid-cols-2 items-center px-10">
|
||||||
<label for="engine" class="text-base font-bold text-stone-100">{$t('forms.engine')}</label>
|
<label for="engine" class="text-base font-bold text-stone-100">{$t('forms.engine')}</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
id="engine"
|
id="engine"
|
||||||
@@ -184,7 +184,7 @@
|
|||||||
value={destination.engine}
|
value={destination.engine}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid lg:grid-cols-2 items-center px-10">
|
||||||
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
|
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
id="network"
|
id="network"
|
||||||
@@ -196,14 +196,15 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if $appSession.teamId === '0'}
|
{#if $appSession.teamId === '0'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid lg:grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="changeProxySetting"
|
||||||
loading={loading.proxy}
|
loading={loading.proxy}
|
||||||
disabled={cannotDisable}
|
disabled={cannotDisable}
|
||||||
bind:setting={destination.isCoolifyProxyUsed}
|
bind:setting={destination.isCoolifyProxyUsed}
|
||||||
on:click={changeProxySetting}
|
on:click={changeProxySetting}
|
||||||
title={$t('destination.use_coolify_proxy')}
|
title={$t('destination.use_coolify_proxy')}
|
||||||
description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration. Databases will have their own proxy. <br><br>${
|
description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration.${
|
||||||
cannotDisable
|
cannotDisable
|
||||||
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
|
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
|
||||||
: ''
|
: ''
|
||||||
|
|||||||
@@ -33,11 +33,7 @@
|
|||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
<div class="flex items-center space-x-2 pb-5">
|
<div class="flex items-center space-x-2 pb-5">
|
||||||
<div class="title font-bold">{$t('forms.configuration')}</div>
|
<div class="title font-bold">{$t('forms.configuration')}</div>
|
||||||
<button
|
<button type="submit" class="btn btn-sm bg-destinations" class:loading disabled={loading}
|
||||||
type="submit"
|
|
||||||
class="btn btn-sm bg-destinations"
|
|
||||||
class:loading={loading}
|
|
||||||
disabled={loading}
|
|
||||||
>{loading
|
>{loading
|
||||||
? payload.isCoolifyProxyUsed
|
? payload.isCoolifyProxyUsed
|
||||||
? $t('destination.new.saving_and_configuring_proxy')
|
? $t('destination.new.saving_and_configuring_proxy')
|
||||||
@@ -69,12 +65,13 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if $appSession.teamId === '0'}
|
{#if $appSession.teamId === '0'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="changeProxySetting"
|
||||||
bind:setting={payload.isCoolifyProxyUsed}
|
bind:setting={payload.isCoolifyProxyUsed}
|
||||||
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
|
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
|
||||||
title={$t('destination.use_coolify_proxy')}
|
title={$t('destination.use_coolify_proxy')}
|
||||||
description={$t('destination.new.install_proxy')}
|
description={'This will install a proxy on the destination to allow you to access your applications and services without any manual configuration.'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import SimpleExplainer from '$lib/components/SimpleExplainer.svelte';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
@@ -29,22 +29,18 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="text-center flex justify-center">
|
<div class="text-center flex justify-center">
|
||||||
<Explainer
|
<SimpleExplainer
|
||||||
customClass="max-w-[32rem]"
|
customClass="max-w-[32rem]"
|
||||||
text="Remote Docker Engines are using <span class='text-white font-bold'>SSH</span> to communicate with the remote docker engine.
|
text="Remote Docker Engines are using <span class='text-white font-bold'>SSH</span> to communicate with the remote docker engine.
|
||||||
You need to setup an <span class='text-white font-bold'>SSH key</span> in advance on the server and install Docker.
|
You need to setup an <span class='text-white font-bold'>SSH key</span> in advance on the server and install Docker.
|
||||||
<br>See <a class='text-white' href='https://docs.coollabs.io/coolify/destinations#remote-docker-engine'>docs</a> for more details."
|
<br>See <a class='text-white' href='https://docs.coollabs.io/coolify/destinations#remote-docker-engine' target='blank'>docs</a> for more details."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center px-6 pb-8">
|
<div class="flex justify-center px-6 pb-8">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
<div class="flex items-center space-x-2 pb-5">
|
<div class="flex items-center space-x-2 pb-5">
|
||||||
<div class="title font-bold">{$t('forms.configuration')}</div>
|
<div class="title font-bold">{$t('forms.configuration')}</div>
|
||||||
<button
|
<button type="submit" class="btn btn-sm bg-destinations" class:loading disabled={loading}
|
||||||
type="submit"
|
|
||||||
class="btn btn-sm bg-destinations"
|
|
||||||
class:loading={loading}
|
|
||||||
disabled={loading}
|
|
||||||
>{loading
|
>{loading
|
||||||
? payload.isCoolifyProxyUsed
|
? payload.isCoolifyProxyUsed
|
||||||
? $t('destination.new.saving_and_configuring_proxy')
|
? $t('destination.new.saving_and_configuring_proxy')
|
||||||
@@ -97,12 +93,13 @@
|
|||||||
bind:value={payload.network}
|
bind:value={payload.network}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="isCoolifyProxyUsed"
|
||||||
bind:setting={payload.isCoolifyProxyUsed}
|
bind:setting={payload.isCoolifyProxyUsed}
|
||||||
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
|
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
|
||||||
title={$t('destination.use_coolify_proxy')}
|
title={$t('destination.use_coolify_proxy')}
|
||||||
description={$t('destination.new.install_proxy')}
|
description={'This will install a proxy on the destination to allow you to access your applications and services without any manual configuration.'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -259,14 +259,15 @@
|
|||||||
/></a
|
/></a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="changeProxySetting"
|
||||||
disabled={cannotDisable}
|
disabled={cannotDisable}
|
||||||
loading={loading.proxy}
|
loading={loading.proxy}
|
||||||
bind:setting={destination.isCoolifyProxyUsed}
|
bind:setting={destination.isCoolifyProxyUsed}
|
||||||
on:click={changeProxySetting}
|
on:click={changeProxySetting}
|
||||||
title={$t('destination.use_coolify_proxy')}
|
title={$t('destination.use_coolify_proxy')}
|
||||||
description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration. Databases will have their own proxy. <br><br>${
|
description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration.${
|
||||||
cannotDisable
|
cannotDisable
|
||||||
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
|
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
|
||||||
: ''
|
: ''
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
return handlerNotFoundLoad(error, url);
|
return handlerNotFoundLoad(error, url);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -56,12 +56,16 @@
|
|||||||
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
||||||
import { appSession } from '$lib/store';
|
import { appSession } from '$lib/store';
|
||||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
const isDestinationDeletable = destination?.application.length === 0 && destination?.database.length === 0 && destination?.service.length === 0
|
const isDestinationDeletable =
|
||||||
|
destination?.application.length === 0 &&
|
||||||
|
destination?.database.length === 0 &&
|
||||||
|
destination?.service.length === 0;
|
||||||
|
|
||||||
async function deleteDestination(destination: any) {
|
async function deleteDestination(destination: any) {
|
||||||
if (!isDestinationDeletable) return
|
if (!isDestinationDeletable) return;
|
||||||
const sure = confirm($t('application.confirm_to_delete', { name: destination.name }));
|
const sure = confirm($t('application.confirm_to_delete', { name: destination.name }));
|
||||||
if (sure) {
|
if (sure) {
|
||||||
try {
|
try {
|
||||||
@@ -74,12 +78,12 @@
|
|||||||
}
|
}
|
||||||
function deletable() {
|
function deletable() {
|
||||||
if (!isDestinationDeletable) {
|
if (!isDestinationDeletable) {
|
||||||
return "Please delete all resources before deleting this."
|
return 'Please delete all resources before deleting this.';
|
||||||
}
|
}
|
||||||
if ($appSession.isAdmin) {
|
if ($appSession.isAdmin) {
|
||||||
return $t('destination.delete_destination')
|
return $t('destination.delete_destination');
|
||||||
} else {
|
} else {
|
||||||
return $t('destination.permission_denied_delete_destination')
|
return $t('destination.permission_denied_delete_destination');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -87,14 +91,15 @@
|
|||||||
{#if id !== 'new'}
|
{#if id !== 'new'}
|
||||||
<nav class="nav-side">
|
<nav class="nav-side">
|
||||||
<button
|
<button
|
||||||
|
id="delete"
|
||||||
on:click={() => deleteDestination(destination)}
|
on:click={() => deleteDestination(destination)}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!$appSession.isAdmin && isDestinationDeletable}
|
disabled={!$appSession.isAdmin && isDestinationDeletable}
|
||||||
class:hover:text-red-500={$appSession.isAdmin && isDestinationDeletable}
|
class:hover:text-red-500={$appSession.isAdmin && isDestinationDeletable}
|
||||||
class="icons tooltip tooltip-primary tooltip-left bg-transparent text-sm"
|
class="icons bg-transparent text-sm"
|
||||||
class:text-stone-600={!isDestinationDeletable}
|
class:text-stone-600={!isDestinationDeletable}><DeleteIcon /></button
|
||||||
data-tip={deletable()}><DeleteIcon /></button
|
|
||||||
>
|
>
|
||||||
</nav>
|
</nav>
|
||||||
|
<Tooltip triggeredBy="#delete">{deletable()}</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -108,24 +108,21 @@
|
|||||||
|
|
||||||
<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">Identity and Access Management</div>
|
<div class="mr-4 text-2xl tracking-tight">Identity and Access Management</div>
|
||||||
<button
|
<button on:click={newTeam} class="btn btn-square btn-sm bg-iam">
|
||||||
on:click={newTeam}
|
<svg
|
||||||
class="btn btn-square btn-sm bg-iam"
|
class="h-6 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
|
||||||
>
|
>
|
||||||
<svg
|
</button>
|
||||||
class="h-6 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
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if invitations.length > 0}
|
{#if invitations.length > 0}
|
||||||
@@ -170,14 +167,11 @@
|
|||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each accounts as account}
|
{#each accounts as account}
|
||||||
<tr>
|
<tr class="grid items-center justify-center gap-2 lg:grid-flow-col">
|
||||||
<td class="px-2">{account.email}</td>
|
<td class="px-2">{account.email}</td>
|
||||||
<td class="flex space-x-2">
|
<td class="flex space-x-2">
|
||||||
<form on:submit|preventDefault={() => resetPassword(account.id)}>
|
<form on:submit|preventDefault={() => resetPassword(account.id)}>
|
||||||
<button
|
<button class="my-4 btn btn-sm bg-iam">Reset Password</button>
|
||||||
class="my-4 btn btn-sm bg-iam"
|
|
||||||
>Reset Password</button
|
|
||||||
>
|
|
||||||
</form>
|
</form>
|
||||||
<form on:submit|preventDefault={() => deleteUser(account.id)}>
|
<form on:submit|preventDefault={() => deleteUser(account.id)}>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
async function deleteTeam() {
|
async function deleteTeam() {
|
||||||
@@ -69,15 +70,14 @@
|
|||||||
<nav class="nav-side">
|
<nav class="nav-side">
|
||||||
{#if team.id !== '0'}
|
{#if team.id !== '0'}
|
||||||
<button
|
<button
|
||||||
|
id="delete"
|
||||||
on:click={deleteTeam}
|
on:click={deleteTeam}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!$appSession.isAdmin}
|
disabled={!$appSession.isAdmin}
|
||||||
class:hover:text-red-500={$appSession.isAdmin}
|
class:hover:text-red-500={$appSession.isAdmin}
|
||||||
class="icons tooltip tooltip-primary tooltip-left bg-transparent text-sm"
|
class="icons bg-transparent text-sm"><DeleteIcon /></button
|
||||||
data-tip={$appSession.isAdmin
|
|
||||||
? 'Delete'
|
|
||||||
: $t('destination.permission_denied_delete_destination')}><DeleteIcon /></button
|
|
||||||
>
|
>
|
||||||
|
<Tooltip triggeredBy="#delete">Delete</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
</nav>
|
</nav>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
export let team: any;
|
export let team: any;
|
||||||
export let invitations: any[];
|
export let invitations: any[];
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import SimpleExplainer from '$lib/components/SimpleExplainer.svelte';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
||||||
{#if team.id === '0'}
|
{#if team.id === '0'}
|
||||||
<Explainer customClass="w-full" text={$t('team.root_team_explainer')} />
|
<SimpleExplainer customClass="w-full" text={$t('team.root_team_explainer')} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<input id="name" name="name" placeholder="name" bind:value={team.name} />
|
<input id="name" name="name" placeholder="name" bind:value={team.name} />
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Explainer text={$t('team.invite_only_register_explainer')} />
|
<SimpleExplainer text={$t('team.invite_only_register_explainer')} />
|
||||||
<div class="flex-col space-y-2 px-4 pt-5 sm:px-6">
|
<div class="flex-col space-y-2 px-4 pt-5 sm:px-6">
|
||||||
<div class="flex space-x-0">
|
<div class="flex space-x-0">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
export let name = '';
|
export let name = '';
|
||||||
export let value = '';
|
export let value = '';
|
||||||
export let isNewSecret = false;
|
export let isNewSecret = false;
|
||||||
|
|||||||
@@ -71,4 +71,8 @@
|
|||||||
<a href="https://searxng.org" target="_blank">
|
<a href="https://searxng.org" target="_blank">
|
||||||
<Icons.Searxng />
|
<Icons.Searxng />
|
||||||
</a>
|
</a>
|
||||||
|
{:else if service.type === 'weblate'}
|
||||||
|
<a href="https://weblate.org" target="_blank">
|
||||||
|
<Icons.Weblate />
|
||||||
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -7,8 +7,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-5">
|
<div class="flex space-x-1 py-5">
|
||||||
<div class="title">Ghost</div>
|
<div class="title">
|
||||||
<Explainer text={'You can change these values in the Ghost admin panel.'} />
|
Ghost <Explainer explanation="You can change these values in the Ghost admin panel." />
|
||||||
|
</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="email">{$t('forms.default_email_address')}</label>
|
<label for="email">{$t('forms.default_email_address')}</label>
|
||||||
|
|||||||
@@ -1,17 +1,51 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import { addToast, status } from '$lib/store';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
import { post } from '$lib/api';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { errorNotification } from '$lib/common';
|
||||||
export let service: any;
|
export let service: any;
|
||||||
function toggleEmailSmtpUseTls() {
|
|
||||||
service.glitchTip.emailSmtpUseTls = !service.glitchTip.emailSmtpUseTls;
|
const { id } = $page.params;
|
||||||
}
|
let loading = false;
|
||||||
function toggleEmailSmtpUseSsl() {
|
|
||||||
service.glitchTip.emailSmtpUseSsl = !service.glitchTip.emailSmtpUseSsl;
|
async function changeSettings(name: any) {
|
||||||
}
|
if (loading || $status.service.isRunning) return;
|
||||||
function toggleEnableOpenUserRegistration() {
|
|
||||||
service.glitchTip.enableOpenUserRegistration = !service.glitchTip.enableOpenUserRegistration;
|
let enableOpenUserRegistration = service.glitchTip.enableOpenUserRegistration;
|
||||||
|
let emailSmtpUseSsl = service.glitchTip.emailSmtpUseSsl;
|
||||||
|
let emailSmtpUseTls = service.glitchTip.emailSmtpUseTls;
|
||||||
|
|
||||||
|
loading = true;
|
||||||
|
if (name === 'enableOpenUserRegistration') {
|
||||||
|
enableOpenUserRegistration = !enableOpenUserRegistration;
|
||||||
|
}
|
||||||
|
if (name === 'emailSmtpUseSsl') {
|
||||||
|
emailSmtpUseSsl = !emailSmtpUseSsl;
|
||||||
|
}
|
||||||
|
if (name === 'emailSmtpUseTls') {
|
||||||
|
emailSmtpUseTls = !emailSmtpUseTls;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await post(`/services/${id}/glitchtip/settings`, {
|
||||||
|
enableOpenUserRegistration,
|
||||||
|
emailSmtpUseSsl,
|
||||||
|
emailSmtpUseTls
|
||||||
|
});
|
||||||
|
service.glitchTip.emailSmtpUseTls = emailSmtpUseTls;
|
||||||
|
service.glitchTip.emailSmtpUseSsl = emailSmtpUseSsl;
|
||||||
|
service.glitchTip.enableOpenUserRegistration = enableOpenUserRegistration;
|
||||||
|
return addToast({
|
||||||
|
message: 'Settings updated.',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -19,109 +53,121 @@
|
|||||||
<div class="title">GlitchTip</div>
|
<div class="title">GlitchTip</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-2 font-bold">
|
|
||||||
<div class="subtitle">Settings</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="enableOpenUserRegistration"
|
||||||
|
bind:setting={service.glitchTip.enableOpenUserRegistration}
|
||||||
|
{loading}
|
||||||
|
disabled={$status.service.isRunning}
|
||||||
|
on:click={() => changeSettings('enableOpenUserRegistration')}
|
||||||
|
title="Enable Open User Registration"
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
<!-- <Setting
|
||||||
bind:setting={service.glitchTip.enableOpenUserRegistration}
|
bind:setting={service.glitchTip.enableOpenUserRegistration}
|
||||||
on:click={toggleEnableOpenUserRegistration}
|
on:click={toggleEnableOpenUserRegistration}
|
||||||
title={'Enable Open User Registration'}
|
title={'Enable Open User Registration'}
|
||||||
description={''}
|
description={''}
|
||||||
/>
|
/> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-2 font-bold">
|
<div class="flex space-x-1 py-2 font-bold">
|
||||||
<div class="subtitle">Email settings</div>
|
<div class="subtitle">Email settings</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<Setting
|
||||||
|
id="emailSmtpUseTls"
|
||||||
|
bind:setting={service.glitchTip.emailSmtpUseTls}
|
||||||
|
{loading}
|
||||||
|
disabled={$status.service.isRunning}
|
||||||
|
on:click={() => changeSettings('emailSmtpUseTls')}
|
||||||
|
title="Use TLS for SMTP"
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="defaultEmailFrom" class="text-base font-bold text-stone-100">Default Email From</label
|
<Setting
|
||||||
>
|
id="emailSmtpUseSsl"
|
||||||
|
bind:setting={service.glitchTip.emailSmtpUseSsl}
|
||||||
|
{loading}
|
||||||
|
disabled={$status.service.isRunning}
|
||||||
|
on:click={() => changeSettings('emailSmtpUseSsl')}
|
||||||
|
title="Use SSL for SMTP"
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultEmailFrom">Default Email From</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
required
|
required
|
||||||
name="defaultEmailFrom"
|
name="defaultEmailFrom"
|
||||||
id="defaultEmailFrom"
|
id="defaultEmailFrom"
|
||||||
value={service.glitchTip.defaultEmailFrom}
|
bind:value={service.glitchTip.defaultEmailFrom}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="emailSmtpHost" class="text-base font-bold text-stone-100">SMTP Host</label>
|
<label for="emailSmtpHost">SMTP Host</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="emailSmtpHost"
|
name="emailSmtpHost"
|
||||||
id="emailSmtpHost"
|
id="emailSmtpHost"
|
||||||
value={service.glitchTip.emailSmtpHost}
|
bind:value={service.glitchTip.emailSmtpHost}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="emailSmtpPort" class="text-base font-bold text-stone-100">SMTP Port</label>
|
<label for="emailSmtpPort">SMTP Port</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="emailSmtpPort"
|
name="emailSmtpPort"
|
||||||
id="emailSmtpPort"
|
id="emailSmtpPort"
|
||||||
value={service.glitchTip.emailSmtpPort}
|
bind:value={service.glitchTip.emailSmtpPort}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="emailSmtpUser" class="text-base font-bold text-stone-100">SMTP User</label>
|
<label for="emailSmtpUser">SMTP User</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="emailSmtpUser"
|
name="emailSmtpUser"
|
||||||
id="emailSmtpUser"
|
id="emailSmtpUser"
|
||||||
value={service.glitchTip.emailSmtpUser}
|
bind:value={service.glitchTip.emailSmtpUser}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="emailSmtpPassword" class="text-base font-bold text-stone-100">SMTP Password</label>
|
<label for="emailSmtpPassword">SMTP Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="emailSmtpPassword"
|
name="emailSmtpPassword"
|
||||||
id="emailSmtpPassword"
|
id="emailSmtpPassword"
|
||||||
value={service.glitchTip.emailSmtpPassword}
|
bind:value={service.glitchTip.emailSmtpPassword}
|
||||||
isPasswordField
|
isPasswordField
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<label for="emailBackend">Email Backend</label>
|
||||||
bind:setting={service.glitchTip.emailSmtpUseTls}
|
<CopyPasswordField
|
||||||
on:click={toggleEmailSmtpUseTls}
|
name="emailBackend"
|
||||||
title={'SMTP Use TLS'}
|
id="emailBackend"
|
||||||
description={''}
|
bind:value={service.glitchTip.emailBackend}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<label for="mailgunApiKey">Mailgun API Key</label>
|
||||||
bind:setting={service.glitchTip.emailSmtpUseSsl}
|
|
||||||
on:click={toggleEmailSmtpUseSsl}
|
|
||||||
title={'SMTP Use SSL'}
|
|
||||||
description={''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="emailBackend" class="text-base font-bold text-stone-100">Email Backend</label>
|
|
||||||
<CopyPasswordField name="emailBackend" id="emailBackend" value={service.glitchTip.emailBackend} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="mailgunApiKey" class="text-base font-bold text-stone-100">Mailgun API Key</label>
|
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="mailgunApiKey"
|
name="mailgunApiKey"
|
||||||
id="mailgunApiKey"
|
id="mailgunApiKey"
|
||||||
value={service.glitchTip.mailgunApiKey}
|
bind:value={service.glitchTip.mailgunApiKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="sendgridApiKey" class="text-base font-bold text-stone-100">SendGrid API Key</label>
|
<label for="sendgridApiKey">SendGrid API Key</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="sendgridApiKey"
|
name="sendgridApiKey"
|
||||||
id="sendgridApiKey"
|
id="sendgridApiKey"
|
||||||
value={service.glitchTip.sendgridApiKey}
|
bind:value={service.glitchTip.sendgridApiKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -130,35 +176,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="defaultEmail" class="text-base font-bold text-stone-100">{$t('forms.email')}</label>
|
<label for="defaultEmail">{$t('forms.email')}</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="defaultEmail"
|
name="defaultEmail"
|
||||||
id="defaultEmail"
|
id="defaultEmail"
|
||||||
value={service.glitchTip.defaultEmail}
|
bind:value={service.glitchTip.defaultEmail}
|
||||||
readonly
|
readonly
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="defaultUsername" class="text-base font-bold text-stone-100"
|
<label for="defaultUsername">{$t('forms.username')}</label>
|
||||||
>{$t('forms.username')}</label
|
|
||||||
>
|
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="defaultUsername"
|
name="defaultUsername"
|
||||||
id="defaultUsername"
|
id="defaultUsername"
|
||||||
value={service.glitchTip.defaultUsername}
|
bind:value={service.glitchTip.defaultUsername}
|
||||||
readonly
|
readonly
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="defaultPassword" class="text-base font-bold text-stone-100"
|
<label for="defaultPassword">{$t('forms.password')}</label>
|
||||||
>{$t('forms.password')}</label
|
|
||||||
>
|
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="defaultPassword"
|
name="defaultPassword"
|
||||||
id="defaultPassword"
|
id="defaultPassword"
|
||||||
value={service.glitchTip.defaultPassword}
|
bind:value={service.glitchTip.defaultPassword}
|
||||||
readonly
|
readonly
|
||||||
disabled
|
disabled
|
||||||
isPasswordField
|
isPasswordField
|
||||||
@@ -170,38 +212,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="postgresqlUser" class="text-base font-bold text-stone-100"
|
<label for="postgresqlUser">{$t('forms.username')}</label>
|
||||||
>{$t('forms.username')}</label
|
|
||||||
>
|
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="postgresqlUser"
|
name="postgresqlUser"
|
||||||
id="postgresqlUser"
|
id="postgresqlUser"
|
||||||
value={service.glitchTip.postgresqlUser}
|
bind:value={service.glitchTip.postgresqlUser}
|
||||||
readonly
|
readonly
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="postgresqlPassword" class="text-base font-bold text-stone-100"
|
<label for="postgresqlPassword">{$t('forms.password')}</label>
|
||||||
>{$t('forms.password')}</label
|
|
||||||
>
|
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
id="postgresqlPassword"
|
id="postgresqlPassword"
|
||||||
isPasswordField
|
isPasswordField
|
||||||
readonly
|
readonly
|
||||||
disabled
|
disabled
|
||||||
name="postgresqlPassword"
|
name="postgresqlPassword"
|
||||||
value={service.glitchTip.postgresqlPassword}
|
bind:value={service.glitchTip.postgresqlPassword}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="postgresqlDatabase" class="text-base font-bold text-stone-100"
|
<label for="postgresqlDatabase">{$t('index.database')}</label>
|
||||||
>{$t('index.database')}</label
|
|
||||||
>
|
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="postgresqlDatabase"
|
name="postgresqlDatabase"
|
||||||
id="postgresqlDatabase"
|
id="postgresqlDatabase"
|
||||||
value={service.glitchTip.postgresqlDatabase}
|
bind:value={service.glitchTip.postgresqlDatabase}
|
||||||
readonly
|
readonly
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
export let service: any;
|
export let service: any;
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
<div class="title">Plausible Analytics</div>
|
<div class="title">Plausible Analytics</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="scriptName">Script Name</label>
|
<label for="scriptName"
|
||||||
|
>Script Name <Explainer
|
||||||
|
explanation="Useful if you would like to rename the collector script to prevent it blocked by AdBlockers."
|
||||||
|
/></label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
name="scriptName"
|
name="scriptName"
|
||||||
id="scriptName"
|
id="scriptName"
|
||||||
@@ -21,9 +25,6 @@
|
|||||||
bind:value={service.plausibleAnalytics.scriptName}
|
bind:value={service.plausibleAnalytics.scriptName}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Explainer
|
|
||||||
text="Useful if you would like to rename the collector script to prevent it blocked by AdBlockers."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="email">{$t('forms.email')}</label>
|
<label for="email">{$t('forms.email')}</label>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { appSession, disabledButton, status, location, setLocation, addToast } from '$lib/store';
|
import { appSession, disabledButton, status, location, setLocation, addToast } from '$lib/store';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
|
|
||||||
import Fider from './_Fider.svelte';
|
import Fider from './_Fider.svelte';
|
||||||
@@ -30,6 +29,9 @@
|
|||||||
import Appwrite from './_Appwrite.svelte';
|
import Appwrite from './_Appwrite.svelte';
|
||||||
import Moodle from './_Moodle.svelte';
|
import Moodle from './_Moodle.svelte';
|
||||||
import Searxng from './_Searxng.svelte';
|
import Searxng from './_Searxng.svelte';
|
||||||
|
import Weblate from './_Weblate.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import Taiga from './_Taiga.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
$: isDisabled =
|
$: isDisabled =
|
||||||
@@ -282,8 +284,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 px-10">
|
<div class="grid grid-cols-2 px-10">
|
||||||
<div class="flex-col ">
|
<div class="flex-col ">
|
||||||
<label for="apiFqdn" class="pt-2 text-base font-bold text-stone-100">API URL</label>
|
<label for="apiFqdn" class="pt-2 text-base font-bold text-stone-100"
|
||||||
<Explainer text={$t('application.https_explainer')} />
|
>API URL <Explainer explanation={$t('application.https_explainer')} /></label
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
@@ -301,9 +304,9 @@
|
|||||||
<div class="grid grid-cols-2 px-10">
|
<div class="grid grid-cols-2 px-10">
|
||||||
<div class="flex-col ">
|
<div class="flex-col ">
|
||||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
||||||
>{$t('application.url_fqdn')}</label
|
>{$t('application.url_fqdn')}
|
||||||
>
|
<Explainer explanation={$t('application.https_explainer')} />
|
||||||
<Explainer text={$t('application.https_explainer')} />
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
@@ -354,6 +357,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="dualCerts"
|
||||||
disabled={$status.service.isRunning}
|
disabled={$status.service.isRunning}
|
||||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||||
bind:setting={dualCerts}
|
bind:setting={dualCerts}
|
||||||
@@ -363,7 +367,11 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
|
<label for="exposePort" class="text-base font-bold text-stone-100"
|
||||||
|
>Exposed Port <Explainer
|
||||||
|
explanation={'You can expose your application to a port on the host system.<br><br>Useful if you would like to use your own reverse proxy or tunnel and also in development mode. Otherwise leave empty.'}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
readonly={!$appSession.isAdmin && !$status.service.isRunning}
|
readonly={!$appSession.isAdmin && !$status.service.isRunning}
|
||||||
disabled={!$appSession.isAdmin ||
|
disabled={!$appSession.isAdmin ||
|
||||||
@@ -374,9 +382,6 @@
|
|||||||
bind:value={service.exposePort}
|
bind:value={service.exposePort}
|
||||||
placeholder="12345"
|
placeholder="12345"
|
||||||
/>
|
/>
|
||||||
<Explainer
|
|
||||||
text={'You can expose your application to a port on the host system.<br><br>Useful if you would like to use your own reverse proxy or tunnel and also in development mode. Otherwise leave empty.'}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if service.type === 'plausibleanalytics'}
|
{#if service.type === 'plausibleanalytics'}
|
||||||
@@ -405,6 +410,10 @@
|
|||||||
<GlitchTip bind:service />
|
<GlitchTip bind:service />
|
||||||
{:else if service.type === 'searxng'}
|
{:else if service.type === 'searxng'}
|
||||||
<Searxng bind:service />
|
<Searxng bind:service />
|
||||||
|
{:else if service.type === 'weblate'}
|
||||||
|
<Weblate bind:service />
|
||||||
|
{:else if service.type === 'taiga'}
|
||||||
|
<Taiga bind:service />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
118
apps/ui/src/routes/services/[id]/_Services/_Taiga.svelte
Normal file
118
apps/ui/src/routes/services/[id]/_Services/_Taiga.svelte
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
export let service: any;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">Taiga</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="secretKey">Secret Key</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="secretKey"
|
||||||
|
id="secretKey"
|
||||||
|
isPasswordField
|
||||||
|
value={service.taiga.secretKey}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">Django</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="djangoAdminUser">Admin User</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="djangoAdminUser"
|
||||||
|
id="djangoAdminUser"
|
||||||
|
value={service.taiga.djangoAdminUser}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="djangoAdminPassword">Admin Password</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="djangoAdminPassword"
|
||||||
|
id="djangoAdminPassword"
|
||||||
|
isPasswordField
|
||||||
|
value={service.taiga.djangoAdminPassword}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">RabbitMQ</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="rabbitMQUser">User</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="rabbitMQUser"
|
||||||
|
id="rabbitMQUser"
|
||||||
|
value={service.taiga.rabbitMQUser}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="rabbitMQPassword">Password</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="rabbitMQPassword"
|
||||||
|
id="rabbitMQPassword"
|
||||||
|
isPasswordField
|
||||||
|
value={service.taiga.rabbitMQPassword}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">PostgreSQL</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlHost">PostgreSQL Host</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlHost"
|
||||||
|
id="postgresqlHost"
|
||||||
|
value={service.taiga.postgresqlHost}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlPort">PostgreSQL Port</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlPort"
|
||||||
|
id="postgresqlPort"
|
||||||
|
value={service.taiga.postgresqlPort}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlUser">PostgreSQL User</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlUser"
|
||||||
|
id="postgresqlUser"
|
||||||
|
value={service.taiga.postgresqlUser}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlPassword">PostgreSQL Password</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlPassword"
|
||||||
|
id="postgresqlPassword"
|
||||||
|
isPasswordField
|
||||||
|
value={service.taiga.postgresqlPassword}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
|
||||||
export let service: any;
|
export let service: any;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -13,7 +12,11 @@
|
|||||||
<input name="adminUser" id="adminUser" placeholder="admin" value="admin" disabled readonly />
|
<input name="adminUser" id="adminUser" placeholder="admin" value="admin" disabled readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="umamiAdminPassword">Initial Admin Password</label>
|
<label for="umamiAdminPassword"
|
||||||
|
>Initial Admin Password <Explainer
|
||||||
|
explanation="It could be changed in Umami. <br>This is just the password set initially after the first start."
|
||||||
|
/></label
|
||||||
|
>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
isPasswordField
|
isPasswordField
|
||||||
name="umamiAdminPassword"
|
name="umamiAdminPassword"
|
||||||
@@ -23,7 +26,4 @@
|
|||||||
disabled
|
disabled
|
||||||
readonly
|
readonly
|
||||||
/>
|
/>
|
||||||
<Explainer
|
|
||||||
text="It could be changed in Umami. <br>This is just the password set initially after the first start."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
66
apps/ui/src/routes/services/[id]/_Services/_Weblate.svelte
Normal file
66
apps/ui/src/routes/services/[id]/_Services/_Weblate.svelte
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
export let service: any;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">Weblate</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="adminPassword">Admin password</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="adminPassword"
|
||||||
|
id="adminPassword"
|
||||||
|
isPasswordField
|
||||||
|
value={service.weblate.adminPassword}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">PostgreSQL</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlHost">PostgreSQL Host</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlHost"
|
||||||
|
id="postgresqlHost"
|
||||||
|
value={service.weblate.postgresqlHost}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlPort">PostgreSQL Port</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlPort"
|
||||||
|
id="postgresqlPort"
|
||||||
|
value={service.weblate.postgresqlPort}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlUser">PostgreSQL User</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlUser"
|
||||||
|
id="postgresqlUser"
|
||||||
|
value={service.weblate.postgresqlUser}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlPassword">PostgreSQL Password</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlPassword"
|
||||||
|
id="postgresqlPassword"
|
||||||
|
isPasswordField
|
||||||
|
value={service.weblate.postgresqlPassword}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@@ -93,6 +93,7 @@ define('SUBDOMAIN_INSTALL', false);`
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="ftpEnabled"
|
||||||
bind:setting={service.wordpress.ftpEnabled}
|
bind:setting={service.wordpress.ftpEnabled}
|
||||||
loading={ftpLoading}
|
loading={ftpLoading}
|
||||||
disabled={!$status.service.isRunning}
|
disabled={!$status.service.isRunning}
|
||||||
@@ -127,6 +128,7 @@ define('SUBDOMAIN_INSTALL', false);`
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="ownMysql"
|
||||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||||
bind:setting={service.wordpress.ownMysql}
|
bind:setting={service.wordpress.ownMysql}
|
||||||
disabled={$status.service.isRunning}
|
disabled={$status.service.isRunning}
|
||||||
|
|||||||
@@ -56,13 +56,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||||
import Loading from '$lib/components/Loading.svelte';
|
|
||||||
import { del, get, post } from '$lib/api';
|
import { del, get, post } from '$lib/api';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
||||||
import { appSession, disabledButton, status, location, setLocation } from '$lib/store';
|
import { appSession, disabledButton, status, location, setLocation } from '$lib/store';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
export let service: any;
|
export let service: any;
|
||||||
@@ -74,13 +74,12 @@
|
|||||||
!service.version ||
|
!service.version ||
|
||||||
!service.type;
|
!service.type;
|
||||||
|
|
||||||
let loading = false;
|
|
||||||
let statusInterval: any;
|
let statusInterval: any;
|
||||||
|
|
||||||
async function deleteService() {
|
async function deleteService() {
|
||||||
const sure = confirm($t('application.confirm_to_delete', { name: service.name }));
|
const sure = confirm($t('application.confirm_to_delete', { name: service.name }));
|
||||||
if (sure) {
|
if (sure) {
|
||||||
loading = true;
|
$status.service.initialLoading = true;
|
||||||
try {
|
try {
|
||||||
if (service.type && $status.service.isRunning)
|
if (service.type && $status.service.isRunning)
|
||||||
await post(`/services/${service.id}/${service.type}/stop`, {});
|
await post(`/services/${service.id}/${service.type}/stop`, {});
|
||||||
@@ -89,31 +88,34 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
$status.service.initialLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function stopService() {
|
async function stopService() {
|
||||||
const sure = confirm($t('database.confirm_stop', { name: service.name }));
|
const sure = confirm($t('database.confirm_stop', { name: service.name }));
|
||||||
if (sure) {
|
if (sure) {
|
||||||
loading = true;
|
$status.service.initialLoading = true;
|
||||||
try {
|
try {
|
||||||
await post(`/services/${service.id}/${service.type}/stop`, {});
|
await post(`/services/${service.id}/${service.type}/stop`, {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
$status.service.initialLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function startService() {
|
async function startService() {
|
||||||
loading = true;
|
$status.service.initialLoading = true;
|
||||||
|
$status.service.loading = true;
|
||||||
try {
|
try {
|
||||||
await post(`/services/${service.id}/${service.type}/start`, {});
|
await post(`/services/${service.id}/${service.type}/start`, {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
$status.service.initialLoading = false;
|
||||||
|
$status.service.loading = false;
|
||||||
|
await getStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getStatus() {
|
async function getStatus() {
|
||||||
@@ -127,6 +129,9 @@
|
|||||||
}
|
}
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
$status.service.initialLoading = true;
|
$status.service.initialLoading = true;
|
||||||
|
$status.service.isRunning = false;
|
||||||
|
$status.service.isExited = false;
|
||||||
|
$status.service.loading = false;
|
||||||
$location = null;
|
$location = null;
|
||||||
clearInterval(statusInterval);
|
clearInterval(statusInterval);
|
||||||
});
|
});
|
||||||
@@ -146,269 +151,260 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="nav-side">
|
<nav class="nav-side">
|
||||||
{#if loading}
|
{#if service.type && service.destinationDockerId && service.version && service.fqdn}
|
||||||
<Loading fullscreen cover />
|
{#if $location}
|
||||||
{:else}
|
<a
|
||||||
{#if service.type && service.destinationDockerId && service.version}
|
id="open"
|
||||||
{#if $location}
|
href={$location}
|
||||||
<a
|
target="_blank"
|
||||||
href={$location}
|
class="icons flex items-center bg-transparent text-sm"
|
||||||
target="_blank"
|
><svg
|
||||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
><svg
|
class="h-6 w-6"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
viewBox="0 0 24 24"
|
||||||
class="h-6 w-6"
|
stroke-width="1.5"
|
||||||
viewBox="0 0 24 24"
|
stroke="currentColor"
|
||||||
stroke-width="1.5"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke-linecap="round"
|
||||||
fill="none"
|
stroke-linejoin="round"
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
|
||||||
<line x1="10" y1="14" x2="20" y2="4" />
|
|
||||||
<polyline points="15 4 20 4 20 9" />
|
|
||||||
</svg></a
|
|
||||||
>
|
>
|
||||||
<div class="border border-stone-700 h-8" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
{/if}
|
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||||
{#if $status.service.isExited}
|
<line x1="10" y1="14" x2="20" y2="4" />
|
||||||
<a
|
<polyline points="15 4 20 4 20 9" />
|
||||||
href={!$disabledButton ? `/services/${id}/logs` : null}
|
</svg></a
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center text-red-500 tooltip-error"
|
>
|
||||||
data-tip="Service exited with an error!"
|
<Tooltip triggeredBy="#open">Open</Tooltip>
|
||||||
sveltekit:prefetch
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentcolor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path
|
|
||||||
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
|
|
||||||
/>
|
|
||||||
<line x1="12" y1="8" x2="12" y2="12" />
|
|
||||||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
{#if $status.service.initialLoading}
|
|
||||||
<button
|
|
||||||
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
|
||||||
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
|
||||||
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
|
||||||
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
|
||||||
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
|
||||||
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{:else if $status.service.isRunning}
|
|
||||||
<button
|
|
||||||
on:click={stopService}
|
|
||||||
type="submit"
|
|
||||||
disabled={$disabledButton}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
|
|
||||||
data-tip={$appSession.isAdmin
|
|
||||||
? $t('service.stop_service')
|
|
||||||
: $t('service.permission_denied_stop_service')}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<rect x="6" y="5" width="4" height="14" rx="1" />
|
|
||||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
on:click={startService}
|
|
||||||
type="submit"
|
|
||||||
disabled={$disabledButton}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
|
|
||||||
data-tip={$appSession.isAdmin
|
|
||||||
? $t('service.start_service')
|
|
||||||
: $t('service.permission_denied_start_service')}
|
|
||||||
><svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M7 4v16l13 -8z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
<div class="border border-stone-700 h-8" />
|
<div class="border border-stone-700 h-8" />
|
||||||
{/if}
|
{/if}
|
||||||
{#if service.type && service.destinationDockerId && service.version}
|
{#if $status.service.isExited}
|
||||||
<a
|
<a
|
||||||
href="/services/{id}"
|
id="error"
|
||||||
|
href={!$disabledButton ? `/services/${id}/logs` : null}
|
||||||
|
class="icons bg-transparent text-sm flex items-center text-red-500 tooltip-error"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
class="hover:text-yellow-500 rounded"
|
|
||||||
class:text-yellow-500={$page.url.pathname === `/services/${id}`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/services/${id}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<svg
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm disabled:text-red-500"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
data-tip={$t('application.configurations')}
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentcolor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<svg
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path
|
||||||
class="h-6 w-6"
|
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
|
||||||
viewBox="0 0 24 24"
|
/>
|
||||||
stroke-width="1.5"
|
<line x1="12" y1="8" x2="12" y2="12" />
|
||||||
stroke="currentColor"
|
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||||
fill="none"
|
</svg>
|
||||||
stroke-linecap="round"
|
</a>
|
||||||
stroke-linejoin="round"
|
<Tooltip triggeredBy="#error">Service exited with an error!</Tooltip>
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<rect x="4" y="8" width="4" height="4" />
|
|
||||||
<line x1="6" y1="4" x2="6" y2="8" />
|
|
||||||
<line x1="6" y1="12" x2="6" y2="20" />
|
|
||||||
<rect x="10" y="14" width="4" height="4" />
|
|
||||||
<line x1="12" y1="4" x2="12" y2="14" />
|
|
||||||
<line x1="12" y1="18" x2="12" y2="20" />
|
|
||||||
<rect x="16" y="5" width="4" height="4" />
|
|
||||||
<line x1="18" y1="4" x2="18" y2="5" />
|
|
||||||
<line x1="18" y1="9" x2="18" y2="20" />
|
|
||||||
</svg></button
|
|
||||||
></a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/services/{id}/secrets"
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-pink-500 rounded"
|
|
||||||
class:text-pink-500={$page.url.pathname === `/services/${id}/secrets`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/secrets`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm disabled:text-red-500"
|
|
||||||
data-tip={$t('application.secret')}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path
|
|
||||||
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
|
|
||||||
/>
|
|
||||||
<circle cx="12" cy="11" r="1" />
|
|
||||||
<line x1="12" y1="12" x2="12" y2="14.5" />
|
|
||||||
</svg></button
|
|
||||||
></a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/services/{id}/storages"
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-pink-500 rounded"
|
|
||||||
class:text-pink-500={$page.url.pathname === `/services/${id}/storages`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/storages`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm disabled:text-red-500"
|
|
||||||
data-tip="Persistent Storage"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
|
||||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
|
|
||||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
|
||||||
</svg>
|
|
||||||
</button></a
|
|
||||||
>
|
|
||||||
<div class="border border-stone-700 h-8" />
|
|
||||||
<a
|
|
||||||
href={!$disabledButton && $status.service.isRunning ? `/services/${id}/logs` : null}
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-pink-500 rounded"
|
|
||||||
class:text-pink-500={$page.url.pathname === `/services/${id}/logs`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/logs`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
disabled={!$status.service.isRunning}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
|
||||||
data-tip={$t('service.logs')}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
|
||||||
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
|
||||||
<line x1="3" y1="6" x2="3" y2="19" />
|
|
||||||
<line x1="12" y1="6" x2="12" y2="19" />
|
|
||||||
<line x1="21" y1="6" x2="21" y2="19" />
|
|
||||||
</svg></button
|
|
||||||
></a
|
|
||||||
>
|
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
{#if $status.service.initialLoading}
|
||||||
on:click={deleteService}
|
<button
|
||||||
type="submit"
|
class="icons flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
||||||
disabled={!$appSession.isAdmin}
|
>
|
||||||
class:hover:text-red-500={$appSession.isAdmin}
|
<svg
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
data-tip={$appSession.isAdmin
|
class="h-6 w-6"
|
||||||
? $t('service.delete_service')
|
viewBox="0 0 24 24"
|
||||||
: $t('service.permission_denied_delete_service')}><DeleteIcon /></button
|
stroke-width="1.5"
|
||||||
>
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
||||||
|
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
||||||
|
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
||||||
|
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
||||||
|
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
||||||
|
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{:else if $status.service.isRunning}
|
||||||
|
<button
|
||||||
|
id="stop"
|
||||||
|
on:click={stopService}
|
||||||
|
type="submit"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent text-sm flex items-center space-x-2 text-red-500"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<rect x="6" y="5" width="4" height="14" rx="1" />
|
||||||
|
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<Tooltip triggeredBy="#stop">Stop</Tooltip>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
id="start"
|
||||||
|
on:click={startService}
|
||||||
|
type="submit"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent text-sm flex items-center space-x-2 text-green-500"
|
||||||
|
><svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M7 4v16l13 -8z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<Tooltip triggeredBy="#start">Start</Tooltip>
|
||||||
|
{/if}
|
||||||
|
<div class="border border-stone-700 h-8" />
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if service.type && service.destinationDockerId && service.version}
|
||||||
|
<a
|
||||||
|
id="configuration"
|
||||||
|
href="/services/{id}"
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-yellow-500 rounded"
|
||||||
|
class:text-yellow-500={$page.url.pathname === `/services/${id}`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/services/${id}`}
|
||||||
|
>
|
||||||
|
<button class="icons bg-transparent text-sm disabled:text-red-500">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<rect x="4" y="8" width="4" height="4" />
|
||||||
|
<line x1="6" y1="4" x2="6" y2="8" />
|
||||||
|
<line x1="6" y1="12" x2="6" y2="20" />
|
||||||
|
<rect x="10" y="14" width="4" height="4" />
|
||||||
|
<line x1="12" y1="4" x2="12" y2="14" />
|
||||||
|
<line x1="12" y1="18" x2="12" y2="20" />
|
||||||
|
<rect x="16" y="5" width="4" height="4" />
|
||||||
|
<line x1="18" y1="4" x2="18" y2="5" />
|
||||||
|
<line x1="18" y1="9" x2="18" y2="20" />
|
||||||
|
</svg></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
<Tooltip triggeredBy="#configuration">Configuration</Tooltip>
|
||||||
|
<a
|
||||||
|
id="secrets"
|
||||||
|
href="/services/{id}/secrets"
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-pink-500 rounded"
|
||||||
|
class:text-pink-500={$page.url.pathname === `/services/${id}/secrets`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/secrets`}
|
||||||
|
>
|
||||||
|
<button class="icons bg-transparent text-sm disabled:text-red-500">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
|
||||||
|
/>
|
||||||
|
<circle cx="12" cy="11" r="1" />
|
||||||
|
<line x1="12" y1="12" x2="12" y2="14.5" />
|
||||||
|
</svg></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
<Tooltip triggeredBy="#secrets">Secrets</Tooltip>
|
||||||
|
<a
|
||||||
|
id="persistentstorage"
|
||||||
|
href="/services/{id}/storages"
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-pink-500 rounded"
|
||||||
|
class:text-pink-500={$page.url.pathname === `/services/${id}/storages`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/storages`}
|
||||||
|
>
|
||||||
|
<button class="icons bg-transparent text-sm disabled:text-red-500">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
||||||
|
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
|
||||||
|
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
||||||
|
</svg>
|
||||||
|
</button></a
|
||||||
|
>
|
||||||
|
<Tooltip triggeredBy="#persistentstorage">Persistent Storage</Tooltip>
|
||||||
|
<div class="border border-stone-700 h-8" />
|
||||||
|
<a
|
||||||
|
id="logs"
|
||||||
|
href={!$disabledButton && $status.service.isRunning ? `/services/${id}/logs` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-pink-500 rounded"
|
||||||
|
class:text-pink-500={$page.url.pathname === `/services/${id}/logs`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/logs`}
|
||||||
|
>
|
||||||
|
<button disabled={!$status.service.isRunning} class="icons bg-transparent text-sm">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
||||||
|
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
||||||
|
<line x1="3" y1="6" x2="3" y2="19" />
|
||||||
|
<line x1="12" y1="6" x2="12" y2="19" />
|
||||||
|
<line x1="21" y1="6" x2="21" y2="19" />
|
||||||
|
</svg></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
<Tooltip triggeredBy="#logs">Logs</Tooltip>
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
id="delete"
|
||||||
|
on:click={deleteService}
|
||||||
|
type="submit"
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
|
class:hover:text-red-500={$appSession.isAdmin}
|
||||||
|
class="icons bg-transparent text-sm"><DeleteIcon /></button
|
||||||
|
>
|
||||||
|
<Tooltip triggeredBy="#delete">Delete</Tooltip>
|
||||||
</nav>
|
</nav>
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
|
||||||
let service: any = {};
|
let service: any = {};
|
||||||
let logsLoading = false;
|
let logsLoading = false;
|
||||||
@@ -126,9 +127,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="flex justify-end sticky top-0 p-1 mx-1">
|
<div class="flex justify-end sticky top-0 p-1 mx-1">
|
||||||
<button
|
<button
|
||||||
|
id="follow"
|
||||||
on:click={followBuild}
|
on:click={followBuild}
|
||||||
class="bg-transparent btn btn-sm tooltip tooltip-primary tooltip-bottom"
|
class="bg-transparent btn btn-sm"
|
||||||
data-tip="Follow logs"
|
|
||||||
class:text-green-500={followingLogs}
|
class:text-green-500={followingLogs}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -148,6 +149,7 @@
|
|||||||
<line x1="16" y1="12" x2="12" y2="16" />
|
<line x1="16" y1="12" x2="12" y2="16" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
<Tooltip triggeredBy="#follow">Follow Logs</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="font-mono w-full leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
class="font-mono w-full leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
||||||
|
|||||||
@@ -25,14 +25,47 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
import pLimit from 'p-limit';
|
||||||
import ServiceLinks from './_ServiceLinks.svelte';
|
import ServiceLinks from './_ServiceLinks.svelte';
|
||||||
|
import { addToast } from '$lib/store';
|
||||||
|
import { saveSecret } from './utils';
|
||||||
|
const limit = pLimit(1);
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
let batchSecrets = '';
|
||||||
|
|
||||||
async function refreshSecrets() {
|
async function refreshSecrets() {
|
||||||
const data = await get(`/services/${id}/secrets`);
|
const data = await get(`/services/${id}/secrets`);
|
||||||
secrets = [...data.secrets];
|
secrets = [...data.secrets];
|
||||||
}
|
}
|
||||||
|
async function getValues(e: any) {
|
||||||
|
e.preventDefault();
|
||||||
|
const eachValuePair = batchSecrets.split('\n');
|
||||||
|
const batchSecretsPairs = eachValuePair
|
||||||
|
.filter((secret) => !secret.startsWith('#') && secret)
|
||||||
|
.map((secret) => {
|
||||||
|
const [name, ...rest] = secret.split('=');
|
||||||
|
const value = rest.join('=');
|
||||||
|
const cleanValue = value?.replaceAll('"', '') || '';
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
value: cleanValue,
|
||||||
|
isNew: !secrets.find((secret: any) => name === secret.name)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
batchSecretsPairs.map(({ name, value, isNew }) =>
|
||||||
|
limit(() => saveSecret({ name, value, serviceId: id, isNew }))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
batchSecrets = '';
|
||||||
|
await refreshSecrets();
|
||||||
|
addToast({
|
||||||
|
message: 'Secrets saved.',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -93,4 +126,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<h2 class="title my-6 font-bold">Paste .env file</h2>
|
||||||
|
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
|
||||||
|
<textarea bind:value={batchSecrets} class="mb-2 min-h-[200px] w-full" />
|
||||||
|
<button class="btn btn-sm bg-applications" type="submit">Batch add secrets</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import Storage from './_Storage.svelte';
|
import Storage from './_Storage.svelte';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import SimpleExplainer from '$lib/components/SimpleExplainer.svelte';
|
||||||
import ServiceLinks from './_ServiceLinks.svelte';
|
import ServiceLinks from './_ServiceLinks.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
|
|
||||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||||
<div class="flex justify-center py-4 text-center">
|
<div class="flex justify-center py-4 text-center">
|
||||||
<Explainer
|
<SimpleExplainer
|
||||||
customClass="w-full"
|
customClass="w-full"
|
||||||
text={'You can specify any folder that you want to be persistent across restarts. <br>This is useful for storing data for VSCode server or WordPress.'}
|
text={'You can specify any folder that you want to be persistent across restarts. <br>This is useful for storing data for VSCode server or WordPress.'}
|
||||||
/>
|
/>
|
||||||
|
|||||||
42
apps/ui/src/routes/services/[id]/utils.ts
Normal file
42
apps/ui/src/routes/services/[id]/utils.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { post } from '$lib/api';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
import { errorNotification } from '$lib/common';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isNew: boolean;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
isBuildSecret?: boolean;
|
||||||
|
isPRMRSecret?: boolean;
|
||||||
|
isNewSecret?: boolean;
|
||||||
|
serviceId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function saveSecret({
|
||||||
|
isNew,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
isBuildSecret,
|
||||||
|
isPRMRSecret,
|
||||||
|
isNewSecret,
|
||||||
|
serviceId
|
||||||
|
}: Props): Promise<void> {
|
||||||
|
if (!name) return errorNotification(`${t.get('forms.name')} ${t.get('forms.is_required')}`);
|
||||||
|
if (!value) return errorNotification(`${t.get('forms.value')} ${t.get('forms.is_required')}`);
|
||||||
|
try {
|
||||||
|
await post(`/services/${serviceId}/secrets`, {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
isBuildSecret,
|
||||||
|
isPRMRSecret,
|
||||||
|
isNew: isNew || false
|
||||||
|
});
|
||||||
|
if (isNewSecret) {
|
||||||
|
name = '';
|
||||||
|
value = '';
|
||||||
|
isBuildSecret = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,13 +19,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let settings: any;
|
export let settings: any;
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { del, get, post } from '$lib/api';
|
import { del, get, post } from '$lib/api';
|
||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { addToast, appSession, features } from '$lib/store';
|
import { addToast, appSession, features } from '$lib/store';
|
||||||
import { errorNotification, getDomain } from '$lib/common';
|
import { errorNotification, getDomain } from '$lib/common';
|
||||||
import Menu from './_Menu.svelte';
|
import Menu from './_Menu.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
|
||||||
let isRegistrationEnabled = settings.isRegistrationEnabled;
|
let isRegistrationEnabled = settings.isRegistrationEnabled;
|
||||||
let dualCerts = settings.dualCerts;
|
let dualCerts = settings.dualCerts;
|
||||||
@@ -195,8 +195,8 @@
|
|||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="pt-2 text-base font-bold text-stone-100">
|
<div class="pt-2 text-base font-bold text-stone-100">
|
||||||
{$t('application.url_fqdn')}
|
{$t('application.url_fqdn')}
|
||||||
|
<Explainer explanation={$t('setting.ssl_explainer')} />
|
||||||
</div>
|
</div>
|
||||||
<Explainer text={$t('setting.ssl_explainer')} />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="justify-start text-left">
|
<div class="justify-start text-left">
|
||||||
<input
|
<input
|
||||||
@@ -250,8 +250,8 @@
|
|||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="pt-2 text-base font-bold text-stone-100">
|
<div class="pt-2 text-base font-bold text-stone-100">
|
||||||
{$t('forms.public_port_range')}
|
{$t('forms.public_port_range')}
|
||||||
|
<Explainer explanation={$t('forms.public_port_range_explainer')} />
|
||||||
</div>
|
</div>
|
||||||
<Explainer text={$t('forms.public_port_range_explainer')} />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto flex-row items-center justify-center space-y-2">
|
<div class="mx-auto flex-row items-center justify-center space-y-2">
|
||||||
<input
|
<input
|
||||||
@@ -273,6 +273,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="isDNSCheckEnabled"
|
||||||
bind:setting={isDNSCheckEnabled}
|
bind:setting={isDNSCheckEnabled}
|
||||||
title={$t('setting.is_dns_check_enabled')}
|
title={$t('setting.is_dns_check_enabled')}
|
||||||
description={$t('setting.is_dns_check_enabled_explainer')}
|
description={$t('setting.is_dns_check_enabled_explainer')}
|
||||||
@@ -280,18 +281,19 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="flex-col">
|
<div class="text-base font-bold text-stone-100">
|
||||||
<div class="pt-2 text-base font-bold text-stone-100">
|
Custom DNS servers <Explainer
|
||||||
Custom DNS servers
|
explanation="You can specify a custom DNS server to verify your domains all over Coolify.<br><br>By default, the OS defined DNS servers are used."
|
||||||
</div>
|
/>
|
||||||
<Explainer text="You can specify a custom DNS server to verify your domains all over Coolify.<br><br>By default, the OS defined DNS servers are used." />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto flex-row items-center justify-center space-y-2">
|
|
||||||
|
<div class="flex-row items-center justify-center">
|
||||||
<input placeholder="1.1.1.1,8.8.8.8" bind:value={DNSServers} />
|
<input placeholder="1.1.1.1,8.8.8.8" bind:value={DNSServers} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="dualCerts"
|
||||||
dataTooltip={$t('setting.must_remove_domain_before_changing')}
|
dataTooltip={$t('setting.must_remove_domain_before_changing')}
|
||||||
disabled={isFqdnSet}
|
disabled={isFqdnSet}
|
||||||
bind:setting={dualCerts}
|
bind:setting={dualCerts}
|
||||||
@@ -302,6 +304,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="isRegistrationEnabled"
|
||||||
bind:setting={isRegistrationEnabled}
|
bind:setting={isRegistrationEnabled}
|
||||||
title={$t('setting.registration_allowed')}
|
title={$t('setting.registration_allowed')}
|
||||||
description={$t('setting.registration_allowed_explainer')}
|
description={$t('setting.registration_allowed_explainer')}
|
||||||
@@ -311,6 +314,7 @@
|
|||||||
{#if browser && $features.beta}
|
{#if browser && $features.beta}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
id="isAutoUpdateEnabled"
|
||||||
bind:setting={isAutoUpdateEnabled}
|
bind:setting={isAutoUpdateEnabled}
|
||||||
title={$t('setting.auto_update_enabled')}
|
title={$t('setting.auto_update_enabled')}
|
||||||
description={$t('setting.auto_update_enabled_explainer')}
|
description={$t('setting.auto_update_enabled_explainer')}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
export let settings: any;
|
export let settings: any;
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { getAPIUrl, getWebhookUrl, post } from '$lib/api';
|
import { getAPIUrl, getWebhookUrl, post } from '$lib/api';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { dashify, errorNotification, getDomain } from '$lib/common';
|
import { dashify, errorNotification, getDomain } from '$lib/common';
|
||||||
import { addToast, appSession } from '$lib/store';
|
import { addToast, appSession } from '$lib/store';
|
||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
apiUrl: source.apiUrl.replace(/\/$/, '')
|
apiUrl: source.apiUrl.replace(/\/$/, '')
|
||||||
});
|
});
|
||||||
return addToast({
|
return addToast({
|
||||||
message:'Configuration saved.',
|
message: 'Configuration saved.',
|
||||||
type: 'success'
|
type: 'success'
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
<div class="mx-auto max-w-4xl px-6">
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
{#if !source.githubAppId}
|
{#if !source.githubAppId}
|
||||||
<form on:submit|preventDefault={newGithubApp} class="py-4">
|
<form on:submit|preventDefault={newGithubApp} class="py-4">
|
||||||
<div class="flex space-x-1 pb-7">
|
<div class="grid gap-1 lg:grid-flow-col pb-7">
|
||||||
<div class="title">General</div>
|
<div class="title">General</div>
|
||||||
{#if !source.githubAppId}
|
{#if !source.githubAppId}
|
||||||
<button class="btn btn-sm bg-sources" type="submit">Save & Redirect to GitHub</button>
|
<button class="btn btn-sm bg-sources" type="submit">Save & Redirect to GitHub</button>
|
||||||
@@ -101,21 +101,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
<div class="grid grid-flow-row gap-2">
|
<div class="grid grid-flow-row gap-2">
|
||||||
<div class="mt-2 grid grid-cols-2 items-center">
|
<div class="mt-2 grid lg:grid-cols-2 items-center">
|
||||||
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||||
<input name="name" id="name" required bind:value={source.name} />
|
<input name="name" id="name" required bind:value={source.name} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid lg:grid-cols-2 items-center">
|
||||||
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
|
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
|
||||||
<input name="htmlUrl" id="htmlUrl" required bind:value={source.htmlUrl} />
|
<input name="htmlUrl" id="htmlUrl" required bind:value={source.htmlUrl} />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid lg:grid-cols-2 items-center">
|
||||||
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
||||||
<input name="apiUrl" id="apiUrl" required bind:value={source.apiUrl} />
|
<input name="apiUrl" id="apiUrl" required bind:value={source.apiUrl} />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid lg:grid-cols-2 items-center">
|
||||||
<label for="customPort" class="text-base font-bold text-stone-100">Custom SSH Port</label>
|
<label for="customPort" class="text-base font-bold text-stone-100"
|
||||||
|
>Custom SSH Port <Explainer
|
||||||
|
explanation={'If you use a self-hosted version of Git, you can provide custom port for all the Git related actions.'}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
name="customPort"
|
name="customPort"
|
||||||
id="customPort"
|
id="customPort"
|
||||||
@@ -124,18 +128,15 @@
|
|||||||
required
|
required
|
||||||
value={source.customPort}
|
value={source.customPort}
|
||||||
/>
|
/>
|
||||||
<Explainer
|
|
||||||
text="If you use a self-hosted version of Git, you can provide custom port for all the Git related actions."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2">
|
<div class="grid lg:grid-cols-2">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
|
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
|
||||||
>Organization</label
|
>Organization
|
||||||
|
<Explainer
|
||||||
|
explanation={"Fill it if you would like to use an organization's as your Git Source. Otherwise your user will be used."}
|
||||||
|
/></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>
|
</div>
|
||||||
<input
|
<input
|
||||||
name="organization"
|
name="organization"
|
||||||
@@ -148,29 +149,30 @@
|
|||||||
</form>
|
</form>
|
||||||
{:else if source.githubApp?.installationId}
|
{:else if source.githubApp?.installationId}
|
||||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||||
<div class="flex space-x-1 pb-5 ">
|
<div class="flex md:flex-row space-y-2 md:space-y-0 space-x-0 md:space-x-2 flex-col pb-5">
|
||||||
<div class="title">{$t('general')}</div>
|
<div class="title">{$t('general')}</div>
|
||||||
|
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<button
|
<button class="btn btn-sm bg-sources" type="submit" disabled={loading}
|
||||||
class="btn btn-sm bg-sources"
|
>{loading ? 'Saving...' : 'Save'}</button
|
||||||
type="submit"
|
|
||||||
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
|
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="btn btn-sm"
|
class="btn btn-sm"
|
||||||
href={`${source.htmlUrl}/${source.htmlUrl === 'https://github.com' ? 'apps' : 'github-apps'}/${source.githubApp.name}/installations/new`}
|
href={`${source.htmlUrl}/${
|
||||||
|
source.htmlUrl === 'https://github.com' ? 'apps' : 'github-apps'
|
||||||
|
}/${source.githubApp.name}/installations/new`}
|
||||||
>{$t('source.change_app_settings', { name: 'GitHub' })}</a
|
>{$t('source.change_app_settings', { name: 'GitHub' })}</a
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
<div class="grid grid-flow-row gap-2">
|
<div class="grid grid-flow-row gap-2">
|
||||||
<div class="mt-2 grid grid-cols-2 items-center">
|
<div class="mt-2 grid lg:grid-cols-2 items-center">
|
||||||
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
||||||
<input name="name" id="name" required bind:value={source.name} />
|
<input name="name" id="name" required bind:value={source.name} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid lg:grid-cols-2 items-center">
|
||||||
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
|
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
|
||||||
<input
|
<input
|
||||||
name="htmlUrl"
|
name="htmlUrl"
|
||||||
@@ -181,7 +183,7 @@
|
|||||||
bind:value={source.htmlUrl}
|
bind:value={source.htmlUrl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid lg:grid-cols-2 items-center">
|
||||||
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
||||||
<input
|
<input
|
||||||
name="apiUrl"
|
name="apiUrl"
|
||||||
@@ -193,9 +195,11 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if selfHosted}
|
{#if selfHosted}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid lg:grid-cols-2 items-center">
|
||||||
<label for="customPort" class="text-base font-bold text-stone-100"
|
<label for="customPort" class="text-base font-bold text-stone-100"
|
||||||
>Custom SSH Port</label
|
>Custom SSH Port <Explainer
|
||||||
|
explanation="If you use a self-hosted version of Git, you can provide custom port for all the Git related actions."
|
||||||
|
/></label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
name="customPort"
|
name="customPort"
|
||||||
@@ -205,12 +209,9 @@
|
|||||||
required
|
required
|
||||||
value={source.customPort}
|
value={source.customPort}
|
||||||
/>
|
/>
|
||||||
<Explainer
|
|
||||||
text="If you use a self-hosted version of Git, you can provide custom port for all the Git related actions."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-2">
|
<div class="grid lg:grid-cols-2">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
|
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
|
||||||
>Organization</label
|
>Organization</label
|
||||||
@@ -229,10 +230,12 @@
|
|||||||
</form>
|
</form>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a href={`${source.htmlUrl}/${source.htmlUrl === 'https://github.com' ? 'apps' : 'github-apps'}/${source.githubApp.name}/installations/new`}>
|
<a
|
||||||
<button class="box-selection bg-sources text-xl font-bold"
|
href={`${source.htmlUrl}/${
|
||||||
>Install Repositories</button
|
source.htmlUrl === 'https://github.com' ? 'apps' : 'github-apps'
|
||||||
></a
|
}/${source.githubApp.name}/installations/new`}
|
||||||
|
>
|
||||||
|
<button class="box-selection bg-sources text-xl font-bold">Install Repositories</button></a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let source: any;
|
export let source: any;
|
||||||
export let settings: any;
|
export let settings: any;
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { getAPIUrl, post } from '$lib/api';
|
import { getAPIUrl, post } from '$lib/api';
|
||||||
@@ -11,6 +10,7 @@
|
|||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { addToast, appSession } from '$lib/store';
|
import { addToast, appSession } from '$lib/store';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
let url = settings.fqdn ? settings.fqdn : window.location.origin;
|
let url = settings.fqdn ? settings.fqdn : window.location.origin;
|
||||||
@@ -148,10 +148,8 @@
|
|||||||
<div class="flex space-x-1 pb-7">
|
<div class="flex space-x-1 pb-7">
|
||||||
<div class="title">General</div>
|
<div class="title">General</div>
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<button
|
<button type="submit" class="btn btn-sm bg-sources" disabled={loading}
|
||||||
type="submit"
|
>{loading ? $t('forms.saving') : $t('forms.save')}</button
|
||||||
class="btn btn-sm bg-sources"
|
|
||||||
disabled={loading}>{loading ? $t('forms.saving') : $t('forms.save')}</button
|
|
||||||
>
|
>
|
||||||
{#if source.gitlabAppId}
|
{#if source.gitlabAppId}
|
||||||
<button class="btn btn-sm" on:click|preventDefault={changeSettings}
|
<button class="btn btn-sm" on:click|preventDefault={changeSettings}
|
||||||
@@ -166,16 +164,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
{#if !source.gitlabAppId}
|
{#if !source.gitlabAppId}
|
||||||
<Explainer
|
<a
|
||||||
customClass="w-full"
|
href="https://docs.coollabs.io/coolify/sources#how-to-integrate-with-gitlab"
|
||||||
text="<span class='font-bold text-base text-white'>Scopes required:</span>
|
class="font-bold "
|
||||||
<br>- <span class='text-sources font-bold'>api</span> (Access the authenticated user's API)
|
target="_blank"
|
||||||
<br>- <span class='text-sources font-bold'>read_repository</span> (Allows read-only access to the repository)
|
rel="noopener noreferrer">Documentation and detailed instructions.</a
|
||||||
<br>- <span class='text-sources 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 <span class='text-sources font-bold'>Expire Access Tokens</span>
|
|
||||||
<br><br>Webhook URL: <span class='text-sources font-bold'>{url}/webhooks/gitlab</span>"
|
|
||||||
/>
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="type" class="text-base font-bold text-stone-100">Application Type</label>
|
<label for="type" class="text-base font-bold text-stone-100">Application Type</label>
|
||||||
<select name="type" id="type" class="w-96" bind:value={applicationType}>
|
<select name="type" id="type" class="w-96" bind:value={applicationType}>
|
||||||
@@ -245,7 +239,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if selfHosted}
|
{#if selfHosted}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="customPort" class="text-base font-bold text-stone-100">Custom SSH Port</label>
|
<label for="customPort" class="text-base font-bold text-stone-100"
|
||||||
|
>Custom SSH Port <Explainer
|
||||||
|
explanation={'If you use a self-hosted version of Git, you can provide custom port for all the Git related actions.'}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
name="customPort"
|
name="customPort"
|
||||||
id="customPort"
|
id="customPort"
|
||||||
@@ -254,19 +252,16 @@
|
|||||||
required
|
required
|
||||||
bind:value={source.customPort}
|
bind:value={source.customPort}
|
||||||
/>
|
/>
|
||||||
<Explainer
|
|
||||||
text="If you use a self-hosted version of Git, you can provide custom port for all the Git related actions."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-start">
|
<div class="grid grid-cols-2 items-start">
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<label for="oauthId" class="pt-2 text-base font-bold text-stone-100"
|
<label for="oauthId" class="pt-2 text-base font-bold text-stone-100"
|
||||||
>{$t('source.oauth_id')}</label
|
>{$t('source.oauth_id')}
|
||||||
|
{#if !source.gitlabAppId}
|
||||||
|
<Explainer explanation={$t('source.oauth_id_explainer')} />
|
||||||
|
{/if}</label
|
||||||
>
|
>
|
||||||
{#if !source.gitlabAppId}
|
|
||||||
<Explainer text={$t('source.oauth_id_explainer')} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
disabled={source.gitlabAppId}
|
disabled={source.gitlabAppId}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
import { appSession } from '$lib/store';
|
import { appSession } from '$lib/store';
|
||||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
async function deleteSource(name: string) {
|
async function deleteSource(name: string) {
|
||||||
@@ -55,15 +56,14 @@
|
|||||||
{#if id !== 'new'}
|
{#if id !== 'new'}
|
||||||
<nav class="nav-side">
|
<nav class="nav-side">
|
||||||
<button
|
<button
|
||||||
|
id="delete"
|
||||||
on:click={() => deleteSource(source.name)}
|
on:click={() => deleteSource(source.name)}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!$appSession.isAdmin}
|
disabled={!$appSession.isAdmin}
|
||||||
class:hover:text-red-500={$appSession.isAdmin}
|
class:hover:text-red-500={$appSession.isAdmin}
|
||||||
class="icons tooltip tooltip-primary tooltip-bottom bg-transparent text-sm"
|
class="icons bg-transparent text-sm"><DeleteIcon /></button
|
||||||
data-tip={$appSession.isAdmin
|
|
||||||
? $t('source.delete_git_source')
|
|
||||||
: $t('source.permission_denied')}><DeleteIcon /></button
|
|
||||||
>
|
>
|
||||||
</nav>
|
</nav>
|
||||||
|
<Tooltip triggeredBy="#delete">Delete</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ label {
|
|||||||
@apply inline-block w-64 text-xs tracking-tight md:text-sm;
|
@apply inline-block w-64 text-xs tracking-tight md:text-sm;
|
||||||
}
|
}
|
||||||
.btn {
|
.btn {
|
||||||
@apply text-white text-base;
|
@apply text-white text-base min-w-fit;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -195,4 +195,8 @@ a {
|
|||||||
}
|
}
|
||||||
.table *{
|
.table *{
|
||||||
border: none;
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
@apply w-48 lg:w-96;
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
const defaultTheme = require('tailwindcss/defaultTheme');
|
const defaultTheme = require('tailwindcss/defaultTheme');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}'],
|
content: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}', "./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}",],
|
||||||
important: true,
|
important: true,
|
||||||
daisyui: {
|
daisyui: {
|
||||||
themes: [
|
themes: [
|
||||||
{
|
{
|
||||||
coollabs: {
|
coollabs: {
|
||||||
"base-100":"#323232",
|
"base-100": "#323232",
|
||||||
"base-200":"#242424",
|
"base-200": "#242424",
|
||||||
"base-300":"#181818",
|
"base-300": "#181818",
|
||||||
"primary": "#6d28d9",
|
"primary": "#6d28d9",
|
||||||
"primary-content": "#fff",
|
"primary-content": "#fff",
|
||||||
"secondary": "#343232",
|
"secondary": "#343232",
|
||||||
@@ -40,13 +40,13 @@ module.exports = {
|
|||||||
sans: ['Poppins', ...defaultTheme.fontFamily.sans]
|
sans: ['Poppins', ...defaultTheme.fontFamily.sans]
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
"applications":"#16A34A",
|
"applications": "#16A34A",
|
||||||
"databases":"#9333EA",
|
"databases": "#9333EA",
|
||||||
"destinations":"#0284C7",
|
"destinations": "#0284C7",
|
||||||
"sources":"#EA580C",
|
"sources": "#EA580C",
|
||||||
"services":"#DB2777",
|
"services": "#DB2777",
|
||||||
"settings":"#FEE440",
|
"settings": "#FEE440",
|
||||||
"iam":"#C026D3",
|
"iam": "#C026D3",
|
||||||
coollabs: '#6B16ED',
|
coollabs: '#6B16ED',
|
||||||
'coollabs-100': '#7317FF',
|
'coollabs-100': '#7317FF',
|
||||||
coolblack: '#161616',
|
coolblack: '#161616',
|
||||||
@@ -62,5 +62,6 @@ module.exports = {
|
|||||||
scrollbar: ['dark'],
|
scrollbar: ['dark'],
|
||||||
extend: {}
|
extend: {}
|
||||||
},
|
},
|
||||||
|
darkMode: 'class',
|
||||||
plugins: [require('tailwindcss-scrollbar'), require('daisyui'), require("@tailwindcss/typography")]
|
plugins: [require('tailwindcss-scrollbar'), require('daisyui'), require("@tailwindcss/typography")]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "3.8.7",
|
"version": "3.9.0-rc.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": "github:coollabsio/coolify",
|
"repository": "github:coollabsio/coolify",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"oc": "opencollective-setup",
|
"oc": "opencollective-setup",
|
||||||
|
"translate": "pnpm run --filter i18n-converter translate",
|
||||||
"db:studio": "pnpm run --filter api db:studio",
|
"db:studio": "pnpm run --filter api db:studio",
|
||||||
"db:push": "pnpm run --filter api db:push",
|
"db:push": "pnpm run --filter api db:push",
|
||||||
"db:seed": "pnpm run --filter api db:seed",
|
"db:seed": "pnpm run --filter api db:seed",
|
||||||
|
|||||||
150
pnpm-lock.yaml
generated
150
pnpm-lock.yaml
generated
@@ -23,7 +23,7 @@ importers:
|
|||||||
'@fastify/static': 6.5.0
|
'@fastify/static': 6.5.0
|
||||||
'@iarna/toml': 2.2.5
|
'@iarna/toml': 2.2.5
|
||||||
'@ladjs/graceful': 3.0.2
|
'@ladjs/graceful': 3.0.2
|
||||||
'@prisma/client': 4.2.1
|
'@prisma/client': 3.15.2
|
||||||
'@types/node': 18.7.13
|
'@types/node': 18.7.13
|
||||||
'@types/node-os-utils': 1.3.0
|
'@types/node-os-utils': 1.3.0
|
||||||
'@typescript-eslint/eslint-plugin': 5.35.1
|
'@typescript-eslint/eslint-plugin': 5.35.1
|
||||||
@@ -56,7 +56,7 @@ importers:
|
|||||||
p-all: 4.0.0
|
p-all: 4.0.0
|
||||||
p-throttle: 5.0.0
|
p-throttle: 5.0.0
|
||||||
prettier: 2.7.1
|
prettier: 2.7.1
|
||||||
prisma: 4.2.1
|
prisma: 3.15.2
|
||||||
public-ip: 6.0.1
|
public-ip: 6.0.1
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
ssh-config: 4.1.6
|
ssh-config: 4.1.6
|
||||||
@@ -74,7 +74,7 @@ importers:
|
|||||||
'@fastify/static': 6.5.0
|
'@fastify/static': 6.5.0
|
||||||
'@iarna/toml': 2.2.5
|
'@iarna/toml': 2.2.5
|
||||||
'@ladjs/graceful': 3.0.2
|
'@ladjs/graceful': 3.0.2
|
||||||
'@prisma/client': 4.2.1_prisma@4.2.1
|
'@prisma/client': 3.15.2_prisma@3.15.2
|
||||||
axios: 0.27.2
|
axios: 0.27.2
|
||||||
bcryptjs: 2.4.3
|
bcryptjs: 2.4.3
|
||||||
bree: 9.1.2
|
bree: 9.1.2
|
||||||
@@ -112,14 +112,28 @@ importers:
|
|||||||
eslint-plugin-prettier: 4.2.1_tgumt6uwl2md3n6uqnggd6wvce
|
eslint-plugin-prettier: 4.2.1_tgumt6uwl2md3n6uqnggd6wvce
|
||||||
nodemon: 2.0.19
|
nodemon: 2.0.19
|
||||||
prettier: 2.7.1
|
prettier: 2.7.1
|
||||||
prisma: 4.2.1
|
prisma: 3.15.2
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
tsconfig-paths: 4.1.0
|
tsconfig-paths: 4.1.0
|
||||||
typescript: 4.7.4
|
typescript: 4.7.4
|
||||||
|
|
||||||
|
apps/i18n:
|
||||||
|
specifiers:
|
||||||
|
dotenv: 16.0.2
|
||||||
|
gettext-parser: 6.0.0
|
||||||
|
got: 12.3.1
|
||||||
|
node-gettext: 3.0.0
|
||||||
|
dependencies:
|
||||||
|
dotenv: 16.0.2
|
||||||
|
gettext-parser: 6.0.0
|
||||||
|
got: 12.3.1
|
||||||
|
node-gettext: 3.0.0
|
||||||
|
|
||||||
apps/ui:
|
apps/ui:
|
||||||
specifiers:
|
specifiers:
|
||||||
|
'@floating-ui/dom': 1.0.1
|
||||||
'@playwright/test': 1.25.1
|
'@playwright/test': 1.25.1
|
||||||
|
'@popperjs/core': 2.11.6
|
||||||
'@sveltejs/adapter-static': 1.0.0-next.39
|
'@sveltejs/adapter-static': 1.0.0-next.39
|
||||||
'@sveltejs/kit': 1.0.0-next.405
|
'@sveltejs/kit': 1.0.0-next.405
|
||||||
'@tailwindcss/typography': ^0.5.4
|
'@tailwindcss/typography': ^0.5.4
|
||||||
@@ -127,11 +141,14 @@ importers:
|
|||||||
'@typescript-eslint/eslint-plugin': 5.35.1
|
'@typescript-eslint/eslint-plugin': 5.35.1
|
||||||
'@typescript-eslint/parser': 5.35.1
|
'@typescript-eslint/parser': 5.35.1
|
||||||
autoprefixer: 10.4.8
|
autoprefixer: 10.4.8
|
||||||
|
classnames: 2.3.1
|
||||||
cuid: 2.1.8
|
cuid: 2.1.8
|
||||||
daisyui: 2.24.0
|
daisyui: 2.24.0
|
||||||
eslint: 8.22.0
|
eslint: 8.22.0
|
||||||
eslint-config-prettier: 8.5.0
|
eslint-config-prettier: 8.5.0
|
||||||
eslint-plugin-svelte3: 4.0.0
|
eslint-plugin-svelte3: 4.0.0
|
||||||
|
flowbite: 1.5.2
|
||||||
|
flowbite-svelte: 0.26.2
|
||||||
js-cookie: 3.0.1
|
js-cookie: 3.0.1
|
||||||
p-limit: 4.0.0
|
p-limit: 4.0.0
|
||||||
postcss: 8.4.16
|
postcss: 8.4.16
|
||||||
@@ -157,15 +174,20 @@ importers:
|
|||||||
svelte-select: 4.4.7
|
svelte-select: 4.4.7
|
||||||
sveltekit-i18n: 2.2.2_svelte@3.49.0
|
sveltekit-i18n: 2.2.2_svelte@3.49.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@floating-ui/dom': 1.0.1
|
||||||
'@playwright/test': 1.25.1
|
'@playwright/test': 1.25.1
|
||||||
|
'@popperjs/core': 2.11.6
|
||||||
'@sveltejs/kit': 1.0.0-next.405_svelte@3.49.0+vite@3.0.5
|
'@sveltejs/kit': 1.0.0-next.405_svelte@3.49.0+vite@3.0.5
|
||||||
'@types/js-cookie': 3.0.2
|
'@types/js-cookie': 3.0.2
|
||||||
'@typescript-eslint/eslint-plugin': 5.35.1_ktjxjibzrfqejavile4bhmzhjq
|
'@typescript-eslint/eslint-plugin': 5.35.1_ktjxjibzrfqejavile4bhmzhjq
|
||||||
'@typescript-eslint/parser': 5.35.1_4rv7y5c6xz3vfxwhbrcxxi73bq
|
'@typescript-eslint/parser': 5.35.1_4rv7y5c6xz3vfxwhbrcxxi73bq
|
||||||
autoprefixer: 10.4.8_postcss@8.4.16
|
autoprefixer: 10.4.8_postcss@8.4.16
|
||||||
|
classnames: 2.3.1
|
||||||
eslint: 8.22.0
|
eslint: 8.22.0
|
||||||
eslint-config-prettier: 8.5.0_eslint@8.22.0
|
eslint-config-prettier: 8.5.0_eslint@8.22.0
|
||||||
eslint-plugin-svelte3: 4.0.0_laaqauvsmoyypsiqkozwyi2fn4
|
eslint-plugin-svelte3: 4.0.0_laaqauvsmoyypsiqkozwyi2fn4
|
||||||
|
flowbite: 1.5.2
|
||||||
|
flowbite-svelte: 0.26.2
|
||||||
postcss: 8.4.16
|
postcss: 8.4.16
|
||||||
prettier: 2.7.1
|
prettier: 2.7.1
|
||||||
prettier-plugin-svelte: 2.7.0_o3ioganyptcsrh6x4hnxvjkpqi
|
prettier-plugin-svelte: 2.7.0_o3ioganyptcsrh6x4hnxvjkpqi
|
||||||
@@ -357,6 +379,16 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@floating-ui/core/1.0.1:
|
||||||
|
resolution: {integrity: sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@floating-ui/dom/1.0.1:
|
||||||
|
resolution: {integrity: sha512-wBDiLUKWU8QNPNOTAFHiIAkBv1KlHauG2AhqjSeh2H+wR8PX+AArXfz8NkRexH5PgMJMmSOS70YS89AbWYh5dA==}
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/core': 1.0.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@humanwhocodes/config-array/0.10.4:
|
/@humanwhocodes/config-array/0.10.4:
|
||||||
resolution: {integrity: sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==}
|
resolution: {integrity: sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==}
|
||||||
engines: {node: '>=10.10.0'}
|
engines: {node: '>=10.10.0'}
|
||||||
@@ -446,9 +478,13 @@ packages:
|
|||||||
playwright-core: 1.25.1
|
playwright-core: 1.25.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@prisma/client/4.2.1_prisma@4.2.1:
|
/@popperjs/core/2.11.6:
|
||||||
resolution: {integrity: sha512-PZBkY60+k5oix+e6IUfl3ub8TbRLNsPLdfWrdy2eh80WcHTaT+/UfvXf/B7gXedH7FRtbPFHZXk1hZenJiJZFQ==}
|
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
|
||||||
engines: {node: '>=14.17'}
|
dev: true
|
||||||
|
|
||||||
|
/@prisma/client/3.15.2_prisma@3.15.2:
|
||||||
|
resolution: {integrity: sha512-ErqtwhX12ubPhU4d++30uFY/rPcyvjk+mdifaZO5SeM21zS3t4jQrscy8+6IyB0GIYshl5ldTq6JSBo1d63i8w==}
|
||||||
|
engines: {node: '>=12.6'}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
prisma: '*'
|
prisma: '*'
|
||||||
@@ -456,16 +492,16 @@ packages:
|
|||||||
prisma:
|
prisma:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/engines-version': 4.2.0-33.2920a97877e12e055c1333079b8d19cee7f33826
|
'@prisma/engines-version': 3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e
|
||||||
prisma: 4.2.1
|
prisma: 3.15.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@prisma/engines-version/4.2.0-33.2920a97877e12e055c1333079b8d19cee7f33826:
|
/@prisma/engines-version/3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e:
|
||||||
resolution: {integrity: sha512-tktkqdiwqE4QhmE088boPt+FwPj1Jub/zk+5F6sEfcRHzO5yz9jyMD5HFVtiwxZPLx/8Xg9ElnuTi8E5lWVQFQ==}
|
resolution: {integrity: sha512-e3k2Vd606efd1ZYy2NQKkT4C/pn31nehyLhVug6To/q8JT8FpiMrDy7zmm3KLF0L98NOQQcutaVtAPhzKhzn9w==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@prisma/engines/4.2.1:
|
/@prisma/engines/3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e:
|
||||||
resolution: {integrity: sha512-0KqBwREUOjBiHwITsQzw2DWfLHjntvbqzGRawj4sBMnIiL5CXwyDUKeHOwXzKMtNr1rEjxEsypM14g0CzLRK3g==}
|
resolution: {integrity: sha512-NHlojO1DFTsSi3FtEleL9QWXeSF/UjhCW0fgpi7bumnNZ4wj/eQ+BJJ5n2pgoOliTOGv9nX2qXvmHap7rJMNmg==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
|
|
||||||
/@rollup/pluginutils/4.2.1:
|
/@rollup/pluginutils/4.2.1:
|
||||||
@@ -1988,6 +2024,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/classnames/2.3.1:
|
||||||
|
resolution: {integrity: sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/clean-stack/4.2.0:
|
/clean-stack/4.2.0:
|
||||||
resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==}
|
resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -2101,6 +2141,11 @@ packages:
|
|||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/content-type/1.0.4:
|
||||||
|
resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/convert-hrtime/3.0.0:
|
/convert-hrtime/3.0.0:
|
||||||
resolution: {integrity: sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==}
|
resolution: {integrity: sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -2448,6 +2493,11 @@ packages:
|
|||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/dotenv/16.0.2:
|
||||||
|
resolution: {integrity: sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/dotenv/8.6.0:
|
/dotenv/8.6.0:
|
||||||
resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==}
|
resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2481,6 +2531,12 @@ packages:
|
|||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/encoding/0.1.13:
|
||||||
|
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
|
||||||
|
dependencies:
|
||||||
|
iconv-lite: 0.6.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/end-of-stream/1.4.4:
|
/end-of-stream/1.4.4:
|
||||||
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
|
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3433,6 +3489,24 @@ packages:
|
|||||||
resolution: {integrity: sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==}
|
resolution: {integrity: sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/flowbite-svelte/0.26.2:
|
||||||
|
resolution: {integrity: sha512-CpZ33fyMuzrYvECKN2rD5zNa/ZY0c4cDMcY6wE/hvajFybbCqwMYzd0AkA88zTNm5fknEN0/E23TVmD4Qkhzzw==}
|
||||||
|
engines: {node: '>=16.0.0', npm: '>=7.0.0'}
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/dom': 1.0.1
|
||||||
|
'@popperjs/core': 2.11.6
|
||||||
|
classnames: 2.3.1
|
||||||
|
flowbite: 1.5.2
|
||||||
|
svelte-heros: 2.3.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/flowbite/1.5.2:
|
||||||
|
resolution: {integrity: sha512-oSKhPkg0bYb4dZG4ypSh++dPrFM3IOSER6HOwJHYRFPW5u9PXL4AQFMQh7Kytgws2xrDQyl/zqefZmymeWt2IA==}
|
||||||
|
dependencies:
|
||||||
|
'@popperjs/core': 2.11.6
|
||||||
|
mini-svg-data-uri: 1.4.4
|
||||||
|
dev: true
|
||||||
|
|
||||||
/follow-redirects/1.15.0:
|
/follow-redirects/1.15.0:
|
||||||
resolution: {integrity: sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==}
|
resolution: {integrity: sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
@@ -3560,6 +3634,15 @@ packages:
|
|||||||
get-intrinsic: 1.1.1
|
get-intrinsic: 1.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/gettext-parser/6.0.0:
|
||||||
|
resolution: {integrity: sha512-eWFsR78gc/eKnzDgc919Us3cbxQbzxK1L8vAIZrKMQqOUgULyeqmczNlBjTlVTk2FaB7nV9C1oobd/PGBOqNmg==}
|
||||||
|
dependencies:
|
||||||
|
content-type: 1.0.4
|
||||||
|
encoding: 0.1.13
|
||||||
|
readable-stream: 4.1.0
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/glob-parent/5.1.2:
|
/glob-parent/5.1.2:
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -3759,6 +3842,13 @@ packages:
|
|||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/iconv-lite/0.6.3:
|
||||||
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dependencies:
|
||||||
|
safer-buffer: 2.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ieee754/1.2.1:
|
/ieee754/1.2.1:
|
||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -4264,6 +4354,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
|
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/lodash.get/4.4.2:
|
||||||
|
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lodash.includes/4.3.0:
|
/lodash.includes/4.3.0:
|
||||||
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -4434,6 +4528,11 @@ packages:
|
|||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/mini-svg-data-uri/1.4.4:
|
||||||
|
resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
|
||||||
|
hasBin: true
|
||||||
|
dev: true
|
||||||
|
|
||||||
/minimalistic-assert/1.0.1:
|
/minimalistic-assert/1.0.1:
|
||||||
resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
|
resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -4535,6 +4634,12 @@ packages:
|
|||||||
engines: {node: '>= 6.13.0'}
|
engines: {node: '>= 6.13.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/node-gettext/3.0.0:
|
||||||
|
resolution: {integrity: sha512-/VRYibXmVoN6tnSAY2JWhNRhWYJ8Cd844jrZU/DwLVoI4vBI6ceYbd8i42sYZ9uOgDH3S7vslIKOWV/ZrT2YBA==}
|
||||||
|
dependencies:
|
||||||
|
lodash.get: 4.4.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/node-os-utils/1.3.7:
|
/node-os-utils/1.3.7:
|
||||||
resolution: {integrity: sha512-fvnX9tZbR7WfCG5BAy3yO/nCLyjVWD6MghEq0z5FDfN+ZXpLWNITBdbifxQkQ25ebr16G0N7eRWJisOcMEHG3Q==}
|
resolution: {integrity: sha512-fvnX9tZbR7WfCG5BAy3yO/nCLyjVWD6MghEq0z5FDfN+ZXpLWNITBdbifxQkQ25ebr16G0N7eRWJisOcMEHG3Q==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -5071,13 +5176,13 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/prisma/4.2.1:
|
/prisma/3.15.2:
|
||||||
resolution: {integrity: sha512-HuYqnTDgH8atjPGtYmY0Ql9XrrJnfW7daG1PtAJRW0E6gJxc50lY3vrIDn0yjMR3TvRlypjTcspQX8DT+xD4Sg==}
|
resolution: {integrity: sha512-nMNSMZvtwrvoEQ/mui8L/aiCLZRCj5t6L3yujKpcDhIPk7garp8tL4nMx2+oYsN0FWBacevJhazfXAbV1kfBzA==}
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=12.6'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/engines': 4.2.1
|
'@prisma/engines': 3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e
|
||||||
|
|
||||||
/private/0.1.8:
|
/private/0.1.8:
|
||||||
resolution: {integrity: sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==}
|
resolution: {integrity: sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==}
|
||||||
@@ -5210,6 +5315,13 @@ packages:
|
|||||||
abort-controller: 3.0.0
|
abort-controller: 3.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/readable-stream/4.1.0:
|
||||||
|
resolution: {integrity: sha512-sVisi3+P2lJ2t0BPbpK629j8wRW06yKGJUcaLAGXPAUhyUxVJm7VsCTit1PFgT4JHUDMrGNR+ZjSKpzGaRF3zw==}
|
||||||
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
dependencies:
|
||||||
|
abort-controller: 3.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/readdirp/3.6.0:
|
/readdirp/3.6.0:
|
||||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||||
engines: {node: '>=8.10.0'}
|
engines: {node: '>=8.10.0'}
|
||||||
@@ -5795,6 +5907,10 @@ packages:
|
|||||||
- sugarss
|
- sugarss
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/svelte-heros/2.3.5:
|
||||||
|
resolution: {integrity: sha512-08PdccaeRPP1pVa90AGieTwGzrNtXpC1Fry+i95OTvcR3xbGRU/hxK4rnaFYvGgk1Pxj9YT6GKGTEX8uXE9XJQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/svelte-hmr/0.14.12_svelte@3.49.0:
|
/svelte-hmr/0.14.12_svelte@3.49.0:
|
||||||
resolution: {integrity: sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==}
|
resolution: {integrity: sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==}
|
||||||
engines: {node: ^12.20 || ^14.13.1 || >= 16}
|
engines: {node: ^12.20 || ^14.13.1 || >= 16}
|
||||||
|
|||||||
Reference in New Issue
Block a user