Merge branch 'next' into ui
This commit is contained in:
commit
ae4cf44728
@ -125,7 +125,7 @@ ### Add supported versions
|
|||||||
|
|
||||||
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
|
||||||
{
|
{
|
||||||
@ -209,21 +209,22 @@ ### Add required functions/properties
|
|||||||
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. 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)
|
5. 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.
|
||||||
|
|
||||||
|
@ -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");
|
@ -348,6 +348,7 @@ model Service {
|
|||||||
wordpress Wordpress?
|
wordpress Wordpress?
|
||||||
appwrite Appwrite?
|
appwrite Appwrite?
|
||||||
searxng Searxng?
|
searxng Searxng?
|
||||||
|
weblate Weblate?
|
||||||
}
|
}
|
||||||
|
|
||||||
model PlausibleAnalytics {
|
model PlausibleAnalytics {
|
||||||
@ -559,3 +560,18 @@ 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])
|
||||||
|
}
|
||||||
|
@ -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.9';
|
export const version = '3.9.0';
|
||||||
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';
|
||||||
@ -45,7 +45,7 @@ export function getAPIUrl() {
|
|||||||
if (process.env.CODESANDBOX_HOST) {
|
if (process.env.CODESANDBOX_HOST) {
|
||||||
return `https://${process.env.CODESANDBOX_HOST.replace(/\$PORT/, '3001')}`
|
return `https://${process.env.CODESANDBOX_HOST.replace(/\$PORT/, '3001')}`
|
||||||
}
|
}
|
||||||
return isDev ? 'http://localhost:3001' : 'http://localhost:3000';
|
return isDev ? 'http://host.docker.internal:3001' : 'http://localhost:3000';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUIUrl() {
|
export function getUIUrl() {
|
||||||
@ -1309,6 +1309,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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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,8 @@ export const includeServices: any = {
|
|||||||
moodle: true,
|
moodle: true,
|
||||||
appwrite: true,
|
appwrite: true,
|
||||||
glitchTip: true,
|
glitchTip: true,
|
||||||
searxng: true
|
searxng: true,
|
||||||
|
weblate: true
|
||||||
};
|
};
|
||||||
export async function configureServiceType({
|
export async function configureServiceType({
|
||||||
id,
|
id,
|
||||||
@ -312,6 +297,27 @@ 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 {
|
} else {
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@ -338,7 +344,7 @@ 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.service.delete({ where: { id } });
|
await prisma.service.delete({ where: { id } });
|
||||||
}
|
}
|
@ -63,6 +63,9 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
if (type === 'searxng') {
|
if (type === 'searxng') {
|
||||||
return await startSearXNGService(request)
|
return await startSearXNGService(request)
|
||||||
}
|
}
|
||||||
|
if (type === 'weblate') {
|
||||||
|
return await startWeblateService(request)
|
||||||
|
}
|
||||||
throw `Service type ${type} not supported.`
|
throw `Service type ${type} not supported.`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw { status: 500, message: error?.message || error }
|
throw { status: 500, message: error?.message || error }
|
||||||
@ -2224,3 +2227,106 @@ async function startSearXNGService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function startWeblateService(request: FastifyRequest<ServiceStartStop>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
|
const {
|
||||||
|
weblate: { adminPassword, postgresqlHost, postgresqlPort, postgresqlUser, postgresqlPassword, postgresqlDatabase }
|
||||||
|
} = service;
|
||||||
|
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort, persistentStorage, fqdn } =
|
||||||
|
service;
|
||||||
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
|
const port = getServiceMainPort('weblate');
|
||||||
|
|
||||||
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
|
const image = getServiceImage(type);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
weblate: {
|
||||||
|
image: `${image}:${version}`,
|
||||||
|
volume: `${id}-data:/app/data`,
|
||||||
|
environmentVariables: {
|
||||||
|
WEBLATE_SITE_DOMAIN: getDomain(fqdn),
|
||||||
|
WEBLATE_ADMIN_PASSWORD: adminPassword,
|
||||||
|
POSTGRES_PASSWORD: postgresqlPassword,
|
||||||
|
POSTGRES_USER: postgresqlUser,
|
||||||
|
POSTGRES_DATABASE: postgresqlDatabase,
|
||||||
|
POSTGRES_HOST: postgresqlHost,
|
||||||
|
POSTGRES_PORT: postgresqlPort,
|
||||||
|
REDIS_HOST: `${id}-redis`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
postgresql: {
|
||||||
|
image: `postgres:14-alpine`,
|
||||||
|
volume: `${id}-postgresql-data:/var/lib/postgresql/data`,
|
||||||
|
environmentVariables: {
|
||||||
|
POSTGRES_PASSWORD: postgresqlPassword,
|
||||||
|
POSTGRES_USER: postgresqlUser,
|
||||||
|
POSTGRES_DB: postgresqlDatabase,
|
||||||
|
POSTGRES_HOST: postgresqlHost,
|
||||||
|
POSTGRES_PORT: postgresqlPort,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
image: `redis:6-alpine`,
|
||||||
|
volume: `${id}-redis-data:/data`,
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
if (serviceSecret.length > 0) {
|
||||||
|
serviceSecret.forEach((secret) => {
|
||||||
|
config.weblate.environmentVariables[secret.name] = secret.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
|
const composeFile: ComposeFile = {
|
||||||
|
version: '3.8',
|
||||||
|
services: {
|
||||||
|
[id]: {
|
||||||
|
container_name: id,
|
||||||
|
image: config.weblate.image,
|
||||||
|
environment: config.weblate.environmentVariables,
|
||||||
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
|
volumes,
|
||||||
|
labels: makeLabelForServices('weblate'),
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
[`${id}-postgresql`]: {
|
||||||
|
container_name: `${id}-postgresql`,
|
||||||
|
image: config.postgresql.image,
|
||||||
|
environment: config.postgresql.environmentVariables,
|
||||||
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
|
volumes,
|
||||||
|
labels: makeLabelForServices('weblate'),
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
[`${id}-redis`]: {
|
||||||
|
container_name: `${id}-redis`,
|
||||||
|
image: config.redis.image,
|
||||||
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
|
volumes,
|
||||||
|
labels: makeLabelForServices('weblate'),
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
networks: {
|
||||||
|
[network]: {
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
volumes: volumeMounts
|
||||||
|
};
|
||||||
|
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||||
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
await startServiceContainers(destinationDocker.id, composeFileDestination)
|
||||||
|
return {}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
@ -688,3 +736,52 @@ export const searxng = [{
|
|||||||
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
|
||||||
|
}]
|
@ -190,4 +190,15 @@ 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
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
@ -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
|
||||||
@ -312,6 +319,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
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@ 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 bcrypt from 'bcryptjs';
|
||||||
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 { 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, removeContainer } 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 { 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 +269,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 +399,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,12 @@ export interface ActivateWordpressFtp extends OnlyId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SetGlitchTipSettings extends OnlyId {
|
||||||
|
Body: {
|
||||||
|
enableOpenUserRegistration: boolean,
|
||||||
|
emailSmtpUseSsl: boolean,
|
||||||
|
emailSmtpUseTls: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
>
|
@ -17,3 +17,4 @@ 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';
|
@ -62,9 +62,7 @@
|
|||||||
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';
|
|
||||||
|
|
||||||
let loading = false;
|
|
||||||
let statusInterval: any;
|
let statusInterval: any;
|
||||||
$disabledButton =
|
$disabledButton =
|
||||||
!$appSession.isAdmin ||
|
!$appSession.isAdmin ||
|
||||||
@ -78,7 +76,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'
|
||||||
@ -98,22 +99,41 @@
|
|||||||
async function deleteApplication(name: string) {
|
async function deleteApplication(name: string) {
|
||||||
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 });
|
||||||
return await goto(`/applications`);
|
return await goto(`/applications`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
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`, {});
|
||||||
|
} 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() {
|
||||||
@ -152,209 +172,136 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="nav-side">
|
<nav class="nav-side">
|
||||||
{#if loading}
|
{#if $location}
|
||||||
<Loading fullscreen cover />
|
<a
|
||||||
{:else}
|
href={$location}
|
||||||
{#if $location}
|
target="_blank"
|
||||||
<a
|
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||||
href={$location}
|
><svg
|
||||||
target="_blank"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
class="h-6 w-6"
|
||||||
><svg
|
viewBox="0 0 24 24"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
stroke-width="1.5"
|
||||||
class="h-6 w-6"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
fill="none"
|
||||||
stroke-width="1.5"
|
stroke-linecap="round"
|
||||||
stroke="currentColor"
|
stroke-linejoin="round"
|
||||||
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
|
|
||||||
>
|
>
|
||||||
|
<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" />
|
<div class="border border-coolgray-500 h-8" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
{/if}
|
{#if $status.application.isExited}
|
||||||
|
<a
|
||||||
{#if $status.application.isExited}
|
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
||||||
<a
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center text-error"
|
||||||
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
data-tip="Application exited with an error!"
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center text-error"
|
sveltekit:prefetch
|
||||||
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"
|
||||||
>
|
>
|
||||||
<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"
|
{/if}
|
||||||
>
|
{#if $status.application.initialLoading}
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<button
|
||||||
<path
|
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
||||||
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"
|
>
|
||||||
/>
|
<svg
|
||||||
<line x1="12" y1="8" x2="12" y2="12" />
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
class="h-6 w-6"
|
||||||
</svg>
|
viewBox="0 0 24 24"
|
||||||
</a>
|
stroke-width="1.5"
|
||||||
{/if}
|
stroke="currentColor"
|
||||||
{#if $status.application.initialLoading}
|
fill="none"
|
||||||
<button
|
stroke-linecap="round"
|
||||||
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<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="h-6 w-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 d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
on:click={stopApplication}
|
||||||
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
type="submit"
|
||||||
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
disabled={$disabledButton}
|
||||||
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-error"
|
||||||
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
data-tip={$appSession.isAdmin
|
||||||
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
? 'Stop'
|
||||||
</svg>
|
: $t('application.permission_denied_stop_application')}
|
||||||
</button>
|
>
|
||||||
{:else if $status.application.isRunning}
|
<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>
|
||||||
|
<button
|
||||||
|
on:click={restartApplication}
|
||||||
|
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
|
||||||
|
? 'Restart (useful for changing secrets)'
|
||||||
|
: $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" />
|
||||||
|
<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>
|
||||||
|
<form on:submit|preventDefault={() => handleDeploySubmit(true)}>
|
||||||
<button
|
<button
|
||||||
on:click={stopApplication}
|
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={$disabledButton}
|
disabled={$disabledButton}
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-error"
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2"
|
||||||
data-tip={$appSession.isAdmin
|
data-tip={$appSession.isAdmin
|
||||||
? $t('application.stop_application')
|
? 'Force Rebuild without cache'
|
||||||
: $t('application.permission_denied_stop_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" />
|
|
||||||
<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" />
|
|
||||||
<a
|
|
||||||
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 tooltip tooltip-primary tooltip-bottom text-sm"
|
|
||||||
data-tip="Configurations"
|
|
||||||
>
|
|
||||||
<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
|
|
||||||
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 tooltip tooltip-primary tooltip-bottom text-sm"
|
|
||||||
data-tip="Secrets"
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -368,24 +315,21 @@
|
|||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path
|
<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"
|
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)"
|
||||||
/>
|
/>
|
||||||
<circle cx="12" cy="11" r="1" />
|
</svg>
|
||||||
<line x1="12" y1="12" x2="12" y2="14.5" />
|
</button>
|
||||||
</svg></button
|
</form>
|
||||||
></a
|
{:else}
|
||||||
>
|
<form on:submit|preventDefault={() => handleDeploySubmit(false)}>
|
||||||
<a
|
|
||||||
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
|
<button
|
||||||
|
type="submit"
|
||||||
disabled={$disabledButton}
|
disabled={$disabledButton}
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-success"
|
||||||
data-tip="Persistent Storages"
|
data-tip={$appSession.isAdmin
|
||||||
|
? 'Deploy'
|
||||||
|
: 'You do not have permission to deploy application.'}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -398,92 +342,124 @@
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
<path d="M7 4v16l13 -8z" />
|
||||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
|
|
||||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</button></a
|
</button>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="border border-coolgray-500 h-8" />
|
||||||
|
<a
|
||||||
|
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 tooltip tooltip-primary tooltip-bottom text-sm"
|
||||||
|
data-tip="Configurations"
|
||||||
>
|
>
|
||||||
{#if !application.settings.isBot}
|
<svg
|
||||||
<a
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
href={!$disabledButton ? `/applications/${id}/previews` : null}
|
class="h-6 w-6"
|
||||||
sveltekit:prefetch
|
viewBox="0 0 24 24"
|
||||||
class="hover:text-orange-500 rounded"
|
stroke-width="1.5"
|
||||||
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
stroke="currentColor"
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<button
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
disabled={$disabledButton}
|
<rect x="4" y="8" width="4" height="4" />
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
<line x1="6" y1="4" x2="6" y2="8" />
|
||||||
data-tip="Previews"
|
<line x1="6" y1="12" x2="6" y2="20" />
|
||||||
>
|
<rect x="10" y="14" width="4" height="4" />
|
||||||
<svg
|
<line x1="12" y1="4" x2="12" y2="14" />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<line x1="12" y1="18" x2="12" y2="20" />
|
||||||
class="w-6 h-6"
|
<rect x="16" y="5" width="4" height="4" />
|
||||||
viewBox="0 0 24 24"
|
<line x1="18" y1="4" x2="18" y2="5" />
|
||||||
stroke-width="1.5"
|
<line x1="18" y1="9" x2="18" y2="20" />
|
||||||
stroke="currentColor"
|
</svg></button
|
||||||
fill="none"
|
></a
|
||||||
stroke-linecap="round"
|
>
|
||||||
stroke-linejoin="round"
|
<a
|
||||||
>
|
href={!$disabledButton ? `/applications/${id}/secrets` : null}
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
sveltekit:prefetch
|
||||||
<circle cx="7" cy="18" r="2" />
|
class="hover:text-pink-500 rounded"
|
||||||
<circle cx="7" cy="6" r="2" />
|
class:text-pink-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||||
<circle cx="17" cy="12" r="2" />
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||||
<line x1="7" y1="8" x2="7" y2="16" />
|
>
|
||||||
<path d="M7 8a4 4 0 0 0 4 4h4" />
|
<button
|
||||||
</svg></button
|
disabled={$disabledButton}
|
||||||
></a
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||||
|
data-tip="Secrets"
|
||||||
|
>
|
||||||
|
<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"
|
||||||
>
|
>
|
||||||
{/if}
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<div class="border border-coolgray-500 h-8" />
|
<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={!$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 tooltip tooltip-primary tooltip-bottom text-sm"
|
||||||
|
data-tip="Persistent Storages"
|
||||||
|
>
|
||||||
|
<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
|
<a
|
||||||
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
|
href={!$disabledButton ? `/applications/${id}/previews` : null}
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
class="hover:text-sky-500 rounded"
|
class="hover:text-orange-500 rounded"
|
||||||
class:text-sky-500={$page.url.pathname === `/applications/${id}/logs`}
|
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`}
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||||
>
|
|
||||||
<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
|
<button
|
||||||
disabled={$disabledButton}
|
disabled={$disabledButton}
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||||
data-tip="Build Logs"
|
data-tip="Previews"
|
||||||
>
|
>
|
||||||
<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"
|
||||||
@ -492,31 +468,94 @@
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<circle cx="19" cy="13" r="2" />
|
<circle cx="7" cy="18" r="2" />
|
||||||
<circle cx="4" cy="17" r="2" />
|
<circle cx="7" cy="6" r="2" />
|
||||||
<circle cx="13" cy="17" r="2" />
|
<circle cx="17" cy="12" r="2" />
|
||||||
<line x1="13" y1="19" x2="4" y2="19" />
|
<line x1="7" y1="8" x2="7" y2="16" />
|
||||||
<line x1="4" y1="15" x2="13" y2="15" />
|
<path d="M7 8a4 4 0 0 0 4 4h4" />
|
||||||
<path d="M8 12v-5h2a3 3 0 0 1 3 3v5" />
|
</svg></button
|
||||||
<path d="M5 15v-2a1 1 0 0 1 1 -1h7" />
|
></a
|
||||||
<path d="M19 11v-7l-6 7" />
|
|
||||||
</svg>
|
|
||||||
</button></a
|
|
||||||
>
|
>
|
||||||
<div class="border border-coolgray-500 h-8" />
|
|
||||||
|
|
||||||
<button
|
|
||||||
on:click={() => deleteApplication(application.name)}
|
|
||||||
type="submit"
|
|
||||||
disabled={!$appSession.isAdmin}
|
|
||||||
class:hover:text-red-500={$appSession.isAdmin}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
|
||||||
data-tip={$appSession.isAdmin
|
|
||||||
? $t('application.delete_application')
|
|
||||||
: $t('application.permission_denied_delete_application')}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</button>
|
|
||||||
{/if}
|
{/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
|
||||||
|
on:click={() => deleteApplication(application.name)}
|
||||||
|
type="submit"
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
|
class:hover:text-red-500={$appSession.isAdmin}
|
||||||
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||||
|
data-tip={$appSession.isAdmin
|
||||||
|
? $t('application.delete_application')
|
||||||
|
: $t('application.permission_denied_delete_application')}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -60,11 +60,9 @@
|
|||||||
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';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
let loading = false;
|
|
||||||
let statusInterval: any = false;
|
let statusInterval: any = false;
|
||||||
|
|
||||||
$disabledButton = !$appSession.isAdmin;
|
$disabledButton = !$appSession.isAdmin;
|
||||||
@ -72,36 +70,41 @@
|
|||||||
async function deleteDatabase() {
|
async function deleteDatabase() {
|
||||||
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 });
|
||||||
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() {
|
||||||
@ -137,120 +140,36 @@
|
|||||||
|
|
||||||
{#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}
|
href={!$disabledButton ? `/databases/${id}/logs` : null}
|
||||||
{#if $status.database.isExited}
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center text-red-500 tooltip-error"
|
||||||
<a
|
data-tip="Service exited with an 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"
|
|
||||||
>
|
|
||||||
<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 tooltip-bottom 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 +182,27 @@
|
|||||||
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}
|
on:click={stopDatabase}
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
type="submit"
|
||||||
data-tip={$t('database.logs')}
|
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
|
<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 +211,112 @@
|
|||||||
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" />
|
{:else}
|
||||||
</svg></button
|
<button
|
||||||
></a
|
on:click={startDatabase}
|
||||||
>
|
type="submit"
|
||||||
<button
|
disabled={!$appSession.isAdmin}
|
||||||
on:click={deleteDatabase}
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
|
||||||
type="submit"
|
data-tip={$appSession.isAdmin
|
||||||
disabled={!$appSession.isAdmin}
|
? $t('database.start_database')
|
||||||
class:hover:text-red-500={$appSession.isAdmin}
|
: $t('database.permission_denied_start_database')}
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
><svg
|
||||||
data-tip={$appSession.isAdmin
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
? $t('database.delete_database')
|
class="w-6 h-6"
|
||||||
: $t('database.permission_denied_delete_database')}><DeleteIcon /></button
|
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" />
|
||||||
|
<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
|
||||||
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm disabled:text-red-500"
|
||||||
|
data-tip={$t('application.configurations')}
|
||||||
|
>
|
||||||
|
<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
|
||||||
|
>
|
||||||
|
<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
|
||||||
|
disabled={!$status.database.isRunning}
|
||||||
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||||
|
data-tip={$t('database.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
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
on:click={deleteDatabase}
|
||||||
|
type="submit"
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
|
class:hover:text-red-500={$appSession.isAdmin}
|
||||||
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||||
|
data-tip={$appSession.isAdmin
|
||||||
|
? $t('database.delete_database')
|
||||||
|
: $t('database.permission_denied_delete_database')}><DeleteIcon /></button
|
||||||
|
>
|
||||||
</nav>
|
</nav>
|
||||||
{/if}
|
{/if}
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -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}
|
||||||
|
@ -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,76 +53,33 @@
|
|||||||
<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
|
||||||
|
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">
|
|
||||||
<label for="defaultEmailFrom" class="text-base font-bold text-stone-100">Default Email From</label
|
|
||||||
>
|
|
||||||
<CopyPasswordField
|
|
||||||
required
|
|
||||||
name="defaultEmailFrom"
|
|
||||||
id="defaultEmailFrom"
|
|
||||||
value={service.glitchTip.defaultEmailFrom}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="emailSmtpHost" class="text-base font-bold text-stone-100">SMTP Host</label>
|
|
||||||
<CopyPasswordField
|
|
||||||
name="emailSmtpHost"
|
|
||||||
id="emailSmtpHost"
|
|
||||||
value={service.glitchTip.emailSmtpHost}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="emailSmtpPort" class="text-base font-bold text-stone-100">SMTP Port</label>
|
|
||||||
<CopyPasswordField
|
|
||||||
name="emailSmtpPort"
|
|
||||||
id="emailSmtpPort"
|
|
||||||
value={service.glitchTip.emailSmtpPort}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="emailSmtpUser" class="text-base font-bold text-stone-100">SMTP User</label>
|
|
||||||
<CopyPasswordField
|
|
||||||
name="emailSmtpUser"
|
|
||||||
id="emailSmtpUser"
|
|
||||||
value={service.glitchTip.emailSmtpUser}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="emailSmtpPassword" class="text-base font-bold text-stone-100">SMTP Password</label>
|
|
||||||
<CopyPasswordField
|
|
||||||
name="emailSmtpPassword"
|
|
||||||
id="emailSmtpPassword"
|
|
||||||
value={service.glitchTip.emailSmtpPassword}
|
|
||||||
isPasswordField
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
bind:setting={service.glitchTip.emailSmtpUseTls}
|
bind:setting={service.glitchTip.emailSmtpUseTls}
|
||||||
on:click={toggleEmailSmtpUseTls}
|
{loading}
|
||||||
title={'SMTP Use TLS'}
|
disabled={$status.service.isRunning}
|
||||||
|
on:click={() => changeSettings('emailSmtpUseTls')}
|
||||||
|
title="Use TLS for SMTP"
|
||||||
description={''}
|
description={''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -96,32 +87,84 @@
|
|||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
bind:setting={service.glitchTip.emailSmtpUseSsl}
|
bind:setting={service.glitchTip.emailSmtpUseSsl}
|
||||||
on:click={toggleEmailSmtpUseSsl}
|
{loading}
|
||||||
title={'SMTP Use SSL'}
|
disabled={$status.service.isRunning}
|
||||||
|
on:click={() => changeSettings('emailSmtpUseSsl')}
|
||||||
|
title="Use SSL for SMTP"
|
||||||
description={''}
|
description={''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="emailBackend" class="text-base font-bold text-stone-100">Email Backend</label>
|
<label for="defaultEmailFrom">Default Email From</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"
|
required
|
||||||
id="mailgunApiKey"
|
name="defaultEmailFrom"
|
||||||
value={service.glitchTip.mailgunApiKey}
|
id="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="sendgridApiKey" class="text-base font-bold text-stone-100">SendGrid API Key</label>
|
<label for="emailSmtpHost">SMTP Host</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpHost"
|
||||||
|
id="emailSmtpHost"
|
||||||
|
bind:value={service.glitchTip.emailSmtpHost}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpPort">SMTP Port</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpPort"
|
||||||
|
id="emailSmtpPort"
|
||||||
|
bind:value={service.glitchTip.emailSmtpPort}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpUser">SMTP User</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpUser"
|
||||||
|
id="emailSmtpUser"
|
||||||
|
bind:value={service.glitchTip.emailSmtpUser}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpPassword">SMTP Password</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpPassword"
|
||||||
|
id="emailSmtpPassword"
|
||||||
|
bind:value={service.glitchTip.emailSmtpPassword}
|
||||||
|
isPasswordField
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailBackend">Email Backend</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailBackend"
|
||||||
|
id="emailBackend"
|
||||||
|
bind:value={service.glitchTip.emailBackend}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="mailgunApiKey">Mailgun API Key</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="mailgunApiKey"
|
||||||
|
id="mailgunApiKey"
|
||||||
|
bind:value={service.glitchTip.mailgunApiKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<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 +173,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 +209,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
|
||||||
/>
|
/>
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
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';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
$: isDisabled =
|
$: isDisabled =
|
||||||
@ -405,6 +406,8 @@
|
|||||||
<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 />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
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>
|
@ -56,7 +56,6 @@
|
|||||||
<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';
|
||||||
@ -74,13 +73,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 +87,36 @@
|
|||||||
} 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;
|
||||||
|
$status.service.loading = 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;
|
||||||
|
$status.service.loading = 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() {
|
||||||
@ -146,269 +149,265 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="nav-side">
|
<nav class="nav-side">
|
||||||
{#if loading}
|
{#if service.type && service.destinationDockerId && service.version}
|
||||||
<Loading fullscreen cover />
|
{#if $location}
|
||||||
{:else}
|
<a
|
||||||
{#if service.type && service.destinationDockerId && service.version}
|
href={$location}
|
||||||
{#if $location}
|
target="_blank"
|
||||||
<a
|
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||||
href={$location}
|
><svg
|
||||||
target="_blank"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
class="h-6 w-6"
|
||||||
><svg
|
viewBox="0 0 24 24"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
stroke-width="1.5"
|
||||||
class="h-6 w-6"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
fill="none"
|
||||||
stroke-width="1.5"
|
stroke-linecap="round"
|
||||||
stroke="currentColor"
|
stroke-linejoin="round"
|
||||||
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
|
|
||||||
>
|
>
|
||||||
<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!"
|
|
||||||
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}"
|
href={!$disabledButton ? `/services/${id}/logs` : null}
|
||||||
|
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!"
|
||||||
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"
|
|
||||||
>
|
|
||||||
<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 tooltip-bottom 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
|
||||||
|
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" />
|
||||||
|
{/if}
|
||||||
|
{#if service.type && service.destinationDockerId && service.version}
|
||||||
|
<a
|
||||||
|
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 tooltip tooltip-primary tooltip-bottom text-sm disabled:text-red-500"
|
||||||
|
data-tip={$t('application.configurations')}
|
||||||
|
>
|
||||||
|
<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
|
||||||
|
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
|
||||||
|
on:click={deleteService}
|
||||||
|
type="submit"
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
|
class:hover:text-red-500={$appSession.isAdmin}
|
||||||
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||||
|
data-tip={$appSession.isAdmin
|
||||||
|
? $t('service.delete_service')
|
||||||
|
: $t('service.permission_denied_delete_service')}><DeleteIcon /></button
|
||||||
|
>
|
||||||
</nav>
|
</nav>
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -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>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -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.9",
|
"version": "3.9.0",
|
||||||
"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",
|
||||||
|
@ -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,11 +112,23 @@ 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:
|
||||||
'@playwright/test': 1.25.1
|
'@playwright/test': 1.25.1
|
||||||
@ -446,9 +458,9 @@ packages:
|
|||||||
playwright-core: 1.25.1
|
playwright-core: 1.25.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@prisma/client/4.2.1_prisma@4.2.1:
|
/@prisma/client/3.15.2_prisma@3.15.2:
|
||||||
resolution: {integrity: sha512-PZBkY60+k5oix+e6IUfl3ub8TbRLNsPLdfWrdy2eh80WcHTaT+/UfvXf/B7gXedH7FRtbPFHZXk1hZenJiJZFQ==}
|
resolution: {integrity: sha512-ErqtwhX12ubPhU4d++30uFY/rPcyvjk+mdifaZO5SeM21zS3t4jQrscy8+6IyB0GIYshl5ldTq6JSBo1d63i8w==}
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=12.6'}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
prisma: '*'
|
prisma: '*'
|
||||||
@ -456,16 +468,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:
|
||||||
@ -2101,6 +2113,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 +2465,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 +2503,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:
|
||||||
@ -3560,6 +3588,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 +3796,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 +4308,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
|
||||||
@ -4535,6 +4583,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 +5125,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 +5264,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'}
|
||||||
|
Loading…
Reference in New Issue
Block a user