feat: remote docker engine

This commit is contained in:
Andras Bacsai
2022-07-21 12:43:53 +00:00
parent 4df32a9bc0
commit 890ea2e2d4
29 changed files with 598 additions and 503 deletions

1
TODO.md Normal file
View File

@@ -0,0 +1 @@
- Database connecting string for remote docker engines

View File

@@ -32,6 +32,7 @@
"dayjs": "1.11.3", "dayjs": "1.11.3",
"dockerode": "3.3.2", "dockerode": "3.3.2",
"dotenv-extended": "2.9.0", "dotenv-extended": "2.9.0",
"execa": "^6.1.0",
"fastify": "4.2.1", "fastify": "4.2.1",
"fastify-plugin": "4.0.0", "fastify-plugin": "4.0.0",
"generate-password": "1.7.0", "generate-password": "1.7.0",

View File

@@ -3,10 +3,8 @@ CREATE TABLE "SshKey" (
"id" TEXT NOT NULL PRIMARY KEY, "id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL, "name" TEXT NOT NULL,
"privateKey" TEXT NOT NULL, "privateKey" TEXT NOT NULL,
"destinationDockerId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL, "updatedAt" DATETIME NOT NULL
CONSTRAINT "SshKey_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE
); );
-- RedefineTables -- RedefineTables
@@ -23,7 +21,9 @@ CREATE TABLE "new_DestinationDocker" (
"remoteVerified" BOOLEAN NOT NULL DEFAULT false, "remoteVerified" BOOLEAN NOT NULL DEFAULT false,
"isCoolifyProxyUsed" BOOLEAN DEFAULT false, "isCoolifyProxyUsed" BOOLEAN DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL "updatedAt" DATETIME NOT NULL,
"sshKeyId" TEXT,
CONSTRAINT "DestinationDocker_sshKeyId_fkey" FOREIGN KEY ("sshKeyId") REFERENCES "SshKey" ("id") ON DELETE SET NULL ON UPDATE CASCADE
); );
INSERT INTO "new_DestinationDocker" ("createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt") SELECT "createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt" FROM "DestinationDocker"; INSERT INTO "new_DestinationDocker" ("createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt") SELECT "createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt" FROM "DestinationDocker";
DROP TABLE "DestinationDocker"; DROP TABLE "DestinationDocker";
@@ -31,6 +31,3 @@ ALTER TABLE "new_DestinationDocker" RENAME TO "DestinationDocker";
CREATE UNIQUE INDEX "DestinationDocker_network_key" ON "DestinationDocker"("network"); CREATE UNIQUE INDEX "DestinationDocker_network_key" ON "DestinationDocker"("network");
PRAGMA foreign_key_check; PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON; PRAGMA foreign_keys=ON;
-- CreateIndex
CREATE UNIQUE INDEX "SshKey_destinationDockerId_key" ON "SshKey"("destinationDockerId");

View File

