From 4f5fe3d38308a5808ca7f1b8bcea65cdfb1bc7e8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 23 Aug 2022 10:11:38 +0200 Subject: [PATCH] feat: Searxng service --- .../migration.sql | 13 + apps/api/prisma/schema.prisma | 38 +-- apps/api/src/lib/common.ts | 97 +++++--- apps/api/src/lib/serviceFields.ts | 17 ++ .../src/routes/api/v1/databases/handlers.ts | 4 +- .../src/routes/api/v1/services/handlers.ts | 227 +++++++----------- apps/ui/src/lib/common.ts | 11 + .../components/svg/services/Searxng.svelte | 57 +++++ .../svg/services/ServiceIcons.svelte | 2 + .../src/lib/components/svg/services/index.ts | 1 + .../routes/services/[id]/_ServiceLinks.svelte | 6 +- .../services/[id]/_Services/_Searxng.svelte | 36 +++ .../services/[id]/_Services/_Services.svelte | 3 + 13 files changed, 328 insertions(+), 184 deletions(-) create mode 100644 apps/api/prisma/migrations/20220823070532_service_searxng/migration.sql create mode 100644 apps/ui/src/lib/components/svg/services/Searxng.svelte create mode 100644 apps/ui/src/routes/services/[id]/_Services/_Searxng.svelte diff --git a/apps/api/prisma/migrations/20220823070532_service_searxng/migration.sql b/apps/api/prisma/migrations/20220823070532_service_searxng/migration.sql new file mode 100644 index 000000000..81ecc81c8 --- /dev/null +++ b/apps/api/prisma/migrations/20220823070532_service_searxng/migration.sql @@ -0,0 +1,13 @@ +-- CreateTable +CREATE TABLE "Searxng" ( + "id" TEXT NOT NULL PRIMARY KEY, + "secretKey" TEXT NOT NULL, + "redisPassword" TEXT NOT NULL, + "serviceId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Searxng_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "Searxng_serviceId_key" ON "Searxng"("serviceId"); diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index d4a7ce2f5..02440300b 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -327,23 +327,23 @@ model Service { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) - - 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[] - teams Team[] + fider Fider? + ghost Ghost? + glitchTip GlitchTip? + hasura Hasura? + meiliSearch MeiliSearch? + minio Minio? + moodle Moodle? + plausibleAnalytics PlausibleAnalytics? + umami Umami? + vscodeserver Vscodeserver? + wordpress Wordpress? + appwrite Appwrite? + searxng Searxng? } model PlausibleAnalytics { @@ -545,3 +545,13 @@ model GlitchTip { updatedAt DateTime @updatedAt service Service @relation(fields: [serviceId], references: [id]) } + +model Searxng { + id String @id @default(cuid()) + secretKey String + redisPassword String + 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 a69901a52..cd1268ebb 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -81,6 +81,7 @@ export const include: any = { moodle: true, appwrite: true, glitchTip: true, + searxng: true }; export const uniqueName = (): string => uniqueNamesGenerator(customConfig); @@ -311,6 +312,17 @@ export const supportedServiceTypesAndVersions = [ main: 8000 } }, + { + name: 'searxng', + fancyName: 'SearXNG', + baseImage: 'searxng/searxng', + images: [], + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 8080 + } + }, ]; export async function checkDoubleBranch(branch: string, projectId: number): Promise { @@ -608,7 +620,7 @@ export async function createRemoteEngineConfiguration(id: string) { config.append({ Host: remoteIpAddress, Hostname: 'localhost', - Port: Number(localPort), + Port: localPort.toString(), User: remoteUser, IdentityFile: sshKeyFile, StrictHostKeyChecking: 'no' @@ -753,13 +765,18 @@ export async function listSettings(): Promise { } -export function generatePassword(length = 24, symbols = false): string { - return generator.generate({ +export function generatePassword({ length = 24, symbols = false, isHex = false }: { length?: number, symbols?: boolean, isHex?: boolean } | null): string { + if (isHex) { + return crypto.randomBytes(length).toString("hex"); + } + const password = generator.generate({ length, numbers: true, strict: true, symbols }); + + return password; } export function generateDatabaseConfiguration(database: any, arch: string): @@ -1418,11 +1435,11 @@ export async function configureServiceType({ type: string; }): Promise { if (type === 'plausibleanalytics') { - const password = encrypt(generatePassword()); + const password = encrypt(generatePassword({})); const postgresqlUser = cuid(); - const postgresqlPassword = encrypt(generatePassword()); + const postgresqlPassword = encrypt(generatePassword({})); const postgresqlDatabase = 'plausibleanalytics'; - const secretKeyBase = encrypt(generatePassword(64)); + const secretKeyBase = encrypt(generatePassword({ length: 64 })); await prisma.service.update({ where: { id }, @@ -1446,22 +1463,22 @@ export async function configureServiceType({ }); } else if (type === 'minio') { const rootUser = cuid(); - const rootUserPassword = encrypt(generatePassword()); + const rootUserPassword = encrypt(generatePassword({})); await prisma.service.update({ where: { id }, data: { type, minio: { create: { rootUser, rootUserPassword } } } }); } else if (type === 'vscodeserver') { - const password = encrypt(generatePassword()); + const password = encrypt(generatePassword({})); await prisma.service.update({ where: { id }, data: { type, vscodeserver: { create: { password } } } }); } else if (type === 'wordpress') { const mysqlUser = cuid(); - const mysqlPassword = encrypt(generatePassword()); + const mysqlPassword = encrypt(generatePassword({})); const mysqlRootUser = cuid(); - const mysqlRootUserPassword = encrypt(generatePassword()); + const mysqlRootUserPassword = encrypt(generatePassword({})); await prisma.service.update({ where: { id }, data: { @@ -1499,11 +1516,11 @@ export async function configureServiceType({ }); } else if (type === 'ghost') { const defaultEmail = `${cuid()}@example.com`; - const defaultPassword = encrypt(generatePassword()); + const defaultPassword = encrypt(generatePassword({})); const mariadbUser = cuid(); - const mariadbPassword = encrypt(generatePassword()); + const mariadbPassword = encrypt(generatePassword({})); const mariadbRootUser = cuid(); - const mariadbRootUserPassword = encrypt(generatePassword()); + const mariadbRootUserPassword = encrypt(generatePassword({})); await prisma.service.update({ where: { id }, @@ -1522,7 +1539,7 @@ export async function configureServiceType({ } }); } else if (type === 'meilisearch') { - const masterKey = encrypt(generatePassword(32)); + const masterKey = encrypt(generatePassword({ length: 32 })); await prisma.service.update({ where: { id }, data: { @@ -1531,11 +1548,11 @@ export async function configureServiceType({ } }); } else if (type === 'umami') { - const umamiAdminPassword = encrypt(generatePassword()); + const umamiAdminPassword = encrypt(generatePassword({})); const postgresqlUser = cuid(); - const postgresqlPassword = encrypt(generatePassword()); + const postgresqlPassword = encrypt(generatePassword({})); const postgresqlDatabase = 'umami'; - const hashSalt = encrypt(generatePassword(64)); + const hashSalt = encrypt(generatePassword({ length: 64 })); await prisma.service.update({ where: { id }, data: { @@ -1553,9 +1570,9 @@ export async function configureServiceType({ }); } else if (type === 'hasura') { const postgresqlUser = cuid(); - const postgresqlPassword = encrypt(generatePassword()); + const postgresqlPassword = encrypt(generatePassword({})); const postgresqlDatabase = 'hasura'; - const graphQLAdminPassword = encrypt(generatePassword()); + const graphQLAdminPassword = encrypt(generatePassword({})); await prisma.service.update({ where: { id }, data: { @@ -1572,9 +1589,9 @@ export async function configureServiceType({ }); } else if (type === 'fider') { const postgresqlUser = cuid(); - const postgresqlPassword = encrypt(generatePassword()); + const postgresqlPassword = encrypt(generatePassword({})); const postgresqlDatabase = 'fider'; - const jwtSecret = encrypt(generatePassword(64, true)); + const jwtSecret = encrypt(generatePassword({ length: 64, symbols: true })); await prisma.service.update({ where: { id }, data: { @@ -1591,13 +1608,13 @@ export async function configureServiceType({ }); } else if (type === 'moodle') { const defaultUsername = cuid(); - const defaultPassword = encrypt(generatePassword()); + const defaultPassword = encrypt(generatePassword({})); const defaultEmail = `${cuid()} @example.com`; const mariadbUser = cuid(); - const mariadbPassword = encrypt(generatePassword()); + const mariadbPassword = encrypt(generatePassword({})); const mariadbDatabase = 'moodle_db'; const mariadbRootUser = cuid(); - const mariadbRootUserPassword = encrypt(generatePassword()); + const mariadbRootUserPassword = encrypt(generatePassword({})); await prisma.service.update({ where: { id }, data: { @@ -1617,15 +1634,15 @@ export async function configureServiceType({ } }); } else if (type === 'appwrite') { - const opensslKeyV1 = encrypt(generatePassword()); - const executorSecret = encrypt(generatePassword()); - const redisPassword = encrypt(generatePassword()); + const opensslKeyV1 = encrypt(generatePassword({})); + const executorSecret = encrypt(generatePassword({})); + const redisPassword = encrypt(generatePassword({})); const mariadbHost = `${id}-mariadb` const mariadbUser = cuid(); - const mariadbPassword = encrypt(generatePassword()); + const mariadbPassword = encrypt(generatePassword({})); const mariadbDatabase = 'appwrite'; const mariadbRootUser = cuid(); - const mariadbRootUserPassword = encrypt(generatePassword()); + const mariadbRootUserPassword = encrypt(generatePassword({})); await prisma.service.update({ where: { id }, data: { @@ -1648,11 +1665,11 @@ export async function configureServiceType({ } else if (type === 'glitchTip') { const defaultUsername = cuid(); const defaultEmail = `${defaultUsername}@example.com`; - const defaultPassword = encrypt(generatePassword()); + const defaultPassword = encrypt(generatePassword({})); const postgresqlUser = cuid(); - const postgresqlPassword = encrypt(generatePassword()); + const postgresqlPassword = encrypt(generatePassword({})); const postgresqlDatabase = 'glitchTip'; - const secretKeyBase = encrypt(generatePassword(64)); + const secretKeyBase = encrypt(generatePassword({ length: 64 })); await prisma.service.update({ where: { id }, @@ -1671,6 +1688,21 @@ export async function configureServiceType({ } } }); + } else if (type === 'searxng') { + const secretKey = encrypt(generatePassword({ length: 32, isHex: true })) + const redisPassword = encrypt(generatePassword({})); + await prisma.service.update({ + where: { id }, + data: { + type, + searxng: { + create: { + secretKey, + redisPassword, + } + } + } + }); } else { await prisma.service.update({ where: { id }, @@ -1696,6 +1728,7 @@ export async function removeService({ id }: { id: string }): Promise { await prisma.glitchTip.deleteMany({ where: { serviceId: id } }); await prisma.moodle.deleteMany({ where: { serviceId: id } }); await prisma.appwrite.deleteMany({ where: { serviceId: id } }); + await prisma.searxng.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 d0adba9fd..a9d5b379a 100644 --- a/apps/api/src/lib/serviceFields.ts +++ b/apps/api/src/lib/serviceFields.ts @@ -671,3 +671,20 @@ export const glitchTip = [{ isBoolean: true, isEncrypted: false }] + +export const searxng = [{ + name: 'secretKey', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'redisPassword', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}] \ No newline at end of file diff --git a/apps/api/src/routes/api/v1/databases/handlers.ts b/apps/api/src/routes/api/v1/databases/handlers.ts index 77d27ebc1..8372fb76d 100644 --- a/apps/api/src/routes/api/v1/databases/handlers.ts +++ b/apps/api/src/routes/api/v1/databases/handlers.ts @@ -29,9 +29,9 @@ export async function newDatabase(request: FastifyRequest, reply: FastifyReply) const name = uniqueName(); const dbUser = cuid(); - const dbUserPassword = encrypt(generatePassword()); + const dbUserPassword = encrypt(generatePassword({})); const rootUser = cuid(); - const rootUserPassword = encrypt(generatePassword()); + const rootUserPassword = encrypt(generatePassword({})); const defaultDatabase = cuid(); const { id } = await prisma.database.create({ diff --git a/apps/api/src/routes/api/v1/services/handlers.ts b/apps/api/src/routes/api/v1/services/handlers.ts index d5746cf6c..c2a23e5de 100644 --- a/apps/api/src/routes/api/v1/services/handlers.ts +++ b/apps/api/src/routes/api/v1/services/handlers.ts @@ -583,6 +583,9 @@ export async function startService(request: FastifyRequest) { if (type === 'glitchTip') { return await startGlitchTipService(request) } + if (type === 'searxng') { + return await startSearXNGService(request) + } throw `Service type ${type} not supported.` } catch (error) { throw { status: 500, message: error?.message || error } @@ -591,56 +594,6 @@ export async function startService(request: FastifyRequest) { export async function stopService(request: FastifyRequest) { try { return await stopServiceContainers(request) - // const { type } = request.params - // if (type === 'plausibleanalytics') { - // return await stopPlausibleAnalyticsService(request) - // } - // if (type === 'nocodb') { - // return await stopNocodbService(request) - // } - // if (type === 'minio') { - // return await stopMinioService(request) - // } - // if (type === 'vscodeserver') { - // return await stopVscodeService(request) - // } - // if (type === 'wordpress') { - // return await stopWordpressService(request) - // } - // if (type === 'vaultwarden') { - // return await stopVaultwardenService(request) - // } - // if (type === 'languagetool') { - // return await stopLanguageToolService(request) - // } - // if (type === 'n8n') { - // return await stopN8nService(request) - // } - // if (type === 'uptimekuma') { - // return await stopUptimekumaService(request) - // } - // if (type === 'ghost') { - // return await stopGhostService(request) - // } - // if (type === 'meilisearch') { - // return await stopMeilisearchService(request) - // } - // if (type === 'umami') { - // return await stopUmamiService(request) - // } - // if (type === 'hasura') { - // return await stopHasuraService(request) - // } - // if (type === 'fider') { - // return await stopFiderService(request) - // } - // 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 } } @@ -2415,6 +2368,7 @@ async function startAppWriteService(request: FastifyRequest) { } async function startServiceContainers(dockerId, composeFileDestination) { await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} build --no-cache` }) await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} create` }) await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} start` }) await asyncSleep(1000); @@ -2662,37 +2616,19 @@ async function startGlitchTipService(request: FastifyRequest) 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`] + depends_on: [`${id}-postgresql`, `${id}-redis`], + ...defaultComposeConfiguration(network), }, [`${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`] + depends_on: [`${id}-postgresql`, `${id}-redis`], + ...defaultComposeConfiguration(network), }, [`${id}-setup`]: { container_name: `${id}-setup`, @@ -2707,32 +2643,14 @@ async function startGlitchTipService(request: FastifyRequest) 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' - } - } + ...defaultComposeConfiguration(network), }, [`${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' - } - } + ...defaultComposeConfiguration(network), } }, networks: { @@ -2761,54 +2679,93 @@ async function startGlitchTipService(request: FastifyRequest) return errorHandler({ status, message }) } } -async function stopGlitchTipService(request: FastifyRequest) { + +async function startSearXNGService(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); + const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort, persistentStorage, fqdn, searxng: { secretKey, redisPassword } } = + service; + const network = destinationDockerId && destinationDocker.network; + const port = getServiceMainPort('searxng'); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + const image = getServiceImage(type); + + const config = { + searxng: { + image: `${image}:${version}`, + volume: `${id}-searxng:/etc/searxng`, + environmentVariables: { + SEARXNG_BASE_URL: `${fqdn}` + }, + }, + redis: { + image: 'redis:7-alpine', } + }; + + const settingsYml = ` + # see https://docs.searxng.org/admin/engines/settings.html#use-default-settings + use_default_settings: true + server: + secret_key: ${secretKey} + limiter: true + image_proxy: true + ui: + static_use_hash: true + redis: + url: redis://:${redisPassword}@${id}-redis:6379/0` + + const Dockerfile = ` + FROM ${config.searxng.image} + COPY ./settings.yml /etc/searxng/settings.yml`; + + if (serviceSecret.length > 0) { + serviceSecret.forEach((secret) => { + config.searxng.environmentVariables[secret.name] = secret.value; + }); } + const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config) + const composeFile: ComposeFile = { + version: '3.8', + services: { + [id]: { + build: workdir, + container_name: id, + volumes, + environment: config.searxng.environmentVariables, + ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), + labels: makeLabelForServices('searxng'), + cap_drop: ['ALL'], + cap_add: ['CHOWN', 'SETGID', 'SETUID', 'DAC_OVERRIDE'], + depends_on: [`${id}-redis`], + ...defaultComposeConfiguration(network), + }, + [`${id}-redis`]: { + container_name: `${id}-redis`, + image: config.redis.image, + command: `redis-server --requirepass ${redisPassword} --save "" --appendonly "no"`, + labels: makeLabelForServices('searxng'), + cap_drop: ['ALL'], + cap_add: ['SETGID', 'SETUID', 'DAC_OVERRIDE'], + ...defaultComposeConfiguration(network), + }, + }, + networks: { + [network]: { + external: true + } + }, + volumes: volumeMounts + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile); + await fs.writeFile(`${workdir}/settings.yml`, settingsYml); + + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -2865,7 +2822,7 @@ export async function activateWordpressFtp(request: FastifyRequest diff --git a/apps/ui/src/lib/components/svg/services/Searxng.svelte b/apps/ui/src/lib/components/svg/services/Searxng.svelte new file mode 100644 index 000000000..52cbaed81 --- /dev/null +++ b/apps/ui/src/lib/components/svg/services/Searxng.svelte @@ -0,0 +1,57 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte b/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte index fb89bf898..754068e2d 100644 --- a/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte +++ b/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte @@ -38,4 +38,6 @@ {:else if type === 'glitchTip'} +{:else if type === 'searxng'} + {/if} diff --git a/apps/ui/src/lib/components/svg/services/index.ts b/apps/ui/src/lib/components/svg/services/index.ts index 30b37922a..15fa32f90 100644 --- a/apps/ui/src/lib/components/svg/services/index.ts +++ b/apps/ui/src/lib/components/svg/services/index.ts @@ -16,3 +16,4 @@ 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'; +export { default as Searxng } from './Searxng.svelte'; \ No newline at end of file diff --git a/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte b/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte index f0136089a..22044229c 100644 --- a/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte +++ b/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte @@ -57,7 +57,7 @@ {:else if service.type === 'appwrite'} - + {:else if service.type === 'moodle'} @@ -67,4 +67,8 @@ +{:else if service.type === 'searxng'} + + + {/if} diff --git a/apps/ui/src/routes/services/[id]/_Services/_Searxng.svelte b/apps/ui/src/routes/services/[id]/_Services/_Searxng.svelte new file mode 100644 index 000000000..a4d547d58 --- /dev/null +++ b/apps/ui/src/routes/services/[id]/_Services/_Searxng.svelte @@ -0,0 +1,36 @@ + + +
+
SearXNG
+
+ +
+ + +
+
+
Redis
+
+ +
+ + +
diff --git a/apps/ui/src/routes/services/[id]/_Services/_Services.svelte b/apps/ui/src/routes/services/[id]/_Services/_Services.svelte index a755a7d7d..b7d365680 100644 --- a/apps/ui/src/routes/services/[id]/_Services/_Services.svelte +++ b/apps/ui/src/routes/services/[id]/_Services/_Services.svelte @@ -29,6 +29,7 @@ import Wordpress from './_Wordpress.svelte'; import Appwrite from './_Appwrite.svelte'; import Moodle from './_Moodle.svelte'; + import Searxng from './_Searxng.svelte'; const { id } = $page.params; $: isDisabled = @@ -402,6 +403,8 @@ {:else if service.type === 'glitchTip'} + {:else if service.type === 'searxng'} + {/if}