fix: states and exposed ports

This commit is contained in:
Andras Bacsai 2022-07-22 12:01:07 +00:00
parent df01139c41
commit a02bcc3d02
13 changed files with 112 additions and 75 deletions

View File

@ -38,6 +38,7 @@
"get-port": "6.1.2", "get-port": "6.1.2",
"got": "12.1.0", "got": "12.1.0",
"is-ip": "4.0.0", "is-ip": "4.0.0",
"is-port-reachable": "4.0.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "8.5.1",
"node-forge": "1.3.1", "node-forge": "1.3.1",

View File

@ -31,7 +31,7 @@ const customConfig: Config = {
export const defaultProxyImage = `coolify-haproxy-alpine:latest`; export const defaultProxyImage = `coolify-haproxy-alpine:latest`;
export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`; export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`;
export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`; export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`;
export const defaultTraefikImage = `traefik:v2.6`; export const defaultTraefikImage = `traefik:v2.8`;
export function getAPIUrl() { export function getAPIUrl() {
if (process.env.GITPOD_WORKSPACE_URL) { if (process.env.GITPOD_WORKSPACE_URL) {
const { href } = new URL(process.env.GITPOD_WORKSPACE_URL) const { href } = new URL(process.env.GITPOD_WORKSPACE_URL)
@ -994,49 +994,58 @@ export async function updatePasswordInDb(database, user, newPassword, isRoot) {
} }
} }
} }
export async function getExposedFreePort(id, exposePort) { export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress) {
const { default: getPort } = await import('get-port'); const { default: getPort } = await import('get-port');
const applicationUsed = await ( const applicationUsed = await (
await prisma.application.findMany({ await prisma.application.findMany({
where: { exposePort: { not: null }, id: { not: id } }, where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
select: { exposePort: true } select: { exposePort: true }
}) })
).map((a) => a.exposePort); ).map((a) => a.exposePort);
const serviceUsed = await ( const serviceUsed = await (
await prisma.service.findMany({ await prisma.service.findMany({
where: { exposePort: { not: null }, id: { not: id } }, where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
select: { exposePort: true } select: { exposePort: true }
}) })
).map((a) => a.exposePort); ).map((a) => a.exposePort);
const usedPorts = [...applicationUsed, ...serviceUsed]; const usedPorts = [...applicationUsed, ...serviceUsed];
return await getPort({ port: exposePort, exclude: usedPorts }); if (remoteIpAddress) {
const { default: checkPort } = await import('is-port-reachable');
const found = await checkPort(exposePort, { host: remoteIpAddress });
if (!found) {
return exposePort
} }
export async function getFreePublicPort() { return false
}
return await getPort({ port: Number(exposePort), exclude: usedPorts });
}
export async function getFreePublicPort(id, dockerId) {
const { default: getPort, portNumbers } = await import('get-port'); const { default: getPort, portNumbers } = await import('get-port');
const data = await prisma.setting.findFirst(); const data = await prisma.setting.findFirst();
const { minPort, maxPort } = data; const { minPort, maxPort } = data;
const dbUsed = await ( const dbUsed = await (
await prisma.database.findMany({ await prisma.database.findMany({
where: { publicPort: { not: null } }, where: { publicPort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
select: { publicPort: true } select: { publicPort: true }
}) })
).map((a) => a.publicPort); ).map((a) => a.publicPort);
const wpFtpUsed = await ( const wpFtpUsed = await (
await prisma.wordpress.findMany({ await prisma.wordpress.findMany({
where: { ftpPublicPort: { not: null } }, where: { ftpPublicPort: { not: null }, id: { not: id }, service: { destinationDockerId: dockerId } },
select: { ftpPublicPort: true } select: { ftpPublicPort: true }
}) })
).map((a) => a.ftpPublicPort); ).map((a) => a.ftpPublicPort);
const wpUsed = await ( const wpUsed = await (
await prisma.wordpress.findMany({ await prisma.wordpress.findMany({
where: { mysqlPublicPort: { not: null } }, where: { mysqlPublicPort: { not: null }, id: { not: id }, service: { destinationDockerId: dockerId } },
select: { mysqlPublicPort: true } select: { mysqlPublicPort: true }
}) })
).map((a) => a.mysqlPublicPort); ).map((a) => a.mysqlPublicPort);
const minioUsed = await ( const minioUsed = await (
await prisma.minio.findMany({ await prisma.minio.findMany({
where: { publicPort: { not: null } }, where: { publicPort: { not: null }, id: { not: id }, service: { destinationDockerId: dockerId } },
select: { publicPort: true } select: { publicPort: true }
}) })
).map((a) => a.publicPort); ).map((a) => a.publicPort);
@ -1044,7 +1053,6 @@ export async function getFreePublicPort() {
return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts }); return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
} }
export async function startTraefikTCPProxy( export async function startTraefikTCPProxy(
destinationDocker: any, destinationDocker: any,
id: string, id: string,
@ -1067,11 +1075,11 @@ export async function startTraefikTCPProxy(
const ip = JSON.parse(Config)[0].Gateway; const ip = JSON.parse(Config)[0].Gateway;
const tcpProxy = { const tcpProxy = {
version: '3.5', version: '3.8',
services: { services: {
[`${id}-${publicPort}`]: { [`${id}-${publicPort}`]: {
container_name: container, container_name: container,
image: 'traefik:v2.6', image: 'traefik:v2.8',
command: [ command: [
`--entrypoints.tcp.address=:${publicPort}`, `--entrypoints.tcp.address=:${publicPort}`,
`--entryPoints.tcp.forwardedHeaders.insecure=true`, `--entryPoints.tcp.forwardedHeaders.insecure=true`,

View File

@ -5,7 +5,7 @@ import axios from 'axios';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common'; import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getExposedFreePort, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common'; import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common';
import { checkContainer, dockerInstance, isContainerExited, removeContainer } from '../../../../lib/docker'; import { checkContainer, dockerInstance, isContainerExited, removeContainer } from '../../../../lib/docker';
import { scheduler } from '../../../../lib/scheduler'; import { scheduler } from '../../../../lib/scheduler';
@ -18,7 +18,7 @@ export async function listApplications(request: FastifyRequest) {
const { teamId } = request.user const { teamId } = request.user
const applications = await prisma.application.findMany({ const applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { teams: true } include: { teams: true, destinationDocker: true }
}); });
const settings = await prisma.setting.findFirst() const settings = await prisma.setting.findFirst()
return { return {
@ -249,7 +249,6 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
dockerFileLocation, dockerFileLocation,
denoMainFile denoMainFile
}); });
console.log({ baseImage })
await prisma.application.update({ await prisma.application.update({
where: { id }, where: { id },
data: { data: {
@ -363,15 +362,17 @@ export async function checkDNS(request: FastifyRequest<CheckDNS>) {
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` } throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
} }
if (exposePort) { if (exposePort) {
if (exposePort < 1024 || exposePort > 65535) { if (exposePort < 1024 || exposePort > 65535) {
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` } throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
} }
const availablePort = await getExposedFreePort(id, exposePort); const { destinationDocker: { id: dockerId, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
if (configuredPort !== exposePort) {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) { if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` } throw { status: 500, message: `Port ${exposePort} is already in use.` }
} }
} }
}
if (isDNSCheckEnabled && !isDev && !forceSave) { if (isDNSCheckEnabled && !isDev && !forceSave) {
return await checkDomainsIsValidInDNS({ hostname: request.hostname.split(':')[0], fqdn, dualCerts }); return await checkDomainsIsValidInDNS({ hostname: request.hostname.split(':')[0], fqdn, dualCerts });
} }

View File

@ -13,15 +13,10 @@ import { SaveDatabaseType } from './types';
export async function listDatabases(request: FastifyRequest) { export async function listDatabases(request: FastifyRequest) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
let databases = [] const databases = await prisma.database.findMany({
if (teamId === '0') { where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
databases = await prisma.database.findMany({ include: { teams: true } }); include: { teams: true, destinationDocker: true }
} else {
databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId } } },
include: { teams: true }
}); });
}
return { return {
databases databases
} }
@ -431,8 +426,10 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
const { isPublic, appendOnly = true } = request.body; const { isPublic, appendOnly = true } = request.body;
const publicPort = await getFreePublicPort();
const settings = await listSettings(); const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
const publicPort = await getFreePublicPort(id, dockerId);
await prisma.database.update({ await prisma.database.update({
where: { id }, where: { id },
data: { data: {

View File

@ -2,7 +2,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
import fs from 'fs/promises'; import fs from 'fs/promises';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { prisma, uniqueName, asyncExecShell, getServiceImage, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions, executeDockerCmd, listSettings, getExposedFreePort } from '../../../../lib/common'; import { prisma, uniqueName, asyncExecShell, getServiceImage, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions, executeDockerCmd, listSettings, getFreeExposedPort } from '../../../../lib/common';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import { checkContainer, dockerInstance, isContainerExited, removeContainer } from '../../../../lib/docker'; import { checkContainer, dockerInstance, isContainerExited, removeContainer } from '../../../../lib/docker';
import cuid from 'cuid'; import cuid from 'cuid';
@ -145,15 +145,10 @@ import type { ActivateWordpressFtp, CheckService, DeleteServiceSecret, DeleteSer
export async function listServices(request: FastifyRequest) { export async function listServices(request: FastifyRequest) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
let services = [] const services = await prisma.service.findMany({
if (teamId === '0') { where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
services = await prisma.service.findMany({ include: { teams: true } }); include: { teams: true, destinationDocker: true }
} else {
services = await prisma.service.findMany({
where: { teams: { some: { id: teamId } } },
include: { teams: true }
}); });
}
return { return {
services services
} }
@ -376,12 +371,14 @@ export async function checkService(request: FastifyRequest<CheckService>) {
if (exposePort < 1024 || exposePort > 65535) { if (exposePort < 1024 || exposePort > 65535) {
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` } throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
} }
const { destinationDocker: { id: dockerId, remoteIpAddress }, exposePort: configuredPort } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } })
const availablePort = await getExposedFreePort(id, exposePort); if (configuredPort !== exposePort) {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) { if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` } throw { status: 500, message: `Port ${exposePort} is already in use.` }
} }
} }
}
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@ -980,7 +977,9 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const port = getServiceMainPort('minio'); const port = getServiceMainPort('minio');
const publicPort = await getFreePublicPort(); const { service: { destinationDocker: { id: dockerId } } } = await prisma.minio.findUnique({ where: { id }, include: { service: { include: { destinationDocker: true } } } })
const publicPort = await getFreePublicPort(id, dockerId);
const consolePort = 9001; const consolePort = 9001;
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
@ -2675,8 +2674,8 @@ export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, re
export async function activateWordpressFtp(request: FastifyRequest<ActivateWordpressFtp>, reply: FastifyReply) { export async function activateWordpressFtp(request: FastifyRequest<ActivateWordpressFtp>, reply: FastifyReply) {
const { id } = request.params const { id } = request.params
const { ftpEnabled } = request.body; const { ftpEnabled } = request.body;
const { service: { destinationDocker: { id: dockerId } } } = await prisma.wordpress.findUnique({ where: { id }, include: { service: { include: { destinationDocker: true } } } })
const publicPort = await getFreePublicPort(); const publicPort = await getFreePublicPort(id, dockerId);
let ftpUser = cuid(); let ftpUser = cuid();
let ftpPassword = generatePassword(); let ftpPassword = generatePassword();

View File

@ -41,7 +41,7 @@
import Setting from './_Setting.svelte'; import Setting from './_Setting.svelte';
const { id } = $page.params; const { id } = $page.params;
$: isDisabled = !$appSession.isAdmin || $status.application.isRunning; $: isDisabled = !$appSession.isAdmin || $status.application.isRunning || $status.application.initialLoading;
let domainEl: HTMLInputElement; let domainEl: HTMLInputElement;
@ -536,7 +536,7 @@
<div class="grid grid-cols-2 items-center pb-8"> <div class="grid grid-cols-2 items-center pb-8">
<Setting <Setting
dataTooltip={$t('forms.must_be_stopped_to_modify')} dataTooltip={$t('forms.must_be_stopped_to_modify')}
disabled={isDisabled} disabled={$status.application.isRunning}
isCenter={false} isCenter={false}
bind:setting={dualCerts} bind:setting={dualCerts}
title={$t('application.ssl_www_and_non_www')} title={$t('application.ssl_www_and_non_www')}

View File

@ -140,6 +140,9 @@
{#if application.fqdn} {#if application.fqdn}
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div> <div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
{/if} {/if}
{#if application.destinationDocker.name}
<div class="truncate text-center">{application.destinationDocker.name}</div>
{/if}
{#if !application.gitSourceId || !application.repository || !application.branch} {#if !application.gitSourceId || !application.repository || !application.branch}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white"> <div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Git Source Missing Git Source Missing

View File

@ -150,7 +150,7 @@
> >
<input <input
value={database.version} value={database.version}
disabled={$status.database.isRunning} disabled={$status.database.isRunning || $status.service.initialLoading}
class:cursor-pointer={!$status.database.isRunning} class:cursor-pointer={!$status.database.isRunning}
/></a /></a
> >

View File

@ -100,6 +100,9 @@
{#if $appSession.teamId === '0' && otherDatabases.length > 0} {#if $appSession.teamId === '0' && otherDatabases.length > 0}
<div class="truncate text-center">{database.teams[0].name}</div> <div class="truncate text-center">{database.teams[0].name}</div>
{/if} {/if}
{#if database.destinationDocker.name}
<div class="truncate text-center">{database.destinationDocker.name}</div>
{/if}
{#if !database.type} {#if !database.type}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white"> <div class="truncate text-center font-bold text-red-500 group-hover:text-white">
{$t('application.configuration.configuration_missing')} {$t('application.configuration.configuration_missing')}

View File

@ -31,6 +31,8 @@
const { id } = $page.params; const { id } = $page.params;
$: isDisabled = !$appSession.isAdmin || $status.service.initialLoading;
let loading = false; let loading = false;
let loadingVerification = false; let loadingVerification = false;
let dualCerts = service.dualCerts; let dualCerts = service.dualCerts;
@ -45,7 +47,7 @@
exposePort: service.exposePort exposePort: service.exposePort
}); });
await post(`/services/${id}`, { ...service }); await post(`/services/${id}`, { ...service });
setLocation(service) setLocation(service);
$disabledButton = false; $disabledButton = false;
toast.push('Configuration saved.'); toast.push('Configuration saved.');
} catch (error) { } catch (error) {
@ -145,7 +147,7 @@
<div class="grid grid-cols-2 items-center px-10"> <div class="grid grid-cols-2 items-center px-10">
<label for="version" class="text-base font-bold text-stone-100">Version / Tag</label> <label for="version" class="text-base font-bold text-stone-100">Version / Tag</label>
<a <a
href={$appSession.isAdmin && !$status.service.isRunning href={$appSession.isAdmin && !$status.service.isRunning && !$status.service.initialLoading
? `/services/${id}/configuration/version?from=/services/${id}` ? `/services/${id}/configuration/version?from=/services/${id}`
: ''} : ''}
class="no-underline" class="no-underline"
@ -153,7 +155,7 @@
<input <input
value={service.version} value={service.version}
id="service" id="service"
disabled={$status.service.isRunning} disabled={$status.service.isRunning || $status.service.initialLoading}
class:cursor-pointer={!$status.service.isRunning} class:cursor-pointer={!$status.service.isRunning}
/></a /></a
> >
@ -184,7 +186,9 @@
<CopyPasswordField <CopyPasswordField
placeholder="eg: https://console.min.io" placeholder="eg: https://console.min.io"
readonly={!$appSession.isAdmin && !$status.service.isRunning} readonly={!$appSession.isAdmin && !$status.service.isRunning}
disabled={!$appSession.isAdmin || $status.service.isRunning} disabled={!$appSession.isAdmin ||
$status.service.isRunning ||
$status.service.initialLoading}
name="fqdn" name="fqdn"
id="fqdn" id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$" pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
@ -201,7 +205,7 @@
<CopyPasswordField <CopyPasswordField
placeholder="eg: https://min.io" placeholder="eg: https://min.io"
readonly={!$appSession.isAdmin && !$status.service.isRunning} readonly={!$appSession.isAdmin && !$status.service.isRunning}
disabled={!$appSession.isAdmin || $status.service.isRunning} disabled={isDisabled}
name="apiFqdn" name="apiFqdn"
id="apiFqdn" id="apiFqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$" pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
@ -221,7 +225,9 @@
<CopyPasswordField <CopyPasswordField
placeholder="eg: https://analytics.coollabs.io" placeholder="eg: https://analytics.coollabs.io"
readonly={!$appSession.isAdmin && !$status.service.isRunning} readonly={!$appSession.isAdmin && !$status.service.isRunning}
disabled={!$appSession.isAdmin || $status.service.isRunning} disabled={!$appSession.isAdmin ||
$status.service.isRunning ||
$status.service.initialLoading}
name="fqdn" name="fqdn"
id="fqdn" id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$" pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
@ -245,7 +251,9 @@
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label> <label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
<input <input
readonly={!$appSession.isAdmin && !$status.service.isRunning} readonly={!$appSession.isAdmin && !$status.service.isRunning}
disabled={!$appSession.isAdmin || $status.service.isRunning} disabled={!$appSession.isAdmin ||
$status.service.isRunning ||
$status.service.initialLoading}
name="exposePort" name="exposePort"
id="exposePort" id="exposePort"
bind:value={service.exposePort} bind:value={service.exposePort}

View File

@ -76,13 +76,13 @@
<label for="extraConfig">{$t('forms.extra_config')}</label> <label for="extraConfig">{$t('forms.extra_config')}</label>
<textarea <textarea
bind:value={service.wordpress.extraConfig} bind:value={service.wordpress.extraConfig}
disabled={$status.service.isRunning} disabled={$status.service.isRunning || $status.service.initialLoading}
readonly={$status.service.isRunning} readonly={$status.service.isRunning}
class:resize-none={$status.service.isRunning} class:resize-none={$status.service.isRunning}
rows="5" rows="5"
name="extraConfig" name="extraConfig"
id="extraConfig" id="extraConfig"
placeholder={!$status.service.isRunning placeholder={!$status.service.isRunning && !$status.service.initialLoading
? `${$t('forms.eg')}: ? `${$t('forms.eg')}:
define('WP_ALLOW_MULTISITE', true); define('WP_ALLOW_MULTISITE', true);
@ -112,7 +112,14 @@ define('SUBDOMAIN_INSTALL', false);`
</div> </div>
<div class="grid grid-cols-2 items-center px-10"> <div class="grid grid-cols-2 items-center px-10">
<label for="ftpPassword">Password</label> <label for="ftpPassword">Password</label>
<CopyPasswordField id="ftpPassword" isPasswordField readonly disabled name="ftpPassword" value={ftpPassword} /> <CopyPasswordField
id="ftpPassword"
isPasswordField
readonly
disabled
name="ftpPassword"
value={ftpPassword}
/>
</div> </div>
{/if} {/if}
<div class="flex space-x-1 py-5 font-bold"> <div class="flex space-x-1 py-5 font-bold">

View File

@ -85,6 +85,9 @@
{#if service.fqdn} {#if service.fqdn}
<div class="truncate text-center">{getDomain(service.fqdn) || ''}</div> <div class="truncate text-center">{getDomain(service.fqdn) || ''}</div>
{/if} {/if}
{#if service.destinationDocker.name}
<div class="truncate text-center">{service.destinationDocker.name}</div>
{/if}
{#if !service.type || !service.fqdn} {#if !service.type || !service.fqdn}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white"> <div class="truncate text-center font-bold text-red-500 group-hover:text-white">
{$t('application.configuration.configuration_missing')} {$t('application.configuration.configuration_missing')}

View File

@ -44,6 +44,7 @@ importers:
get-port: 6.1.2 get-port: 6.1.2
got: 12.1.0 got: 12.1.0
is-ip: 4.0.0 is-ip: 4.0.0
is-port-reachable: ^4.0.0
js-yaml: 4.1.0 js-yaml: 4.1.0
jsonwebtoken: 8.5.1 jsonwebtoken: 8.5.1
node-forge: 1.3.1 node-forge: 1.3.1
@ -83,6 +84,7 @@ importers:
get-port: 6.1.2 get-port: 6.1.2
got: 12.1.0 got: 12.1.0
is-ip: 4.0.0 is-ip: 4.0.0
is-port-reachable: 4.0.0
js-yaml: 4.1.0 js-yaml: 4.1.0
jsonwebtoken: 8.5.1 jsonwebtoken: 8.5.1
node-forge: 1.3.1 node-forge: 1.3.1
@ -3345,6 +3347,11 @@ packages:
engines: {node: '>=0.12.0'} engines: {node: '>=0.12.0'}
dev: true dev: true
/is-port-reachable/4.0.0:
resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/is-regex/1.1.4: /is-regex/1.1.4:
resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}