work-work

This commit is contained in:
Andras Bacsai
2022-10-17 15:43:57 +02:00
parent a7e86d9afd
commit 8f660c0276
12 changed files with 322 additions and 105 deletions

View File

@@ -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");

View File

@@ -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?

View File

@@ -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 };
} }

View File

@@ -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 } });

View File

@@ -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 = {

View File

@@ -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.",
},
] ]
} }
] ]

View File

@@ -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()
if (variableId.startsWith('$$secret_')) { let missingVariables = new Set()
const secretName = variableId.replace('$$secret_', '');
let secretValue = defaultValue || value || null; foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id))
if (defaultValue === '$$generate_password') {
secretValue = generatePassword({}); if (foundTemplate.variables.length > 0) {
foundTemplate.variables = foundTemplate.variables.map(variable => {
let { id: variableId } = variable;
if (variableId.startsWith('$$secret_')) {
if (variable.defaultValue === '$$generate_password') {
variable.value = generatePassword({});
} else if (variable.defaultValue === '$$generate_passphrase') {
variable.value = cuid();
}
} }
if (defaultValue === '$$generate_username') { if (variableId.startsWith('$$config_')) {
secretValue = cuid(); if (variable.defaultValue === '$$generate_username') {
variable.value = cuid();
} else {
variable.value = variable.defaultValue
}
} }
if (defaultValue === '$$generate_passphrase') { if (variable.value) {
secretValue = cuid(); generatedVariables.add(`${variableId}=${variable.value}`)
} else {
missingVariables.add(variableId)
} }
await prisma.serviceSecret.create({ return variable
data: { name: secretName, value: encrypt(secretValue), service: { connect: { id } } } })
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({
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, version: foundTemplate.serviceDefaultVersion } })
return reply.code(201).send()
} else {
throw { status: 404, message: 'Service type not found.' }
} }
await prisma.service.update({ where: { id }, data: { type: name, version: serviceDefaultVersion } })
return reply.code(201).send()
} 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 }) {

View File

@@ -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));

View File

@@ -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> -->

View File

@@ -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 {

View File

@@ -26,7 +26,7 @@
<script lang="ts"> <script lang="ts">
export let services: any; export let services: any;
let search = ''; let search = '';
let filteredServices = services; let filteredServices = services;
@@ -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);

View File

@@ -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>