wip: docker compose
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "dockerComposeFile" TEXT;
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "dockerComposeFileLocation" TEXT;
|
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "dockerComposeConfiguration" TEXT;
|
@@ -94,43 +94,46 @@ model TeamInvitation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Application {
|
model Application {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
fqdn String?
|
fqdn String?
|
||||||
repository String?
|
repository String?
|
||||||
configHash String?
|
configHash String?
|
||||||
branch String?
|
branch String?
|
||||||
buildPack String?
|
buildPack String?
|
||||||
projectId Int?
|
projectId Int?
|
||||||
port Int?
|
port Int?
|
||||||
exposePort Int?
|
exposePort Int?
|
||||||
installCommand String?
|
installCommand String?
|
||||||
buildCommand String?
|
buildCommand String?
|
||||||
startCommand String?
|
startCommand String?
|
||||||
baseDirectory String?
|
baseDirectory String?
|
||||||
publishDirectory String?
|
publishDirectory String?
|
||||||
deploymentType String?
|
deploymentType String?
|
||||||
phpModules String?
|
phpModules String?
|
||||||
pythonWSGI String?
|
pythonWSGI String?
|
||||||
pythonModule String?
|
pythonModule String?
|
||||||
pythonVariable String?
|
pythonVariable String?
|
||||||
dockerFileLocation String?
|
dockerFileLocation String?
|
||||||
denoMainFile String?
|
denoMainFile String?
|
||||||
denoOptions String?
|
denoOptions String?
|
||||||
createdAt DateTime @default(now())
|
dockerComposeFile String?
|
||||||
updatedAt DateTime @updatedAt
|
dockerComposeFileLocation String?
|
||||||
destinationDockerId String?
|
dockerComposeConfiguration String?
|
||||||
gitSourceId String?
|
createdAt DateTime @default(now())
|
||||||
baseImage String?
|
updatedAt DateTime @updatedAt
|
||||||
baseBuildImage String?
|
destinationDockerId String?
|
||||||
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
gitSourceId String?
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
baseImage String?
|
||||||
persistentStorage ApplicationPersistentStorage[]
|
baseBuildImage String?
|
||||||
settings ApplicationSettings?
|
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
||||||
secrets Secret[]
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
teams Team[]
|
persistentStorage ApplicationPersistentStorage[]
|
||||||
connectedDatabase ApplicationConnectedDatabase?
|
settings ApplicationSettings?
|
||||||
previewApplication PreviewApplication[]
|
secrets Secret[]
|
||||||
|
teams Team[]
|
||||||
|
connectedDatabase ApplicationConnectedDatabase?
|
||||||
|
previewApplication PreviewApplication[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model PreviewApplication {
|
model PreviewApplication {
|
||||||
|
@@ -212,17 +212,37 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
||||||
|
const labels = makeLabelForStandaloneApplication({
|
||||||
|
applicationId,
|
||||||
|
fqdn,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
pullmergeRequestId,
|
||||||
|
buildPack,
|
||||||
|
repository,
|
||||||
|
branch,
|
||||||
|
projectId,
|
||||||
|
port: exposePort ? `${exposePort}:${port}` : port,
|
||||||
|
commit,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
publishDirectory
|
||||||
|
});
|
||||||
if (forceRebuild) deployNeeded = true
|
if (forceRebuild) deployNeeded = true
|
||||||
if (!imageFound || deployNeeded) {
|
if (!imageFound || deployNeeded) {
|
||||||
// if (true) {
|
|
||||||
if (buildpacks[buildPack])
|
if (buildpacks[buildPack])
|
||||||
await buildpacks[buildPack]({
|
await buildpacks[buildPack]({
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
|
network: destinationDocker.network,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
domain,
|
domain,
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
volumes,
|
||||||
|
labels,
|
||||||
pullmergeRequestId,
|
pullmergeRequestId,
|
||||||
buildPack,
|
buildPack,
|
||||||
repository,
|
repository,
|
||||||
@@ -248,7 +268,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
denoOptions,
|
denoOptions,
|
||||||
baseImage,
|
baseImage,
|
||||||
baseBuildImage,
|
baseBuildImage,
|
||||||
deploymentType
|
deploymentType,
|
||||||
});
|
});
|
||||||
else {
|
else {
|
||||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||||
@@ -257,112 +277,137 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
} else {
|
} else {
|
||||||
await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
|
await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker stop -t 0 ${imageId}` })
|
if (buildPack === 'compose') {
|
||||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker rm ${imageId}` })
|
try {
|
||||||
} catch (error) {
|
await executeDockerCmd({
|
||||||
//
|
dockerId: destinationDockerId,
|
||||||
}
|
command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0`
|
||||||
const envs = [
|
})
|
||||||
`PORT=${port}`
|
await executeDockerCmd({
|
||||||
];
|
dockerId: destinationDockerId,
|
||||||
if (secrets.length > 0) {
|
command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
|
||||||
secrets.forEach((secret) => {
|
})
|
||||||
if (pullmergeRequestId) {
|
} catch (error) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
//
|
||||||
if (isSecretFound.length > 0) {
|
}
|
||||||
envs.push(`${secret.name}=${isSecretFound[0].value}`);
|
try {
|
||||||
} else {
|
await executeDockerCmd({ debug, buildId, applicationId, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||||
}
|
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||||
} else {
|
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
||||||
if (!secret.isPRMRSecret) {
|
await prisma.application.update({
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
where: { id: applicationId },
|
||||||
}
|
data: { configHash: currentHash }
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await saveBuildLog({ line: error, buildId, applicationId });
|
||||||
|
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
||||||
|
if (foundBuild) {
|
||||||
|
await prisma.build.update({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: {
|
||||||
|
status: 'failed'
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
|
||||||
const labels = makeLabelForStandaloneApplication({
|
} else {
|
||||||
applicationId,
|
try {
|
||||||
fqdn,
|
await executeDockerCmd({
|
||||||
name,
|
dockerId: destinationDockerId,
|
||||||
type,
|
command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0`
|
||||||
pullmergeRequestId,
|
})
|
||||||
buildPack,
|
await executeDockerCmd({
|
||||||
repository,
|
dockerId: destinationDockerId,
|
||||||
branch,
|
command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
|
||||||
projectId,
|
})
|
||||||
port: exposePort ? `${exposePort}:${port}` : port,
|
} catch (error) {
|
||||||
commit,
|
//
|
||||||
installCommand,
|
}
|
||||||
buildCommand,
|
const envs = [
|
||||||
startCommand,
|
`PORT=${port}`
|
||||||
baseDirectory,
|
];
|
||||||
publishDirectory
|
if (secrets.length > 0) {
|
||||||
});
|
secrets.forEach((secret) => {
|
||||||
let envFound = false;
|
if (pullmergeRequestId) {
|
||||||
try {
|
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
if (isSecretFound.length > 0) {
|
||||||
} catch (error) {
|
envs.push(`${secret.name}=${isSecretFound[0].value}`);
|
||||||
//
|
} else {
|
||||||
}
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
try {
|
}
|
||||||
await saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
} else {
|
||||||
const composeVolumes = volumes.map((volume) => {
|
if (!secret.isPRMRSecret) {
|
||||||
return {
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
[`${volume.split(':')[0]}`]: {
|
}
|
||||||
name: volume.split(':')[0]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const composeFile = {
|
|
||||||
version: '3.8',
|
|
||||||
services: {
|
|
||||||
[imageId]: {
|
|
||||||
image: `${applicationId}:${tag}`,
|
|
||||||
container_name: imageId,
|
|
||||||
volumes,
|
|
||||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
|
||||||
labels,
|
|
||||||
depends_on: [],
|
|
||||||
expose: [port],
|
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
|
||||||
// logging: {
|
|
||||||
// driver: 'fluentd',
|
|
||||||
// },
|
|
||||||
...defaultComposeConfiguration(destinationDocker.network),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
networks: {
|
|
||||||
[destinationDocker.network]: {
|
|
||||||
external: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
volumes: Object.assign({}, ...composeVolumes)
|
|
||||||
};
|
|
||||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
|
||||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
|
||||||
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
|
||||||
} catch (error) {
|
|
||||||
await saveBuildLog({ line: error, buildId, applicationId });
|
|
||||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
|
||||||
if (foundBuild) {
|
|
||||||
await prisma.build.update({
|
|
||||||
where: { id: buildId },
|
|
||||||
data: {
|
|
||||||
status: 'failed'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw new Error(error);
|
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||||
|
|
||||||
|
let envFound = false;
|
||||||
|
try {
|
||||||
|
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
||||||
|
const composeVolumes = volumes.map((volume) => {
|
||||||
|
return {
|
||||||
|
[`${volume.split(':')[0]}`]: {
|
||||||
|
name: volume.split(':')[0]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const composeFile = {
|
||||||
|
version: '3.8',
|
||||||
|
services: {
|
||||||
|
[imageId]: {
|
||||||
|
image: `${applicationId}:${tag}`,
|
||||||
|
container_name: imageId,
|
||||||
|
volumes,
|
||||||
|
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||||
|
labels,
|
||||||
|
depends_on: [],
|
||||||
|
expose: [port],
|
||||||
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
|
...defaultComposeConfiguration(destinationDocker.network),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
networks: {
|
||||||
|
[destinationDocker.network]: {
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
volumes: Object.assign({}, ...composeVolumes)
|
||||||
|
};
|
||||||
|
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
||||||
|
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||||
|
} catch (error) {
|
||||||
|
await saveBuildLog({ line: error, buildId, applicationId });
|
||||||
|
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
||||||
|
if (foundBuild) {
|
||||||
|
await prisma.build.update({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: {
|
||||||
|
status: 'failed'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||||
|
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
||||||
|
if (!pullmergeRequestId) await prisma.application.update({
|
||||||
|
where: { id: applicationId },
|
||||||
|
data: { configHash: currentHash }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
|
||||||
if (!pullmergeRequestId) await prisma.application.update({
|
|
||||||
where: { id: applicationId },
|
|
||||||
data: { configHash: currentHash }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
@@ -634,6 +634,7 @@ export function makeLabelForStandaloneApplication({
|
|||||||
return [
|
return [
|
||||||
'coolify.managed=true',
|
'coolify.managed=true',
|
||||||
`coolify.version=${version}`,
|
`coolify.version=${version}`,
|
||||||
|
`coolify.applicationId=${applicationId}`,
|
||||||
`coolify.type=standalone-application`,
|
`coolify.type=standalone-application`,
|
||||||
`coolify.configuration=${base64Encode(
|
`coolify.configuration=${base64Encode(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
@@ -1,34 +1,99 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { executeDockerCmd } from '../common';
|
import { defaultComposeConfiguration, executeDockerCmd } from '../common';
|
||||||
import { buildImage } from './common';
|
import { buildImage, saveBuildLog } from './common';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
|
import { getSecrets } from '../../routes/api/v1/applications/handlers';
|
||||||
|
|
||||||
export default async function (data) {
|
export default async function (data) {
|
||||||
let {
|
let {
|
||||||
applicationId,
|
applicationId,
|
||||||
|
debug,
|
||||||
|
buildId,
|
||||||
dockerId,
|
dockerId,
|
||||||
debug,
|
network,
|
||||||
tag,
|
volumes,
|
||||||
workdir,
|
labels,
|
||||||
buildId,
|
workdir,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
secrets,
|
secrets,
|
||||||
pullmergeRequestId,
|
pullmergeRequestId,
|
||||||
dockerFileLocation
|
port
|
||||||
} = data
|
} = data
|
||||||
const file = `${workdir}${baseDirectory}/docker-compose.yml`;
|
const fileYml = `${workdir}${baseDirectory}/docker-compose.yml`;
|
||||||
const dockerComposeRaw = await fs.readFile(`${file}`, 'utf8')
|
const fileYaml = `${workdir}${baseDirectory}/docker-compose.yaml`;
|
||||||
|
let dockerComposeRaw = null;
|
||||||
|
let isYml = false;
|
||||||
|
try {
|
||||||
|
dockerComposeRaw = await fs.readFile(`${fileYml}`, 'utf8')
|
||||||
|
isYml = true
|
||||||
|
} catch (error) { }
|
||||||
|
try {
|
||||||
|
dockerComposeRaw = await fs.readFile(`${fileYaml}`, 'utf8')
|
||||||
|
} catch (error) { }
|
||||||
|
|
||||||
|
if (!dockerComposeRaw) {
|
||||||
|
throw ('docker-compose.yml or docker-compose.yaml are not found!');
|
||||||
|
}
|
||||||
const dockerComposeYaml = yaml.load(dockerComposeRaw)
|
const dockerComposeYaml = yaml.load(dockerComposeRaw)
|
||||||
if (!dockerComposeYaml.services) {
|
if (!dockerComposeYaml.services) {
|
||||||
throw 'No Services found in docker-compose file.'
|
throw 'No Services found in docker-compose file.'
|
||||||
}
|
}
|
||||||
|
const envs = [
|
||||||
|
`PORT=${port}`
|
||||||
|
];
|
||||||
|
if (getSecrets.length > 0) {
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
if (secret.isBuildSecret) {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||||
|
if (isSecretFound.length > 0) {
|
||||||
|
envs.push(`${secret.name}=${isSecretFound[0].value}`);
|
||||||
|
} else {
|
||||||
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||||
|
let envFound = false;
|
||||||
|
try {
|
||||||
|
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
const composeVolumes = volumes.map((volume) => {
|
||||||
|
return {
|
||||||
|
[`${volume.split(':')[0]}`]: {
|
||||||
|
name: volume.split(':')[0]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
let networks = {}
|
||||||
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
||||||
value['container_name'] = `${applicationId}-${key}`
|
value['container_name'] = `${applicationId}-${key}`
|
||||||
console.log({key, value});
|
value['env_file'] = envFound ? [`${workdir}/.env`] : []
|
||||||
|
value['labels'] = labels
|
||||||
|
value['volumes'] = volumes
|
||||||
|
if (value['networks']?.length > 0) {
|
||||||
|
value['networks'].forEach((network) => {
|
||||||
|
networks[network] = {
|
||||||
|
name: network
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value['networks'] = [...value['networks'] || '', network]
|
||||||
|
dockerComposeYaml.services[key] = { ...dockerComposeYaml.services[key], restart: defaultComposeConfiguration(network).restart, deploy: defaultComposeConfiguration(network).deploy }
|
||||||
}
|
}
|
||||||
|
dockerComposeYaml['volumes'] = Object.assign({ ...dockerComposeYaml['volumes'] }, ...composeVolumes)
|
||||||
throw 'Halting'
|
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } })
|
||||||
// await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} pull` })
|
await fs.writeFile(`${workdir}/docker-compose.${isYml ? 'yml' : 'yaml'}`, yaml.dump(dockerComposeYaml));
|
||||||
// await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} build --progress plain --pull` })
|
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} pull` })
|
||||||
// await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} up -d` })
|
await saveBuildLog({ line: 'Pulling images from Compose file.', buildId, applicationId });
|
||||||
|
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} build --progress plain` })
|
||||||
|
await saveBuildLog({ line: 'Building images from Compose file.', buildId, applicationId });
|
||||||
}
|
}
|
||||||
|
@@ -289,13 +289,16 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
baseImage,
|
baseImage,
|
||||||
baseBuildImage,
|
baseBuildImage,
|
||||||
deploymentType,
|
deploymentType,
|
||||||
baseDatabaseBranch
|
baseDatabaseBranch,
|
||||||
|
dockerComposeFile,
|
||||||
|
dockerComposeFileLocation,
|
||||||
|
dockerComposeConfiguration
|
||||||
} = request.body
|
} = request.body
|
||||||
|
console.log({dockerComposeConfiguration})
|
||||||
if (port) port = Number(port);
|
if (port) port = Number(port);
|
||||||
if (exposePort) {
|
if (exposePort) {
|
||||||
exposePort = Number(exposePort);
|
exposePort = Number(exposePort);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { destinationDocker: { engine, remoteEngine, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
const { destinationDocker: { engine, remoteEngine, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress })
|
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress })
|
||||||
if (denoOptions) denoOptions = denoOptions.trim();
|
if (denoOptions) denoOptions = denoOptions.trim();
|
||||||
@@ -324,6 +327,9 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
baseImage,
|
baseImage,
|
||||||
baseBuildImage,
|
baseBuildImage,
|
||||||
deploymentType,
|
deploymentType,
|
||||||
|
dockerComposeFile,
|
||||||
|
dockerComposeFileLocation,
|
||||||
|
dockerComposeConfiguration,
|
||||||
...defaultConfiguration,
|
...defaultConfiguration,
|
||||||
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
|
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
|
||||||
}
|
}
|
||||||
@@ -342,6 +348,9 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
baseImage,
|
baseImage,
|
||||||
baseBuildImage,
|
baseBuildImage,
|
||||||
deploymentType,
|
deploymentType,
|
||||||
|
dockerComposeFile,
|
||||||
|
dockerComposeFileLocation,
|
||||||
|
dockerComposeConfiguration,
|
||||||
...defaultConfiguration
|
...defaultConfiguration
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -21,7 +21,10 @@ export interface SaveApplication extends OnlyId {
|
|||||||
baseImage: string,
|
baseImage: string,
|
||||||
baseBuildImage: string,
|
baseBuildImage: string,
|
||||||
deploymentType: string,
|
deploymentType: string,
|
||||||
baseDatabaseBranch: string
|
baseDatabaseBranch: string,
|
||||||
|
dockerComposeFile: string,
|
||||||
|
dockerComposeFileLocation: string,
|
||||||
|
dockerComposeConfiguration: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface SaveApplicationSettings extends OnlyId {
|
export interface SaveApplicationSettings extends OnlyId {
|
||||||
|
@@ -48,6 +48,7 @@
|
|||||||
"daisyui": "2.24.2",
|
"daisyui": "2.24.2",
|
||||||
"dayjs": "1.11.5",
|
"dayjs": "1.11.5",
|
||||||
"js-cookie": "3.0.1",
|
"js-cookie": "3.0.1",
|
||||||
|
"js-yaml": "4.1.0",
|
||||||
"p-limit": "4.0.0",
|
"p-limit": "4.0.0",
|
||||||
"svelte-file-dropzone": "^1.0.0",
|
"svelte-file-dropzone": "^1.0.0",
|
||||||
"svelte-select": "4.4.7",
|
"svelte-select": "4.4.7",
|
||||||
|
@@ -29,7 +29,7 @@ export function findBuildPack(pack: string, packageManager = 'npm') {
|
|||||||
port: 80
|
port: 80
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (pack === 'docker') {
|
if (pack === 'docker' || pack === 'compose') {
|
||||||
return {
|
return {
|
||||||
...metaData,
|
...metaData,
|
||||||
installCommand: null,
|
installCommand: null,
|
||||||
@@ -39,6 +39,7 @@ export function findBuildPack(pack: string, packageManager = 'npm') {
|
|||||||
port: null
|
port: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pack === 'svelte') {
|
if (pack === 'svelte') {
|
||||||
return {
|
return {
|
||||||
...metaData,
|
...metaData,
|
||||||
@@ -236,13 +237,13 @@ export const buildPacks = [
|
|||||||
isCoolifyBuildPack: true,
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'compose',
|
name: 'compose',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
fancyName: 'Docker Compose',
|
fancyName: 'Docker Compose',
|
||||||
hoverColor: 'hover:bg-sky-700',
|
hoverColor: 'hover:bg-sky-700',
|
||||||
color: 'bg-sky-700',
|
color: 'bg-sky-700',
|
||||||
isCoolifyBuildPack: true,
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'svelte',
|
name: 'svelte',
|
||||||
type: 'specific',
|
type: 'specific',
|
||||||
@@ -357,14 +358,14 @@ export const buildPacks = [
|
|||||||
color: 'bg-green-700',
|
color: 'bg-green-700',
|
||||||
isCoolifyBuildPack: true,
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'heroku',
|
name: 'heroku',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
fancyName: 'Heroku',
|
fancyName: 'Heroku',
|
||||||
hoverColor: 'hover:bg-purple-700',
|
hoverColor: 'hover:bg-purple-700',
|
||||||
color: 'bg-purple-700',
|
color: 'bg-purple-700',
|
||||||
isHerokuBuildPack: true,
|
isHerokuBuildPack: true,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
export const scanningTemplates = {
|
export const scanningTemplates = {
|
||||||
'@sveltejs/kit': {
|
'@sveltejs/kit': {
|
||||||
|
@@ -14,6 +14,8 @@
|
|||||||
export let foundConfig: any;
|
export let foundConfig: any;
|
||||||
export let scanning: any;
|
export let scanning: any;
|
||||||
export let packageManager: any;
|
export let packageManager: any;
|
||||||
|
export let dockerComposeFile: any = null;
|
||||||
|
export let dockerComposeFileLocation: string | null = null;
|
||||||
|
|
||||||
async function handleSubmit(name: string) {
|
async function handleSubmit(name: string) {
|
||||||
try {
|
try {
|
||||||
@@ -25,10 +27,12 @@
|
|||||||
delete tempBuildPack.fancyName;
|
delete tempBuildPack.fancyName;
|
||||||
delete tempBuildPack.color;
|
delete tempBuildPack.color;
|
||||||
delete tempBuildPack.hoverColor;
|
delete tempBuildPack.hoverColor;
|
||||||
|
await post(`/applications/${id}`, {
|
||||||
if (foundConfig?.buildPack !== name) {
|
...tempBuildPack,
|
||||||
await post(`/applications/${id}`, { ...tempBuildPack, buildPack: name });
|
buildPack: name,
|
||||||
}
|
dockerComposeFile,
|
||||||
|
dockerComposeFileLocation
|
||||||
|
});
|
||||||
await post(`/applications/${id}/configuration/buildpack`, { buildPack: name });
|
await post(`/applications/${id}/configuration/buildpack`, { buildPack: name });
|
||||||
return await goto(from || `/applications/${id}`);
|
return await goto(from || `/applications/${id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@@ -34,12 +34,15 @@
|
|||||||
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/templates';
|
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/templates';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import BuildPack from './_BuildPack.svelte';
|
import BuildPack from './_BuildPack.svelte';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
let scanning = true;
|
let scanning: boolean = true;
|
||||||
let foundConfig: any = null;
|
let foundConfig: any = null;
|
||||||
let packageManager = 'npm';
|
let packageManager: string = 'npm';
|
||||||
|
let dockerComposeFile: any = null;
|
||||||
|
let dockerComposeFileLocation: string | null = null;
|
||||||
|
|
||||||
export let apiUrl: any;
|
export let apiUrl: any;
|
||||||
export let projectId: any;
|
export let projectId: any;
|
||||||
@@ -60,10 +63,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function scanRepository(): Promise<void> {
|
async function scanRepository(isPublicRepository: boolean): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (type === 'gitlab') {
|
if (type === 'gitlab') {
|
||||||
const files = await get(`${apiUrl}/v4/projects/${projectId}/repository/tree`, {
|
if (isPublicRepository) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = isPublicRepository ? `` : `/v4/projects/${projectId}/repository/tree`;
|
||||||
|
const files = await get(`${apiUrl}${url}`, {
|
||||||
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
||||||
});
|
});
|
||||||
const packageJson = files.find(
|
const packageJson = files.find(
|
||||||
@@ -82,6 +89,14 @@
|
|||||||
(file: { name: string; type: string }) =>
|
(file: { name: string; type: string }) =>
|
||||||
file.name === 'Dockerfile' && file.type === 'blob'
|
file.name === 'Dockerfile' && file.type === 'blob'
|
||||||
);
|
);
|
||||||
|
const dockerComposeFileYml = files.find(
|
||||||
|
(file: { name: string; type: string }) =>
|
||||||
|
file.name === 'docker-compose.yml' && file.type === 'blob'
|
||||||
|
);
|
||||||
|
const dockerComposeFileYaml = files.find(
|
||||||
|
(file: { name: string; type: string }) =>
|
||||||
|
file.name === 'docker-compose.yaml' && file.type === 'blob'
|
||||||
|
);
|
||||||
const cargoToml = files.find(
|
const cargoToml = files.find(
|
||||||
(file: { name: string; type: string }) =>
|
(file: { name: string; type: string }) =>
|
||||||
file.name === 'Cargo.toml' && file.type === 'blob'
|
file.name === 'Cargo.toml' && file.type === 'blob'
|
||||||
@@ -105,11 +120,12 @@
|
|||||||
const laravel = files.find(
|
const laravel = files.find(
|
||||||
(file: { name: string; type: string }) => file.name === 'artisan' && file.type === 'blob'
|
(file: { name: string; type: string }) => file.name === 'artisan' && file.type === 'blob'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (yarnLock) packageManager = 'yarn';
|
if (yarnLock) packageManager = 'yarn';
|
||||||
if (pnpmLock) packageManager = 'pnpm';
|
if (pnpmLock) packageManager = 'pnpm';
|
||||||
|
|
||||||
if (dockerfile) {
|
if (dockerComposeFileYml || dockerComposeFileYaml) {
|
||||||
|
foundConfig = findBuildPack('dockercompose', packageManager);
|
||||||
|
} else if (dockerfile) {
|
||||||
foundConfig = findBuildPack('docker', packageManager);
|
foundConfig = findBuildPack('docker', packageManager);
|
||||||
} else if (packageJson && !laravel) {
|
} else if (packageJson && !laravel) {
|
||||||
const path = packageJson.path;
|
const path = packageJson.path;
|
||||||
@@ -135,8 +151,13 @@
|
|||||||
foundConfig = findBuildPack('node', packageManager);
|
foundConfig = findBuildPack('node', packageManager);
|
||||||
}
|
}
|
||||||
} else if (type === 'github') {
|
} else if (type === 'github') {
|
||||||
|
const headers = isPublicRepository
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
Authorization: `token ${$appSession.tokens.github}`
|
||||||
|
};
|
||||||
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
|
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
|
||||||
Authorization: `Bearer ${$appSession.tokens.github}`,
|
...headers,
|
||||||
Accept: 'application/vnd.github.v2.json'
|
Accept: 'application/vnd.github.v2.json'
|
||||||
});
|
});
|
||||||
const packageJson = files.find(
|
const packageJson = files.find(
|
||||||
@@ -155,6 +176,14 @@
|
|||||||
(file: { name: string; type: string }) =>
|
(file: { name: string; type: string }) =>
|
||||||
file.name === 'Dockerfile' && file.type === 'file'
|
file.name === 'Dockerfile' && file.type === 'file'
|
||||||
);
|
);
|
||||||
|
const dockerComposeFileYml = files.find(
|
||||||
|
(file: { name: string; type: string }) =>
|
||||||
|
file.name === 'docker-compose.yml' && file.type === 'file'
|
||||||
|
);
|
||||||
|
const dockerComposeFileYaml = files.find(
|
||||||
|
(file: { name: string; type: string }) =>
|
||||||
|
file.name === 'docker-compose.yaml' && file.type === 'file'
|
||||||
|
);
|
||||||
const cargoToml = files.find(
|
const cargoToml = files.find(
|
||||||
(file: { name: string; type: string }) =>
|
(file: { name: string; type: string }) =>
|
||||||
file.name === 'Cargo.toml' && file.type === 'file'
|
file.name === 'Cargo.toml' && file.type === 'file'
|
||||||
@@ -182,7 +211,25 @@
|
|||||||
if (yarnLock) packageManager = 'yarn';
|
if (yarnLock) packageManager = 'yarn';
|
||||||
if (pnpmLock) packageManager = 'pnpm';
|
if (pnpmLock) packageManager = 'pnpm';
|
||||||
|
|
||||||
if (dockerfile) {
|
if (dockerComposeFileYml || dockerComposeFileYaml) {
|
||||||
|
foundConfig = findBuildPack('compose', packageManager);
|
||||||
|
const data = await get(
|
||||||
|
`${apiUrl}/repos/${repository}/contents/${
|
||||||
|
dockerComposeFileYml ? 'docker-compose.yml' : 'docker-compose.yaml'
|
||||||
|
}?ref=${branch}`,
|
||||||
|
{
|
||||||
|
...headers,
|
||||||
|
Accept: 'application/vnd.github.v2.json'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (data?.content) {
|
||||||
|
const content = atob(data.content);
|
||||||
|
dockerComposeFile = JSON.stringify(yaml.load(content) || null);
|
||||||
|
dockerComposeFileLocation = dockerComposeFileYml
|
||||||
|
? 'docker-compose.yml'
|
||||||
|
: 'docker-compose.yaml';
|
||||||
|
}
|
||||||
|
} else if (dockerfile) {
|
||||||
foundConfig = findBuildPack('docker', packageManager);
|
foundConfig = findBuildPack('docker', packageManager);
|
||||||
} else if (packageJson && !laravel) {
|
} else if (packageJson && !laravel) {
|
||||||
const data: any = await get(`${packageJson.git_url}`, {
|
const data: any = await get(`${packageJson.git_url}`, {
|
||||||
@@ -237,7 +284,7 @@
|
|||||||
if (error.message === 'Bad credentials') {
|
if (error.message === 'Bad credentials') {
|
||||||
const { token } = await get(`/applications/${id}/configuration/githubToken`);
|
const { token } = await get(`/applications/${id}/configuration/githubToken`);
|
||||||
$appSession.tokens.github = token;
|
$appSession.tokens.github = token;
|
||||||
return await scanRepository();
|
return await scanRepository(isPublicRepository);
|
||||||
}
|
}
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -246,11 +293,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (!isPublicRepository) {
|
await scanRepository(isPublicRepository);
|
||||||
await scanRepository();
|
|
||||||
} else {
|
|
||||||
scanning = false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -266,7 +309,12 @@
|
|||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-wrap justify-center">
|
||||||
{#each buildPacks.filter((bp) => bp.isHerokuBuildPack === true) as buildPack}
|
{#each buildPacks.filter((bp) => bp.isHerokuBuildPack === true) as buildPack}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
|
<BuildPack
|
||||||
|
{packageManager}
|
||||||
|
{buildPack}
|
||||||
|
{scanning}
|
||||||
|
bind:foundConfig
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -274,9 +322,16 @@
|
|||||||
<div class="max-w-screen-2xl mx-auto px-10">
|
<div class="max-w-screen-2xl mx-auto px-10">
|
||||||
<div class="title pb-2">Coolify Base</div>
|
<div class="title pb-2">Coolify Base</div>
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-wrap justify-center">
|
||||||
{#each buildPacks.filter((bp) => bp.isCoolifyBuildPack === true && bp.type ==='base') as buildPack}
|
{#each buildPacks.filter((bp) => bp.isCoolifyBuildPack === true && bp.type === 'base') as buildPack}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
|
<BuildPack
|
||||||
|
{packageManager}
|
||||||
|
{buildPack}
|
||||||
|
{scanning}
|
||||||
|
bind:foundConfig
|
||||||
|
{dockerComposeFile}
|
||||||
|
{dockerComposeFileLocation}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -284,9 +339,14 @@
|
|||||||
<div class="max-w-screen-2xl mx-auto px-10">
|
<div class="max-w-screen-2xl mx-auto px-10">
|
||||||
<div class="title pb-2">Coolify Specific</div>
|
<div class="title pb-2">Coolify Specific</div>
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-wrap justify-center">
|
||||||
{#each buildPacks.filter((bp) => bp.isCoolifyBuildPack === true && bp.type ==='specific') as buildPack}
|
{#each buildPacks.filter((bp) => bp.isCoolifyBuildPack === true && bp.type === 'specific') as buildPack}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
|
<BuildPack
|
||||||
|
{packageManager}
|
||||||
|
{buildPack}
|
||||||
|
{scanning}
|
||||||
|
bind:foundConfig
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -29,7 +29,7 @@
|
|||||||
export let application: any;
|
export let application: any;
|
||||||
export let settings: any;
|
export let settings: any;
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Select from 'svelte-select';
|
import Select from 'svelte-select';
|
||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
@@ -45,10 +45,9 @@
|
|||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
|
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { fade } from 'svelte/transition';
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
@@ -58,6 +57,10 @@
|
|||||||
let loading = false;
|
let loading = false;
|
||||||
let fqdnEl: any = null;
|
let fqdnEl: any = null;
|
||||||
let forceSave = false;
|
let forceSave = false;
|
||||||
|
let isPublicRepository = application.settings.isPublicRepository;
|
||||||
|
let apiUrl = application.gitSource.apiUrl;
|
||||||
|
let branch = application.branch;
|
||||||
|
let repository = application.repository;
|
||||||
let debug = application.settings.debug;
|
let debug = application.settings.debug;
|
||||||
let previews = application.settings.previews;
|
let previews = application.settings.previews;
|
||||||
let dualCerts = application.settings.dualCerts;
|
let dualCerts = application.settings.dualCerts;
|
||||||
@@ -66,6 +69,11 @@
|
|||||||
let isBot = application.settings.isBot;
|
let isBot = application.settings.isBot;
|
||||||
let isDBBranching = application.settings.isDBBranching;
|
let isDBBranching = application.settings.isDBBranching;
|
||||||
|
|
||||||
|
let dockerComposeFile = JSON.parse(application.dockerComposeFile) || null;
|
||||||
|
let dockerComposeServices: any[] = [];
|
||||||
|
let dockerComposeFileLocation = application.dockerComposeFileLocation;
|
||||||
|
let dockerComposeConfiguration = JSON.parse(application.dockerComposeConfiguration) || {};
|
||||||
|
|
||||||
let baseDatabaseBranch: any = application?.connectedDatabase?.hostedDatabaseDBName || null;
|
let baseDatabaseBranch: any = application?.connectedDatabase?.hostedDatabaseDBName || null;
|
||||||
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||||
let isHttps = application.fqdn && application.fqdn.startsWith('https://');
|
let isHttps = application.fqdn && application.fqdn.startsWith('https://');
|
||||||
@@ -86,6 +94,26 @@
|
|||||||
label: 'Uvicorn'
|
label: 'Uvicorn'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function normalizeDockerServices(services: any[]) {
|
||||||
|
const tempdockerComposeServices = [];
|
||||||
|
for (const [name, data] of Object.entries(services)) {
|
||||||
|
tempdockerComposeServices.push({
|
||||||
|
name,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const service of tempdockerComposeServices) {
|
||||||
|
if (!dockerComposeConfiguration[service.name]) {
|
||||||
|
dockerComposeConfiguration[service.name] = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tempdockerComposeServices;
|
||||||
|
}
|
||||||
|
if (dockerComposeFile?.services) {
|
||||||
|
dockerComposeServices = normalizeDockerServices(dockerComposeFile.services);
|
||||||
|
}
|
||||||
|
|
||||||
function containerClass() {
|
function containerClass() {
|
||||||
return 'text-white bg-transparent font-thin px-0 w-full border border-dashed border-coolgray-200';
|
return 'text-white bg-transparent font-thin px-0 w-full border border-dashed border-coolgray-200';
|
||||||
}
|
}
|
||||||
@@ -214,7 +242,11 @@
|
|||||||
dualCerts,
|
dualCerts,
|
||||||
exposePort: application.exposePort
|
exposePort: application.exposePort
|
||||||
}));
|
}));
|
||||||
await post(`/applications/${id}`, { ...application, baseDatabaseBranch });
|
await post(`/applications/${id}`, {
|
||||||
|
...application,
|
||||||
|
baseDatabaseBranch,
|
||||||
|
dockerComposeConfiguration: JSON.stringify(dockerComposeConfiguration)
|
||||||
|
});
|
||||||
setLocation(application, settings);
|
setLocation(application, settings);
|
||||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
||||||
|
|
||||||
@@ -281,6 +313,36 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function reloadCompose() {
|
||||||
|
try {
|
||||||
|
const headers = isPublicRepository
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
Authorization: `token ${$appSession.tokens.github}`
|
||||||
|
};
|
||||||
|
const data = await get(
|
||||||
|
`${apiUrl}/repos/${repository}/contents/${dockerComposeFileLocation}?ref=${branch}`,
|
||||||
|
{
|
||||||
|
...headers,
|
||||||
|
Accept: 'application/vnd.github.v2.json'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (data?.content) {
|
||||||
|
const content = atob(data.content);
|
||||||
|
let dockerComposeFileContent = JSON.stringify(yaml.load(content) || null);
|
||||||
|
let dockerComposeFileContentJSON = JSON.parse(dockerComposeFileContent);
|
||||||
|
dockerComposeServices = normalizeDockerServices(dockerComposeFileContentJSON?.services);
|
||||||
|
application.dockerComposeFile = dockerComposeFileContent;
|
||||||
|
await handleSubmit();
|
||||||
|
}
|
||||||
|
addToast({
|
||||||
|
message: 'Compose file reloaded.',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
@@ -372,18 +434,20 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
{#if application.buildPack !== 'compose'}
|
||||||
<Setting
|
<div class="grid grid-cols-2 items-center">
|
||||||
id="isBot"
|
<Setting
|
||||||
isCenter={false}
|
id="isBot"
|
||||||
bind:setting={isBot}
|
isCenter={false}
|
||||||
on:click={() => changeSettings('isBot')}
|
bind:setting={isBot}
|
||||||
title="Is your application a bot?"
|
on:click={() => changeSettings('isBot')}
|
||||||
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>"
|
title="Is your application a bot?"
|
||||||
disabled={$status.application.isRunning}
|
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}
|
||||||
</div>
|
/>
|
||||||
{#if !isBot}
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if !isBot && application.buildPack !== 'compose'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="fqdn"
|
<label for="fqdn"
|
||||||
>{$t('application.url_fqdn')}
|
>{$t('application.url_fqdn')}
|
||||||
@@ -454,7 +518,7 @@
|
|||||||
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
|
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if isHttps}
|
{#if isHttps && application.buildPack !== 'compose'}
|
||||||
<div class="grid grid-cols-2 items-center pb-4">
|
<div class="grid grid-cols-2 items-center pb-4">
|
||||||
<Setting
|
<Setting
|
||||||
id="isCustomSSL"
|
id="isCustomSSL"
|
||||||
@@ -468,349 +532,395 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{#if application.buildPack !== 'compose'}
|
||||||
<div class="title font-bold pb-3 pt-10 border-b border-coolgray-500 mb-6">Build & Deploy</div>
|
<div class="title font-bold pb-3 pt-10 border-b border-coolgray-500 mb-6">
|
||||||
<div class="grid grid-flow-row gap-2 px-4 pr-5">
|
Build & Deploy
|
||||||
{#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'}
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-flow-row gap-2 px-4 pr-5">
|
||||||
<label for="baseBuildImage"
|
{#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'}
|
||||||
>{$t('application.base_build_image')}
|
|
||||||
<Explainer
|
|
||||||
explanation={application.buildPack === 'laravel'
|
|
||||||
? 'For building frontend assets with webpack.'
|
|
||||||
: 'Image that will be used during the build process.'}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div class="custom-select-wrapper">
|
|
||||||
<Select
|
|
||||||
{isDisabled}
|
|
||||||
containerClasses={isDisabled && containerClass()}
|
|
||||||
id="baseBuildImages"
|
|
||||||
showIndicator={!$status.application.isRunning}
|
|
||||||
items={application.baseBuildImages}
|
|
||||||
on:select={selectBaseBuildImage}
|
|
||||||
value={application.baseBuildImage}
|
|
||||||
isClearable={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if application.buildPack !== 'docker'}
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<label for="baseImage"
|
|
||||||
>{$t('application.base_image')}
|
|
||||||
<Explainer explanation={'Image that will be used for the deployment.'} /></label
|
|
||||||
>
|
|
||||||
<div class="custom-select-wrapper">
|
|
||||||
<Select
|
|
||||||
{isDisabled}
|
|
||||||
containerClasses={isDisabled && containerClass()}
|
|
||||||
id="baseImages"
|
|
||||||
showIndicator={!$status.application.isRunning}
|
|
||||||
items={application.baseImages}
|
|
||||||
on:select={selectBaseImage}
|
|
||||||
value={application.baseImage}
|
|
||||||
isClearable={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if application.buildPack !== 'docker' && (application.buildPack === 'nextjs' || application.buildPack === 'nuxtjs')}
|
|
||||||
<div class="grid grid-cols-2 items-center pb-8">
|
|
||||||
<label for="deploymentType"
|
|
||||||
>Deployment Type
|
|
||||||
<Explainer
|
|
||||||
explanation={"Defines how to deploy your application. <br><br><span class='text-green-500 font-bold'>Static</span> is for static websites, <span class='text-green-500 font-bold'>node</span> is for server-side applications."}
|
|
||||||
/></label
|
|
||||||
>
|
|
||||||
<div class="custom-select-wrapper">
|
|
||||||
<Select
|
|
||||||
{isDisabled}
|
|
||||||
containerClasses={isDisabled && containerClass()}
|
|
||||||
id="deploymentTypes"
|
|
||||||
showIndicator={!$status.application.isRunning}
|
|
||||||
items={['static', 'node']}
|
|
||||||
on:select={selectDeploymentType}
|
|
||||||
value={application.deploymentType}
|
|
||||||
isClearable={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $features.beta}
|
|
||||||
{#if !application.settings.isBot && !application.settings.isPublicRepository}
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<label for="baseBuildImage"
|
||||||
id="isDBBranching"
|
>{$t('application.base_build_image')}
|
||||||
isCenter={false}
|
<Explainer
|
||||||
bind:setting={isDBBranching}
|
explanation={application.buildPack === 'laravel'
|
||||||
on:click={() => changeSettings('isDBBranching')}
|
? 'For building frontend assets with webpack.'
|
||||||
title="Enable DB Branching"
|
: 'Image that will be used during the build process.'}
|
||||||
description="Enable DB Branching"
|
/>
|
||||||
/>
|
</label>
|
||||||
|
<div class="custom-select-wrapper">
|
||||||
|
<Select
|
||||||
|
{isDisabled}
|
||||||
|
containerClasses={isDisabled && containerClass()}
|
||||||
|
id="baseBuildImages"
|
||||||
|
showIndicator={!$status.application.isRunning}
|
||||||
|
items={application.baseBuildImages}
|
||||||
|
on:select={selectBaseBuildImage}
|
||||||
|
value={application.baseBuildImage}
|
||||||
|
isClearable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if isDBBranching}
|
{/if}
|
||||||
<button
|
{#if application.buildPack !== 'docker'}
|
||||||
on:click|stopPropagation|preventDefault={() =>
|
<div class="grid grid-cols-2 items-center">
|
||||||
goto(`/applications/${id}/configuration/database`)}
|
<label for="baseImage"
|
||||||
class="btn btn-sm">Configure Connected Database</button
|
>{$t('application.base_image')}
|
||||||
|
<Explainer explanation={'Image that will be used for the deployment.'} /></label
|
||||||
>
|
>
|
||||||
{#if application.connectedDatabase}
|
<div class="custom-select-wrapper">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<Select
|
||||||
<label for="baseImage"
|
{isDisabled}
|
||||||
>Base Database
|
containerClasses={isDisabled && containerClass()}
|
||||||
<Explainer
|
id="baseImages"
|
||||||
explanation={'The name of the database that will be used as base when branching.'}
|
showIndicator={!$status.application.isRunning}
|
||||||
/></label
|
items={application.baseImages}
|
||||||
>
|
on:select={selectBaseImage}
|
||||||
<input
|
value={application.baseImage}
|
||||||
name="baseDatabaseBranch"
|
isClearable={false}
|
||||||
required
|
/>
|
||||||
id="baseDatabaseBranch"
|
</div>
|
||||||
bind:value={baseDatabaseBranch}
|
</div>
|
||||||
/>
|
{/if}
|
||||||
</div>
|
{#if application.buildPack !== 'docker' && (application.buildPack === 'nextjs' || application.buildPack === 'nuxtjs')}
|
||||||
<div class="text-center bg-green-600 rounded">
|
<div class="grid grid-cols-2 items-center pb-8">
|
||||||
Connected to {application.connectedDatabase.databaseId}
|
<label for="deploymentType"
|
||||||
</div>
|
>Deployment Type
|
||||||
|
<Explainer
|
||||||
|
explanation={"Defines how to deploy your application. <br><br><span class='text-green-500 font-bold'>Static</span> is for static websites, <span class='text-green-500 font-bold'>node</span> is for server-side applications."}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
|
<div class="custom-select-wrapper">
|
||||||
|
<Select
|
||||||
|
{isDisabled}
|
||||||
|
containerClasses={isDisabled && containerClass()}
|
||||||
|
id="deploymentTypes"
|
||||||
|
showIndicator={!$status.application.isRunning}
|
||||||
|
items={['static', 'node']}
|
||||||
|
on:select={selectDeploymentType}
|
||||||
|
value={application.deploymentType}
|
||||||
|
isClearable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $features.beta}
|
||||||
|
{#if !application.settings.isBot && !application.settings.isPublicRepository}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<Setting
|
||||||
|
id="isDBBranching"
|
||||||
|
isCenter={false}
|
||||||
|
bind:setting={isDBBranching}
|
||||||
|
on:click={() => changeSettings('isDBBranching')}
|
||||||
|
title="Enable DB Branching"
|
||||||
|
description="Enable DB Branching"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if isDBBranching}
|
||||||
|
<button
|
||||||
|
on:click|stopPropagation|preventDefault={() =>
|
||||||
|
goto(`/applications/${id}/configuration/database`)}
|
||||||
|
class="btn btn-sm">Configure Connected Database</button
|
||||||
|
>
|
||||||
|
{#if application.connectedDatabase}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="baseImage"
|
||||||
|
>Base Database
|
||||||
|
<Explainer
|
||||||
|
explanation={'The name of the database that will be used as base when branching.'}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
name="baseDatabaseBranch"
|
||||||
|
required
|
||||||
|
id="baseDatabaseBranch"
|
||||||
|
bind:value={baseDatabaseBranch}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="text-center bg-green-600 rounded">
|
||||||
|
Connected to {application.connectedDatabase.databaseId}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if application.buildPack === 'python'}
|
{#if application.buildPack === 'python'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<label for="pythonModule">WSGI / ASGI</label>
|
|
||||||
<div class="custom-select-wrapper">
|
|
||||||
<Select
|
|
||||||
id="wsgi"
|
|
||||||
items={wsgis}
|
|
||||||
on:select={selectWSGI}
|
|
||||||
value={application.pythonWSGI}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<label for="pythonModule">Module</label>
|
|
||||||
<input
|
|
||||||
disabled={isDisabled}
|
|
||||||
readonly={!$appSession.isAdmin}
|
|
||||||
name="pythonModule"
|
|
||||||
id="pythonModule"
|
|
||||||
required
|
|
||||||
class="w-full"
|
|
||||||
bind:value={application.pythonModule}
|
|
||||||
placeholder={application.pythonWSGI?.toLowerCase() !== 'none' ? 'main' : 'main.py'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{#if application.pythonWSGI?.toLowerCase() === 'gunicorn'}
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="pythonVariable">Variable</label>
|
<label for="pythonModule">WSGI / ASGI</label>
|
||||||
|
<div class="custom-select-wrapper">
|
||||||
|
<Select
|
||||||
|
id="wsgi"
|
||||||
|
items={wsgis}
|
||||||
|
on:select={selectWSGI}
|
||||||
|
value={application.pythonWSGI}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="pythonModule">Module</label>
|
||||||
<input
|
<input
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
readonly={!$appSession.isAdmin}
|
readonly={!$appSession.isAdmin}
|
||||||
name="pythonVariable"
|
name="pythonModule"
|
||||||
id="pythonVariable"
|
id="pythonModule"
|
||||||
required
|
required
|
||||||
class="w-full"
|
class="w-full"
|
||||||
bind:value={application.pythonVariable}
|
bind:value={application.pythonModule}
|
||||||
placeholder="default: app"
|
placeholder={application.pythonWSGI?.toLowerCase() !== 'none' ? 'main' : 'main.py'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{#if application.pythonWSGI?.toLowerCase() === 'gunicorn'}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="pythonVariable">Variable</label>
|
||||||
|
<input
|
||||||
|
disabled={isDisabled}
|
||||||
|
readonly={!$appSession.isAdmin}
|
||||||
|
name="pythonVariable"
|
||||||
|
id="pythonVariable"
|
||||||
|
required
|
||||||
|
class="w-full"
|
||||||
|
bind:value={application.pythonVariable}
|
||||||
|
placeholder="default: app"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if application.pythonWSGI?.toLowerCase() === 'uvicorn'}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="pythonVariable">Variable</label>
|
||||||
|
<input
|
||||||
|
disabled={isDisabled}
|
||||||
|
readonly={!$appSession.isAdmin}
|
||||||
|
name="pythonVariable"
|
||||||
|
id="pythonVariable"
|
||||||
|
required
|
||||||
|
class="w-full"
|
||||||
|
bind:value={application.pythonVariable}
|
||||||
|
placeholder="default: app"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if application.pythonWSGI?.toLowerCase() === 'uvicorn'}
|
{#if !staticDeployments.includes(application.buildPack)}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center pt-4">
|
||||||
<label for="pythonVariable">Variable</label>
|
<label for="port"
|
||||||
|
>{$t('forms.port')}
|
||||||
|
<Explainer explanation={'The port your application listens on.'} /></label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
|
class="w-full"
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
readonly={!$appSession.isAdmin}
|
readonly={!$appSession.isAdmin}
|
||||||
name="pythonVariable"
|
name="port"
|
||||||
id="pythonVariable"
|
id="port"
|
||||||
required
|
bind:value={application.port}
|
||||||
class="w-full"
|
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
|
||||||
bind:value={application.pythonVariable}
|
|
||||||
placeholder="default: app"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
<div class="grid grid-cols-2 items-center pb-4">
|
||||||
{#if !staticDeployments.includes(application.buildPack)}
|
<label for="exposePort"
|
||||||
<div class="grid grid-cols-2 items-center pt-4">
|
>Exposed Port <Explainer
|
||||||
<label for="port"
|
explanation={'You can expose your application to a port on the host system.<br><br>Useful if you would like to use your own reverse proxy or tunnel and also in development mode. Otherwise leave empty.'}
|
||||||
>{$t('forms.port')}
|
|
||||||
<Explainer explanation={'The port your application listens on.'} /></label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="w-full"
|
|
||||||
disabled={isDisabled}
|
|
||||||
readonly={!$appSession.isAdmin}
|
|
||||||
name="port"
|
|
||||||
id="port"
|
|
||||||
bind:value={application.port}
|
|
||||||
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div class="grid grid-cols-2 items-center pb-4">
|
|
||||||
<label for="exposePort"
|
|
||||||
>Exposed Port <Explainer
|
|
||||||
explanation={'You can expose your application to a port on the host system.<br><br>Useful if you would like to use your own reverse proxy or tunnel and also in development mode. Otherwise leave empty.'}
|
|
||||||
/></label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="w-full"
|
|
||||||
readonly={!$appSession.isAdmin && !$status.application.isRunning}
|
|
||||||
disabled={isDisabled}
|
|
||||||
name="exposePort"
|
|
||||||
id="exposePort"
|
|
||||||
bind:value={application.exposePort}
|
|
||||||
placeholder="12345"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<label for="installCommand">{$t('application.install_command')}</label>
|
|
||||||
<input
|
|
||||||
class="w-full"
|
|
||||||
disabled={isDisabled}
|
|
||||||
readonly={!$appSession.isAdmin}
|
|
||||||
name="installCommand"
|
|
||||||
id="installCommand"
|
|
||||||
bind:value={application.installCommand}
|
|
||||||
placeholder="{$t('forms.default')}: yarn install"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<label for="buildCommand">{$t('application.build_command')}</label>
|
|
||||||
<input
|
|
||||||
class="w-full"
|
|
||||||
disabled={isDisabled}
|
|
||||||
readonly={!$appSession.isAdmin}
|
|
||||||
name="buildCommand"
|
|
||||||
id="buildCommand"
|
|
||||||
bind:value={application.buildCommand}
|
|
||||||
placeholder="{$t('forms.default')}: yarn build"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-center pb-8">
|
|
||||||
<label for="startCommand">{$t('application.start_command')}</label>
|
|
||||||
<input
|
|
||||||
class="w-full"
|
|
||||||
disabled={isDisabled}
|
|
||||||
readonly={!$appSession.isAdmin}
|
|
||||||
name="startCommand"
|
|
||||||
id="startCommand"
|
|
||||||
bind:value={application.startCommand}
|
|
||||||
placeholder="{$t('forms.default')}: yarn start"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if application.buildPack === 'deno'}
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<label for="denoMainFile">Main File</label>
|
|
||||||
<input
|
|
||||||
class="w-full"
|
|
||||||
disabled={isDisabled}
|
|
||||||
readonly={!$appSession.isAdmin}
|
|
||||||
name="denoMainFile"
|
|
||||||
id="denoMainFile"
|
|
||||||
bind:value={application.denoMainFile}
|
|
||||||
placeholder="default: main.ts"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<label for="denoOptions"
|
|
||||||
>Arguments <Explainer
|
|
||||||
explanation={"List of arguments to pass to <span class='text-settings font-bold'>deno run</span> command. Could include permissions, configurations files, etc."}
|
|
||||||
/></label
|
/></label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
class="w-full"
|
class="w-full"
|
||||||
|
readonly={!$appSession.isAdmin && !$status.application.isRunning}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
readonly={!$appSession.isAdmin}
|
name="exposePort"
|
||||||
name="denoOptions"
|
id="exposePort"
|
||||||
id="denoOptions"
|
bind:value={application.exposePort}
|
||||||
bind:value={application.denoOptions}
|
placeholder="12345"
|
||||||
placeholder="eg: --allow-net --allow-hrtime --config path/to/file.json"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||||
{#if application.buildPack !== 'laravel'}
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<label for="installCommand">{$t('application.install_command')}</label>
|
||||||
<div class="flex-col">
|
<input
|
||||||
<label for="baseDirectory"
|
class="w-full"
|
||||||
>{$t('forms.base_directory')}
|
disabled={isDisabled}
|
||||||
<Explainer
|
readonly={!$appSession.isAdmin}
|
||||||
explanation={"Directory to use as the base for all commands.<br>Could be useful with <span class='text-settings font-bold'>monorepos</span>."}
|
name="installCommand"
|
||||||
|
id="installCommand"
|
||||||
|
bind:value={application.installCommand}
|
||||||
|
placeholder="{$t('forms.default')}: yarn install"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="buildCommand">{$t('application.build_command')}</label>
|
||||||
|
<input
|
||||||
|
class="w-full"
|
||||||
|
disabled={isDisabled}
|
||||||
|
readonly={!$appSession.isAdmin}
|
||||||
|
name="buildCommand"
|
||||||
|
id="buildCommand"
|
||||||
|
bind:value={application.buildCommand}
|
||||||
|
placeholder="{$t('forms.default')}: yarn build"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center pb-8">
|
||||||
|
<label for="startCommand">{$t('application.start_command')}</label>
|
||||||
|
<input
|
||||||
|
class="w-full"
|
||||||
|
disabled={isDisabled}
|
||||||
|
readonly={!$appSession.isAdmin}
|
||||||
|
name="startCommand"
|
||||||
|
id="startCommand"
|
||||||
|
bind:value={application.startCommand}
|
||||||
|
placeholder="{$t('forms.default')}: yarn start"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if application.buildPack === 'deno'}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="denoMainFile">Main File</label>
|
||||||
|
<input
|
||||||
|
class="w-full"
|
||||||
|
disabled={isDisabled}
|
||||||
|
readonly={!$appSession.isAdmin}
|
||||||
|
name="denoMainFile"
|
||||||
|
id="denoMainFile"
|
||||||
|
bind:value={application.denoMainFile}
|
||||||
|
placeholder="default: main.ts"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="denoOptions"
|
||||||
|
>Arguments <Explainer
|
||||||
|
explanation={"List of arguments to pass to <span class='text-settings font-bold'>deno run</span> command. Could include permissions, configurations files, etc."}
|
||||||
/></label
|
/></label
|
||||||
>
|
>
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
class="w-full"
|
|
||||||
disabled={isDisabled}
|
|
||||||
readonly={!$appSession.isAdmin}
|
|
||||||
name="baseDirectory"
|
|
||||||
id="baseDirectory"
|
|
||||||
bind:value={application.baseDirectory}
|
|
||||||
placeholder="{$t('forms.default')}: /"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if application.buildPack === 'docker'}
|
|
||||||
<div class="grid grid-cols-2 items-center pb-4">
|
|
||||||
<label for="dockerFileLocation" class=""
|
|
||||||
>Dockerfile Location <Explainer
|
|
||||||
explanation={"Should be absolute path, like <span class='text-settings font-bold'>/data/Dockerfile</span> or <span class='text-settings font-bold'>/Dockerfile.</span>"}
|
|
||||||
/></label
|
|
||||||
>
|
|
||||||
<div class="form-control w-full">
|
|
||||||
<input
|
<input
|
||||||
class="w-full input"
|
class="w-full"
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
readonly={!$appSession.isAdmin}
|
readonly={!$appSession.isAdmin}
|
||||||
name="dockerFileLocation"
|
name="denoOptions"
|
||||||
id="dockerFileLocation"
|
id="denoOptions"
|
||||||
bind:value={application.dockerFileLocation}
|
bind:value={application.denoOptions}
|
||||||
placeholder="default: /Dockerfile"
|
placeholder="eg: --allow-net --allow-hrtime --config path/to/file.json"
|
||||||
/>
|
/>
|
||||||
{#if application.baseDirectory}
|
</div>
|
||||||
<label class="label">
|
{/if}
|
||||||
<span class="label-text-alt text-xs"
|
{#if application.buildPack !== 'laravel'}
|
||||||
>Path: {application.baseDirectory.replace(
|
<div class="grid grid-cols-2 items-center">
|
||||||
/^\/$/,
|
<div class="flex-col">
|
||||||
''
|
<label for="baseDirectory"
|
||||||
)}{application.dockerFileLocation}</span
|
>{$t('forms.base_directory')}
|
||||||
>
|
<Explainer
|
||||||
</label>
|
explanation={"Directory to use as the base for all commands.<br>Could be useful with <span class='text-settings font-bold'>monorepos</span>."}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="w-full"
|
||||||
|
disabled={isDisabled}
|
||||||
|
readonly={!$appSession.isAdmin}
|
||||||
|
name="baseDirectory"
|
||||||
|
id="baseDirectory"
|
||||||
|
bind:value={application.baseDirectory}
|
||||||
|
placeholder="{$t('forms.default')}: /"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if application.buildPack === 'docker'}
|
||||||
|
<div class="grid grid-cols-2 items-center pb-4">
|
||||||
|
<label for="dockerFileLocation" class=""
|
||||||
|
>Dockerfile Location <Explainer
|
||||||
|
explanation={"Should be absolute path, like <span class='text-settings font-bold'>/data/Dockerfile</span> or <span class='text-settings font-bold'>/Dockerfile.</span>"}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
|
<div class="form-control w-full">
|
||||||
|
<input
|
||||||
|
class="w-full input"
|
||||||
|
disabled={isDisabled}
|
||||||
|
readonly={!$appSession.isAdmin}
|
||||||
|
name="dockerFileLocation"
|
||||||
|
id="dockerFileLocation"
|
||||||
|
bind:value={application.dockerFileLocation}
|
||||||
|
placeholder="default: /Dockerfile"
|
||||||
|
/>
|
||||||
|
{#if application.baseDirectory}
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt text-xs"
|
||||||
|
>Path: {application.baseDirectory.replace(
|
||||||
|
/^\/$/,
|
||||||
|
''
|
||||||
|
)}{application.dockerFileLocation}</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<div class="flex-col">
|
||||||
|
<label for="publishDirectory"
|
||||||
|
>{$t('forms.publish_directory')}
|
||||||
|
<Explainer
|
||||||
|
explanation={"Directory containing all the assets for deployment. <br> For example: <span class='text-settings font-bold'>dist</span>,<span class='text-settings font-bold'>_site</span> or <span class='text-settings font-bold'>public</span>."}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
class="w-full"
|
||||||
|
disabled={isDisabled}
|
||||||
|
readonly={!$appSession.isAdmin}
|
||||||
|
name="publishDirectory"
|
||||||
|
id="publishDirectory"
|
||||||
|
required={application.deploymentType === 'static'}
|
||||||
|
bind:value={application.publishDirectory}
|
||||||
|
placeholder=" {$t('forms.default')}: /"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="title font-bold pb-3 pt-10 border-b border-coolgray-500 mb-6">
|
||||||
|
Docker Compose
|
||||||
|
{#if $appSession.isAdmin}
|
||||||
|
<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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<div class="flex-col">
|
|
||||||
<label for="publishDirectory"
|
|
||||||
>{$t('forms.publish_directory')}
|
|
||||||
<Explainer
|
|
||||||
explanation={"Directory containing all the assets for deployment. <br> For example: <span class='text-settings font-bold'>dist</span>,<span class='text-settings font-bold'>_site</span> or <span class='text-settings font-bold'>public</span>."}
|
|
||||||
/></label
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input
|
<div class="grid grid-cols-2 items-center">
|
||||||
class="w-full"
|
<label for="fqdn"
|
||||||
disabled={isDisabled}
|
>{$t('application.url_fqdn')}
|
||||||
readonly={!$appSession.isAdmin}
|
<Explainer
|
||||||
name="publishDirectory"
|
explanation={"If you specify <span class='text-settings font-bold'>https</span>, the application will be accessible only over https.<br>SSL certificate will be generated automatically.<br><br>If you specify <span class='text-settings font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-settings font-bold'>You must set your DNS to point to the server IP in advance.</span>"}
|
||||||
id="publishDirectory"
|
/>
|
||||||
required={application.deploymentType === 'static'}
|
</label>
|
||||||
bind:value={application.publishDirectory}
|
<div>
|
||||||
placeholder=" {$t('forms.default')}: /"
|
<input
|
||||||
/>
|
class="w-full"
|
||||||
</div>
|
name="fqdn"
|
||||||
{/if}
|
id="fqdn"
|
||||||
</div>
|
bind:value={dockerComposeConfiguration[service.name].fqdn}
|
||||||
|
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||||
|
placeholder="eg: https://coollabs.io"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -22,6 +22,10 @@ services:
|
|||||||
published: 3001
|
published: 3001
|
||||||
protocol: tcp
|
protocol: tcp
|
||||||
mode: host
|
mode: host
|
||||||
|
- target: 5555
|
||||||
|
published: 5555
|
||||||
|
protocol: tcp
|
||||||
|
mode: host
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/app
|
- ./:/app
|
||||||
- '/var/run/docker.sock:/var/run/docker.sock'
|
- '/var/run/docker.sock:/var/run/docker.sock'
|
||||||
|
@@ -8,14 +8,16 @@
|
|||||||
"oc": "opencollective-setup",
|
"oc": "opencollective-setup",
|
||||||
"translate": "pnpm run --filter i18n-converter translate",
|
"translate": "pnpm run --filter i18n-converter translate",
|
||||||
"db:studio": "pnpm run --filter api db:studio",
|
"db:studio": "pnpm run --filter api db:studio",
|
||||||
|
"db:studio:container": "docker exec coolify pnpm run --filter api db:studio",
|
||||||
"db:push": "pnpm run --filter api db:push",
|
"db:push": "pnpm run --filter api db:push",
|
||||||
"db:seed": "pnpm run --filter api db:seed",
|
"db:seed": "pnpm run --filter api db:seed",
|
||||||
"db:migrate": "pnpm run --filter api db:migrate",
|
"db:migrate": "pnpm run --filter api db:migrate",
|
||||||
|
"db:migrate:container": "docker exec coolify pnpm run --filter api db:migrate",
|
||||||
"format": "run-p -l -n format:*",
|
"format": "run-p -l -n format:*",
|
||||||
"format:api": "NODE_ENV=development pnpm run --filter api format",
|
"format:api": "NODE_ENV=development pnpm run --filter api format",
|
||||||
"lint": "run-p -l -n lint:*",
|
"lint": "run-p -l -n lint:*",
|
||||||
"lint:api": "NODE_ENV=development pnpm run --filter api lint",
|
"lint:api": "NODE_ENV=development pnpm run --filter api lint",
|
||||||
"dev:container": "docker-compose -f docker-compose-dev.yaml up",
|
"dev:container": "docker-compose -f docker-compose-dev.yaml up || docker compose -f docker-compose-dev.yaml up",
|
||||||
"dev": "run-p -l -n dev:api dev:ui",
|
"dev": "run-p -l -n dev:api dev:ui",
|
||||||
"dev:api": "NODE_ENV=development pnpm run --filter api dev",
|
"dev:api": "NODE_ENV=development pnpm run --filter api dev",
|
||||||
"dev:ui": "NODE_ENV=development pnpm run --filter ui dev",
|
"dev:ui": "NODE_ENV=development pnpm run --filter ui dev",
|
||||||
|
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -159,6 +159,7 @@ importers:
|
|||||||
flowbite: 1.5.2
|
flowbite: 1.5.2
|
||||||
flowbite-svelte: 0.26.2
|
flowbite-svelte: 0.26.2
|
||||||
js-cookie: 3.0.1
|
js-cookie: 3.0.1
|
||||||
|
js-yaml: 4.1.0
|
||||||
p-limit: 4.0.0
|
p-limit: 4.0.0
|
||||||
postcss: 8.4.16
|
postcss: 8.4.16
|
||||||
prettier: 2.7.1
|
prettier: 2.7.1
|
||||||
@@ -181,6 +182,7 @@ importers:
|
|||||||
daisyui: 2.24.2_25hquoklqeoqwmt7fwvvcyxm5e
|
daisyui: 2.24.2_25hquoklqeoqwmt7fwvvcyxm5e
|
||||||
dayjs: 1.11.5
|
dayjs: 1.11.5
|
||||||
js-cookie: 3.0.1
|
js-cookie: 3.0.1
|
||||||
|
js-yaml: 4.1.0
|
||||||
p-limit: 4.0.0
|
p-limit: 4.0.0
|
||||||
svelte-file-dropzone: 1.0.0
|
svelte-file-dropzone: 1.0.0
|
||||||
svelte-select: 4.4.7
|
svelte-select: 4.4.7
|
||||||
|
Reference in New Issue
Block a user