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 diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile index 04d811629..e365fb489 100644 --- a/.gitpod.Dockerfile +++ b/.gitpod.Dockerfile @@ -1,2 +1,2 @@ -FROM gitpod/workspace-node:2022-06-20-19-54-55 +FROM gitpod/workspace-full:2022-08-17-18-37-55 RUN brew install buildpacks/tap/pack \ No newline at end of file diff --git a/.gitpod.yml b/.gitpod.yml index 2cd6c8113..940733ac9 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,11 +1,11 @@ # This configuration file was automatically generated by Gitpod. # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) # and commit this file to your remote git repository to share the goodness with others. -image: - file: .gitpod.Dockerfile -tasks: - - init: pnpm install && pnpm db:push && pnpm db:seed - command: pnpm dev +#image: +# file: .gitpod.Dockerfile +#tasks: +# - init: pnpm install && pnpm db:push && pnpm db:seed +# command: pnpm dev ports: - port: 3001 diff --git a/README.md b/README.md index 475d00d1d..afa8eca48 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ ### Services - [Umami](https://github.com/mikecao/umami) - [Fider](https://fider.io) - [Hasura](https://hasura.io) +- [GlitchTip](https://glitchtip.com) ## Migration from v1 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 6af0c4535..d4a7ce2f5 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -316,31 +316,32 @@ model DatabaseSettings { } model Service { - id String @id @default(cuid()) + id String @id @default(cuid()) name String fqdn String? exposePort Int? - dualCerts Boolean @default(false) + dualCerts Boolean @default(false) type String? version String? destinationDockerId String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) - fider Fider? - ghost Ghost? - hasura Hasura? - meiliSearch MeiliSearch? - minio Minio? - moodle Moodle? - plausibleAnalytics PlausibleAnalytics? - persistentStorage ServicePersistentStorage[] - serviceSecret ServiceSecret[] - umami Umami? - vscodeserver Vscodeserver? - wordpress Wordpress? - appwrite Appwrite? + fider Fider? + ghost Ghost? + glitchTip GlitchTip? + hasura Hasura? + meiliSearch MeiliSearch? + minio Minio? + moodle Moodle? + plausibleAnalytics PlausibleAnalytics? + persistentStorage ServicePersistentStorage[] + serviceSecret ServiceSecret[] + umami Umami? + vscodeserver Vscodeserver? + wordpress Wordpress? + appwrite Appwrite? teams Team[] } @@ -517,3 +518,30 @@ model Appwrite { 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 1c9c000ad..2985728a8 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -17,7 +17,7 @@ import { checkContainer, removeContainer } from './docker'; import { day } from './dayjs'; import * as serviceFields from './serviceFields' -export const version = '3.6.0'; +export const version = '3.7.0'; export const isDev = process.env.NODE_ENV === 'development'; const algorithm = 'aes-256-ctr'; @@ -79,7 +79,8 @@ export const include: any = { hasura: true, fider: true, moodle: true, - appwrite: true + appwrite: true, + glitchTip: true, }; export const uniqueName = (): string => uniqueNamesGenerator(customConfig); @@ -287,7 +288,7 @@ export const supportedServiceTypesAndVersions = [ ports: { main: 80 } - } + }, // { // name: 'moodle', // fancyName: 'Moodle', @@ -299,6 +300,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 { @@ -1607,7 +1619,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: { @@ -1629,6 +1667,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.moodle.deleteMany({ where: { serviceId: id } }); await prisma.appwrite.deleteMany({ where: { serviceId: id } }); await prisma.service.delete({ where: { id } }); @@ -1769,7 +1808,7 @@ export function convertTolOldVolumeNames(type) { // export async function getAvailableServices(): Promise { // const { data } = await axios.get(`https://gist.githubusercontent.com/andrasbacsai/4aac36d8d6214dbfc34fa78110554a50/raw/5b27e6c37d78aaeedc1148d797112c827a2f43cf/availableServices.json`) // return data -// +// export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) { // Cleanup old coolify images try { @@ -1832,8 +1871,6 @@ export function persistentVolumes(id, persistentStorage, config) { ...composeVolumes ) || {} return { volumes, volumeMounts } - - } export function defaultComposeConfiguration(network: string): any { return { @@ -1848,4 +1885,4 @@ export function defaultComposeConfiguration(network: string): any { } } } -} \ No newline at end of file +} diff --git a/apps/api/src/lib/serviceFields.ts b/apps/api/src/lib/serviceFields.ts index be8c34c4f..d0adba9fd 100644 --- a/apps/api/src/lib/serviceFields.ts +++ b/apps/api/src/lib/serviceFields.ts @@ -557,4 +557,117 @@ export const appwrite = [{ isNumber: false, isBoolean: false, isEncrypted: false -}] \ No newline at end of file +}] + +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 +}] diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index 57da91478..65932ea2b 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -395,7 +395,7 @@ export async function checkDNS(request: FastifyRequest) { if (found) { throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` } } - await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }) + if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }) if (isDNSCheckEnabled && !isDev && !forceSave) { let hostname = request.hostname.split(':')[0]; if (remoteEngine) hostname = remoteIpAddress; diff --git a/apps/api/src/routes/api/v1/services/handlers.ts b/apps/api/src/routes/api/v1/services/handlers.ts index bba1b26bd..d5746cf6c 100644 --- a/apps/api/src/routes/api/v1/services/handlers.ts +++ b/apps/api/src/routes/api/v1/services/handlers.ts @@ -30,7 +30,7 @@ import { defaultServiceConfigurations } from '../../../../lib/services'; // serviceSecret.forEach((secret) => { // environmentVariables[secret.name] = secret.value; // }); -// } +// } // config.newVolumes = {} // for (const service of Object.entries(config.services)) { // const name = service[0] @@ -98,7 +98,7 @@ import { defaultServiceConfigurations } from '../../../../lib/services'; // } // console.log(config.services) -// console.log(config.volumes) +// console.log(config.volumes) // // config.services[id] = JSON.parse(JSON.stringify(config.services[type])) // // config.services[id].container_name = id @@ -378,7 +378,7 @@ export async function checkService(request: FastifyRequest) { } } } - await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }) + if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }) if (isDNSCheckEnabled && !isDev && !forceSave) { let hostname = request.hostname.split(':')[0]; if (remoteEngine) hostname = remoteIpAddress; @@ -580,6 +580,9 @@ export async function startService(request: FastifyRequest) { if (type === 'appwrite') { return await startAppWriteService(request) } + if (type === 'glitchTip') { + return await startGlitchTipService(request) + } throw `Service type ${type} not supported.` } catch (error) { throw { status: 500, message: error?.message || error } @@ -634,6 +637,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 } @@ -1105,7 +1111,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]: { @@ -2563,6 +2569,252 @@ async function startMoodleService(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 { const { id } = request.params diff --git a/apps/ui/src/lib/common.ts b/apps/ui/src/lib/common.ts index 3834422e5..5c0e87ed4 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', @@ -170,6 +170,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 c5f537736..fb89bf898 100644 --- a/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte +++ b/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte @@ -36,4 +36,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 1e47a5def..30b37922a 100644 --- a/apps/ui/src/lib/components/svg/services/index.ts +++ b/apps/ui/src/lib/components/svg/services/index.ts @@ -15,4 +15,4 @@ export { default as Hasura } from './Hasura.svelte'; export { default as Fider } from './Fider.svelte'; export { default as Appwrite } from './Appwrite.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 9303a0fb2..f0136089a 100644 --- a/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte +++ b/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte @@ -63,4 +63,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 d6e6ddb7f..a755a7d7d 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'; @@ -399,6 +400,8 @@ {:else if service.type === 'moodle'} + {:else if service.type === 'glitchTip'} + {/if} diff --git a/package.json b/package.json index 2288f4c4f..803ad6d61 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "3.6.0", + "version": "3.7.0", "license": "Apache-2.0", "repository": "github:coollabsio/coolify", "scripts": {