wip
This commit is contained in:
parent
2007ba0c3b
commit
4ad7e1f8e6
@ -17,6 +17,7 @@
|
||||
"@playwright/test": "1.28.1",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.48",
|
||||
"@sveltejs/kit": "1.0.0-next.572",
|
||||
"@types/js-cookie": "3.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.44.0",
|
||||
"@typescript-eslint/parser": "5.44.0",
|
||||
"autoprefixer": "10.4.13",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { writable, readable, type Writable, type Readable } from 'svelte/store';
|
||||
import { writable, readable, type Writable } from 'svelte/store';
|
||||
import superjson from 'superjson';
|
||||
import type { AppRouter, PrismaPermission } from 'server/src/trpc';
|
||||
import type { AppRouter } from 'server/src/trpc';
|
||||
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
|
||||
import { browser, dev } from '$app/environment';
|
||||
import Cookies from 'js-cookie';
|
||||
|
@ -6,7 +6,7 @@
|
||||
import { appSession } from '$lib/store';
|
||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import UpdateAvailable from '$lib/components/UpdateAvailable.svelte';
|
||||
// import UpdateAvailable from '$lib/components/UpdateAvailable.svelte';
|
||||
import Cookies from 'js-cookie';
|
||||
import { errorNotification } from '$lib/common';
|
||||
import Toasts from '$lib/components/Toasts.svelte';
|
||||
@ -346,7 +346,7 @@
|
||||
</svg>
|
||||
IAM {#if $appSession.pendingInvitations.length > 0}
|
||||
<span class="indicator-item rounded-full badge badge-primary"
|
||||
>{pendingInvitations.length}</span
|
||||
>{$appSession.pendingInvitations.length}</span
|
||||
>
|
||||
{/if}
|
||||
</a>
|
||||
|
@ -2,6 +2,18 @@
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
import { dev } from '$app/environment';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { asyncSleep, errorNotification, getRndInteger } from '$lib/common';
|
||||
import { appSession, search, t } from '$lib/store';
|
||||
|
||||
import ApplicationsIcons from '$lib/components/svg/applications/ApplicationIcons.svelte';
|
||||
import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte';
|
||||
import ServiceIcons from '$lib/components/svg/services/ServiceIcons.svelte';
|
||||
import NewResource from '$lib/components/NewResource.svelte';
|
||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||
|
||||
const {
|
||||
applications,
|
||||
foundUnconfiguredApplication,
|
||||
@ -14,17 +26,6 @@
|
||||
settings
|
||||
} = data;
|
||||
let filtered: any = setInitials();
|
||||
import { asyncSleep, errorNotification, getRndInteger } from '$lib/common';
|
||||
import { appSession, search, t } from '$lib/store';
|
||||
|
||||
import ApplicationsIcons from '$lib/components/svg/applications/ApplicationIcons.svelte';
|
||||
import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte';
|
||||
import ServiceIcons from '$lib/components/svg/services/ServiceIcons.svelte';
|
||||
import { dev } from '$app/environment';
|
||||
import NewResource from '$lib/components/NewResource.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||
|
||||
let numberOfGetStatus = 0;
|
||||
let status: any = {};
|
||||
let noInitialStatus: any = {
|
||||
@ -155,7 +156,7 @@
|
||||
let isRunning = false;
|
||||
let isDegraded = false;
|
||||
if (buildPack || simpleDockerfile) {
|
||||
const response = await t.applications.status.query({ id })
|
||||
const response = await t.applications.status.query({ id });
|
||||
if (response.length === 0) {
|
||||
isRunning = false;
|
||||
} else if (response.length === 1) {
|
||||
@ -177,7 +178,7 @@
|
||||
}
|
||||
}
|
||||
} else if (typeof dualCerts !== 'undefined') {
|
||||
const response = await t.services.status.query({ id })
|
||||
const response = await t.services.status.query({ id });
|
||||
if (Object.keys(response).length === 0) {
|
||||
isRunning = false;
|
||||
} else {
|
||||
@ -197,7 +198,7 @@
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const response = await get(`/databases/${id}/status`);
|
||||
const response = await t.databases.status.query({ id });
|
||||
isRunning = response.isRunning;
|
||||
}
|
||||
|
||||
@ -381,7 +382,7 @@
|
||||
'Are you sure? This will delete all UNCONFIGURED applications and their data.'
|
||||
);
|
||||
if (sure) {
|
||||
// await post(`/applications/cleanup/unconfigured`, {});
|
||||
await t.applications.cleanup.query();
|
||||
return window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
@ -394,7 +395,7 @@
|
||||
'Are you sure? This will delete all UNCONFIGURED services and their data.'
|
||||
);
|
||||
if (sure) {
|
||||
// await post(`/services/cleanup/unconfigured`, {});
|
||||
await t.services.cleanup.query();
|
||||
return window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
@ -407,7 +408,7 @@
|
||||
'Are you sure? This will delete all UNCONFIGURED databases and their data.'
|
||||
);
|
||||
if (sure) {
|
||||
// await post(`/databases/cleanup/unconfigured`, {});
|
||||
await t.databases.cleanup.query();
|
||||
return window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
@ -418,7 +419,7 @@
|
||||
try {
|
||||
const sure = confirm('Are you sure? This will delete this application!');
|
||||
if (sure) {
|
||||
// await del(`/applications/${id}`, { force: true });
|
||||
await t.applications.delete.mutate({ id, force: true });
|
||||
return window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
@ -429,6 +430,7 @@
|
||||
try {
|
||||
const sure = confirm('Are you sure? This will delete this service!');
|
||||
if (sure) {
|
||||
await t.services.delete.mutate({ id });
|
||||
// await del(`/services/${id}`, {});
|
||||
return window.location.reload();
|
||||
}
|
||||
@ -440,7 +442,7 @@
|
||||
try {
|
||||
const sure = confirm('Are you sure? This will delete this database!');
|
||||
if (sure) {
|
||||
// await del(`/databases/${id}`, { force: true });
|
||||
await t.databases.delete.mutate({ id, force: true });
|
||||
return window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"exclude": ["node_modules/*", ".svelte-kit/*", "public/*"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
@ -10,12 +11,8 @@
|
||||
"sourceMap": true,
|
||||
"strict": false,
|
||||
"paths": {
|
||||
"$lib": [
|
||||
"src/lib"
|
||||
],
|
||||
"$lib/*": [
|
||||
"src/lib/*"
|
||||
],
|
||||
"$lib": ["src/lib"],
|
||||
"$lib/*": ["src/lib/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,7 @@
|
||||
"@types/node": "18.11.9",
|
||||
"@types/node-fetch": "2.6.2",
|
||||
"@types/shell-quote": "^1.7.1",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/ws": "8.5.3",
|
||||
"npm-run-all": "4.1.5",
|
||||
"rimraf": "3.0.2",
|
||||
|
@ -1,5 +1,5 @@
|
||||
const dotenv = require('dotenv');
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
// const isDev = process.env.NODE_ENV === 'development';
|
||||
// dotenv.config({ path: isDev ? '../../.env' : '.env' });
|
||||
dotenv.config();
|
||||
const { z } = require('zod');
|
||||
|
@ -68,8 +68,8 @@ export const decrypt = (hashString: string) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export function generateRangeArray(start, end) {
|
||||
return Array.from({ length: end - start }, (v, k) => k + start);
|
||||
export function generateRangeArray(start: number, end: number) {
|
||||
return Array.from({ length: end - start }, (_v, k) => k + start);
|
||||
}
|
||||
export function generateTimestamp(): string {
|
||||
return `${day().format('HH:mm:ss.SSS')}`;
|
||||
@ -94,7 +94,7 @@ export async function getTemplates() {
|
||||
let data = await open.readFile({ encoding: 'utf-8' });
|
||||
let jsonData = JSON.parse(data);
|
||||
if (isARM(process.arch)) {
|
||||
jsonData = jsonData.filter((d) => d.arch !== 'amd64');
|
||||
jsonData = jsonData.filter((d: { arch: string }) => d.arch !== 'amd64');
|
||||
}
|
||||
return jsonData;
|
||||
} catch (error) {
|
||||
@ -109,3 +109,26 @@ export function isARM(arch: string) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function removeService({ id }: { id: string }): Promise<void> {
|
||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.serviceSetting.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.fider.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.umami.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.hasura.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.glitchTip.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.moodle.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.searxng.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.weblate.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.taiga.deleteMany({ where: { serviceId: id } });
|
||||
|
||||
await prisma.service.delete({ where: { id } });
|
||||
}
|
||||
|
@ -1,31 +1,39 @@
|
||||
import { executeCommand } from "./executeCommand";
|
||||
import { executeCommand } from './executeCommand';
|
||||
|
||||
export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<{ found: boolean, status?: { isExited: boolean, isRunning: boolean, isRestarting: boolean } }> {
|
||||
export async function checkContainer({
|
||||
dockerId,
|
||||
container,
|
||||
remove = false
|
||||
}: {
|
||||
dockerId: string;
|
||||
container: string;
|
||||
remove?: boolean;
|
||||
}): Promise<{
|
||||
found: boolean;
|
||||
status?: { isExited: boolean; isRunning: boolean; isRestarting: boolean };
|
||||
}> {
|
||||
let containerFound = false;
|
||||
try {
|
||||
const { stdout } = await executeCommand({
|
||||
dockerId,
|
||||
command:
|
||||
`docker inspect --format '{{json .State}}' ${container}`
|
||||
command: `docker inspect --format '{{json .State}}' ${container}`
|
||||
});
|
||||
containerFound = true
|
||||
containerFound = true;
|
||||
const parsedStdout = JSON.parse(stdout);
|
||||
const status = parsedStdout.Status;
|
||||
const isRunning = status === 'running';
|
||||
const isRestarting = status === 'restarting'
|
||||
const isExited = status === 'exited'
|
||||
const isRestarting = status === 'restarting';
|
||||
const isExited = status === 'exited';
|
||||
if (status === 'created') {
|
||||
await executeCommand({
|
||||
dockerId,
|
||||
command:
|
||||
`docker rm ${container}`
|
||||
command: `docker rm ${container}`
|
||||
});
|
||||
}
|
||||
if (remove && status === 'exited') {
|
||||
await executeCommand({
|
||||
dockerId,
|
||||
command:
|
||||
`docker rm ${container}`
|
||||
command: `docker rm ${container}`
|
||||
});
|
||||
}
|
||||
|
||||
@ -43,5 +51,74 @@ export async function checkContainer({ dockerId, container, remove = false }: {
|
||||
return {
|
||||
found: false
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
export async function removeContainer({
|
||||
id,
|
||||
dockerId
|
||||
}: {
|
||||
id: string;
|
||||
dockerId: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const { stdout } = await executeCommand({
|
||||
dockerId,
|
||||
command: `docker inspect --format '{{json .State}}' ${id}`
|
||||
});
|
||||
if (JSON.parse(stdout).Running) {
|
||||
await executeCommand({ dockerId, command: `docker stop -t 0 ${id}` });
|
||||
await executeCommand({ dockerId, command: `docker rm ${id}` });
|
||||
}
|
||||
if (JSON.parse(stdout).Status === 'exited') {
|
||||
await executeCommand({ dockerId, command: `docker rm ${id}` });
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function stopDatabaseContainer(database: any): Promise<boolean> {
|
||||
let everStarted = false;
|
||||
const {
|
||||
id,
|
||||
destinationDockerId,
|
||||
destinationDocker: { engine, id: dockerId }
|
||||
} = database;
|
||||
if (destinationDockerId) {
|
||||
try {
|
||||
const { stdout } = await executeCommand({
|
||||
dockerId,
|
||||
command: `docker inspect --format '{{json .State}}' ${id}`
|
||||
});
|
||||
|
||||
if (stdout) {
|
||||
everStarted = true;
|
||||
await removeContainer({ id, dockerId });
|
||||
}
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
}
|
||||
return everStarted;
|
||||
}
|
||||
export async function stopTcpHttpProxy(
|
||||
id: string,
|
||||
destinationDocker: any,
|
||||
publicPort: number,
|
||||
forceName: string | null = null
|
||||
): Promise<{ stdout: string; stderr: string } | Error | unknown> {
|
||||
const { id: dockerId } = destinationDocker;
|
||||
let container = `${id}-${publicPort}`;
|
||||
if (forceName) container = forceName;
|
||||
const { found } = await checkContainer({ dockerId, container });
|
||||
try {
|
||||
if (!found) return true;
|
||||
return await executeCommand({
|
||||
dockerId,
|
||||
command: `docker stop -t 0 ${container} && docker rm ${container}`,
|
||||
shell: true
|
||||
});
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import sshConfig from 'ssh-config';
|
||||
|
||||
import { getFreeSSHLocalPort } from './ssh';
|
||||
import { env } from '../env';
|
||||
import { saveBuildLog } from './logging';
|
||||
import { BuildLog, saveBuildLog } from './logging';
|
||||
import { decrypt } from './common';
|
||||
|
||||
export async function executeCommand({
|
||||
@ -31,23 +31,26 @@ export async function executeCommand({
|
||||
const { execa, execaCommand } = await import('execa');
|
||||
const { parse } = await import('shell-quote');
|
||||
const parsedCommand = parse(command);
|
||||
const dockerCommand = parsedCommand[0];
|
||||
const dockerArgs = parsedCommand.slice(1);
|
||||
const dockerCommand = parsedCommand[0]?.toString();
|
||||
const dockerArgs = parsedCommand.slice(1).toString();
|
||||
|
||||
if (dockerId) {
|
||||
if (dockerId && dockerCommand && dockerArgs) {
|
||||
const destinationDocker = await prisma.destinationDocker.findUnique({
|
||||
where: { id: dockerId }
|
||||
});
|
||||
if (!destinationDocker) {
|
||||
throw new Error('Destination docker not found');
|
||||
}
|
||||
let { remoteEngine, remoteIpAddress, engine } = destinationDocker;
|
||||
let {
|
||||
remoteEngine,
|
||||
remoteIpAddress,
|
||||
engine = 'unix:///var/run/docker.sock'
|
||||
} = destinationDocker;
|
||||
if (remoteEngine) {
|
||||
await createRemoteEngineConfiguration(dockerId);
|
||||
engine = `ssh://${remoteIpAddress}-remote`;
|
||||
} else {
|
||||
engine = 'unix:///var/run/docker.sock';
|
||||
}
|
||||
|
||||
if (env.CODESANDBOX_HOST) {
|
||||
if (command.startsWith('docker compose')) {
|
||||
command = command.replace(/docker compose/gi, 'docker-compose');
|
||||
@ -73,12 +76,12 @@ export async function executeCommand({
|
||||
}
|
||||
const logs: any[] = [];
|
||||
if (subprocess && subprocess.stdout && subprocess.stderr) {
|
||||
subprocess.stdout.on('data', async (data) => {
|
||||
subprocess.stdout.on('data', async (data: string) => {
|
||||
const stdout = data.toString();
|
||||
const array = stdout.split('\n');
|
||||
for (const line of array) {
|
||||
if (line !== '\n' && line !== '') {
|
||||
const log = {
|
||||
const log: BuildLog = {
|
||||
line: `${line.replace('\n', '')}`,
|
||||
buildId,
|
||||
applicationId
|
||||
@ -90,7 +93,7 @@ export async function executeCommand({
|
||||
}
|
||||
}
|
||||
});
|
||||
subprocess.stderr.on('data', async (data) => {
|
||||
subprocess.stderr.on('data', async (data: string) => {
|
||||
const stderr = data.toString();
|
||||
const array = stderr.split('\n');
|
||||
for (const line of array) {
|
||||
@ -107,7 +110,7 @@ export async function executeCommand({
|
||||
}
|
||||
}
|
||||
});
|
||||
subprocess.on('exit', async (code) => {
|
||||
subprocess.on('exit', async (code: number) => {
|
||||
if (code === 0) {
|
||||
resolve('success');
|
||||
} else {
|
||||
|
@ -2,15 +2,13 @@ import { prisma } from '../prisma';
|
||||
import { encrypt, generateTimestamp, isDev } from './common';
|
||||
import { day } from './dayjs';
|
||||
|
||||
export const saveBuildLog = async ({
|
||||
line,
|
||||
buildId,
|
||||
applicationId
|
||||
}: {
|
||||
line: string;
|
||||
buildId: string;
|
||||
applicationId: string;
|
||||
}): Promise<any> => {
|
||||
export type Line = string | { shortMessage: string; stderr: string };
|
||||
export type BuildLog = {
|
||||
line: Line;
|
||||
buildId?: string;
|
||||
applicationId?: string;
|
||||
};
|
||||
export const saveBuildLog = async ({ line, buildId, applicationId }: BuildLog): Promise<any> => {
|
||||
if (buildId === 'undefined' || buildId === 'null' || !buildId) return;
|
||||
if (applicationId === 'undefined' || applicationId === 'null' || !applicationId) return;
|
||||
const { default: got } = await import('got');
|
||||
|
@ -12,7 +12,7 @@ const prismaGlobal = global as typeof global & {
|
||||
export const prisma: PrismaClient =
|
||||
prismaGlobal.prisma ||
|
||||
new PrismaClient({
|
||||
log: env.NODE_ENV === 'developments' ? ['query', 'error', 'warn'] : ['error']
|
||||
log: env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error']
|
||||
});
|
||||
|
||||
if (env.NODE_ENV !== 'production') {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { inferAsyncReturnType } from '@trpc/server';
|
||||
import { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify';
|
||||
import type { inferAsyncReturnType } from '@trpc/server';
|
||||
import type { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { env } from '../env';
|
||||
export interface User {
|
||||
|
@ -6,7 +6,8 @@ import {
|
||||
authRouter,
|
||||
dashboardRouter,
|
||||
applicationsRouter,
|
||||
servicesRouter
|
||||
servicesRouter,
|
||||
databasesRouter
|
||||
} from './routers';
|
||||
|
||||
export const appRouter = router({
|
||||
@ -14,7 +15,8 @@ export const appRouter = router({
|
||||
auth: authRouter,
|
||||
dashboard: dashboardRouter,
|
||||
applications: applicationsRouter,
|
||||
services: servicesRouter
|
||||
services: servicesRouter,
|
||||
databases: databasesRouter
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
@ -1,72 +1,44 @@
|
||||
import { z } from 'zod';
|
||||
import { privateProcedure, router } from '../trpc';
|
||||
import { decrypt, isARM, listSettings } from '../../lib/common';
|
||||
import { decrypt, isARM } from '../../lib/common';
|
||||
import { prisma } from '../../prisma';
|
||||
import { executeCommand } from '../../lib/executeCommand';
|
||||
import { checkContainer } from '../../lib/docker';
|
||||
import { checkContainer, removeContainer } from '../../lib/docker';
|
||||
|
||||
export const applicationsRouter = router({
|
||||
status: privateProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string()
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const id = input.id;
|
||||
const teamId = ctx.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw { status: 400, message: 'Team not found.' };
|
||||
}
|
||||
let payload = [];
|
||||
const application: any = await getApplicationFromDB(id, teamId);
|
||||
if (application?.destinationDockerId) {
|
||||
if (application.buildPack === 'compose') {
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: application.destinationDocker.id,
|
||||
command: `docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'`
|
||||
});
|
||||
const containersArray = containers.trim().split('\n');
|
||||
if (containersArray.length > 0 && containersArray[0] !== '') {
|
||||
for (const container of containersArray) {
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let isRestarting = false;
|
||||
const containerObj = JSON.parse(container);
|
||||
const status = containerObj.State;
|
||||
if (status === 'running') {
|
||||
isRunning = true;
|
||||
}
|
||||
if (status === 'exited') {
|
||||
isExited = true;
|
||||
}
|
||||
if (status === 'restarting') {
|
||||
isRestarting = true;
|
||||
}
|
||||
payload.push({
|
||||
name: containerObj.Names,
|
||||
status: {
|
||||
isRunning,
|
||||
isExited,
|
||||
isRestarting
|
||||
}
|
||||
});
|
||||
status: privateProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
||||
const id: string = input.id;
|
||||
const teamId = ctx.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw { status: 400, message: 'Team not found.' };
|
||||
}
|
||||
let payload = [];
|
||||
const application: any = await getApplicationFromDB(id, teamId);
|
||||
if (application?.destinationDockerId) {
|
||||
if (application.buildPack === 'compose') {
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: application.destinationDocker.id,
|
||||
command: `docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'`
|
||||
});
|
||||
const containersArray = containers.trim().split('\n');
|
||||
if (containersArray.length > 0 && containersArray[0] !== '') {
|
||||
for (const container of containersArray) {
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let isRestarting = false;
|
||||
const containerObj = JSON.parse(container);
|
||||
const status = containerObj.State;
|
||||
if (status === 'running') {
|
||||
isRunning = true;
|
||||
}
|
||||
if (status === 'exited') {
|
||||
isExited = true;
|
||||
}
|
||||
if (status === 'restarting') {
|
||||
isRestarting = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let isRestarting = false;
|
||||
const status = await checkContainer({
|
||||
dockerId: application.destinationDocker.id,
|
||||
container: id
|
||||
});
|
||||
if (status?.found) {
|
||||
isRunning = status.status.isRunning;
|
||||
isExited = status.status.isExited;
|
||||
isRestarting = status.status.isRestarting;
|
||||
payload.push({
|
||||
name: id,
|
||||
name: containerObj.Names,
|
||||
status: {
|
||||
isRunning,
|
||||
isExited,
|
||||
@ -75,8 +47,108 @@ export const applicationsRouter = router({
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let isRestarting = false;
|
||||
const status = await checkContainer({
|
||||
dockerId: application.destinationDocker.id,
|
||||
container: id
|
||||
});
|
||||
if (status?.found) {
|
||||
isRunning = status.status.isRunning;
|
||||
isExited = status.status.isExited;
|
||||
isRestarting = status.status.isRestarting;
|
||||
payload.push({
|
||||
name: id,
|
||||
status: {
|
||||
isRunning,
|
||||
isExited,
|
||||
isRestarting
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
return payload;
|
||||
}),
|
||||
cleanup: privateProcedure.query(async ({ ctx }) => {
|
||||
const teamId = ctx.user?.teamId;
|
||||
let applications = await prisma.application.findMany({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { settings: true, destinationDocker: true, teams: true }
|
||||
});
|
||||
for (const application of applications) {
|
||||
if (
|
||||
!application.buildPack ||
|
||||
!application.destinationDockerId ||
|
||||
!application.branch ||
|
||||
(!application.settings?.isBot && !application?.fqdn)
|
||||
) {
|
||||
if (application?.destinationDockerId && application.destinationDocker?.network) {
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: application.destinationDocker.id,
|
||||
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${application.id} --format '{{json .}}'`
|
||||
});
|
||||
if (containers) {
|
||||
const containersArray = containers.trim().split('\n');
|
||||
for (const container of containersArray) {
|
||||
const containerObj = JSON.parse(container);
|
||||
const id = containerObj.ID;
|
||||
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
await prisma.applicationSettings.deleteMany({ where: { applicationId: application.id } });
|
||||
await prisma.buildLog.deleteMany({ where: { applicationId: application.id } });
|
||||
await prisma.build.deleteMany({ where: { applicationId: application.id } });
|
||||
await prisma.secret.deleteMany({ where: { applicationId: application.id } });
|
||||
await prisma.applicationPersistentStorage.deleteMany({
|
||||
where: { applicationId: application.id }
|
||||
});
|
||||
await prisma.applicationConnectedDatabase.deleteMany({
|
||||
where: { applicationId: application.id }
|
||||
});
|
||||
await prisma.application.deleteMany({ where: { id: application.id } });
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
delete: privateProcedure
|
||||
.input(z.object({ force: z.boolean(), id: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { id, force } = input;
|
||||
const teamId = ctx.user?.teamId;
|
||||
const application = await prisma.application.findUnique({
|
||||
where: { id },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
if (!force && application?.destinationDockerId && application.destinationDocker?.network) {
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: application.destinationDocker.id,
|
||||
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
||||
});
|
||||
if (containers) {
|
||||
const containersArray = containers.trim().split('\n');
|
||||
for (const container of containersArray) {
|
||||
const containerObj = JSON.parse(container);
|
||||
const id = containerObj.ID;
|
||||
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
await prisma.applicationSettings.deleteMany({ where: { application: { id } } });
|
||||
await prisma.buildLog.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.build.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.secret.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.applicationConnectedDatabase.deleteMany({ where: { applicationId: id } });
|
||||
if (teamId === '0') {
|
||||
await prisma.application.deleteMany({ where: { id } });
|
||||
} else {
|
||||
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
|
||||
}
|
||||
return {};
|
||||
})
|
||||
});
|
||||
|
||||
|
84
apps/server/src/trpc/routers/databases.ts
Normal file
84
apps/server/src/trpc/routers/databases.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { z } from 'zod';
|
||||
import { privateProcedure, router } from '../trpc';
|
||||
import { decrypt } from '../../lib/common';
|
||||
import { prisma } from '../../prisma';
|
||||
import { executeCommand } from '../../lib/executeCommand';
|
||||
import { stopDatabaseContainer, stopTcpHttpProxy } from '../../lib/docker';
|
||||
|
||||
export const databasesRouter = router({
|
||||
status: privateProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
||||
const id = input.id;
|
||||
const teamId = ctx.user?.teamId;
|
||||
|
||||
let isRunning = false;
|
||||
const database = await prisma.database.findFirst({
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
if (database) {
|
||||
const { destinationDockerId, destinationDocker } = database;
|
||||
if (destinationDockerId) {
|
||||
try {
|
||||
const { stdout } = await executeCommand({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker inspect --format '{{json .State}}' ${id}`
|
||||
});
|
||||
|
||||
if (JSON.parse(stdout).Running) {
|
||||
isRunning = true;
|
||||
}
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
isRunning
|
||||
};
|
||||
}),
|
||||
cleanup: privateProcedure.query(async ({ ctx }) => {
|
||||
const teamId = ctx.user?.teamId;
|
||||
let databases = await prisma.database.findMany({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { settings: true, destinationDocker: true, teams: true }
|
||||
});
|
||||
for (const database of databases) {
|
||||
if (!database?.version) {
|
||||
const { id } = database;
|
||||
if (database.destinationDockerId) {
|
||||
const everStarted = await stopDatabaseContainer(database);
|
||||
if (everStarted)
|
||||
await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
||||
}
|
||||
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
||||
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
|
||||
await prisma.database.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
delete: privateProcedure
|
||||
.input(z.object({ id: z.string(), force: z.boolean() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { id, force } = input;
|
||||
const teamId = ctx.user?.teamId;
|
||||
const database = await prisma.database.findFirst({
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
if (!force) {
|
||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||
if (database.rootUserPassword)
|
||||
database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||
if (database.destinationDockerId) {
|
||||
const everStarted = await stopDatabaseContainer(database);
|
||||
if (everStarted)
|
||||
await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
||||
}
|
||||
}
|
||||
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
||||
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
|
||||
await prisma.database.delete({ where: { id } });
|
||||
return {};
|
||||
})
|
||||
});
|
@ -3,3 +3,4 @@ export * from './dashboard';
|
||||
export * from './settings';
|
||||
export * from './applications';
|
||||
export * from './services';
|
||||
export * from './databases';
|
||||
|
@ -1,82 +1,135 @@
|
||||
import { z } from 'zod';
|
||||
import { privateProcedure, router } from '../trpc';
|
||||
import { decrypt, getTemplates, listSettings } from '../../lib/common';
|
||||
import { decrypt, getTemplates, removeService } from '../../lib/common';
|
||||
import { prisma } from '../../prisma';
|
||||
import { executeCommand } from '../../lib/executeCommand';
|
||||
import { checkContainer } from '../../lib/docker';
|
||||
|
||||
export const servicesRouter = router({
|
||||
status: privateProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string()
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const id = input.id;
|
||||
const teamId = ctx.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw { status: 400, message: 'Team not found.' };
|
||||
}
|
||||
const service = await getServiceFromDB({ id, teamId });
|
||||
const { destinationDockerId } = service;
|
||||
let payload = {};
|
||||
if (destinationDockerId) {
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: service.destinationDocker.id,
|
||||
command: `docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'`
|
||||
});
|
||||
if (containers) {
|
||||
const containersArray = containers.trim().split('\n');
|
||||
if (containersArray.length > 0 && containersArray[0] !== '') {
|
||||
const templates = await getTemplates();
|
||||
let template = templates.find((t) => t.type === service.type);
|
||||
const templateStr = JSON.stringify(template);
|
||||
if (templateStr) {
|
||||
template = JSON.parse(templateStr.replaceAll('$$id', service.id));
|
||||
}
|
||||
for (const container of containersArray) {
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let isRestarting = false;
|
||||
let isExcluded = false;
|
||||
const containerObj = JSON.parse(container);
|
||||
const exclude = template?.services[containerObj.Names]?.exclude;
|
||||
if (exclude) {
|
||||
payload[containerObj.Names] = {
|
||||
status: {
|
||||
isExcluded: true,
|
||||
isRunning: false,
|
||||
isExited: false,
|
||||
isRestarting: false
|
||||
}
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
const status = containerObj.State;
|
||||
if (status === 'running') {
|
||||
isRunning = true;
|
||||
}
|
||||
if (status === 'exited') {
|
||||
isExited = true;
|
||||
}
|
||||
if (status === 'restarting') {
|
||||
isRestarting = true;
|
||||
}
|
||||
status: privateProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
||||
const id = input.id;
|
||||
const teamId = ctx.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw { status: 400, message: 'Team not found.' };
|
||||
}
|
||||
const service = await getServiceFromDB({ id, teamId });
|
||||
const { destinationDockerId } = service;
|
||||
let payload = {};
|
||||
if (destinationDockerId) {
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: service.destinationDocker.id,
|
||||
command: `docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'`
|
||||
});
|
||||
if (containers) {
|
||||
const containersArray = containers.trim().split('\n');
|
||||
if (containersArray.length > 0 && containersArray[0] !== '') {
|
||||
const templates = await getTemplates();
|
||||
let template = templates.find((t: { type: string }) => t.type === service.type);
|
||||
const templateStr = JSON.stringify(template);
|
||||
if (templateStr) {
|
||||
template = JSON.parse(templateStr.replaceAll('$$id', service.id));
|
||||
}
|
||||
for (const container of containersArray) {
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let isRestarting = false;
|
||||
let isExcluded = false;
|
||||
const containerObj = JSON.parse(container);
|
||||
const exclude = template?.services[containerObj.Names]?.exclude;
|
||||
if (exclude) {
|
||||
payload[containerObj.Names] = {
|
||||
status: {
|
||||
isExcluded,
|
||||
isRunning,
|
||||
isExited,
|
||||
isRestarting
|
||||
isExcluded: true,
|
||||
isRunning: false,
|
||||
isExited: false,
|
||||
isRestarting: false
|
||||
}
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
const status = containerObj.State;
|
||||
if (status === 'running') {
|
||||
isRunning = true;
|
||||
}
|
||||
if (status === 'exited') {
|
||||
isExited = true;
|
||||
}
|
||||
if (status === 'restarting') {
|
||||
isRestarting = true;
|
||||
}
|
||||
payload[containerObj.Names] = {
|
||||
status: {
|
||||
isExcluded,
|
||||
isRunning,
|
||||
isExited,
|
||||
isRestarting
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
return payload;
|
||||
}),
|
||||
cleanup: privateProcedure.query(async ({ ctx }) => {
|
||||
const teamId = ctx.user?.teamId;
|
||||
let services = await prisma.service.findMany({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, teams: true }
|
||||
});
|
||||
for (const service of services) {
|
||||
if (!service.fqdn) {
|
||||
if (service.destinationDockerId) {
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: service.destinationDockerId,
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}`
|
||||
});
|
||||
if (containers) {
|
||||
const containerArray = containers.split('\n');
|
||||
if (containerArray.length > 0) {
|
||||
for (const container of containerArray) {
|
||||
await executeCommand({
|
||||
dockerId: service.destinationDockerId,
|
||||
command: `docker stop -t 0 ${container}`
|
||||
});
|
||||
await executeCommand({
|
||||
dockerId: service.destinationDockerId,
|
||||
command: `docker rm --force ${container}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await removeService({ id: service.id });
|
||||
}
|
||||
}
|
||||
}),
|
||||
delete: privateProcedure
|
||||
.input(z.object({ force: z.boolean(), id: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
// todo: check if user is allowed to delete service
|
||||
const { id } = input;
|
||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.serviceSetting.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.fider.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.umami.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.hasura.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.glitchTip.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.moodle.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.searxng.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.weblate.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.taiga.deleteMany({ where: { serviceId: id } });
|
||||
|
||||
await prisma.service.delete({ where: { id } });
|
||||
return {};
|
||||
})
|
||||
});
|
||||
|
||||
|
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -160,6 +160,7 @@ importers:
|
||||
'@sveltejs/kit': 1.0.0-next.572
|
||||
'@trpc/client': 10.1.0
|
||||
'@trpc/server': 10.1.0
|
||||
'@types/js-cookie': 3.0.2
|
||||
'@typescript-eslint/eslint-plugin': 5.44.0
|
||||
'@typescript-eslint/parser': 5.44.0
|
||||
autoprefixer: 10.4.13
|
||||
@ -196,6 +197,7 @@ importers:
|
||||
'@playwright/test': 1.28.1
|
||||
'@sveltejs/adapter-static': 1.0.0-next.48
|
||||
'@sveltejs/kit': 1.0.0-next.572_svelte@3.53.1+vite@3.2.4
|
||||
'@types/js-cookie': 3.0.2
|
||||
'@typescript-eslint/eslint-plugin': 5.44.0_fnsv2sbzcckq65bwfk7a5xwslu
|
||||
'@typescript-eslint/parser': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a
|
||||
autoprefixer: 10.4.13_postcss@8.4.19
|
||||
@ -237,6 +239,7 @@ importers:
|
||||
'@prisma/client': 4.6.1
|
||||
'@trpc/client': 10.1.0
|
||||
'@trpc/server': 10.1.0
|
||||
'@types/bcryptjs': ^2.4.2
|
||||
'@types/jsonwebtoken': ^8.5.9
|
||||
'@types/node': 18.11.9
|
||||
'@types/node-fetch': 2.6.2
|
||||
@ -299,6 +302,7 @@ importers:
|
||||
ws: 8.11.0
|
||||
zod: 3.19.1
|
||||
devDependencies:
|
||||
'@types/bcryptjs': 2.4.2
|
||||
'@types/jsonwebtoken': 8.5.9
|
||||
'@types/node': 18.11.9
|
||||
'@types/node-fetch': 2.6.2
|
||||
@ -2026,6 +2030,10 @@ packages:
|
||||
resolution: {integrity: sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==}
|
||||
dev: false
|
||||
|
||||
/@types/bcryptjs/2.4.2:
|
||||
resolution: {integrity: sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==}
|
||||
dev: true
|
||||
|
||||
/@types/cacheable-request/6.0.2:
|
||||
resolution: {integrity: sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==}
|
||||
dependencies:
|
||||
|
Loading…
x
Reference in New Issue
Block a user