mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-16 20:49:28 +00:00
@@ -604,53 +604,54 @@ async function cleanupStorage() {
|
||||
if (!destination.remoteVerified) continue;
|
||||
enginesDone.add(destination.remoteIpAddress);
|
||||
}
|
||||
let lowDiskSpace = false;
|
||||
try {
|
||||
let stdout = null;
|
||||
if (!isDev) {
|
||||
const output = await executeCommand({
|
||||
dockerId: destination.id,
|
||||
command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
|
||||
shell: true
|
||||
});
|
||||
stdout = output.stdout;
|
||||
} else {
|
||||
const output = await executeCommand({
|
||||
command: `df -kPT /`
|
||||
});
|
||||
stdout = output.stdout;
|
||||
}
|
||||
let lines = stdout.trim().split('\n');
|
||||
let header = lines[0];
|
||||
let regex =
|
||||
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
||||
const boundaries = [];
|
||||
let match;
|
||||
await cleanupDockerStorage(destination.id);
|
||||
// let lowDiskSpace = false;
|
||||
// try {
|
||||
// let stdout = null;
|
||||
// if (!isDev) {
|
||||
// const output = await executeCommand({
|
||||
// dockerId: destination.id,
|
||||
// command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
|
||||
// shell: true
|
||||
// });
|
||||
// stdout = output.stdout;
|
||||
// } else {
|
||||
// const output = await executeCommand({
|
||||
// command: `df -kPT /`
|
||||
// });
|
||||
// stdout = output.stdout;
|
||||
// }
|
||||
// let lines = stdout.trim().split('\n');
|
||||
// let header = lines[0];
|
||||
// let regex =
|
||||
// /^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
||||
// const boundaries = [];
|
||||
// let match;
|
||||
|
||||
while ((match = regex.exec(header))) {
|
||||
boundaries.push(match[0].length);
|
||||
}
|
||||
// while ((match = regex.exec(header))) {
|
||||
// boundaries.push(match[0].length);
|
||||
// }
|
||||
|
||||
boundaries[boundaries.length - 1] = -1;
|
||||
const data = lines.slice(1).map((line) => {
|
||||
const cl = boundaries.map((boundary) => {
|
||||
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||
line = line.slice(boundary);
|
||||
return column.trim();
|
||||
});
|
||||
return {
|
||||
capacity: Number.parseInt(cl[5], 10) / 100
|
||||
};
|
||||
});
|
||||
if (data.length > 0) {
|
||||
const { capacity } = data[0];
|
||||
if (capacity > 0.8) {
|
||||
lowDiskSpace = true;
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
if (lowDiskSpace) {
|
||||
await cleanupDockerStorage(destination.id);
|
||||
}
|
||||
// boundaries[boundaries.length - 1] = -1;
|
||||
// const data = lines.slice(1).map((line) => {
|
||||
// const cl = boundaries.map((boundary) => {
|
||||
// const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||
// line = line.slice(boundary);
|
||||
// return column.trim();
|
||||
// });
|
||||
// return {
|
||||
// capacity: Number.parseInt(cl[5], 10) / 100
|
||||
// };
|
||||
// });
|
||||
// if (data.length > 0) {
|
||||
// const { capacity } = data[0];
|
||||
// if (capacity > 0.8) {
|
||||
// lowDiskSpace = true;
|
||||
// }
|
||||
// }
|
||||
// } catch (error) {}
|
||||
// if (lowDiskSpace) {
|
||||
// await cleanupDockerStorage(destination.id);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { saveBuildLog } from './buildPacks/common';
|
||||
import { scheduler } from './scheduler';
|
||||
import type { ExecaChildProcess } from 'execa';
|
||||
|
||||
export const version = '3.12.31';
|
||||
export const version = '3.12.32';
|
||||
export const isDev = process.env.NODE_ENV === 'development';
|
||||
export const proxyPort = process.env.COOLIFY_PROXY_PORT;
|
||||
export const proxySecurePort = process.env.COOLIFY_PROXY_SECURE_PORT;
|
||||
@@ -579,7 +579,8 @@ export async function executeCommand({
|
||||
stream = false,
|
||||
buildId,
|
||||
applicationId,
|
||||
debug
|
||||
debug,
|
||||
timeout = 0
|
||||
}: {
|
||||
command: string;
|
||||
sshCommand?: boolean;
|
||||
@@ -589,6 +590,7 @@ export async function executeCommand({
|
||||
buildId?: string;
|
||||
applicationId?: string;
|
||||
debug?: boolean;
|
||||
timeout?: number;
|
||||
}): Promise<ExecaChildProcess<string>> {
|
||||
const { execa, execaCommand } = await import('execa');
|
||||
const { parse } = await import('shell-quote');
|
||||
@@ -613,20 +615,26 @@ export async function executeCommand({
|
||||
}
|
||||
if (sshCommand) {
|
||||
if (shell) {
|
||||
return execaCommand(`ssh ${remoteIpAddress}-remote ${command}`);
|
||||
return execaCommand(`ssh ${remoteIpAddress}-remote ${command}`, {
|
||||
timeout
|
||||
});
|
||||
}
|
||||
return await execa('ssh', [`${remoteIpAddress}-remote`, dockerCommand, ...dockerArgs]);
|
||||
return await execa('ssh', [`${remoteIpAddress}-remote`, dockerCommand, ...dockerArgs], {
|
||||
timeout
|
||||
});
|
||||
}
|
||||
if (stream) {
|
||||
return await new Promise(async (resolve, reject) => {
|
||||
let subprocess = null;
|
||||
if (shell) {
|
||||
subprocess = execaCommand(command, {
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||
timeout
|
||||
});
|
||||
} else {
|
||||
subprocess = execa(dockerCommand, dockerArgs, {
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||
timeout
|
||||
});
|
||||
}
|
||||
const logs = [];
|
||||
@@ -680,19 +688,26 @@ export async function executeCommand({
|
||||
} else {
|
||||
if (shell) {
|
||||
return await execaCommand(command, {
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||
timeout
|
||||
});
|
||||
} else {
|
||||
return await execa(dockerCommand, dockerArgs, {
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||
timeout
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (shell) {
|
||||
return execaCommand(command, { shell: true });
|
||||
return execaCommand(command, {
|
||||
shell: true,
|
||||
timeout
|
||||
});
|
||||
}
|
||||
return await execa(dockerCommand, dockerArgs);
|
||||
return await execa(dockerCommand, dockerArgs, {
|
||||
timeout
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -849,97 +864,97 @@ export function generatePassword({
|
||||
|
||||
type DatabaseConfiguration =
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MYSQL_DATABASE: string;
|
||||
MYSQL_PASSWORD: string;
|
||||
MYSQL_ROOT_USER: string;
|
||||
MYSQL_USER: string;
|
||||
MYSQL_ROOT_PASSWORD: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MYSQL_DATABASE: string;
|
||||
MYSQL_PASSWORD: string;
|
||||
MYSQL_ROOT_USER: string;
|
||||
MYSQL_USER: string;
|
||||
MYSQL_ROOT_PASSWORD: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MONGO_INITDB_ROOT_USERNAME?: string;
|
||||
MONGO_INITDB_ROOT_PASSWORD?: string;
|
||||
MONGODB_ROOT_USER?: string;
|
||||
MONGODB_ROOT_PASSWORD?: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MONGO_INITDB_ROOT_USERNAME?: string;
|
||||
MONGO_INITDB_ROOT_PASSWORD?: string;
|
||||
MONGODB_ROOT_USER?: string;
|
||||
MONGODB_ROOT_PASSWORD?: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MARIADB_ROOT_USER: string;
|
||||
MARIADB_ROOT_PASSWORD: string;
|
||||
MARIADB_USER: string;
|
||||
MARIADB_PASSWORD: string;
|
||||
MARIADB_DATABASE: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MARIADB_ROOT_USER: string;
|
||||
MARIADB_ROOT_PASSWORD: string;
|
||||
MARIADB_USER: string;
|
||||
MARIADB_PASSWORD: string;
|
||||
MARIADB_DATABASE: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
POSTGRES_PASSWORD?: string;
|
||||
POSTGRES_USER?: string;
|
||||
POSTGRES_DB?: string;
|
||||
POSTGRESQL_POSTGRES_PASSWORD?: string;
|
||||
POSTGRESQL_USERNAME?: string;
|
||||
POSTGRESQL_PASSWORD?: string;
|
||||
POSTGRESQL_DATABASE?: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
POSTGRES_PASSWORD?: string;
|
||||
POSTGRES_USER?: string;
|
||||
POSTGRES_DB?: string;
|
||||
POSTGRESQL_POSTGRES_PASSWORD?: string;
|
||||
POSTGRESQL_USERNAME?: string;
|
||||
POSTGRESQL_PASSWORD?: string;
|
||||
POSTGRESQL_DATABASE?: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
REDIS_AOF_ENABLED: string;
|
||||
REDIS_PASSWORD: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
REDIS_AOF_ENABLED: string;
|
||||
REDIS_PASSWORD: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
COUCHDB_PASSWORD: string;
|
||||
COUCHDB_USER: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
COUCHDB_PASSWORD: string;
|
||||
COUCHDB_USER: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
EDGEDB_SERVER_PASSWORD: string;
|
||||
EDGEDB_SERVER_USER: string;
|
||||
EDGEDB_SERVER_DATABASE: string;
|
||||
EDGEDB_SERVER_TLS_CERT_MODE: string;
|
||||
};
|
||||
};
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
EDGEDB_SERVER_PASSWORD: string;
|
||||
EDGEDB_SERVER_USER: string;
|
||||
EDGEDB_SERVER_DATABASE: string;
|
||||
EDGEDB_SERVER_TLS_CERT_MODE: string;
|
||||
};
|
||||
};
|
||||
export function generateDatabaseConfiguration(database: any): DatabaseConfiguration {
|
||||
const { id, dbUser, dbUserPassword, rootUser, rootUserPassword, defaultDatabase, version, type } =
|
||||
database;
|
||||
@@ -1038,9 +1053,8 @@ export function generateDatabaseConfiguration(database: any): DatabaseConfigurat
|
||||
};
|
||||
if (isARM()) {
|
||||
configuration.volume = `${id}-${type}-data:/data`;
|
||||
configuration.command = `/usr/local/bin/redis-server --appendonly ${
|
||||
appendOnly ? 'yes' : 'no'
|
||||
} --requirepass ${dbUserPassword}`;
|
||||
configuration.command = `/usr/local/bin/redis-server --appendonly ${appendOnly ? 'yes' : 'no'
|
||||
} --requirepass ${dbUserPassword}`;
|
||||
}
|
||||
return configuration;
|
||||
} else if (type === 'couchdb') {
|
||||
@@ -1125,12 +1139,12 @@ export type ComposeFileService = {
|
||||
command?: string;
|
||||
ports?: string[];
|
||||
build?:
|
||||
| {
|
||||
context: string;
|
||||
dockerfile: string;
|
||||
args?: Record<string, unknown>;
|
||||
}
|
||||
| string;
|
||||
| {
|
||||
context: string;
|
||||
dockerfile: string;
|
||||
args?: Record<string, unknown>;
|
||||
}
|
||||
| string;
|
||||
deploy?: {
|
||||
restart_policy?: {
|
||||
condition?: string;
|
||||
@@ -1201,7 +1215,7 @@ export const createDirectories = async ({
|
||||
let workdirFound = false;
|
||||
try {
|
||||
workdirFound = !!(await fs.stat(workdir));
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
if (workdirFound) {
|
||||
await executeCommand({ command: `rm -fr ${workdir}` });
|
||||
}
|
||||
@@ -1728,7 +1742,7 @@ export async function stopBuild(buildId, applicationId) {
|
||||
}
|
||||
}
|
||||
count++;
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
@@ -1751,7 +1765,7 @@ export async function cleanupDockerStorage(dockerId) {
|
||||
// Cleanup images that are not used by any container
|
||||
try {
|
||||
await executeCommand({ dockerId, command: `docker image prune -af` });
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
|
||||
// Prune coolify managed containers
|
||||
try {
|
||||
@@ -1759,12 +1773,12 @@ export async function cleanupDockerStorage(dockerId) {
|
||||
dockerId,
|
||||
command: `docker container prune -f --filter "label=coolify.managed=true"`
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
|
||||
// Cleanup build caches
|
||||
try {
|
||||
await executeCommand({ dockerId, command: `docker builder prune -af` });
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
export function persistentVolumes(id, persistentStorage, config) {
|
||||
|
||||
@@ -640,8 +640,7 @@ export async function restartApplication(
|
||||
|
||||
const volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
||||
}${storage.path}`;
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||
}) || [];
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
|
||||
@@ -18,6 +18,7 @@ import type {
|
||||
Proxy,
|
||||
SaveDestinationSettings
|
||||
} from './types';
|
||||
import { removeService } from '../../../../lib/services/common';
|
||||
|
||||
export async function listDestinations(request: FastifyRequest<ListDestinations>) {
|
||||
try {
|
||||
@@ -143,6 +144,35 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function forceDeleteDestination(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const services = await prisma.service.findMany({ where: { destinationDockerId: id } });
|
||||
for (const service of services) {
|
||||
await removeService({ id: service.id });
|
||||
}
|
||||
const applications = await prisma.application.findMany({ where: { destinationDockerId: id } });
|
||||
for (const application of applications) {
|
||||
await prisma.applicationSettings.deleteMany({ where: { application: { id: 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.previewApplication.deleteMany({ where: { applicationId: application.id } });
|
||||
}
|
||||
const databases = await prisma.database.findMany({ where: { destinationDockerId: id } });
|
||||
for (const database of databases) {
|
||||
await prisma.databaseSettings.deleteMany({ where: { databaseId: database.id } });
|
||||
await prisma.databaseSecret.deleteMany({ where: { databaseId: database.id } });
|
||||
await prisma.database.delete({ where: { id: database.id } });
|
||||
}
|
||||
await prisma.destinationDocker.delete({ where: { id } });
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
@@ -318,6 +348,7 @@ export async function verifyRemoteDockerEngineFn(id: string) {
|
||||
}
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } });
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw new Error('Error while verifying remote docker engine');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { assignSSHKey, checkDestination, deleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
|
||||
import { assignSSHKey, checkDestination, deleteDestination, forceDeleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
|
||||
|
||||
import type { OnlyId } from '../../../../types';
|
||||
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
||||
@@ -14,6 +14,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
|
||||
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
|
||||
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
|
||||
fastify.delete<OnlyId>('/:id/force', async (request) => await forceDeleteDestination(request));
|
||||
fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request));
|
||||
|
||||
fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));
|
||||
|
||||
@@ -75,6 +75,25 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
async function forceDeleteDestination(destination: any) {
|
||||
let sure = confirm($t('application.confirm_to_delete', { name: destination.name }));
|
||||
if (sure) {
|
||||
sure = confirm(
|
||||
'Are you REALLY sure? This will delete all resources associated with this destination, but not on the destination (server) itself. You will have manually delete everything on the server afterwards.'
|
||||
);
|
||||
if (sure) {
|
||||
sure = confirm('REALLY?');
|
||||
if (sure) {
|
||||
try {
|
||||
await del(`/destinations/${destination.id}/force`, { id: destination.id });
|
||||
return await goto('/', { replaceState: true });
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function deletable() {
|
||||
if (!isDestinationDeletable) {
|
||||
return 'Please delete all resources before deleting this.';
|
||||
@@ -88,7 +107,7 @@
|
||||
</script>
|
||||
|
||||
{#if $page.params.id !== 'new'}
|
||||
<nav class="header lg:flex-row flex-col-reverse">
|
||||
<nav class="header lg:flex-row flex-col-reverse gap-2">
|
||||
<div class="flex flex-row space-x-2 font-bold pt-10 lg:pt-0">
|
||||
<div class="flex flex-col items-center justify-center title">
|
||||
{#if $page.url.pathname === `/destinations/${$page.params.id}`}
|
||||
@@ -111,6 +130,16 @@
|
||||
>
|
||||
<Tooltip triggeredBy="#delete">{deletable()}</Tooltip>
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap justify-center lg:justify-start lg:py-0 items-center">
|
||||
<button
|
||||
id="forceDelete"
|
||||
on:click={() => forceDeleteDestination(destination)}
|
||||
type="submit"
|
||||
disabled={!$appSession.isAdmin && isDestinationDeletable}
|
||||
class="icons bg-transparent text-sm text-red-500"><DeleteIcon /></button
|
||||
>
|
||||
<Tooltip triggeredBy="#forceDelete">Force Delete</Tooltip>
|
||||
</div>
|
||||
</nav>
|
||||
{/if}
|
||||
<slot />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "3.12.31",
|
||||
"version": "3.12.32",
|
||||
"license": "Apache-2.0",
|
||||
"repository": "github:coollabsio/coolify",
|
||||
"scripts": {
|
||||
@@ -50,4 +50,4 @@
|
||||
"open-source",
|
||||
"coolify"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user