fix: toast, rde, webhooks
This commit is contained in:
parent
fa9738a2e0
commit
9dfbbe58ff
@ -34,7 +34,6 @@ export async function migrateServicesToNewTemplate() {
|
|||||||
if (!service.type) {
|
if (!service.type) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
console.log(service.type)
|
|
||||||
let template = templates.find(t => fixType(t.type) === fixType(service.type));
|
let template = templates.find(t => fixType(t.type) === fixType(service.type));
|
||||||
if (template) {
|
if (template) {
|
||||||
template = JSON.parse(JSON.stringify(template).replaceAll('$$id', service.id))
|
template = JSON.parse(JSON.stringify(template).replaceAll('$$id', service.id))
|
||||||
|
@ -505,56 +505,59 @@ export async function createRemoteEngineConfiguration(id: string) {
|
|||||||
const localPort = await getFreeSSHLocalPort(id);
|
const localPort = await getFreeSSHLocalPort(id);
|
||||||
const {
|
const {
|
||||||
sshKey: { privateKey },
|
sshKey: { privateKey },
|
||||||
|
network,
|
||||||
remoteIpAddress,
|
remoteIpAddress,
|
||||||
remotePort,
|
remotePort,
|
||||||
remoteUser
|
remoteUser
|
||||||
} = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } });
|
} = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } });
|
||||||
await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 });
|
await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 });
|
||||||
// Needed for remote docker compose
|
// Needed for remote docker compose
|
||||||
const { stdout: numberOfSSHAgentsRunning } = await asyncExecShell(
|
// const { stdout: numberOfSSHAgentsRunning } = await asyncExecShell(
|
||||||
`ps ax | grep [s]sh-agent | grep coolify-ssh-agent.pid | grep -v grep | wc -l`
|
// `ps ax | grep [s]sh-agent | grep coolify-ssh-agent.pid | grep -v grep | wc -l`
|
||||||
);
|
// );
|
||||||
if (numberOfSSHAgentsRunning !== '' && Number(numberOfSSHAgentsRunning.trim()) == 0) {
|
// if (numberOfSSHAgentsRunning !== '' && Number(numberOfSSHAgentsRunning.trim()) == 0) {
|
||||||
try {
|
// try {
|
||||||
await fs.stat(`/tmp/coolify-ssh-agent.pid`);
|
// await fs.stat(`/tmp/coolify-ssh-agent.pid`);
|
||||||
await fs.rm(`/tmp/coolify-ssh-agent.pid`);
|
// await fs.rm(`/tmp/coolify-ssh-agent.pid`);
|
||||||
} catch (error) { }
|
// } catch (error) { }
|
||||||
await asyncExecShell(`eval $(ssh-agent -sa /tmp/coolify-ssh-agent.pid)`);
|
// await asyncExecShell(`eval $(ssh-agent -sa /tmp/coolify-ssh-agent.pid)`);
|
||||||
}
|
// }
|
||||||
await asyncExecShell(`SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh-add -q ${sshKeyFile}`);
|
// await asyncExecShell(`SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh-add -q ${sshKeyFile}`);
|
||||||
|
|
||||||
const { stdout: numberOfSSHTunnelsRunning } = await asyncExecShell(
|
// const { stdout: numberOfSSHTunnelsRunning } = await asyncExecShell(
|
||||||
`ps ax | grep 'ssh -F /dev/null -o StrictHostKeyChecking no -fNL ${localPort}:localhost:${remotePort}' | grep -v grep | wc -l`
|
// `ps ax | grep 'ssh -F /dev/null -o StrictHostKeyChecking no -fNL ${localPort}:localhost:${remotePort}' | grep -v grep | wc -l`
|
||||||
);
|
// );
|
||||||
if (numberOfSSHTunnelsRunning !== '' && Number(numberOfSSHTunnelsRunning.trim()) == 0) {
|
// if (numberOfSSHTunnelsRunning !== '' && Number(numberOfSSHTunnelsRunning.trim()) == 0) {
|
||||||
try {
|
// try {
|
||||||
await asyncExecShell(
|
// await asyncExecShell(
|
||||||
`SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh -F /dev/null -o "StrictHostKeyChecking no" -fNL ${localPort}:localhost:${remotePort} ${remoteUser}@${remoteIpAddress}`
|
// `SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh -F /dev/null -o "StrictHostKeyChecking no" -fNL ${localPort}:localhost:${remotePort} ${remoteUser}@${remoteIpAddress}`
|
||||||
);
|
// );
|
||||||
} catch (error) { }
|
// } catch (error) { }
|
||||||
}
|
// }
|
||||||
const config = sshConfig.parse('');
|
const config = sshConfig.parse('');
|
||||||
const foundWildcard = config.find({ Host: '*' });
|
const Host = `${remoteIpAddress}-remote`
|
||||||
if (!foundWildcard) {
|
|
||||||
|
await asyncExecShell(`ssh-keygen -R ${Host}`);
|
||||||
|
await asyncExecShell(`ssh-keygen -R ${remoteIpAddress}`);
|
||||||
|
await asyncExecShell(`ssh-keygen -R localhost:${localPort}`);
|
||||||
|
|
||||||
|
const found = config.find({ Host });
|
||||||
|
const foundIp = config.find({ Host: remoteIpAddress });
|
||||||
|
|
||||||
|
if (found) config.remove({ Host })
|
||||||
|
if (foundIp) config.remove({ Host: remoteIpAddress })
|
||||||
|
|
||||||
config.append({
|
config.append({
|
||||||
Host: '*',
|
Host,
|
||||||
StrictHostKeyChecking: 'no',
|
Hostname: remoteIpAddress,
|
||||||
ControlMaster: 'auto',
|
Port: remotePort.toString(),
|
||||||
ControlPath: `${homedir}/.ssh/coolify-%r@%h:%p`,
|
|
||||||
ControlPersist: '10m'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const found = config.find({ Host: remoteIpAddress });
|
|
||||||
if (!found) {
|
|
||||||
config.append({
|
|
||||||
Host: remoteIpAddress,
|
|
||||||
Hostname: 'localhost',
|
|
||||||
Port: localPort.toString(),
|
|
||||||
User: remoteUser,
|
User: remoteUser,
|
||||||
|
StrictHostKeyChecking: 'no',
|
||||||
IdentityFile: sshKeyFile,
|
IdentityFile: sshKeyFile,
|
||||||
StrictHostKeyChecking: 'no'
|
ControlMaster: 'auto',
|
||||||
|
ControlPath: `${homedir}/.ssh/coolify-${remoteIpAddress}-%r@%h:%p`,
|
||||||
|
ControlPersist: '10m'
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fs.stat(`${homedir}/.ssh/`);
|
await fs.stat(`${homedir}/.ssh/`);
|
||||||
@ -565,27 +568,23 @@ export async function createRemoteEngineConfiguration(id: string) {
|
|||||||
}
|
}
|
||||||
export async function executeSSHCmd({ dockerId, command }) {
|
export async function executeSSHCmd({ dockerId, command }) {
|
||||||
const { execaCommand } = await import('execa')
|
const { execaCommand } = await import('execa')
|
||||||
let { remoteEngine, remoteIpAddress, engine, remoteUser } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
let { remoteEngine, remoteIpAddress } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
||||||
if (remoteEngine) {
|
if (remoteEngine) {
|
||||||
await createRemoteEngineConfiguration(dockerId)
|
await createRemoteEngineConfiguration(dockerId)
|
||||||
engine = `ssh://${remoteIpAddress}`
|
|
||||||
} else {
|
|
||||||
engine = 'unix:///var/run/docker.sock'
|
|
||||||
}
|
}
|
||||||
if (process.env.CODESANDBOX_HOST) {
|
if (process.env.CODESANDBOX_HOST) {
|
||||||
if (command.startsWith('docker compose')) {
|
if (command.startsWith('docker compose')) {
|
||||||
command = command.replace(/docker compose/gi, 'docker-compose')
|
command = command.replace(/docker compose/gi, 'docker-compose')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
command = `ssh ${remoteIpAddress} ${command}`
|
return await execaCommand(`ssh ${remoteIpAddress}-remote ${command}`)
|
||||||
return await execaCommand(command)
|
|
||||||
}
|
}
|
||||||
export async function executeDockerCmd({ debug, buildId, applicationId, dockerId, command }: { debug?: boolean, buildId?: string, applicationId?: string, dockerId: string, command: string }): Promise<any> {
|
export async function executeDockerCmd({ debug, buildId, applicationId, dockerId, command }: { debug?: boolean, buildId?: string, applicationId?: string, dockerId: string, command: string }): Promise<any> {
|
||||||
const { execaCommand } = await import('execa')
|
const { execaCommand } = await import('execa')
|
||||||
let { remoteEngine, remoteIpAddress, engine, remoteUser } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
||||||
if (remoteEngine) {
|
if (remoteEngine) {
|
||||||
await createRemoteEngineConfiguration(dockerId);
|
await createRemoteEngineConfiguration(dockerId);
|
||||||
engine = `ssh://${remoteIpAddress}`;
|
engine = `ssh://${remoteIpAddress}-remote`;
|
||||||
} else {
|
} else {
|
||||||
engine = 'unix:///var/run/docker.sock';
|
engine = 'unix:///var/run/docker.sock';
|
||||||
}
|
}
|
||||||
|
@ -204,8 +204,8 @@ export async function assignSSHKey(request: FastifyRequest) {
|
|||||||
}
|
}
|
||||||
export async function verifyRemoteDockerEngineFn(id: string) {
|
export async function verifyRemoteDockerEngineFn(id: string) {
|
||||||
await createRemoteEngineConfiguration(id);
|
await createRemoteEngineConfiguration(id);
|
||||||
const { remoteIpAddress, remoteUser, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
|
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
|
||||||
const host = `ssh://${remoteUser}@${remoteIpAddress}`
|
const host = `ssh://${remoteIpAddress}-remote`
|
||||||
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`);
|
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`);
|
||||||
if (!stdout) {
|
if (!stdout) {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
|
||||||
@ -215,8 +215,8 @@ export async function verifyRemoteDockerEngineFn(id: string) {
|
|||||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable coolify-infra`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable coolify-infra`);
|
||||||
}
|
}
|
||||||
if (isCoolifyProxyUsed) await startTraefikProxy(id);
|
if (isCoolifyProxyUsed) await startTraefikProxy(id);
|
||||||
const { stdout: daemonJson } = await executeSSHCmd({ dockerId: id, command: `cat /etc/docker/daemon.json` });
|
|
||||||
try {
|
try {
|
||||||
|
const { stdout: daemonJson } = await executeSSHCmd({ dockerId: id, command: `cat /etc/docker/daemon.json` });
|
||||||
let daemonJsonParsed = JSON.parse(daemonJson);
|
let daemonJsonParsed = JSON.parse(daemonJson);
|
||||||
let isUpdated = false;
|
let isUpdated = false;
|
||||||
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
|
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
|
||||||
|
@ -4,272 +4,22 @@ import { TraefikOtherConfiguration } from "./types";
|
|||||||
import { OnlyId } from "../../../types";
|
import { OnlyId } from "../../../types";
|
||||||
import { getTemplates } from "../../../lib/services";
|
import { getTemplates } from "../../../lib/services";
|
||||||
|
|
||||||
function generateLoadBalancerService(id, port) {
|
async function applicationConfiguration(traefik: any, remoteId: string | null = null) {
|
||||||
return {
|
console.log({ type: 'applications', remoteId })
|
||||||
loadbalancer: {
|
let applications = []
|
||||||
servers: [
|
if (remoteId) {
|
||||||
{
|
applications = await prisma.application.findMany({
|
||||||
url: `http://${id}:${port}`
|
where: { destinationDocker: { id: remoteId } },
|
||||||
}
|
include: { destinationDocker: true, settings: true }
|
||||||
]
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function generateHttpRouter(id, nakedDomain, pathPrefix) {
|
|
||||||
return {
|
|
||||||
entrypoints: ['web'],
|
|
||||||
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
|
||||||
service: `${id}`,
|
|
||||||
middlewares: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function generateProtocolRedirectRouter(id, nakedDomain, pathPrefix, fromTo) {
|
|
||||||
if (fromTo === 'https-to-http') {
|
|
||||||
return {
|
|
||||||
entrypoints: ['websecure'],
|
|
||||||
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
|
||||||
service: `${id}`,
|
|
||||||
tls: {
|
|
||||||
domains: {
|
|
||||||
main: `${nakedDomain}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
middlewares: ['redirect-to-http']
|
|
||||||
}
|
|
||||||
} else if (fromTo === 'http-to-https') {
|
|
||||||
return {
|
|
||||||
entrypoints: ['web'],
|
|
||||||
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
|
||||||
service: `${id}`,
|
|
||||||
middlewares: ['redirect-to-https']
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
function configureMiddleware(
|
|
||||||
{ id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type, isCustomSSL },
|
|
||||||
traefik
|
|
||||||
) {
|
|
||||||
if (isHttps) {
|
|
||||||
traefik.http.routers[id] = {
|
|
||||||
entrypoints: ['web'],
|
|
||||||
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/\`)`,
|
|
||||||
service: `${id}`,
|
|
||||||
middlewares: ['redirect-to-https']
|
|
||||||
};
|
|
||||||
|
|
||||||
traefik.http.services[id] = {
|
|
||||||
loadbalancer: {
|
|
||||||
servers: [
|
|
||||||
{
|
|
||||||
url: `http://${container}:${port}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (type === 'appwrite') {
|
|
||||||
traefik.http.routers[`${id}-realtime`] = {
|
|
||||||
entrypoints: ['websecure'],
|
|
||||||
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/v1/realtime\`)`,
|
|
||||||
service: `${`${id}-realtime`}`,
|
|
||||||
tls: {
|
|
||||||
domains: {
|
|
||||||
main: `${domain}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
middlewares: []
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
traefik.http.services[`${id}-realtime`] = {
|
|
||||||
loadbalancer: {
|
|
||||||
servers: [
|
|
||||||
{
|
|
||||||
url: `http://${container}-realtime:${port}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (isDualCerts) {
|
|
||||||
traefik.http.routers[`${id}-secure`] = {
|
|
||||||
entrypoints: ['websecure'],
|
|
||||||
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/\`)`,
|
|
||||||
service: `${id}`,
|
|
||||||
tls: isCustomSSL ? true : {
|
|
||||||
certresolver: 'letsencrypt'
|
|
||||||
},
|
|
||||||
middlewares: []
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
if (isWWW) {
|
applications = await prisma.application.findMany({
|
||||||
traefik.http.routers[`${id}-secure-www`] = {
|
|
||||||
entrypoints: ['websecure'],
|
|
||||||
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`/\`)`,
|
|
||||||
service: `${id}`,
|
|
||||||
tls: isCustomSSL ? true : {
|
|
||||||
certresolver: 'letsencrypt'
|
|
||||||
},
|
|
||||||
middlewares: []
|
|
||||||
};
|
|
||||||
traefik.http.routers[`${id}-secure`] = {
|
|
||||||
entrypoints: ['websecure'],
|
|
||||||
rule: `Host(\`${nakedDomain}\`) && PathPrefix(\`/\`)`,
|
|
||||||
service: `${id}`,
|
|
||||||
tls: {
|
|
||||||
domains: {
|
|
||||||
main: `${domain}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
middlewares: ['redirect-to-www']
|
|
||||||
};
|
|
||||||
traefik.http.routers[`${id}`].middlewares.push('redirect-to-www');
|
|
||||||
} else {
|
|
||||||
traefik.http.routers[`${id}-secure-www`] = {
|
|
||||||
entrypoints: ['websecure'],
|
|
||||||
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`/\`)`,
|
|
||||||
service: `${id}`,
|
|
||||||
tls: {
|
|
||||||
domains: {
|
|
||||||
main: `${domain}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
middlewares: ['redirect-to-non-www']
|
|
||||||
};
|
|
||||||
traefik.http.routers[`${id}-secure`] = {
|
|
||||||
entrypoints: ['websecure'],
|
|
||||||
rule: `Host(\`${domain}\`) && PathPrefix(\`/\`)`,
|
|
||||||
service: `${id}`,
|
|
||||||
tls: isCustomSSL ? true : {
|
|
||||||
certresolver: 'letsencrypt'
|
|
||||||
},
|
|
||||||
middlewares: []
|
|
||||||
};
|
|
||||||
traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
traefik.http.routers[id] = {
|
|
||||||
entrypoints: ['web'],
|
|
||||||
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/\`)`,
|
|
||||||
service: `${id}`,
|
|
||||||
middlewares: []
|
|
||||||
};
|
|
||||||
|
|
||||||
traefik.http.routers[`${id}-secure`] = {
|
|
||||||
entrypoints: ['websecure'],
|
|
||||||
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/\`)`,
|
|
||||||
service: `${id}`,
|
|
||||||
tls: {
|
|
||||||
domains: {
|
|
||||||
main: `${nakedDomain}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
middlewares: ['redirect-to-http']
|
|
||||||
};
|
|
||||||
|
|
||||||
traefik.http.services[id] = {
|
|
||||||
loadbalancer: {
|
|
||||||
servers: [
|
|
||||||
{
|
|
||||||
url: `http://${container}:${port}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (type === 'appwrite') {
|
|
||||||
traefik.http.routers[`${id}-realtime`] = {
|
|
||||||
entrypoints: ['web'],
|
|
||||||
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/v1/realtime\`)`,
|
|
||||||
service: `${id}-realtime`,
|
|
||||||
middlewares: []
|
|
||||||
};
|
|
||||||
traefik.http.services[`${id}-realtime`] = {
|
|
||||||
loadbalancer: {
|
|
||||||
servers: [
|
|
||||||
{
|
|
||||||
url: `http://${container}-realtime:${port}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDualCerts) {
|
|
||||||
if (isWWW) {
|
|
||||||
traefik.http.routers[`${id}`].middlewares.push('redirect-to-www');
|
|
||||||
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-www');
|
|
||||||
} else {
|
|
||||||
traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www');
|
|
||||||
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-non-www');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'plausibleanalytics' && scriptName && scriptName !== 'plausible.js') {
|
|
||||||
if (!traefik.http.routers[`${id}`].middlewares.includes(`${id}-redir`)) {
|
|
||||||
traefik.http.routers[`${id}`].middlewares.push(`${id}-redir`);
|
|
||||||
}
|
|
||||||
if (!traefik.http.routers[`${id}-secure`].middlewares.includes(`${id}-redir`)) {
|
|
||||||
traefik.http.routers[`${id}-secure`].middlewares.push(`${id}-redir`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function traefikConfiguration(request, reply) {
|
|
||||||
try {
|
|
||||||
const sslpath = '/etc/traefik/acme/custom';
|
|
||||||
const certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } } } } })
|
|
||||||
let parsedCertificates = []
|
|
||||||
for (const certificate of certificates) {
|
|
||||||
parsedCertificates.push({
|
|
||||||
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
|
||||||
keyFile: `${sslpath}/${certificate.id}-key.pem`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const traefik = {
|
|
||||||
tls: {
|
|
||||||
certificates: parsedCertificates
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
routers: {},
|
|
||||||
services: {},
|
|
||||||
middlewares: {
|
|
||||||
'redirect-to-https': {
|
|
||||||
redirectscheme: {
|
|
||||||
scheme: 'https'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'redirect-to-http': {
|
|
||||||
redirectscheme: {
|
|
||||||
scheme: 'http'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'redirect-to-non-www': {
|
|
||||||
redirectregex: {
|
|
||||||
regex: '^https?://www\\.(.+)',
|
|
||||||
replacement: 'http://${1}'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'redirect-to-www': {
|
|
||||||
redirectregex: {
|
|
||||||
regex: '^https?://(?:www\\.)?(.+)',
|
|
||||||
replacement: 'http://www.${1}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const applications = await prisma.application.findMany({
|
|
||||||
where: { destinationDocker: { remoteEngine: false } },
|
where: { destinationDocker: { remoteEngine: false } },
|
||||||
include: { destinationDocker: true, settings: true }
|
include: { destinationDocker: true, settings: true }
|
||||||
});
|
});
|
||||||
const data = {
|
}
|
||||||
applications: [],
|
const configurableApplications = []
|
||||||
services: [],
|
if (applications.length > 0) {
|
||||||
coolify: []
|
|
||||||
};
|
|
||||||
for (const application of applications) {
|
for (const application of applications) {
|
||||||
const {
|
const {
|
||||||
fqdn,
|
fqdn,
|
||||||
@ -294,7 +44,7 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
data.applications.push({
|
configurableApplications.push({
|
||||||
id: `${id}-${key}`,
|
id: `${id}-${key}`,
|
||||||
container: `${id}-${key}`,
|
container: `${id}-${key}`,
|
||||||
port: customPort ? customPort : port || 3000,
|
port: customPort ? customPort : port || 3000,
|
||||||
@ -304,7 +54,8 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
isDualCerts: dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL,
|
||||||
|
pathPrefix: '/'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,7 +68,7 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
data.applications.push({
|
configurableApplications.push({
|
||||||
id,
|
id,
|
||||||
container: id,
|
container: id,
|
||||||
port: port || 3000,
|
port: port || 3000,
|
||||||
@ -327,7 +78,8 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
isDualCerts: dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL,
|
||||||
|
pathPrefix: '/'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (previews) {
|
if (previews) {
|
||||||
@ -341,7 +93,7 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
for (const container of containers) {
|
for (const container of containers) {
|
||||||
const previewDomain = `${container.split('-')[1]}.${domain}`;
|
const previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||||
const nakedDomain = previewDomain.replace(/^www\./, '');
|
const nakedDomain = previewDomain.replace(/^www\./, '');
|
||||||
data.applications.push({
|
configurableApplications.push({
|
||||||
id: container,
|
id: container,
|
||||||
container,
|
container,
|
||||||
port: port || 3000,
|
port: port || 3000,
|
||||||
@ -351,7 +103,8 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
isDualCerts: dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL,
|
||||||
|
pathPrefix: '/'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -359,7 +112,103 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const services: any = await prisma.service.findMany({
|
for (const application of configurableApplications) {
|
||||||
|
let { id, port, isCustomSSL, pathPrefix, isHttps, nakedDomain, isWWW, domain, dualCerts } = application
|
||||||
|
if (isHttps) {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}`] = generateHttpRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix)
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`] = generateProtocolRedirectRouter(`${id}-${port || 'default'}-secure`, nakedDomain, pathPrefix, 'http-to-https')
|
||||||
|
traefik.http.services[`${id}-${port || 'default'}`] = generateLoadBalancerService(id, port)
|
||||||
|
if (dualCerts) {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`${pathPrefix}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: isCustomSSL ? true : {
|
||||||
|
certresolver: 'letsencrypt'
|
||||||
|
},
|
||||||
|
middlewares: []
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (isWWW) {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure-www`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: isCustomSSL ? true : {
|
||||||
|
certresolver: 'letsencrypt'
|
||||||
|
},
|
||||||
|
middlewares: []
|
||||||
|
};
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `Host(\`${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: {
|
||||||
|
domains: {
|
||||||
|
main: `${domain}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
middlewares: ['redirect-to-www']
|
||||||
|
};
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-www');
|
||||||
|
} else {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure-www`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: {
|
||||||
|
domains: {
|
||||||
|
main: `${domain}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
middlewares: ['redirect-to-non-www']
|
||||||
|
};
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `Host(\`${domain}\`) && PathPrefix(\`${pathPrefix}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: isCustomSSL ? true : {
|
||||||
|
certresolver: 'letsencrypt'
|
||||||
|
},
|
||||||
|
middlewares: []
|
||||||
|
};
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-non-www');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}`] = generateHttpRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix)
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`] = generateProtocolRedirectRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix, 'https-to-http')
|
||||||
|
traefik.http.services[`${id}-${port || 'default'}`] = generateLoadBalancerService(id, port)
|
||||||
|
|
||||||
|
if (!dualCerts) {
|
||||||
|
if (isWWW) {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-www');
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`].middlewares.push('redirect-to-www');
|
||||||
|
} else {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-non-www');
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`].middlewares.push('redirect-to-non-www');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function serviceConfiguration(traefik: any, remoteId: string | null = null) {
|
||||||
|
console.log({ type: 'services', remoteId })
|
||||||
|
let services = [];
|
||||||
|
if (remoteId) {
|
||||||
|
services = await prisma.service.findMany({
|
||||||
|
where: { destinationDocker: { id: remoteId } },
|
||||||
|
include: {
|
||||||
|
destinationDocker: true,
|
||||||
|
persistentStorage: true,
|
||||||
|
serviceSecret: true,
|
||||||
|
serviceSetting: true,
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: 'desc' }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
services = await prisma.service.findMany({
|
||||||
where: { destinationDocker: { remoteEngine: false } },
|
where: { destinationDocker: { remoteEngine: false } },
|
||||||
include: {
|
include: {
|
||||||
destinationDocker: true,
|
destinationDocker: true,
|
||||||
@ -369,7 +218,10 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const configurableServices = []
|
||||||
|
if (services.length > 0) {
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
let {
|
let {
|
||||||
fqdn,
|
fqdn,
|
||||||
@ -399,7 +251,7 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
configuration.port = foundPortVariable.value
|
configuration.port = foundPortVariable.value
|
||||||
}
|
}
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
data.services.push({
|
configurableServices.push({
|
||||||
id: oneService,
|
id: oneService,
|
||||||
publicPort,
|
publicPort,
|
||||||
fqdn,
|
fqdn,
|
||||||
@ -416,7 +268,7 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
port = foundPortVariable.value
|
port = foundPortVariable.value
|
||||||
}
|
}
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
data.services.push({
|
configurableServices.push({
|
||||||
id: oneService,
|
id: oneService,
|
||||||
configuration: {
|
configuration: {
|
||||||
port
|
port
|
||||||
@ -431,7 +283,8 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const service of data.services) {
|
|
||||||
|
for (const service of configurableServices) {
|
||||||
let { id, fqdn, dualCerts, configuration, isCustomSSL = false } = service
|
let { id, fqdn, dualCerts, configuration, isCustomSSL = false } = service
|
||||||
let port, pathPrefix, customDomain;
|
let port, pathPrefix, customDomain;
|
||||||
if (configuration) {
|
if (configuration) {
|
||||||
@ -523,14 +376,17 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function coolifyConfiguration(traefik: any) {
|
||||||
const { fqdn, dualCerts } = await prisma.setting.findFirst();
|
const { fqdn, dualCerts } = await prisma.setting.findFirst();
|
||||||
|
let coolifyConfigurations = []
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
data.coolify.push({
|
coolifyConfigurations.push({
|
||||||
id: isDev ? 'host.docker.internal' : 'coolify',
|
id: isDev ? 'host.docker.internal' : 'coolify',
|
||||||
container: isDev ? 'host.docker.internal' : 'coolify',
|
container: isDev ? 'host.docker.internal' : 'coolify',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
@ -538,39 +394,191 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
nakedDomain,
|
nakedDomain,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
isDualCerts: dualCerts
|
isDualCerts: dualCerts,
|
||||||
|
pathPrefix: '/'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (const application of data.applications) {
|
|
||||||
configureMiddleware(application, traefik);
|
|
||||||
}
|
|
||||||
// for (const service of data.services) {
|
|
||||||
// const { id, scriptName } = service;
|
|
||||||
|
|
||||||
// configureMiddleware(service, traefik);
|
|
||||||
// if (service.type === 'minio') {
|
|
||||||
// service.id = id + '-minio';
|
|
||||||
// service.container = id;
|
|
||||||
// service.domain = service.otherDomain;
|
|
||||||
// service.nakedDomain = service.otherNakedDomain;
|
|
||||||
// service.isHttps = service.otherIsHttps;
|
|
||||||
// service.isWWW = service.otherIsWWW;
|
|
||||||
// service.port = 9000;
|
|
||||||
// configureMiddleware(service, traefik);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (scriptName) {
|
for (const coolify of coolifyConfigurations) {
|
||||||
// traefik.http.middlewares[`${id}-redir`] = {
|
const { id, pathPrefix, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type, isCustomSSL } = coolify;
|
||||||
// replacepathregex: {
|
if (isHttps) {
|
||||||
// regex: `/js/${scriptName}`,
|
traefik.http.routers[`${id}-${port || 'default'}`] = generateHttpRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix)
|
||||||
// replacement: '/js/plausible.js'
|
traefik.http.routers[`${id}-${port || 'default'}-secure`] = generateProtocolRedirectRouter(`${id}-${port || 'default'}-secure`, nakedDomain, pathPrefix, 'http-to-https')
|
||||||
// }
|
traefik.http.services[`${id}-${port || 'default'}`] = generateLoadBalancerService(id, port)
|
||||||
// };
|
if (dualCerts) {
|
||||||
// }
|
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
|
||||||
// }
|
entrypoints: ['websecure'],
|
||||||
for (const coolify of data.coolify) {
|
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`${pathPrefix}\`)`,
|
||||||
configureMiddleware(coolify, traefik);
|
service: `${id}`,
|
||||||
|
tls: isCustomSSL ? true : {
|
||||||
|
certresolver: 'letsencrypt'
|
||||||
|
},
|
||||||
|
middlewares: []
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (isWWW) {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure-www`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: isCustomSSL ? true : {
|
||||||
|
certresolver: 'letsencrypt'
|
||||||
|
},
|
||||||
|
middlewares: []
|
||||||
|
};
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `Host(\`${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: {
|
||||||
|
domains: {
|
||||||
|
main: `${domain}`
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
middlewares: ['redirect-to-www']
|
||||||
|
};
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-www');
|
||||||
|
} else {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure-www`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: {
|
||||||
|
domains: {
|
||||||
|
main: `${domain}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
middlewares: ['redirect-to-non-www']
|
||||||
|
};
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `Host(\`${domain}\`) && PathPrefix(\`${pathPrefix}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: isCustomSSL ? true : {
|
||||||
|
certresolver: 'letsencrypt'
|
||||||
|
},
|
||||||
|
middlewares: []
|
||||||
|
};
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-non-www');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}`] = generateHttpRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix)
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`] = generateProtocolRedirectRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix, 'https-to-http')
|
||||||
|
traefik.http.services[`${id}-${port || 'default'}`] = generateLoadBalancerService(id, port)
|
||||||
|
|
||||||
|
if (!dualCerts) {
|
||||||
|
if (isWWW) {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-www');
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`].middlewares.push('redirect-to-www');
|
||||||
|
} else {
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-non-www');
|
||||||
|
traefik.http.routers[`${id}-${port || 'default'}-secure`].middlewares.push('redirect-to-non-www');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function generateLoadBalancerService(id, port) {
|
||||||
|
return {
|
||||||
|
loadbalancer: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `http://${id}:${port}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function generateHttpRouter(id, nakedDomain, pathPrefix) {
|
||||||
|
return {
|
||||||
|
entrypoints: ['web'],
|
||||||
|
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
||||||
|
service: `${id}`,
|
||||||
|
middlewares: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function generateProtocolRedirectRouter(id, nakedDomain, pathPrefix, fromTo) {
|
||||||
|
if (fromTo === 'https-to-http') {
|
||||||
|
return {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: {
|
||||||
|
domains: {
|
||||||
|
main: `${nakedDomain}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
middlewares: ['redirect-to-http']
|
||||||
|
}
|
||||||
|
} else if (fromTo === 'http-to-https') {
|
||||||
|
return {
|
||||||
|
entrypoints: ['web'],
|
||||||
|
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
||||||
|
service: `${id}`,
|
||||||
|
middlewares: ['redirect-to-https']
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export async function traefikConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
|
||||||
|
try {
|
||||||
|
const { id = null } = request.params
|
||||||
|
const sslpath = '/etc/traefik/acme/custom';
|
||||||
|
|
||||||
|
let certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } } } } })
|
||||||
|
|
||||||
|
if (remote) {
|
||||||
|
certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } })
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsedCertificates = []
|
||||||
|
for (const certificate of certificates) {
|
||||||
|
parsedCertificates.push({
|
||||||
|
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
||||||
|
keyFile: `${sslpath}/${certificate.id}-key.pem`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const traefik = {
|
||||||
|
tls: {
|
||||||
|
certificates: parsedCertificates
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
routers: {},
|
||||||
|
services: {},
|
||||||
|
middlewares: {
|
||||||
|
'redirect-to-https': {
|
||||||
|
redirectscheme: {
|
||||||
|
scheme: 'https'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'redirect-to-http': {
|
||||||
|
redirectscheme: {
|
||||||
|
scheme: 'http'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'redirect-to-non-www': {
|
||||||
|
redirectregex: {
|
||||||
|
regex: '^https?://www\\.(.+)',
|
||||||
|
replacement: 'http://${1}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'redirect-to-www': {
|
||||||
|
redirectregex: {
|
||||||
|
regex: '^https?://(?:www\\.)?(.+)',
|
||||||
|
replacement: 'http://www.${1}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await applicationConfiguration(traefik, id)
|
||||||
|
await serviceConfiguration(traefik, id)
|
||||||
|
if (!remote) {
|
||||||
|
await coolifyConfiguration(traefik)
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(traefik.http.routers).length === 0) {
|
if (Object.keys(traefik.http.routers).length === 0) {
|
||||||
traefik.http.routers = null;
|
traefik.http.routers = null;
|
||||||
}
|
}
|
||||||
@ -584,7 +592,6 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function traefikOtherConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
|
export async function traefikOtherConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.query
|
const { id } = request.query
|
||||||
@ -705,273 +712,3 @@ export async function traefikOtherConfiguration(request: FastifyRequest<TraefikO
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remoteTraefikConfiguration(request: FastifyRequest<OnlyId>) {
|
|
||||||
const { id } = request.params
|
|
||||||
try {
|
|
||||||
const sslpath = '/etc/traefik/acme/custom';
|
|
||||||
const certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } })
|
|
||||||
let parsedCertificates = []
|
|
||||||
for (const certificate of certificates) {
|
|
||||||
parsedCertificates.push({
|
|
||||||
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
|
||||||
keyFile: `${sslpath}/${certificate.id}-key.pem`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const traefik = {
|
|
||||||
tls: {
|
|
||||||
certificates: parsedCertificates
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
routers: {},
|
|
||||||
services: {},
|
|
||||||
middlewares: {
|
|
||||||
'redirect-to-https': {
|
|
||||||
redirectscheme: {
|
|
||||||
scheme: 'https'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'redirect-to-http': {
|
|
||||||
redirectscheme: {
|
|
||||||
scheme: 'http'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'redirect-to-non-www': {
|
|
||||||
redirectregex: {
|
|
||||||
regex: '^https?://www\\.(.+)',
|
|
||||||
replacement: 'http://${1}'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'redirect-to-www': {
|
|
||||||
redirectregex: {
|
|
||||||
regex: '^https?://(?:www\\.)?(.+)',
|
|
||||||
replacement: 'http://www.${1}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const applications = await prisma.application.findMany({
|
|
||||||
where: { destinationDocker: { id } },
|
|
||||||
include: { destinationDocker: true, settings: true }
|
|
||||||
});
|
|
||||||
const data = {
|
|
||||||
applications: [],
|
|
||||||
services: [],
|
|
||||||
coolify: []
|
|
||||||
};
|
|
||||||
for (const application of applications) {
|
|
||||||
const {
|
|
||||||
fqdn,
|
|
||||||
id,
|
|
||||||
port,
|
|
||||||
buildPack,
|
|
||||||
dockerComposeConfiguration,
|
|
||||||
destinationDocker,
|
|
||||||
destinationDockerId,
|
|
||||||
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\./, '');
|
|
||||||
const isHttps = fqdn.startsWith('https://');
|
|
||||||
const isWWW = fqdn.includes('www.');
|
|
||||||
if (isRunning) {
|
|
||||||
data.applications.push({
|
|
||||||
id,
|
|
||||||
container: id,
|
|
||||||
port: port || 3000,
|
|
||||||
domain,
|
|
||||||
nakedDomain,
|
|
||||||
isRunning,
|
|
||||||
isHttps,
|
|
||||||
isWWW,
|
|
||||||
isDualCerts: dualCerts,
|
|
||||||
isCustomSSL
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (previews) {
|
|
||||||
const { stdout } = await executeDockerCmd({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
|
|
||||||
const containers = stdout
|
|
||||||
.trim()
|
|
||||||
.split('\n')
|
|
||||||
.filter((a) => a)
|
|
||||||
.map((c) => c.replace(/"/g, ''));
|
|
||||||
if (containers.length > 0) {
|
|
||||||
for (const container of containers) {
|
|
||||||
const previewDomain = `${container.split('-')[1]}.${domain}`;
|
|
||||||
const nakedDomain = previewDomain.replace(/^www\./, '');
|
|
||||||
data.applications.push({
|
|
||||||
id: container,
|
|
||||||
container,
|
|
||||||
port: port || 3000,
|
|
||||||
domain: previewDomain,
|
|
||||||
isRunning,
|
|
||||||
nakedDomain,
|
|
||||||
isHttps,
|
|
||||||
isWWW,
|
|
||||||
isDualCerts: dualCerts,
|
|
||||||
isCustomSSL
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const services: any = await prisma.service.findMany({
|
|
||||||
where: { destinationDocker: { id } },
|
|
||||||
include: {
|
|
||||||
destinationDocker: true,
|
|
||||||
persistentStorage: true,
|
|
||||||
serviceSecret: true,
|
|
||||||
serviceSetting: true,
|
|
||||||
},
|
|
||||||
orderBy: { createdAt: 'desc' }
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const service of services) {
|
|
||||||
const {
|
|
||||||
fqdn,
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
destinationDocker,
|
|
||||||
destinationDockerId,
|
|
||||||
dualCerts,
|
|
||||||
plausibleAnalytics
|
|
||||||
} = service;
|
|
||||||
if (destinationDockerId) {
|
|
||||||
const templates = await getTemplates();
|
|
||||||
let found = templates.find((a) => a.type === type);
|
|
||||||
if (found) {
|
|
||||||
const port = found.ports.main;
|
|
||||||
const publicPort = service[type]?.publicPort;
|
|
||||||
const isRunning = true;
|
|
||||||
if (fqdn) {
|
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
|
||||||
const isHttps = fqdn.startsWith('https://');
|
|
||||||
const isWWW = fqdn.includes('www.');
|
|
||||||
if (isRunning) {
|
|
||||||
// Plausible Analytics custom script
|
|
||||||
let scriptName = false;
|
|
||||||
if (type === 'plausibleanalytics' && plausibleAnalytics.scriptName !== 'plausible.js') {
|
|
||||||
scriptName = plausibleAnalytics.scriptName;
|
|
||||||
}
|
|
||||||
|
|
||||||
let container = id;
|
|
||||||
let otherDomain = null;
|
|
||||||
let otherNakedDomain = null;
|
|
||||||
let otherIsHttps = null;
|
|
||||||
let otherIsWWW = null;
|
|
||||||
|
|
||||||
// if (type === 'minio' && service.minio.apiFqdn) {
|
|
||||||
// otherDomain = getDomain(service.minio.apiFqdn);
|
|
||||||
// otherNakedDomain = otherDomain.replace(/^www\./, '');
|
|
||||||
// otherIsHttps = service.minio.apiFqdn.startsWith('https://');
|
|
||||||
// otherIsWWW = service.minio.apiFqdn.includes('www.');
|
|
||||||
// }
|
|
||||||
if (type === 'minio') {
|
|
||||||
const domain = service.serviceSetting.find((a) => a.name === 'MINIO_SERVER_URL')?.value
|
|
||||||
otherDomain = getDomain(domain);
|
|
||||||
otherNakedDomain = otherDomain.replace(/^www\./, '');
|
|
||||||
otherIsHttps = domain.startsWith('https://');
|
|
||||||
otherIsWWW = domain.includes('www.');
|
|
||||||
}
|
|
||||||
data.services.push({
|
|
||||||
id,
|
|
||||||
container,
|
|
||||||
type,
|
|
||||||
otherDomain,
|
|
||||||
otherNakedDomain,
|
|
||||||
otherIsHttps,
|
|
||||||
otherIsWWW,
|
|
||||||
port,
|
|
||||||
publicPort,
|
|
||||||
domain,
|
|
||||||
nakedDomain,
|
|
||||||
isRunning,
|
|
||||||
isHttps,
|
|
||||||
isWWW,
|
|
||||||
isDualCerts: dualCerts,
|
|
||||||
scriptName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (const application of data.applications) {
|
|
||||||
configureMiddleware(application, traefik);
|
|
||||||
}
|
|
||||||
for (const service of data.services) {
|
|
||||||
const { id, scriptName } = service;
|
|
||||||
|
|
||||||
configureMiddleware(service, traefik);
|
|
||||||
if (service.type === 'minio') {
|
|
||||||
service.id = id + '-minio';
|
|
||||||
service.container = id;
|
|
||||||
service.domain = service.otherDomain;
|
|
||||||
service.nakedDomain = service.otherNakedDomain;
|
|
||||||
service.isHttps = service.otherIsHttps;
|
|
||||||
service.isWWW = service.otherIsWWW;
|
|
||||||
service.port = 9000;
|
|
||||||
configureMiddleware(service, traefik);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scriptName) {
|
|
||||||
traefik.http.middlewares[`${id}-redir`] = {
|
|
||||||
replacepathregex: {
|
|
||||||
regex: `/js/${scriptName}`,
|
|
||||||
replacement: '/js/plausible.js'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const coolify of data.coolify) {
|
|
||||||
configureMiddleware(coolify, traefik);
|
|
||||||
}
|
|
||||||
if (Object.keys(traefik.http.routers).length === 0) {
|
|
||||||
traefik.http.routers = null;
|
|
||||||
}
|
|
||||||
if (Object.keys(traefik.http.services).length === 0) {
|
|
||||||
traefik.http.services = null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...traefik
|
|
||||||
}
|
|
||||||
} catch ({ status, message }) {
|
|
||||||
return errorHandler({ status, message })
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,13 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { OnlyId } from '../../../types';
|
import { OnlyId } from '../../../types';
|
||||||
import { remoteTraefikConfiguration, traefikConfiguration, traefikOtherConfiguration } from './handlers';
|
import { traefikConfiguration, traefikOtherConfiguration } from './handlers';
|
||||||
import { TraefikOtherConfiguration } from './types';
|
import { TraefikOtherConfiguration } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply));
|
fastify.get<OnlyId>('/main.json', async (request, reply) => traefikConfiguration(request, false));
|
||||||
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
|
fastify.get<OnlyId>('/remote/:id', async (request) => traefikConfiguration(request, true));
|
||||||
|
|
||||||
fastify.get<OnlyId>('/remote/:id', async (request) => remoteTraefikConfiguration(request));
|
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export interface OnlyId {
|
export interface OnlyId {
|
||||||
Params: { id: string },
|
Params: { id?: string },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
export let type = 'info';
|
export let type = 'info';
|
||||||
function success() {
|
function success() {
|
||||||
if (type === 'success') {
|
if (type === 'success') {
|
||||||
return 'bg-coollabs';
|
return 'bg-dark lg:bg-primary';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
{#if $toasts.length > 0}
|
{#if $toasts.length > 0}
|
||||||
<section>
|
<section>
|
||||||
<article class="toast toast-top toast-center rounded-none min-w-[18rem]" role="alert">
|
<article class="toast toast-top toast-center rounded-none w-2/3 lg:w-[20rem]" role="alert">
|
||||||
{#each $toasts as toast (toast.id)}
|
{#each $toasts as toast (toast.id)}
|
||||||
<Toast
|
<Toast
|
||||||
type={toast.type}
|
type={toast.type}
|
||||||
|
@ -68,12 +68,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full relative p-5 ">
|
<div class="w-full relative p-5 ">
|
||||||
{#if loading.usage}
|
|
||||||
<span class="indicator-item badge bg-yellow-500 badge-sm" />
|
|
||||||
{:else}
|
|
||||||
<span class="indicator-item badge bg-success badge-sm" />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="w-full flex flex-col lg:flex-row space-y-4 lg:space-y-0 space-x-4">
|
<div class="w-full flex flex-col lg:flex-row space-y-4 lg:space-y-0 space-x-4">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<h1 class="font-bold text-lg lg:text-xl truncate">
|
<h1 class="font-bold text-lg lg:text-xl truncate">
|
||||||
@ -99,6 +93,11 @@
|
|||||||
class="btn btn-sm">Cleanup Storage</button
|
class="btn btn-sm">Cleanup Storage</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if loading.usage}
|
||||||
|
<button id="streaming" class=" btn btn-sm bg-transparent border-none loading"
|
||||||
|
>Getting data...</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex lg:flex-row flex-col gap-4">
|
<div class="flex lg:flex-row flex-col gap-4">
|
||||||
<div class="flex lg:flex-row flex-col space-x-0 lg:space-x-2 space-y-2 lg:space-y-0" />
|
<div class="flex lg:flex-row flex-col space-x-0 lg:space-x-2 space-y-2 lg:space-y-0" />
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
|
|
||||||
let statusInterval: any;
|
let statusInterval: any;
|
||||||
let forceDelete = false;
|
let forceDelete = false;
|
||||||
|
let stopping = false;
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
||||||
|
|
||||||
@ -138,17 +138,17 @@
|
|||||||
}
|
}
|
||||||
async function stopApplication() {
|
async function stopApplication() {
|
||||||
try {
|
try {
|
||||||
$status.application.initialLoading = true;
|
stopping = true;
|
||||||
await post(`/applications/${id}/stop`, {});
|
await post(`/applications/${id}/stop`, {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
$status.application.initialLoading = false;
|
stopping = false;
|
||||||
await getStatus();
|
await getStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getStatus() {
|
async function getStatus() {
|
||||||
if ($status.application.loading) return;
|
if ($status.application.loading && stopping) return;
|
||||||
$status.application.loading = true;
|
$status.application.loading = true;
|
||||||
const data = await get(`/applications/${id}/status`);
|
const data = await get(`/applications/${id}/status`);
|
||||||
|
|
||||||
@ -298,7 +298,29 @@
|
|||||||
Application Error
|
Application Error
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $status.application.initialLoading}
|
{#if stopping}
|
||||||
|
<button class="btn btn-ghost btn-sm gap-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6 animate-spin duration-500 ease-in-out"
|
||||||
|
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="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
||||||
|
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
||||||
|
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
||||||
|
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
||||||
|
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
||||||
|
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
||||||
|
</svg>
|
||||||
|
Stopping...
|
||||||
|
</button>
|
||||||
|
{:else if $status.application.initialLoading}
|
||||||
<button class="btn btn-ghost btn-sm gap-2">
|
<button class="btn btn-ghost btn-sm gap-2">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -321,7 +343,6 @@
|
|||||||
Loading...
|
Loading...
|
||||||
</button>
|
</button>
|
||||||
{:else if $status.application.overallStatus === 'healthy'}
|
{:else if $status.application.overallStatus === 'healthy'}
|
||||||
|
|
||||||
{#if application.buildPack !== 'compose'}
|
{#if application.buildPack !== 'compose'}
|
||||||
<button
|
<button
|
||||||
on:click={restartApplication}
|
on:click={restartApplication}
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
<div class="flex justify-center px-6 pb-8">
|
<div class="flex justify-center px-6 pb-8">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
<div class="flex items-start lg:items-center space-x-0 lg:space-x-4 pb-5 flex-col space-y-4 lg:space-y-0">
|
<div class="flex items-start lg:items-center space-x-0 lg:space-x-4 pb-5 flex-col lg:flex-row space-y-4 lg:space-y-0">
|
||||||
<div class="title font-bold">{$t('forms.configuration')}</div>
|
<div class="title font-bold">{$t('forms.configuration')}</div>
|
||||||
<button type="submit" class="btn btn-sm bg-destinations w-full lg:w-fit" class:loading disabled={loading}
|
<button type="submit" class="btn btn-sm bg-destinations w-full lg:w-fit" class:loading disabled={loading}
|
||||||
>{loading
|
>{loading
|
||||||
|
@ -184,15 +184,17 @@
|
|||||||
? 'Verify Remote Docker Engine'
|
? 'Verify Remote Docker Engine'
|
||||||
: 'Check Remote Docker Engine'}</button
|
: 'Check Remote Docker Engine'}</button
|
||||||
>
|
>
|
||||||
|
{#if destination.remoteVerified}
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm"
|
class="btn btn-sm"
|
||||||
class:loading={loading.restart}
|
class:loading={loading.restart}
|
||||||
class:bg-error={!loading.restart}
|
class:bg-error={!loading.restart}
|
||||||
disabled={loading.restart}
|
disabled={loading.restart}
|
||||||
on:click|preventDefault={forceRestartProxy}>{$t('destination.force_restart_proxy')}</button
|
on:click|preventDefault={forceRestartProxy}
|
||||||
|
>{$t('destination.force_restart_proxy')}</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10 ">
|
<div class="grid grid-cols-2 items-center px-10 ">
|
||||||
<label for="name">{$t('forms.name')}</label>
|
<label for="name">{$t('forms.name')}</label>
|
||||||
|
Loading…
Reference in New Issue
Block a user