work-work
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ServiceSetting" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"value" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "ServiceSetting_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "ServiceSetting_serviceId_name_key" ON "ServiceSetting"("serviceId", "name");
|
@@ -398,6 +398,7 @@ model Service {
|
|||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
persistentStorage ServicePersistentStorage[]
|
persistentStorage ServicePersistentStorage[]
|
||||||
serviceSecret ServiceSecret[]
|
serviceSecret ServiceSecret[]
|
||||||
|
serviceSetting ServiceSetting[]
|
||||||
teams Team[]
|
teams Team[]
|
||||||
|
|
||||||
fider Fider?
|
fider Fider?
|
||||||
@@ -417,6 +418,18 @@ model Service {
|
|||||||
taiga Taiga?
|
taiga Taiga?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model ServiceSetting {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
serviceId String
|
||||||
|
name String
|
||||||
|
value String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
|
||||||
|
@@unique([serviceId, name])
|
||||||
|
}
|
||||||
|
|
||||||
model PlausibleAnalytics {
|
model PlausibleAnalytics {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
email String?
|
email String?
|
||||||
|
@@ -1454,7 +1454,6 @@ export async function getServiceFromDB({
|
|||||||
}
|
}
|
||||||
let { type } = body;
|
let { type } = body;
|
||||||
type = fixType(type);
|
type = fixType(type);
|
||||||
|
|
||||||
if (body?.serviceSecret.length > 0) {
|
if (body?.serviceSecret.length > 0) {
|
||||||
body.serviceSecret = body.serviceSecret.map((s) => {
|
body.serviceSecret = body.serviceSecret.map((s) => {
|
||||||
s.value = decrypt(s.value);
|
s.value = decrypt(s.value);
|
||||||
@@ -1462,7 +1461,7 @@ export async function getServiceFromDB({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
body[type] = { ...body[type], ...getUpdateableFields(type, body[type]) };
|
// body[type] = { ...body[type], ...getUpdateableFields(type, body[type]) };
|
||||||
return { ...body, settings };
|
return { ...body, settings };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@ export const includeServices: any = {
|
|||||||
destinationDocker: true,
|
destinationDocker: true,
|
||||||
persistentStorage: true,
|
persistentStorage: true,
|
||||||
serviceSecret: true,
|
serviceSecret: true,
|
||||||
|
serviceSetting: true,
|
||||||
minio: true,
|
minio: true,
|
||||||
plausibleAnalytics: true,
|
plausibleAnalytics: true,
|
||||||
vscodeserver: true,
|
vscodeserver: true,
|
||||||
@@ -362,6 +363,7 @@ export async function configureServiceType({
|
|||||||
|
|
||||||
export async function removeService({ id }: { id: string }): Promise<void> {
|
export async function removeService({ id }: { id: string }): Promise<void> {
|
||||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||||
|
await prisma.serviceSetting.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.fider.deleteMany({ where: { serviceId: id } });
|
await prisma.fider.deleteMany({ where: { serviceId: id } });
|
||||||
|
@@ -7,6 +7,7 @@ import { asyncSleep, ComposeFile, createDirectories, defaultComposeConfiguration
|
|||||||
import { defaultServiceConfigurations } from '../services';
|
import { defaultServiceConfigurations } from '../services';
|
||||||
import { OnlyId } from '../../types';
|
import { OnlyId } from '../../types';
|
||||||
import templates from '../templates'
|
import templates from '../templates'
|
||||||
|
import { parseAndFindServiceTemplates } from '../../routes/api/v1/services/handlers';
|
||||||
|
|
||||||
// export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
// export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
||||||
// try {
|
// try {
|
||||||
@@ -691,36 +692,36 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
|
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort, persistentStorage } =
|
const { type, version, destinationDockerId, destinationDocker, serviceSecret,serviceSetting, exposePort, persistentStorage } =
|
||||||
service;
|
service;
|
||||||
|
|
||||||
let template = templates.find((template) => template.name === type);
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
|
const template: any = await parseAndFindServiceTemplates(service, workdir, true)
|
||||||
template = JSON.parse(JSON.stringify(template).replaceAll('$$id', id).replaceAll('$$fqdn', service.fqdn))
|
|
||||||
|
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
|
|
||||||
const config = {};
|
const config = {};
|
||||||
for (const service in template.services) {
|
for (const service in template.services) {
|
||||||
|
console.log(template.services[service])
|
||||||
config[service] = {
|
config[service] = {
|
||||||
container_name: id,
|
container_name: service,
|
||||||
image: template.services[service].image.replace('$$core_version', version),
|
image: template.services[service].image,
|
||||||
expose: template.services[service].ports,
|
expose: template.services[service].ports,
|
||||||
// ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
// ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
volumes: template.services[service].volumes,
|
volumes: template.services[service].volumes,
|
||||||
environment: {},
|
environment: template.services[service].environment,
|
||||||
depends_on: template.services[service].depends_on,
|
depends_on: template.services[service].depends_on,
|
||||||
ulimits: template.services[service].ulimits,
|
ulimits: template.services[service].ulimits,
|
||||||
labels: makeLabelForServices(type),
|
labels: makeLabelForServices(type),
|
||||||
...defaultComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
if (serviceSecret.length > 0) {
|
|
||||||
serviceSecret.forEach((secret) => {
|
// Generate files for builds
|
||||||
config[service].environment[secret.name] = secret.value;
|
if (template.services[service].build) {
|
||||||
});
|
if (template.services[service]?.extras?.files?.length > 0) {
|
||||||
|
console.log(template.services[service]?.extras?.files)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
|
||||||
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
|
|
||||||
const composeFile: ComposeFile = {
|
const composeFile: ComposeFile = {
|
||||||
|
@@ -4,10 +4,10 @@ export default [
|
|||||||
"serviceDefaultVersion": "0.198.1",
|
"serviceDefaultVersion": "0.198.1",
|
||||||
"name": "n8n",
|
"name": "n8n",
|
||||||
"displayName": "n8n.io",
|
"displayName": "n8n.io",
|
||||||
"isOfficial": true,
|
|
||||||
"description": "n8n is a free and open node based Workflow Automation Tool.",
|
"description": "n8n is a free and open node based Workflow Automation Tool.",
|
||||||
"services": {
|
"services": {
|
||||||
"$$id": {
|
"$$id": {
|
||||||
|
"name": "N8n",
|
||||||
"documentation": "Taken from https://hub.docker.com/r/n8nio/n8n",
|
"documentation": "Taken from https://hub.docker.com/r/n8nio/n8n",
|
||||||
"depends_on": [],
|
"depends_on": [],
|
||||||
"image": "n8nio/n8n:$$core_version",
|
"image": "n8nio/n8n:$$core_version",
|
||||||
@@ -17,24 +17,31 @@ export default [
|
|||||||
"/var/run/docker.sock:/var/run/docker.sock"
|
"/var/run/docker.sock:/var/run/docker.sock"
|
||||||
],
|
],
|
||||||
"environment": [
|
"environment": [
|
||||||
"WEBHOOK_URL=$$fqdn"
|
"WEBHOOK_URL=$$config_webhook_url"
|
||||||
],
|
],
|
||||||
"ports": [
|
"ports": [
|
||||||
"5678"
|
"5678"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"variables": []
|
"variables": [
|
||||||
|
{
|
||||||
|
"id": "$$config_webhook_url",
|
||||||
|
"name": "WEBHOOK_URL",
|
||||||
|
"label": "Webhook URL",
|
||||||
|
"defaultValue": "$$generate_fqdn",
|
||||||
|
"description": "",
|
||||||
|
}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"templateVersion": "1.0.0",
|
"templateVersion": "1.0.0",
|
||||||
"serviceDefaultVersion": "stable",
|
"serviceDefaultVersion": "stable",
|
||||||
"name": "plausibleanalytics",
|
"name": "plausibleanalytics",
|
||||||
"displayName": "PlausibleAnalytics",
|
"displayName": "PlausibleAnalytics",
|
||||||
"isOfficial": true,
|
|
||||||
"description": "Plausible is a lightweight and open-source website analytics tool.",
|
"description": "Plausible is a lightweight and open-source website analytics tool.",
|
||||||
"services": {
|
"services": {
|
||||||
"$$id": {
|
"$$id": {
|
||||||
|
"name": "Plausible Analytics",
|
||||||
"documentation": "Taken from https://plausible.io/",
|
"documentation": "Taken from https://plausible.io/",
|
||||||
"command": ['sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run"'],
|
"command": ['sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run"'],
|
||||||
"depends_on": [
|
"depends_on": [
|
||||||
@@ -43,31 +50,33 @@ export default [
|
|||||||
],
|
],
|
||||||
"image": "plausible/analytics:$$core_version",
|
"image": "plausible/analytics:$$core_version",
|
||||||
"environment": [
|
"environment": [
|
||||||
"ADMIN_USER_EMAIL=$$secret_email",
|
"ADMIN_USER_EMAIL=$$config_admin_user_email",
|
||||||
"ADMIN_USER_NAME=$$secret_name",
|
"ADMIN_USER_NAME=$$config_admin_user_name",
|
||||||
"ADMIN_USER_PASSWORD=$$secret_password",
|
"ADMIN_USER_PASSWORD=$$secret_admin_user_password",
|
||||||
"BASE_URL=$$fqdn",
|
"BASE_URL=$$config_base_url",
|
||||||
"SECRET_KEY_BASE=$$secret_key_base",
|
"SECRET_KEY_BASE=$$secret_secret_key_base",
|
||||||
"DISABLE_AUTH=$$secret_disable_auth",
|
"DISABLE_AUTH=$$config_disable_auth",
|
||||||
"DISABLE_REGISTRATION=$$secret_disable_registration",
|
"DISABLE_REGISTRATION=$$config_disable_registration",
|
||||||
"DATABASE_URL=postgresql://$$secret_postgresql_username:$$secret_postgresql_password@$$id-postgresql:5432/$$secret_postgresql_database",
|
"DATABASE_URL=$$secret_database_url",
|
||||||
"CLICKHOUSE_DATABASE_URL=http://$$id-clickhouse:8123/plausible",
|
"CLICKHOUSE_DATABASE_URL=$$secret_clickhouse_database_url",
|
||||||
],
|
],
|
||||||
"ports": [
|
"ports": [
|
||||||
"8000"
|
"8000"
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"$$id-postgresql": {
|
"$$id-postgresql": {
|
||||||
|
"name": "PostgreSQL",
|
||||||
"documentation": "Taken from https://plausible.io/",
|
"documentation": "Taken from https://plausible.io/",
|
||||||
"image": "bitnami/postgresql:13.2.0",
|
"image": "bitnami/postgresql:13.2.0",
|
||||||
"environment": [
|
"environment": [
|
||||||
"POSTGRESQL_PASSWORD=$$secret_postgresql_password",
|
"POSTGRESQL_PASSWORD=$$secret_postgresql_password",
|
||||||
"POSTGRESQL_USERNAME=$$secret_postgresql_username",
|
"POSTGRESQL_USERNAME=$$config_postgresql_username",
|
||||||
"POSTGRESQL_DATABASE=$$secret_postgresql_database",
|
"POSTGRESQL_DATABASE=$$config_postgresql_database",
|
||||||
],
|
],
|
||||||
|
|
||||||
},
|
},
|
||||||
"$$id-clickhouse": {
|
"$$id-clickhouse": {
|
||||||
|
"name": "Clickhouse",
|
||||||
"documentation": "Taken from https://plausible.io/",
|
"documentation": "Taken from https://plausible.io/",
|
||||||
"build": "$$workdir",
|
"build": "$$workdir",
|
||||||
"image": "yandex/clickhouse-server:21.3.2.5",
|
"image": "yandex/clickhouse-server:21.3.2.5",
|
||||||
@@ -102,69 +111,98 @@ export default [
|
|||||||
},
|
},
|
||||||
"variables": [
|
"variables": [
|
||||||
{
|
{
|
||||||
"id": "$$secret_email",
|
"id": "$$config_base_url",
|
||||||
"label": "Admin Email",
|
"name": "BASE_URL",
|
||||||
|
"label": "Base URL",
|
||||||
|
"defaultValue": "$$generate_fqdn",
|
||||||
|
"description": "You must set this to the FQDN of the Plausible Analytics instance. This is used to generate the links to the Plausible Analytics instance.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "$$secret_database_url",
|
||||||
|
"name": "DATABASE_URL",
|
||||||
|
"label": "Database URL for PostgreSQL",
|
||||||
|
"defaultValue": "postgresql://$$config_postgresql_username:$$secret_postgresql_password@$$id-postgresql:5432/$$config_postgresql_database",
|
||||||
|
"description": "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "$$secret_clickhouse_database_url",
|
||||||
|
"name": "CLICKHOUSE_DATABASE_URL",
|
||||||
|
"label": "Database URL for Clickhouse",
|
||||||
|
"defaultValue": "http://$$id-clickhouse:8123/plausible",
|
||||||
|
"description": "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "$$config_admin_user_email",
|
||||||
|
"name": "ADMIN_USER_EMAIL",
|
||||||
|
"label": "Admin Email Address",
|
||||||
"defaultValue": "admin@example.com",
|
"defaultValue": "admin@example.com",
|
||||||
"description": "This is the admin email. Please change it.",
|
"description": "This is the admin email. Please change it.",
|
||||||
"validRegex": /^([^\s^\/])+$/
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "$$secret_name",
|
"id": "$$config_admin_user_name",
|
||||||
"label": "Admin Name",
|
"name": "ADMIN_USER_NAME",
|
||||||
|
"label": "Admin User Name",
|
||||||
"defaultValue": "$$generate_username",
|
"defaultValue": "$$generate_username",
|
||||||
"description": "This is the admin username. Please change it.",
|
"description": "This is the admin username. Please change it.",
|
||||||
"validRegex": /^([^\s^\/])+$/
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "$$secret_password",
|
"id": "$$secret_admin_user_password",
|
||||||
"label": "Admin Password",
|
"name": "ADMIN_USER_PASSWORD",
|
||||||
"defaultValue":"$$generate_password",
|
"showAsConfiguration": true,
|
||||||
|
"label": "Admin User Password",
|
||||||
|
"defaultValue": "$$generate_password",
|
||||||
"description": "This is the admin password. Please change it.",
|
"description": "This is the admin password. Please change it.",
|
||||||
"validRegex": /^([^\s^\/])+$/
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "$$secret_secret_key_base",
|
"id": "$$secret_secret_key_base",
|
||||||
|
"name": "SECRET_KEY_BASE",
|
||||||
"label": "Secret Key Base",
|
"label": "Secret Key Base",
|
||||||
"defaultValue":"$$generate_passphrase",
|
"defaultValue": "$$generate_passphrase",
|
||||||
"description": "",
|
"description": "",
|
||||||
"validRegex": /^([^\s^\/])+$/
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "$$secret_disable_auth",
|
"id": "$$config_disable_auth",
|
||||||
"label": "Disable Auth",
|
"name": "DISABLE_AUTH",
|
||||||
|
"label": "Disable Authentication",
|
||||||
"defaultValue": "false",
|
"defaultValue": "false",
|
||||||
"description": "",
|
"description": "",
|
||||||
"validRegex": /^([^\s^\/])+$/
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "$$secret_disable_registration",
|
"id": "$$config_disable_registration",
|
||||||
|
"name": "DISABLE_REGISTRATION",
|
||||||
"label": "Disable Registration",
|
"label": "Disable Registration",
|
||||||
"defaultValue": "true",
|
"defaultValue": "true",
|
||||||
"description": "",
|
"description": "",
|
||||||
"validRegex": /^([^\s^\/])+$/
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "$$secret_postgresql_username",
|
"id": "$$config_postgresql_username",
|
||||||
|
"name": "POSTGRESQL_USERNAME",
|
||||||
"label": "PostgreSQL Username",
|
"label": "PostgreSQL Username",
|
||||||
"defaultValue": "postgresql",
|
"defaultValue": "postgresql",
|
||||||
"description": "",
|
"description": "",
|
||||||
"validRegex": /^([^\s^\/])+$/
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "$$secret_postgresql_password",
|
"id": "$$secret_postgresql_password",
|
||||||
|
"name": "POSTGRESQL_PASSWORD",
|
||||||
"label": "PostgreSQL Password",
|
"label": "PostgreSQL Password",
|
||||||
"defaultValue": "$$generate_password",
|
"defaultValue": "$$generate_password",
|
||||||
"description": "",
|
"description": "",
|
||||||
"validRegex": /^([^\s^\/])+$/
|
|
||||||
}
|
}
|
||||||
,
|
,
|
||||||
{
|
{
|
||||||
"id": "$$secret_postgresql_database",
|
"id": "$$config_postgresql_database",
|
||||||
|
"name": "POSTGRESQL_DATABASE",
|
||||||
"label": "PostgreSQL Database",
|
"label": "PostgreSQL Database",
|
||||||
"defaultValue": "plausible",
|
"defaultValue": "plausible",
|
||||||
"description": "",
|
"description": "",
|
||||||
"validRegex": /^([^\s^\/])+$/
|
},
|
||||||
}
|
{
|
||||||
|
"id": "$$config_scriptName",
|
||||||
|
"name": "SCRIPT_NAME",
|
||||||
|
"label": "Custom Script Name",
|
||||||
|
"defaultValue": "plausible.js",
|
||||||
|
"description": "This is the default script name.",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -114,13 +114,70 @@ export async function getServiceStatus(request: FastifyRequest<OnlyId>) {
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function parseAndFindServiceTemplates(service: any) {
|
export async function parseAndFindServiceTemplates(service: any, workdir?: string, isDeploy: boolean = false) {
|
||||||
const foundTemplate = templates.find(t => t.name === service.type)
|
const foundTemplate = templates.find(t => t.name === service.type)
|
||||||
|
let parsedTemplate = {}
|
||||||
if (foundTemplate) {
|
if (foundTemplate) {
|
||||||
return JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', service.id).replaceAll('$$fqdn', service.fqdn))
|
if (!isDeploy) {
|
||||||
|
for (const [key, value] of Object.entries(foundTemplate.services)) {
|
||||||
|
const realKey = key.replace('$$id', service.id)
|
||||||
|
parsedTemplate[realKey] = {
|
||||||
|
name: value.name,
|
||||||
|
image: value.image,
|
||||||
|
environment: []
|
||||||
|
}
|
||||||
|
if (value.environment?.length > 0) {
|
||||||
|
for (const env of value.environment) {
|
||||||
|
const [envKey, envValue] = env.split('=')
|
||||||
|
const label = foundTemplate.variables.find(v => v.name === envKey)?.label
|
||||||
|
const description = foundTemplate.variables.find(v => v.name === envKey)?.description
|
||||||
|
const defaultValue = foundTemplate.variables.find(v => v.name === envKey)?.defaultValue
|
||||||
|
const showAsConfiguration = foundTemplate.variables.find(v => v.name === envKey)?.showAsConfiguration
|
||||||
|
if (envValue.startsWith('$$config') || showAsConfiguration) {
|
||||||
|
parsedTemplate[realKey].environment.push(
|
||||||
|
{ name: envKey, value: envValue, label, description, defaultValue }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsedTemplate = foundTemplate
|
||||||
|
}
|
||||||
|
// replace $$id and $$workdir
|
||||||
|
parsedTemplate = JSON.parse(JSON.stringify(parsedTemplate).replaceAll('$$id', service.id).replaceAll('$$core_version', foundTemplate.serviceDefaultVersion))
|
||||||
|
|
||||||
|
// replace $$fqdn
|
||||||
|
if (workdir) {
|
||||||
|
parsedTemplate = JSON.parse(JSON.stringify(parsedTemplate).replaceAll('$$workdir', workdir))
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace $$config
|
||||||
|
if (service.serviceSetting.length > 0) {
|
||||||
|
for (const setting of service.serviceSetting) {
|
||||||
|
const { name, value } = setting
|
||||||
|
if (service.fqdn && value === '$$generate_fqdn') {
|
||||||
|
parsedTemplate = JSON.parse(JSON.stringify(parsedTemplate).replaceAll(`$$config_${name.toLowerCase()}`, service.fqdn))
|
||||||
|
} else {
|
||||||
|
parsedTemplate = JSON.parse(JSON.stringify(parsedTemplate).replaceAll(`$$config_${name.toLowerCase()}`, value))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace $$secret
|
||||||
|
if (service.serviceSecret.length > 0) {
|
||||||
|
for (const secret of service.serviceSecret) {
|
||||||
|
const { name, value } = secret
|
||||||
|
parsedTemplate = JSON.parse(JSON.stringify(parsedTemplate).replaceAll(`$$secret_${name.toLowerCase()}`, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parsedTemplate
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getService(request: FastifyRequest<OnlyId>) {
|
export async function getService(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
@@ -129,10 +186,11 @@ export async function getService(request: FastifyRequest<OnlyId>) {
|
|||||||
if (!service) {
|
if (!service) {
|
||||||
throw { status: 404, message: 'Service not found.' }
|
throw { status: 404, message: 'Service not found.' }
|
||||||
}
|
}
|
||||||
|
const template = await parseAndFindServiceTemplates(service)
|
||||||
return {
|
return {
|
||||||
settings: await listSettings(),
|
settings: await listSettings(),
|
||||||
service,
|
service,
|
||||||
template: parseAndFindServiceTemplates(service)
|
template,
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -150,30 +208,69 @@ export async function getServiceType(request: FastifyRequest) {
|
|||||||
export async function saveServiceType(request: FastifyRequest<SaveServiceType>, reply: FastifyReply) {
|
export async function saveServiceType(request: FastifyRequest<SaveServiceType>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { name, variables = [], serviceDefaultVersion = 'latest' } = request.body;
|
const { type } = request.body;
|
||||||
if (variables.length > 0) {
|
let foundTemplate = templates.find(t => t.name === type)
|
||||||
for (const variable of variables) {
|
if (foundTemplate) {
|
||||||
const { id: variableId, defaultValue, value = null } = variable;
|
let generatedVariables = new Set()
|
||||||
|
let missingVariables = new Set()
|
||||||
|
|
||||||
|
foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id))
|
||||||
|
|
||||||
|
if (foundTemplate.variables.length > 0) {
|
||||||
|
foundTemplate.variables = foundTemplate.variables.map(variable => {
|
||||||
|
let { id: variableId } = variable;
|
||||||
if (variableId.startsWith('$$secret_')) {
|
if (variableId.startsWith('$$secret_')) {
|
||||||
const secretName = variableId.replace('$$secret_', '');
|
if (variable.defaultValue === '$$generate_password') {
|
||||||
let secretValue = defaultValue || value || null;
|
variable.value = generatePassword({});
|
||||||
if (defaultValue === '$$generate_password') {
|
} else if (variable.defaultValue === '$$generate_passphrase') {
|
||||||
secretValue = generatePassword({});
|
variable.value = cuid();
|
||||||
}
|
}
|
||||||
if (defaultValue === '$$generate_username') {
|
|
||||||
secretValue = cuid();
|
|
||||||
}
|
}
|
||||||
if (defaultValue === '$$generate_passphrase') {
|
if (variableId.startsWith('$$config_')) {
|
||||||
secretValue = cuid();
|
if (variable.defaultValue === '$$generate_username') {
|
||||||
|
variable.value = cuid();
|
||||||
|
} else {
|
||||||
|
variable.value = variable.defaultValue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (variable.value) {
|
||||||
|
generatedVariables.add(`${variableId}=${variable.value}`)
|
||||||
|
} else {
|
||||||
|
missingVariables.add(variableId)
|
||||||
|
}
|
||||||
|
return variable
|
||||||
|
})
|
||||||
|
if (missingVariables.size > 0) {
|
||||||
|
foundTemplate.variables = foundTemplate.variables.map(variable => {
|
||||||
|
if (missingVariables.has(variable.id)) {
|
||||||
|
variable.value = variable.defaultValue
|
||||||
|
for (const generatedVariable of generatedVariables) {
|
||||||
|
let [id, value] = generatedVariable.split('=')
|
||||||
|
variable.value = variable.value.replaceAll(id, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return variable
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for (const variable of foundTemplate.variables) {
|
||||||
|
if (variable.id.startsWith('$$secret_')) {
|
||||||
await prisma.serviceSecret.create({
|
await prisma.serviceSecret.create({
|
||||||
data: { name: secretName, value: encrypt(secretValue), service: { connect: { id } } }
|
data: { name: variable.name, value: encrypt(variable.value), service: { connect: { id } } }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (variable.id.startsWith('$$config_')) {
|
||||||
|
await prisma.serviceSetting.create({
|
||||||
|
data: { name: variable.name, value: variable.value, service: { connect: { id } } }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await prisma.service.update({ where: { id }, data: { type: name, version: serviceDefaultVersion } })
|
await prisma.service.update({ where: { id }, data: { type, version: foundTemplate.serviceDefaultVersion } })
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
|
} else {
|
||||||
|
throw { status: 404, message: 'Service type not found.' }
|
||||||
|
}
|
||||||
|
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
@@ -344,23 +441,30 @@ export async function checkService(request: FastifyRequest<CheckService>) {
|
|||||||
export async function saveService(request: FastifyRequest<SaveService>, reply: FastifyReply) {
|
export async function saveService(request: FastifyRequest<SaveService>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
let { name, fqdn, exposePort, type } = request.body;
|
let { name, fqdn, exposePort, type, serviceSetting } = request.body;
|
||||||
|
|
||||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||||
if (exposePort) exposePort = Number(exposePort);
|
if (exposePort) exposePort = Number(exposePort);
|
||||||
|
|
||||||
type = fixType(type)
|
type = fixType(type)
|
||||||
const update = saveUpdateableFields(type, request.body[type])
|
// const update = saveUpdateableFields(type, request.body[type])
|
||||||
const data = {
|
const data = {
|
||||||
fqdn,
|
fqdn,
|
||||||
name,
|
name,
|
||||||
exposePort,
|
exposePort,
|
||||||
}
|
}
|
||||||
if (Object.keys(update).length > 0) {
|
// if (Object.keys(update).length > 0) {
|
||||||
data[type] = { update: update }
|
// data[type] = { update: update }
|
||||||
|
// }
|
||||||
|
for (const setting of serviceSetting) {
|
||||||
|
const { id: settingId, value, changed = false } = setting
|
||||||
|
if (setting.changed) {
|
||||||
|
await prisma.serviceSetting.update({ where: { id: settingId }, data: { value } })
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id }, data
|
where: { id }, data
|
||||||
|
|
||||||
});
|
});
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
|
@@ -72,6 +72,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.get<OnlyId>('/:id/usage', async (request) => await getServiceUsage(request));
|
fastify.get<OnlyId>('/:id/usage', async (request) => await getServiceUsage(request));
|
||||||
fastify.get<GetServiceLogs>('/:id/logs', async (request) => await getServiceLogs(request));
|
fastify.get<GetServiceLogs>('/:id/logs', async (request) => await getServiceLogs(request));
|
||||||
|
|
||||||
|
fastify.post<ServiceStartStop>('/:id/start', async (request) => await startService(request));
|
||||||
fastify.post<ServiceStartStop>('/:id/:type/start', async (request) => await startService(request));
|
fastify.post<ServiceStartStop>('/:id/:type/start', async (request) => await startService(request));
|
||||||
fastify.post<ServiceStartStop>('/:id/:type/stop', async (request) => await stopService(request));
|
fastify.post<ServiceStartStop>('/:id/:type/stop', async (request) => await stopService(request));
|
||||||
fastify.post<ServiceStartStop & SetWordpressSettings & SetGlitchTipSettings>('/: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));
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-row border-b border-coolgray-500 my-6 space-x-2">
|
<!-- <div class="flex flex-row border-b border-coolgray-500 my-6 space-x-2">
|
||||||
<div class="title font-bold pb-3">Plausible Analytics</div>
|
<div class="title font-bold pb-3">Plausible Analytics</div>
|
||||||
<ServiceStatus id={service.id} />
|
<ServiceStatus id={service.id} />
|
||||||
</div>
|
</div>
|
||||||
@@ -123,4 +123,4 @@
|
|||||||
<div class="flex flex-row my-6 space-x-2">
|
<div class="flex flex-row my-6 space-x-2">
|
||||||
<div class="title font-bold pb-3">ClickHouse</div>
|
<div class="title font-bold pb-3">ClickHouse</div>
|
||||||
<ServiceStatus id={`${service.id}-clickhouse`} />
|
<ServiceStatus id={`${service.id}-clickhouse`} />
|
||||||
</div>
|
</div> -->
|
@@ -41,6 +41,7 @@
|
|||||||
},
|
},
|
||||||
stuff: {
|
stuff: {
|
||||||
service,
|
service,
|
||||||
|
template,
|
||||||
readOnly,
|
readOnly,
|
||||||
settings
|
settings
|
||||||
}
|
}
|
||||||
@@ -111,7 +112,7 @@
|
|||||||
$status.service.initialLoading = true;
|
$status.service.initialLoading = true;
|
||||||
$status.service.loading = true;
|
$status.service.loading = true;
|
||||||
try {
|
try {
|
||||||
await post(`/services/${service.id}/${service.type}/start`, {});
|
await post(`/services/${service.id}/start`, {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
|
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
async function handleSubmit(service: any) {
|
async function handleSubmit(service: any) {
|
||||||
try {
|
try {
|
||||||
await post(`/services/${id}/configuration/type`, { ...service });
|
await post(`/services/${id}/configuration/type`, { type: service.name });
|
||||||
return await goto(from || `/services/${id}`);
|
return await goto(from || `/services/${id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
@@ -10,11 +10,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let service: any;
|
export let service: any;
|
||||||
export let readOnly: any;
|
export let readOnly: any;
|
||||||
export let settings: any;
|
export let template: any;
|
||||||
|
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
@@ -31,16 +29,18 @@
|
|||||||
} from '$lib/store';
|
} from '$lib/store';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
import * as Services from '$lib/components/Services';
|
// import * as Services from '$lib/components/Services';
|
||||||
|
|
||||||
import DocLink from '$lib/components/DocLink.svelte';
|
import DocLink from '$lib/components/DocLink.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import ServiceStatus from '$lib/components/ServiceStatus.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
let serviceName: any = service.type && service.type[0].toUpperCase() + service.type.substring(1);
|
// let serviceName: any = service.type && service.type[0].toUpperCase() + service.type.substring(1);
|
||||||
$: isDisabled =
|
$: isDisabled =
|
||||||
!$appSession.isAdmin || $status.service.isRunning || $status.service.initialLoading;
|
!$appSession.isAdmin || $status.service.isRunning || $status.service.initialLoading;
|
||||||
|
|
||||||
|
let newConfiguration = null;
|
||||||
let forceSave = false;
|
let forceSave = false;
|
||||||
let loading = {
|
let loading = {
|
||||||
save: false,
|
save: false,
|
||||||
@@ -69,17 +69,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit(e: any) {
|
||||||
|
const formData = new FormData(e.target);
|
||||||
|
for (let field of formData) {
|
||||||
|
const [key, value] = field;
|
||||||
|
for (const setting of service.serviceSetting) {
|
||||||
|
if (setting.name === key && setting.value !== value) {
|
||||||
|
setting.changed = true;
|
||||||
|
setting.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (loading.save) return;
|
if (loading.save) return;
|
||||||
loading.save = true;
|
loading.save = true;
|
||||||
try {
|
try {
|
||||||
await post(`/services/${id}/check`, {
|
// await post(`/services/${id}/check`, {
|
||||||
fqdn: service.fqdn,
|
// fqdn: service.fqdn,
|
||||||
forceSave,
|
// forceSave,
|
||||||
dualCerts,
|
// dualCerts,
|
||||||
otherFqdns: service.minio?.apiFqdn ? [service.minio?.apiFqdn] : [],
|
// otherFqdns: service.minio?.apiFqdn ? [service.minio?.apiFqdn] : [],
|
||||||
exposePort: service.exposePort
|
// exposePort: service.exposePort
|
||||||
});
|
// });
|
||||||
await post(`/services/${id}`, { ...service });
|
await post(`/services/${id}`, { ...service });
|
||||||
setLocation(service);
|
setLocation(service);
|
||||||
forceSave = false;
|
forceSave = false;
|
||||||
@@ -174,10 +184,10 @@
|
|||||||
if (service.type === 'wordpress') {
|
if (service.type === 'wordpress') {
|
||||||
service.wordpress.mysqlDatabase = 'db';
|
service.wordpress.mysqlDatabase = 'db';
|
||||||
}
|
}
|
||||||
if (service.type === 'plausibleanalytics') {
|
// if (service.type === 'plausibleanalytics') {
|
||||||
service.plausibleAnalytics.email = 'noreply@demo.com';
|
// service.plausibleAnalytics.email = 'noreply@demo.com';
|
||||||
service.plausibleAnalytics.username = 'admin';
|
// service.plausibleAnalytics.username = 'admin';
|
||||||
}
|
// }
|
||||||
if (service.type === 'minio') {
|
if (service.type === 'minio') {
|
||||||
service.minio.apiFqdn = `http://${cuid()}.demo.coolify.io`;
|
service.minio.apiFqdn = `http://${cuid()}.demo.coolify.io`;
|
||||||
}
|
}
|
||||||
@@ -187,13 +197,13 @@
|
|||||||
if (service.type === 'fider') {
|
if (service.type === 'fider') {
|
||||||
service.fider.emailNoreply = 'noreply@demo.com';
|
service.fider.emailNoreply = 'noreply@demo.com';
|
||||||
}
|
}
|
||||||
await handleSubmit();
|
// await handleSubmit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<form on:submit|preventDefault={() => handleSubmit()}>
|
<form on:submit|preventDefault={handleSubmit}>
|
||||||
<div class="mx-auto w-full">
|
<div class="mx-auto w-full">
|
||||||
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
|
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
|
||||||
<div class="title font-bold pb-3 ">General</div>
|
<div class="title font-bold pb-3 ">General</div>
|
||||||
@@ -213,7 +223,7 @@
|
|||||||
: $t('forms.save')}</button
|
: $t('forms.save')}</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if service.type === 'plausibleanalytics' && $status.service.isRunning}
|
<!-- {#if service.type === 'plausibleanalytics' && $status.service.isRunning}
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm"
|
class="btn btn-sm"
|
||||||
@@ -231,7 +241,7 @@
|
|||||||
class:loading={loading.cleanup}>Cleanup Unnecessary Database Logs</button
|
class:loading={loading.cleanup}>Cleanup Unnecessary Database Logs</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if} -->
|
||||||
{#if service.type === 'appwrite' && $status.service.isRunning}
|
{#if service.type === 'appwrite' && $status.service.isRunning}
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm"
|
class="btn btn-sm"
|
||||||
@@ -412,6 +422,41 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<svelte:component this={Services[serviceName]} bind:service {readOnly} {settings} />
|
<div />
|
||||||
|
<div>
|
||||||
|
{#each Object.keys(template) as oneService}
|
||||||
|
<div class="flex flex-row border-b border-coolgray-500 my-6 space-x-2">
|
||||||
|
<div class="title font-bold pb-3">{template[oneService].name}</div>
|
||||||
|
<ServiceStatus id={template[oneService]} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-flow-row gap-2 px-4">
|
||||||
|
{#if template[oneService].environment.length > 0}
|
||||||
|
{#each template[oneService].environment as variable}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for={variable.name}>{variable.label}</label>
|
||||||
|
{#if variable.defaultValue === '$$generate_fqdn'}
|
||||||
|
<input
|
||||||
|
class="w-full"
|
||||||
|
disabled
|
||||||
|
readonly
|
||||||
|
name={variable.name}
|
||||||
|
id={variable.name}
|
||||||
|
value={service.fqdn}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<input
|
||||||
|
class="w-full"
|
||||||
|
name={variable.name}
|
||||||
|
id={variable.name}
|
||||||
|
value={variable.value}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user