feat: docker compose support
This commit is contained in:
parent
d8206c0e3e
commit
d27426fd8f
@ -110,23 +110,64 @@ export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { teamId } = request.user
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let isRestarting = false;
|
||||
let payload = []
|
||||
const application: any = await getApplicationFromDB(id, teamId);
|
||||
if (application?.destinationDockerId) {
|
||||
const status = await checkContainer({ dockerId: application.destinationDocker.id, container: id });
|
||||
if (status?.found) {
|
||||
isRunning = status.status.isRunning;
|
||||
isExited = status.status.isExited;
|
||||
isRestarting = status.status.isRestarting
|
||||
if (application.buildPack === 'compose') {
|
||||
const { stdout: containers } = await executeDockerCmd({
|
||||
dockerId: application.destinationDocker.id,
|
||||
command:
|
||||
`docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'`
|
||||
});
|
||||
const containersArray = containers.trim().split('\n');
|
||||
if (containersArray.length > 0 && containersArray[0] !== '') {
|
||||
for (const container of containersArray) {
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let isRestarting = false;
|
||||
const containerObj = JSON.parse(container);
|
||||
const status = containerObj.State
|
||||
if (status === 'running') {
|
||||
isRunning = true;
|
||||
}
|
||||
if (status === 'exited') {
|
||||
isExited = true;
|
||||
}
|
||||
if (status === 'restarting') {
|
||||
isRestarting = true;
|
||||
}
|
||||
payload.push({
|
||||
name: containerObj.Names,
|
||||
status: {
|
||||
isRunning,
|
||||
isExited,
|
||||
isRestarting
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let isRestarting = false;
|
||||
const status = await checkContainer({ dockerId: application.destinationDocker.id, container: id });
|
||||
if (status?.found) {
|
||||
isRunning = status.status.isRunning;
|
||||
isExited = status.status.isExited;
|
||||
isRestarting = status.status.isRestarting
|
||||
payload.push({
|
||||
name: id,
|
||||
status: {
|
||||
isRunning,
|
||||
isExited,
|
||||
isRestarting
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
isRunning,
|
||||
isRestarting,
|
||||
isExited,
|
||||
};
|
||||
return payload
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
@ -294,7 +335,6 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
||||
dockerComposeFileLocation,
|
||||
dockerComposeConfiguration
|
||||
} = request.body
|
||||
console.log({dockerComposeConfiguration})
|
||||
if (port) port = Number(port);
|
||||
if (exposePort) {
|
||||
exposePort = Number(exposePort);
|
||||
@ -515,6 +555,21 @@ export async function stopApplication(request: FastifyRequest<OnlyId>, reply: Fa
|
||||
const application: any = await getApplicationFromDB(id, teamId);
|
||||
if (application?.destinationDockerId) {
|
||||
const { id: dockerId } = application.destinationDocker;
|
||||
if (application.buildPack === 'compose') {
|
||||
const { stdout: containers } = await executeDockerCmd({
|
||||
dockerId: application.destinationDocker.id,
|
||||
command:
|
||||
`docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'`
|
||||
});
|
||||
const containersArray = containers.trim().split('\n');
|
||||
if (containersArray.length > 0 && containersArray[0] !== '') {
|
||||
for (const container of containersArray) {
|
||||
const containerObj = JSON.parse(container);
|
||||
await removeContainer({ id: containerObj.ID, dockerId: application.destinationDocker.id });
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
const { found } = await checkContainer({ dockerId, container: id });
|
||||
if (found) {
|
||||
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
||||
|
@ -234,6 +234,8 @@ export async function traefikConfiguration(request, reply) {
|
||||
fqdn,
|
||||
id,
|
||||
port,
|
||||
buildPack,
|
||||
dockerComposeConfiguration,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
settings: { previews, dualCerts, isCustomSSL }
|
||||
@ -241,6 +243,33 @@ export async function traefikConfiguration(request, reply) {
|
||||
if (destinationDockerId) {
|
||||
const { network, id: dockerId } = destinationDocker;
|
||||
const isRunning = true;
|
||||
if (buildPack === 'compose') {
|
||||
const services = Object.entries(JSON.parse(dockerComposeConfiguration))
|
||||
for (const service of services) {
|
||||
const [key, value] = service
|
||||
const { port: customPort, fqdn } = value
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
data.applications.push({
|
||||
id: `${id}-${key}`,
|
||||
container: `${id}-${key}`,
|
||||
port: customPort ? customPort : port || 3000,
|
||||
domain,
|
||||
nakedDomain,
|
||||
isRunning,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL
|
||||
});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
@ -604,13 +633,41 @@ export async function remoteTraefikConfiguration(request: FastifyRequest<OnlyId>
|
||||
fqdn,
|
||||
id,
|
||||
port,
|
||||
buildPack,
|
||||
dockerComposeConfiguration,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
settings: { previews, dualCerts }
|
||||
settings: { previews, dualCerts, isCustomSSL }
|
||||
} = application;
|
||||
if (destinationDockerId) {
|
||||
const { id: dockerId, network } = destinationDocker;
|
||||
const isRunning = true;
|
||||
if (buildPack === 'compose') {
|
||||
const services = Object.entries(JSON.parse(dockerComposeConfiguration))
|
||||
for (const service of services) {
|
||||
const [key, value] = service
|
||||
const { port: customPort, fqdn } = value
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
data.applications.push({
|
||||
id: `${id}-${key}`,
|
||||
container: `${id}-${key}`,
|
||||
port: customPort ? customPort : port || 3000,
|
||||
domain,
|
||||
nakedDomain,
|
||||
isRunning,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL
|
||||
});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
@ -626,7 +683,8 @@ export async function remoteTraefikConfiguration(request: FastifyRequest<OnlyId>
|
||||
isRunning,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL
|
||||
});
|
||||
}
|
||||
if (previews) {
|
||||
@ -649,7 +707,8 @@ export async function remoteTraefikConfiguration(request: FastifyRequest<OnlyId>
|
||||
nakedDomain,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ export const isDeploymentEnabled: Writable<boolean> = writable(false);
|
||||
export function checkIfDeploymentEnabledApplications(isAdmin: boolean, application: any) {
|
||||
return (
|
||||
isAdmin &&
|
||||
(application.buildPack === 'compose') ||
|
||||
(application.fqdn || application.settings.isBot) &&
|
||||
application.gitSource &&
|
||||
application.repository &&
|
||||
@ -74,9 +75,8 @@ export function checkIfDeploymentEnabledServices(isAdmin: boolean, service: any)
|
||||
}
|
||||
export const status: Writable<any> = writable({
|
||||
application: {
|
||||
isRunning: false,
|
||||
isExited: false,
|
||||
isRestarting: false,
|
||||
statuses: [],
|
||||
overallStatus: 'degraded',
|
||||
loading: false,
|
||||
initialLoading: true
|
||||
},
|
||||
|
@ -59,7 +59,6 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { t } from '$lib/translations';
|
||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||
import {
|
||||
appSession,
|
||||
status,
|
||||
@ -140,13 +139,11 @@
|
||||
async function stopApplication() {
|
||||
try {
|
||||
$status.application.initialLoading = true;
|
||||
// $status.application.loading = true;
|
||||
await post(`/applications/${id}/stop`, {});
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
$status.application.initialLoading = false;
|
||||
// $status.application.loading = false;
|
||||
await getStatus();
|
||||
}
|
||||
}
|
||||
@ -154,18 +151,45 @@
|
||||
if ($status.application.loading) return;
|
||||
$status.application.loading = true;
|
||||
const data = await get(`/applications/${id}/status`);
|
||||
$status.application.isRunning = data.isRunning;
|
||||
$status.application.isExited = data.isExited;
|
||||
$status.application.isRestarting = data.isRestarting;
|
||||
|
||||
$status.application.statuses = data;
|
||||
const numberOfApplications =
|
||||
application.buildPack === 'compose'
|
||||
? Object.entries(JSON.parse(application.dockerComposeConfiguration)).length
|
||||
: 1;
|
||||
if ($status.application.statuses.length === 0) {
|
||||
$status.application.overallStatus = 'stopped';
|
||||
} else {
|
||||
if ($status.application.statuses.length !== numberOfApplications) {
|
||||
$status.application.overallStatus = 'degraded';
|
||||
} else {
|
||||
for (const oneStatus of $status.application.statuses) {
|
||||
if (oneStatus.status.isExited || oneStatus.status.isRestarting) {
|
||||
$status.application.overallStatus = 'degraded';
|
||||
break;
|
||||
}
|
||||
if (oneStatus.status.isRunning) {
|
||||
$status.application.overallStatus = 'healthy';
|
||||
}
|
||||
if (
|
||||
!oneStatus.status.isExited &&
|
||||
!oneStatus.status.isRestarting &&
|
||||
!oneStatus.status.isRunning
|
||||
) {
|
||||
$status.application.overallStatus = 'stopped';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$status.application.loading = false;
|
||||
$status.application.initialLoading = false;
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
$status.application.initialLoading = true;
|
||||
$status.application.isRunning = false;
|
||||
$status.application.isExited = false;
|
||||
$status.application.isRestarting = false;
|
||||
// $status.application.isRunning = false;
|
||||
// $status.application.isExited = false;
|
||||
// $status.application.isRestarting = false;
|
||||
$status.application.loading = false;
|
||||
$location = null;
|
||||
$isDeploymentEnabled = false;
|
||||
@ -173,15 +197,11 @@
|
||||
});
|
||||
onMount(async () => {
|
||||
setLocation(application, settings);
|
||||
$status.application.isRunning = false;
|
||||
$status.application.isExited = false;
|
||||
$status.application.isRestarting = false;
|
||||
// $status.application.isRunning = false;
|
||||
// $status.application.isExited = false;
|
||||
// $status.application.isRestarting = false;
|
||||
$status.application.loading = false;
|
||||
if (
|
||||
application.gitSourceId &&
|
||||
application.destinationDockerId &&
|
||||
(application.fqdn || application.settings.isBot)
|
||||
) {
|
||||
if ($isDeploymentEnabled) {
|
||||
await getStatus();
|
||||
statusInterval = setInterval(async () => {
|
||||
await getStatus();
|
||||
@ -208,10 +228,15 @@
|
||||
<div>Configurations</div>
|
||||
<div
|
||||
class="badge rounded uppercase"
|
||||
class:text-green-500={$status.application.isRunning}
|
||||
class:text-red-500={!$status.application.isRunning}
|
||||
class:text-green-500={$status.application.overallStatus === 'healthy'}
|
||||
class:text-yellow-400={$status.application.overallStatus === 'degraded'}
|
||||
class:text-red-500={$status.application.overallStatus === 'stopped'}
|
||||
>
|
||||
{$status.application.isRunning ? 'Running' : 'Stopped'}
|
||||
{$status.application.overallStatus === 'healthy'
|
||||
? 'Running'
|
||||
: $status.application.overallStatus === 'degraded'
|
||||
? 'Degraded'
|
||||
: 'Stopped'}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@ -245,7 +270,7 @@
|
||||
<div
|
||||
class="pt-4 flex flex-row items-start justify-center lg:justify-end space-x-2 order-1 lg:order-2"
|
||||
>
|
||||
{#if $status.application.isExited || $status.application.isRestarting}
|
||||
{#if $status.application.overallStatus === 'degraded' && application.buildPack !== 'compose'}
|
||||
<a
|
||||
id="applicationerror"
|
||||
href={$isDeploymentEnabled ? `/applications/${id}/logs` : null}
|
||||
@ -293,7 +318,7 @@
|
||||
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
||||
</svg>
|
||||
</button>
|
||||
{:else if $status.application.isRunning}
|
||||
{:else if $status.application.overallStatus === 'healthy'}
|
||||
<button
|
||||
id="stop"
|
||||
on:click={stopApplication}
|
||||
@ -385,11 +410,11 @@
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</svg>
|
||||
Deploy
|
||||
{$status.application.overallStatus === 'degraded' ? 'Restart Degraded Services' : 'Deploy'}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if $location && $status.application.isRunning}
|
||||
{#if $location && $status.application.overallStatus === 'healthy'}
|
||||
<a id="openApplication" href={$location} target="_blank" class="icons bg-transparent "
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -28,6 +28,8 @@
|
||||
<script lang="ts">
|
||||
export let application: any;
|
||||
export let settings: any;
|
||||
|
||||
import yaml from 'js-yaml';
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import Select from 'svelte-select';
|
||||
@ -47,13 +49,16 @@
|
||||
import Setting from '$lib/components/Setting.svelte';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
$: isDisabled =
|
||||
!$appSession.isAdmin || $status.application.isRunning || $status.application.initialLoading;
|
||||
!$appSession.isAdmin ||
|
||||
$status.application.overallStatus === 'degraded' ||
|
||||
$status.application.overallStatus === 'healthy' ||
|
||||
$status.application.initialLoading;
|
||||
|
||||
let statues: any = {};
|
||||
let loading = false;
|
||||
let fqdnEl: any = null;
|
||||
let forceSave = false;
|
||||
@ -176,7 +181,7 @@
|
||||
isCustomSSL = !isCustomSSL;
|
||||
}
|
||||
if (name === 'isBot') {
|
||||
if ($status.application.isRunning) return;
|
||||
if ($status.application.overallStatus !== 'stopped') return;
|
||||
isBot = !isBot;
|
||||
application.settings.isBot = isBot;
|
||||
application.fqdn = null;
|
||||
@ -228,9 +233,9 @@
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
||||
}
|
||||
}
|
||||
async function handleSubmit() {
|
||||
async function handleSubmit(toast: boolean = true) {
|
||||
if (loading) return;
|
||||
loading = true;
|
||||
if (toast) loading = true;
|
||||
try {
|
||||
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||
if (application.deploymentType)
|
||||
@ -252,7 +257,7 @@
|
||||
|
||||
forceSave = false;
|
||||
|
||||
addToast({
|
||||
toast && addToast({
|
||||
message: 'Configuration saved.',
|
||||
type: 'success'
|
||||
});
|
||||
@ -333,7 +338,7 @@
|
||||
let dockerComposeFileContentJSON = JSON.parse(dockerComposeFileContent);
|
||||
dockerComposeServices = normalizeDockerServices(dockerComposeFileContentJSON?.services);
|
||||
application.dockerComposeFile = dockerComposeFileContent;
|
||||
await handleSubmit();
|
||||
await handleSubmit(false);
|
||||
}
|
||||
addToast({
|
||||
message: 'Compose file reloaded.',
|
||||
@ -343,6 +348,30 @@
|
||||
errorNotification(error);
|
||||
}
|
||||
}
|
||||
$: if ($status.application.statuses) {
|
||||
for (const service of dockerComposeServices) {
|
||||
getStatus(service);
|
||||
}
|
||||
}
|
||||
function getStatus(service: any) {
|
||||
let foundStatus = null;
|
||||
const foundService = $status.application.statuses.find(
|
||||
(s: any) => s.name === `${application.id}-${service.name}`
|
||||
);
|
||||
if (foundService) {
|
||||
const statusText = foundService?.status;
|
||||
if (statusText?.isRunning) {
|
||||
foundStatus = 'Running';
|
||||
}
|
||||
if (statusText?.isExited) {
|
||||
foundStatus = 'Exited';
|
||||
}
|
||||
if (statusText?.isRestarting) {
|
||||
foundStatus = 'Restarting';
|
||||
}
|
||||
}
|
||||
statues[service.name] = foundStatus || 'Stopped';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
@ -443,7 +472,7 @@
|
||||
on:click={() => changeSettings('isBot')}
|
||||
title="Is your application a bot?"
|
||||
description="You can deploy applications without domains or make them to listen on the <span class='text-settings font-bold'>Exposed Port</span>.<br></Setting><br>Useful to host <span class='text-settings font-bold'>Twitch bots, regular jobs, or anything that does not require an incoming HTTP connection.</span>"
|
||||
disabled={$status.application.isRunning}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@ -510,12 +539,12 @@
|
||||
<Setting
|
||||
id="dualCerts"
|
||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||
disabled={$status.application.isRunning}
|
||||
disabled={isDisabled}
|
||||
isCenter={false}
|
||||
bind:setting={dualCerts}
|
||||
title={$t('application.ssl_www_and_non_www')}
|
||||
description="Generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-settings'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both."
|
||||
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
|
||||
on:click={() => !isDisabled && changeSettings('dualCerts')}
|
||||
/>
|
||||
</div>
|
||||
{#if isHttps && application.buildPack !== 'compose'}
|
||||
@ -552,7 +581,7 @@
|
||||
{isDisabled}
|
||||
containerClasses={isDisabled && containerClass()}
|
||||
id="baseBuildImages"
|
||||
showIndicator={!$status.application.isRunning}
|
||||
showIndicator={!isDisabled}
|
||||
items={application.baseBuildImages}
|
||||
on:select={selectBaseBuildImage}
|
||||
value={application.baseBuildImage}
|
||||
@ -572,7 +601,7 @@
|
||||
{isDisabled}
|
||||
containerClasses={isDisabled && containerClass()}
|
||||
id="baseImages"
|
||||
showIndicator={!$status.application.isRunning}
|
||||
showIndicator={!isDisabled}
|
||||
items={application.baseImages}
|
||||
on:select={selectBaseImage}
|
||||
value={application.baseImage}
|
||||
@ -594,7 +623,7 @@
|
||||
{isDisabled}
|
||||
containerClasses={isDisabled && containerClass()}
|
||||
id="deploymentTypes"
|
||||
showIndicator={!$status.application.isRunning}
|
||||
showIndicator={!isDisabled}
|
||||
items={['static', 'node']}
|
||||
on:select={selectDeploymentType}
|
||||
value={application.deploymentType}
|
||||
@ -705,7 +734,9 @@
|
||||
<div class="grid grid-cols-2 items-center pt-4">
|
||||
<label for="port"
|
||||
>{$t('forms.port')}
|
||||
<Explainer explanation={'The port your application listens on.'} /></label
|
||||
<Explainer
|
||||
explanation={'The port your application listens inside the docker container.'}
|
||||
/></label
|
||||
>
|
||||
<input
|
||||
class="w-full"
|
||||
@ -726,7 +757,7 @@
|
||||
>
|
||||
<input
|
||||
class="w-full"
|
||||
readonly={!$appSession.isAdmin && !$status.application.isRunning}
|
||||
readonly={!isDisabled}
|
||||
disabled={isDisabled}
|
||||
name="exposePort"
|
||||
id="exposePort"
|
||||
@ -884,23 +915,29 @@
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
on:click|preventDefault={reloadCompose}
|
||||
class:loading
|
||||
disabled={loading}>Reload Docker Compose File</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2">
|
||||
{#each dockerComposeServices as service}
|
||||
<div class="grid items-center mb-6">
|
||||
<div class="text-xl font-bold uppercase">{service.name}</div>
|
||||
{#if service.data?.image}
|
||||
<div class="text-xs">{service.data.image}</div>
|
||||
{:else}
|
||||
<div class="text-xs">No image, build required</div>
|
||||
{/if}
|
||||
<div class="grid items-center bg-coolgray-100 rounded border border-coolgray-300 p-2 px-4">
|
||||
<div class="text-xl font-bold uppercase">
|
||||
{service.name}
|
||||
<span
|
||||
class="badge rounded text-white"
|
||||
class:text-red-500={statues[service.name] === 'Exited' ||
|
||||
statues[service.name] === 'Stopped'}
|
||||
class:text-yellow-400={statues[service.name] === 'Restarting'}
|
||||
class:text-green-500={statues[service.name] === 'Running'}
|
||||
>{statues[service.name] || 'Loading...'}</span
|
||||
>
|
||||
</div>
|
||||
<div class="text-xs">{application.id}-{service.name}</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<div class="grid grid-cols-2 items-center px-8">
|
||||
<label for="fqdn"
|
||||
>{$t('application.url_fqdn')}
|
||||
<Explainer
|
||||
@ -910,6 +947,8 @@
|
||||
<div>
|
||||
<input
|
||||
class="w-full"
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
bind:value={dockerComposeConfiguration[service.name].fqdn}
|
||||
@ -918,6 +957,23 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-8 pb-4">
|
||||
<label for="port"
|
||||
>{$t('forms.port')}
|
||||
<Explainer
|
||||
explanation={'The port your application listens inside the docker container.'}
|
||||
/></label
|
||||
>
|
||||
<input
|
||||
class="w-full"
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="port"
|
||||
id="port"
|
||||
bind:value={dockerComposeConfiguration[service.name].port}
|
||||
placeholder="{$t('forms.default')}: 3000"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
Loading…
x
Reference in New Issue
Block a user