feat: specific git commit deployment
feat: revert to specific image fix: no system wide docker registries
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `isSystemWide` on the `DockerRegistry` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_DockerRegistry" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"url" TEXT NOT NULL,
|
||||||
|
"username" TEXT,
|
||||||
|
"password" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"teamId" TEXT,
|
||||||
|
CONSTRAINT "DockerRegistry_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_DockerRegistry" ("createdAt", "id", "name", "password", "teamId", "updatedAt", "url", "username") SELECT "createdAt", "id", "name", "password", "teamId", "updatedAt", "url", "username" FROM "DockerRegistry";
|
||||||
|
DROP TABLE "DockerRegistry";
|
||||||
|
ALTER TABLE "new_DockerRegistry" RENAME TO "DockerRegistry";
|
||||||
|
CREATE TABLE "new_Application" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"repository" TEXT,
|
||||||
|
"configHash" TEXT,
|
||||||
|
"branch" TEXT,
|
||||||
|
"buildPack" TEXT,
|
||||||
|
"projectId" INTEGER,
|
||||||
|
"port" INTEGER,
|
||||||
|
"exposePort" INTEGER,
|
||||||
|
"installCommand" TEXT,
|
||||||
|
"buildCommand" TEXT,
|
||||||
|
"startCommand" TEXT,
|
||||||
|
"baseDirectory" TEXT,
|
||||||
|
"publishDirectory" TEXT,
|
||||||
|
"deploymentType" TEXT,
|
||||||
|
"phpModules" TEXT,
|
||||||
|
"pythonWSGI" TEXT,
|
||||||
|
"pythonModule" TEXT,
|
||||||
|
"pythonVariable" TEXT,
|
||||||
|
"dockerFileLocation" TEXT,
|
||||||
|
"denoMainFile" TEXT,
|
||||||
|
"denoOptions" TEXT,
|
||||||
|
"dockerComposeFile" TEXT,
|
||||||
|
"dockerComposeFileLocation" TEXT,
|
||||||
|
"dockerComposeConfiguration" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"destinationDockerId" TEXT,
|
||||||
|
"gitSourceId" TEXT,
|
||||||
|
"gitCommitHash" TEXT,
|
||||||
|
"baseImage" TEXT,
|
||||||
|
"baseBuildImage" TEXT,
|
||||||
|
"dockerRegistryId" TEXT,
|
||||||
|
CONSTRAINT "Application_gitSourceId_fkey" FOREIGN KEY ("gitSourceId") REFERENCES "GitSource" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "Application_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "Application_dockerRegistryId_fkey" FOREIGN KEY ("dockerRegistryId") REFERENCES "DockerRegistry" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Application" ("baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "dockerRegistryId", "exposePort", "fqdn", "gitCommitHash", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt") SELECT "baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "dockerRegistryId", "exposePort", "fqdn", "gitCommitHash", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt" FROM "Application";
|
||||||
|
DROP TABLE "Application";
|
||||||
|
ALTER TABLE "new_Application" RENAME TO "Application";
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
@@ -139,8 +139,8 @@ model Application {
|
|||||||
teams Team[]
|
teams Team[]
|
||||||
connectedDatabase ApplicationConnectedDatabase?
|
connectedDatabase ApplicationConnectedDatabase?
|
||||||
previewApplication PreviewApplication[]
|
previewApplication PreviewApplication[]
|
||||||
dockerRegistryId String @default("0")
|
dockerRegistryId String?
|
||||||
dockerRegistry DockerRegistry @relation(fields: [dockerRegistryId], references: [id])
|
dockerRegistry DockerRegistry? @relation(fields: [dockerRegistryId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model PreviewApplication {
|
model PreviewApplication {
|
||||||
@@ -302,17 +302,16 @@ model SshKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model DockerRegistry {
|
model DockerRegistry {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
url String
|
url String
|
||||||
username String?
|
username String?
|
||||||
password String?
|
password String?
|
||||||
isSystemWide Boolean @default(false)
|
createdAt DateTime @default(now())
|
||||||
createdAt DateTime @default(now())
|
updatedAt DateTime @updatedAt
|
||||||
updatedAt DateTime @updatedAt
|
teamId String?
|
||||||
teamId String?
|
team Team? @relation(fields: [teamId], references: [id])
|
||||||
team Team? @relation(fields: [teamId], references: [id])
|
application Application[]
|
||||||
application Application[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model GitSource {
|
model GitSource {
|
||||||
|
@@ -91,10 +91,10 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add default docker registry (dockerhub)
|
// Add default docker registry (dockerhub)
|
||||||
const registries = await prisma.dockerRegistry.findMany()
|
// const registries = await prisma.dockerRegistry.findMany()
|
||||||
if (registries.length === 0) {
|
// if (registries.length === 0) {
|
||||||
await prisma.dockerRegistry.create({ data: { id: "0", name: 'Docker Hub', url: 'https://index.docker.io/v1/', isSystemWide: true } })
|
// await prisma.dockerRegistry.create({ data: { id: "0", name: 'Docker Hub', url: 'https://index.docker.io/v1/', isSystemWide: true, team: { connect: { id: '0' } } } })
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
main()
|
main()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
@@ -654,8 +654,14 @@ export async function buildImage({
|
|||||||
}
|
}
|
||||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
||||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||||
const { dockerRegistry: { url, username, password } } = await prisma.application.findUnique({ where: { id: applicationId }, select: { dockerRegistry: true } })
|
|
||||||
const location = await saveDockerRegistryCredentials({ url, username, password, workdir })
|
let location = null
|
||||||
|
|
||||||
|
const { dockerRegistry } = await prisma.application.findUnique({ where: { id: applicationId }, select: { dockerRegistry: true } })
|
||||||
|
if (dockerRegistry) {
|
||||||
|
const { url, username, password } = dockerRegistry
|
||||||
|
location = await saveDockerRegistryCredentials({ url, username, password, workdir })
|
||||||
|
}
|
||||||
|
|
||||||
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker ${location ? `--config ${location}` : ''} build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` })
|
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker ${location ? `--config ${location}` : ''} build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` })
|
||||||
|
|
||||||
|
@@ -37,6 +37,13 @@ export default async function ({
|
|||||||
buildId,
|
buildId,
|
||||||
applicationId
|
applicationId
|
||||||
});
|
});
|
||||||
|
if (gitCommitHash) {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `Checking out ${gitCommitHash} commit.`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
|
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
);
|
);
|
||||||
@@ -68,6 +75,13 @@ export default async function ({
|
|||||||
buildId,
|
buildId,
|
||||||
applicationId
|
applicationId
|
||||||
});
|
});
|
||||||
|
if (gitCommitHash) {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `Checking out ${gitCommitHash} commit.`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
|
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
);
|
);
|
||||||
|
@@ -39,7 +39,13 @@ export default async function ({
|
|||||||
buildId,
|
buildId,
|
||||||
applicationId
|
applicationId
|
||||||
});
|
});
|
||||||
|
if (gitCommitHash) {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `Checking out ${gitCommitHash} commit.`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
if (forPublic) {
|
if (forPublic) {
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
|
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
@@ -49,7 +55,7 @@ export default async function ({
|
|||||||
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
|
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||||
return commit.replace('\n', '');
|
return commit.replace('\n', '');
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ import { checkDomainsIsValidInDNS, checkExposedPort, createDirectories, decrypt,
|
|||||||
import { checkContainer, formatLabelsOnDocker, removeContainer } from '../../../../lib/docker';
|
import { checkContainer, formatLabelsOnDocker, removeContainer } from '../../../../lib/docker';
|
||||||
|
|
||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication, RestartPreviewApplication, GetBuilds } from './types';
|
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication, RestartPreviewApplication, GetBuilds, RestartApplication } from './types';
|
||||||
import { OnlyId } from '../../../../types';
|
import { OnlyId } from '../../../../types';
|
||||||
|
|
||||||
function filterObject(obj, callback) {
|
function filterObject(obj, callback) {
|
||||||
@@ -443,9 +443,10 @@ export async function stopPreviewApplication(request: FastifyRequest<StopPreview
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function restartApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
export async function restartApplication(request: FastifyRequest<RestartApplication>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
|
const { imageId = null } = request.body
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
let application: any = await getApplicationFromDB(id, teamId);
|
let application: any = await getApplicationFromDB(id, teamId);
|
||||||
if (application?.destinationDockerId) {
|
if (application?.destinationDockerId) {
|
||||||
@@ -475,17 +476,22 @@ export async function restartApplication(request: FastifyRequest<OnlyId>, reply:
|
|||||||
const { workdir } = await createDirectories({ repository, buildId });
|
const { workdir } = await createDirectories({ repository, buildId });
|
||||||
const labels = []
|
const labels = []
|
||||||
let image = null
|
let image = null
|
||||||
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` })
|
if (imageId) {
|
||||||
const containersArray = container.trim().split('\n');
|
image = imageId
|
||||||
for (const container of containersArray) {
|
} else {
|
||||||
const containerObj = formatLabelsOnDocker(container);
|
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` })
|
||||||
image = containerObj[0].Image
|
const containersArray = container.trim().split('\n');
|
||||||
Object.keys(containerObj[0].Labels).forEach(function (key) {
|
for (const container of containersArray) {
|
||||||
if (key.startsWith('coolify')) {
|
const containerObj = formatLabelsOnDocker(container);
|
||||||
labels.push(`${key}=${containerObj[0].Labels[key]}`)
|
image = containerObj[0].Image
|
||||||
}
|
Object.keys(containerObj[0].Labels).forEach(function (key) {
|
||||||
})
|
if (key.startsWith('coolify')) {
|
||||||
|
labels.push(`${key}=${containerObj[0].Labels[key]}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageFound = false;
|
let imageFound = false;
|
||||||
try {
|
try {
|
||||||
await executeDockerCmd({
|
await executeDockerCmd({
|
||||||
@@ -681,6 +687,38 @@ export async function getUsage(request) {
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function getDockerImages(request) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params
|
||||||
|
const teamId = request.user?.teamId;
|
||||||
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
|
const { stdout } = await executeDockerCmd({ dockerId: application.destinationDocker.id, command: `docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}' | grep -i ${id} | grep -v cache` });
|
||||||
|
const { stdout: runningImage } = await executeDockerCmd({ dockerId: application.destinationDocker.id, command: `docker ps -a --filter 'label=com.docker.compose.service=${id}' --format {{.Image}}` });
|
||||||
|
const images = stdout.trim().split('\n');
|
||||||
|
let imagesAvailables = [];
|
||||||
|
for (const image of images) {
|
||||||
|
const [repository, tag, createdAt] = image.split('#');
|
||||||
|
if (tag.includes('-')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const [year, time] = createdAt.split(' ');
|
||||||
|
imagesAvailables.push({
|
||||||
|
repository,
|
||||||
|
tag,
|
||||||
|
createdAt: day(year + time).unix()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
imagesAvailables = imagesAvailables.sort((a, b) => b.tag - a.tag);
|
||||||
|
|
||||||
|
return {
|
||||||
|
imagesAvailables,
|
||||||
|
runningImage
|
||||||
|
}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getUsageByContainer(request) {
|
export async function getUsageByContainer(request) {
|
||||||
try {
|
try {
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { OnlyId } from '../../../../types';
|
import { OnlyId } from '../../../../types';
|
||||||
import { cancelDeployment, checkDNS, checkDomain, checkRepository, cleanupUnconfiguredApplications, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, getUsageByContainer, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRegistry, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers';
|
import { cancelDeployment, checkDNS, checkDomain, checkRepository, cleanupUnconfiguredApplications, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getDockerImages, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, getUsageByContainer, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRegistry, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers';
|
||||||
|
|
||||||
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuilds, GetImages, RestartPreviewApplication, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
|
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuilds, GetImages, RestartApplication, RestartPreviewApplication, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.addHook('onRequest', async (request) => {
|
fastify.addHook('onRequest', async (request) => {
|
||||||
@@ -21,7 +21,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
|
|
||||||
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
|
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
|
||||||
|
|
||||||
fastify.post<OnlyId>('/:id/restart', async (request, reply) => await restartApplication(request, reply));
|
fastify.post<RestartApplication>('/:id/restart', async (request, reply) => await restartApplication(request, reply));
|
||||||
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
|
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
|
||||||
fastify.post<StopPreviewApplication>('/:id/stop/preview', async (request, reply) => await stopPreviewApplication(request, reply));
|
fastify.post<StopPreviewApplication>('/:id/stop/preview', async (request, reply) => await stopPreviewApplication(request, reply));
|
||||||
|
|
||||||
@@ -53,6 +53,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.get('/:id/usage', async (request) => await getUsage(request))
|
fastify.get('/:id/usage', async (request) => await getUsage(request))
|
||||||
fastify.get('/:id/usage/:containerId', async (request) => await getUsageByContainer(request))
|
fastify.get('/:id/usage/:containerId', async (request) => await getUsageByContainer(request))
|
||||||
|
|
||||||
|
fastify.get('/:id/images', async (request) => await getDockerImages(request))
|
||||||
|
|
||||||
fastify.post<DeployApplication>('/:id/deploy', async (request) => await deployApplication(request))
|
fastify.post<DeployApplication>('/:id/deploy', async (request) => await deployApplication(request))
|
||||||
fastify.post<CancelDeployment>('/:id/cancel', async (request, reply) => await cancelDeployment(request, reply));
|
fastify.post<CancelDeployment>('/:id/cancel', async (request, reply) => await cancelDeployment(request, reply));
|
||||||
|
|
||||||
|
@@ -141,4 +141,12 @@ export interface RestartPreviewApplication {
|
|||||||
id: string,
|
id: string,
|
||||||
pullmergeRequestId: string | null,
|
pullmergeRequestId: string | null,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
export interface RestartApplication {
|
||||||
|
Params: {
|
||||||
|
id: string,
|
||||||
|
},
|
||||||
|
Body: {
|
||||||
|
imageId: string | null,
|
||||||
|
}
|
||||||
}
|
}
|
@@ -11,15 +11,8 @@ export async function listAllSettings(request: FastifyRequest) {
|
|||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } })
|
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } })
|
||||||
let publicRegistries = await prisma.dockerRegistry.findMany({ where: { isSystemWide: true } })
|
let registries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId } } })
|
||||||
let privateRegistries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId }, isSystemWide: false } })
|
registries = registries.map((registry) => {
|
||||||
publicRegistries = publicRegistries.map((registry) => {
|
|
||||||
if (registry.password) {
|
|
||||||
registry.password = decrypt(registry.password)
|
|
||||||
}
|
|
||||||
return registry
|
|
||||||
})
|
|
||||||
privateRegistries = privateRegistries.map((registry) => {
|
|
||||||
if (registry.password) {
|
if (registry.password) {
|
||||||
registry.password = decrypt(registry.password)
|
registry.password = decrypt(registry.password)
|
||||||
}
|
}
|
||||||
@@ -42,10 +35,7 @@ export async function listAllSettings(request: FastifyRequest) {
|
|||||||
settings,
|
settings,
|
||||||
certificates: cns,
|
certificates: cns,
|
||||||
sshKeys: unencryptedKeys,
|
sshKeys: unencryptedKeys,
|
||||||
registries: {
|
registries
|
||||||
public: publicRegistries,
|
|
||||||
private: privateRegistries
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -221,11 +211,11 @@ export async function setDockerRegistry(request: FastifyRequest<SetDefaultRegist
|
|||||||
export async function addDockerRegistry(request: FastifyRequest<AddDefaultRegistry>, reply: FastifyReply) {
|
export async function addDockerRegistry(request: FastifyRequest<AddDefaultRegistry>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { name, url, username, password, isSystemWide } = request.body;
|
const { name, url, username, password } = request.body;
|
||||||
|
|
||||||
let encryptedPassword = ''
|
let encryptedPassword = ''
|
||||||
if (password) encryptedPassword = encrypt(password)
|
if (password) encryptedPassword = encrypt(password)
|
||||||
await prisma.dockerRegistry.create({ data: { name, url, username, password: encryptedPassword, isSystemWide, team: { connect: { id: teamId } } } })
|
await prisma.dockerRegistry.create({ data: { name, url, username, password: encryptedPassword, team: { connect: { id: teamId } } } })
|
||||||
|
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -236,7 +226,7 @@ export async function deleteDockerRegistry(request: FastifyRequest<OnlyIdInBody>
|
|||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.body;
|
const { id } = request.body;
|
||||||
await prisma.application.updateMany({ where: { dockerRegistryId: id }, data: { dockerRegistryId: '0' } })
|
await prisma.application.updateMany({ where: { dockerRegistryId: id }, data: { dockerRegistryId: null } })
|
||||||
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } })
|
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } })
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
|
@@ -23,7 +23,6 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.post<SetDefaultRegistry>('/registry', async (request, reply) => await setDockerRegistry(request, reply));
|
fastify.post<SetDefaultRegistry>('/registry', async (request, reply) => await setDockerRegistry(request, reply));
|
||||||
fastify.post<AddDefaultRegistry>('/registry/new', async (request, reply) => await addDockerRegistry(request, reply));
|
fastify.post<AddDefaultRegistry>('/registry/new', async (request, reply) => await addDockerRegistry(request, reply));
|
||||||
fastify.delete<OnlyIdInBody>('/registry', async (request, reply) => await deleteDockerRegistry(request, reply));
|
fastify.delete<OnlyIdInBody>('/registry', async (request, reply) => await deleteDockerRegistry(request, reply));
|
||||||
// fastify.delete<>('/registry', async (request, reply) => await deleteSSHKey(request, reply));
|
|
||||||
|
|
||||||
fastify.post('/upload', async (request) => {
|
fastify.post('/upload', async (request) => {
|
||||||
try {
|
try {
|
||||||
|
@@ -65,6 +65,5 @@ export interface AddDefaultRegistry {
|
|||||||
name: string
|
name: string
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
isSystemWide: boolean
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -268,7 +268,7 @@
|
|||||||
<a
|
<a
|
||||||
id="settings"
|
id="settings"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/ssh'}
|
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/docker'}
|
||||||
class="icons hover:text-settings"
|
class="icons hover:text-settings"
|
||||||
class:text-settings={$page.url.pathname.startsWith('/settings')}
|
class:text-settings={$page.url.pathname.startsWith('/settings')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
<a
|
<a
|
||||||
id="git"
|
id="git"
|
||||||
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
|
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
|
||||||
target="_blank"
|
target="_blank noreferrer"
|
||||||
class="no-underline"
|
class="no-underline"
|
||||||
>
|
>
|
||||||
{#if application.gitSource?.type === 'gitlab'}
|
{#if application.gitSource?.type === 'gitlab'}
|
||||||
@@ -165,7 +165,9 @@
|
|||||||
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/logs`}
|
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/logs`}
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={$status.application.overallStatus !== 'stopped' ? `/applications/${$page.params.id}/logs` : ''}
|
href={$status.application.overallStatus !== 'stopped'
|
||||||
|
? `/applications/${$page.params.id}/logs`
|
||||||
|
: ''}
|
||||||
class="no-underline w-full"
|
class="no-underline w-full"
|
||||||
><svg
|
><svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -216,12 +218,38 @@
|
|||||||
<li class="menu-title">
|
<li class="menu-title">
|
||||||
<span>Advanced</span>
|
<span>Advanced</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li
|
||||||
|
class="rounded"
|
||||||
|
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/revert`}
|
||||||
|
>
|
||||||
|
<a href={`/applications/${$page.params.id}/revert`} class="no-underline w-full">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M20 5v14l-12 -7z" />
|
||||||
|
<line x1="4" y1="5" x2="4" y2="19" />
|
||||||
|
</svg>
|
||||||
|
Revert</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
<li
|
<li
|
||||||
class="rounded"
|
class="rounded"
|
||||||
class:text-stone-600={$status.application.overallStatus !== 'healthy'}
|
class:text-stone-600={$status.application.overallStatus !== 'healthy'}
|
||||||
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/usage`}
|
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/usage`}
|
||||||
>
|
>
|
||||||
<a href={$status.application.overallStatus === 'healthy' ? `/applications/${$page.params.id}/usage` : ''} class="no-underline w-full"
|
<a
|
||||||
|
href={$status.application.overallStatus === 'healthy'
|
||||||
|
? `/applications/${$page.params.id}/usage`
|
||||||
|
: ''}
|
||||||
|
class="no-underline w-full"
|
||||||
><svg
|
><svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="w-6 h-6"
|
class="w-6 h-6"
|
||||||
|
@@ -96,6 +96,18 @@
|
|||||||
|
|
||||||
async function handleDeploySubmit(forceRebuild = false) {
|
async function handleDeploySubmit(forceRebuild = false) {
|
||||||
if (!$isDeploymentEnabled) return;
|
if (!$isDeploymentEnabled) return;
|
||||||
|
if (application.gitCommitHash && !application.settings.isPublicRepository) {
|
||||||
|
const sure = await confirm(
|
||||||
|
`Are you sure you want to deploy a specific commit (${application.gitCommitHash})? This will disable the "Automatic Deployment" feature to prevent accidental overwrites of incoming commits.`
|
||||||
|
);
|
||||||
|
if (!sure) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
await post(`/applications/${id}/settings`, {
|
||||||
|
autodeploy: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!statusInterval) {
|
if (!statusInterval) {
|
||||||
statusInterval = setInterval(async () => {
|
statusInterval = setInterval(async () => {
|
||||||
await getStatus();
|
await getStatus();
|
||||||
|
@@ -46,73 +46,48 @@
|
|||||||
|
|
||||||
<div class="flex flex-col justify-center w-full">
|
<div class="flex flex-col justify-center w-full">
|
||||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row mx-auto gap-4">
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row mx-auto gap-4">
|
||||||
{#each registries.public as registry}
|
{#if registries.length > 0}
|
||||||
<button
|
{#each registries as registry}
|
||||||
on:click={() => handleSubmit(registry.id)}
|
<button
|
||||||
class="box-selection hover:bg-primary relative"
|
on:click={() => handleSubmit(registry.id)}
|
||||||
>
|
class="box-selection hover:bg-primary relative"
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
|
|
||||||
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" />
|
<svg
|
||||||
<path
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
|
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
|
||||||
/>
|
viewBox="0 0 24 24"
|
||||||
<path d="M5 10h3v3h-3z" />
|
stroke-width="1.5"
|
||||||
<path d="M8 10h3v3h-3z" />
|
stroke="currentColor"
|
||||||
<path d="M11 10h3v3h-3z" />
|
fill="none"
|
||||||
<path d="M8 7h3v3h-3z" />
|
stroke-linecap="round"
|
||||||
<path d="M11 7h3v3h-3z" />
|
stroke-linejoin="round"
|
||||||
<path d="M11 4h3v3h-3z" />
|
>
|
||||||
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<line x1="10" y1="16" x2="10" y2="16.01" />
|
<path
|
||||||
</svg>
|
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
|
||||||
|
/>
|
||||||
|
<path d="M5 10h3v3h-3z" />
|
||||||
|
<path d="M8 10h3v3h-3z" />
|
||||||
|
<path d="M11 10h3v3h-3z" />
|
||||||
|
<path d="M8 7h3v3h-3z" />
|
||||||
|
<path d="M11 7h3v3h-3z" />
|
||||||
|
<path d="M11 4h3v3h-3z" />
|
||||||
|
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
|
||||||
|
<line x1="10" y1="16" x2="10" y2="16.01" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
<div class="font-bold text-xl text-center truncate">{registry.name}</div>
|
<div class="font-bold text-xl text-center truncate">{registry.name}</div>
|
||||||
<div class="text-center truncate">{registry.url}</div>
|
<div class="text-center truncate">{registry.url}</div>
|
||||||
<div>public</div>
|
</button>
|
||||||
</button>
|
{/each}
|
||||||
{/each}
|
{:else}
|
||||||
{#each registries.private as registry}
|
<div class="flex flex-col items-center gap-2">
|
||||||
<button
|
<div class="text-center text-xl font-bold pb-4">No registries found.</div>
|
||||||
on:click={() => handleSubmit(registry.id)}
|
<div class="flex gap-2">
|
||||||
class="box-selection hover:bg-primary relative"
|
<a class="btn btn-sm" href={from || `/applications/${id}`}>Go back</a>
|
||||||
>
|
<a class="btn btn-sm btn-primary" href={`/settings/docker`}>Add a Docker Registry</a>
|
||||||
<svg
|
</div>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
</div>
|
||||||
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
|
{/if}
|
||||||
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="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
|
|
||||||
/>
|
|
||||||
<path d="M5 10h3v3h-3z" />
|
|
||||||
<path d="M8 10h3v3h-3z" />
|
|
||||||
<path d="M11 10h3v3h-3z" />
|
|
||||||
<path d="M8 7h3v3h-3z" />
|
|
||||||
<path d="M11 7h3v3h-3z" />
|
|
||||||
<path d="M11 4h3v3h-3z" />
|
|
||||||
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
|
|
||||||
<line x1="10" y1="16" x2="10" y2="16.01" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<div class="font-bold text-xl text-center truncate">{registry.name}</div>
|
|
||||||
<div class="text-center truncate">{registry.url}</div>
|
|
||||||
<div>private</div>
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -515,40 +515,37 @@
|
|||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if application.settings.isPublicRepository}
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<label for="repository">Git commit</label>
|
||||||
<label for="repository">Git commit</label>
|
<div class="flex gap-2">
|
||||||
<div class="flex gap-2">
|
<input
|
||||||
<input
|
class="w-full"
|
||||||
class="w-full"
|
disabled={isDisabled}
|
||||||
disabled={isDisabled}
|
placeholder="default: latest commit"
|
||||||
placeholder="default: latest commit"
|
bind:value={application.gitCommitHash}
|
||||||
bind:value={application.gitCommitHash}
|
/>
|
||||||
/>
|
<a
|
||||||
<a
|
href="{application.gitSource
|
||||||
href="{application.gitSource
|
.htmlUrl}/{application.repository}/commits/{application.branch}"
|
||||||
.htmlUrl}/{application.repository}/commits/{application.branch}"
|
target="_blank noreferrer"
|
||||||
target="_blank"
|
class="btn btn-primary text-xs"
|
||||||
rel="noreferrer"
|
>Commits<svg
|
||||||
class="btn btn-primary text-xs"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>Commits<svg
|
fill="currentColor"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
viewBox="0 0 24 24"
|
||||||
fill="currentColor"
|
stroke-width="3"
|
||||||
viewBox="0 0 24 24"
|
stroke="currentColor"
|
||||||
stroke-width="3"
|
class="w-3 h-3 text-white ml-2"
|
||||||
stroke="currentColor"
|
|
||||||
class="w-3 h-3 text-white ml-2"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"
|
|
||||||
/>
|
|
||||||
</svg></a
|
|
||||||
>
|
>
|
||||||
</div>
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"
|
||||||
|
/>
|
||||||
|
</svg></a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="repository">{$t('application.git_repository')}</label>
|
<label for="repository">{$t('application.git_repository')}</label>
|
||||||
{#if isDisabled || application.settings.isPublicRepository}
|
{#if isDisabled || application.settings.isPublicRepository}
|
||||||
@@ -575,7 +572,7 @@
|
|||||||
<input
|
<input
|
||||||
class="capitalize w-full"
|
class="capitalize w-full"
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
value={application.dockerRegistry.name}
|
value={application.dockerRegistry?.name || 'DockerHub (unauthenticated)'}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<a
|
<a
|
||||||
@@ -583,7 +580,7 @@
|
|||||||
class="no-underline"
|
class="no-underline"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
value={application.dockerRegistry.name}
|
value={application.dockerRegistry?.name || 'DockerHub (unauthenticated)'}
|
||||||
id="registry"
|
id="registry"
|
||||||
class="cursor-pointer hover:bg-coolgray-500 capitalize w-full"
|
class="cursor-pointer hover:bg-coolgray-500 capitalize w-full"
|
||||||
/></a
|
/></a
|
||||||
|
119
apps/ui/src/routes/applications/[id]/revert.svelte
Normal file
119
apps/ui/src/routes/applications/[id]/revert.svelte
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<script context="module" lang="ts">
|
||||||
|
import type { Load } from '@sveltejs/kit';
|
||||||
|
export const load: Load = async ({ fetch, params, stuff, url }) => {
|
||||||
|
try {
|
||||||
|
const response = await get(`/applications/${params.id}/images`);
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
application: stuff.application,
|
||||||
|
...response
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
error: new Error(`Could not load ${url}`)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export let application: any;
|
||||||
|
export let imagesAvailables: any;
|
||||||
|
export let runningImage: any;
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { get, post } from '$lib/api';
|
||||||
|
import { status, addToast } from '$lib/store';
|
||||||
|
import { errorNotification } from '$lib/common';
|
||||||
|
|
||||||
|
const { id } = $page.params;
|
||||||
|
|
||||||
|
async function revertApplication(image: any) {
|
||||||
|
const sure = confirm(`Are you sure you want to revert to ${image.tag} ?`);
|
||||||
|
if (sure) {
|
||||||
|
try {
|
||||||
|
$status.application.initialLoading = true;
|
||||||
|
$status.application.loading = true;
|
||||||
|
const imageId = `${image.repository}:${image.tag}`;
|
||||||
|
await post(`/applications/${id}/restart`, { imageId });
|
||||||
|
addToast({
|
||||||
|
type: 'success',
|
||||||
|
message: 'Revert successful.'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
$status.application.initialLoading = false;
|
||||||
|
$status.application.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="mx-auto w-full">
|
||||||
|
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
|
||||||
|
<div class="title font-bold pb-3">Revert Application</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
You can revert application to a previously built image. Currently only locally stored images
|
||||||
|
supported.
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div class="pb-4">
|
||||||
|
If you do not want the next commit to overwrite the reverted application, temporary disable <span
|
||||||
|
class="text-yellow-400 font-bold">Automatic Deployment</span
|
||||||
|
>
|
||||||
|
feature <a href={`/applications/${id}/features`}>here</a>.
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="px-4 lg:pb-10 pb-6 flex flex-wrap items-center justify-center lg:justify-start gap-8"
|
||||||
|
>
|
||||||
|
{#each imagesAvailables as image}
|
||||||
|
<div class="gap-2 py-4 m-2">
|
||||||
|
<div class="flex flex-col justify-center items-center">
|
||||||
|
<div class="text-xl font-bold">
|
||||||
|
{image.tag}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
class="flex no-underline text-xs my-4"
|
||||||
|
href="{application.gitSource.htmlUrl}/{application.repository}/commit/{image.tag}"
|
||||||
|
target="_blank noreferrer"
|
||||||
|
>
|
||||||
|
<button class="btn btn-sm">
|
||||||
|
Check Commit
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="3"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="w-3 h-3 text-white ml-2"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button></a
|
||||||
|
>
|
||||||
|
{#if image.repository + ':' + image.tag !== runningImage}
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-primary w-full"
|
||||||
|
on:click={() => revertApplication(image)}>Revert Now</button
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<button class="btn btn-sm btn-primary w-full btn-disabled bg-transparent underline"
|
||||||
|
>Currently Used</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -4,10 +4,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul class="menu border bg-coolgray-100 border-coolgray-200 rounded p-2 space-y-2">
|
<ul class="menu border bg-coolgray-100 border-coolgray-200 rounded p-2 space-y-2">
|
||||||
|
<li class="menu-title">
|
||||||
|
<span>General</span>
|
||||||
|
</li>
|
||||||
{#if $appSession.teamId === '0'}
|
{#if $appSession.teamId === '0'}
|
||||||
<li class="menu-title">
|
|
||||||
<span>General</span>
|
|
||||||
</li>
|
|
||||||
<li class="rounded" class:bg-coollabs={$page.url.pathname === `/settings/coolify`}>
|
<li class="rounded" class:bg-coollabs={$page.url.pathname === `/settings/coolify`}>
|
||||||
<a href={`/settings/coolify`} class="no-underline w-full"
|
<a href={`/settings/coolify`} class="no-underline w-full"
|
||||||
><svg
|
><svg
|
||||||
@@ -27,35 +27,35 @@
|
|||||||
</svg>Coolify Settings</a
|
</svg>Coolify Settings</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="rounded" class:bg-coollabs={$page.url.pathname === `/settings/docker`}>
|
|
||||||
<a href={`/settings/docker`} class="no-underline w-full">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path
|
|
||||||
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
|
|
||||||
/>
|
|
||||||
<path d="M5 10h3v3h-3z" />
|
|
||||||
<path d="M8 10h3v3h-3z" />
|
|
||||||
<path d="M11 10h3v3h-3z" />
|
|
||||||
<path d="M8 7h3v3h-3z" />
|
|
||||||
<path d="M11 7h3v3h-3z" />
|
|
||||||
<path d="M11 4h3v3h-3z" />
|
|
||||||
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
|
|
||||||
<line x1="10" y1="16" x2="10" y2="16.01" />
|
|
||||||
</svg>Docker Registries</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
<li class="rounded" class:bg-coollabs={$page.url.pathname === `/settings/docker`}>
|
||||||
|
<a href={`/settings/docker`} class="no-underline w-full">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
|
||||||
|
/>
|
||||||
|
<path d="M5 10h3v3h-3z" />
|
||||||
|
<path d="M8 10h3v3h-3z" />
|
||||||
|
<path d="M11 10h3v3h-3z" />
|
||||||
|
<path d="M8 7h3v3h-3z" />
|
||||||
|
<path d="M11 7h3v3h-3z" />
|
||||||
|
<path d="M11 4h3v3h-3z" />
|
||||||
|
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
|
||||||
|
<line x1="10" y1="16" x2="10" y2="16.01" />
|
||||||
|
</svg>Docker Registries</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="menu-title">
|
<li class="menu-title">
|
||||||
<span>Keys & Certificates</span>
|
<span>Keys & Certificates</span>
|
||||||
</li>
|
</li>
|
||||||
|
@@ -22,17 +22,13 @@
|
|||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import { addToast } from '$lib/store';
|
import { addToast } from '$lib/store';
|
||||||
const publicRegistries = registries.public;
|
|
||||||
const privateRegistries = registries.private;
|
|
||||||
|
|
||||||
let isModalActive = false;
|
let isModalActive = false;
|
||||||
|
|
||||||
let newRegistry = {
|
let newRegistry = {
|
||||||
name: '',
|
name: '',
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
url: '',
|
url: ''
|
||||||
isSystemWide: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
@@ -71,6 +67,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function addRegistry(type: string) {
|
||||||
|
switch (type) {
|
||||||
|
case 'dockerhub':
|
||||||
|
newRegistry = {
|
||||||
|
name: 'Docker Hub',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
url: 'https://index.docker.io/v1/'
|
||||||
|
};
|
||||||
|
await handleSubmit();
|
||||||
|
break;
|
||||||
|
case 'gcrio':
|
||||||
|
newRegistry = {
|
||||||
|
name: 'Google Container Registry',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
url: 'https://gcr.io'
|
||||||
|
};
|
||||||
|
await handleSubmit();
|
||||||
|
break;
|
||||||
|
case 'github':
|
||||||
|
newRegistry = {
|
||||||
|
name: 'GitHub Container Registry',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
url: 'https://ghcr.io'
|
||||||
|
};
|
||||||
|
await handleSubmit();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
@@ -81,57 +108,34 @@
|
|||||||
>Add Docker Registry</label
|
>Add Docker Registry</label
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center pb-4 gap-2">
|
||||||
|
<div class="text-xs">Quick Action</div>
|
||||||
|
<button class="btn btn-sm text-xs" on:click={() => addRegistry('dockerhub')}>DockerHub</button>
|
||||||
|
<button class="btn btn-sm text-xs" on:click={() => addRegistry('gcrio')}
|
||||||
|
>Google Container Registry (gcr.io)</button
|
||||||
|
>
|
||||||
|
<button class="btn btn-sm text-xs" on:click={() => addRegistry('github')}
|
||||||
|
>GitHub Container Registry (ghcr.io)</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{#if registries.length > 0}
|
||||||
<div class="mx-auto w-full">
|
<div class="mx-auto w-full">
|
||||||
<table class="table w-full">
|
<table class="table w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>SystemWide</th>
|
|
||||||
<th>Username</th>
|
<th>Username</th>
|
||||||
<th>Password</th>
|
<th>Password</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each publicRegistries as registry}
|
{#each registries as registry}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{registry.name}<div class="text-xs">{registry.url}</div></td>
|
|
||||||
<td>{(registry.isSystemWide && 'Yes') || 'No'}</td>
|
|
||||||
<td>
|
|
||||||
<CopyPasswordField
|
|
||||||
name="username"
|
|
||||||
id="Username"
|
|
||||||
bind:value={registry.username}
|
|
||||||
placeholder="Username"
|
|
||||||
/></td
|
|
||||||
>
|
|
||||||
<td
|
<td
|
||||||
><CopyPasswordField
|
>{registry.name}
|
||||||
isPasswordField={true}
|
<div class="text-xs">{registry.url}</div></td
|
||||||
name="Password"
|
|
||||||
id="Password"
|
|
||||||
bind:value={registry.password}
|
|
||||||
placeholder="Password"
|
|
||||||
/></td
|
|
||||||
>
|
>
|
||||||
<td>
|
|
||||||
<button on:click={() => setRegistry(registry)} class="btn btn-sm btn-primary"
|
|
||||||
>Set</button
|
|
||||||
>
|
|
||||||
{#if registry.id !== '0'}
|
|
||||||
<button
|
|
||||||
on:click={() => deleteDockerRegistry(registry.id)}
|
|
||||||
class="btn btn-sm btn-error">Delete</button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
{#each privateRegistries as registry}
|
|
||||||
<tr>
|
|
||||||
<td>{registry.name} <div class="text-xs">{registry.url}</div></td>
|
|
||||||
<td>{(registry.isSystemWide && 'Yes') || 'No'}</td>
|
|
||||||
<td>
|
<td>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
name="username"
|
name="username"
|
||||||
@@ -166,6 +170,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if isModalActive}
|
{#if isModalActive}
|
||||||
|
Reference in New Issue
Block a user