fix: update prisma

feat(beta): database branching
This commit is contained in:
Andras Bacsai
2022-09-05 15:41:32 +02:00
parent c9aecd51f3
commit 5a15e64471
12 changed files with 707 additions and 687 deletions

View File

@@ -15,7 +15,7 @@
}, },
"dependencies": { "dependencies": {
"@breejs/ts-worker": "2.0.0", "@breejs/ts-worker": "2.0.0",
"@fastify/autoload": "5.2.0", "@fastify/autoload": "5.3.1",
"@fastify/cookie": "8.1.0", "@fastify/cookie": "8.1.0",
"@fastify/cors": "8.1.0", "@fastify/cors": "8.1.0",
"@fastify/env": "4.1.0", "@fastify/env": "4.1.0",
@@ -23,7 +23,7 @@
"@fastify/static": "6.5.0", "@fastify/static": "6.5.0",
"@iarna/toml": "2.2.5", "@iarna/toml": "2.2.5",
"@ladjs/graceful": "3.0.2", "@ladjs/graceful": "3.0.2",
"@prisma/client": "3.15.2", "@prisma/client": "4.3.1",
"axios": "0.27.2", "axios": "0.27.2",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bree": "9.1.2", "bree": "9.1.2",
@@ -37,7 +37,7 @@
"fastify": "4.5.3", "fastify": "4.5.3",
"fastify-plugin": "4.2.1", "fastify-plugin": "4.2.1",
"generate-password": "1.7.0", "generate-password": "1.7.0",
"got": "12.3.1", "got": "12.4.1",
"is-ip": "5.0.0", "is-ip": "5.0.0",
"is-port-reachable": "4.0.0", "is-port-reachable": "4.0.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
@@ -52,20 +52,20 @@
"unique-names-generator": "4.7.1" "unique-names-generator": "4.7.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.7.13", "@types/node": "18.7.15",
"@types/node-os-utils": "1.3.0", "@types/node-os-utils": "1.3.0",
"@typescript-eslint/eslint-plugin": "5.35.1", "@typescript-eslint/eslint-plugin": "5.36.1",
"@typescript-eslint/parser": "5.35.1", "@typescript-eslint/parser": "5.36.1",
"esbuild": "0.15.5", "esbuild": "0.15.7",
"eslint": "8.23.0", "eslint": "8.23.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.5.0",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "4.2.1",
"nodemon": "2.0.19", "nodemon": "2.0.19",
"prettier": "2.7.1", "prettier": "2.7.1",
"prisma": "3.15.2", "prisma": "4.3.1",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"tsconfig-paths": "4.1.0", "tsconfig-paths": "4.1.0",
"typescript": "4.7.4" "typescript": "4.8.2"
}, },
"prisma": { "prisma": {
"seed": "node prisma/seed.js" "seed": "node prisma/seed.js"

View File

@@ -0,0 +1,20 @@
/*
Warnings:
- You are about to alter the column `time` on the `BuildLog` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`.
*/
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_BuildLog" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT,
"buildId" TEXT NOT NULL,
"line" TEXT NOT NULL,
"time" BIGINT NOT NULL
);
INSERT INTO "new_BuildLog" ("applicationId", "buildId", "id", "line", "time") SELECT "applicationId", "buildId", "id", "line", "time" FROM "BuildLog";
DROP TABLE "BuildLog";
ALTER TABLE "new_BuildLog" RENAME TO "BuildLog";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -0,0 +1,20 @@
-- CreateTable
CREATE TABLE "ApplicationConnectedDatabase" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT NOT NULL,
"databaseId" TEXT,
"hostedDatabaseType" TEXT,
"hostedDatabaseHost" TEXT,
"hostedDatabasePort" INTEGER,
"hostedDatabaseName" TEXT,
"hostedDatabaseUser" TEXT,
"hostedDatabasePassword" TEXT,
"hostedDatabaseDBName" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "ApplicationConnectedDatabase_databaseId_fkey" FOREIGN KEY ("databaseId") REFERENCES "Database" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "ApplicationConnectedDatabase_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "ApplicationConnectedDatabase_applicationId_key" ON "ApplicationConnectedDatabase"("applicationId");

View File

@@ -117,6 +117,24 @@ model Application {
settings ApplicationSettings? settings ApplicationSettings?
secrets Secret[] secrets Secret[]
teams Team[] teams Team[]
connectedDatabase ApplicationConnectedDatabase?
}
model ApplicationConnectedDatabase {
id String @id @default(cuid())
applicationId String @unique
databaseId String?
hostedDatabaseType String?
hostedDatabaseHost String?
hostedDatabasePort Int?
hostedDatabaseName String?
hostedDatabaseUser String?
hostedDatabasePassword String?
hostedDatabaseDBName String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
database Database? @relation(fields: [databaseId], references: [id])
application Application @relation(fields: [applicationId], references: [id])
} }
model ApplicationSettings { model ApplicationSettings {
@@ -187,7 +205,7 @@ model BuildLog {
applicationId String? applicationId String?
buildId String buildId String
line String line String
time Int time BigInt
} }
model Build { model Build {
@@ -292,22 +310,23 @@ model GitlabApp {
} }
model Database { model Database {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
publicPort Int? publicPort Int?
defaultDatabase String? defaultDatabase String?
type String? type String?
version String? version String?
dbUser String? dbUser String?
dbUserPassword String? dbUserPassword String?
rootUser String? rootUser String?
rootUserPassword String? rootUserPassword 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])
settings DatabaseSettings? settings DatabaseSettings?
teams Team[] teams Team[]
applicationConnectedDatabase ApplicationConnectedDatabase[]
} }
model DatabaseSettings { model DatabaseSettings {

View File

@@ -156,7 +156,8 @@ export async function getApplicationFromDB(id: string, teamId: string) {
settings: true, settings: true,
gitSource: { include: { githubApp: true, gitlabApp: true } }, gitSource: { include: { githubApp: true, gitlabApp: true } },
secrets: true, secrets: true,
persistentStorage: true persistentStorage: true,
connectedDatabase: true
} }
}); });
if (!application) { if (!application) {
@@ -190,7 +191,8 @@ export async function getApplicationFromDBWebhook(projectId: number, branch: str
settings: true, settings: true,
gitSource: { include: { githubApp: true, gitlabApp: true } }, gitSource: { include: { githubApp: true, gitlabApp: true } },
secrets: true, secrets: true,
persistentStorage: true persistentStorage: true,
connectedDatabase: true
} }
}); });
if (applications.length === 0) { if (applications.length === 0) {
@@ -242,7 +244,8 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
denoOptions, denoOptions,
baseImage, baseImage,
baseBuildImage, baseBuildImage,
deploymentType deploymentType,
baseDatabaseBranch
} = request.body } = request.body
if (port) port = Number(port); if (port) port = Number(port);
if (exposePort) { if (exposePort) {
@@ -263,22 +266,43 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
dockerFileLocation, dockerFileLocation,
denoMainFile denoMainFile
}); });
await prisma.application.update({ if (baseDatabaseBranch) {
where: { id }, await prisma.application.update({
data: { where: { id },
name, data: {
fqdn, name,
exposePort, fqdn,
pythonWSGI, exposePort,
pythonModule, pythonWSGI,
pythonVariable, pythonModule,
denoOptions, pythonVariable,
baseImage, denoOptions,
baseBuildImage, baseImage,
deploymentType, baseBuildImage,
...defaultConfiguration deploymentType,
} ...defaultConfiguration,
}); connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
}
});
} else {
await prisma.application.update({
where: { id },
data: {
name,
fqdn,
exposePort,
pythonWSGI,
pythonModule,
pythonVariable,
denoOptions,
baseImage,
baseBuildImage,
deploymentType,
...defaultConfiguration
}
});
}
return reply.code(201).send(); return reply.code(201).send();
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -289,7 +313,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
export async function saveApplicationSettings(request: FastifyRequest<SaveApplicationSettings>, reply: FastifyReply) { export async function saveApplicationSettings(request: FastifyRequest<SaveApplicationSettings>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot } = request.body const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot, isDBBranching } = request.body
// const isDouble = await checkDoubleBranch(branch, projectId); // const isDouble = await checkDoubleBranch(branch, projectId);
// if (isDouble && autodeploy) { // if (isDouble && autodeploy) {
// await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } }) // await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
@@ -297,7 +321,7 @@ export async function saveApplicationSettings(request: FastifyRequest<SaveApplic
// } // }
await prisma.application.update({ await prisma.application.update({
where: { id }, where: { id },
data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot } } }, data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot, isDBBranching } } },
include: { destinationDocker: true } include: { destinationDocker: true }
}); });
return reply.code(201).send(); return reply.code(201).send();
@@ -478,6 +502,7 @@ export async function deleteApplication(request: FastifyRequest<DeleteApplicatio
await prisma.build.deleteMany({ where: { applicationId: id } }); await prisma.build.deleteMany({ where: { applicationId: id } });
await prisma.secret.deleteMany({ where: { applicationId: id } }); await prisma.secret.deleteMany({ where: { applicationId: id } });
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } }); await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
await prisma.applicationConnectedDatabase.deleteMany({ where: { applicationId: id } });
if (teamId === '0') { if (teamId === '0') {
await prisma.application.deleteMany({ where: { id } }); await prisma.application.deleteMany({ where: { id } });
} else { } else {
@@ -738,6 +763,17 @@ export async function saveBuildPack(request, reply) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveConnectedDatabase(request, reply) {
try {
const { id } = request.params
const { databaseId, type } = request.body
console.log({ databaseId, type })
await prisma.application.update({ where: { id }, data: { connectedDatabase: { upsert: { create: { database: { connect: { id: databaseId } }, hostedDatabaseType: type }, update: { database: { connect: { id: databaseId } }, hostedDatabaseType: type } } } } })
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function getSecrets(request: FastifyRequest<OnlyId>) { export async function getSecrets(request: FastifyRequest<OnlyId>) {
try { try {
@@ -986,11 +1022,13 @@ export async function getBuildIdLogs(request: FastifyRequest<GetBuildIdLogs>) {
where: { buildId, time: { gt: sequence } }, where: { buildId, time: { gt: sequence } },
orderBy: { time: 'asc' } orderBy: { time: 'asc' }
}); });
const data = await prisma.build.findFirst({ where: { id: buildId } }); const data = await prisma.build.findFirst({ where: { id: buildId } });
const createdAt = day(data.createdAt).utc(); const createdAt = day(data.createdAt).utc();
return { return {
logs, logs: logs.map(log => {
log.time = Number(log.time)
return log
}),
took: day().diff(createdAt) / 1000, took: day().diff(createdAt) / 1000,
status: data?.status || 'queued' status: data?.status || 'queued'
} }
@@ -1067,3 +1105,58 @@ export async function cancelDeployment(request: FastifyRequest<CancelDeployment>
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function createdBranchDatabase(database: any, baseDatabaseBranch: string, pullmergeRequestId: string) {
try {
if (!baseDatabaseBranch) return
const { id, type, destinationDockerId, rootUser, rootUserPassword, dbUser } = database;
if (destinationDockerId) {
if (type === 'postgresql') {
const decryptedRootUserPassword = decrypt(rootUserPassword);
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} pg_dump -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/${baseDatabaseBranch}" --encoding=UTF8 --schema-only -f /tmp/${baseDatabaseBranch}.dump`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "CREATE DATABASE branch_${pullmergeRequestId}"`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} psql -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/branch_${pullmergeRequestId}" -f /tmp/${baseDatabaseBranch}.dump`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "ALTER DATABASE branch_${pullmergeRequestId} OWNER TO ${dbUser}"`
})
}
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function removeBranchDatabase(database: any, pullmergeRequestId: string) {
try {
const { id, type, destinationDockerId, rootUser, rootUserPassword } = database;
if (destinationDockerId) {
if (type === 'postgresql') {
const decryptedRootUserPassword = decrypt(rootUserPassword);
// Terminate all connections to the database
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'branch_${pullmergeRequestId}' AND pid <> pg_backend_pid();"`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "DROP DATABASE branch_${pullmergeRequestId}"`
})
}
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}

View File

@@ -1,6 +1,6 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { OnlyId } from '../../../../types'; import { OnlyId } from '../../../../types';
import { cancelDeployment, checkDNS, checkDomain, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, restartApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication } from './handlers'; import { cancelDeployment, checkDNS, checkDomain, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, restartApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication } from './handlers';
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types'; import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
@@ -55,6 +55,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/:id/configuration/buildpack', async (request) => await getBuildPack(request)); fastify.get('/:id/configuration/buildpack', async (request) => await getBuildPack(request));
fastify.post('/:id/configuration/buildpack', async (request, reply) => await saveBuildPack(request, reply)); fastify.post('/:id/configuration/buildpack', async (request, reply) => await saveBuildPack(request, reply));
fastify.post('/:id/configuration/database', async (request, reply) => await saveConnectedDatabase(request, reply));
fastify.get<OnlyId>('/:id/configuration/sshkey', async (request) => await getGitLabSSHKey(request)); fastify.get<OnlyId>('/:id/configuration/sshkey', async (request) => await getGitLabSSHKey(request));
fastify.post<OnlyId>('/:id/configuration/sshkey', async (request, reply) => await saveGitLabSSHKey(request, reply)); fastify.post<OnlyId>('/:id/configuration/sshkey', async (request, reply) => await saveGitLabSSHKey(request, reply));

View File

@@ -20,12 +20,13 @@ export interface SaveApplication extends OnlyId {
denoOptions: string, denoOptions: string,
baseImage: string, baseImage: string,
baseBuildImage: string, baseBuildImage: string,
deploymentType: string deploymentType: string,
baseDatabaseBranch: string
} }
} }
export interface SaveApplicationSettings extends OnlyId { export interface SaveApplicationSettings extends OnlyId {
Querystring: { domain: string; }; Querystring: { domain: string; };
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; }; Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean };
} }
export interface DeleteApplication extends OnlyId { export interface DeleteApplication extends OnlyId {
Querystring: { domain: string; }; Querystring: { domain: string; };

View File

@@ -3,8 +3,7 @@ import cuid from "cuid";
import crypto from "crypto"; import crypto from "crypto";
import { encrypt, errorHandler, getUIUrl, isDev, prisma } from "../../../lib/common"; import { encrypt, errorHandler, getUIUrl, isDev, prisma } from "../../../lib/common";
import { checkContainer, removeContainer } from "../../../lib/docker"; import { checkContainer, removeContainer } from "../../../lib/docker";
import { scheduler } from "../../../lib/scheduler"; import { createdBranchDatabase, getApplicationFromDBWebhook, removeBranchDatabase } from "../../api/v1/applications/handlers";
import { getApplicationFromDBWebhook } from "../../api/v1/applications/handlers";
import type { FastifyReply, FastifyRequest } from "fastify"; import type { FastifyReply, FastifyRequest } from "fastify";
import type { GitHubEvents, InstallGithub } from "./types"; import type { GitHubEvents, InstallGithub } from "./types";
@@ -67,7 +66,6 @@ export async function configureGitHubApp(request, reply) {
} }
export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promise<any> { export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promise<any> {
try { try {
const allowedGithubEvents = ['push', 'pull_request']; const allowedGithubEvents = ['push', 'pull_request'];
const allowedActions = ['opened', 'reopened', 'synchronize', 'closed']; const allowedActions = ['opened', 'reopened', 'synchronize', 'closed'];
const githubEvent = request.headers['x-github-event']?.toString().toLowerCase(); const githubEvent = request.headers['x-github-event']?.toString().toLowerCase();
@@ -133,8 +131,6 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
where: { id: application.id }, where: { id: application.id },
data: { updatedAt: new Date() } data: { updatedAt: new Date() }
}); });
console.log(application.id)
await prisma.build.create({ await prisma.build.create({
data: { data: {
id: buildId, id: buildId,
@@ -178,6 +174,16 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
where: { id: application.id }, where: { id: application.id },
data: { updatedAt: new Date() } 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);
}
}
}
await prisma.build.create({ await prisma.build.create({
data: { data: {
id: buildId, id: buildId,
@@ -197,9 +203,17 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
} else if (pullmergeRequestAction === 'closed') { } else if (pullmergeRequestAction === 'closed') {
if (application.destinationDockerId) { if (application.destinationDockerId) {
const id = `${application.id}-${pullmergeRequestId}`; const id = `${application.id}-${pullmergeRequestId}`;
await removeContainer({ id, dockerId: application.destinationDocker.id }); try {
await removeContainer({ id, dockerId: application.destinationDocker.id });
} catch (error) { }
}
if (application.connectedDatabase.databaseId) {
const databaseId = application.connectedDatabase.databaseId;
const database = await prisma.database.findUnique({ where: { id: databaseId } });
if (database) {
await removeBranchDatabase(database, pullmergeRequestId);
}
} }
} }
} }
} }

View File

@@ -19,11 +19,11 @@
"@popperjs/core": "2.11.6", "@popperjs/core": "2.11.6",
"@sveltejs/kit": "1.0.0-next.405", "@sveltejs/kit": "1.0.0-next.405",
"@types/js-cookie": "3.0.2", "@types/js-cookie": "3.0.2",
"@typescript-eslint/eslint-plugin": "5.35.1", "@typescript-eslint/eslint-plugin": "5.36.1",
"@typescript-eslint/parser": "5.35.1", "@typescript-eslint/parser": "5.36.1",
"autoprefixer": "10.4.8", "autoprefixer": "10.4.8",
"classnames": "2.3.1", "classnames": "2.3.1",
"eslint": "8.22.0", "eslint": "8.23.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.5.0",
"eslint-plugin-svelte3": "4.0.0", "eslint-plugin-svelte3": "4.0.0",
"flowbite": "1.5.2", "flowbite": "1.5.2",
@@ -31,21 +31,21 @@
"postcss": "8.4.16", "postcss": "8.4.16",
"prettier": "2.7.1", "prettier": "2.7.1",
"prettier-plugin-svelte": "2.7.0", "prettier-plugin-svelte": "2.7.0",
"svelte": "3.49.0", "svelte": "3.50.0",
"svelte-check": "2.8.1", "svelte-check": "2.9.0",
"svelte-preprocess": "4.10.7", "svelte-preprocess": "4.10.7",
"tailwindcss": "3.1.8", "tailwindcss": "3.1.8",
"tailwindcss-scrollbar": "0.1.0", "tailwindcss-scrollbar": "0.1.0",
"tslib": "2.4.0", "tslib": "2.4.0",
"typescript": "4.7.4", "typescript": "4.8.2",
"vite": "3.0.5" "vite": "3.1.0"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@sveltejs/adapter-static": "1.0.0-next.39", "@sveltejs/adapter-static": "1.0.0-next.39",
"@tailwindcss/typography": "^0.5.4", "@tailwindcss/typography": "^0.5.7",
"cuid": "2.1.8", "cuid": "2.1.8",
"daisyui": "2.24.0", "daisyui": "2.24.2",
"js-cookie": "3.0.1", "js-cookie": "3.0.1",
"p-limit": "4.0.0", "p-limit": "4.0.0",
"svelte-select": "4.4.7", "svelte-select": "4.4.7",

View File

@@ -0,0 +1,162 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async () => {
try {
const response = await get(`/databases`);
return {
props: {
...response
}
};
} catch (error: any) {
return {
status: 500,
error: new Error(error)
};
}
};
</script>
<script lang="ts">
export let databases: any = [];
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { appSession } from '$lib/store';
import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte';
import { errorNotification } from '$lib/common';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
const from = $page.url.searchParams.get('from');
let remoteDatabase = {
name: null,
type: null,
host: null,
port: null,
user: null,
password: null,
database: null
};
const ownDatabases = databases.filter((database: any) => {
if (database.teams[0].id === $appSession.teamId) {
return database;
}
});
const otherDatabases = databases.filter((database: any) => {
if (database.teams[0].id !== $appSession.teamId) {
return database;
}
});
async function addCoolifyDatabase(database: any) {
try {
await post(`/applications/${$page.params.id}/configuration/database`, {
databaseId: database.id,
type: database.type
});
return window.location.assign(from || `/applications/${$page.params.id}/`);
} catch (error) {
return errorNotification(error);
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Select a Database</div>
</div>
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
{#if !databases || ownDatabases.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>
</div>
{/if}
{#if ownDatabases.length > 0 || otherDatabases.length > 0}
<div class="flex flex-col">
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
{#each ownDatabases as database}
<button on:click={() => addCoolifyDatabase(database)} class="p-2 no-underline">
<div class="box-selection group relative hover:bg-purple-600">
<DatabaseIcons type={database.type} isAbsolute={true} />
<div class="truncate text-center text-xl font-bold">
{database.name}
</div>
{#if $appSession.teamId === '0' && otherDatabases.length > 0}
<div class="truncate text-center">{database.teams[0].name}</div>
{/if}
{#if database.destinationDocker?.name}
<div class="truncate text-center">{database.destinationDocker.name}</div>
{/if}
{#if !database.type}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
{$t('application.configuration.configuration_missing')}
</div>
{/if}
</div>
</button>
{/each}
</div>
{#if otherDatabases.length > 0 && $appSession.teamId === '0'}
<div class="px-6 pb-5 pt-10 text-2xl font-bold text-center">Other Databases</div>
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
{#each otherDatabases as database}
<a href="/databases/{database.id}" class="p-2 no-underline">
<div class="box-selection group relative hover:bg-purple-600">
<DatabaseIcons type={database.type} isAbsolute={true} />
<div class="truncate text-center text-xl font-bold">
{database.name}
</div>
{#if $appSession.teamId === '0'}
<div class="truncate text-center">{database.teams[0].name}</div>
{/if}
{#if !database.type}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Configuration missing
</div>
{:else}
<div class="text-center truncate">{database.type}</div>
{/if}
</div>
</a>
{/each}
</div>
{/if}
</div>
{/if}
<div class="mx-auto max-w-4xl p-6">
<div class="grid grid-flow-row gap-2 px-10">
<div class="font-bold text-xl tracking-tight">Connect a Hosted / Remote Database</div>
<div class="mt-2 grid grid-cols-2 items-center px-4">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<input name="name" id="name" required bind:value={remoteDatabase.name} />
</div>
<div class="mt-2 grid grid-cols-2 items-center px-4">
<label for="type" class="text-base font-bold text-stone-100">Type</label>
<input name="type" id="type" required bind:value={remoteDatabase.type} />
</div>
<div class="mt-2 grid grid-cols-2 items-center px-4">
<label for="host" class="text-base font-bold text-stone-100">Host</label>
<input name="host" id="host" required bind:value={remoteDatabase.host} />
</div>
<div class="mt-2 grid grid-cols-2 items-center px-4">
<label for="port" class="text-base font-bold text-stone-100">Port</label>
<input name="port" id="port" required bind:value={remoteDatabase.port} />
</div>
<div class="mt-2 grid grid-cols-2 items-center px-4">
<label for="user" class="text-base font-bold text-stone-100">User</label>
<input name="user" id="user" required bind:value={remoteDatabase.user} />
</div>
<div class="mt-2 grid grid-cols-2 items-center px-4">
<label for="password" class="text-base font-bold text-stone-100">Password</label>
<input name="password" id="password" required bind:value={remoteDatabase.password} />
</div>
<div class="mt-2 grid grid-cols-2 items-center px-4">
<label for="database" class="text-base font-bold text-stone-100">Database Name</label>
<input name="database" id="database" required bind:value={remoteDatabase.database} />
</div>
</div>
</div>
</div>

View File

@@ -31,7 +31,6 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import Select from 'svelte-select'; import Select from 'svelte-select';
import { get, post } from '$lib/api'; import { get, post } from '$lib/api';
import cuid from 'cuid'; import cuid from 'cuid';
import { import {
@@ -40,13 +39,15 @@
checkIfDeploymentEnabledApplications, checkIfDeploymentEnabledApplications,
setLocation, setLocation,
status, status,
isDeploymentEnabled isDeploymentEnabled,
features
} from '$lib/store'; } from '$lib/store';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common'; import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
import Setting from '$lib/components/Setting.svelte'; import Setting from '$lib/components/Setting.svelte';
import Tooltip from '$lib/components/Tooltip.svelte'; import Tooltip from '$lib/components/Tooltip.svelte';
import Explainer from '$lib/components/Explainer.svelte'; import Explainer from '$lib/components/Explainer.svelte';
import { goto } from '$app/navigation';
const { id } = $page.params; const { id } = $page.params;
@@ -73,6 +74,7 @@
let isBot = application.settings.isBot; let isBot = application.settings.isBot;
let isDBBranching = application.settings.isDBBranching; let isDBBranching = application.settings.isDBBranching;
let baseDatabaseBranch: any = application?.connectedDatabase?.hostedDatabaseDBName || null;
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, ''); let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
let isNonWWWDomainOK = false; let isNonWWWDomainOK = false;
let isWWWDomainOK = false; let isWWWDomainOK = false;
@@ -172,7 +174,6 @@
} }
if (name === 'isDBBranching') { if (name === 'isDBBranching') {
isDBBranching = !isDBBranching; isDBBranching = !isDBBranching;
application.settings.isDBBranching = isDBBranching;
} }
try { try {
await post(`/applications/${id}/settings`, { await post(`/applications/${id}/settings`, {
@@ -181,6 +182,7 @@
dualCerts, dualCerts,
isBot, isBot,
autodeploy, autodeploy,
isDBBranching,
branch: application.branch, branch: application.branch,
projectId: application.projectId projectId: application.projectId
}); });
@@ -204,6 +206,9 @@
if (name === 'isBot') { if (name === 'isBot') {
isBot = !isBot; isBot = !isBot;
} }
if (name === 'isDBBranching') {
isDBBranching = !isDBBranching;
}
return errorNotification(error); return errorNotification(error);
} finally { } finally {
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application); $isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
@@ -223,7 +228,7 @@
dualCerts, dualCerts,
exposePort: application.exposePort exposePort: application.exposePort
})); }));
await post(`/applications/${id}`, { ...application }); await post(`/applications/${id}`, { ...application, baseDatabaseBranch });
setLocation(application, settings); setLocation(application, settings);
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application); $isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
@@ -525,6 +530,46 @@
</div> </div>
</div> </div>
{/if} {/if}
{#if $features.beta}
{#if !application.settings.isBot && !application.settings.isPublicRepository}
<div class="grid grid-cols-2 items-center">
<Setting
id="isDBBranching"
isCenter={false}
bind:setting={isDBBranching}
on:click={() => changeSettings('isDBBranching')}
title="Enable DB Branching"
description="Enable DB Branching"
/>
</div>
{#if isDBBranching}
<button
on:click|stopPropagation|preventDefault={() =>
goto(`/applications/${id}/configuration/database`)}
class="btn btn-sm">Configure Connected Database</button
>
{#if application.connectedDatabase}
<div class="grid grid-cols-2 items-center">
<label for="baseImage" class="text-base font-bold text-stone-100"
>Base Database
<Explainer
explanation={'The name of the database that will be used as base when branching.'}
/></label
>
<input
name="baseDatabaseBranch"
required
id="baseDatabaseBranch"
bind:value={baseDatabaseBranch}
/>
</div>
<div class="text-center bg-green-600 rounded">
Connected to {application.connectedDatabase.databaseId}
</div>
{/if}
{/if}
{/if}
{/if}
</div> </div>
<div class="flex space-x-1 py-5 font-bold"> <div class="flex space-x-1 py-5 font-bold">
<div class="title">{$t('application.application')}</div> <div class="title">{$t('application.application')}</div>
@@ -846,16 +891,6 @@
/> />
</div> </div>
{/if} {/if}
<div class="grid grid-cols-2 items-center">
<Setting
id="isDBBranching"
isCenter={false}
bind:setting={isDBBranching}
on:click={() => changeSettings('isDBBranching')}
title="Enable DB Branching"
description="Enable DB Branching"
/>
</div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
id="debug" id="debug"

864
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff