commit
						ac5cc8b299
					
				| @ -472,15 +472,15 @@ export const saveBuildLog = async ({ | ||||
| 
 | ||||
| 	if (isDev) { | ||||
| 		console.debug(`[${applicationId}] ${addTimestamp}`); | ||||
| 		return | ||||
| 	}  | ||||
| 	} | ||||
| 	try { | ||||
| 		return await got.post(`${fluentBitUrl}/${applicationId}_buildlog_${buildId}.csv`, { | ||||
| 			json: { | ||||
| 				line: encrypt(line) | ||||
| 			} | ||||
| 		}) | ||||
| 	} catch(error) { | ||||
| 	} catch (error) { | ||||
| 		if (isDev) return | ||||
| 		return await prisma.buildLog.create({ | ||||
| 			data: { | ||||
| 				line: addTimestamp, buildId, time: Number(day().valueOf()), applicationId | ||||
|  | ||||
| @ -21,7 +21,7 @@ import { scheduler } from './scheduler'; | ||||
| import { supportedServiceTypesAndVersions } from './services/supportedVersions'; | ||||
| import { includeServices } from './services/common'; | ||||
| 
 | ||||
| export const version = '3.10.11'; | ||||
| export const version = '3.10.12'; | ||||
| export const isDev = process.env.NODE_ENV === 'development'; | ||||
| 
 | ||||
| const algorithm = 'aes-256-ctr'; | ||||
|  | ||||
| @ -1883,11 +1883,11 @@ async function stopServiceContainers(request: FastifyRequest<ServiceStartStop>) | ||||
|         if (destinationDockerId) { | ||||
|             await executeDockerCmd({ | ||||
|                 dockerId: destinationDockerId, | ||||
|                 command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}|xargs -n 1 docker stop -t 0` | ||||
|                 command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0` | ||||
|             }) | ||||
|             await executeDockerCmd({ | ||||
|                 dockerId: destinationDockerId, | ||||
|                 command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}|xargs -n 1 docker rm --force` | ||||
|                 command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}|xargs -r -n 1 docker rm --force` | ||||
|             }) | ||||
|             return {} | ||||
|         } | ||||
|  | ||||
| @ -69,6 +69,43 @@ export async function getImages(request: FastifyRequest<GetImages>) { | ||||
|         return errorHandler({ status, message }) | ||||
|     } | ||||
| } | ||||
| export async function cleanupUnconfiguredApplications(request: FastifyRequest<any>) { | ||||
|     try { | ||||
|         const teamId = request.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 executeDockerCmd({ | ||||
|                         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 {} | ||||
|     } catch ({ status, message }) { | ||||
|         return errorHandler({ status, message }) | ||||
|     } | ||||
| } | ||||
| export async function getApplicationStatus(request: FastifyRequest<OnlyId>) { | ||||
|     try { | ||||
|         const { id } = request.params | ||||
| @ -761,7 +798,10 @@ export async function saveBuildPack(request, reply) { | ||||
|     try { | ||||
|         const { id } = request.params | ||||
|         const { buildPack } = request.body | ||||
|         await prisma.application.update({ where: { id }, data: { buildPack } }); | ||||
|         const { baseImage, baseBuildImage } = setDefaultBaseImage( | ||||
|             buildPack | ||||
|         ); | ||||
|         await prisma.application.update({ where: { id }, data: { buildPack, baseImage, baseBuildImage } }); | ||||
|         return reply.code(201).send() | ||||
|     } catch ({ status, message }) { | ||||
|         return errorHandler({ status, message }) | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { FastifyPluginAsync } from 'fastify'; | ||||
| import { OnlyId } from '../../../../types'; | ||||
| import { cancelDeployment, checkDNS, checkDomain, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey,  saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers'; | ||||
| import { cancelDeployment, checkDNS, checkDomain, checkRepository, cleanupUnconfiguredApplications, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey,  saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers'; | ||||
| 
 | ||||
| import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuilds, GetImages, RestartPreviewApplication, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types'; | ||||
| 
 | ||||
| @ -11,6 +11,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => { | ||||
|     fastify.get('/', async (request) => await listApplications(request)); | ||||
|     fastify.post<GetImages>('/images', async (request) => await getImages(request)); | ||||
| 
 | ||||
|     fastify.post<any>('/cleanup/unconfigured', async (request) => await cleanupUnconfiguredApplications(request)); | ||||
| 
 | ||||
|     fastify.post('/new', async (request, reply) => await newApplication(request, reply)); | ||||
| 
 | ||||
|     fastify.get<OnlyId>('/:id', async (request) => await getApplication(request)); | ||||
|  | ||||
| @ -51,6 +51,30 @@ export async function newDatabase(request: FastifyRequest, reply: FastifyReply) | ||||
|         return errorHandler({ status, message }) | ||||
|     } | ||||
| } | ||||
| export async function cleanupUnconfiguredDatabases(request: FastifyRequest) { | ||||
|     try { | ||||
|         const teamId = request.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 {} | ||||
|     } catch ({ status, message }) { | ||||
|         return errorHandler({ status, message }) | ||||
|     } | ||||
| } | ||||
| export async function getDatabaseStatus(request: FastifyRequest<OnlyId>) { | ||||
|     try { | ||||
|         const { id } = request.params; | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { FastifyPluginAsync } from 'fastify'; | ||||
| import { deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers'; | ||||
| import { cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers'; | ||||
| 
 | ||||
| import type { OnlyId } from '../../../../types'; | ||||
| 
 | ||||
| @ -12,6 +12,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => { | ||||
|     fastify.get('/', async (request) => await listDatabases(request)); | ||||
|     fastify.post('/new', async (request, reply) => await newDatabase(request, reply)); | ||||
| 
 | ||||
|     fastify.post<any>('/cleanup/unconfigured', async (request) => await cleanupUnconfiguredDatabases(request)); | ||||
| 
 | ||||
|     fastify.get<OnlyId>('/:id', async (request) => await getDatabase(request)); | ||||
|     fastify.post<SaveDatabase>('/:id', async (request, reply) => await saveDatabase(request, reply)); | ||||
|     fastify.delete<DeleteDatabase>('/:id', async (request) => await deleteDatabase(request)); | ||||
|  | ||||
| @ -122,7 +122,7 @@ export async function showDashboard(request: FastifyRequest) { | ||||
| 	try { | ||||
| 		const userId = request.user.userId; | ||||
| 		const teamId = request.user.teamId; | ||||
| 		const applications = await prisma.application.findMany({ | ||||
| 		let applications = await prisma.application.findMany({ | ||||
| 			where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, | ||||
| 			include: { settings: true, destinationDocker: true, teams: true }, | ||||
| 		}); | ||||
| @ -143,7 +143,29 @@ export async function showDashboard(request: FastifyRequest) { | ||||
| 			include: { teams: true }, | ||||
| 		}); | ||||
| 		const settings = await listSettings(); | ||||
| 
 | ||||
| 		let foundUnconfiguredApplication = false; | ||||
| 		for (const application of applications) { | ||||
| 			if (!application.buildPack || !application.destinationDockerId || !application.branch || (!application.settings?.isBot && !application?.fqdn)) { | ||||
| 				foundUnconfiguredApplication = true | ||||
| 			} | ||||
| 		} | ||||
| 		let foundUnconfiguredService = false; | ||||
| 		for (const service of services) { | ||||
| 			if (!service.fqdn) { | ||||
| 				foundUnconfiguredService = true | ||||
| 			} | ||||
| 		} | ||||
| 		let foundUnconfiguredDatabase = false; | ||||
| 		for (const database of databases) { | ||||
| 			if (!database.version) { | ||||
| 				foundUnconfiguredDatabase = true | ||||
| 			} | ||||
| 		} | ||||
| 		return { | ||||
| 			foundUnconfiguredApplication, | ||||
| 			foundUnconfiguredDatabase, | ||||
| 			foundUnconfiguredService, | ||||
| 			applications, | ||||
| 			databases, | ||||
| 			services, | ||||
|  | ||||
| @ -36,6 +36,33 @@ export async function newService(request: FastifyRequest, reply: FastifyReply) { | ||||
|         return errorHandler({ status, message }) | ||||
|     } | ||||
| } | ||||
| export async function cleanupUnconfiguredServices(request: FastifyRequest) { | ||||
|     try { | ||||
|         const teamId = request.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) { | ||||
|                     await executeDockerCmd({ | ||||
|                         dockerId: service.destinationDockerId, | ||||
|                         command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0` | ||||
|                     }) | ||||
|                     await executeDockerCmd({ | ||||
|                         dockerId: service.destinationDockerId, | ||||
|                         command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}|xargs -r -n 1 docker rm --force` | ||||
|                     }) | ||||
|                 } | ||||
|                 await removeService({ id: service.id }); | ||||
|             } | ||||
|         } | ||||
|         return {} | ||||
|     } catch ({ status, message }) { | ||||
|         return errorHandler({ status, message }) | ||||
|     } | ||||
| } | ||||
| export async function getServiceStatus(request: FastifyRequest<OnlyId>) { | ||||
|     try { | ||||
|         const teamId = request.user.teamId; | ||||
|  | ||||
| @ -5,6 +5,7 @@ import { | ||||
|     checkService, | ||||
|     checkServiceDomain, | ||||
|     cleanupPlausibleLogs, | ||||
|     cleanupUnconfiguredServices, | ||||
|     deleteService, | ||||
|     deleteServiceSecret, | ||||
|     deleteServiceStorage, | ||||
| @ -39,6 +40,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => { | ||||
|     fastify.get('/', async (request) => await listServices(request)); | ||||
|     fastify.post('/new', async (request, reply) => await newService(request, reply)); | ||||
| 
 | ||||
|     fastify.post<any>('/cleanup/unconfigured', async (request) => await cleanupUnconfiguredServices(request)); | ||||
| 
 | ||||
|     fastify.get<OnlyId>('/:id', async (request) => await getService(request)); | ||||
|     fastify.post<SaveService>('/:id', async (request, reply) => await saveService(request, reply)); | ||||
|     fastify.delete<OnlyId>('/:id', async (request) => await deleteService(request)); | ||||
|  | ||||
| @ -19,7 +19,7 @@ | ||||
| <div class="dropdown dropdown-bottom"> | ||||
| 	<slot> | ||||
| 		<label for="new" tabindex="0" class="btn btn-sm text-sm bg-coollabs hover:bg-coollabs-100"> | ||||
| 			Create New Resource <svg | ||||
| 			<svg | ||||
| 				class="h-6 w-6" | ||||
| 				xmlns="http://www.w3.org/2000/svg" | ||||
| 				fill="none" | ||||
| @ -31,8 +31,8 @@ | ||||
| 					stroke-width="2" | ||||
| 					d="M12 6v6m0 0v6m0-6h6m-6 0H6" | ||||
| 				/></svg | ||||
| 			></label | ||||
| 		> | ||||
| 			> Create New Resource | ||||
| 		</label> | ||||
| 	</slot> | ||||
| 
 | ||||
| 	<ul id="new" tabindex="0" class="dropdown-content menu p-2 shadow bg-coolgray-300 rounded w-52"> | ||||
|  | ||||
| @ -91,12 +91,17 @@ | ||||
| 					forceDelete = true; | ||||
| 				} | ||||
| 				return errorNotification(error); | ||||
| 			}  | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	async function handleDeploySubmit(forceRebuild = false) { | ||||
| 		if (!$isDeploymentEnabled) return; | ||||
| 		if (!statusInterval) { | ||||
| 			statusInterval = setInterval(async () => { | ||||
| 				await getStatus(); | ||||
| 			}, 2000); | ||||
| 		} | ||||
| 		try { | ||||
| 			const { buildId } = await post(`/applications/${id}/deploy`, { | ||||
| 				...application, | ||||
| @ -212,30 +217,29 @@ | ||||
| 			{/if} | ||||
| 		</div> | ||||
| 		{#if $page.url.pathname.startsWith(`/applications/${id}/configuration/`)} | ||||
| 		<div class="px-2"> | ||||
| 			{#if forceDelete} | ||||
| 				<button | ||||
| 					on:click={() => deleteApplication(application.name, true)} | ||||
| 					disabled={!$appSession.isAdmin} | ||||
| 					class:bg-red-600={$appSession.isAdmin} | ||||
| 					class:hover:bg-red-500={$appSession.isAdmin} | ||||
| 					class="btn btn-sm btn-error text-sm" | ||||
| 				> | ||||
| 					Force Delete Application | ||||
| 				</button> | ||||
| 			{:else} | ||||
| 				<button | ||||
| 					on:click={() => deleteApplication(application.name, false)} | ||||
| 					disabled={!$appSession.isAdmin} | ||||
| 					class:bg-red-600={$appSession.isAdmin} | ||||
| 					class:hover:bg-red-500={$appSession.isAdmin} | ||||
| 					class="btn btn-sm btn-error text-sm" | ||||
| 				> | ||||
| 				 Delete Application | ||||
| 				</button> | ||||
| 			{/if} | ||||
| 		</div> | ||||
| 
 | ||||
| 			<div class="px-2"> | ||||
| 				{#if forceDelete} | ||||
| 					<button | ||||
| 						on:click={() => deleteApplication(application.name, true)} | ||||
| 						disabled={!$appSession.isAdmin} | ||||
| 						class:bg-red-600={$appSession.isAdmin} | ||||
| 						class:hover:bg-red-500={$appSession.isAdmin} | ||||
| 						class="btn btn-sm btn-error text-sm" | ||||
| 					> | ||||
| 						Force Delete Application | ||||
| 					</button> | ||||
| 				{:else} | ||||
| 					<button | ||||
| 						on:click={() => deleteApplication(application.name, false)} | ||||
| 						disabled={!$appSession.isAdmin} | ||||
| 						class:bg-red-600={$appSession.isAdmin} | ||||
| 						class:hover:bg-red-500={$appSession.isAdmin} | ||||
| 						class="btn btn-sm btn-error text-sm" | ||||
| 					> | ||||
| 						Delete Application | ||||
| 					</button> | ||||
| 				{/if} | ||||
| 			</div> | ||||
| 		{/if} | ||||
| 	</nav> | ||||
| 	<div | ||||
|  | ||||
| @ -153,7 +153,7 @@ | ||||
| {:else} | ||||
| 	<form on:submit|preventDefault={handleSubmit} class="px-10"> | ||||
| 		<div class="flex lg:flex-row flex-col lg:space-y-0 space-y-2 space-x-0 lg:space-x-2 items-center lg:justify-center"> | ||||
| 				<div class="custom-select-wrapper"><label for="repository" class="pb-1">Repository</label> | ||||
| 				<div class="custom-select-wrapper w-full"><label for="repository" class="pb-1">Repository</label> | ||||
| 					<Select | ||||
| 						placeholder={loading.repositories | ||||
| 							? $t('application.configuration.loading_repositories') | ||||
| @ -168,7 +168,7 @@ | ||||
| 					/> | ||||
| 				</div> | ||||
| 				<input class="hidden" bind:value={selected.projectId} name="projectId" /> | ||||
| 				<div class="custom-select-wrapper"><label for="repository" class="pb-1">Branch</label> | ||||
| 				<div class="custom-select-wrapper w-full"><label for="repository" class="pb-1">Branch</label> | ||||
| 					<Select | ||||
| 						placeholder={loading.branches | ||||
| 							? $t('application.configuration.loading_branches') | ||||
|  | ||||
| @ -328,8 +328,10 @@ | ||||
| </script> | ||||
| 
 | ||||
| <form on:submit={handleSubmit}> | ||||
| 	<div class="flex lg:flex-row flex-col lg:space-y-0 space-y-2 space-x-0 lg:space-x-2 items-center lg:justify-center"> | ||||
| 		<div class="custom-select-wrapper"> | ||||
| 	<div | ||||
| 		class="flex lg:flex-row flex-col lg:space-y-0 space-y-2 space-x-0 lg:space-x-2 items-center lg:justify-center lg:px-0 px-8" | ||||
| 	> | ||||
| 		<div class="custom-select-wrapper w-full"> | ||||
| 			<label for="groups" class="pb-1">Groups</label> | ||||
| 			<Select | ||||
| 				placeholder={loading.base | ||||
| @ -355,7 +357,7 @@ | ||||
| 				optionIdentifier="id" | ||||
| 			/> | ||||
| 		</div> | ||||
| 		<div class="custom-select-wrapper"> | ||||
| 		<div class="custom-select-wrapper w-full"> | ||||
| 			<label for="projects" class="pb-1">Projects</label> | ||||
| 			<Select | ||||
| 				placeholder={loading.projects | ||||
| @ -381,7 +383,7 @@ | ||||
| 				isSearchable={true} | ||||
| 			/> | ||||
| 		</div> | ||||
| 		<div class="custom-select-wrapper"> | ||||
| 		<div class="custom-select-wrapper w-full"> | ||||
| 			<label for="branches" class="pb-1">Branches</label> | ||||
| 			<Select | ||||
| 				placeholder={loading.branches | ||||
|  | ||||
| @ -172,7 +172,6 @@ | ||||
| 			 | ||||
| 			<div class="custom-select-wrapper"> | ||||
| 				<Select | ||||
| 					class="w-full" | ||||
| 					placeholder={loading.branches | ||||
| 						? $t('application.configuration.loading_branches') | ||||
| 						: branchSelectOptions.length ===0 | ||||
|  | ||||
| @ -26,8 +26,6 @@ | ||||
| </script> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| 	import { t } from '$lib/translations'; | ||||
| 
 | ||||
| 	export let application: any; | ||||
| 	export let appId: string; | ||||
| 	export let settings: any; | ||||
|  | ||||
| @ -25,7 +25,7 @@ | ||||
| </script> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| 	import { page, session } from '$app/stores'; | ||||
| 	import { page } from '$app/stores'; | ||||
| 	import { goto } from '$app/navigation'; | ||||
| 	import { get, post } from '$lib/api'; | ||||
| 	import { t } from '$lib/translations'; | ||||
|  | ||||
| @ -18,6 +18,7 @@ | ||||
| 	let fromDb = false; | ||||
| 	let cancelInprogress = false; | ||||
| 	let position = 0; | ||||
| 	let loading = true; | ||||
| 	const { id } = $page.params; | ||||
| 
 | ||||
| 	const cleanAnsiCodes = (str: string) => str.replace(/\x1B\[(\d+)m/g, ''); | ||||
| @ -46,6 +47,7 @@ | ||||
| 	} | ||||
| 	async function streamLogs(sequence = 0) { | ||||
| 		try { | ||||
| 			loading = true; | ||||
| 			let { | ||||
| 				logs: responseLogs, | ||||
| 				status, | ||||
| @ -60,6 +62,7 @@ | ||||
| 
 | ||||
| 			streamInterval = setInterval(async () => { | ||||
| 				if (status !== 'running' && status !== 'queued') { | ||||
| 					loading = false; | ||||
| 					clearInterval(streamInterval); | ||||
| 					return; | ||||
| 				} | ||||
| @ -75,6 +78,7 @@ | ||||
| 					logs = logs.concat( | ||||
| 						data.logs.map((log: any) => ({ ...log, line: cleanAnsiCodes(log.line) })) | ||||
| 					); | ||||
| 					loading = false; | ||||
| 				} catch (error) { | ||||
| 					return errorNotification(error); | ||||
| 				} | ||||
| @ -171,13 +175,13 @@ | ||||
| 	<div | ||||
| 		bind:this={logsEl} | ||||
| 		on:scroll={detect} | ||||
| 		class="font-mono w-full bg-coolgray-100 border border-coolgray-200 p-5 overflow-x-auto overflox-y-auto max-h-[80vh] rounded mb-20 flex flex-col scrollbar-thumb-coollabs scrollbar-track-coolgray-200 scrollbar-w-1" | ||||
| 		class="font-mono w-full bg-coolgray-100 border border-coolgray-200 p-5 overflow-x-auto overflox-y-auto max-h-[80vh] rounded mb-20 flex flex-col scrollbar-thumb-coollabs scrollbar-track-coolgray-200 scrollbar-w-1 whitespace-pre" | ||||
| 	> | ||||
| 		{#each logs as log} | ||||
| 			{#if fromDb} | ||||
| 				<div>{log.line + '\n'}</div> | ||||
| 				{log.line + '\n'} | ||||
| 			{:else} | ||||
| 				<div>[{day.unix(log.time).format('HH:mm:ss.SSS')}] {log.line + '\n'}</div> | ||||
| 				[{day.unix(log.time).format('HH:mm:ss.SSS')}] {log.line + '\n'} | ||||
| 			{/if} | ||||
| 		{/each} | ||||
| 	</div> | ||||
| @ -185,6 +189,10 @@ | ||||
| 	<div | ||||
| 		class="font-mono w-full bg-coolgray-200 p-5 overflow-x-auto overflox-y-auto max-h-[80vh] rounded mb-20 flex flex-col whitespace-nowrap scrollbar-thumb-coollabs scrollbar-track-coolgray-200 scrollbar-w-1" | ||||
| 	> | ||||
| 		{dev ? 'In development, logs are  shown in the console.' : 'No logs found yet.'} | ||||
| 		{loading | ||||
| 			? 'Loading logs...' | ||||
| 			: dev | ||||
| 			? 'In development, logs are shown in the console.' | ||||
| 			: 'No logs found yet.'} | ||||
| 	</div> | ||||
| {/if} | ||||
|  | ||||
| @ -55,7 +55,6 @@ | ||||
| 	export let database: any; | ||||
| 	import { del, get, post } from '$lib/api'; | ||||
| 	import { t } from '$lib/translations'; | ||||
| 	import { goto } from '$app/navigation'; | ||||
| 	import { page } from '$app/stores'; | ||||
| 	import { errorNotification, handlerNotFoundLoad } from '$lib/common'; | ||||
| 	import { appSession, status, isDeploymentEnabled } from '$lib/store'; | ||||
|  | ||||
| @ -21,6 +21,9 @@ | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| 	export let applications: any; | ||||
| 	export let foundUnconfiguredApplication: boolean; | ||||
| 	export let foundUnconfiguredService: boolean; | ||||
| 	export let foundUnconfiguredDatabase: boolean; | ||||
| 	export let databases: any; | ||||
| 	export let services: any; | ||||
| 	export let settings: any; | ||||
| @ -28,9 +31,9 @@ | ||||
| 	export let destinations: any; | ||||
| 
 | ||||
| 	let filtered: any = setInitials(); | ||||
| 	import { get } from '$lib/api'; | ||||
| 	import { get, post } from '$lib/api'; | ||||
| 	import { t } from '$lib/translations'; | ||||
| 	import { asyncSleep, getRndInteger } from '$lib/common'; | ||||
| 	import { asyncSleep, errorNotification, getRndInteger } from '$lib/common'; | ||||
| 	import { appSession, search } from '$lib/store'; | ||||
| 
 | ||||
| 	import ApplicationsIcons from '$lib/components/svg/applications/ApplicationIcons.svelte'; | ||||
| @ -54,31 +57,28 @@ | ||||
| 	doSearch(); | ||||
| 
 | ||||
| 	async function refreshStatusApplications() { | ||||
| 		loading.applications = true; | ||||
| 		noInitialStatus.applications = false; | ||||
| 		numberOfGetStatus = 0; | ||||
| 		for (const application of applications) { | ||||
| 			await getStatus(application, true); | ||||
| 			status[application.id] = 'loading'; | ||||
| 			getStatus(application, true); | ||||
| 		} | ||||
| 		loading.applications = false; | ||||
| 	} | ||||
| 	async function refreshStatusServices() { | ||||
| 		loading.services = true; | ||||
| 		noInitialStatus.services = false; | ||||
| 		numberOfGetStatus = 0; | ||||
| 		for (const service of services) { | ||||
| 			await getStatus(service, true); | ||||
| 			status[service.id] = 'loading'; | ||||
| 			getStatus(service, true); | ||||
| 		} | ||||
| 		loading.services = false; | ||||
| 	} | ||||
| 	async function refreshStatusDatabases() { | ||||
| 		loading.databases = true; | ||||
| 		noInitialStatus.databases = false; | ||||
| 		numberOfGetStatus = 0; | ||||
| 		for (const database of databases) { | ||||
| 			await getStatus(database, true); | ||||
| 			status[database.id] = 'loading'; | ||||
| 			getStatus(database, true); | ||||
| 		} | ||||
| 		loading.databases = false; | ||||
| 	} | ||||
| 	function setInitials(onlyOthers: boolean = false) { | ||||
| 		return { | ||||
| @ -325,6 +325,45 @@ | ||||
| 			filtered = setInitials(); | ||||
| 		} | ||||
| 	} | ||||
| 	async function cleanupApplications() { | ||||
| 		try { | ||||
| 			const sure = confirm( | ||||
| 				'Are you sure? This will delete all UNCONFIGURED applications and their data.' | ||||
| 			); | ||||
| 			if (sure) { | ||||
| 				await post(`/applications/cleanup/unconfigured`, {}); | ||||
| 				return window.location.reload(); | ||||
| 			} | ||||
| 		} catch (error) { | ||||
| 			return errorNotification(error); | ||||
| 		} | ||||
| 	} | ||||
| 	async function cleanupServices() { | ||||
| 		try { | ||||
| 			const sure = confirm( | ||||
| 				'Are you sure? This will delete all UNCONFIGURED services and their data.' | ||||
| 			); | ||||
| 			if (sure) { | ||||
| 				await post(`/services/cleanup/unconfigured`, {}); | ||||
| 				return window.location.reload(); | ||||
| 			} | ||||
| 		} catch (error) { | ||||
| 			return errorNotification(error); | ||||
| 		} | ||||
| 	} | ||||
| 	async function cleanupDatabases() { | ||||
| 		try { | ||||
| 			const sure = confirm( | ||||
| 				'Are you sure? This will delete all UNCONFIGURED databases and their data.' | ||||
| 			); | ||||
| 			if (sure) { | ||||
| 				await post(`/databases/cleanup/unconfigured`, {}); | ||||
| 				return window.location.reload(); | ||||
| 			} | ||||
| 		} catch (error) { | ||||
| 			return errorNotification(error); | ||||
| 		} | ||||
| 	} | ||||
| </script> | ||||
| 
 | ||||
| <nav class="header"> | ||||
| @ -334,7 +373,7 @@ | ||||
| 	{/if} | ||||
| </nav> | ||||
| <div class="container lg:mx-auto lg:p-0 px-8 pt-5"> | ||||
| 	<div class="space-x-2 lg:flex lg:justify-center  text-center mb-4 "> | ||||
| 	<div class="space-x-2 lg:flex lg:justify-center text-center mb-4 "> | ||||
| 		<button | ||||
| 			class="btn btn-sm btn-ghost" | ||||
| 			class:bg-applications={$search === '!app'} | ||||
| @ -521,15 +560,19 @@ | ||||
| 		</div> | ||||
| 	{/if} | ||||
| 	{#if (filtered.applications.length > 0 && applications.length > 0) || filtered.otherApplications.length > 0} | ||||
| 		<div class="flex items-center mt-10"> | ||||
| 			<h1 class="title lg:text-3xl pr-4">Applications</h1> | ||||
| 			<button | ||||
| 				class="btn btn-sm btn-primary" | ||||
| 				class:loading={loading.applications} | ||||
| 				disabled={loading.applications} | ||||
| 				on:click={refreshStatusApplications} | ||||
| 		<div class="flex items-center mt-10 space-x-2"> | ||||
| 			<h1 class="title lg:text-3xl">Applications</h1> | ||||
| 			<button class="btn btn-sm btn-primary" on:click={refreshStatusApplications} | ||||
| 				>{noInitialStatus.applications ? 'Load Status' : 'Refresh Status'}</button | ||||
| 			> | ||||
| 			{#if foundUnconfiguredApplication} | ||||
| 				<button | ||||
| 					class="btn btn-sm" | ||||
| 					class:loading={loading.applications} | ||||
| 					disabled={loading.applications} | ||||
| 					on:click={cleanupApplications}>Cleanup Unconfigured Resources</button | ||||
| 				> | ||||
| 			{/if} | ||||
| 		</div> | ||||
| 	{/if} | ||||
| 	{#if filtered.applications.length > 0 && applications.length > 0} | ||||
| @ -547,7 +590,7 @@ | ||||
| 								<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 							{:then} | ||||
| 								{#if !noInitialStatus.applications} | ||||
| 									{#if loading.applications} | ||||
| 									{#if status[application.id] === 'loading'} | ||||
| 										<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 									{:else if status[application.id] === 'running'} | ||||
| 										<span class="indicator-item badge bg-success badge-sm" /> | ||||
| @ -559,7 +602,7 @@ | ||||
| 							<div class="w-full flex flex-row"> | ||||
| 								<ApplicationsIcons {application} isAbsolute={true} /> | ||||
| 								<div class="w-full flex flex-col"> | ||||
| 									<h1 class="font-bold text-lg lg:text-xl truncate"> | ||||
| 									<h1 class="font-bold text-base truncate"> | ||||
| 										{application.name} | ||||
| 										{#if application.settings?.isBot} | ||||
| 											<span class="text-xs badge bg-coolblack border-none text-applications" | ||||
| @ -654,7 +697,7 @@ | ||||
| 							<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 						{:then} | ||||
| 							{#if !noInitialStatus.applications} | ||||
| 								{#if loading.applications} | ||||
| 								{#if status[application.id] === 'loading'} | ||||
| 									<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 								{:else if status[application.id] === 'running'} | ||||
| 									<span class="indicator-item badge bg-success badge-sm" /> | ||||
| @ -666,7 +709,7 @@ | ||||
| 						<div class="w-full flex flex-row"> | ||||
| 							<ApplicationsIcons {application} isAbsolute={true} /> | ||||
| 							<div class="w-full flex flex-col"> | ||||
| 								<h1 class="font-bold text-lg lg:text-xl truncate"> | ||||
| 								<h1 class="font-bold text-base truncate"> | ||||
| 									{application.name} | ||||
| 									{#if application.settings?.isBot} | ||||
| 										<span class="text-xs badge bg-coolblack border-none text-applications">BOT</span | ||||
| @ -740,15 +783,19 @@ | ||||
| 		</div> | ||||
| 	{/if} | ||||
| 	{#if (filtered.services.length > 0 && services.length > 0) || filtered.otherServices.length > 0} | ||||
| 		<div class="flex items-center mt-10"> | ||||
| 			<h1 class="title lg:text-3xl pr-4">Services</h1> | ||||
| 			<button | ||||
| 				class="btn btn-sm btn-primary" | ||||
| 				class:loading={loading.services} | ||||
| 				disabled={loading.services} | ||||
| 				on:click={refreshStatusServices} | ||||
| 		<div class="flex items-center mt-10 space-x-2"> | ||||
| 			<h1 class="title lg:text-3xl">Services</h1> | ||||
| 			<button class="btn btn-sm btn-primary" on:click={refreshStatusServices} | ||||
| 				>{noInitialStatus.services ? 'Load Status' : 'Refresh Status'}</button | ||||
| 			> | ||||
| 			{#if foundUnconfiguredService} | ||||
| 				<button | ||||
| 					class="btn btn-sm" | ||||
| 					class:loading={loading.services} | ||||
| 					disabled={loading.services} | ||||
| 					on:click={cleanupServices}>Cleanup Unconfigured Resources</button | ||||
| 				> | ||||
| 			{/if} | ||||
| 		</div> | ||||
| 	{/if} | ||||
| 	{#if filtered.services.length > 0 && services.length > 0} | ||||
| @ -766,7 +813,7 @@ | ||||
| 								<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 							{:then} | ||||
| 								{#if !noInitialStatus.services} | ||||
| 									{#if loading.services} | ||||
| 									{#if status[service.id] === 'loading'} | ||||
| 										<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 									{:else if status[service.id] === 'running'} | ||||
| 										<span class="indicator-item badge bg-success badge-sm" /> | ||||
| @ -778,7 +825,7 @@ | ||||
| 							<div class="w-full flex flex-row"> | ||||
| 								<ServiceIcons type={service.type} isAbsolute={true} /> | ||||
| 								<div class="w-full flex flex-col"> | ||||
| 									<h1 class="font-bold text-lg lg:text-xl truncate">{service.name}</h1> | ||||
| 									<h1 class="font-bold text-base truncate">{service.name}</h1> | ||||
| 									<div class="h-10 text-xs"> | ||||
| 										{#if service?.fqdn} | ||||
| 											<h2>{service?.fqdn.replace('https://', '').replace('http://', '')}</h2> | ||||
| @ -839,7 +886,7 @@ | ||||
| 							<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 						{:then} | ||||
| 							{#if !noInitialStatus.services} | ||||
| 								{#if loading.services} | ||||
| 								{#if status[service.id] === 'loading'} | ||||
| 									<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 								{:else if status[service.id] === 'running'} | ||||
| 									<span class="indicator-item badge bg-success badge-sm" /> | ||||
| @ -851,7 +898,7 @@ | ||||
| 						<div class="w-full flex flex-row"> | ||||
| 							<ServiceIcons type={service.type} isAbsolute={true} /> | ||||
| 							<div class="w-full flex flex-col"> | ||||
| 								<h1 class="font-bold text-lg lg:text-xl truncate">{service.name}</h1> | ||||
| 								<h1 class="font-bold text-base truncate">{service.name}</h1> | ||||
| 								<div class="h-10 text-xs"> | ||||
| 									{#if service?.fqdn} | ||||
| 										<h2>{service?.fqdn.replace('https://', '').replace('http://', '')}</h2> | ||||
| @ -894,15 +941,19 @@ | ||||
| 		</div> | ||||
| 	{/if} | ||||
| 	{#if (filtered.databases.length > 0 && databases.length > 0) || filtered.otherDatabases.length > 0} | ||||
| 		<div class="flex items-center mt-10"> | ||||
| 			<h1 class="title lg:text-3xl pr-4">Databases</h1> | ||||
| 			<button | ||||
| 				class="btn btn-sm btn-primary" | ||||
| 				on:click={refreshStatusDatabases} | ||||
| 				class:loading={loading.databases} | ||||
| 				disabled={loading.databases} | ||||
| 		<div class="flex items-center mt-10 space-x-2"> | ||||
| 			<h1 class="title lg:text-3xl">Databases</h1> | ||||
| 			<button class="btn btn-sm btn-primary" on:click={refreshStatusDatabases} | ||||
| 				>{noInitialStatus.databases ? 'Load Status' : 'Refresh Status'}</button | ||||
| 			> | ||||
| 			{#if foundUnconfiguredDatabase} | ||||
| 				<button | ||||
| 					class="btn btn-sm" | ||||
| 					class:loading={loading.databases} | ||||
| 					disabled={loading.databases} | ||||
| 					on:click={cleanupDatabases}>Cleanup Unconfigured Resources</button | ||||
| 				> | ||||
| 			{/if} | ||||
| 		</div> | ||||
| 	{/if} | ||||
| 	{#if filtered.databases.length > 0 && databases.length > 0} | ||||
| @ -920,9 +971,9 @@ | ||||
| 								<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 							{:then} | ||||
| 								{#if !noInitialStatus.databases} | ||||
| 									{#if loading.databases} | ||||
| 									{#if status[database.id] === 'loading'} | ||||
| 										<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 									{:else if status[databases.id] === 'running'} | ||||
| 									{:else if status[database.id] === 'running'} | ||||
| 										<span class="indicator-item badge bg-success badge-sm" /> | ||||
| 									{:else} | ||||
| 										<span class="indicator-item badge bg-error badge-sm" /> | ||||
| @ -933,7 +984,7 @@ | ||||
| 								<DatabaseIcons type={database.type} isAbsolute={true} /> | ||||
| 								<div class="w-full flex flex-col"> | ||||
| 									<div class="h-10"> | ||||
| 										<h1 class="font-bold text-lg lg:text-xl truncate">{database.name}</h1> | ||||
| 										<h1 class="font-bold text-base truncate">{database.name}</h1> | ||||
| 										<div class="h-10 text-xs"> | ||||
| 											{#if database?.version} | ||||
| 												<h2 class="">{database?.version}</h2> | ||||
| @ -997,9 +1048,9 @@ | ||||
| 							<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 						{:then} | ||||
| 							{#if !noInitialStatus.databases} | ||||
| 								{#if loading.databases} | ||||
| 								{#if status[database.id] === 'loading'} | ||||
| 									<span class="indicator-item badge bg-yellow-300 badge-sm" /> | ||||
| 								{:else if status[databases.id] === 'running'} | ||||
| 								{:else if status[database.id] === 'running'} | ||||
| 									<span class="indicator-item badge bg-success badge-sm" /> | ||||
| 								{:else} | ||||
| 									<span class="indicator-item badge bg-error badge-sm" /> | ||||
| @ -1010,7 +1061,7 @@ | ||||
| 							<DatabaseIcons type={database.type} isAbsolute={true} /> | ||||
| 							<div class="w-full flex flex-col"> | ||||
| 								<div class="h-10"> | ||||
| 									<h1 class="font-bold text-lg lg:text-xl truncate">{database.name}</h1> | ||||
| 									<h1 class="font-bold text-base truncate">{database.name}</h1> | ||||
| 									<div class="h-10 text-xs"> | ||||
| 										{#if database?.version} | ||||
| 											<h2 class="">{database?.version}</h2> | ||||
| @ -1129,7 +1180,7 @@ | ||||
| 								</div> | ||||
| 								<div class="w-full flex flex-col"> | ||||
| 									<div class="h-10"> | ||||
| 										<h1 class="font-bold text-lg lg:text-xl truncate">{source.name}</h1> | ||||
| 										<h1 class="font-bold text-base truncate">{source.name}</h1> | ||||
| 										{#if source.teams.length > 0 && source.teams[0]?.name} | ||||
| 											<div class="truncate text-xs">{source.teams[0]?.name}</div> | ||||
| 										{/if} | ||||
| @ -1218,7 +1269,7 @@ | ||||
| 							</div> | ||||
| 							<div class="w-full flex flex-col"> | ||||
| 								<div class="h-10"> | ||||
| 									<h1 class="font-bold text-lg lg:text-xl truncate">{source.name}</h1> | ||||
| 									<h1 class="font-bold text-base truncate">{source.name}</h1> | ||||
| 									{#if source.teams.length > 0 && source.teams[0]?.name} | ||||
| 										<div class="truncate text-xs">{source.teams[0]?.name}</div> | ||||
| 									{/if} | ||||
| @ -1292,7 +1343,7 @@ | ||||
| 									{/if} | ||||
| 								</div> | ||||
| 								<div class="w-full flex flex-col"> | ||||
| 									<h1 class="font-bold text-lg lg:text-xl truncate">{destination.name}</h1> | ||||
| 									<h1 class="font-bold text-base truncate">{destination.name}</h1> | ||||
| 									<div class="h-10 text-xs"> | ||||
| 										{#if $appSession.teamId === '0' && destination.remoteVerified === false && destination.remoteEngine} | ||||
| 											<h2 class="text-red-500">Not verified yet</h2> | ||||
| @ -1373,7 +1424,7 @@ | ||||
| 								{/if} | ||||
| 							</div> | ||||
| 							<div class="w-full flex flex-col"> | ||||
| 								<h1 class="font-bold text-lg lg:text-xl truncate">{destination.name}</h1> | ||||
| 								<h1 class="font-bold text-base truncate">{destination.name}</h1> | ||||
| 								<div class="h-10 text-xs"> | ||||
| 									{#if $appSession.teamId === '0' && destination.remoteVerified === false && destination.remoteEngine} | ||||
| 										<h2 class="text-red-500">Not verified yet</h2> | ||||
|  | ||||
| @ -43,7 +43,7 @@ textarea { | ||||
| } | ||||
| 
 | ||||
| #svelte .custom-select-wrapper .selectContainer { | ||||
| 	@apply h-12 w-96 rounded bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm ; | ||||
| 	@apply h-12 rounded bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm ; | ||||
| } | ||||
| 
 | ||||
| #svelte .listContainer { | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "coolify", | ||||
|   "description": "An open-source & self-hostable Heroku / Netlify alternative.", | ||||
|   "version": "3.10.11", | ||||
|   "version": "3.10.12", | ||||
|   "license": "Apache-2.0", | ||||
|   "repository": "github:coollabsio/coolify", | ||||
|   "scripts": { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user