feat: restart application

This commit is contained in:
Andras Bacsai 2022-08-31 15:03:04 +02:00
parent b239d21961
commit 92f513d514
3 changed files with 470 additions and 316 deletions

View File

@ -3,16 +3,23 @@ import crypto from 'node:crypto'
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
import axios from 'axios'; import axios from 'axios';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import fs from 'fs/promises';
import yaml from 'js-yaml';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common'; import { makeLabelForStandaloneApplication, setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
import { checkDomainsIsValidInDNS, checkDoubleBranch, checkExposedPort, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common'; import { checkDomainsIsValidInDNS, checkDoubleBranch, checkExposedPort, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker'; import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
import { scheduler } from '../../../../lib/scheduler';
import type { FastifyRequest } from 'fastify'; import type { FastifyRequest } from 'fastify';
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication } from './types'; import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication } from './types';
import { OnlyId } from '../../../../types'; import { OnlyId } from '../../../../types';
function filterObject(obj, callback) {
return Object.fromEntries(Object.entries(obj).
filter(([key, val]) => callback(val, key)));
}
export async function listApplications(request: FastifyRequest) { export async function listApplications(request: FastifyRequest) {
try { try {
const { teamId } = request.user const { teamId } = request.user
@ -312,6 +319,113 @@ export async function stopPreviewApplication(request: FastifyRequest<StopPreview
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function restartApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try {
const { id } = request.params
const { teamId } = request.user
let application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) {
const buildId = cuid();
const { id: dockerId, network } = application.destinationDocker;
const { secrets, pullmergeRequestId, port, repository, persistentStorage, id: applicationId, buildPack, exposePort } = application;
const envs = [
`PORT=${port}`
];
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
envs.push(`${secret.name}=${secret.value}`);
}
} else {
if (!secret.isPRMRSecret) {
envs.push(`${secret.name}=${secret.value}`);
}
}
});
}
const { workdir } = await createDirectories({ repository, buildId });
const labels = []
let image = null
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` })
const containersArray = container.trim().split('\n');
for (const container of containersArray) {
const containerObj = formatLabelsOnDocker(container);
image = containerObj[0].Image
Object.keys(containerObj[0].Labels).forEach(function (key) {
if (key.startsWith('coolify')) {
labels.push(`${key}=${containerObj[0].Labels[key]}`)
}
})
}
let imageFound = false;
try {
await executeDockerCmd({
dockerId,
command: `docker image inspect ${image}`
})
imageFound = true;
} catch (error) {
//
}
if (!imageFound) {
throw { status: 500, message: 'Image not found, cannot restart application.' }
}
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
let envFound = false;
try {
envFound = !!(await fs.stat(`${workdir}/.env`));
} catch (error) {
//
}
const volumes =
persistentStorage?.map((storage) => {
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
}${storage.path}`;
}) || [];
const composeVolumes = volumes.map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
name: volume.split(':')[0]
}
};
});
const composeFile = {
version: '3.8',
services: {
[applicationId]: {
image,
container_name: applicationId,
volumes,
env_file: envFound ? [`${workdir}/.env`] : [],
labels,
depends_on: [],
expose: [port],
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
...defaultComposeConfiguration(network),
}
},
networks: {
[network]: {
external: true
}
},
volumes: Object.assign({}, ...composeVolumes)
};
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
await executeDockerCmd({ dockerId, command: `docker compose --project-directory ${workdir} up -d` })
return reply.code(201).send();
}
throw { status: 500, message: 'Application cannot be restarted.' }
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function stopApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) { export async function stopApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params

View File

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

View File

@ -62,9 +62,7 @@
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { appSession, disabledButton, status, location, setLocation, addToast } from '$lib/store'; import { appSession, disabledButton, status, location, setLocation, addToast } from '$lib/store';
import { errorNotification, handlerNotFoundLoad } from '$lib/common'; import { errorNotification, handlerNotFoundLoad } from '$lib/common';
import Loading from '$lib/components/Loading.svelte';
let loading = false;
let statusInterval: any; let statusInterval: any;
$disabledButton = $disabledButton =
!$appSession.isAdmin || !$appSession.isAdmin ||
@ -78,7 +76,10 @@
async function handleDeploySubmit(forceRebuild = false) { async function handleDeploySubmit(forceRebuild = false) {
try { try {
const { buildId } = await post(`/applications/${id}/deploy`, { ...application, forceRebuild }); const { buildId } = await post(`/applications/${id}/deploy`, {
...application,
forceRebuild
});
addToast({ addToast({
message: $t('application.deployment_queued'), message: $t('application.deployment_queued'),
type: 'success' type: 'success'
@ -98,22 +99,41 @@
async function deleteApplication(name: string) { async function deleteApplication(name: string) {
const sure = confirm($t('application.confirm_to_delete', { name })); const sure = confirm($t('application.confirm_to_delete', { name }));
if (sure) { if (sure) {
loading = true; $status.application.initialLoading = true;
try { try {
await del(`/applications/${id}`, { id }); await del(`/applications/${id}`, { id });
return await goto(`/applications`); return await goto(`/applications`);
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally {
$status.application.initialLoading = false;
} }
} }
} }
async function restartApplication() {
try {
$status.application.initialLoading = true;
$status.application.loading = true;
await post(`/applications/${id}/restart`, {});
} catch (error) {
return errorNotification(error);
} finally {
$status.application.initialLoading = false;
$status.application.loading = false;
await getStatus();
}
}
async function stopApplication() { async function stopApplication() {
try { try {
loading = true; $status.application.initialLoading = true;
$status.application.loading = true;
await post(`/applications/${id}/stop`, {}); await post(`/applications/${id}/stop`, {});
return window.location.reload();
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally {
$status.application.initialLoading = false;
$status.application.loading = false;
await getStatus();
} }
} }
async function getStatus() { async function getStatus() {
@ -152,209 +172,136 @@
</script> </script>
<nav class="nav-side"> <nav class="nav-side">
{#if loading} {#if $location}
<Loading fullscreen cover /> <a
{:else} href={$location}
{#if $location} target="_blank"
<a class="icons tooltip-bottom flex items-center bg-transparent text-sm"
href={$location} ><svg
target="_blank" xmlns="http://www.w3.org/2000/svg"
class="icons tooltip-bottom flex items-center bg-transparent text-sm" class="h-6 w-6"
><svg viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" stroke-width="1.5"
class="h-6 w-6" stroke="currentColor"
viewBox="0 0 24 24" fill="none"
stroke-width="1.5" stroke-linecap="round"
stroke="currentColor" stroke-linejoin="round"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
<div class="border border-coolgray-500 h-8" /> <div class="border border-coolgray-500 h-8" />
{/if}
{/if} {#if $status.application.isExited}
<a
{#if $status.application.isExited} href={!$disabledButton ? `/applications/${id}/logs` : null}
<a class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center text-error"
href={!$disabledButton ? `/applications/${id}/logs` : null} data-tip="Application exited with an error!"
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center text-error" sveltekit:prefetch
data-tip="Application exited with an error!" >
sveltekit:prefetch <svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentcolor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
> >
<svg <path stroke="none" d="M0 0h24v24H0z" fill="none" />
xmlns="http://www.w3.org/2000/svg" <path
class="w-6 h-6" d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
viewBox="0 0 24 24" />
stroke-width="1.5" <line x1="12" y1="8" x2="12" y2="12" />
stroke="currentcolor" <line x1="12" y1="16" x2="12.01" y2="16" />
fill="none" </svg>
stroke-linecap="round" </a>
stroke-linejoin="round" {/if}
> {#if $status.application.initialLoading}
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <button
<path class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z" >
/> <svg
<line x1="12" y1="8" x2="12" y2="12" /> xmlns="http://www.w3.org/2000/svg"
<line x1="12" y1="16" x2="12.01" y2="16" /> class="h-6 w-6"
</svg> viewBox="0 0 24 24"
</a> stroke-width="1.5"
{/if} stroke="currentColor"
{#if $status.application.initialLoading} fill="none"
<button stroke-linecap="round"
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out" stroke-linejoin="round"
> >
<svg <path stroke="none" d="M0 0h24v24H0z" fill="none" />
xmlns="http://www.w3.org/2000/svg" <path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
class="h-6 w-6" <line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
viewBox="0 0 24 24" <line x1="4.06" y1="11" x2="4.06" y2="11.01" />
stroke-width="1.5" <line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
stroke="currentColor" <line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
fill="none" <line x1="11" y1="19.94" x2="11" y2="19.95" />
stroke-linecap="round" </svg>
stroke-linejoin="round" </button>
> {:else if $status.application.isRunning}
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <button
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" /> on:click={stopApplication}
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" /> type="submit"
<line x1="4.06" y1="11" x2="4.06" y2="11.01" /> disabled={$disabledButton}
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" /> class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-error"
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" /> data-tip={$appSession.isAdmin
<line x1="11" y1="19.94" x2="11" y2="19.95" /> ? 'Stop'
</svg> : $t('application.permission_denied_stop_application')}
</button> >
{:else if $status.application.isRunning} <svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="6" y="5" width="4" height="14" rx="1" />
<rect x="14" y="5" width="4" height="14" rx="1" />
</svg>
</button>
<button
on:click={restartApplication}
type="submit"
disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 "
data-tip={$appSession.isAdmin
? 'Restart (useful for changing secrets)'
: $t('application.permission_denied_stop_application')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" />
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" />
</svg>
</button>
<form on:submit|preventDefault={() => handleDeploySubmit(true)}>
<button <button
on:click={stopApplication}
type="submit" type="submit"
disabled={$disabledButton} disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-error" class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2"
data-tip={$appSession.isAdmin data-tip={$appSession.isAdmin
? $t('application.stop_application') ? 'Force Rebuild without cache'
: $t('application.permission_denied_stop_application')} : 'You do not have permission to rebuild application.'}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="6" y="5" width="4" height="14" rx="1" />
<rect x="14" y="5" width="4" height="14" rx="1" />
</svg>
</button>
<form on:submit|preventDefault={() => handleDeploySubmit(true)}>
<button
type="submit"
disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2"
data-tip={$appSession.isAdmin
? 'Force Rebuild Application'
: 'You do not have permission to rebuild application.'}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M16.3 5h.7a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h5l-2.82 -2.82m0 5.64l2.82 -2.82"
transform="rotate(-45 12 12)"
/>
</svg>
</button>
</form>
{:else}
<form on:submit|preventDefault={() => handleDeploySubmit(false)}>
<button
type="submit"
disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-success"
data-tip={$appSession.isAdmin
? 'Deploy'
: 'You do not have permission to deploy application.'}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg>
</button>
</form>
{/if}
<div class="border border-coolgray-500 h-8" />
<a
href={!$disabledButton ? `/applications/${id}` : null}
sveltekit:prefetch
class="hover:text-yellow-500 rounded"
class:text-yellow-500={$page.url.pathname === `/applications/${id}`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}`}
>
<button
disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip="Configurations"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg></button
></a
>
<a
href={!$disabledButton ? `/applications/${id}/secrets` : null}
sveltekit:prefetch
class="hover:text-pink-500 rounded"
class:text-pink-500={$page.url.pathname === `/applications/${id}/secrets`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`}
>
<button
disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip="Secrets"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -368,24 +315,21 @@
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path <path
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3" d="M16.3 5h.7a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h5l-2.82 -2.82m0 5.64l2.82 -2.82"
transform="rotate(-45 12 12)"
/> />
<circle cx="12" cy="11" r="1" /> </svg>
<line x1="12" y1="12" x2="12" y2="14.5" /> </button>
</svg></button </form>
></a {:else}
> <form on:submit|preventDefault={() => handleDeploySubmit(false)}>
<a
href={!$disabledButton ? `/applications/${id}/storages` : null}
sveltekit:prefetch
class="hover:text-pink-500 rounded"
class:text-pink-500={$page.url.pathname === `/applications/${id}/storages`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storages`}
>
<button <button
type="submit"
disabled={$disabledButton} disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm" class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2 text-success"
data-tip="Persistent Storages" data-tip={$appSession.isAdmin
? 'Deploy'
: 'You do not have permission to deploy application.'}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -398,92 +342,124 @@
stroke-linejoin="round" stroke-linejoin="round"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<ellipse cx="12" cy="6" rx="8" ry="3" /> <path d="M7 4v16l13 -8z" />
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
</svg> </svg>
</button></a </button>
</form>
{/if}
<div class="border border-coolgray-500 h-8" />
<a
href={!$disabledButton ? `/applications/${id}` : null}
sveltekit:prefetch
class="hover:text-yellow-500 rounded"
class:text-yellow-500={$page.url.pathname === `/applications/${id}`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}`}
>
<button
disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip="Configurations"
> >
{#if !application.settings.isBot} <svg
<a xmlns="http://www.w3.org/2000/svg"
href={!$disabledButton ? `/applications/${id}/previews` : null} class="h-6 w-6"
sveltekit:prefetch viewBox="0 0 24 24"
class="hover:text-orange-500 rounded" stroke-width="1.5"
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`} stroke="currentColor"
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`} fill="none"
stroke-linecap="round"
stroke-linejoin="round"
> >
<button <path stroke="none" d="M0 0h24v24H0z" fill="none" />
disabled={$disabledButton} <rect x="4" y="8" width="4" height="4" />
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm" <line x1="6" y1="4" x2="6" y2="8" />
data-tip="Previews" <line x1="6" y1="12" x2="6" y2="20" />
> <rect x="10" y="14" width="4" height="4" />
<svg <line x1="12" y1="4" x2="12" y2="14" />
xmlns="http://www.w3.org/2000/svg" <line x1="12" y1="18" x2="12" y2="20" />
class="w-6 h-6" <rect x="16" y="5" width="4" height="4" />
viewBox="0 0 24 24" <line x1="18" y1="4" x2="18" y2="5" />
stroke-width="1.5" <line x1="18" y1="9" x2="18" y2="20" />
stroke="currentColor" </svg></button
fill="none" ></a
stroke-linecap="round" >
stroke-linejoin="round" <a
> href={!$disabledButton ? `/applications/${id}/secrets` : null}
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> sveltekit:prefetch
<circle cx="7" cy="18" r="2" /> class="hover:text-pink-500 rounded"
<circle cx="7" cy="6" r="2" /> class:text-pink-500={$page.url.pathname === `/applications/${id}/secrets`}
<circle cx="17" cy="12" r="2" /> class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`}
<line x1="7" y1="8" x2="7" y2="16" /> >
<path d="M7 8a4 4 0 0 0 4 4h4" /> <button
</svg></button disabled={$disabledButton}
></a class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip="Secrets"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
> >
{/if} <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<div class="border border-coolgray-500 h-8" /> <path
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
/>
<circle cx="12" cy="11" r="1" />
<line x1="12" y1="12" x2="12" y2="14.5" />
</svg></button
></a
>
<a
href={!$disabledButton ? `/applications/${id}/storages` : null}
sveltekit:prefetch
class="hover:text-pink-500 rounded"
class:text-pink-500={$page.url.pathname === `/applications/${id}/storages`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storages`}
>
<button
disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip="Persistent Storages"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<ellipse cx="12" cy="6" rx="8" ry="3" />
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
</svg>
</button></a
>
{#if !application.settings.isBot}
<a <a
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null} href={!$disabledButton ? `/applications/${id}/previews` : null}
sveltekit:prefetch sveltekit:prefetch
class="hover:text-sky-500 rounded" class="hover:text-orange-500 rounded"
class:text-sky-500={$page.url.pathname === `/applications/${id}/logs`} class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`} class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
>
<button
disabled={$disabledButton || !$status.application.isRunning}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip={$t('application.logs')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
<line x1="3" y1="6" x2="3" y2="19" />
<line x1="12" y1="6" x2="12" y2="19" />
<line x1="21" y1="6" x2="21" y2="19" />
</svg>
</button></a
>
<a
href={!$disabledButton ? `/applications/${id}/logs/build` : null}
sveltekit:prefetch
class="hover:text-red-500 rounded"
class:text-red-500={$page.url.pathname === `/applications/${id}/logs/build`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs/build`}
> >
<button <button
disabled={$disabledButton} disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm" class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip="Build Logs" data-tip="Previews"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6" class="w-6 h-6"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="currentColor" stroke="currentColor"
@ -492,31 +468,94 @@
stroke-linejoin="round" stroke-linejoin="round"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="19" cy="13" r="2" /> <circle cx="7" cy="18" r="2" />
<circle cx="4" cy="17" r="2" /> <circle cx="7" cy="6" r="2" />
<circle cx="13" cy="17" r="2" /> <circle cx="17" cy="12" r="2" />
<line x1="13" y1="19" x2="4" y2="19" /> <line x1="7" y1="8" x2="7" y2="16" />
<line x1="4" y1="15" x2="13" y2="15" /> <path d="M7 8a4 4 0 0 0 4 4h4" />
<path d="M8 12v-5h2a3 3 0 0 1 3 3v5" /> </svg></button
<path d="M5 15v-2a1 1 0 0 1 1 -1h7" /> ></a
<path d="M19 11v-7l-6 7" />
</svg>
</button></a
> >
<div class="border border-coolgray-500 h-8" />
<button
on:click={() => deleteApplication(application.name)}
type="submit"
disabled={!$appSession.isAdmin}
class:hover:text-red-500={$appSession.isAdmin}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip={$appSession.isAdmin
? $t('application.delete_application')
: $t('application.permission_denied_delete_application')}
>
<DeleteIcon />
</button>
{/if} {/if}
<div class="border border-coolgray-500 h-8" />
<a
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
sveltekit:prefetch
class="hover:text-sky-500 rounded"
class:text-sky-500={$page.url.pathname === `/applications/${id}/logs`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`}
>
<button
disabled={$disabledButton || !$status.application.isRunning}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip={$t('application.logs')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
<line x1="3" y1="6" x2="3" y2="19" />
<line x1="12" y1="6" x2="12" y2="19" />
<line x1="21" y1="6" x2="21" y2="19" />
</svg>
</button></a
>
<a
href={!$disabledButton ? `/applications/${id}/logs/build` : null}
sveltekit:prefetch
class="hover:text-red-500 rounded"
class:text-red-500={$page.url.pathname === `/applications/${id}/logs/build`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs/build`}
>
<button
disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip="Build Logs"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="19" cy="13" r="2" />
<circle cx="4" cy="17" r="2" />
<circle cx="13" cy="17" r="2" />
<line x1="13" y1="19" x2="4" y2="19" />
<line x1="4" y1="15" x2="13" y2="15" />
<path d="M8 12v-5h2a3 3 0 0 1 3 3v5" />
<path d="M5 15v-2a1 1 0 0 1 1 -1h7" />
<path d="M19 11v-7l-6 7" />
</svg>
</button></a
>
<div class="border border-coolgray-500 h-8" />
<button
on:click={() => deleteApplication(application.name)}
type="submit"
disabled={!$appSession.isAdmin}
class:hover:text-red-500={$appSession.isAdmin}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip={$appSession.isAdmin
? $t('application.delete_application')
: $t('application.permission_denied_delete_application')}
>
<DeleteIcon />
</button>
</nav> </nav>
<slot /> <slot />