Merge pull request #538 from MrSquaare/feature/glitchtip-service
feat: add GlitchTip service
This commit is contained in:
commit
2e82c9d312
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
.pnpm-store
|
||||||
build
|
build
|
||||||
.svelte-kit
|
.svelte-kit
|
||||||
package
|
package
|
||||||
|
@ -92,6 +92,7 @@ ### Services
|
|||||||
- [Umami](https://github.com/mikecao/umami)
|
- [Umami](https://github.com/mikecao/umami)
|
||||||
- [Fider](https://fider.io)
|
- [Fider](https://fider.io)
|
||||||
- [Hasura](https://hasura.io)
|
- [Hasura](https://hasura.io)
|
||||||
|
- [GlitchTip](https://glitchtip.com)
|
||||||
|
|
||||||
## Migration from v1
|
## Migration from v1
|
||||||
|
|
||||||
|
@ -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");
|
@ -316,31 +316,32 @@ model DatabaseSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Service {
|
model Service {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
fqdn String?
|
fqdn String?
|
||||||
exposePort Int?
|
exposePort Int?
|
||||||
dualCerts Boolean @default(false)
|
dualCerts Boolean @default(false)
|
||||||
type String?
|
type String?
|
||||||
version String?
|
version String?
|
||||||
destinationDockerId String?
|
destinationDockerId String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
|
|
||||||
fider Fider?
|
fider Fider?
|
||||||
ghost Ghost?
|
ghost Ghost?
|
||||||
hasura Hasura?
|
glitchTip GlitchTip?
|
||||||
meiliSearch MeiliSearch?
|
hasura Hasura?
|
||||||
minio Minio?
|
meiliSearch MeiliSearch?
|
||||||
moodle Moodle?
|
minio Minio?
|
||||||
plausibleAnalytics PlausibleAnalytics?
|
moodle Moodle?
|
||||||
persistentStorage ServicePersistentStorage[]
|
plausibleAnalytics PlausibleAnalytics?
|
||||||
serviceSecret ServiceSecret[]
|
persistentStorage ServicePersistentStorage[]
|
||||||
umami Umami?
|
serviceSecret ServiceSecret[]
|
||||||
vscodeserver Vscodeserver?
|
umami Umami?
|
||||||
wordpress Wordpress?
|
vscodeserver Vscodeserver?
|
||||||
appwrite Appwrite?
|
wordpress Wordpress?
|
||||||
|
appwrite Appwrite?
|
||||||
|
|
||||||
teams Team[]
|
teams Team[]
|
||||||
}
|
}
|
||||||
@ -517,3 +518,30 @@ model Appwrite {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
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])
|
||||||
|
}
|
||||||
|
@ -79,7 +79,8 @@ export const include: any = {
|
|||||||
hasura: true,
|
hasura: true,
|
||||||
fider: true,
|
fider: true,
|
||||||
moodle: true,
|
moodle: true,
|
||||||
appwrite: true
|
appwrite: true,
|
||||||
|
glitchTip: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
|
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
|
||||||
@ -287,7 +288,7 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
ports: {
|
ports: {
|
||||||
main: 80
|
main: 80
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
// {
|
// {
|
||||||
// name: 'moodle',
|
// name: 'moodle',
|
||||||
// fancyName: 'Moodle',
|
// fancyName: 'Moodle',
|
||||||
@ -299,6 +300,17 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
// main: 8080
|
// 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<boolean> {
|
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
|
||||||
@ -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({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@ -1629,6 +1667,7 @@ export async function removeService({ id }: { id: string }): Promise<void> {
|
|||||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.wordpress.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.moodle.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.service.delete({ where: { id } });
|
await prisma.service.delete({ where: { id } });
|
||||||
@ -1832,8 +1871,6 @@ export function persistentVolumes(id, persistentStorage, config) {
|
|||||||
...composeVolumes
|
...composeVolumes
|
||||||
) || {}
|
) || {}
|
||||||
return { volumes, volumeMounts }
|
return { volumes, volumeMounts }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
export function defaultComposeConfiguration(network: string): any {
|
export function defaultComposeConfiguration(network: string): any {
|
||||||
return {
|
return {
|
||||||
|
@ -558,3 +558,116 @@ export const appwrite = [{
|
|||||||
isBoolean: false,
|
isBoolean: false,
|
||||||
isEncrypted: 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
|
||||||
|
}]
|
||||||
|
@ -580,6 +580,9 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
if (type === 'appwrite') {
|
if (type === 'appwrite') {
|
||||||
return await startAppWriteService(request)
|
return await startAppWriteService(request)
|
||||||
}
|
}
|
||||||
|
if (type === 'glitchTip') {
|
||||||
|
return await startGlitchTipService(request)
|
||||||
|
}
|
||||||
throw `Service type ${type} not supported.`
|
throw `Service type ${type} not supported.`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw { status: 500, message: error?.message || error }
|
throw { status: 500, message: error?.message || error }
|
||||||
@ -634,6 +637,9 @@ export async function stopService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
// if (type === 'moodle') {
|
// if (type === 'moodle') {
|
||||||
// return await stopMoodleService(request)
|
// return await stopMoodleService(request)
|
||||||
// }
|
// }
|
||||||
|
// if (type === 'glitchTip') {
|
||||||
|
// return await stopGlitchTipService(request)
|
||||||
|
// }
|
||||||
// throw `Service type ${type} not supported.`
|
// throw `Service type ${type} not supported.`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw { status: 500, message: error?.message || error }
|
throw { status: 500, message: error?.message || error }
|
||||||
@ -1105,7 +1111,7 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
|
|
||||||
const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config.wordpress)
|
const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config.wordpress)
|
||||||
|
|
||||||
let composeFile: ComposeFile = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
[id]: {
|
[id]: {
|
||||||
@ -2563,6 +2569,252 @@ async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function startGlitchTipService(request: FastifyRequest<ServiceStartStop>) {
|
||||||
|
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<ServiceStartStop>) {
|
||||||
|
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<OnlyId>, reply: FastifyReply) {
|
export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
|
@ -158,7 +158,7 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
ports: {
|
ports: {
|
||||||
main: 80
|
main: 80
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
// {
|
// {
|
||||||
// name: 'moodle',
|
// name: 'moodle',
|
||||||
// fancyName: 'Moodle',
|
// fancyName: 'Moodle',
|
||||||
@ -170,6 +170,17 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
// main: 8080
|
// 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) =>
|
export const asyncSleep = (delay: number) =>
|
||||||
|
51
apps/ui/src/lib/components/svg/services/GlitchTip.svelte
Normal file
51
apps/ui/src/lib/components/svg/services/GlitchTip.svelte
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
style="isolation:isolate"
|
||||||
|
viewBox="0 0 400 400"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="_clipPath_5kOQy2sGcuF9aeG3NHWmCAGgMEPQrnNW">
|
||||||
|
<rect width="400" height="400" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#_clipPath_5kOQy2sGcuF9aeG3NHWmCAGgMEPQrnNW)">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d=" M 276.155 367.684 L 337.655 367.684 L 337.655 180.781 L 205.525 180.781 L 205.525 241.801 L 267.987 241.801 L 267.987 258.617 C 267.987 291.29 238.678 308.586 202.162 308.586 C 156.998 308.586 127.689 282.641 127.689 226.906 L 127.689 173.094 C 127.689 117.359 156.998 91.414 202.162 91.414 C 241.08 91.414 261.74 112.554 271.83 138.5 L 331.409 104.386 C 306.424 52.976 261.74 26.55 202.162 26.55 C 111.353 26.55 50.333 88.531 50.333 201.441 C 50.333 313.872 110.873 373.45 187.748 373.45 C 238.197 373.45 268.947 347.985 273.752 314.352 L 276.155 314.352 L 276.155 367.684 Z "
|
||||||
|
fill="rgb(132,24,128)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g opacity="0.5">
|
||||||
|
<path
|
||||||
|
d=" M 139.701 175.78 L 139.701 173.094 C 139.701 117.359 169.01 91.414 214.174 91.414 C 253.092 91.414 273.752 112.554 283.842 138.5 L 343.421 104.386 C 318.436 52.976 273.752 26.55 214.174 26.55 C 128.962 26.55 69.981 81.125 63.033 181.145 L 139.701 175.78 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g opacity="0.5">
|
||||||
|
<path
|
||||||
|
d=" M 349.667 305.194 L 349.667 247.137 L 279.998 252.019 L 279.998 258.617 C 279.998 291.29 250.69 308.586 214.174 308.586 C 179.697 308.586 154.459 293.467 144.446 261.518 L 70.341 266.711 C 76.285 288.796 85.348 307.563 96.86 322.909 L 349.667 305.194 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
d=" M 337.655 247.03 L 337.655 180.781 L 205.525 180.781 L 205.525 241.801 L 267.987 241.801 L 267.987 251.912 L 337.655 247.03 Z M 132.401 261.413 C 129.319 251.534 127.689 240.048 127.689 226.906 L 127.689 175.099 L 51.069 180.468 C 50.581 187.25 50.333 194.242 50.333 201.441 C 50.333 225.632 53.136 247.376 58.301 266.606 L 132.401 261.413 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d=" M 337.655 305.862 L 337.655 367.684 L 276.155 367.684 L 276.155 314.352 L 273.752 314.352 C 268.947 347.985 238.197 373.45 187.748 373.45 C 146.712 373.45 110.33 356.473 85.327 323.543 L 337.655 305.862 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(255,63,42)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
@ -36,4 +36,6 @@
|
|||||||
<Icons.Appwrite {isAbsolute} />
|
<Icons.Appwrite {isAbsolute} />
|
||||||
{:else if type === 'moodle'}
|
{:else if type === 'moodle'}
|
||||||
<Icons.Moodle {isAbsolute} />
|
<Icons.Moodle {isAbsolute} />
|
||||||
|
{:else if type === 'glitchTip'}
|
||||||
|
<Icons.GlitchTip {isAbsolute} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -15,4 +15,4 @@ export { default as Hasura } from './Hasura.svelte';
|
|||||||
export { default as Fider } from './Fider.svelte';
|
export { default as Fider } from './Fider.svelte';
|
||||||
export { default as Appwrite } from './Appwrite.svelte';
|
export { default as Appwrite } from './Appwrite.svelte';
|
||||||
export { default as Moodle } from './Moodle.svelte';
|
export { default as Moodle } from './Moodle.svelte';
|
||||||
|
export { default as GlitchTip } from './GlitchTip.svelte';
|
||||||
|
@ -63,4 +63,8 @@
|
|||||||
<a href="https://moodle.org" target="_blank">
|
<a href="https://moodle.org" target="_blank">
|
||||||
<Icons.Moodle />
|
<Icons.Moodle />
|
||||||
</a>
|
</a>
|
||||||
|
{:else if service.type === 'glitchTip'}
|
||||||
|
<a href="https://glitchtip.com" target="_blank">
|
||||||
|
<Icons.GlitchTip />
|
||||||
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
208
apps/ui/src/routes/services/[id]/_Services/_GlitchTip.svelte
Normal file
208
apps/ui/src/routes/services/[id]/_Services/_GlitchTip.svelte
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
export let service: any;
|
||||||
|
function toggleEmailSmtpUseTls() {
|
||||||
|
service.glitchTip.emailSmtpUseTls = !service.glitchTip.emailSmtpUseTls;
|
||||||
|
}
|
||||||
|
function toggleEmailSmtpUseSsl() {
|
||||||
|
service.glitchTip.emailSmtpUseSsl = !service.glitchTip.emailSmtpUseSsl;
|
||||||
|
}
|
||||||
|
function toggleEnableOpenUserRegistration() {
|
||||||
|
service.glitchTip.enableOpenUserRegistration = !service.glitchTip.enableOpenUserRegistration;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">GlitchTip</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-2 font-bold">
|
||||||
|
<div class="subtitle">Settings</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<Setting
|
||||||
|
bind:setting={service.glitchTip.enableOpenUserRegistration}
|
||||||
|
on:click={toggleEnableOpenUserRegistration}
|
||||||
|
title={'Enable Open User Registration'}
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-2 font-bold">
|
||||||
|
<div class="subtitle">Email settings</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultEmailFrom" class="text-base font-bold text-stone-100">Default Email From</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
required
|
||||||
|
name="defaultEmailFrom"
|
||||||
|
id="defaultEmailFrom"
|
||||||
|
value={service.glitchTip.defaultEmailFrom}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpHost" class="text-base font-bold text-stone-100">SMTP Host</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpHost"
|
||||||
|
id="emailSmtpHost"
|
||||||
|
value={service.glitchTip.emailSmtpHost}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpPort" class="text-base font-bold text-stone-100">SMTP Port</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpPort"
|
||||||
|
id="emailSmtpPort"
|
||||||
|
value={service.glitchTip.emailSmtpPort}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpUser" class="text-base font-bold text-stone-100">SMTP User</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpUser"
|
||||||
|
id="emailSmtpUser"
|
||||||
|
value={service.glitchTip.emailSmtpUser}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpPassword" class="text-base font-bold text-stone-100">SMTP Password</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpPassword"
|
||||||
|
id="emailSmtpPassword"
|
||||||
|
value={service.glitchTip.emailSmtpPassword}
|
||||||
|
isPasswordField
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<Setting
|
||||||
|
bind:setting={service.glitchTip.emailSmtpUseTls}
|
||||||
|
on:click={toggleEmailSmtpUseTls}
|
||||||
|
title={'SMTP Use TLS'}
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<Setting
|
||||||
|
bind:setting={service.glitchTip.emailSmtpUseSsl}
|
||||||
|
on:click={toggleEmailSmtpUseSsl}
|
||||||
|
title={'SMTP Use SSL'}
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailBackend" class="text-base font-bold text-stone-100">Email Backend</label>
|
||||||
|
<CopyPasswordField name="emailBackend" id="emailBackend" value={service.glitchTip.emailBackend} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="mailgunApiKey" class="text-base font-bold text-stone-100">Mailgun API Key</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="mailgunApiKey"
|
||||||
|
id="mailgunApiKey"
|
||||||
|
value={service.glitchTip.mailgunApiKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="sendgridApiKey" class="text-base font-bold text-stone-100">SendGrid API Key</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="sendgridApiKey"
|
||||||
|
id="sendgridApiKey"
|
||||||
|
value={service.glitchTip.sendgridApiKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-2 font-bold">
|
||||||
|
<div class="subtitle">Default User & Superuser</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultEmail" class="text-base font-bold text-stone-100">{$t('forms.email')}</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="defaultEmail"
|
||||||
|
id="defaultEmail"
|
||||||
|
value={service.glitchTip.defaultEmail}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultUsername" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.username')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="defaultUsername"
|
||||||
|
id="defaultUsername"
|
||||||
|
value={service.glitchTip.defaultUsername}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultPassword" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.password')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="defaultPassword"
|
||||||
|
id="defaultPassword"
|
||||||
|
value={service.glitchTip.defaultPassword}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
isPasswordField
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">PostgreSQL</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlUser" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.username')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlUser"
|
||||||
|
id="postgresqlUser"
|
||||||
|
value={service.glitchTip.postgresqlUser}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlPassword" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.password')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
id="postgresqlPassword"
|
||||||
|
isPasswordField
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
name="postgresqlPassword"
|
||||||
|
value={service.glitchTip.postgresqlPassword}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlDatabase" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('index.database')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlDatabase"
|
||||||
|
id="postgresqlDatabase"
|
||||||
|
value={service.glitchTip.postgresqlDatabase}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import Fider from './_Fider.svelte';
|
import Fider from './_Fider.svelte';
|
||||||
import Ghost from './_Ghost.svelte';
|
import Ghost from './_Ghost.svelte';
|
||||||
|
import GlitchTip from './_GlitchTip.svelte';
|
||||||
import Hasura from './_Hasura.svelte';
|
import Hasura from './_Hasura.svelte';
|
||||||
import MeiliSearch from './_MeiliSearch.svelte';
|
import MeiliSearch from './_MeiliSearch.svelte';
|
||||||
import MinIo from './_MinIO.svelte';
|
import MinIo from './_MinIO.svelte';
|
||||||
@ -399,6 +400,8 @@
|
|||||||
<Appwrite bind:service {readOnly} />
|
<Appwrite bind:service {readOnly} />
|
||||||
{:else if service.type === 'moodle'}
|
{:else if service.type === 'moodle'}
|
||||||
<Moodle bind:service {readOnly} />
|
<Moodle bind:service {readOnly} />
|
||||||
|
{:else if service.type === 'glitchTip'}
|
||||||
|
<GlitchTip bind:service />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
Loading…
Reference in New Issue
Block a user