@@ -213,17 +213,17 @@ model DestinationDocker {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
database Database[] database Database[]
service Service[] service Service[]
sshKey SshKey? sshKey SshKey? @relation(fields: [sshKeyId], references: [id])
sshKeyId String?
} }
model SshKey { model SshKey {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
privateKey String privateKey String
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) createdAt DateTime @default(now())
destinationDockerId String? @unique updatedAt DateTime @updatedAt
createdAt DateTime @default(now()) destinationDocker DestinationDocker[]
updatedAt DateTime @updatedAt
} }
model GitSource { model GitSource {

View File

@@ -5,7 +5,7 @@ import yaml from 'js-yaml';
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common'; import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
import { createDirectories, decrypt, executeDockerCmd, getDomain, prisma } from '../lib/common'; import { createDirectories, decrypt, executeDockerCmd, getDomain, prisma } from '../lib/common';
import { dockerInstance, getEngine } from '../lib/docker'; import Dockerode from 'dockerode';
import * as importers from '../lib/importers'; import * as importers from '../lib/importers';
import * as buildpacks from '../lib/buildPacks'; import * as buildpacks from '../lib/buildPacks';
@@ -104,9 +104,6 @@ import * as buildpacks from '../lib/buildPacks';
destinationType = 'docker'; destinationType = 'docker';
} }
if (destinationType === 'docker') { if (destinationType === 'docker') {
const docker = dockerInstance({ destinationDocker });
const host = getEngine(destinationDocker.engine);
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } }); await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
const { workdir, repodir } = await createDirectories({ repository, buildId }); const { workdir, repodir } = await createDirectories({ repository, buildId });
const configuration = await setDefaultConfiguration(message); const configuration = await setDefaultConfiguration(message);
@@ -185,18 +182,23 @@ import * as buildpacks from '../lib/buildPacks';
} else { } else {
deployNeeded = true; deployNeeded = true;
} }
const image = await docker.engine.getImage(`${applicationId}:${tag}`);
let imageFound = false; let imageFound = false;
try { try {
await image.inspect(); await executeDockerCmd({
dockerId: destinationDocker.id,
command: `docker image inspect ${applicationId}:${tag}`
})
imageFound = true; imageFound = true;
} catch (error) { } catch (error) {
// //
} }
if (!imageFound || deployNeeded) { // if (!imageFound || deployNeeded) {
if (true) {
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage); await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
if (buildpacks[buildPack]) if (buildpacks[buildPack])
await buildpacks[buildPack]({ await buildpacks[buildPack]({
dockerId: destinationDocker.id,
buildId, buildId,
applicationId, applicationId,
domain, domain,
@@ -212,7 +214,6 @@ import * as buildpacks from '../lib/buildPacks';
commit, commit,
tag, tag,
workdir, workdir,
docker,
port: exposePort ? `${exposePort}:${port}` : port, port: exposePort ? `${exposePort}:${port}` : port,
installCommand, installCommand,
buildCommand, buildCommand,
@@ -299,7 +300,7 @@ import * as buildpacks from '../lib/buildPacks';
container_name: imageId, container_name: imageId,
volumes, volumes,
env_file: envFound ? [`${workdir}/.env`] : [], env_file: envFound ? [`${workdir}/.env`] : [],
networks: [docker.network], networks: [destinationDocker.network],
labels, labels,
depends_on: [], depends_on: [],
restart: 'always', restart: 'always',
@@ -318,7 +319,7 @@ import * as buildpacks from '../lib/buildPacks';
} }
}, },
networks: { networks: {
[docker.network]: { [destinationDocker.network]: {
external: true external: true
} }
}, },

View File

@@ -1,7 +1,8 @@
import { asyncExecShell, base64Encode, generateTimestamp, getDomain, isDev, prisma, version } from "../common"; import { asyncExecShell, base64Encode, executeDockerCmd, generateTimestamp, getDomain, isDev, prisma, version } from "../common";
import { scheduler } from "../scheduler"; import { scheduler } from "../scheduler";
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { day } from "../dayjs"; import { day } from "../dayjs";
import { spawn } from 'node:child_process'
const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy']; const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy'];
const nodeBased = [ const nodeBased = [
'react', 'react',
@@ -511,8 +512,8 @@ export async function buildImage({
applicationId, applicationId,
tag, tag,
workdir, workdir,
docker,
buildId, buildId,
dockerId,
isCache = false, isCache = false,
debug = false, debug = false,
dockerFileLocation = '/Dockerfile' dockerFileLocation = '/Dockerfile'
@@ -522,6 +523,9 @@ export async function buildImage({
} else { } else {
await saveBuildLog({ line: `Building image started.`, buildId, applicationId }); await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
} }
if (debug) {
await saveBuildLog({ line: `\n###############\nIMPORTANT: Due to some issues during implementing Remote Docker Engine, the builds logs are not streamed at the moment. You will see the full build log when the build is finished!\n###############`, buildId, applicationId });
}
if (!debug && isCache) { if (!debug && isCache) {
await saveBuildLog({ await saveBuildLog({
line: `Debug turned off. To see more details, allow it in the configuration.`, line: `Debug turned off. To see more details, allow it in the configuration.`,
@@ -529,15 +533,56 @@ export async function buildImage({
applicationId applicationId
}); });
} }
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
const stream = await docker.engine.buildImage( const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
{ src: ['.'], context: workdir }, const { stderr } = await executeDockerCmd({ dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` })
{ if (debug) {
dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation, const array = stderr.split('\n')
t: `${applicationId}:${tag}${isCache ? '-cache' : ''}` for (const line of array) {
if (line !== '\n') {
await saveBuildLog({
line: `${line.replace('\n', '')}`,
buildId,
applicationId
});
}
} }
); }
await streamEvents({ stream, docker, buildId, applicationId, debug });
// await new Promise((resolve, reject) => {
// const command = spawn(`docker`, ['build', '-f', `${workdir}${dockerFile}`, '-t', `${cache}`,`${workdir}`], {
// env: {
// DOCKER_HOST: 'ssh://root@95.217.178.202',
// DOCKER_BUILDKIT: '1'
// }
// });
// command.stdout.on('data', function (data) {
// console.log('stdout: ' + data);
// });
// command.stderr.on('data', function (data) {
// console.log('stderr: ' + data);
// });
// command.on('error', function (error) {
// console.log(error)
// reject(error)
// })
// command.on('exit', function (code) {
// console.log('exit code: ' + code);
// resolve(code)
// });
// })
// console.log({ stdout, stderr })
// const stream = await docker.engine.buildImage(
// { src: ['.'], context: workdir },
// {
// dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation,
// t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
// }
// );
// await streamEvents({ stream, docker, buildId, applicationId, debug });
await saveBuildLog({ line: `Building image successful!`, buildId, applicationId }); await saveBuildLog({ line: `Building image successful!`, buildId, applicationId });
} }
@@ -617,18 +662,17 @@ export function makeLabelForStandaloneApplication({
export async function buildCacheImageWithNode(data, imageForBuild) { export async function buildCacheImageWithNode(data, imageForBuild) {
const { const {
applicationId,
tag,
workdir, workdir,
docker,
buildId, buildId,
baseDirectory, baseDirectory,
installCommand, installCommand,
buildCommand, buildCommand,
debug,
secrets, secrets,
pullmergeRequestId pullmergeRequestId
} = data; } = data;
data.isCache = true
const isPnpm = checkPnpm(installCommand, buildCommand); const isPnpm = checkPnpm(installCommand, buildCommand);
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${imageForBuild}`); Dockerfile.push(`FROM ${imageForBuild}`);
@@ -659,11 +703,13 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
Dockerfile.push(`COPY .${baseDirectory || ''} ./`); Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${buildCommand}`); Dockerfile.push(`RUN ${buildCommand}`);
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); await buildImage(data);
} }
export async function buildCacheImageForLaravel(data, imageForBuild) { export async function buildCacheImageForLaravel(data, imageForBuild) {
const { applicationId, tag, workdir, docker, buildId, debug, secrets, pullmergeRequestId } = data; const { workdir, buildId, secrets, pullmergeRequestId } = data;
data.isCache = true
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${imageForBuild}`); Dockerfile.push(`FROM ${imageForBuild}`);
Dockerfile.push('WORKDIR /app'); Dockerfile.push('WORKDIR /app');
@@ -687,22 +733,17 @@ export async function buildCacheImageForLaravel(data, imageForBuild) {
Dockerfile.push(`COPY resources /app/resources`); Dockerfile.push(`COPY resources /app/resources`);
Dockerfile.push(`RUN yarn install && yarn production`); Dockerfile.push(`RUN yarn install && yarn production`);
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); await buildImage(data);
} }
export async function buildCacheImageWithCargo(data, imageForBuild) { export async function buildCacheImageWithCargo(data, imageForBuild) {
const { const {
applicationId, applicationId,
tag,
workdir, workdir,
docker,
buildId, buildId,
baseDirectory,
installCommand,
buildCommand,
debug,
secrets
} = data; } = data;
data.isCache = true
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`); Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`);
Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
@@ -717,5 +758,5 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`); Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`);
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json'); Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); await buildImage(data);
} }

View File

@@ -1,18 +1,18 @@
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { buildImage } from './common'; import { buildImage } from './common';
export default async function ({ export default async function (data) {
applicationId, let {
debug, applicationId,
tag, debug,
workdir, tag,
docker, workdir,
buildId, buildId,
baseDirectory, baseDirectory,
secrets, secrets,
pullmergeRequestId, pullmergeRequestId,
dockerFileLocation dockerFileLocation
}) { } = data
try { try {
const file = `${workdir}${dockerFileLocation}`; const file = `${workdir}${dockerFileLocation}`;
let dockerFileOut = `${workdir}`; let dockerFileOut = `${workdir}`;
@@ -45,7 +45,7 @@ export default async function ({
} }
await fs.writeFile(`${dockerFileOut}${dockerFileLocation}`, Dockerfile.join('\n')); await fs.writeFile(`${dockerFileOut}${dockerFileLocation}`, Dockerfile.join('\n'));
await buildImage({ applicationId, tag, workdir, docker, buildId, debug, dockerFileLocation }); await buildImage(data);
} catch (error) { } catch (error) {
throw error; throw error;
} }

View File

@@ -473,6 +473,8 @@ export async function createRemoteEngineConfiguration(id: string) {
const sshKeyFile = `/tmp/id_rsa-${id}` const sshKeyFile = `/tmp/id_rsa-${id}`
const { sshKey: { privateKey }, remoteIpAddress, remotePort, remoteUser } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } }) const { sshKey: { privateKey }, remoteIpAddress, remotePort, remoteUser } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } })
await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 }) await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 })
// Needed for remote docker compose
await asyncExecShell(`eval $(ssh-agent -s) && ssh-add -q ${sshKeyFile}`)
const config = sshConfig.parse('') const config = sshConfig.parse('')
const found = config.find({ Host: remoteIpAddress }) const found = config.find({ Host: remoteIpAddress })
if (!found) { if (!found) {
@@ -542,14 +544,13 @@ export async function startTraefikProxy(id: string): Promise<void> {
data: { isCoolifyProxyUsed: true } data: { isCoolifyProxyUsed: true }
}); });
} }
if (!remoteEngine) await configureNetworkTraefikProxy(engine); if (!remoteEngine) await configureNetworkTraefikProxy(engine, id);
} }
export async function configureNetworkTraefikProxy(engine: string): Promise<void> { export async function configureNetworkTraefikProxy(engine: string, id: string): Promise<void> {
const destinations = await prisma.destinationDocker.findMany({ where: { engine } }); const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
const { stdout: networks } = await executeDockerCmd({ const { stdout: networks } = await executeDockerCmd({
dockerId: '', dockerId: id,
command: command:
`docker ps -a --filter name=coolify-proxy --format '{{json .Networks}}'` `docker ps -a --filter name=coolify-proxy --format '{{json .Networks}}'`
}); });

View File

@@ -47,13 +47,10 @@ export async function checkContainer({ dockerId, container, remove = false }: {
return containerFound; return containerFound;
} }
export async function isContainerExited(engine: string, containerName: string): Promise<boolean> { export async function isContainerExited(dockerId: string, containerName: string): Promise<boolean> {
let isExited = false; let isExited = false;
const host = getEngine(engine);
try { try {
const { stdout } = await asyncExecShell( const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect -f '{{.State.Status}}' ${containerName}` })
`DOCKER_HOST="${host}" docker inspect -f '{{.State.Status}}' ${containerName}`
);
if (stdout.trim() === 'exited') { if (stdout.trim() === 'exited') {
isExited = true; isExited = true;
} }
@@ -72,11 +69,11 @@ export async function removeContainer({
dockerId: string; dockerId: string;
}): Promise<void> { }): Promise<void> {
try { try {
const { stdout } =await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}`}) const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
if (JSON.parse(stdout).Running) { if (JSON.parse(stdout).Running) {
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}`}) await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
await executeDockerCmd({ dockerId, command: `docker rm ${id}`}) await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@@ -5,8 +5,8 @@ import axios from 'axios';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common'; import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
import { asyncExecShell, checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common'; import { asyncExecShell, checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common';
import { checkContainer, dockerInstance, getEngine, isContainerExited, removeContainer } from '../../../../lib/docker'; import { checkContainer, dockerInstance, isContainerExited, removeContainer } from '../../../../lib/docker';
import { scheduler } from '../../../../lib/scheduler'; import { scheduler } from '../../../../lib/scheduler';
import type { FastifyRequest } from 'fastify'; import type { FastifyRequest } from 'fastify';
@@ -62,23 +62,36 @@ export async function getImages(request: FastifyRequest<GetImages>) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const { teamId } = request.user
let isRunning = false;
let isExited = false;
const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) {
isRunning = await checkContainer({ dockerId: application.destinationDocker.id, container: id });
isExited = await isContainerExited(application.destinationDocker.id, id);
}
return {
isQueueActive: scheduler.workers.has('deployApplication'),
isRunning,
isExited,
};
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function getApplication(request: FastifyRequest<OnlyId>) { export async function getApplication(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const { teamId } = request.user const { teamId } = request.user
const appId = process.env['COOLIFY_APP_ID']; const appId = process.env['COOLIFY_APP_ID'];
let isRunning = false;
let isExited = false;
const application: any = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId && application.destinationDocker?.engine) {
isRunning = await checkContainer({ dockerId: application.destinationDocker.id, container: id });
isExited = await isContainerExited(application.destinationDocker.engine, id);
}
return { return {
isQueueActive: scheduler.workers.has('deployApplication'),
isRunning,
isExited,
application, application,
appId appId
}; };
@@ -284,8 +297,8 @@ export async function stopApplication(request: FastifyRequest<OnlyId>, reply: Fa
const { id } = request.params const { id } = request.params
const { teamId } = request.user const { teamId } = request.user
const application: any = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId && application.destinationDocker?.engine) { if (application?.destinationDockerId) {
const { engine, id: dockerId } = application.destinationDocker; const { id: dockerId } = application.destinationDocker;
const found = await checkContainer({ dockerId, container: id }); const found = await checkContainer({ dockerId, container: id });
if (found) { if (found) {
await removeContainer({ id, dockerId: application.destinationDocker.id }); await removeContainer({ id, dockerId: application.destinationDocker.id });
@@ -304,11 +317,11 @@ export async function deleteApplication(request: FastifyRequest<DeleteApplicatio
where: { id }, where: { id },
include: { destinationDocker: true } include: { destinationDocker: true }
}); });
if (application?.destinationDockerId && application.destinationDocker?.engine && application.destinationDocker?.network) { if (application?.destinationDockerId && application.destinationDocker?.network) {
const host = getEngine(application.destinationDocker.engine); const { stdout: containers } = await executeDockerCmd({
const { stdout: containers } = await asyncExecShell( dockerId: application.destinationDocker.id,
`DOCKER_HOST=${host} docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'` command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
); })
if (containers) { if (containers) {
const containersArray = containers.trim().split('\n'); const containersArray = containers.trim().split('\n');
for (const container of containersArray) { for (const container of containersArray) {
@@ -739,44 +752,39 @@ export async function getPreviews(request: FastifyRequest<OnlyId>) {
export async function getApplicationLogs(request: FastifyRequest<GetApplicationLogs>) { export async function getApplicationLogs(request: FastifyRequest<GetApplicationLogs>) {
try { try {
const { id } = request.params const { id } = request.params;
let { since = 0 } = request.query let { since = 0 } = request.query
if (since !== 0) { if (since !== 0) {
since = day(since).unix(); since = day(since).unix();
} }
const { destinationDockerId, destinationDocker } = await prisma.application.findUnique({ const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.application.findUnique({
where: { id }, where: { id },
include: { destinationDocker: true } include: { destinationDocker: true }
}); });
if (destinationDockerId) { if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker });
try { try {
const container = await docker.engine.getContainer(id); // const found = await checkContainer({ dockerId, container: id })
if (container) { // if (found) {
const { default: ansi } = await import('strip-ansi') const { default: ansi } = await import('strip-ansi')
const logs = ( const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
await container.logs({ const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
stdout: true, const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
stderr: true, const logs = stripLogsStderr.concat(stripLogsStdout)
timestamps: true, const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1))
since, return { logs: sortedLogs }
tail: 5000 // }
}) } catch (error) {
) const { statusCode } = error;
.toString() if (statusCode === 404) {
.split('\n')
.map((l) => ansi(l.slice(8)))
.filter((a) => a);
return { return {
logs logs: []
}; };
} }
} catch (error) {
return {
logs: []
};
} }
} }
return {
message: 'No logs found.'
}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ 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, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers'; import { cancelDeployment, checkDNS, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers';
import type { CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage } from './types'; import type { CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage } from './types';
@@ -17,6 +17,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post<SaveApplication>('/:id', async (request, reply) => await saveApplication(request, reply)); fastify.post<SaveApplication>('/:id', async (request, reply) => await saveApplication(request, reply));
fastify.delete<DeleteApplication>('/:id', async (request, reply) => await deleteApplication(request, reply)); fastify.delete<DeleteApplication>('/:id', async (request, reply) => await deleteApplication(request, reply));
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply)); fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
fastify.post<SaveApplicationSettings>('/:id/settings', async (request, reply) => await saveApplicationSettings(request, reply)); fastify.post<SaveApplicationSettings>('/:id/settings', async (request, reply) => await saveApplicationSettings(request, reply));

View File

@@ -3,9 +3,10 @@ import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import fs from 'fs/promises'; import fs from 'fs/promises';
import { asyncExecShell, ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
import { dockerInstance, getEngine } from '../../../../lib/docker'; import { checkContainer } from '../../../../lib/docker';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types'; import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
import { SaveDatabaseType } from './types'; import { SaveDatabaseType } from './types';
@@ -98,7 +99,7 @@ export async function getDatabase(request: FastifyRequest<OnlyId>) {
throw { status: 404, message: 'Database not found.' } throw { status: 404, message: 'Database not found.' }
} }
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
const configuration = generateDatabaseConfiguration(database); const configuration = generateDatabaseConfiguration(database);
const settings = await listSettings(); const settings = await listSettings();
return { return {
@@ -236,7 +237,6 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
generateDatabaseConfiguration(database); generateDatabaseConfiguration(database);
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const volumeName = volume.split(':')[0]; const volumeName = volume.split(':')[0];
const labels = await makeLabelForStandaloneDatabase({ id, image, volume }); const labels = await makeLabelForStandaloneDatabase({ id, image, volume });
@@ -322,39 +322,27 @@ export async function stopDatabase(request: FastifyRequest<OnlyId>) {
} }
export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>) { export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>) {
try { try {
const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
let { since = 0 } = request.query let { since = 0 } = request.query
if (since !== 0) { if (since !== 0) {
since = day(since).unix(); since = day(since).unix();
} }
const { destinationDockerId, destinationDocker } = await prisma.database.findUnique({ const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.database.findUnique({
where: { id }, where: { id },
include: { destinationDocker: true } include: { destinationDocker: true }
}); });
if (destinationDockerId) { if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker });
try { try {
const container = await docker.engine.getContainer(id); // const found = await checkContainer({ dockerId, container: id })
if (container) { // if (found) {
const { default: ansi } = await import('strip-ansi') const { default: ansi } = await import('strip-ansi')
const logs = ( const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
await container.logs({ const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
stdout: true, const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
stderr: true, const logs = stripLogsStderr.concat(stripLogsStdout)
timestamps: true, const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1))
since, return { logs: sortedLogs }
tail: 5000 // }
})
)
.toString()
.split('\n')
.map((l) => ansi(l.slice(8)))
.filter((a) => a);
return {
logs
};
}
} catch (error) { } catch (error) {
const { statusCode } = error; const { statusCode } = error;
if (statusCode === 404) { if (statusCode === 404) {

View File

@@ -12,10 +12,11 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post('/new', async (request, reply) => await newDatabase(request, reply)); fastify.post('/new', async (request, reply) => await newDatabase(request, reply));
fastify.get<OnlyId>('/:id', async (request) => await getDatabase(request)); fastify.get<OnlyId>('/:id', async (request) => await getDatabase(request));
fastify.get<OnlyId>('/:id/status', async (request) => await getDatabaseStatus(request));
fastify.post<SaveDatabase>('/:id', async (request, reply) => await saveDatabase(request, reply)); fastify.post<SaveDatabase>('/:id', async (request, reply) => await saveDatabase(request, reply));
fastify.delete<OnlyId>('/:id', async (request) => await deleteDatabase(request)); fastify.delete<OnlyId>('/:id', async (request) => await deleteDatabase(request));
fastify.get<OnlyId>('/:id/status', async (request) => await getDatabaseStatus(request));
fastify.post<SaveDatabaseSettings>('/:id/settings', async (request) => await saveDatabaseSettings(request)); fastify.post<SaveDatabaseSettings>('/:id/settings', async (request) => await saveDatabaseSettings(request));
fastify.get('/:id/configuration/type', async (request) => await getDatabaseTypes(request)); fastify.get('/:id/configuration/type', async (request) => await getDatabaseTypes(request));

View File

@@ -2,7 +2,7 @@ import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import sshConfig from 'ssh-config' import sshConfig from 'ssh-config'
import fs from 'fs/promises' import fs from 'fs/promises'
import { asyncExecShell, decrypt, errorHandler, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common'; import { asyncExecShell, decrypt, errorHandler, executeDockerCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
import { checkContainer, dockerInstance, getEngine } from '../../../../lib/docker'; import { checkContainer, dockerInstance, getEngine } from '../../../../lib/docker';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
@@ -67,9 +67,11 @@ export async function getDestination(request: FastifyRequest<OnlyId>) {
} }
export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) { export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
try { try {
const { id } = request.params
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
console.log({ name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort })
if (id === 'new') { if (id === 'new') {
if (engine) { if (engine) {
const host = getEngine(engine); const host = getEngine(engine);
@@ -100,17 +102,12 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
} }
} }
return reply.code(201).send({ id: destination.id }); return reply.code(201).send({ id: destination.id });
} } else {
if (remoteIpAddress) { const destination = await prisma.destinationDocker.create({
await prisma.destinationDocker.create({
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort } data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort }
}); });
return reply.code(201).send() return reply.code(201).send({ id: destination.id })
} }
throw {
message: `Cannot save Docker Engine.`
};
} else { } else {
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } }); await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
return reply.code(201).send(); return reply.code(201).send();
@@ -125,22 +122,20 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
export async function deleteDestination(request: FastifyRequest<OnlyId>) { export async function deleteDestination(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const destination = await prisma.destinationDocker.delete({ where: { id } }); const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } });
if (destination.isCoolifyProxyUsed) { if (isCoolifyProxyUsed) {
const host = getEngine(destination.engine); if (engine || remoteVerified) {
const { network } = destination; const { stdout: found } = await executeDockerCmd({
const settings = await prisma.setting.findFirst(); dockerId: id,
const containerName = settings.isTraefikUsed ? 'coolify-proxy' : 'coolify-haproxy'; command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
const { stdout: found } = await asyncExecShell( })
`DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=${containerName} --format '{{.}}'` if (found) {
); await executeDockerCmd({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` })
if (found) { await executeDockerCmd({ dockerId: id, command: `docker network rm ${network}` })
await asyncExecShell( }
`DOCKER_HOST="${host}" docker network disconnect ${network} ${containerName}`
);
await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`);
} }
} }
await prisma.destinationDocker.delete({ where: { id } });
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -168,6 +163,7 @@ export async function startProxy(request: FastifyRequest<Proxy>) {
await startTraefikProxy(id); await startTraefikProxy(id);
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
console.log({status, message})
await stopTraefikProxy(id); await stopTraefikProxy(id);
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
@@ -245,9 +241,9 @@ export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const destination = await prisma.destinationDocker.findUnique({ where: { id } }) const destination = await prisma.destinationDocker.findUnique({ where: { id } })
const containerName = 'coolify-proxy'; const isRunning = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy' })
return { return {
isRunning: await checkContainer({ dockerId: destination.id, container: containerName }) isRunning
} }
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })

View File

@@ -2,9 +2,9 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
import fs from 'fs/promises'; import fs from 'fs/promises';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions } from '../../../../lib/common'; import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions, executeDockerCmd } from '../../../../lib/common';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import { checkContainer, dockerInstance, getEngine, removeContainer } from '../../../../lib/docker'; import { checkContainer, dockerInstance, isContainerExited, removeContainer } from '../../../../lib/docker';
import cuid from 'cuid'; import cuid from 'cuid';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
@@ -172,6 +172,31 @@ export async function newService(request: FastifyRequest, reply: FastifyReply) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getServiceStatus(request: FastifyRequest<OnlyId>) {
try {
const teamId = request.user.teamId;
const { id } = request.params;
let isRunning = false;
let isExited = false
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, settings } = service;
if (destinationDockerId) {
isRunning = await checkContainer({ dockerId: service.destinationDocker.id, container: id });
isExited = await isContainerExited(service.destinationDocker.id, id);
}
return {
isRunning,
isExited,
settings
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function getService(request: FastifyRequest<OnlyId>) { export async function getService(request: FastifyRequest<OnlyId>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
@@ -181,36 +206,8 @@ export async function getService(request: FastifyRequest<OnlyId>) {
if (!service) { if (!service) {
throw { status: 404, message: 'Service not found.' } throw { status: 404, message: 'Service not found.' }
} }
const { destinationDockerId, destinationDocker, type, version, settings } = service;
let isRunning = false;
if (destinationDockerId) {
const host = getEngine(destinationDocker.engine);
const docker = dockerInstance({ destinationDocker });
const baseImage = getServiceImage(type);
const images = getServiceImages(type);
docker.engine.pull(`${baseImage}:${version}`);
if (images?.length > 0) {
for (const image of images) {
docker.engine.pull(`${image}:latest`);
}
}
try {
const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
);
if (JSON.parse(stdout).Running) {
isRunning = true;
}
} catch (error) {
//
}
}
return { return {
isRunning,
service, service,
settings
} }
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -299,33 +296,22 @@ export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
if (since !== 0) { if (since !== 0) {
since = day(since).unix(); since = day(since).unix();
} }
const { destinationDockerId, destinationDocker } = await prisma.service.findUnique({ const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.service.findUnique({
where: { id }, where: { id },
include: { destinationDocker: true } include: { destinationDocker: true }
}); });
if (destinationDockerId) { if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker });
try { try {
const container = await docker.engine.getContainer(id); // const found = await checkContainer({ dockerId, container: id })
if (container) { // if (found) {
const { default: ansi } = await import('strip-ansi') const { default: ansi } = await import('strip-ansi')
const logs = ( const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
await container.logs({ const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
stdout: true, const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
stderr: true, const logs = stripLogsStderr.concat(stripLogsStdout)
timestamps: true, const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1))
since, return { logs: sortedLogs }
tail: 5000 // }
})
)
.toString()
.split('\n')
.map((l) => ansi(l.slice(8)))
.filter((a) => a);
return {
logs
};
}
} catch (error) { } catch (error) {
const { statusCode } = error; const { statusCode } = error;
if (statusCode === 404) { if (statusCode === 404) {
@@ -742,7 +728,6 @@ async function startPlausibleAnalyticsService(request: FastifyRequest<ServiceSta
}); });
} }
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('plausibleanalytics'); const port = getServiceMainPort('plausibleanalytics');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -859,10 +844,8 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await asyncExecShell( await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
);
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -905,7 +888,6 @@ async function startNocodbService(request: FastifyRequest<ServiceStartStop>) {
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } = const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
service; service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('nocodb'); const port = getServiceMainPort('nocodb');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -956,8 +938,8 @@ async function startNocodbService(request: FastifyRequest<ServiceStartStop>) {
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -970,7 +952,6 @@ async function stopNocodbService(request: FastifyRequest<ServiceStartStop>) {
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker, fqdn } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
await removeContainer({ id, dockerId: destinationDocker.id }); await removeContainer({ id, dockerId: destinationDocker.id });
@@ -999,7 +980,6 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
} = service; } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('minio'); const port = getServiceMainPort('minio');
const publicPort = await getFreePort(); const publicPort = await getFreePort();
@@ -1058,8 +1038,8 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } }); await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
@@ -1071,10 +1051,9 @@ async function stopMinioService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
await prisma.minio.update({ where: { serviceId: id }, data: { publicPort: null } }) await prisma.minio.update({ where: { serviceId: id }, data: { publicPort: null } })
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
await removeContainer({ id, dockerId: destinationDocker.id }); await removeContainer({ id, dockerId: destinationDocker.id });
@@ -1103,7 +1082,6 @@ async function startVscodeService(request: FastifyRequest<ServiceStartStop>) {
} = service; } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('vscodeserver'); const port = getServiceMainPort('vscodeserver');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1175,16 +1153,16 @@ async function startVscodeService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
const changePermissionOn = persistentStorage.map((p) => p.path); const changePermissionOn = persistentStorage.map((p) => p.path);
if (changePermissionOn.length > 0) { if (changePermissionOn.length > 0) {
await asyncExecShell( await executeDockerCmd({
`DOCKER_HOST=${host} docker exec -u root ${id} chown -R 1000:1000 ${changePermissionOn.join( dockerId: destinationDocker.id, command: `docker exec -u root ${id} chown -R 1000:1000 ${changePermissionOn.join(
' ' ' '
)}` )}`
); })
} }
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
@@ -1196,9 +1174,8 @@ async function stopVscodeService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
await removeContainer({ id, dockerId: destinationDocker.id }); await removeContainer({ id, dockerId: destinationDocker.id });
@@ -1236,7 +1213,6 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
} = service; } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const image = getServiceImage(type); const image = getServiceImage(type);
const port = getServiceMainPort('wordpress'); const port = getServiceMainPort('wordpress');
@@ -1328,8 +1304,10 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
} }
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -1346,7 +1324,6 @@ async function stopWordpressService(request: FastifyRequest<ServiceStartStop>) {
wordpress: { ftpEnabled } wordpress: { ftpEnabled }
} = service; } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
try { try {
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
@@ -1393,7 +1370,6 @@ async function startVaultwardenService(request: FastifyRequest<ServiceStartStop>
service; service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('vaultwarden'); const port = getServiceMainPort('vaultwarden');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1444,8 +1420,10 @@ async function startVaultwardenService(request: FastifyRequest<ServiceStartStop>
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -1456,10 +1434,8 @@ async function stopVaultwardenService(request: FastifyRequest<ServiceStartStop>)
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
try { try {
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
@@ -1483,7 +1459,6 @@ async function startLanguageToolService(request: FastifyRequest<ServiceStartStop
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } = const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
service; service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('languagetool'); const port = getServiceMainPort('languagetool');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1536,8 +1511,9 @@ async function startLanguageToolService(request: FastifyRequest<ServiceStartStop
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -1548,10 +1524,8 @@ async function stopLanguageToolService(request: FastifyRequest<ServiceStartStop>
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
try { try {
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
@@ -1575,7 +1549,6 @@ async function startN8nService(request: FastifyRequest<ServiceStartStop>) {
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } = const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
service; service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('n8n'); const port = getServiceMainPort('n8n');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1628,8 +1601,10 @@ async function startN8nService(request: FastifyRequest<ServiceStartStop>) {
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -1640,10 +1615,8 @@ async function stopN8nService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
try { try {
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
@@ -1667,7 +1640,6 @@ async function startUptimekumaService(request: FastifyRequest<ServiceStartStop>)
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } = const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
service; service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('uptimekuma'); const port = getServiceMainPort('uptimekuma');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1719,8 +1691,9 @@ async function startUptimekumaService(request: FastifyRequest<ServiceStartStop>)
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -1731,10 +1704,8 @@ async function stopUptimekumaService(request: FastifyRequest<ServiceStartStop>)
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
try { try {
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
@@ -1774,7 +1745,6 @@ async function startGhostService(request: FastifyRequest<ServiceStartStop>) {
} }
} = service; } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type); const image = getServiceImage(type);
@@ -1871,8 +1841,9 @@ async function startGhostService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -1883,10 +1854,8 @@ async function stopGhostService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
try { try {
let found = await checkContainer({ dockerId: destinationDocker.id, container: id }); let found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
@@ -1917,7 +1886,6 @@ async function startMeilisearchService(request: FastifyRequest<ServiceStartStop>
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } = const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
service; service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('meilisearch'); const port = getServiceMainPort('meilisearch');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1972,8 +1940,10 @@ async function startMeilisearchService(request: FastifyRequest<ServiceStartStop>
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -1984,10 +1954,8 @@ async function stopMeilisearchService(request: FastifyRequest<ServiceStartStop>)
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
try { try {
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
@@ -2024,7 +1992,6 @@ async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
} }
} = service; } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('umami'); const port = getServiceMainPort('umami');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -2191,8 +2158,10 @@ async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -2203,10 +2172,8 @@ async function stopUmamiService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
try { try {
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
@@ -2245,7 +2212,6 @@ async function startHasuraService(request: FastifyRequest<ServiceStartStop>) {
hasura: { postgresqlUser, postgresqlPassword, postgresqlDatabase } hasura: { postgresqlUser, postgresqlPassword, postgresqlDatabase }
} = service; } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('hasura'); const port = getServiceMainPort('hasura');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -2327,8 +2293,9 @@ async function startHasuraService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -2339,10 +2306,8 @@ async function stopHasuraService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
try { try {
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
@@ -2396,7 +2361,6 @@ async function startFiderService(request: FastifyRequest<ServiceStartStop>) {
} }
} = service; } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('fider'); const port = getServiceMainPort('fider');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -2489,8 +2453,8 @@ async function startFiderService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
@@ -2502,10 +2466,8 @@ async function stopFiderService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
try { try {
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
@@ -2554,12 +2516,10 @@ async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
} }
} = service; } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('moodle'); const port = getServiceMainPort('moodle');
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type); const image = getServiceImage(type);
const domain = getDomain(fqdn);
const config = { const config = {
moodle: { moodle: {
image: `${image}:${version}`, image: `${image}:${version}`,
@@ -2652,8 +2612,10 @@ async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
@@ -2665,10 +2627,8 @@ async function stopMoodleService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service; const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) { if (destinationDockerId) {
const engine = destinationDocker.engine;
try { try {
const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) { if (found) {
@@ -2703,14 +2663,10 @@ export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, re
plausibleAnalytics: { postgresqlUser, postgresqlPassword, postgresqlDatabase } plausibleAnalytics: { postgresqlUser, postgresqlPassword, postgresqlDatabase }
} = await getServiceFromDB({ id, teamId }); } = await getServiceFromDB({ id, teamId });
if (destinationDockerId) { if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker }); await executeDockerCmd({
const container = await docker.engine.getContainer(id); dockerId: destinationDocker.id,
const command = await container.exec({ command: `docker exec ${id} 'psql -H postgresql://${postgresqlUser}:${postgresqlPassword}@localhost:5432/${postgresqlDatabase} -c "UPDATE users SET email_verified = true;"'`
Cmd: [ })
`psql -H postgresql://${postgresqlUser}:${postgresqlPassword}@localhost:5432/${postgresqlDatabase} -c "UPDATE users SET email_verified = true;"`
]
});
await command.start();
return await reply.code(201).send() return await reply.code(201).send()
} }
throw { status: 500, message: 'Could not activate users.' } throw { status: 500, message: 'Could not activate users.' }
@@ -2742,7 +2698,6 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
ftpHostKeyPrivate ftpHostKeyPrivate
} = data; } = data;
const { network, engine } = destinationDocker; const { network, engine } = destinationDocker;
const host = getEngine(engine);
if (ftpEnabled) { if (ftpEnabled) {
if (user) ftpUser = user; if (user) ftpUser = user;
if (savedPassword) ftpPassword = decrypt(savedPassword); if (savedPassword) ftpPassword = decrypt(savedPassword);
@@ -2841,9 +2796,11 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
); );
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`); await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose)); await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
await asyncExecShell( await executeDockerCmd({
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` dockerId: destinationDocker.id,
); command: `docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
})
} }
return reply.code(201).send({ return reply.code(201).send({
publicPort, publicPort,
@@ -2856,9 +2813,11 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
data: { ftpPublicPort: null } data: { ftpPublicPort: null }
}); });
try { try {
await asyncExecShell( await executeDockerCmd({
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` dockerId: destinationDocker.id,
); command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
})
} catch (error) { } catch (error) {
// //
} }

View File

@@ -9,6 +9,7 @@ import {
getService, getService,
getServiceLogs, getServiceLogs,
getServiceSecrets, getServiceSecrets,
getServiceStatus,
getServiceStorages, getServiceStorages,
getServiceType, getServiceType,
getServiceUsage, getServiceUsage,
@@ -41,6 +42,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post<SaveService>('/:id', async (request, reply) => await saveService(request, reply)); fastify.post<SaveService>('/:id', async (request, reply) => await saveService(request, reply));
fastify.delete<OnlyId>('/:id', async (request) => await deleteService(request)); fastify.delete<OnlyId>('/:id', async (request) => await deleteService(request));
fastify.get<OnlyId>('/:id/status', async (request) => await getServiceStatus(request));
fastify.post<CheckService>('/:id/check', async (request) => await checkService(request)); fastify.post<CheckService>('/:id/check', async (request) => await checkService(request));
fastify.post<SaveServiceSettings>('/:id/settings', async (request, reply) => await saveServiceSettings(request, reply)); fastify.post<SaveServiceSettings>('/:id/settings', async (request, reply) => await saveServiceSettings(request, reply));

View File

@@ -250,7 +250,7 @@
"no_destination_found": "No destination found", "no_destination_found": "No destination found",
"new_error_network_already_exists": "Network {{network}} already configured for another team!", "new_error_network_already_exists": "Network {{network}} already configured for another team!",
"new": { "new": {
"saving_and_configuring_proxy": "Saving and configuring proxy...", "saving_and_configuring_proxy": "Saving...",
"install_proxy": "This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker).<br><br>Databases will have their own proxy.", "install_proxy": "This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker).<br><br>Databases will have their own proxy.",
"add_new_destination": "Add New Destination", "add_new_destination": "Add New Destination",
"predefined_destinations": "Predefined destinations" "predefined_destinations": "Predefined destinations"

View File

@@ -41,14 +41,16 @@ export const status: Writable<any> = writable({
initialLoading: true initialLoading: true
}, },
service: { service: {
initialLoading: true, isRunning: false,
isExited: false,
loading: false, loading: false,
isRunning: false initialLoading: true
}, },
database: { database: {
initialLoading: true, isRunning: false,
isExited: false,
loading: false, loading: false,
isRunning: false initialLoading: true
} }
}); });
@@ -60,7 +62,6 @@ export const features = readable({
export const location: Writable<null | string> = writable(null) export const location: Writable<null | string> = writable(null)
export const setLocation = (resource: any) => { export const setLocation = (resource: any) => {
console.log(GITPOD_WORKSPACE_URL)
if (GITPOD_WORKSPACE_URL && resource.exposePort) { if (GITPOD_WORKSPACE_URL && resource.exposePort) {
const { href } = new URL(GITPOD_WORKSPACE_URL); const { href } = new URL(GITPOD_WORKSPACE_URL);
const newURL = href const newURL = href

View File

@@ -36,7 +36,6 @@
return { return {
props: { props: {
isQueueActive,
application application
}, },
stuff: { stuff: {
@@ -53,7 +52,6 @@
<script lang="ts"> <script lang="ts">
export let application: any; export let application: any;
export let isQueueActive: any;
import { page } from '$app/stores'; import { page } from '$app/stores';
import DeleteIcon from '$lib/components/DeleteIcon.svelte'; import DeleteIcon from '$lib/components/DeleteIcon.svelte';
@@ -68,7 +66,7 @@
let loading = false; let loading = false;
let statusInterval: any; let statusInterval: any;
let isQueueActive= false;
$disabledButton = $disabledButton =
!$appSession.isAdmin || !$appSession.isAdmin ||
!application.fqdn || !application.fqdn ||
@@ -114,12 +112,12 @@
return window.location.reload(); return window.location.reload();
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} }
} }
async function getStatus() { async function getStatus() {
if ($status.application.loading) return; if ($status.application.loading) return;
$status.application.loading = true; $status.application.loading = true;
const data = await get(`/applications/${id}`); const data = await get(`/applications/${id}/status`);
isQueueActive = data.isQueueActive; isQueueActive = data.isQueueActive;
$status.application.isRunning = data.isRunning; $status.application.isRunning = data.isRunning;
$status.application.isExited = data.isExited; $status.application.isExited = data.isExited;

View File

@@ -1,32 +1,13 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
import { onDestroy, onMount } from 'svelte';
export const load: Load = async ({ fetch, params, url, stuff }) => {
try {
const response = await get(`/applications/${params.id}/logs`);
return {
props: {
application: stuff.application,
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts"> <script lang="ts">
export let application: any;
import { page } from '$app/stores'; import { page } from '$app/stores';
import { get } from '$lib/api'; import { get } from '$lib/api';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { errorNotification } from '$lib/common'; import { errorNotification } from '$lib/common';
import LoadingLogs from '$lib/components/LoadingLogs.svelte'; import LoadingLogs from '$lib/components/LoadingLogs.svelte';
import { onMount, onDestroy } from 'svelte';
let application: any = {};
let logsLoading = false;
let loadLogsInterval: any = null; let loadLogsInterval: any = null;
let logs: any = []; let logs: any = [];
let lastLog: any = null; let lastLog: any = null;
@@ -37,6 +18,8 @@
const { id } = $page.params; const { id } = $page.params;
onMount(async () => { onMount(async () => {
const response = await get(`/applications/${id}`);
application = response.application;
loadAllLogs(); loadAllLogs();
loadLogsInterval = setInterval(() => { loadLogsInterval = setInterval(() => {
loadLogs(); loadLogs();
@@ -48,6 +31,7 @@
}); });
async function loadAllLogs() { async function loadAllLogs() {
try { try {
logsLoading = true;
const data: any = await get(`/applications/${id}/logs`); const data: any = await get(`/applications/${id}/logs`);
if (data?.logs) { if (data?.logs) {
lastLog = data.logs[data.logs.length - 1]; lastLog = data.logs[data.logs.length - 1];
@@ -56,9 +40,12 @@
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return errorNotification(error); return errorNotification(error);
} finally {
logsLoading = false;
} }
} }
async function loadLogs() { async function loadLogs() {
if (logsLoading) return;
try { try {
const newLogs: any = await get( const newLogs: any = await get(
`/applications/${id}/logs?since=${lastLog?.split(' ')[0] || 0}` `/applications/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`

View File

@@ -58,7 +58,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { errorNotification, handlerNotFoundLoad } from '$lib/common'; import { errorNotification, handlerNotFoundLoad } from '$lib/common';
import { appSession, status } from '$lib/store'; import { appSession, status, disabledButton } from '$lib/store';
import DeleteIcon from '$lib/components/DeleteIcon.svelte'; import DeleteIcon from '$lib/components/DeleteIcon.svelte';
import Loading from '$lib/components/Loading.svelte'; import Loading from '$lib/components/Loading.svelte';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
@@ -66,6 +66,9 @@
let loading = false; let loading = false;
let statusInterval: any = false; let statusInterval: any = false;
$disabledButton = !$appSession.isAdmin;
async function deleteDatabase() { async function deleteDatabase() {
const sure = confirm(`Are you sure you would like to delete '${database.name}'?`); const sure = confirm(`Are you sure you would like to delete '${database.name}'?`);
if (sure) { if (sure) {
@@ -138,6 +141,32 @@
<Loading fullscreen cover /> <Loading fullscreen cover />
{:else} {:else}
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase} {#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
{#if $status.database.isExited}
<a
href={!$disabledButton ? `/databases/${id}/logs` : null}
class=" icons bg-transparent tooltip-bottom text-sm flex items-center text-red-500 tooltip-red-500"
data-tooltip="Service exited with an error!"
sveltekit:prefetch
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentcolor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
/>
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
</a>
{/if}
{#if $status.database.initialLoading} {#if $status.database.initialLoading}
<button <button
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out" class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"

View File

@@ -1,31 +1,13 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
import { onDestroy, onMount } from 'svelte';
export const load: Load = async ({ fetch, params, url, stuff }) => {
try {
const response = await get(`/databases/${params.id}/logs`);
return {
props: {
database: stuff.database,
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts"> <script lang="ts">
export let database: any; import { onDestroy, onMount } from 'svelte';
import { page } from '$app/stores'; import { page } from '$app/stores';
import LoadingLogs from './_Loading.svelte'; import LoadingLogs from './_Loading.svelte';
import { get } from '$lib/api'; import { get } from '$lib/api';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { errorNotification } from '$lib/common'; import { errorNotification } from '$lib/common';
const { id } = $page.params;
let loadLogsInterval: any = null; let loadLogsInterval: any = null;
let logs: any = []; let logs: any = [];
@@ -34,9 +16,12 @@ import { errorNotification } from '$lib/common';
let followingLogs: any; let followingLogs: any;
let logsEl: any; let logsEl: any;
let position = 0; let position = 0;
let loadingLogs = false;
let database: any = {};
const { id } = $page.params;
onMount(async () => { onMount(async () => {
const { logs: firstLogs } = await get(`/databases/${id}/logs`);
logs = firstLogs;
loadAllLogs(); loadAllLogs();
loadLogsInterval = setInterval(() => { loadLogsInterval = setInterval(() => {
loadLogs(); loadLogs();
@@ -48,6 +33,7 @@ import { errorNotification } from '$lib/common';
}); });
async function loadAllLogs() { async function loadAllLogs() {
try { try {
loadingLogs = true;
const data: any = await get(`/databases/${id}/logs`); const data: any = await get(`/databases/${id}/logs`);
if (data?.logs) { if (data?.logs) {
lastLog = data.logs[data.logs.length - 1]; lastLog = data.logs[data.logs.length - 1];
@@ -56,13 +42,15 @@ import { errorNotification } from '$lib/common';
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return errorNotification(error); return errorNotification(error);
} finally {
loadingLogs = false;
} }
} }
async function loadLogs() { async function loadLogs() {
if (loadingLogs) return;
try { try {
const newLogs: any = await get( loadingLogs = true;
`/databases/${id}/logs?since=${lastLog?.split(' ')[0] || 0}` const newLogs: any = await get(`/databases/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`);
);
if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) { if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) {
logs = logs.concat(newLogs.logs); logs = logs.concat(newLogs.logs);
@@ -70,6 +58,8 @@ import { errorNotification } from '$lib/common';
} }
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally {
loadingLogs = false;
} }
} }
function detect() { function detect() {

View File

@@ -14,23 +14,25 @@
const { id } = $page.params; const { id } = $page.params;
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock'; let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
let loading = {
let loading = false; restart: false,
let loadingProxy = false; proxy: false,
let restarting = false; save: false
};
async function handleSubmit() { async function handleSubmit() {
loading = true; loading.save = true;
try { try {
return await post(`/destinations/${id}`, { ...destination }); await post(`/destinations/${id}`, { ...destination });
toast.push('Configuration saved.');
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally { } finally {
loading = false; loading.save = false;
} }
} }
onMount(async () => { onMount(async () => {
loadingProxy = true; loading.proxy = true;
const { isRunning } = await get(`/destinations/${id}/status`); const { isRunning } = await get(`/destinations/${id}/status`);
let proxyUsed = !destination.isCoolifyProxyUsed; let proxyUsed = !destination.isCoolifyProxyUsed;
if (isRunning === false && destination.isCoolifyProxyUsed === true) { if (isRunning === false && destination.isCoolifyProxyUsed === true) {
@@ -54,9 +56,10 @@
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally { } finally {
loadingProxy = false; loading.proxy = false;
} }
} }
loading.proxy = false;
}); });
async function changeProxySetting() { async function changeProxySetting() {
if (!cannotDisable) { if (!cannotDisable) {
@@ -73,7 +76,7 @@
} }
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed; destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
try { try {
loadingProxy = true; loading.proxy = true;
await post(`/destinations/${id}/settings`, { await post(`/destinations/${id}/settings`, {
isCoolifyProxyUsed: destination.isCoolifyProxyUsed, isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
engine: destination.engine engine: destination.engine
@@ -86,7 +89,7 @@
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally { } finally {
loadingProxy = false; loading.proxy = false;
} }
} }
} }
@@ -110,7 +113,7 @@
const sure = confirm($t('destination.confirm_restart_proxy')); const sure = confirm($t('destination.confirm_restart_proxy'));
if (sure) { if (sure) {
try { try {
restarting = true; loading.restart = true;
toast.push($t('destination.coolify_proxy_restarting')); toast.push($t('destination.coolify_proxy_restarting'));
await post(`/destinations/${id}/restart`, { await post(`/destinations/${id}/restart`, {
engine: destination.engine, engine: destination.engine,
@@ -121,7 +124,7 @@
window.location.reload(); window.location.reload();
}, 5000); }, 5000);
} finally { } finally {
restarting = false; loading.restart = false;
} }
} }
} }
@@ -134,16 +137,16 @@
<button <button
type="submit" type="submit"
class="bg-sky-600 hover:bg-sky-500" class="bg-sky-600 hover:bg-sky-500"
class:bg-sky-600={!loading} class:bg-sky-600={!loading.save}
class:hover:bg-sky-500={!loading} class:hover:bg-sky-500={!loading.save}
disabled={loading} disabled={loading.save}
>{loading ? $t('forms.saving') : $t('forms.save')} >{loading.save ? $t('forms.saving') : $t('forms.save')}
</button> </button>
<button <button
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'} class={loading.restart ? '' : 'bg-red-600 hover:bg-red-500'}
disabled={restarting} disabled={loading.restart}
on:click|preventDefault={forceRestartProxy} on:click|preventDefault={forceRestartProxy}
>{restarting >{loading.restart
? $t('destination.restarting_please_wait') ? $t('destination.restarting_please_wait')
: $t('destination.force_restart_proxy')}</button : $t('destination.force_restart_proxy')}</button
> >
@@ -185,7 +188,7 @@
{#if $appSession.teamId === '0'} {#if $appSession.teamId === '0'}
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
loading={loadingProxy} loading={loading.proxy}
disabled={cannotDisable} disabled={cannotDisable}
bind:setting={destination.isCoolifyProxyUsed} bind:setting={destination.isCoolifyProxyUsed}
on:click={changeProxySetting} on:click={changeProxySetting}

View File

@@ -21,10 +21,9 @@
payload = { payload = {
name: $t('sources.remote_docker'), name: $t('sources.remote_docker'),
remoteEngine: true, remoteEngine: true,
ipAddress: null, remoteIpAddress: null,
user: 'root', remoteUser: 'root',
port: 22, remotePort: 22,
sshPrivateKey: null,
network: cuid(), network: cuid(),
isCoolifyProxyUsed: true isCoolifyProxyUsed: true
}; };

View File

@@ -18,8 +18,7 @@
const { id } = await post(`/destinations/new`, { const { id } = await post(`/destinations/new`, {
...payload ...payload
}); });
await goto(`/destinations/${id}`); return await goto(`/destinations/${id}`);
window.location.reload();
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally { } finally {
@@ -50,25 +49,25 @@
</div> </div>
<div class="grid grid-cols-2 items-center px-10"> <div class="grid grid-cols-2 items-center px-10">
<label for="ipAddress" class="text-base font-bold text-stone-100" <label for="remoteIpAddress" class="text-base font-bold text-stone-100"
>{$t('forms.ip_address')}</label >{$t('forms.ip_address')}</label
> >
<input <input
required required
name="ipAddress" name="remoteIpAddress"
placeholder="{$t('forms.eg')}: 192.168..." placeholder="{$t('forms.eg')}: 192.168..."
bind:value={payload.ipAddress} bind:value={payload.remoteIpAddress}
/> />
</div> </div>
<div class="grid grid-cols-2 items-center px-10"> <div class="grid grid-cols-2 items-center px-10">
<label for="user" class="text-base font-bold text-stone-100">{$t('forms.user')}</label> <label for="remoteUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
<input required name="user" placeholder="{$t('forms.eg')}: root" bind:value={payload.user} /> <input required name="remoteUser" placeholder="{$t('forms.eg')}: root" bind:value={payload.remoteUser} />
</div> </div>
<div class="grid grid-cols-2 items-center px-10"> <div class="grid grid-cols-2 items-center px-10">
<label for="port" class="text-base font-bold text-stone-100">{$t('forms.port')}</label> <label for="remotePort" class="text-base font-bold text-stone-100">{$t('forms.port')}</label>
<input required name="port" placeholder="{$t('forms.eg')}: 22" bind:value={payload.port} /> <input required name="remotePort" placeholder="{$t('forms.eg')}: 22" bind:value={payload.remotePort} />
</div> </div>
<div class="grid grid-cols-2 items-center px-10"> <div class="grid grid-cols-2 items-center px-10">
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label> <label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>

View File

@@ -9,56 +9,65 @@
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte'; import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { errorNotification, generateRemoteEngine } from '$lib/common'; import { errorNotification } from '$lib/common';
import { appSession } from '$lib/store'; import { appSession } from '$lib/store';
const { id } = $page.params; const { id } = $page.params;
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock'; let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
let loading = false; let loading = {
let loadingProxy = true; restart: false,
let restarting = false; proxy: true,
save: false,
verify: false
};
$: isDisabled = !$appSession.isAdmin; $: isDisabled = !$appSession.isAdmin;
async function handleSubmit() { async function handleSubmit() {
loading = true; loading.save = true;
try { try {
return await post(`/destinations/${id}`, { ...destination }); await post(`/destinations/${id}`, { ...destination });
toast.push('Configuration saved.');
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally { } finally {
loading = false; loading.save = false;
} }
} }
onMount(async () => { onMount(async () => {
loadingProxy = true; loading.proxy = true;
const { isRunning } = await get(`/destinations/${id}/status`); if (destination.remoteEngine && destination.remoteVerified) {
if (isRunning === false && destination.isCoolifyProxyUsed === true) { const { isRunning } = await get(`/destinations/${id}/status`);
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed; if (isRunning === false && destination.isCoolifyProxyUsed === true) {
try { destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
await post(`/destinations/${id}/settings`, { try {
isCoolifyProxyUsed: destination.isCoolifyProxyUsed, await post(`/destinations/${id}/settings`, {
engine: destination.engine isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
}); engine: destination.engine
await stopProxy(); });
} catch (error) { await stopProxy();
return errorNotification(error); } catch (error) {
} return errorNotification(error);
} else if (isRunning === true && destination.isCoolifyProxyUsed === false) { }
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed; } else if (isRunning === true && destination.isCoolifyProxyUsed === false) {
try { destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
await post(`/destinations/${id}/settings`, { try {
isCoolifyProxyUsed: destination.isCoolifyProxyUsed, await post(`/destinations/${id}/settings`, {
engine: destination.engine isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
}); engine: destination.engine
await startProxy(); });
} catch (error) { await startProxy();
return errorNotification(error); } catch (error) {
return errorNotification(error);
}
} }
} }
loadingProxy = false;
loading.proxy = false;
}); });
async function changeProxySetting() { async function changeProxySetting() {
loadingProxy = true; loading.proxy = true;
if (!cannotDisable) { if (!cannotDisable) {
const isProxyActivated = destination.isCoolifyProxyUsed; const isProxyActivated = destination.isCoolifyProxyUsed;
if (isProxyActivated) { if (isProxyActivated) {
@@ -68,7 +77,7 @@
} Coolify proxy? It will remove the proxy for all configured networks and all deployments! Nothing will be reachable if you do it!` } Coolify proxy? It will remove the proxy for all configured networks and all deployments! Nothing will be reachable if you do it!`
); );
if (!sure) { if (!sure) {
loadingProxy = false; loading.proxy = false;
return; return;
} }
} }
@@ -87,7 +96,7 @@
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally { } finally {
loadingProxy = false; loading.proxy = false;
} }
} }
} }
@@ -111,7 +120,7 @@
const sure = confirm($t('destination.confirm_restart_proxy')); const sure = confirm($t('destination.confirm_restart_proxy'));
if (sure) { if (sure) {
try { try {
restarting = true; loading.restart = true;
toast.push($t('destination.coolify_proxy_restarting')); toast.push($t('destination.coolify_proxy_restarting'));
await post(`/destinations/${id}/restart`, { await post(`/destinations/${id}/restart`, {
engine: destination.engine, engine: destination.engine,
@@ -122,18 +131,21 @@
window.location.reload(); window.location.reload();
}, 5000); }, 5000);
} finally { } finally {
restarting = false; loading.restart = false;
} }
} }
} }
async function verifyRemoteDocker() { async function verifyRemoteDocker() {
try { try {
loading = true; loading.verify = true;
return await post(`/destinations/${id}/verify`, {}); await post(`/destinations/${id}/verify`, {});
destination.remoteVerified = true;
toast.push('Remote Docker Engine verified!');
return;
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally { } finally {
loading = false; loading.verify = false;
} }
} }
</script> </script>
@@ -145,24 +157,27 @@
<button <button
type="submit" type="submit"
class="bg-sky-600 hover:bg-sky-500" class="bg-sky-600 hover:bg-sky-500"
class:bg-sky-600={!loading} class:bg-sky-600={!loading.save}
class:hover:bg-sky-500={!loading} class:hover:bg-sky-500={!loading.save}
disabled={loading} disabled={loading.save}
>{loading ? $t('forms.saving') : $t('forms.save')} >{loading.save ? $t('forms.saving') : $t('forms.save')}
</button> </button>
{#if !destination.remoteVerified} {#if !destination.remoteVerified}
<button on:click|preventDefault|stopPropagation={verifyRemoteDocker} <button
>Verify Remote Docker Engine</button disabled={loading.verify}
on:click|preventDefault|stopPropagation={verifyRemoteDocker}
>{loading.verify ? 'Verifying...' : 'Verify Remote Docker Engine'}</button
>
{:else}
<button
class={loading.restart ? '' : 'bg-red-600 hover:bg-red-500'}
disabled={loading.restart}
on:click|preventDefault={forceRestartProxy}
>{loading.restart
? $t('destination.restarting_please_wait')
: $t('destination.force_restart_proxy')}</button
> >
{/if} {/if}
<button
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
disabled={restarting}
on:click|preventDefault={forceRestartProxy}
>{restarting
? $t('destination.restarting_please_wait')
: $t('destination.force_restart_proxy')}</button
>
{/if} {/if}
</div> </div>
<div class="grid grid-cols-2 items-center px-10 "> <div class="grid grid-cols-2 items-center px-10 ">
@@ -232,7 +247,7 @@
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
disabled={cannotDisable} disabled={cannotDisable}
loading={loadingProxy} loading={loading.proxy}
bind:setting={destination.isCoolifyProxyUsed} bind:setting={destination.isCoolifyProxyUsed}
on:click={changeProxySetting} on:click={changeProxySetting}
title={$t('destination.use_coolify_proxy')} title={$t('destination.use_coolify_proxy')}

View File

@@ -120,8 +120,9 @@
async function getStatus() { async function getStatus() {
if ($status.service.loading) return; if ($status.service.loading) return;
$status.service.loading = true; $status.service.loading = true;
const data = await get(`/services/${id}`); const data = await get(`/services/${id}/status`);
$status.service.isRunning = data.isRunning; $status.service.isRunning = data.isRunning;
$status.service.isExited = data.isExited;
$status.service.initialLoading = false; $status.service.initialLoading = false;
$status.service.loading = false; $status.service.loading = false;
} }
@@ -173,6 +174,32 @@
> >
<div class="border border-stone-700 h-8" /> <div class="border border-stone-700 h-8" />
{/if} {/if}
{#if $status.service.isExited}
<a
href={!$disabledButton ? `/services/${id}/logs` : null}
class=" icons bg-transparent tooltip-bottom text-sm flex items-center text-red-500 tooltip-red-500"
data-tooltip="Service exited with an error!"
sveltekit:prefetch
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentcolor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
/>
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
</a>
{/if}
{#if $status.service.initialLoading} {#if $status.service.initialLoading}
<button <button
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out" class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
@@ -347,7 +374,7 @@
> >
<div class="border border-stone-700 h-8" /> <div class="border border-stone-700 h-8" />
<a <a
href={$status.service.isRunning ? `/services/${id}/logs` : null} href={!$disabledButton && $status.service.isRunning ? `/services/${id}/logs` : null}
sveltekit:prefetch sveltekit:prefetch
class="hover:text-pink-500 rounded" class="hover:text-pink-500 rounded"
class:text-pink-500={$page.url.pathname === `/services/${id}/logs`} class:text-pink-500={$page.url.pathname === `/services/${id}/logs`}

View File

@@ -1,32 +1,13 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
import { onDestroy, onMount } from 'svelte';
export const load: Load = async ({ fetch, params, url, stuff }) => {
try {
const response = await get(`/services/${params.id}/logs`);
return {
props: {
service: stuff.service,
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts"> <script lang="ts">
export let service: any;
import { page } from '$app/stores'; import { page } from '$app/stores';
import LoadingLogs from './_Loading.svelte'; import LoadingLogs from './_Loading.svelte';
import { get } from '$lib/api'; import { get } from '$lib/api';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { errorNotification } from '$lib/common'; import { errorNotification } from '$lib/common';
import { onDestroy, onMount } from 'svelte';
let service: any = {};
let logsLoading = false;
let loadLogsInterval: any = null; let loadLogsInterval: any = null;
let logs: any = []; let logs: any = [];
let lastLog: any = null; let lastLog: any = null;
@@ -36,18 +17,23 @@ import { errorNotification } from '$lib/common';
let position = 0; let position = 0;
const { id } = $page.params; const { id } = $page.params;
onMount(async () => { onMount(async () => {
const response = await get(`/services/${id}`);
service = response.service;
loadAllLogs(); loadAllLogs();
loadLogsInterval = setInterval(() => { loadLogsInterval = setInterval(() => {
loadLogs(); loadLogs();
}, 1000); }, 1000);
}); });
onDestroy(() => { onDestroy(() => {
clearInterval(loadLogsInterval); clearInterval(loadLogsInterval);
clearInterval(followingInterval); clearInterval(followingInterval);
}); });
async function loadAllLogs() { async function loadAllLogs() {
try { try {
logsLoading = true;
const data: any = await get(`/services/${id}/logs`); const data: any = await get(`/services/${id}/logs`);
if (data?.logs) { if (data?.logs) {
lastLog = data.logs[data.logs.length - 1]; lastLog = data.logs[data.logs.length - 1];
@@ -56,13 +42,14 @@ import { errorNotification } from '$lib/common';
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return errorNotification(error); return errorNotification(error);
} finally {
logsLoading = false;
} }
} }
async function loadLogs() { async function loadLogs() {
if (logsLoading) return;
try { try {
const newLogs: any = await get( const newLogs: any = await get(`/services/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`);
`/services/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`
);
if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) { if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) {
logs = logs.concat(newLogs.logs); logs = logs.concat(newLogs.logs);

66
pnpm-lock.yaml generated
View File

@@ -38,6 +38,7 @@ importers:
eslint: 8.20.0 eslint: 8.20.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
execa: ^6.1.0
fastify: 4.2.1 fastify: 4.2.1
fastify-plugin: 4.0.0 fastify-plugin: 4.0.0
generate-password: 1.7.0 generate-password: 1.7.0
@@ -53,7 +54,7 @@ importers:
prettier: 2.7.1 prettier: 2.7.1
prisma: 3.15.2 prisma: 3.15.2
rimraf: 3.0.2 rimraf: 3.0.2
ssh-config: ^4.1.6 ssh-config: 4.1.6
strip-ansi: 7.0.1 strip-ansi: 7.0.1
tsconfig-paths: 4.0.0 tsconfig-paths: 4.0.0
typescript: 4.7.4 typescript: 4.7.4
@@ -77,6 +78,7 @@ importers:
dayjs: 1.11.3 dayjs: 1.11.3
dockerode: 3.3.2 dockerode: 3.3.2
dotenv-extended: 2.9.0 dotenv-extended: 2.9.0
execa: 6.1.0
fastify: 4.2.1 fastify: 4.2.1
fastify-plugin: 4.0.0 fastify-plugin: 4.0.0
generate-password: 1.7.0 generate-password: 1.7.0
@@ -2691,6 +2693,21 @@ packages:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
dev: false dev: false
/execa/6.1.0:
resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
cross-spawn: 7.0.3
get-stream: 6.0.1
human-signals: 3.0.1
is-stream: 3.0.0
merge-stream: 2.0.0
npm-run-path: 5.1.0
onetime: 6.0.0
signal-exit: 3.0.7
strip-final-newline: 3.0.0
dev: false
/exit/0.1.2: /exit/0.1.2:
resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -3171,6 +3188,11 @@ packages:
numbered: 1.1.0 numbered: 1.1.0
dev: false dev: false
/human-signals/3.0.1:
resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==}
engines: {node: '>=12.20.0'}
dev: false
/ieee754/1.2.1: /ieee754/1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
dev: false dev: false
@@ -3364,6 +3386,11 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: false dev: false
/is-stream/3.0.0:
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/is-string-and-not-blank/0.0.2: /is-string-and-not-blank/0.0.2:
resolution: {integrity: sha512-FyPGAbNVyZpTeDCTXnzuwbu9/WpNXbCfbHXLpCRpN4GANhS00eEIP5Ef+k5HYSNIzIhdN9zRDoBj6unscECvtQ==} resolution: {integrity: sha512-FyPGAbNVyZpTeDCTXnzuwbu9/WpNXbCfbHXLpCRpN4GANhS00eEIP5Ef+k5HYSNIzIhdN9zRDoBj6unscECvtQ==}
engines: {node: '>=6.4.0'} engines: {node: '>=6.4.0'}
@@ -3712,6 +3739,10 @@ packages:
engines: {node: '>= 0.10.0'} engines: {node: '>= 0.10.0'}
dev: true dev: true
/merge-stream/2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: false
/merge2/1.4.1: /merge2/1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@@ -3754,6 +3785,11 @@ packages:
hasBin: true hasBin: true
dev: false dev: false
/mimic-fn/4.0.0:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
engines: {node: '>=12'}
dev: false
/mimic-response/1.0.1: /mimic-response/1.0.1:
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -3934,6 +3970,13 @@ packages:
string.prototype.padend: 3.1.3 string.prototype.padend: 3.1.3
dev: true dev: true
/npm-run-path/5.1.0:
resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
path-key: 4.0.0
dev: false
/numbered/1.1.0: /numbered/1.1.0:
resolution: {integrity: sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g==} resolution: {integrity: sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g==}
dev: false dev: false
@@ -3981,6 +4024,13 @@ packages:
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
/onetime/6.0.0:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
engines: {node: '>=12'}
dependencies:
mimic-fn: 4.0.0
dev: false
/optionator/0.9.1: /optionator/0.9.1:
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -4172,6 +4222,11 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'} engines: {node: '>=8'}
/path-key/4.0.0:
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
engines: {node: '>=12'}
dev: false
/path-parse/1.0.7: /path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
@@ -4768,6 +4823,10 @@ packages:
get-intrinsic: 1.1.1 get-intrinsic: 1.1.1
object-inspect: 1.12.1 object-inspect: 1.12.1
/signal-exit/3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
dev: false
/simple-update-notifier/1.0.7: /simple-update-notifier/1.0.7:
resolution: {integrity: sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==} resolution: {integrity: sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==}
engines: {node: '>=8.10.0'} engines: {node: '>=8.10.0'}
@@ -4935,6 +4994,11 @@ packages:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'} engines: {node: '>=4'}
/strip-final-newline/3.0.0:
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
engines: {node: '>=12'}
dev: false
/strip-indent/3.0.0: /strip-indent/3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'} engines: {node: '>=8'}