diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 64c664a9e..25e97c31a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,7 +20,8 @@ "svelte.svelte-vscode", "ardenivanov.svelte-intellisense", "Prisma.prisma", - "bradlc.vscode-tailwindcss" + "bradlc.vscode-tailwindcss", + "waderyan.gitblame" ], // Use 'forwardPorts' to make a list of ports inside the container available locally. "forwardPorts": [3000, 3001], diff --git a/apps/api/prisma/migrations/20220907092244_database_secrets/migration.sql b/apps/api/prisma/migrations/20220907092244_database_secrets/migration.sql new file mode 100644 index 000000000..53ff2d19e --- /dev/null +++ b/apps/api/prisma/migrations/20220907092244_database_secrets/migration.sql @@ -0,0 +1,13 @@ +-- CreateTable +CREATE TABLE "DatabaseSecret" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "value" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "databaseId" TEXT NOT NULL, + CONSTRAINT "DatabaseSecret_databaseId_fkey" FOREIGN KEY ("databaseId") REFERENCES "Database" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "DatabaseSecret_name_databaseId_key" ON "DatabaseSecret"("name", "databaseId"); diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 681293b53..df7516e01 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -328,6 +328,19 @@ model Database { settings DatabaseSettings? teams Team[] applicationConnectedDatabase ApplicationConnectedDatabase[] + databaseSecret DatabaseSecret[] +} + +model DatabaseSecret { + id String @id @default(cuid()) + name String + value String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + databaseId String + database Database @relation(fields: [databaseId], references: [id]) + + @@unique([name, databaseId]) } model DatabaseSettings { diff --git a/apps/api/prisma/seed.js b/apps/api/prisma/seed.js index 9fa55136f..eeb41b253 100644 --- a/apps/api/prisma/seed.js +++ b/apps/api/prisma/seed.js @@ -17,7 +17,6 @@ const algorithm = 'aes-256-ctr'; async function main() { // Enable registration for the first user - // Set initial HAProxy password const settingsFound = await prisma.setting.findFirst({}); if (!settingsFound) { await prisma.setting.create({ @@ -25,7 +24,8 @@ async function main() { isRegistrationEnabled: true, proxyPassword: encrypt(generatePassword()), proxyUser: cuid(), - arch: process.arch + arch: process.arch, + DNSServers: '1.1.1.1,8.8.8.8' } }); } else { diff --git a/apps/api/src/lib/buildPacks/common.ts b/apps/api/src/lib/buildPacks/common.ts index b6e57f52c..93b8ca076 100644 --- a/apps/api/src/lib/buildPacks/common.ts +++ b/apps/api/src/lib/buildPacks/common.ts @@ -89,6 +89,22 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st } ]; const phpVersions = [ + { + value: 'webdevops/php-apache:8.2', + label: 'webdevops/php-apache:8.2' + }, + { + value: 'webdevops/php-nginx:8.2', + label: 'webdevops/php-nginx:8.2' + }, + { + value: 'webdevops/php-apache:8.1', + label: 'webdevops/php-apache:8.1' + }, + { + value: 'webdevops/php-nginx:8.1', + label: 'webdevops/php-nginx:8.1' + }, { value: 'webdevops/php-apache:8.0', label: 'webdevops/php-apache:8.0' @@ -145,6 +161,22 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st value: 'webdevops/php-nginx:5.6', label: 'webdevops/php-nginx:5.6' }, + { + value: 'webdevops/php-apache:8.2-alpine', + label: 'webdevops/php-apache:8.2-alpine' + }, + { + value: 'webdevops/php-nginx:8.2-alpine', + label: 'webdevops/php-nginx:8.2-alpine' + }, + { + value: 'webdevops/php-apache:8.1-alpine', + label: 'webdevops/php-apache:8.1-alpine' + }, + { + value: 'webdevops/php-nginx:8.1-alpine', + label: 'webdevops/php-nginx:8.1-alpine' + }, { value: 'webdevops/php-apache:8.0-alpine', label: 'webdevops/php-apache:8.0-alpine' @@ -305,11 +337,11 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st payload.baseImage = 'denoland/deno:latest'; } if (buildPack === 'php') { - payload.baseImage = 'webdevops/php-apache:8.0-alpine'; + payload.baseImage = 'webdevops/php-apache:8.2-alpine'; payload.baseImages = phpVersions; } if (buildPack === 'laravel') { - payload.baseImage = 'webdevops/php-apache:8.0-alpine'; + payload.baseImage = 'webdevops/php-apache:8.2-alpine'; payload.baseBuildImage = 'node:18'; payload.baseBuildImages = nodeVersions; } diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 4f4f28917..7f271d900 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -21,7 +21,7 @@ import { scheduler } from './scheduler'; import { supportedServiceTypesAndVersions } from './services/supportedVersions'; import { includeServices } from './services/common'; -export const version = '3.9.2'; +export const version = '3.10.0'; export const isDev = process.env.NODE_ENV === 'development'; const algorithm = 'aes-256-ctr'; @@ -440,6 +440,12 @@ export const supportedDatabaseTypesAndVersions = [ baseImageARM: 'couchdb', versions: ['3.2.2', '3.1.2', '2.3.1'], versionsARM: ['3.2.2', '3.1.2', '2.3.1'] + }, + { + name: 'edgedb', + fancyName: 'EdgeDB', + baseImage: 'edgedb/edgedb', + versions: ['latest', '2.1', '2.0', '1.4'] } ]; @@ -714,24 +720,20 @@ export function generatePassword({ return password; } -export function generateDatabaseConfiguration( - database: any, - arch: string -): - | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - MYSQL_DATABASE: string; - MYSQL_PASSWORD: string; - MYSQL_ROOT_USER: string; - MYSQL_USER: string; - MYSQL_ROOT_PASSWORD: string; - }; - } +type DatabaseConfiguration = { + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + MYSQL_DATABASE: string; + MYSQL_PASSWORD: string; + MYSQL_ROOT_USER: string; + MYSQL_USER: string; + MYSQL_ROOT_PASSWORD: string; + }; +} | { volume: string; image: string; @@ -760,52 +762,57 @@ export function generateDatabaseConfiguration( }; } | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - POSTGRESQL_POSTGRES_PASSWORD: string; - POSTGRESQL_USERNAME: string; - POSTGRESQL_PASSWORD: string; - POSTGRESQL_DATABASE: string; - }; - } + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + POSTGRES_PASSWORD?: string; + POSTGRES_USER?: string; + POSTGRES_DB?: string; + POSTGRESQL_POSTGRES_PASSWORD?: string; + POSTGRESQL_USERNAME?: string; + POSTGRESQL_PASSWORD?: string; + POSTGRESQL_DATABASE?: string; + }; + } | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - POSTGRES_USER: string; - POSTGRES_PASSWORD: string; - POSTGRES_DB: string; - }; - } + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + REDIS_AOF_ENABLED: string; + REDIS_PASSWORD: string; + }; + } | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - REDIS_AOF_ENABLED: string; - REDIS_PASSWORD: string; - }; - } + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + COUCHDB_PASSWORD: string; + COUCHDB_USER: string; + }; + } | { - volume: string; - image: string; - command?: string; - ulimits: Record; - privatePort: number; - environmentVariables: { - COUCHDB_PASSWORD: string; - COUCHDB_USER: string; - }; - } { + volume: string; + image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + EDGEDB_SERVER_PASSWORD: string; + EDGEDB_SERVER_USER: string; + EDGEDB_SERVER_DATABASE: string; + EDGEDB_SERVER_TLS_CERT_MODE: string; + }; + } +export function generateDatabaseConfiguration(database: any, arch: string): DatabaseConfiguration { const { id, dbUser, @@ -837,7 +844,7 @@ export function generateDatabaseConfiguration( } return configuration; } else if (type === 'mariadb') { - const configuration = { + const configuration: DatabaseConfiguration = { privatePort: 3306, environmentVariables: { MARIADB_ROOT_USER: rootUser, @@ -855,7 +862,7 @@ export function generateDatabaseConfiguration( } return configuration; } else if (type === 'mongodb') { - const configuration = { + const configuration: DatabaseConfiguration = { privatePort: 27017, environmentVariables: { MONGODB_ROOT_USER: rootUser, @@ -874,7 +881,7 @@ export function generateDatabaseConfiguration( } return configuration; } else if (type === 'postgresql') { - const configuration = { + const configuration: DatabaseConfiguration = { privatePort: 5432, environmentVariables: { POSTGRESQL_POSTGRES_PASSWORD: rootUserPassword, @@ -896,7 +903,7 @@ export function generateDatabaseConfiguration( } return configuration; } else if (type === 'redis') { - const configuration = { + const configuration: DatabaseConfiguration = { privatePort: 6379, command: undefined, environmentVariables: { @@ -915,7 +922,7 @@ export function generateDatabaseConfiguration( } return configuration; } else if (type === 'couchdb') { - const configuration = { + const configuration: DatabaseConfiguration = { privatePort: 5984, environmentVariables: { COUCHDB_PASSWORD: dbUserPassword, @@ -928,7 +935,21 @@ export function generateDatabaseConfiguration( if (isARM(arch)) { configuration.volume = `${id}-${type}-data:/opt/couchdb/data`; } - return configuration; + return configuration + } else if (type === 'edgedb') { + const configuration: DatabaseConfiguration = { + privatePort: 5656, + environmentVariables: { + EDGEDB_SERVER_PASSWORD: rootUserPassword, + EDGEDB_SERVER_USER: rootUser, + EDGEDB_SERVER_DATABASE: defaultDatabase, + EDGEDB_SERVER_TLS_CERT_MODE: 'generate_self_signed' + }, + image: `${baseImage}:${version}`, + volume: `${id}-${type}-data:/var/lib/edgedb/data`, + ulimits: {} + }; + return configuration } } export function isARM(arch: string) { diff --git a/apps/api/src/lib/services/handlers.ts b/apps/api/src/lib/services/handlers.ts index d66747e67..f56657ce7 100644 --- a/apps/api/src/lib/services/handlers.ts +++ b/apps/api/src/lib/services/handlers.ts @@ -198,7 +198,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`; await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile); - const { volumeMounts } = persistentVolumes(id, persistentStorage, config.plausibleAnalytics) + const { volumeMounts } = persistentVolumes(id, persistentStorage, config) const composeFile: ComposeFile = { version: '3.8', @@ -333,6 +333,8 @@ async function startMinioService(request: FastifyRequest) { image: `${image}:${version}`, volumes: [`${id}-minio-data:/data`], environmentVariables: { + MINIO_SERVER_URL: fqdn, + MINIO_DOMAIN: getDomain(fqdn), MINIO_ROOT_USER: rootUser, MINIO_ROOT_PASSWORD: rootUserPassword, MINIO_BROWSER_REDIRECT_URL: fqdn @@ -852,7 +854,7 @@ async function startGhostService(request: FastifyRequest) { }); } - const { volumeMounts } = persistentVolumes(id, persistentStorage, config.ghost) + const { volumeMounts } = persistentVolumes(id, persistentStorage, config) const composeFile: ComposeFile = { version: '3.8', services: { @@ -1086,7 +1088,7 @@ async function startUmamiService(request: FastifyRequest) { FROM ${config.postgresql.image} COPY ./schema.postgresql.sql /docker-entrypoint-initdb.d/schema.postgresql.sql`; await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile); - const { volumeMounts } = persistentVolumes(id, persistentStorage, config.umami) + const { volumeMounts } = persistentVolumes(id, persistentStorage, config) const composeFile: ComposeFile = { version: '3.8', services: { @@ -1114,6 +1116,7 @@ async function startUmamiService(request: FastifyRequest) { }, volumes: volumeMounts }; + console.log(composeFile) const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await startServiceContainers(destinationDocker.id, composeFileDestination) @@ -1167,7 +1170,7 @@ async function startHasuraService(request: FastifyRequest) { }); } - const { volumeMounts } = persistentVolumes(id, persistentStorage, config.hasura) + const { volumeMounts } = persistentVolumes(id, persistentStorage, config) const composeFile: ComposeFile = { version: '3.8', services: { @@ -1272,7 +1275,7 @@ async function startFiderService(request: FastifyRequest) { config.fider.environmentVariables[secret.name] = secret.value; }); } - const { volumeMounts } = persistentVolumes(id, persistentStorage, config.fider) + const { volumeMounts } = persistentVolumes(id, persistentStorage, config) const composeFile: ComposeFile = { version: '3.8', services: { @@ -1880,7 +1883,7 @@ async function startMoodleService(request: FastifyRequest) { config.moodle.environmentVariables[secret.name] = secret.value; }); } - const { volumeMounts } = persistentVolumes(id, persistentStorage, config.moodle) + const { volumeMounts } = persistentVolumes(id, persistentStorage, config) const composeFile: ComposeFile = { version: '3.8', services: { @@ -2006,7 +2009,7 @@ async function startGlitchTipService(request: FastifyRequest) config.glitchTip.environmentVariables[secret.name] = secret.value; }); } - const { volumeMounts } = persistentVolumes(id, persistentStorage, config.glitchTip) + const { volumeMounts } = persistentVolumes(id, persistentStorage, config) const composeFile: ComposeFile = { version: '3.8', services: { @@ -2475,7 +2478,7 @@ async function startTaigaService(request: FastifyRequest) { TAIGA_SECRET_KEY: secretKey, } }, - + postgresql: { image: `postgres:12.3`, volumes: [`${id}-postgresql-data:/var/lib/postgresql/data`], diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index e37590381..1d00e94d1 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -525,9 +525,7 @@ export async function checkDomain(request: FastifyRequest) { } export async function checkDNS(request: FastifyRequest) { try { - const { id } = request.params - let { exposePort, fqdn, forceSave, dualCerts } = request.body if (!fqdn) { return {} diff --git a/apps/api/src/routes/api/v1/base/index.ts b/apps/api/src/routes/api/v1/base/index.ts index 76735a032..f1b64baf3 100644 --- a/apps/api/src/routes/api/v1/base/index.ts +++ b/apps/api/src/routes/api/v1/base/index.ts @@ -11,6 +11,7 @@ const root: FastifyPluginAsync = async (fastify): Promise => { version, whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true', whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON, + isRegistrationEnabled: settings.isRegistrationEnabled, } } catch ({ status, message }) { return errorHandler({ status, message }) diff --git a/apps/api/src/routes/api/v1/databases/handlers.ts b/apps/api/src/routes/api/v1/databases/handlers.ts index d646a036f..5e78dbfe9 100644 --- a/apps/api/src/routes/api/v1/databases/handlers.ts +++ b/apps/api/src/routes/api/v1/databases/handlers.ts @@ -3,10 +3,10 @@ import type { FastifyRequest } from 'fastify'; import { FastifyReply } from 'fastify'; import yaml from 'js-yaml'; import fs from 'fs/promises'; -import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; +import { ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; import { day } from '../../../../lib/dayjs'; -import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types'; +import { DeleteDatabaseSecret, GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSecret, SaveDatabaseSettings, SaveVersion } from '../../../../types'; import { DeleteDatabase, SaveDatabaseType } from './types'; export async function listDatabases(request: FastifyRequest) { @@ -61,16 +61,18 @@ export async function getDatabaseStatus(request: FastifyRequest) { where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, include: { destinationDocker: true, settings: true } }); - const { destinationDockerId, destinationDocker } = database; - if (destinationDockerId) { - try { - const { stdout } = await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker inspect --format '{{json .State}}' ${id}` }) + if (database) { + const { destinationDockerId, destinationDocker } = database; + if (destinationDockerId) { + try { + const { stdout } = await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker inspect --format '{{json .State}}' ${id}` }) - if (JSON.parse(stdout).Running) { - isRunning = true; + if (JSON.parse(stdout).Running) { + isRunning = true; + } + } catch (error) { + // } - } catch (error) { - // } } return { @@ -220,7 +222,7 @@ export async function startDatabase(request: FastifyRequest) { const database = await prisma.database.findFirst({ where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { destinationDocker: true, settings: true } + include: { destinationDocker: true, settings: true, databaseSecret: true } }); const { arch } = await listSettings(); if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); @@ -230,7 +232,8 @@ export async function startDatabase(request: FastifyRequest) { destinationDockerId, destinationDocker, publicPort, - settings: { isPublic } + settings: { isPublic }, + databaseSecret } = database; const { privatePort, command, environmentVariables, image, volume, ulimits } = generateDatabaseConfiguration(database, arch); @@ -240,7 +243,11 @@ export async function startDatabase(request: FastifyRequest) { const labels = await makeLabelForStandaloneDatabase({ id, image, volume }); const { workdir } = await createDirectories({ repository: type, buildId: id }); - + if (databaseSecret.length > 0) { + databaseSecret.forEach((secret) => { + environmentVariables[secret.name] = decrypt(secret.value); + }); + } const composeFile: ComposeFile = { version: '3.8', services: { @@ -248,20 +255,11 @@ export async function startDatabase(request: FastifyRequest) { container_name: id, image, command, - networks: [network], environment: environmentVariables, volumes: [volume], ulimits, labels, - restart: 'always', - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultComposeConfiguration(network), } }, networks: { @@ -271,25 +269,16 @@ export async function startDatabase(request: FastifyRequest) { }, volumes: { [volumeName]: { - external: true + name: volumeName, } } }; - const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - try { - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker volume create ${volumeName}` }) - } catch (error) { } - try { - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` }) - if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); - return {}; - } catch (error) { - throw { - error - }; - } + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` }) + if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); + return {}; + } catch ({ status, message }) { return errorHandler({ status, message }) } @@ -376,6 +365,7 @@ export async function deleteDatabase(request: FastifyRequest) { } } await prisma.databaseSettings.deleteMany({ where: { databaseId: id } }); + await prisma.databaseSecret.deleteMany({ where: { databaseId: id } }); await prisma.database.delete({ where: { id } }); return {} } catch ({ status, message }) { @@ -471,4 +461,69 @@ export async function saveDatabaseSettings(request: FastifyRequest) { + try { + const { id } = request.params + let secrets = await prisma.databaseSecret.findMany({ + where: { databaseId: id }, + orderBy: { createdAt: 'desc' } + }); + secrets = secrets.map((secret) => { + secret.value = decrypt(secret.value); + return secret; + }); + + return { + secrets + } + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} + +export async function saveDatabaseSecret(request: FastifyRequest, reply: FastifyReply) { + try { + const { id } = request.params + let { name, value, isNew } = request.body + + if (isNew) { + const found = await prisma.databaseSecret.findFirst({ where: { name, databaseId: id } }); + if (found) { + throw `Secret ${name} already exists.` + } else { + value = encrypt(value.trim()); + await prisma.databaseSecret.create({ + data: { name, value, database: { connect: { id } } } + }); + } + } else { + value = encrypt(value.trim()); + const found = await prisma.databaseSecret.findFirst({ where: { databaseId: id, name } }); + + if (found) { + await prisma.databaseSecret.updateMany({ + where: { databaseId: id, name }, + data: { value } + }); + } else { + await prisma.databaseSecret.create({ + data: { name, value, database: { connect: { id } } } + }); + } + } + return reply.code(201).send() + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} +export async function deleteDatabaseSecret(request: FastifyRequest) { + try { + const { id } = request.params + const { name } = request.body + await prisma.databaseSecret.deleteMany({ where: { databaseId: id, name } }); + return {} + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} diff --git a/apps/api/src/routes/api/v1/databases/index.ts b/apps/api/src/routes/api/v1/databases/index.ts index f15fdd073..8f269c8b5 100644 --- a/apps/api/src/routes/api/v1/databases/index.ts +++ b/apps/api/src/routes/api/v1/databases/index.ts @@ -1,8 +1,9 @@ import { FastifyPluginAsync } from 'fastify'; -import { deleteDatabase, getDatabase, getDatabaseLogs, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers'; +import { deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers'; -import type { DeleteDatabase, GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types'; -import type { SaveDatabaseType } from './types'; +import type { OnlyId } from '../../../../types'; + +import type { DeleteDatabase, SaveDatabaseType, DeleteDatabaseSecret, GetDatabaseLogs, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSecret, SaveDatabaseSettings, SaveVersion } from './types'; const root: FastifyPluginAsync = async (fastify): Promise => { fastify.addHook('onRequest', async (request) => { @@ -19,6 +20,10 @@ const root: FastifyPluginAsync = async (fastify): Promise => { fastify.post('/:id/settings', async (request) => await saveDatabaseSettings(request)); + fastify.get('/:id/secrets', async (request) => await getDatabaseSecrets(request)); + fastify.post('/:id/secrets', async (request, reply) => await saveDatabaseSecret(request, reply)); + fastify.delete('/:id/secrets', async (request) => await deleteDatabaseSecret(request)); + fastify.get('/:id/configuration/type', async (request) => await getDatabaseTypes(request)); fastify.post('/:id/configuration/type', async (request, reply) => await saveDatabaseType(request, reply)); diff --git a/apps/api/src/routes/api/v1/databases/types.ts b/apps/api/src/routes/api/v1/databases/types.ts index a56a45c23..ba9b0b8e9 100644 --- a/apps/api/src/routes/api/v1/databases/types.ts +++ b/apps/api/src/routes/api/v1/databases/types.ts @@ -5,4 +5,51 @@ export interface SaveDatabaseType extends OnlyId { } export interface DeleteDatabase extends OnlyId { Body: { force: string } -} \ No newline at end of file +} +export interface SaveVersion extends OnlyId { + Body: { + version: string + } +} +export interface SaveDatabaseDestination extends OnlyId { + Body: { + destinationId: string + } +} +export interface GetDatabaseLogs extends OnlyId { + Querystring: { + since: number + } +} +export interface SaveDatabase extends OnlyId { + Body: { + name: string, + defaultDatabase: string, + dbUser: string, + dbUserPassword: string, + rootUser: string, + rootUserPassword: string, + version: string, + isRunning: boolean + } +} +export interface SaveDatabaseSettings extends OnlyId { + Body: { + isPublic: boolean, + appendOnly: boolean + } +} + +export interface SaveDatabaseSecret extends OnlyId { + Body: { + name: string, + value: string, + isNew: string, + } +} +export interface DeleteDatabaseSecret extends OnlyId { + Body: { + name: string, + } +} + diff --git a/apps/api/src/routes/api/v1/handlers.ts b/apps/api/src/routes/api/v1/handlers.ts index 702ad31a8..09dfbea48 100644 --- a/apps/api/src/routes/api/v1/handlers.ts +++ b/apps/api/src/routes/api/v1/handlers.ts @@ -52,9 +52,7 @@ export async function update(request: FastifyRequest) { const { latestVersion } = request.body; try { if (!isDev) { - const { isAutoUpdateEnabled } = (await prisma.setting.findFirst()) || { - isAutoUpdateEnabled: false - }; + const { isAutoUpdateEnabled } = await prisma.setting.findFirst(); await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`); await asyncExecShell(`env | grep COOLIFY > .env`); await asyncExecShell( @@ -113,20 +111,31 @@ export async function showDashboard(request: FastifyRequest) { const teamId = request.user.teamId; const applications = await prisma.application.findMany({ where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { settings: true } + include: { settings: true, destinationDocker: true, teams: true } }); const databases = await prisma.database.findMany({ where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { settings: true } + include: { settings: true, destinationDocker: true, teams: true } }); const services = await prisma.service.findMany({ - where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } } + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, teams: true } + }); + const gitSources = await prisma.gitSource.findMany({ + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { teams: true } + }); + const destinations = await prisma.destinationDocker.findMany({ + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { teams: true } }); const settings = await listSettings(); return { applications, databases, services, + gitSources, + destinations, settings, }; } catch ({ status, message }) { diff --git a/apps/api/src/routes/webhooks/github/handlers.ts b/apps/api/src/routes/webhooks/github/handlers.ts index de288022d..ba15c1d60 100644 --- a/apps/api/src/routes/webhooks/github/handlers.ts +++ b/apps/api/src/routes/webhooks/github/handlers.ts @@ -173,16 +173,16 @@ export async function gitHubEvents(request: FastifyRequest): Promi where: { id: application.id }, data: { updatedAt: new Date() } }); - if (application.connectedDatabase && pullmergeRequestAction === 'opened' || pullmergeRequestAction === 'reopened') { - // Coolify hosted database - if (application.connectedDatabase.databaseId) { - const databaseId = application.connectedDatabase.databaseId; - const database = await prisma.database.findUnique({ where: { id: databaseId } }); - if (database) { - await createdBranchDatabase(database, application.connectedDatabase.hostedDatabaseDBName, pullmergeRequestId); - } - } - } + // if (application.connectedDatabase && pullmergeRequestAction === 'opened' || pullmergeRequestAction === 'reopened') { + // // Coolify hosted database + // if (application.connectedDatabase.databaseId) { + // const databaseId = application.connectedDatabase.databaseId; + // const database = await prisma.database.findUnique({ where: { id: databaseId } }); + // if (database) { + // await createdBranchDatabase(database, application.connectedDatabase.hostedDatabaseDBName, pullmergeRequestId); + // } + // } + // } await prisma.build.create({ data: { id: buildId, diff --git a/apps/api/src/types.ts b/apps/api/src/types.ts index 71f3db158..acb44c8e0 100644 --- a/apps/api/src/types.ts +++ b/apps/api/src/types.ts @@ -1,38 +1,4 @@ export interface OnlyId { Params: { id: string }, } -export interface SaveVersion extends OnlyId { - Body: { - version: string - } -} -export interface SaveDatabaseDestination extends OnlyId { - Body: { - destinationId: string - } -} -export interface GetDatabaseLogs extends OnlyId { - Querystring: { - since: number - } -} -export interface SaveDatabase extends OnlyId { - Body: { - name: string, - defaultDatabase: string, - dbUser: string, - dbUserPassword: string, - rootUser: string, - rootUserPassword: string, - version: string, - isRunning: boolean - } -} -export interface SaveDatabaseSettings extends OnlyId { - Body: { - isPublic: boolean, - appendOnly: boolean - } -} - diff --git a/apps/ui/src/lib/components/svg/databases/DatabaseIcons.svelte b/apps/ui/src/lib/components/svg/databases/DatabaseIcons.svelte index 1ef5271c4..4a00860af 100644 --- a/apps/ui/src/lib/components/svg/databases/DatabaseIcons.svelte +++ b/apps/ui/src/lib/components/svg/databases/DatabaseIcons.svelte @@ -16,4 +16,6 @@ {:else if type === 'couchdb'} +{:else if type === 'edgedb'} + {/if} diff --git a/apps/ui/src/lib/components/svg/databases/EdgeDB.svelte b/apps/ui/src/lib/components/svg/databases/EdgeDB.svelte new file mode 100644 index 000000000..57fdebed5 --- /dev/null +++ b/apps/ui/src/lib/components/svg/databases/EdgeDB.svelte @@ -0,0 +1,22 @@ + + + diff --git a/apps/ui/src/lib/components/svg/databases/index.ts b/apps/ui/src/lib/components/svg/databases/index.ts index 1b981d61c..e200b5311 100644 --- a/apps/ui/src/lib/components/svg/databases/index.ts +++ b/apps/ui/src/lib/components/svg/databases/index.ts @@ -6,5 +6,6 @@ export { default as MongoDB } from './MongoDB.svelte'; export { default as MySQL } from './MySQL.svelte'; export { default as PostgreSQL } from './PostgreSQL.svelte'; export { default as Redis } from './Redis.svelte'; +export { default as EdgeDB } from './EdgeDB.svelte'; diff --git a/apps/ui/src/lib/store.ts b/apps/ui/src/lib/store.ts index 4114026d5..5355d22c8 100644 --- a/apps/ui/src/lib/store.ts +++ b/apps/ui/src/lib/store.ts @@ -3,7 +3,7 @@ import cuid from 'cuid'; import { writable, readable, type Writable } from 'svelte/store'; interface AppSession { - registrationEnabled: boolean; + isRegistrationEnabled: boolean; ipv4: string | null, ipv6: string | null, version: string | null, @@ -28,6 +28,7 @@ interface AddToast { } export const loginEmail: Writable = writable() export const appSession: Writable = writable({ + isRegistrationEnabled: false, ipv4: null, ipv6: null, version: null, diff --git a/apps/ui/src/routes/_NewResource.svelte b/apps/ui/src/routes/_NewResource.svelte new file mode 100644 index 000000000..ccc15e8d9 --- /dev/null +++ b/apps/ui/src/routes/_NewResource.svelte @@ -0,0 +1,150 @@ + + + diff --git a/apps/ui/src/routes/__layout.svelte b/apps/ui/src/routes/__layout.svelte index 9f1abcfd0..10a6b1de4 100644 --- a/apps/ui/src/routes/__layout.svelte +++ b/apps/ui/src/routes/__layout.svelte @@ -66,7 +66,7 @@ + + + +
+
{$t('index.applications')}
+ {#if $appSession.isAdmin} + + {/if} +
+ diff --git a/apps/ui/src/routes/applications/index.svelte b/apps/ui/src/routes/applications/index.svelte index 51d2fd74e..8136f76e8 100644 --- a/apps/ui/src/routes/applications/index.svelte +++ b/apps/ui/src/routes/applications/index.svelte @@ -1,145 +1,4 @@ - - - - -
- diff --git a/apps/ui/src/routes/databases/[id]/_DatabaseLinks.svelte b/apps/ui/src/routes/databases/[id]/_DatabaseLinks.svelte index 50eed98b2..f1792cb6b 100644 --- a/apps/ui/src/routes/databases/[id]/_DatabaseLinks.svelte +++ b/apps/ui/src/routes/databases/[id]/_DatabaseLinks.svelte @@ -3,6 +3,7 @@ import Clickhouse from '$lib/components/svg/databases/Clickhouse.svelte'; import CouchDb from '$lib/components/svg/databases/CouchDB.svelte'; + import EdgeDb from '$lib/components/svg/databases/EdgeDB.svelte'; import MariaDb from '$lib/components/svg/databases/MariaDB.svelte'; import MongoDb from '$lib/components/svg/databases/MongoDB.svelte'; import MySql from '$lib/components/svg/databases/MySQL.svelte'; @@ -25,5 +26,7 @@ {:else if database.type === 'redis'} + {:else if database.type === 'edgedb'} + {/if} diff --git a/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte b/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte index 67a5771a7..826ac0821 100644 --- a/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte +++ b/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte @@ -12,6 +12,7 @@ import PostgreSql from './_PostgreSQL.svelte'; import Redis from './_Redis.svelte'; import CouchDb from './_CouchDb.svelte'; + import EdgeDB from './_EdgeDB.svelte'; import { post } from '$lib/api'; import { t } from '$lib/translations'; import { errorNotification } from '$lib/common'; @@ -36,8 +37,10 @@ databaseDefault = database.defaultDatabase; databaseDbUser = database.dbUser; databaseDbUserPassword = database.dbUserPassword; - if (database.type === 'mongodb') { - databaseDefault = '?readPreference=primary&ssl=false'; + if (database.type === 'mongodb' || database.type === 'edgedb') { + if (database.type === 'mongodb') { + databaseDefault = '?readPreference=primary&ssl=false'; + } databaseDbUser = database.rootUser; databaseDbUserPassword = database.rootUserPassword; } else if (database.type === 'redis') { @@ -190,6 +193,8 @@ {:else if database.type === 'couchdb'} + {:else if database.type === 'edgedb'} + {/if}
diff --git a/apps/ui/src/routes/databases/[id]/_Databases/_EdgeDB.svelte b/apps/ui/src/routes/databases/[id]/_Databases/_EdgeDB.svelte new file mode 100644 index 000000000..23110c1f1 --- /dev/null +++ b/apps/ui/src/routes/databases/[id]/_Databases/_EdgeDB.svelte @@ -0,0 +1,54 @@ + + +
+
EdgeDB
+
+
+
+ + +
+
+ + +
+
+ + +
+
diff --git a/apps/ui/src/routes/databases/[id]/_Secret.svelte b/apps/ui/src/routes/databases/[id]/_Secret.svelte new file mode 100644 index 000000000..f67a377e4 --- /dev/null +++ b/apps/ui/src/routes/databases/[id]/_Secret.svelte @@ -0,0 +1,186 @@ + + + + + + + + + + + + + {#if isNewSecret} +
+ +
+ {:else} +
+
+ +
+ {#if !isPRMRSecret} +
+ +
+ {/if} +
+ {/if} + diff --git a/apps/ui/src/routes/databases/[id]/__layout.svelte b/apps/ui/src/routes/databases/[id]/__layout.svelte index d170a993c..569929516 100644 --- a/apps/ui/src/routes/databases/[id]/__layout.svelte +++ b/apps/ui/src/routes/databases/[id]/__layout.svelte @@ -58,7 +58,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { errorNotification, handlerNotFoundLoad } from '$lib/common'; - import { appSession, status, disabledButton } from '$lib/store'; + import { appSession, status, isDeploymentEnabled } from '$lib/store'; import DeleteIcon from '$lib/components/DeleteIcon.svelte'; import { onDestroy, onMount } from 'svelte'; import Tooltip from '$lib/components/Tooltip.svelte'; @@ -68,7 +68,7 @@ import DatabaseLinks from './_DatabaseLinks.svelte'; let statusInterval: any = false; let forceDelete = false; - $disabledButton = !$appSession.isAdmin; + $isDeploymentEnabled = !$appSession.isAdmin; async function deleteDatabase(force: boolean) { const sure = confirm(`Are you sure you would like to delete '${database.name}'?`); @@ -76,7 +76,7 @@ import DatabaseLinks from './_DatabaseLinks.svelte'; $status.database.initialLoading = true; try { await del(`/databases/${database.id}`, { id: database.id, force }); - return await goto('/databases'); + return await goto('/', { replaceState: true }); } catch (error) { return errorNotification(error); } finally { @@ -88,12 +88,15 @@ import DatabaseLinks from './_DatabaseLinks.svelte'; const sure = confirm($t('database.confirm_stop', { name: database.name })); if (sure) { $status.database.initialLoading = true; + $status.database.loading = true; try { await post(`/databases/${database.id}/stop`, {}); } catch (error) { return errorNotification(error); } finally { $status.database.initialLoading = false; + $status.database.loading = false; + await getStatus(); } } } diff --git a/apps/ui/src/routes/databases/[id]/configuration/type.svelte b/apps/ui/src/routes/databases/[id]/configuration/type.svelte index 006ad0a14..148b5a3f7 100644 --- a/apps/ui/src/routes/databases/[id]/configuration/type.svelte +++ b/apps/ui/src/routes/databases/[id]/configuration/type.svelte @@ -31,6 +31,7 @@ const { id } = $page.params; const from = $page.url.searchParams.get('from'); + import { goto } from '$app/navigation'; import { get, post } from '$lib/api'; import { t } from '$lib/translations'; @@ -55,7 +56,7 @@
handleSubmit(type.name)}>
diff --git a/apps/ui/src/routes/databases/[id]/secrets.svelte b/apps/ui/src/routes/databases/[id]/secrets.svelte new file mode 100644 index 000000000..73871ed05 --- /dev/null +++ b/apps/ui/src/routes/databases/[id]/secrets.svelte @@ -0,0 +1,109 @@ + + + + +
+
+
Secrets
+ {database.name} +
+
+
+ + + + + + + + + + + {#each secrets as secret} + {#key secret.id} + + + + {/key} + {/each} + + + + +
{$t('forms.name')}{$t('forms.value')}Need during buildtime?{$t('forms.action')}
+

Paste .env file

+
+