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).
|
||||
|
||||
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
|
||||
{
|
||||
@ -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)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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/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.
|
||||
|
||||
|
@ -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?
|
||||
appwrite Appwrite?
|
||||
searxng Searxng?
|
||||
weblate Weblate?
|
||||
}
|
||||
|
||||
model PlausibleAnalytics {
|
||||
@ -559,3 +560,18 @@ model Searxng {
|
||||
updatedAt DateTime @updatedAt
|
||||
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 { includeServices } from './services/common';
|
||||
|
||||
export const version = '3.8.9';
|
||||
export const version = '3.9.0';
|
||||
export const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
const algorithm = 'aes-256-ctr';
|
||||
@ -45,7 +45,7 @@ export function getAPIUrl() {
|
||||
if (process.env.CODESANDBOX_HOST) {
|
||||
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() {
|
||||
@ -1309,6 +1309,9 @@ export function saveUpdateableFields(type: string, data: any) {
|
||||
temp = Boolean(temp)
|
||||
}
|
||||
}
|
||||
if (k.isNumber && temp === '') {
|
||||
temp = null
|
||||
}
|
||||
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 os from 'os';
|
||||
import sshConfig from 'ssh-config'
|
||||
import { encrypt, generatePassword, prisma } from '../common';
|
||||
|
||||
|
||||
export const version = '3.8.2';
|
||||
export const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
export const includeServices: any = {
|
||||
destinationDocker: true,
|
||||
persistentStorage: true,
|
||||
@ -34,7 +18,8 @@ export const includeServices: any = {
|
||||
moodle: true,
|
||||
appwrite: true,
|
||||
glitchTip: true,
|
||||
searxng: true
|
||||
searxng: true,
|
||||
weblate: true
|
||||
};
|
||||
export async function configureServiceType({
|
||||
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 {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
@ -338,7 +344,7 @@ export async function removeService({ id }: { id: string }): Promise<void> {
|
||||
await prisma.moodle.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.searxng.deleteMany({ where: { serviceId: id } });
|
||||
|
||||
await prisma.weblate.deleteMany({ where: { serviceId: id } });
|
||||
|
||||
await prisma.service.delete({ where: { id } });
|
||||
}
|
@ -63,6 +63,9 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
||||
if (type === 'searxng') {
|
||||
return await startSearXNGService(request)
|
||||
}
|
||||
if (type === 'weblate') {
|
||||
return await startWeblateService(request)
|
||||
}
|
||||
throw `Service type ${type} not supported.`
|
||||
} catch (error) {
|
||||
throw { status: 500, message: error?.message || error }
|
||||
@ -2224,3 +2227,106 @@ async function startSearXNGService(request: FastifyRequest<ServiceStartStop>) {
|
||||
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,
|
||||
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',
|
||||
isEditable: false,
|
||||
@ -624,7 +672,7 @@ export const glitchTip = [{
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'defaultFromEmail',
|
||||
name: 'defaultEmailFrom',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
@ -688,3 +736,52 @@ export const searxng = [{
|
||||
isBoolean: false,
|
||||
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
|
||||
}
|
||||
},
|
||||
{
|
||||
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 axios from 'axios';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
import { day } from '../../../../lib/dayjs';
|
||||
import { 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 { makeLabelForStandaloneApplication, setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/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 { scheduler } from '../../../../lib/scheduler';
|
||||
|
||||
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 { 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) {
|
||||
try {
|
||||
const { teamId } = request.user
|
||||
@ -312,6 +319,113 @@ export async function stopPreviewApplication(request: FastifyRequest<StopPreview
|
||||
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) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
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';
|
||||
|
||||
@ -19,6 +19,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
|
||||
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<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 yaml from 'js-yaml';
|
||||
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 { checkContainer, isContainerExited, removeContainer } from '../../../../lib/docker';
|
||||
import cuid from 'cuid';
|
||||
|
||||
import type { OnlyId } from '../../../../types';
|
||||
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
|
||||
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetGlitchTipSettings, SetWordpressSettings } from './types';
|
||||
import { defaultServiceConfigurations } from '../../../../lib/services';
|
||||
import { supportedServiceTypesAndVersions } from '../../../../lib/services/supportedVersions';
|
||||
import { configureServiceType, removeService } from '../../../../lib/services/common';
|
||||
@ -269,7 +269,6 @@ export async function saveService(request: FastifyRequest<SaveService>, reply: F
|
||||
if (exposePort) exposePort = Number(exposePort);
|
||||
|
||||
type = fixType(type)
|
||||
|
||||
const update = saveUpdateableFields(type, request.body[type])
|
||||
const data = {
|
||||
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 {
|
||||
const { type } = request.params
|
||||
if (type === 'wordpress') {
|
||||
return await setWordpressSettings(request, reply)
|
||||
}
|
||||
if (type === 'glitchtip') {
|
||||
return await setGlitchTipSettings(request, reply)
|
||||
}
|
||||
throw `Service type ${type} not supported.`
|
||||
} catch ({ 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) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
|
@ -29,7 +29,7 @@ import {
|
||||
} from './handlers';
|
||||
|
||||
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';
|
||||
|
||||
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/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/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
|
||||
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"
|
||||
><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"
|
||||
|
@ -40,4 +40,6 @@
|
||||
<Icons.GlitchTip {isAbsolute} />
|
||||
{:else if type === 'searxng'}
|
||||
<Icons.Searxng {isAbsolute} />
|
||||
{:else if type === 'weblate'}
|
||||
<Icons.Weblate {isAbsolute} />
|
||||
{/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 GlitchTip } from './GlitchTip.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 { appSession, disabledButton, status, location, setLocation, addToast } from '$lib/store';
|
||||
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
|
||||
let loading = false;
|
||||
let statusInterval: any;
|
||||
$disabledButton =
|
||||
!$appSession.isAdmin ||
|
||||
@ -78,7 +76,10 @@
|
||||
|
||||
async function handleDeploySubmit(forceRebuild = false) {
|
||||
try {
|
||||
const { buildId } = await post(`/applications/${id}/deploy`, { ...application, forceRebuild });
|
||||
const { buildId } = await post(`/applications/${id}/deploy`, {
|
||||
...application,
|
||||
forceRebuild
|
||||
});
|
||||
addToast({
|
||||
message: $t('application.deployment_queued'),
|
||||
type: 'success'
|
||||
@ -98,22 +99,41 @@
|
||||
async function deleteApplication(name: string) {
|
||||
const sure = confirm($t('application.confirm_to_delete', { name }));
|
||||
if (sure) {
|
||||
loading = true;
|
||||
$status.application.initialLoading = true;
|
||||
try {
|
||||
await del(`/applications/${id}`, { id });
|
||||
return await goto(`/applications`);
|
||||
} catch (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() {
|
||||
try {
|
||||
loading = true;
|
||||
$status.application.initialLoading = true;
|
||||
$status.application.loading = true;
|
||||
await post(`/applications/${id}/stop`, {});
|
||||
return window.location.reload();
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
$status.application.initialLoading = false;
|
||||
$status.application.loading = false;
|
||||
await getStatus();
|
||||
}
|
||||
}
|
||||
async function getStatus() {
|
||||
@ -152,209 +172,136 @@
|
||||
</script>
|
||||
|
||||
<nav class="nav-side">
|
||||
{#if loading}
|
||||
<Loading fullscreen cover />
|
||||
{:else}
|
||||
{#if $location}
|
||||
<a
|
||||
href={$location}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||
<line x1="10" y1="14" x2="20" y2="4" />
|
||||
<polyline points="15 4 20 4 20 9" />
|
||||
</svg></a
|
||||
{#if $location}
|
||||
<a
|
||||
href={$location}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||
<line x1="10" y1="14" x2="20" y2="4" />
|
||||
<polyline points="15 4 20 4 20 9" />
|
||||
</svg></a
|
||||
>
|
||||
<div class="border border-coolgray-500 h-8" />
|
||||
{/if}
|
||||
|
||||
{/if}
|
||||
|
||||
{#if $status.application.isExited}
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center text-error"
|
||||
data-tip="Application exited with an error!"
|
||||
sveltekit:prefetch
|
||||
{#if $status.application.isExited}
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center text-error"
|
||||
data-tip="Application exited with an error!"
|
||||
sveltekit:prefetch
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentcolor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentcolor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
|
||||
/>
|
||||
<line x1="12" y1="8" x2="12" y2="12" />
|
||||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||
</svg>
|
||||
</a>
|
||||
{/if}
|
||||
{#if $status.application.initialLoading}
|
||||
<button
|
||||
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
|
||||
/>
|
||||
<line x1="12" y1="8" x2="12" y2="12" />
|
||||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||
</svg>
|
||||
</a>
|
||||
{/if}
|
||||
{#if $status.application.initialLoading}
|
||||
<button
|
||||
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
||||
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
||||
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
||||
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
||||
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
||||
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
||||
</svg>
|
||||
</button>
|
||||
{:else if $status.application.isRunning}
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
||||
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
||||
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
||||
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
||||
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
||||
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
||||
</svg>
|
||||
</button>
|
||||
{:else if $status.application.isRunning}
|
||||
<button
|
||||
on:click={stopApplication}
|
||||
type="submit"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-error"
|
||||
data-tip={$appSession.isAdmin
|
||||
? 'Stop'
|
||||
: $t('application.permission_denied_stop_application')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<rect x="6" y="5" width="4" height="14" rx="1" />
|
||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||
</svg>
|
||||
</button>
|
||||
<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
|
||||
on:click={stopApplication}
|
||||
type="submit"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-error"
|
||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2"
|
||||
data-tip={$appSession.isAdmin
|
||||
? $t('application.stop_application')
|
||||
: $t('application.permission_denied_stop_application')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<rect x="6" y="5" width="4" height="14" rx="1" />
|
||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||
</svg>
|
||||
</button>
|
||||
<form on:submit|preventDefault={() => handleDeploySubmit(true)}>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2"
|
||||
data-tip={$appSession.isAdmin
|
||||
? 'Force Rebuild Application'
|
||||
: 'You do not have permission to rebuild application.'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M16.3 5h.7a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h5l-2.82 -2.82m0 5.64l2.82 -2.82"
|
||||
transform="rotate(-45 12 12)"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{:else}
|
||||
<form on:submit|preventDefault={() => handleDeploySubmit(false)}>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-success"
|
||||
data-tip={$appSession.isAdmin
|
||||
? 'Deploy'
|
||||
: 'You do not have permission to deploy application.'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<div class="border border-coolgray-500 h-8" />
|
||||
<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"
|
||||
? 'Force Rebuild without cache'
|
||||
: 'You do not have permission to rebuild application.'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -368,24 +315,21 @@
|
||||
>
|
||||
<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"
|
||||
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" />
|
||||
<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`}
|
||||
>
|
||||
</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"
|
||||
data-tip="Persistent Storages"
|
||||
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"
|
||||
@ -398,92 +342,124 @@
|
||||
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" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</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}
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/previews` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-orange-500 rounded"
|
||||
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||
<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"
|
||||
>
|
||||
<button
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||
data-tip="Previews"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<circle cx="7" cy="18" r="2" />
|
||||
<circle cx="7" cy="6" r="2" />
|
||||
<circle cx="17" cy="12" r="2" />
|
||||
<line x1="7" y1="8" x2="7" y2="16" />
|
||||
<path d="M7 8a4 4 0 0 0 4 4h4" />
|
||||
</svg></button
|
||||
></a
|
||||
<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
|
||||
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}
|
||||
<div class="border border-coolgray-500 h-8" />
|
||||
<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={!$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
|
||||
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
|
||||
href={!$disabledButton ? `/applications/${id}/previews` : 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`}
|
||||
class="hover:text-orange-500 rounded"
|
||||
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||
>
|
||||
<button
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||
data-tip="Build Logs"
|
||||
data-tip="Previews"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -492,31 +468,94 @@
|
||||
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
|
||||
<circle cx="7" cy="18" r="2" />
|
||||
<circle cx="7" cy="6" r="2" />
|
||||
<circle cx="17" cy="12" r="2" />
|
||||
<line x1="7" y1="8" x2="7" y2="16" />
|
||||
<path d="M7 8a4 4 0 0 0 4 4h4" />
|
||||
</svg></button
|
||||
></a
|
||||
>
|
||||
<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}
|
||||
<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>
|
||||
<slot />
|
||||
|
@ -60,11 +60,9 @@
|
||||
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
||||
import { appSession, status, disabledButton } from '$lib/store';
|
||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
const { id } = $page.params;
|
||||
|
||||
let loading = false;
|
||||
let statusInterval: any = false;
|
||||
|
||||
$disabledButton = !$appSession.isAdmin;
|
||||
@ -72,36 +70,41 @@
|
||||
async function deleteDatabase() {
|
||||
const sure = confirm(`Are you sure you would like to delete '${database.name}'?`);
|
||||
if (sure) {
|
||||
loading = true;
|
||||
$status.database.initialLoading = true;
|
||||
try {
|
||||
await del(`/databases/${database.id}`, { id: database.id });
|
||||
return await goto('/databases');
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
$status.database.initialLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
async function stopDatabase() {
|
||||
const sure = confirm($t('database.confirm_stop', { name: database.name }));
|
||||
if (sure) {
|
||||
loading = true;
|
||||
$status.database.initialLoading = true;
|
||||
try {
|
||||
await post(`/databases/${database.id}/stop`, {});
|
||||
return window.location.reload();
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
$status.database.initialLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
async function startDatabase() {
|
||||
loading = true;
|
||||
$status.database.initialLoading = true;
|
||||
$status.database.loading = true;
|
||||
try {
|
||||
await post(`/databases/${database.id}/start`, {});
|
||||
return window.location.reload();
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
$status.database.initialLoading = false;
|
||||
$status.database.loading = false;
|
||||
await getStatus();
|
||||
}
|
||||
}
|
||||
async function getStatus() {
|
||||
@ -137,120 +140,36 @@
|
||||
|
||||
{#if id !== 'new'}
|
||||
<nav class="nav-side">
|
||||
{#if loading}
|
||||
<Loading fullscreen cover />
|
||||
{:else}
|
||||
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
|
||||
{#if $status.database.isExited}
|
||||
<a
|
||||
href={!$disabledButton ? `/databases/${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
|
||||
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
|
||||
{#if $status.database.isExited}
|
||||
<a
|
||||
href={!$disabledButton ? `/databases/${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
|
||||
>
|
||||
<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
|
||||
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.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}
|
||||
<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}
|
||||
<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}`}
|
||||
>
|
||||
{#if $status.database.initialLoading}
|
||||
<button
|
||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm disabled:text-red-500"
|
||||
data-tip={$t('application.configurations')}
|
||||
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"
|
||||
@ -263,34 +182,27 @@
|
||||
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`}
|
||||
>
|
||||
<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
|
||||
disabled={!$status.database.isRunning}
|
||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||
data-tip={$t('database.logs')}
|
||||
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="h-6 w-6"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -299,25 +211,112 @@
|
||||
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
|
||||
>
|
||||
<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}
|
||||
<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>
|
||||
{/if}
|
||||
<slot />
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
export let name = '';
|
||||
export let value = '';
|
||||
export let isNewSecret = false;
|
||||
|
@ -71,4 +71,8 @@
|
||||
<a href="https://searxng.org" target="_blank">
|
||||
<Icons.Searxng />
|
||||
</a>
|
||||
{:else if service.type === 'weblate'}
|
||||
<a href="https://weblate.org" target="_blank">
|
||||
<Icons.Weblate />
|
||||
</a>
|
||||
{/if}
|
||||
|
@ -1,17 +1,51 @@
|
||||
<script lang="ts">
|
||||
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 { t } from '$lib/translations';
|
||||
import { post } from '$lib/api';
|
||||
import { page } from '$app/stores';
|
||||
import { errorNotification } from '$lib/common';
|
||||
export let service: any;
|
||||
function toggleEmailSmtpUseTls() {
|
||||
service.glitchTip.emailSmtpUseTls = !service.glitchTip.emailSmtpUseTls;
|
||||
}
|
||||
function toggleEmailSmtpUseSsl() {
|
||||
service.glitchTip.emailSmtpUseSsl = !service.glitchTip.emailSmtpUseSsl;
|
||||
}
|
||||
function toggleEnableOpenUserRegistration() {
|
||||
service.glitchTip.enableOpenUserRegistration = !service.glitchTip.enableOpenUserRegistration;
|
||||
|
||||
const { id } = $page.params;
|
||||
let loading = false;
|
||||
|
||||
async function changeSettings(name: any) {
|
||||
if (loading || $status.service.isRunning) return;
|
||||
|
||||
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>
|
||||
|
||||
@ -19,76 +53,33 @@
|
||||
<div class="title">GlitchTip</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">
|
||||
<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}
|
||||
on:click={toggleEnableOpenUserRegistration}
|
||||
title={'Enable Open User Registration'}
|
||||
description={''}
|
||||
/>
|
||||
/> -->
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-1 py-2 font-bold">
|
||||
<div class="subtitle">Email settings</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">
|
||||
<Setting
|
||||
bind:setting={service.glitchTip.emailSmtpUseTls}
|
||||
on:click={toggleEmailSmtpUseTls}
|
||||
title={'SMTP Use TLS'}
|
||||
{loading}
|
||||
disabled={$status.service.isRunning}
|
||||
on:click={() => changeSettings('emailSmtpUseTls')}
|
||||
title="Use TLS for SMTP"
|
||||
description={''}
|
||||
/>
|
||||
</div>
|
||||
@ -96,32 +87,84 @@
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<Setting
|
||||
bind:setting={service.glitchTip.emailSmtpUseSsl}
|
||||
on:click={toggleEmailSmtpUseSsl}
|
||||
title={'SMTP Use SSL'}
|
||||
{loading}
|
||||
disabled={$status.service.isRunning}
|
||||
on:click={() => changeSettings('emailSmtpUseSsl')}
|
||||
title="Use SSL for SMTP"
|
||||
description={''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="emailBackend" class="text-base font-bold text-stone-100">Email Backend</label>
|
||||
<CopyPasswordField name="emailBackend" id="emailBackend" value={service.glitchTip.emailBackend} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mailgunApiKey" class="text-base font-bold text-stone-100">Mailgun API Key</label>
|
||||
<label for="defaultEmailFrom">Default Email From</label>
|
||||
<CopyPasswordField
|
||||
name="mailgunApiKey"
|
||||
id="mailgunApiKey"
|
||||
value={service.glitchTip.mailgunApiKey}
|
||||
required
|
||||
name="defaultEmailFrom"
|
||||
id="defaultEmailFrom"
|
||||
bind:value={service.glitchTip.defaultEmailFrom}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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
|
||||
name="sendgridApiKey"
|
||||
id="sendgridApiKey"
|
||||
value={service.glitchTip.sendgridApiKey}
|
||||
bind:value={service.glitchTip.sendgridApiKey}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -130,35 +173,31 @@
|
||||
</div>
|
||||
|
||||
<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
|
||||
name="defaultEmail"
|
||||
id="defaultEmail"
|
||||
value={service.glitchTip.defaultEmail}
|
||||
bind:value={service.glitchTip.defaultEmail}
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="defaultUsername" class="text-base font-bold text-stone-100"
|
||||
>{$t('forms.username')}</label
|
||||
>
|
||||
<label for="defaultUsername">{$t('forms.username')}</label>
|
||||
<CopyPasswordField
|
||||
name="defaultUsername"
|
||||
id="defaultUsername"
|
||||
value={service.glitchTip.defaultUsername}
|
||||
bind:value={service.glitchTip.defaultUsername}
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="defaultPassword" class="text-base font-bold text-stone-100"
|
||||
>{$t('forms.password')}</label
|
||||
>
|
||||
<label for="defaultPassword">{$t('forms.password')}</label>
|
||||
<CopyPasswordField
|
||||
name="defaultPassword"
|
||||
id="defaultPassword"
|
||||
value={service.glitchTip.defaultPassword}
|
||||
bind:value={service.glitchTip.defaultPassword}
|
||||
readonly
|
||||
disabled
|
||||
isPasswordField
|
||||
@ -170,38 +209,32 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="postgresqlUser" class="text-base font-bold text-stone-100"
|
||||
>{$t('forms.username')}</label
|
||||
>
|
||||
<label for="postgresqlUser">{$t('forms.username')}</label>
|
||||
<CopyPasswordField
|
||||
name="postgresqlUser"
|
||||
id="postgresqlUser"
|
||||
value={service.glitchTip.postgresqlUser}
|
||||
bind:value={service.glitchTip.postgresqlUser}
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="postgresqlPassword" class="text-base font-bold text-stone-100"
|
||||
>{$t('forms.password')}</label
|
||||
>
|
||||
<label for="postgresqlPassword">{$t('forms.password')}</label>
|
||||
<CopyPasswordField
|
||||
id="postgresqlPassword"
|
||||
isPasswordField
|
||||
readonly
|
||||
disabled
|
||||
name="postgresqlPassword"
|
||||
value={service.glitchTip.postgresqlPassword}
|
||||
bind:value={service.glitchTip.postgresqlPassword}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="postgresqlDatabase" class="text-base font-bold text-stone-100"
|
||||
>{$t('index.database')}</label
|
||||
>
|
||||
<label for="postgresqlDatabase">{$t('index.database')}</label>
|
||||
<CopyPasswordField
|
||||
name="postgresqlDatabase"
|
||||
id="postgresqlDatabase"
|
||||
value={service.glitchTip.postgresqlDatabase}
|
||||
bind:value={service.glitchTip.postgresqlDatabase}
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
|
@ -30,6 +30,7 @@
|
||||
import Appwrite from './_Appwrite.svelte';
|
||||
import Moodle from './_Moodle.svelte';
|
||||
import Searxng from './_Searxng.svelte';
|
||||
import Weblate from './_Weblate.svelte';
|
||||
|
||||
const { id } = $page.params;
|
||||
$: isDisabled =
|
||||
@ -405,6 +406,8 @@
|
||||
<GlitchTip bind:service />
|
||||
{:else if service.type === 'searxng'}
|
||||
<Searxng bind:service />
|
||||
{:else if service.type === 'weblate'}
|
||||
<Weblate bind:service />
|
||||
{/if}
|
||||
</div>
|
||||
</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">
|
||||
import { page } from '$app/stores';
|
||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
import { del, get, post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
import { t } from '$lib/translations';
|
||||
@ -74,13 +73,12 @@
|
||||
!service.version ||
|
||||
!service.type;
|
||||
|
||||
let loading = false;
|
||||
let statusInterval: any;
|
||||
|
||||
async function deleteService() {
|
||||
const sure = confirm($t('application.confirm_to_delete', { name: service.name }));
|
||||
if (sure) {
|
||||
loading = true;
|
||||
$status.service.initialLoading = true;
|
||||
try {
|
||||
if (service.type && $status.service.isRunning)
|
||||
await post(`/services/${service.id}/${service.type}/stop`, {});
|
||||
@ -89,31 +87,36 @@
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
$status.service.initialLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
async function stopService() {
|
||||
const sure = confirm($t('database.confirm_stop', { name: service.name }));
|
||||
if (sure) {
|
||||
loading = true;
|
||||
$status.service.initialLoading = true;
|
||||
$status.service.loading = true;
|
||||
try {
|
||||
await post(`/services/${service.id}/${service.type}/stop`, {});
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
$status.service.initialLoading = false;
|
||||
$status.service.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
async function startService() {
|
||||
loading = true;
|
||||
$status.service.initialLoading = true;
|
||||
$status.service.loading = true
|
||||
try {
|
||||
await post(`/services/${service.id}/${service.type}/start`, {});
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
$status.service.initialLoading = false;
|
||||
$status.service.loading = false;
|
||||
await getStatus();
|
||||
}
|
||||
}
|
||||
async function getStatus() {
|
||||
@ -146,269 +149,265 @@
|
||||
</script>
|
||||
|
||||
<nav class="nav-side">
|
||||
{#if loading}
|
||||
<Loading fullscreen cover />
|
||||
{:else}
|
||||
{#if service.type && service.destinationDockerId && service.version}
|
||||
{#if $location}
|
||||
<a
|
||||
href={$location}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||
<line x1="10" y1="14" x2="20" y2="4" />
|
||||
<polyline points="15 4 20 4 20 9" />
|
||||
</svg></a
|
||||
{#if service.type && service.destinationDockerId && service.version}
|
||||
{#if $location}
|
||||
<a
|
||||
href={$location}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<div class="border border-stone-700 h-8" />
|
||||
{/if}
|
||||
{#if $status.service.isExited}
|
||||
<a
|
||||
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
|
||||
>
|
||||
<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}
|
||||
<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" />
|
||||
{/if}
|
||||
{#if service.type && service.destinationDockerId && service.version}
|
||||
{#if $status.service.isExited}
|
||||
<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
|
||||
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="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentcolor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<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
|
||||
>
|
||||
<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}
|
||||
<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
|
||||
{#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" />
|
||||
{/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}
|
||||
<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>
|
||||
<slot />
|
||||
|
@ -25,14 +25,47 @@
|
||||
import { page } from '$app/stores';
|
||||
import { get } from '$lib/api';
|
||||
import { t } from '$lib/translations';
|
||||
import pLimit from 'p-limit';
|
||||
import ServiceLinks from './_ServiceLinks.svelte';
|
||||
import { addToast } from '$lib/store';
|
||||
import { saveSecret } from './utils';
|
||||
const limit = pLimit(1);
|
||||
|
||||
const { id } = $page.params;
|
||||
let batchSecrets = '';
|
||||
|
||||
async function refreshSecrets() {
|
||||
const data = await get(`/services/${id}/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>
|
||||
|
||||
<div
|
||||
@ -93,4 +126,9 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</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>
|
||||
|
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",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "3.8.9",
|
||||
"version": "3.9.0",
|
||||
"license": "Apache-2.0",
|
||||
"repository": "github:coollabsio/coolify",
|
||||
"scripts": {
|
||||
"oc": "opencollective-setup",
|
||||
"translate": "pnpm run --filter i18n-converter translate",
|
||||
"db:studio": "pnpm run --filter api db:studio",
|
||||
"db:push": "pnpm run --filter api db:push",
|
||||
"db:seed": "pnpm run --filter api db:seed",
|
||||
|
@ -23,7 +23,7 @@ importers:
|
||||
'@fastify/static': 6.5.0
|
||||
'@iarna/toml': 2.2.5
|
||||
'@ladjs/graceful': 3.0.2
|
||||
'@prisma/client': 4.2.1
|
||||
'@prisma/client': 3.15.2
|
||||
'@types/node': 18.7.13
|
||||
'@types/node-os-utils': 1.3.0
|
||||
'@typescript-eslint/eslint-plugin': 5.35.1
|
||||
@ -56,7 +56,7 @@ importers:
|
||||
p-all: 4.0.0
|
||||
p-throttle: 5.0.0
|
||||
prettier: 2.7.1
|
||||
prisma: 4.2.1
|
||||
prisma: 3.15.2
|
||||
public-ip: 6.0.1
|
||||
rimraf: 3.0.2
|
||||
ssh-config: 4.1.6
|
||||
@ -74,7 +74,7 @@ importers:
|
||||
'@fastify/static': 6.5.0
|
||||
'@iarna/toml': 2.2.5
|
||||
'@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
|
||||
bcryptjs: 2.4.3
|
||||
bree: 9.1.2
|
||||
@ -112,11 +112,23 @@ importers:
|
||||
eslint-plugin-prettier: 4.2.1_tgumt6uwl2md3n6uqnggd6wvce
|
||||
nodemon: 2.0.19
|
||||
prettier: 2.7.1
|
||||
prisma: 4.2.1
|
||||
prisma: 3.15.2
|
||||
rimraf: 3.0.2
|
||||
tsconfig-paths: 4.1.0
|
||||
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:
|
||||
specifiers:
|
||||
'@playwright/test': 1.25.1
|
||||
@ -446,9 +458,9 @@ packages:
|
||||
playwright-core: 1.25.1
|
||||
dev: true
|
||||
|
||||
/@prisma/client/4.2.1_prisma@4.2.1:
|
||||
resolution: {integrity: sha512-PZBkY60+k5oix+e6IUfl3ub8TbRLNsPLdfWrdy2eh80WcHTaT+/UfvXf/B7gXedH7FRtbPFHZXk1hZenJiJZFQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
/@prisma/client/3.15.2_prisma@3.15.2:
|
||||
resolution: {integrity: sha512-ErqtwhX12ubPhU4d++30uFY/rPcyvjk+mdifaZO5SeM21zS3t4jQrscy8+6IyB0GIYshl5ldTq6JSBo1d63i8w==}
|
||||
engines: {node: '>=12.6'}
|
||||
requiresBuild: true
|
||||
peerDependencies:
|
||||
prisma: '*'
|
||||
@ -456,16 +468,16 @@ packages:
|
||||
prisma:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@prisma/engines-version': 4.2.0-33.2920a97877e12e055c1333079b8d19cee7f33826
|
||||
prisma: 4.2.1
|
||||
'@prisma/engines-version': 3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e
|
||||
prisma: 3.15.2
|
||||
dev: false
|
||||
|
||||
/@prisma/engines-version/4.2.0-33.2920a97877e12e055c1333079b8d19cee7f33826:
|
||||
resolution: {integrity: sha512-tktkqdiwqE4QhmE088boPt+FwPj1Jub/zk+5F6sEfcRHzO5yz9jyMD5HFVtiwxZPLx/8Xg9ElnuTi8E5lWVQFQ==}
|
||||
/@prisma/engines-version/3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e:
|
||||
resolution: {integrity: sha512-e3k2Vd606efd1ZYy2NQKkT4C/pn31nehyLhVug6To/q8JT8FpiMrDy7zmm3KLF0L98NOQQcutaVtAPhzKhzn9w==}
|
||||
dev: false
|
||||
|
||||
/@prisma/engines/4.2.1:
|
||||
resolution: {integrity: sha512-0KqBwREUOjBiHwITsQzw2DWfLHjntvbqzGRawj4sBMnIiL5CXwyDUKeHOwXzKMtNr1rEjxEsypM14g0CzLRK3g==}
|
||||
/@prisma/engines/3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e:
|
||||
resolution: {integrity: sha512-NHlojO1DFTsSi3FtEleL9QWXeSF/UjhCW0fgpi7bumnNZ4wj/eQ+BJJ5n2pgoOliTOGv9nX2qXvmHap7rJMNmg==}
|
||||
requiresBuild: true
|
||||
|
||||
/@rollup/pluginutils/4.2.1:
|
||||
@ -2101,6 +2113,11 @@ packages:
|
||||
safe-buffer: 5.2.1
|
||||
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:
|
||||
resolution: {integrity: sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==}
|
||||
engines: {node: '>=8'}
|
||||
@ -2448,6 +2465,11 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/dotenv/16.0.2:
|
||||
resolution: {integrity: sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/dotenv/8.6.0:
|
||||
resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==}
|
||||
engines: {node: '>=10'}
|
||||
@ -2481,6 +2503,12 @@ packages:
|
||||
engines: {node: '>= 0.8'}
|
||||
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:
|
||||
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
|
||||
dependencies:
|
||||
@ -3560,6 +3588,15 @@ packages:
|
||||
get-intrinsic: 1.1.1
|
||||
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:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@ -3759,6 +3796,13 @@ packages:
|
||||
safer-buffer: 2.1.2
|
||||
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:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
dev: false
|
||||
@ -4264,6 +4308,10 @@ packages:
|
||||
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
|
||||
dev: false
|
||||
|
||||
/lodash.get/4.4.2:
|
||||
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
|
||||
dev: false
|
||||
|
||||
/lodash.includes/4.3.0:
|
||||
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
||||
dev: false
|
||||
@ -4535,6 +4583,12 @@ packages:
|
||||
engines: {node: '>= 6.13.0'}
|
||||
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:
|
||||
resolution: {integrity: sha512-fvnX9tZbR7WfCG5BAy3yO/nCLyjVWD6MghEq0z5FDfN+ZXpLWNITBdbifxQkQ25ebr16G0N7eRWJisOcMEHG3Q==}
|
||||
dev: false
|
||||
@ -5071,13 +5125,13 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/prisma/4.2.1:
|
||||
resolution: {integrity: sha512-HuYqnTDgH8atjPGtYmY0Ql9XrrJnfW7daG1PtAJRW0E6gJxc50lY3vrIDn0yjMR3TvRlypjTcspQX8DT+xD4Sg==}
|
||||
engines: {node: '>=14.17'}
|
||||
/prisma/3.15.2:
|
||||
resolution: {integrity: sha512-nMNSMZvtwrvoEQ/mui8L/aiCLZRCj5t6L3yujKpcDhIPk7garp8tL4nMx2+oYsN0FWBacevJhazfXAbV1kfBzA==}
|
||||
engines: {node: '>=12.6'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@prisma/engines': 4.2.1
|
||||
'@prisma/engines': 3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e
|
||||
|
||||
/private/0.1.8:
|
||||
resolution: {integrity: sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==}
|
||||
@ -5210,6 +5264,13 @@ packages:
|
||||
abort-controller: 3.0.0
|
||||
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:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
Loading…
Reference in New Issue
Block a user