From 26d0ef9ac9a0ee4e14f65c6997d5c2566889c3c4 Mon Sep 17 00:00:00 2001 From: Guillaume Bonnet Date: Mon, 15 Aug 2022 09:56:34 +0000 Subject: [PATCH 1/3] feat: add GlitchTip service --- README.md | 1 + .../20220815092230_glitchtip/migration.sql | 30 +++ apps/api/prisma/schema.prisma | 28 ++ apps/api/src/lib/common.ts | 41 ++- apps/api/src/lib/serviceFields.ts | 112 ++++++++ .../src/routes/api/v1/services/handlers.ts | 254 +++++++++++++++++- apps/ui/src/lib/common.ts | 11 + .../components/svg/services/GlitchTip.svelte | 51 ++++ .../svg/services/ServiceIcons.svelte | 2 + .../src/lib/components/svg/services/index.ts | 2 +- .../routes/services/[id]/_ServiceLinks.svelte | 5 +- .../services/[id]/_Services/_GlitchTip.svelte | 208 ++++++++++++++ .../services/[id]/_Services/_Services.svelte | 5 +- 13 files changed, 745 insertions(+), 5 deletions(-) create mode 100644 apps/api/prisma/migrations/20220815092230_glitchtip/migration.sql create mode 100644 apps/ui/src/lib/components/svg/services/GlitchTip.svelte create mode 100644 apps/ui/src/routes/services/[id]/_Services/_GlitchTip.svelte diff --git a/README.md b/README.md index 40d7ac2dc..e7a6175da 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ ### Services - [Umami](https://github.com/mikecao/umami) - [Fider](https://fider.io) - [Hasura](https://hasura.io) +- [GlitchTip](https://glitchtip.com) If you have a new service you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community! diff --git a/apps/api/prisma/migrations/20220815092230_glitchtip/migration.sql b/apps/api/prisma/migrations/20220815092230_glitchtip/migration.sql new file mode 100644 index 000000000..dba98ab82 --- /dev/null +++ b/apps/api/prisma/migrations/20220815092230_glitchtip/migration.sql @@ -0,0 +1,30 @@ +-- CreateTable +CREATE TABLE "GlitchTip" ( + "id" TEXT NOT NULL PRIMARY KEY, + "postgresqlUser" TEXT NOT NULL, + "postgresqlPassword" TEXT NOT NULL, + "postgresqlDatabase" TEXT NOT NULL, + "postgresqlPublicPort" INTEGER, + "secretKeyBase" TEXT, + "defaultEmail" TEXT NOT NULL, + "defaultUsername" TEXT NOT NULL, + "defaultPassword" TEXT NOT NULL, + "defaultEmailFrom" TEXT NOT NULL DEFAULT 'glitchtip@domain.tdl', + "emailSmtpHost" TEXT DEFAULT 'domain.tdl', + "emailSmtpPort" INTEGER DEFAULT 25, + "emailSmtpUser" TEXT, + "emailSmtpPassword" TEXT, + "emailSmtpUseTls" BOOLEAN DEFAULT false, + "emailSmtpUseSsl" BOOLEAN DEFAULT false, + "emailBackend" TEXT, + "mailgunApiKey" TEXT, + "sendgridApiKey" TEXT, + "enableOpenUserRegistration" BOOLEAN NOT NULL DEFAULT true, + "serviceId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GlitchTip_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "GlitchTip_serviceId_key" ON "GlitchTip"("serviceId"); diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 7d50b63b8..555c2341b 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -325,6 +325,7 @@ model Service { destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) fider Fider? ghost Ghost? + glitchTip GlitchTip? hasura Hasura? meiliSearch MeiliSearch? minio Minio? @@ -491,3 +492,30 @@ model Moodle { updatedAt DateTime @updatedAt service Service @relation(fields: [serviceId], references: [id]) } + +model GlitchTip { + id String @id @default(cuid()) + postgresqlUser String + postgresqlPassword String + postgresqlDatabase String + postgresqlPublicPort Int? + secretKeyBase String? + defaultEmail String + defaultUsername String + defaultPassword String + defaultEmailFrom String @default("glitchtip@domain.tdl") + emailSmtpHost String? @default("domain.tdl") + emailSmtpPort Int? @default(25) + emailSmtpUser String? + emailSmtpPassword String? + emailSmtpUseTls Boolean? @default(false) + emailSmtpUseSsl Boolean? @default(false) + emailBackend String? + mailgunApiKey String? + sendgridApiKey String? + enableOpenUserRegistration Boolean @default(true) + serviceId String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + service Service @relation(fields: [serviceId], references: [id]) +} diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index ce8b8051d..2c26ab7d1 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -78,6 +78,7 @@ export const include: any = { umami: true, hasura: true, fider: true, + glitchTip: true, }; export const uniqueName = (): string => uniqueNamesGenerator(customConfig); @@ -280,6 +281,17 @@ export const supportedServiceTypesAndVersions = [ // main: 8080 // } // } + { + name: 'glitchTip', + fancyName: 'GlitchTip', + baseImage: 'glitchtip/glitchtip', + images: ['postgres:14-alpine', 'redis:7-alpine'], + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 8000 + } + }, ]; export async function checkDoubleBranch(branch: string, projectId: number): Promise { @@ -1517,7 +1529,33 @@ export async function configureServiceType({ } } }); - } else { + } else if (type === 'glitchTip') { + const defaultUsername = cuid(); + const defaultEmail = `${defaultUsername}@example.com`; + const defaultPassword = encrypt(generatePassword()); + const postgresqlUser = cuid(); + const postgresqlPassword = encrypt(generatePassword()); + const postgresqlDatabase = 'glitchTip'; + const secretKeyBase = encrypt(generatePassword(64)); + + await prisma.service.update({ + where: { id }, + data: { + type, + glitchTip: { + create: { + postgresqlDatabase, + postgresqlUser, + postgresqlPassword, + secretKeyBase, + defaultEmail, + defaultUsername, + defaultPassword, + } + } + } + }); + } else { await prisma.service.update({ where: { id }, data: { @@ -1538,6 +1576,7 @@ export async function removeService({ id }: { id: string }): Promise { await prisma.minio.deleteMany({ where: { serviceId: id } }); await prisma.vscodeserver.deleteMany({ where: { serviceId: id } }); await prisma.wordpress.deleteMany({ where: { serviceId: id } }); + await prisma.glitchTip.deleteMany({ where: { serviceId: id } }); await prisma.serviceSecret.deleteMany({ where: { serviceId: id } }); await prisma.service.delete({ where: { id } }); diff --git a/apps/api/src/lib/serviceFields.ts b/apps/api/src/lib/serviceFields.ts index 89d6a9c70..a678d3057 100644 --- a/apps/api/src/lib/serviceFields.ts +++ b/apps/api/src/lib/serviceFields.ts @@ -476,4 +476,116 @@ export const moodle = [{ isNumber: false, isBoolean: false, isEncrypted: false +}] +export const glitchTip = [{ + 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 +}, +{ + name: 'postgresqlPublicPort', + isEditable: false, + isLowerCase: false, + isNumber: true, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'secretKeyBase', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'defaultEmail', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'defaultUsername', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'defaultPassword', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'defaultFromEmail', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'emailUrl', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'emailBackend', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'mailgunApiKey', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'sendgridApiKey', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'enableOpenUserRegistration', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: true, + isEncrypted: false }] \ No newline at end of file diff --git a/apps/api/src/routes/api/v1/services/handlers.ts b/apps/api/src/routes/api/v1/services/handlers.ts index 8d07ac70b..50c6f7936 100644 --- a/apps/api/src/routes/api/v1/services/handlers.ts +++ b/apps/api/src/routes/api/v1/services/handlers.ts @@ -590,6 +590,9 @@ export async function startService(request: FastifyRequest) { if (type === 'moodle') { return await startMoodleService(request) } + if (type === 'glitchTip') { + return await startGlitchTipService(request) + } throw `Service type ${type} not supported.` } catch (error) { throw { status: 500, message: error?.message || error } @@ -643,6 +646,9 @@ export async function stopService(request: FastifyRequest) { if (type === 'moodle') { return await stopMoodleService(request) } + if (type === 'glitchTip') { + return await stopGlitchTipService(request) + } throw `Service type ${type} not supported.` } catch (error) { throw { status: 500, message: error?.message || error } @@ -1247,7 +1253,7 @@ async function startWordpressService(request: FastifyRequest) const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config.wordpress) - let composeFile: ComposeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { @@ -2633,6 +2639,252 @@ async function stopMoodleService(request: FastifyRequest) { } } +async function startGlitchTipService(request: FastifyRequest) { + try { + const { id } = request.params; + const teamId = request.user.teamId; + const service = await getServiceFromDB({ id, teamId }); + const { + type, + version, + fqdn, + destinationDockerId, + destinationDocker, + serviceSecret, + persistentStorage, + exposePort, + glitchTip: { + postgresqlDatabase, + postgresqlPassword, + postgresqlUser, + secretKeyBase, + defaultEmail, + defaultUsername, + defaultPassword, + defaultFromEmail, + emailSmtpHost, + emailSmtpPort, + emailSmtpUser, + emailSmtpPassword, + emailSmtpUseTls, + emailSmtpUseSsl, + emailBackend, + mailgunApiKey, + sendgridApiKey, + enableOpenUserRegistration, + } + } = service; + const network = destinationDockerId && destinationDocker.network; + const port = getServiceMainPort('glitchTip'); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + const image = getServiceImage(type); + + const config = { + glitchTip: { + image: `${image}:${version}`, + environmentVariables: { + PORT: port, + GLITCHTIP_DOMAIN: fqdn, + SECRET_KEY: secretKeyBase, + DATABASE_URL: `postgresql://${postgresqlUser}:${postgresqlPassword}@${id}-postgresql:5432/${postgresqlDatabase}`, + REDIS_URL: `redis://${id}-redis:6379/0`, + DEFAULT_FROM_EMAIL: defaultFromEmail, + EMAIL_HOST: emailSmtpHost, + EMAIL_PORT: emailSmtpPort, + EMAIL_HOST_USER: emailSmtpUser, + EMAIL_HOST_PASSWORD: emailSmtpPassword, + EMAIL_USE_TLS: emailSmtpUseTls, + EMAIL_USE_SSL: emailSmtpUseSsl, + EMAIL_BACKEND: emailBackend, + MAILGUN_API_KEY: mailgunApiKey, + SENDGRID_API_KEY: sendgridApiKey, + ENABLE_OPEN_USER_REGISTRATION: enableOpenUserRegistration, + DJANGO_SUPERUSER_EMAIL: defaultEmail, + DJANGO_SUPERUSER_USERNAME: defaultUsername, + DJANGO_SUPERUSER_PASSWORD: defaultPassword, + } + }, + postgresql: { + image: 'postgres:14-alpine', + volume: `${id}-postgresql-data:/var/lib/postgresql/data`, + environmentVariables: { + POSTGRES_USER: postgresqlUser, + POSTGRES_PASSWORD: postgresqlPassword, + POSTGRES_DB: postgresqlDatabase + } + }, + redis: { + image: 'redis:7-alpine', + volume: `${id}-redis-data:/data`, + } + }; + if (serviceSecret.length > 0) { + serviceSecret.forEach((secret) => { + config.glitchTip.environmentVariables[secret.name] = secret.value; + }); + } + const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config.glitchTip) + const composeFile: ComposeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image: config.glitchTip.image, + environment: config.glitchTip.environmentVariables, + networks: [network], + volumes, + restart: 'always', + labels: makeLabelForServices('glitchTip'), + ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), + deploy: { + restart_policy: { + condition: 'on-failure', + delay: '5s', + max_attempts: 3, + window: '120s' + } + }, + depends_on: [`${id}-postgresql`, `${id}-redis`] + }, + [`${id}-worker`]: { + container_name: `${id}-worker`, + image: config.glitchTip.image, + command: './bin/run-celery-with-beat.sh', + environment: config.glitchTip.environmentVariables, + networks: [network], + restart: 'always', + deploy: { + restart_policy: { + condition: 'on-failure', + delay: '5s', + max_attempts: 3, + window: '120s' + } + }, + depends_on: [`${id}-postgresql`, `${id}-redis`] + }, + [`${id}-setup`]: { + container_name: `${id}-setup`, + image: config.glitchTip.image, + command: 'sh -c "(./manage.py migrate || true) && (./manage.py createsuperuser --noinput || true)"', + environment: config.glitchTip.environmentVariables, + networks: [network], + restart: "no", + depends_on: [`${id}-postgresql`, `${id}-redis`] + }, + [`${id}-postgresql`]: { + image: config.postgresql.image, + container_name: `${id}-postgresql`, + environment: config.postgresql.environmentVariables, + networks: [network], + volumes: [config.postgresql.volume], + restart: 'always', + deploy: { + restart_policy: { + condition: 'on-failure', + delay: '5s', + max_attempts: 3, + window: '120s' + } + } + }, + [`${id}-redis`]: { + image: config.redis.image, + container_name: `${id}-redis`, + networks: [network], + volumes: [config.redis.volume], + restart: 'always', + deploy: { + restart_policy: { + condition: 'on-failure', + delay: '5s', + max_attempts: 3, + window: '120s' + } + } + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + ...volumeMounts, + [config.postgresql.volume.split(':')[0]]: { + name: config.postgresql.volume.split(':')[0] + }, + [config.redis.volume.split(':')[0]]: { + name: config.redis.volume.split(':')[0] + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + + return {} + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} +async function stopGlitchTipService(request: FastifyRequest) { + try { + const { id } = request.params; + const teamId = request.user.teamId; + const service = await getServiceFromDB({ id, teamId }); + const { destinationDockerId, destinationDocker } = service; + if (destinationDockerId) { + try { + const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); + if (found) { + await removeContainer({ id, dockerId: destinationDocker.id }); + } + } catch (error) { + console.error(error); + } + try { + const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-worker` }); + if (found) { + await removeContainer({ id: `${id}-worker`, dockerId: destinationDocker.id }); + } + } catch (error) { + console.error(error); + } + try { + const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-setup` }); + if (found) { + await removeContainer({ id: `${id}-setup`, dockerId: destinationDocker.id }); + } + } catch (error) { + console.error(error); + } + try { + const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` }); + if (found) { + await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id }); + } + } catch (error) { + console.error(error); + } + try { + const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-redis` }); + if (found) { + await removeContainer({ id: `${id}-redis`, dockerId: destinationDocker.id }); + } + } catch (error) { + console.error(error); + } + } + return {} + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} + export async function activatePlausibleUsers(request: FastifyRequest, reply: FastifyReply) { try { diff --git a/apps/ui/src/lib/common.ts b/apps/ui/src/lib/common.ts index 79c1598b5..3eeffc1db 100644 --- a/apps/ui/src/lib/common.ts +++ b/apps/ui/src/lib/common.ts @@ -159,6 +159,17 @@ export const supportedServiceTypesAndVersions = [ // main: 8080 // } // } + { + name: 'glitchTip', + fancyName: 'GlitchTip', + baseImage: 'glitchtip/glitchtip', + images: ['postgres:14-alpine', 'redis:7-alpine'], + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 8000 + } + }, ]; export const asyncSleep = (delay: number) => diff --git a/apps/ui/src/lib/components/svg/services/GlitchTip.svelte b/apps/ui/src/lib/components/svg/services/GlitchTip.svelte new file mode 100644 index 000000000..f61a87bbd --- /dev/null +++ b/apps/ui/src/lib/components/svg/services/GlitchTip.svelte @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte b/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte index 00e1bcf6e..856d7f785 100644 --- a/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte +++ b/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte @@ -34,4 +34,6 @@ {:else if type === 'moodle'} +{:else if type === 'glitchTip'} + {/if} diff --git a/apps/ui/src/lib/components/svg/services/index.ts b/apps/ui/src/lib/components/svg/services/index.ts index 007ded1ce..08b56de4c 100644 --- a/apps/ui/src/lib/components/svg/services/index.ts +++ b/apps/ui/src/lib/components/svg/services/index.ts @@ -14,4 +14,4 @@ export { default as Umami } from './Umami.svelte'; export { default as Hasura } from './Hasura.svelte'; export { default as Fider } from './Fider.svelte'; export { default as Moodle } from './Moodle.svelte'; - +export { default as GlitchTip } from './GlitchTip.svelte'; diff --git a/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte b/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte index e25f3d2d0..9c1b88544 100644 --- a/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte +++ b/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte @@ -59,5 +59,8 @@ +{:else if service.type === 'glitchTip'} + + + {/if} - diff --git a/apps/ui/src/routes/services/[id]/_Services/_GlitchTip.svelte b/apps/ui/src/routes/services/[id]/_Services/_GlitchTip.svelte new file mode 100644 index 000000000..03fcc3ef4 --- /dev/null +++ b/apps/ui/src/routes/services/[id]/_Services/_GlitchTip.svelte @@ -0,0 +1,208 @@ + + +
+
GlitchTip
+
+ +
+
Settings
+
+ +
+ +
+ +
+
Email settings
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
Default User & Superuser
+
+ +
+ + +
+
+ + +
+
+ + +
+ +
+
PostgreSQL
+
+ +
+ + +
+
+ + +
+
+ + +
diff --git a/apps/ui/src/routes/services/[id]/_Services/_Services.svelte b/apps/ui/src/routes/services/[id]/_Services/_Services.svelte index 3873604d1..56f1ebef5 100644 --- a/apps/ui/src/routes/services/[id]/_Services/_Services.svelte +++ b/apps/ui/src/routes/services/[id]/_Services/_Services.svelte @@ -19,6 +19,7 @@ import Fider from './_Fider.svelte'; import Ghost from './_Ghost.svelte'; + import GlitchTip from './_GlitchTip.svelte'; import Hasura from './_Hasura.svelte'; import MeiliSearch from './_MeiliSearch.svelte'; import MinIo from './_MinIO.svelte'; @@ -37,7 +38,7 @@ save: false, verification: false, cleanup: false - } + }; let dualCerts = service.dualCerts; let nonWWWDomain = service.fqdn && getDomain(service.fqdn).replace(/^www\./, ''); @@ -396,6 +397,8 @@ {:else if service.type === 'moodle'} + {:else if service.type === 'glitchTip'} + {/if} From 49084637226556c6928a3117db1a7f3e9dc9a550 Mon Sep 17 00:00:00 2001 From: Guillaume Bonnet Date: Mon, 15 Aug 2022 09:57:01 +0000 Subject: [PATCH 2/3] chore: add .pnpm-store in .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9e73cbebf..cb934cd76 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store node_modules +.pnpm-store build .svelte-kit package From d80f760c92614840f122fb15798b1f9f7ef49172 Mon Sep 17 00:00:00 2001 From: Guillaume Bonnet Date: Mon, 15 Aug 2022 19:41:38 +0000 Subject: [PATCH 3/3] fix: missing commas --- apps/api/src/lib/common.ts | 2 +- apps/ui/src/lib/common.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index cd60cb4e6..a00ef9394 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -288,7 +288,7 @@ export const supportedServiceTypesAndVersions = [ ports: { main: 80 } - } + }, // { // name: 'moodle', // fancyName: 'Moodle', diff --git a/apps/ui/src/lib/common.ts b/apps/ui/src/lib/common.ts index 7891cabbf..7aae8c3e3 100644 --- a/apps/ui/src/lib/common.ts +++ b/apps/ui/src/lib/common.ts @@ -158,7 +158,7 @@ export const supportedServiceTypesAndVersions = [ ports: { main: 80 } - } + }, // { // name: 'moodle', // fancyName: 'Moodle',