commit
c43b848708
@ -8,4 +8,5 @@ package
|
||||
!.env.example
|
||||
dist
|
||||
client
|
||||
apps/api/db/*.db
|
||||
apps/api/db/*.db
|
||||
local-serve
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -8,4 +8,6 @@ package
|
||||
!.env.example
|
||||
dist
|
||||
client
|
||||
apps/api/db/*.db
|
||||
apps/api/db/*.db
|
||||
local-serve
|
||||
apps/api/db/migration.db-journal
|
11
.gitpod.yml
11
.gitpod.yml
@ -1,9 +1,12 @@
|
||||
# This configuration file was automatically generated by Gitpod.
|
||||
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
||||
# and commit this file to your remote git repository to share the goodness with others.
|
||||
|
||||
image: node:18
|
||||
tasks:
|
||||
- init: npm install && npm run build
|
||||
command: npm run start
|
||||
|
||||
- before: curl -sL https://unpkg.com/@pnpm/self-installer | node
|
||||
- init: pnpm install && pnpm db:push && pnpm db:seed
|
||||
command: pnpm dev
|
||||
|
||||
ports:
|
||||
- port: 3001
|
||||
visibility: public
|
@ -6,6 +6,7 @@
|
||||
"db:push": "prisma db push && prisma generate",
|
||||
"db:seed": "prisma db seed",
|
||||
"db:studio": "prisma studio",
|
||||
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
||||
"dev": "nodemon",
|
||||
"build": "rimraf build && esbuild `find src \\( -name '*.ts' \\)| grep -v client/` --platform=node --outdir=build --format=cjs",
|
||||
"format": "prettier --write 'src/**/*.{js,ts,json,md}'",
|
||||
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "deploymentType" TEXT;
|
@ -0,0 +1,24 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_GitSource" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"type" TEXT,
|
||||
"apiUrl" TEXT,
|
||||
"htmlUrl" TEXT,
|
||||
"customPort" INTEGER NOT NULL DEFAULT 22,
|
||||
"organization" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"githubAppId" TEXT,
|
||||
"gitlabAppId" TEXT,
|
||||
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt" FROM "GitSource";
|
||||
DROP TABLE "GitSource";
|
||||
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
|
||||
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
|
||||
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
@ -91,6 +91,7 @@ model Application {
|
||||
startCommand String?
|
||||
baseDirectory String?
|
||||
publishDirectory String?
|
||||
deploymentType String?
|
||||
phpModules String?
|
||||
pythonWSGI String?
|
||||
pythonModule String?
|
||||
@ -217,6 +218,7 @@ model GitSource {
|
||||
type String?
|
||||
apiUrl String?
|
||||
htmlUrl String?
|
||||
customPort Int @default(22)
|
||||
organization String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
@ -68,7 +68,8 @@ const schema = {
|
||||
};
|
||||
|
||||
const options = {
|
||||
schema
|
||||
schema,
|
||||
dotenv: true
|
||||
};
|
||||
fastify.register(env, options);
|
||||
if (!isDev) {
|
||||
@ -76,7 +77,7 @@ if (!isDev) {
|
||||
root: path.join(__dirname, './public'),
|
||||
preCompressed: true
|
||||
});
|
||||
fastify.setNotFoundHandler(function (request, reply) {
|
||||
fastify.setNotFoundHandler({}, function (request, reply) {
|
||||
if (request.raw.url && request.raw.url.startsWith('/api')) {
|
||||
return reply.status(404).send({
|
||||
success: false
|
||||
@ -105,23 +106,30 @@ fastify.listen({ port, host }, async (err: any, address: any) => {
|
||||
await scheduler.start('cleanupStorage');
|
||||
await scheduler.start('checkProxies')
|
||||
|
||||
// Check if no build is running, try to autoupdate.
|
||||
// Check if no build is running
|
||||
|
||||
// Check for update
|
||||
setInterval(async () => {
|
||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||
if (isAutoUpdateEnabled) {
|
||||
if (scheduler.workers.has('deployApplication')) {
|
||||
scheduler.workers.get('deployApplication').postMessage("status");
|
||||
scheduler.workers.get('deployApplication').postMessage("status:autoUpdater");
|
||||
}
|
||||
}
|
||||
}, 30000 * 10)
|
||||
}, 60000 * 15)
|
||||
|
||||
// Cleanup storage
|
||||
setInterval(async () => {
|
||||
if (scheduler.workers.has('deployApplication')) {
|
||||
scheduler.workers.get('deployApplication').postMessage("status:cleanupStorage");
|
||||
}
|
||||
}, 60000 * 10)
|
||||
|
||||
scheduler.on('worker deleted', async (name) => {
|
||||
if (name === 'autoUpdater') {
|
||||
await scheduler.start('deployApplication');
|
||||
if (name === 'autoUpdater' || name === 'cleanupStorage') {
|
||||
if (!scheduler.workers.has('deployApplication')) await scheduler.start('deployApplication');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
async function initServer() {
|
||||
|
@ -4,7 +4,7 @@ import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
|
||||
import { asyncExecShell, createDirectories, decrypt, getDomain, prisma } from '../lib/common';
|
||||
import { asyncExecShell, createDirectories, decrypt, getDomain, prisma } from '../lib/common';
|
||||
import { dockerInstance, getEngine } from '../lib/docker';
|
||||
import * as importers from '../lib/importers';
|
||||
import * as buildpacks from '../lib/buildPacks';
|
||||
@ -21,8 +21,12 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
parentPort.postMessage('cancelled');
|
||||
return;
|
||||
}
|
||||
if (message === 'status') {
|
||||
parentPort.postMessage({ size: queue.size, pending: queue.pending });
|
||||
if (message === 'status:autoUpdater') {
|
||||
parentPort.postMessage({ size: queue.size, pending: queue.pending, caller: 'autoUpdater' });
|
||||
return;
|
||||
}
|
||||
if (message === 'status:cleanupStorage') {
|
||||
parentPort.postMessage({ size: queue.size, pending: queue.pending, caller: 'cleanupStorage' });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -51,7 +55,8 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
denoOptions,
|
||||
exposePort,
|
||||
baseImage,
|
||||
baseBuildImage
|
||||
baseBuildImage,
|
||||
deploymentType,
|
||||
} = message
|
||||
let {
|
||||
branch,
|
||||
@ -122,6 +127,7 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
repodir,
|
||||
githubAppId: gitSource.githubApp?.id,
|
||||
gitlabAppId: gitSource.gitlabApp?.id,
|
||||
customPort: gitSource.customPort,
|
||||
repository,
|
||||
branch,
|
||||
buildId,
|
||||
@ -221,7 +227,8 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
denoMainFile,
|
||||
denoOptions,
|
||||
baseImage,
|
||||
baseBuildImage
|
||||
baseBuildImage,
|
||||
deploymentType
|
||||
});
|
||||
else {
|
||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||
|
@ -17,7 +17,7 @@ const nodeBased = [
|
||||
'nextjs'
|
||||
];
|
||||
|
||||
export function setDefaultBaseImage(buildPack: string | null) {
|
||||
export function setDefaultBaseImage(buildPack: string | null, deploymentType: string | null = null) {
|
||||
const nodeVersions = [
|
||||
{
|
||||
value: 'node:lts',
|
||||
@ -259,10 +259,17 @@ export function setDefaultBaseImage(buildPack: string | null) {
|
||||
baseBuildImages: []
|
||||
};
|
||||
if (nodeBased.includes(buildPack)) {
|
||||
payload.baseImage = 'node:lts';
|
||||
payload.baseImages = nodeVersions;
|
||||
payload.baseBuildImage = 'node:lts';
|
||||
payload.baseBuildImages = nodeVersions;
|
||||
if (deploymentType === 'static') {
|
||||
payload.baseImage = 'webdevops/nginx:alpine';
|
||||
payload.baseImages = staticVersions;
|
||||
payload.baseBuildImage = 'node:lts';
|
||||
payload.baseBuildImages = nodeVersions;
|
||||
} else {
|
||||
payload.baseImage = 'node:lts';
|
||||
payload.baseImages = nodeVersions;
|
||||
payload.baseBuildImage = 'node:lts';
|
||||
payload.baseBuildImages = nodeVersions;
|
||||
}
|
||||
}
|
||||
if (staticApps.includes(buildPack)) {
|
||||
payload.baseImage = 'webdevops/nginx:alpine';
|
||||
@ -431,7 +438,7 @@ export async function copyBaseConfigurationFiles(
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
} else if (staticApps.includes(buildPack) && baseImage.includes('nginx')) {
|
||||
} else if (baseImage?.includes('nginx')) {
|
||||
await fs.writeFile(
|
||||
`${workdir}/nginx.conf`,
|
||||
`user nginx;
|
||||
|
@ -42,9 +42,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''}/deps.ts /app`);
|
||||
Dockerfile.push(`RUN deno cache deps.ts`);
|
||||
}
|
||||
Dockerfile.push(`COPY ${denoMainFile} /app`);
|
||||
Dockerfile.push(`RUN deno cache ${denoMainFile}`);
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN deno cache ${denoMainFile}`);
|
||||
Dockerfile.push(`ENV NO_COLOR true`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD deno run ${denoOptions ? denoOptions.split(' ') : ''} ${denoMainFile}`);
|
||||
|
@ -9,7 +9,7 @@ const createDockerfile = async (data, imageforBuild): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
if (baseImage.includes('nginx')) {
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
|
@ -1,17 +1,22 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage, checkPnpm } from './common';
|
||||
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const {
|
||||
applicationId,
|
||||
buildId,
|
||||
tag,
|
||||
workdir,
|
||||
publishDirectory,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId
|
||||
pullmergeRequestId,
|
||||
deploymentType,
|
||||
baseImage
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
const isPnpm = checkPnpm(installCommand, buildCommand, startCommand);
|
||||
@ -36,22 +41,34 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
|
||||
if (buildCommand) {
|
||||
if (deploymentType === 'node') {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
} else if (deploymentType === 'static') {
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const { baseImage, baseBuildImage } = data;
|
||||
await createDockerfile(data, baseImage);
|
||||
await buildImage(data);
|
||||
const { baseImage, baseBuildImage, deploymentType, buildCommand } = data;
|
||||
if (deploymentType === 'node') {
|
||||
await createDockerfile(data, baseImage);
|
||||
await buildImage(data);
|
||||
} else if (deploymentType === 'static') {
|
||||
if (buildCommand) await buildCacheImageWithNode(data, baseBuildImage);
|
||||
await createDockerfile(data, baseImage);
|
||||
await buildImage(data);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage, checkPnpm } from './common';
|
||||
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const {
|
||||
applicationId,
|
||||
buildId,
|
||||
tag,
|
||||
workdir,
|
||||
publishDirectory,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
@ -11,7 +15,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
baseDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId,
|
||||
buildId
|
||||
deploymentType,
|
||||
baseImage
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
const isPnpm = checkPnpm(installCommand, buildCommand, startCommand);
|
||||
@ -36,21 +41,34 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
if (buildCommand) {
|
||||
if (deploymentType === 'node') {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
} else if (deploymentType === 'static') {
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const { baseImage, baseBuildImage } = data;
|
||||
await createDockerfile(data, baseImage);
|
||||
await buildImage(data);
|
||||
const { baseImage, baseBuildImage, deploymentType, buildCommand } = data;
|
||||
if (deploymentType === 'node') {
|
||||
await createDockerfile(data, baseImage);
|
||||
await buildImage(data);
|
||||
} else if (deploymentType === 'static') {
|
||||
if (buildCommand) await buildCacheImageWithNode(data, baseBuildImage);
|
||||
await createDockerfile(data, baseImage);
|
||||
await buildImage(data);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
if (baseImage.includes('nginx')) {
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
|
@ -40,7 +40,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
} else {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
}
|
||||
if (baseImage.includes('nginx')) {
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
|
@ -9,7 +9,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
if (baseImage.includes('nginx')) {
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
|
@ -9,7 +9,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
if (baseImage.includes('nginx')) {
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
|
@ -15,6 +15,9 @@ import { checkContainer, getEngine, removeContainer } from './docker';
|
||||
import { day } from './dayjs';
|
||||
import * as serviceFields from './serviceFields'
|
||||
|
||||
export const version = '3.1.0';
|
||||
export const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
const algorithm = 'aes-256-ctr';
|
||||
const customConfig: Config = {
|
||||
dictionaries: [adjectives, colors, animals],
|
||||
@ -22,8 +25,6 @@ const customConfig: Config = {
|
||||
separator: ' ',
|
||||
length: 3
|
||||
};
|
||||
export const isDev = process.env.NODE_ENV === 'development';
|
||||
export const version = '3.0.3';
|
||||
|
||||
export const defaultProxyImage = `coolify-haproxy-alpine:latest`;
|
||||
export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`;
|
||||
|
@ -11,7 +11,8 @@ export default async function ({
|
||||
apiUrl,
|
||||
htmlUrl,
|
||||
branch,
|
||||
buildId
|
||||
buildId,
|
||||
customPort
|
||||
}: {
|
||||
applicationId: string;
|
||||
workdir: string;
|
||||
@ -21,6 +22,7 @@ export default async function ({
|
||||
htmlUrl: string;
|
||||
branch: string;
|
||||
buildId: string;
|
||||
customPort: number;
|
||||
}): Promise<string> {
|
||||
const { default: got } = await import('got')
|
||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
||||
@ -54,7 +56,7 @@ export default async function ({
|
||||
applicationId
|
||||
});
|
||||
await asyncExecShell(
|
||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git ${workdir}/ && cd ${workdir} && 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 submodule update --init --recursive && git lfs pull && cd .. `
|
||||
);
|
||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||
return commit.replace('\n', '');
|
||||
|
@ -9,7 +9,8 @@ export default async function ({
|
||||
repository,
|
||||
branch,
|
||||
buildId,
|
||||
privateSshKey
|
||||
privateSshKey,
|
||||
customPort
|
||||
}: {
|
||||
applicationId: string;
|
||||
workdir: string;
|
||||
@ -19,6 +20,7 @@ export default async function ({
|
||||
buildId: string;
|
||||
repodir: string;
|
||||
privateSshKey: string;
|
||||
customPort: number;
|
||||
}): Promise<string> {
|
||||
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
||||
await saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
|
||||
@ -32,7 +34,7 @@ export default async function ({
|
||||
});
|
||||
|
||||
await asyncExecShell(
|
||||
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && 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 submodule update --init --recursive && git lfs pull && cd .. `
|
||||
);
|
||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||
return commit.replace('\n', '');
|
||||
|
@ -11,11 +11,20 @@ const options: any = {
|
||||
logger: false,
|
||||
workerMessageHandler: async ({ name, message }) => {
|
||||
if (name === 'deployApplication') {
|
||||
if (message.pending === 0) {
|
||||
if (!scheduler.workers.has('autoUpdater')) {
|
||||
await scheduler.stop('deployApplication');
|
||||
await scheduler.run('autoUpdater')
|
||||
if (message.pending === 0 && message.size === 0) {
|
||||
if (message.caller === 'autoUpdater') {
|
||||
if (!scheduler.workers.has('autoUpdater')) {
|
||||
await scheduler.stop('deployApplication');
|
||||
await scheduler.run('autoUpdater')
|
||||
}
|
||||
}
|
||||
if (message.caller === 'cleanupStorage') {
|
||||
if (!scheduler.workers.has('cleanupStorage')) {
|
||||
await scheduler.stop('deployApplication');
|
||||
await scheduler.run('cleanupStorage')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -25,7 +34,6 @@ const options: any = {
|
||||
},
|
||||
{
|
||||
name: 'cleanupStorage',
|
||||
interval: '10m'
|
||||
},
|
||||
{
|
||||
name: 'checkProxies',
|
||||
|
@ -173,6 +173,14 @@ export const wordpress = [{
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'ftpPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
}]
|
||||
export const ghost = [{
|
||||
name: 'defaultEmail',
|
||||
|
@ -31,6 +31,40 @@ export async function listApplications(request: FastifyRequest) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function getImages(request: FastifyRequest) {
|
||||
try {
|
||||
const { buildPack, deploymentType } = request.body
|
||||
let publishDirectory = undefined;
|
||||
let port = undefined
|
||||
const { baseImage, baseBuildImage, baseBuildImages, baseImages, } = setDefaultBaseImage(
|
||||
buildPack, deploymentType
|
||||
);
|
||||
if (buildPack === 'nextjs') {
|
||||
if (deploymentType === 'static') {
|
||||
publishDirectory = 'out'
|
||||
port = '80'
|
||||
} else {
|
||||
publishDirectory = ''
|
||||
port = '3000'
|
||||
}
|
||||
}
|
||||
if (buildPack === 'nuxtjs') {
|
||||
if (deploymentType === 'static') {
|
||||
publishDirectory = 'dist'
|
||||
port = '80'
|
||||
} else {
|
||||
publishDirectory = ''
|
||||
port = '3000'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return { baseImage, baseBuildImage, baseBuildImages, baseImages, publishDirectory, port }
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
|
||||
export async function getApplication(request: FastifyRequest<GetApplication>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
@ -184,7 +218,8 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
||||
denoMainFile,
|
||||
denoOptions,
|
||||
baseImage,
|
||||
baseBuildImage
|
||||
baseBuildImage,
|
||||
deploymentType
|
||||
} = request.body
|
||||
|
||||
if (port) port = Number(port);
|
||||
@ -215,6 +250,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
||||
denoOptions,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType,
|
||||
...defaultConfiguration
|
||||
}
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { cancelDeployment, checkDNS, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers';
|
||||
import { cancelDeployment, checkDNS, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers';
|
||||
|
||||
export interface GetApplication {
|
||||
Params: { id: string; }
|
||||
@ -37,6 +37,7 @@ const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||
return await request.jwtVerify()
|
||||
})
|
||||
fastify.get('/', async (request) => await listApplications(request));
|
||||
fastify.post('/images', async (request) => await getImages(request));
|
||||
|
||||
fastify.post('/new', async (request, reply) => await newApplication(request, reply));
|
||||
|
||||
@ -67,7 +68,7 @@ const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||
|
||||
fastify.post<DeployApplication>('/:id/deploy', async (request) => await deployApplication(request))
|
||||
fastify.post('/:id/cancel', async (request, reply) => await cancelDeployment(request, reply));
|
||||
|
||||
|
||||
fastify.post('/:id/configuration/source', async (request, reply) => await saveApplicationSource(request, reply));
|
||||
|
||||
fastify.get('/:id/configuration/repository', async (request) => await checkRepository(request));
|
||||
|
19
apps/api/src/routes/api/v1/base/index.ts
Normal file
19
apps/api/src/routes/api/v1/base/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { errorHandler, version } from '../../../../lib/common';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||
fastify.get('/', async (request) => {
|
||||
try {
|
||||
return {
|
||||
version,
|
||||
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
||||
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
export default root;
|
@ -258,11 +258,12 @@ export async function getCurrentUser(request: FastifyRequest, fastify) {
|
||||
include: { teams: true, permission: true }
|
||||
})
|
||||
if (user) {
|
||||
const permission = user.permission.find(p => p.teamId === request.query.teamId).permission
|
||||
const payload = {
|
||||
...request.user,
|
||||
teamId: request.query.teamId,
|
||||
permission: user.permission.find(p => p.teamId === request.query.teamId).permission || null,
|
||||
isAdmin: user.permission.find(p => p.teamId === request.query.teamId).permission === 'owner'
|
||||
permission: permission || null,
|
||||
isAdmin: permission === 'owner' || permission === 'admin'
|
||||
|
||||
}
|
||||
token = fastify.jwt.sign(payload)
|
||||
|
@ -2,9 +2,10 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, supportedServiceTypesAndVersions } from '../../../../lib/common';
|
||||
import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, supportedServiceTypesAndVersions, generatePassword, isDev, stopTcpHttpProxy } from '../../../../lib/common';
|
||||
import { day } from '../../../../lib/dayjs';
|
||||
import { checkContainer, dockerInstance, getEngine, removeContainer } from '../../../../lib/docker';
|
||||
import cuid from 'cuid';
|
||||
|
||||
export async function listServices(request: FastifyRequest) {
|
||||
try {
|
||||
@ -42,7 +43,7 @@ export async function getService(request: FastifyRequest) {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.params;
|
||||
const service = await getServiceFromDB({ id, teamId });
|
||||
|
||||
|
||||
if (!service) {
|
||||
throw { status: 404, message: 'Service not found.' }
|
||||
}
|
||||
@ -237,18 +238,20 @@ export async function checkService(request: FastifyRequest) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
let { fqdn, exposePort, otherFqdns } = request.body;
|
||||
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase());
|
||||
if (exposePort) exposePort = Number(exposePort);
|
||||
|
||||
let found = await isDomainConfigured({ id, fqdn });
|
||||
if (found) {
|
||||
throw `Domain already configured.`
|
||||
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
||||
}
|
||||
if (otherFqdns && otherFqdns.length > 0) {
|
||||
for (const ofqdn of otherFqdns) {
|
||||
found = await isDomainConfigured({ id, fqdn: ofqdn, checkOwn: true });
|
||||
if (found) {
|
||||
throw "Domain already configured."
|
||||
throw { status: 500, message: `Domain ${getDomain(ofqdn).replace('www.', '')} is already in use!` }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -257,12 +260,12 @@ export async function checkService(request: FastifyRequest) {
|
||||
exposePort = Number(exposePort);
|
||||
|
||||
if (exposePort < 1024 || exposePort > 65535) {
|
||||
throw `Exposed Port needs to be between 1024 and 65535.`
|
||||
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
||||
}
|
||||
|
||||
const publicPort = await getPort({ port: exposePort });
|
||||
if (publicPort !== exposePort) {
|
||||
throw `Port ${exposePort} is already in use.`
|
||||
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
||||
}
|
||||
}
|
||||
return {}
|
||||
@ -2414,4 +2417,168 @@ export async function activatePlausibleUsers(request: FastifyRequest, reply: Fas
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
}
|
||||
export async function activateWordpressFtp(request: FastifyRequest, reply: FastifyReply) {
|
||||
const { id } = request.params
|
||||
const teamId = request.user.teamId;
|
||||
|
||||
const { ftpEnabled } = request.body;
|
||||
|
||||
const publicPort = await getFreePort();
|
||||
let ftpUser = cuid();
|
||||
let ftpPassword = generatePassword();
|
||||
|
||||
const hostkeyDir = isDev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
|
||||
try {
|
||||
const data = await prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpEnabled },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
const {
|
||||
service: { destinationDockerId, destinationDocker },
|
||||
ftpPublicPort,
|
||||
ftpUser: user,
|
||||
ftpPassword: savedPassword,
|
||||
ftpHostKey,
|
||||
ftpHostKeyPrivate
|
||||
} = data;
|
||||
const { network, engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
if (ftpEnabled) {
|
||||
if (user) ftpUser = user;
|
||||
if (savedPassword) ftpPassword = decrypt(savedPassword);
|
||||
|
||||
const { stdout: password } = await asyncExecShell(
|
||||
`echo ${ftpPassword} | openssl passwd -1 -stdin`
|
||||
);
|
||||
if (destinationDockerId) {
|
||||
try {
|
||||
await fs.stat(hostkeyDir);
|
||||
} catch (error) {
|
||||
await asyncExecShell(`mkdir -p ${hostkeyDir}`);
|
||||
}
|
||||
if (!ftpHostKey) {
|
||||
await asyncExecShell(
|
||||
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
|
||||
);
|
||||
const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`);
|
||||
await prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpHostKey: encrypt(ftpHostKey) }
|
||||
});
|
||||
} else {
|
||||
await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`);
|
||||
}
|
||||
if (!ftpHostKeyPrivate) {
|
||||
await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`);
|
||||
const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`);
|
||||
await prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
|
||||
});
|
||||
} else {
|
||||
await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`);
|
||||
}
|
||||
|
||||
await prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: {
|
||||
ftpPublicPort: publicPort,
|
||||
ftpUser: user ? undefined : ftpUser,
|
||||
ftpPassword: savedPassword ? undefined : encrypt(ftpPassword)
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const isRunning = await checkContainer(engine, `${id}-ftp`);
|
||||
if (isRunning) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
//
|
||||
}
|
||||
const volumes = [
|
||||
`${id}-wordpress-data:/home/${ftpUser}/wordpress`,
|
||||
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
|
||||
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
|
||||
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.sh:/etc/sftp.d/chmod.sh`
|
||||
];
|
||||
|
||||
const compose: ComposeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[`${id}-ftp`]: {
|
||||
image: `atmoz/sftp:alpine`,
|
||||
command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`,
|
||||
extra_hosts: ['host.docker.internal:host-gateway'],
|
||||
container_name: `${id}-ftp`,
|
||||
volumes,
|
||||
networks: [network],
|
||||
depends_on: [],
|
||||
restart: 'always'
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[`${id}-wordpress-data`]: {
|
||||
external: true,
|
||||
name: `${id}-wordpress-data`
|
||||
}
|
||||
}
|
||||
};
|
||||
await fs.writeFile(
|
||||
`${hostkeyDir}/${id}.sh`,
|
||||
`#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key\nuserdel -f xfs\nchown -R 33:33 /home/${ftpUser}/wordpress/`
|
||||
);
|
||||
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
|
||||
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
|
||||
);
|
||||
}
|
||||
return reply.code(201).send({
|
||||
publicPort,
|
||||
ftpUser,
|
||||
ftpPassword
|
||||
})
|
||||
} else {
|
||||
await prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpPublicPort: null }
|
||||
});
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
||||
);
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
await stopTcpHttpProxy(id, destinationDocker, ftpPublicPort);
|
||||
return {
|
||||
};
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
} finally {
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import {
|
||||
activatePlausibleUsers,
|
||||
activateWordpressFtp,
|
||||
checkService,
|
||||
deleteService,
|
||||
deleteServiceSecret,
|
||||
@ -65,6 +66,7 @@ const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||
fastify.post('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply));
|
||||
|
||||
fastify.post('/:id/plausibleanalytics/activate', async (request, reply) => await activatePlausibleUsers(request, reply));
|
||||
fastify.post('/:id/wordpress/ftp', async (request, reply) => await activateWordpressFtp(request, reply));
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
@ -20,10 +20,11 @@ export async function listSources(request: FastifyRequest) {
|
||||
export async function saveSource(request, reply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { name, htmlUrl, apiUrl } = request.body
|
||||
let { name, htmlUrl, apiUrl, customPort } = request.body
|
||||
if (customPort) customPort = Number(customPort)
|
||||
await prisma.gitSource.update({
|
||||
where: { id },
|
||||
data: { name, htmlUrl, apiUrl }
|
||||
data: { name, htmlUrl, apiUrl, customPort }
|
||||
});
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
@ -45,7 +46,8 @@ export async function getSource(request: FastifyRequest) {
|
||||
type: null,
|
||||
htmlUrl: null,
|
||||
apiUrl: null,
|
||||
organization: null
|
||||
organization: null,
|
||||
customPort: 22,
|
||||
},
|
||||
settings
|
||||
}
|
||||
@ -58,7 +60,7 @@ export async function getSource(request: FastifyRequest) {
|
||||
if (!source) {
|
||||
throw { status: 404, message: 'Source not found.' }
|
||||
}
|
||||
|
||||
|
||||
if (source?.githubApp?.clientSecret)
|
||||
source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
|
||||
if (source?.githubApp?.webhookSecret)
|
||||
@ -97,9 +99,12 @@ export async function deleteSource(request) {
|
||||
}
|
||||
export async function saveGitHubSource(request: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { name, type, htmlUrl, apiUrl, organization } = request.body
|
||||
const { teamId } = request.user
|
||||
|
||||
const { id } = request.params
|
||||
let { name, type, htmlUrl, apiUrl, organization, customPort } = request.body
|
||||
|
||||
if (customPort) customPort = Number(customPort)
|
||||
if (id === 'new') {
|
||||
const newId = cuid()
|
||||
await prisma.gitSource.create({
|
||||
@ -109,6 +114,7 @@ export async function saveGitHubSource(request: FastifyRequest, reply: FastifyRe
|
||||
htmlUrl,
|
||||
apiUrl,
|
||||
organization,
|
||||
customPort,
|
||||
type: 'github',
|
||||
teams: { connect: { id: teamId } }
|
||||
}
|
||||
@ -126,15 +132,16 @@ export async function saveGitLabSource(request: FastifyRequest, reply: FastifyRe
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { teamId } = request.user
|
||||
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName } =
|
||||
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort } =
|
||||
request.body
|
||||
|
||||
oauthId = Number(oauthId);
|
||||
if (oauthId) oauthId = Number(oauthId);
|
||||
if (customPort) customPort = Number(customPort)
|
||||
const encryptedAppSecret = encrypt(appSecret);
|
||||
|
||||
if (id === 'new') {
|
||||
const newId = cuid()
|
||||
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, teams: { connect: { id: teamId } } } });
|
||||
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, teams: { connect: { id: teamId } } } });
|
||||
await prisma.gitlabApp.create({
|
||||
data: {
|
||||
teams: { connect: { id: teamId } },
|
||||
@ -150,7 +157,7 @@ export async function saveGitLabSource(request: FastifyRequest, reply: FastifyRe
|
||||
id: newId
|
||||
}
|
||||
} else {
|
||||
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name } });
|
||||
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort } });
|
||||
await prisma.gitlabApp.update({
|
||||
where: { id },
|
||||
data: {
|
||||
|
3
apps/ui/src/app.d.ts
vendored
3
apps/ui/src/app.d.ts
vendored
@ -19,3 +19,6 @@ declare namespace App {
|
||||
privatePort: string;
|
||||
}
|
||||
}
|
||||
|
||||
declare const GITPOD_WORKSPACE_URL: string
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { browser, dev } from '$app/env';
|
||||
import Cookies from 'js-cookie';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
|
||||
export function getAPIUrl() {
|
||||
if (GITPOD_WORKSPACE_URL) {
|
||||
const {href} = new URL(GITPOD_WORKSPACE_URL)
|
||||
const newURL = href.replace('https://','https://3001-').replace(/\/$/,'')
|
||||
return newURL
|
||||
}
|
||||
return dev ? 'http://localhost:3001' : 'http://localhost:3000';
|
||||
}
|
||||
async function send({
|
||||
@ -52,7 +56,7 @@ async function send({
|
||||
}
|
||||
|
||||
if (dev && !path.startsWith('https://')) {
|
||||
path = `http://localhost:3001${path}`;
|
||||
path = `${getAPIUrl()}${path}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${path}`, opts);
|
||||
@ -74,7 +78,7 @@ async function send({
|
||||
return {};
|
||||
}
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 && !path.startsWith('https://api.github')) {
|
||||
if (response.status === 401 && !path.startsWith('https://api.github') && !path.includes('/v4/user')) {
|
||||
Cookies.remove('token');
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,10 @@ export const asyncSleep = (delay: number) =>
|
||||
|
||||
export function errorNotification(error: any): void {
|
||||
if (error.message) {
|
||||
if (error.message === 'Cannot read properties of undefined (reading \'postMessage\')') {
|
||||
toast.push('Currently there is background process in progress. Please try again later.');
|
||||
return;
|
||||
}
|
||||
toast.push(error.message);
|
||||
} else {
|
||||
toast.push('Ooops, something is not okay, are you okay?');
|
||||
|
@ -180,7 +180,7 @@
|
||||
"domain_already_in_use": "Domain {{domain}} is already used.",
|
||||
"dns_not_set_error": "DNS not set correctly or propogated for {{domain}}.<br><br>Please check your DNS settings.",
|
||||
"domain_required": "Domain is required.",
|
||||
"settings_saved": "Settings saved.",
|
||||
"settings_saved": "Configuration saved.",
|
||||
"dns_not_set_partial_error": "DNS not set",
|
||||
"domain_not_valid": "Could not resolve domain or it's not pointing to the server IP address.<br><br>Please check your DNS configuration and try again.",
|
||||
"git_source": "Git Source",
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { browser } from '$app/env';
|
||||
import { writable, readable, type Writable, type Readable } from 'svelte/store';
|
||||
// import { version as currentVersion } from '../../package.json';
|
||||
|
||||
interface AppSession {
|
||||
version: string
|
||||
version: string | null,
|
||||
userId: string | null,
|
||||
teamId: string | null,
|
||||
permission: string,
|
||||
@ -18,7 +17,7 @@ interface AppSession {
|
||||
}
|
||||
export const loginEmail: Writable<string | undefined> = writable()
|
||||
export const appSession: Writable<AppSession> = writable({
|
||||
version: '3.0.3',
|
||||
version: null,
|
||||
userId: null,
|
||||
teamId: null,
|
||||
permission: 'read',
|
||||
|
@ -70,7 +70,8 @@ export function findBuildPack(pack: string, packageManager = 'npm') {
|
||||
...metaData,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
publishDirectory: null,
|
||||
port: 3000
|
||||
port: 3000,
|
||||
deploymentType: 'node'
|
||||
};
|
||||
}
|
||||
if (pack === 'gatsby') {
|
||||
@ -94,7 +95,8 @@ export function findBuildPack(pack: string, packageManager = 'npm') {
|
||||
...metaData,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
publishDirectory: null,
|
||||
port: 3000
|
||||
port: 3000,
|
||||
deploymentType: 'node'
|
||||
};
|
||||
}
|
||||
if (pack === 'preact') {
|
||||
|
@ -1,12 +1,14 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ url }) => {
|
||||
const baseSettings = await get('/base');
|
||||
try {
|
||||
if (Cookies.get('token')) {
|
||||
const response = await get(`/user`);
|
||||
return {
|
||||
props: {
|
||||
...response
|
||||
...response,
|
||||
baseSettings
|
||||
},
|
||||
stuff: {
|
||||
...response
|
||||
@ -16,10 +18,17 @@
|
||||
if (url.pathname !== '/login' && url.pathname !== '/register') {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/login'
|
||||
redirect: '/login',
|
||||
props: {
|
||||
baseSettings
|
||||
}
|
||||
};
|
||||
}
|
||||
return {};
|
||||
return {
|
||||
props: {
|
||||
baseSettings
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error?.code?.startsWith('FAST_JWT') || error.status === 401) {
|
||||
@ -27,25 +36,39 @@
|
||||
if (url.pathname !== '/login') {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/login'
|
||||
redirect: '/login',
|
||||
props: {
|
||||
baseSettings
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
if (url.pathname !== '/login') {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/login'
|
||||
redirect: '/login',
|
||||
props: {
|
||||
baseSettings
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: 500,
|
||||
error: new Error(error)
|
||||
error: new Error(error),
|
||||
props: {
|
||||
baseSettings
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let baseSettings: any;
|
||||
$appSession.version = baseSettings.version;
|
||||
$appSession.whiteLabeled = baseSettings.whiteLabeled;
|
||||
$appSession.whiteLabeledDetails.icon = baseSettings.whiteLabeledIcon;
|
||||
|
||||
export let settings: any;
|
||||
export let userId: string;
|
||||
export let teamId: string;
|
||||
@ -54,7 +77,6 @@
|
||||
import '../tailwind.css';
|
||||
import Cookies from 'js-cookie';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
import { SvelteToast } from '@zerodevx/svelte-toast';
|
||||
import { navigating, page } from '$app/stores';
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
||||
}
|
||||
if (newStorage) toast.push($t('application.storage.storage_saved'));
|
||||
else toast.push($t('application.storage.storage_updated'));
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -41,7 +41,7 @@
|
||||
await del(`/applications/${id}/storages`, { path: storage.path });
|
||||
dispatch('refresh');
|
||||
toast.push($t('application.storage.storage_deleted'));
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +110,7 @@
|
||||
loading = true;
|
||||
await post(`/applications/${id}/stop`, {});
|
||||
return window.location.reload();
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -118,6 +118,7 @@
|
||||
if ($status.application.loading) return;
|
||||
$status.application.loading = true;
|
||||
const data = await get(`/applications/${id}`);
|
||||
isQueueActive = data.isQueueActive;
|
||||
$status.application.isRunning = data.isRunning;
|
||||
$status.application.isExited = data.isExited;
|
||||
$status.application.loading = false;
|
||||
|
@ -109,7 +109,7 @@
|
||||
return true;
|
||||
}
|
||||
showSave = true;
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
showSave = false;
|
||||
return errorNotification(error);
|
||||
}
|
||||
@ -138,7 +138,7 @@
|
||||
return await goto(`${to}?from=${from}`);
|
||||
}
|
||||
return await goto(from || `/applications/${id}/configuration/destination`);
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@
|
||||
branches: false,
|
||||
save: false
|
||||
};
|
||||
let tryAgain = false;
|
||||
|
||||
let htmlUrl = application.gitSource.htmlUrl;
|
||||
let apiUrl = application.gitSource.apiUrl;
|
||||
@ -44,10 +45,7 @@
|
||||
project: undefined,
|
||||
branch: undefined
|
||||
};
|
||||
let search = {
|
||||
project: '',
|
||||
branch: ''
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
if (!$appSession.tokens.gitlab) {
|
||||
await getGitlabToken();
|
||||
@ -58,18 +56,10 @@
|
||||
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
||||
});
|
||||
username = user.username;
|
||||
await loadGroups();
|
||||
} catch (error) {
|
||||
return await getGitlabToken();
|
||||
}
|
||||
try {
|
||||
groups = await get(`${apiUrl}/v4/groups?per_page=5000`, {
|
||||
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
||||
});
|
||||
} catch (error: any) {
|
||||
errorNotification(error);
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
loading.base = false;
|
||||
tryAgain = true;
|
||||
}
|
||||
});
|
||||
function selectGroup(event: any) {
|
||||
@ -77,16 +67,11 @@
|
||||
selected.project = null;
|
||||
selected.branch = null;
|
||||
showSave = false;
|
||||
|
||||
// Clear out projects
|
||||
projects = [];
|
||||
loadProjects();
|
||||
}
|
||||
async function searchProjects(searchText: any) {
|
||||
if (!selected.group) {
|
||||
return;
|
||||
}
|
||||
search.project = searchText;
|
||||
await loadProjects();
|
||||
return projects;
|
||||
}
|
||||
function selectProject(event: any) {
|
||||
selected.project = event.detail;
|
||||
selected.branch = null;
|
||||
@ -116,24 +101,50 @@
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadProjects() {
|
||||
async function loadGroups(page: number = 1) {
|
||||
let perPage = 100;
|
||||
//@ts-ignore
|
||||
const params: any = new URLSearchParams({
|
||||
page: 1,
|
||||
per_page: 25,
|
||||
page,
|
||||
per_page: perPage
|
||||
});
|
||||
loading.base = true;
|
||||
try {
|
||||
const newGroups = await get(`${apiUrl}/v4/groups?${params}`, {
|
||||
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
||||
});
|
||||
groups = groups.concat(newGroups);
|
||||
if (newGroups.length === perPage) {
|
||||
await loadGroups(page + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading.base = false;
|
||||
}
|
||||
}
|
||||
async function loadProjects(page: number = 1) {
|
||||
let perPage = 100;
|
||||
//@ts-ignore
|
||||
const params: any = new URLSearchParams({
|
||||
page,
|
||||
per_page: perPage,
|
||||
archived: false
|
||||
});
|
||||
if (search.project) {
|
||||
params.append('search', search.project);
|
||||
}
|
||||
loading.projects = true;
|
||||
if (username === selected.group.name) {
|
||||
try {
|
||||
params.append('min_access_level', 40);
|
||||
projects = await get(`${apiUrl}/v4/users/${selected.group.name}/projects?${params}`, {
|
||||
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
||||
});
|
||||
const newProjects = await get(
|
||||
`${apiUrl}/v4/users/${selected.group.name}/projects?${params}`,
|
||||
{
|
||||
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
||||
}
|
||||
);
|
||||
projects = projects.concat(newProjects);
|
||||
if (newProjects.length === perPage) {
|
||||
await loadProjects(page + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
@ -141,9 +152,16 @@
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
projects = await get(`${apiUrl}/v4/groups/${selected.group.id}/projects?${params}`, {
|
||||
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
||||
});
|
||||
const newProjects = await get(
|
||||
`${apiUrl}/v4/groups/${selected.group.id}/projects?${params}`,
|
||||
{
|
||||
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
||||
}
|
||||
);
|
||||
projects = projects.concat(newProjects);
|
||||
if (newProjects.length === perPage) {
|
||||
await loadProjects(page + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
@ -151,35 +169,29 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
async function searchBranches(searchText: any) {
|
||||
if (!selected.project) {
|
||||
return;
|
||||
}
|
||||
search.branch = searchText;
|
||||
await loadBranches();
|
||||
return branches;
|
||||
}
|
||||
function selectBranch(event: any) {
|
||||
selected.branch = event.detail;
|
||||
isBranchAlreadyUsed();
|
||||
}
|
||||
async function loadBranches() {
|
||||
async function loadBranches(page: number = 1) {
|
||||
let perPage = 100;
|
||||
//@ts-ignore
|
||||
const params = new URLSearchParams({
|
||||
page: 1,
|
||||
per_page: 100
|
||||
page,
|
||||
per_page: perPage
|
||||
});
|
||||
if (search.branch) {
|
||||
params.append('search', search.branch);
|
||||
}
|
||||
loading.branches = true;
|
||||
try {
|
||||
branches = await get(
|
||||
const newBranches = await get(
|
||||
`${apiUrl}/v4/projects/${selected.project.id}/repository/branches?${params}`,
|
||||
{
|
||||
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
||||
}
|
||||
);
|
||||
branches = branches.concat(newBranches);
|
||||
if (newBranches.length === perPage) {
|
||||
await loadBranches(page + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
@ -203,7 +215,7 @@
|
||||
return true;
|
||||
}
|
||||
showSave = true;
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -369,9 +381,9 @@
|
||||
value={selected.project}
|
||||
isClearable={false}
|
||||
items={projects}
|
||||
loadOptions={searchProjects}
|
||||
labelIdentifier="name"
|
||||
optionIdentifier="id"
|
||||
isSearchable={true}
|
||||
/>
|
||||
</div>
|
||||
<div class="custom-select-wrapper">
|
||||
@ -392,7 +404,7 @@
|
||||
value={selected.branch}
|
||||
isClearable={false}
|
||||
items={branches}
|
||||
loadOptions={searchBranches}
|
||||
isSearchable={true}
|
||||
labelIdentifier="name"
|
||||
optionIdentifier="web_url"
|
||||
/>
|
||||
@ -408,5 +420,17 @@
|
||||
class:hover:bg-orange-500={showSave && !loading.save}
|
||||
>{loading.save ? $t('forms.saving') : $t('forms.save')}</button
|
||||
>
|
||||
{#if tryAgain}
|
||||
<div>
|
||||
An error occured during authenticating with GitLab. Please check your GitLab Source
|
||||
configuration <a href={`/sources/${application.gitSource.id}`}>here.</a>
|
||||
</div>
|
||||
<button
|
||||
class="w-40 bg-green-600"
|
||||
on:click|stopPropagation|preventDefault={() => window.location.reload()}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
|
@ -60,7 +60,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
async function scanRepository() {
|
||||
async function scanRepository(): Promise<void> {
|
||||
try {
|
||||
if (type === 'gitlab') {
|
||||
const files = await get(`${apiUrl}/v4/projects/${projectId}/repository/tree`, {
|
||||
|
@ -51,7 +51,7 @@
|
||||
try {
|
||||
await post(`/applications/${id}/configuration/destination`, { destinationId });
|
||||
return await goto(from || `/applications/${id}/configuration/buildpack`);
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@
|
||||
import { appSession } from '$lib/store';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from')
|
||||
const from = $page.url.searchParams.get('from');
|
||||
|
||||
export let sources: any;
|
||||
const filteredSources = sources.filter(
|
||||
@ -74,9 +74,14 @@
|
||||
<div class="flex flex-col justify-center">
|
||||
{#if !filteredSources || ownSources.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="pb-2 text-center font-bold">{$t('application.configuration.no_configurable_git')}</div>
|
||||
<div class="pb-2 text-center font-bold">
|
||||
{$t('application.configuration.no_configurable_git')}
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<a href="/sources/new?from={$page.url.pathname}" class="add-icon bg-orange-600 hover:bg-orange-500">
|
||||
<a
|
||||
href="/sources/new?from={$page.url.pathname}"
|
||||
class="add-icon bg-orange-600 hover:bg-orange-500"
|
||||
>
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -94,9 +99,46 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row ">
|
||||
{#each ownSources as source}
|
||||
<div class="p-2">
|
||||
<div class="p-2 relative">
|
||||
<div class="absolute -m-4">
|
||||
{#if source?.type === 'gitlab'}
|
||||
<svg viewBox="0 0 128 128" class="w-8">
|
||||
<path
|
||||
fill="#FC6D26"
|
||||
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
|
||||
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
|
||||
fill="#FC6D26"
|
||||
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
|
||||
/><path
|
||||
fill="#FCA326"
|
||||
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
|
||||
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
|
||||
fill="#FCA326"
|
||||
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if source?.type === 'github'}
|
||||
<svg viewBox="0 0 128 128" class="w-8">
|
||||
<g fill="#ffffff"
|
||||
><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
|
||||
/><path
|
||||
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
|
||||
/></g
|
||||
>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
||||
<button
|
||||
disabled={source.gitlabApp && !source.gitlabAppId}
|
||||
|
@ -103,8 +103,18 @@
|
||||
usageInterval = setInterval(async () => {
|
||||
await getUsage();
|
||||
}, 1000);
|
||||
await getBaseBuildImages();
|
||||
});
|
||||
|
||||
async function getBaseBuildImages() {
|
||||
const data = await post(`/applications/images`, {
|
||||
buildPack: application.buildPack,
|
||||
deploymentType: application.deploymentType
|
||||
});
|
||||
application = {
|
||||
...application,
|
||||
...data
|
||||
};
|
||||
}
|
||||
async function changeSettings(name: any) {
|
||||
if (name === 'debug') {
|
||||
debug = !debug;
|
||||
@ -145,10 +155,12 @@
|
||||
}
|
||||
}
|
||||
async function handleSubmit() {
|
||||
if (loading) return;
|
||||
if (loading || !application.fqdn) return;
|
||||
loading = true;
|
||||
try {
|
||||
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||
if (application.deploymentType)
|
||||
application.deploymentType = application.deploymentType.toLowerCase();
|
||||
await post(`/applications/${id}/check`, {
|
||||
fqdn: application.fqdn,
|
||||
forceSave,
|
||||
@ -192,6 +204,11 @@
|
||||
application.baseBuildImage = event.detail.value;
|
||||
await handleSubmit();
|
||||
}
|
||||
async function selectDeploymentType(event: any) {
|
||||
application.deploymentType = event.detail.value;
|
||||
await getBaseBuildImages();
|
||||
await handleSubmit();
|
||||
}
|
||||
|
||||
async function isDNSValid(domain: any, isWWW: any) {
|
||||
try {
|
||||
@ -199,7 +216,7 @@
|
||||
toast.push('DNS configuration is valid.');
|
||||
isWWW ? (isWWWDomainOK = true) : (isNonWWWDomainOK = true);
|
||||
return true;
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
errorNotification(error);
|
||||
isWWW ? (isWWWDomainOK = false) : (isNonWWWDomainOK = false);
|
||||
return false;
|
||||
@ -404,26 +421,6 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if application.buildPack !== 'docker'}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="baseImage" class="text-base font-bold text-stone-100"
|
||||
>{$t('application.base_image')}</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>
|
||||
<Explainer text={$t('application.base_image_explainer')} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'}
|
||||
<div class="grid grid-cols-2 items-center pb-8">
|
||||
<label for="baseBuildImage" class="text-base font-bold text-stone-100"
|
||||
@ -449,6 +446,48 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if application.buildPack !== 'docker'}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="baseImage" class="text-base font-bold text-stone-100"
|
||||
>{$t('application.base_image')}</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>
|
||||
<Explainer text={$t('application.base_image_explainer')} />
|
||||
</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" class="text-base font-bold text-stone-100"
|
||||
>Deployment Type</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>
|
||||
<Explainer
|
||||
text="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."
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">{$t('application.application')}</div>
|
||||
@ -473,6 +512,7 @@
|
||||
bind:this={domainEl}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
required
|
||||
bind:value={application.fqdn}
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
placeholder="eg: https://coollabs.io"
|
||||
@ -516,7 +556,7 @@
|
||||
<div class="grid grid-cols-2 items-center pb-8">
|
||||
<Setting
|
||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||
disabled={$status.application.isRunning}
|
||||
disabled={isDisabled}
|
||||
isCenter={false}
|
||||
bind:setting={dualCerts}
|
||||
title={$t('application.ssl_www_and_non_www')}
|
||||
@ -535,6 +575,7 @@
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="pythonModule" class="text-base font-bold text-stone-100">Module</label>
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="pythonModule"
|
||||
id="pythonModule"
|
||||
@ -547,6 +588,7 @@
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="pythonVariable" class="text-base font-bold text-stone-100">Variable</label>
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="pythonVariable"
|
||||
id="pythonVariable"
|
||||
@ -560,6 +602,7 @@
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="pythonVariable" class="text-base font-bold text-stone-100">Variable</label>
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="pythonVariable"
|
||||
id="pythonVariable"
|
||||
@ -574,6 +617,7 @@
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="port" class="text-base font-bold text-stone-100">{$t('forms.port')}</label>
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="port"
|
||||
id="port"
|
||||
@ -582,28 +626,27 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if application.buildPack !== 'docker'}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
|
||||
<input
|
||||
readonly={!$appSession.isAdmin && !$status.application.isRunning}
|
||||
disabled={isDisabled}
|
||||
name="exposePort"
|
||||
id="exposePort"
|
||||
bind:value={application.exposePort}
|
||||
placeholder="12345"
|
||||
/>
|
||||
<Explainer
|
||||
text={'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.'}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
|
||||
<input
|
||||
readonly={!$appSession.isAdmin && !$status.application.isRunning}
|
||||
disabled={isDisabled}
|
||||
name="exposePort"
|
||||
id="exposePort"
|
||||
bind:value={application.exposePort}
|
||||
placeholder="12345"
|
||||
/>
|
||||
<Explainer
|
||||
text={'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.'}
|
||||
/>
|
||||
</div>
|
||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||
<div class="grid grid-cols-2 items-center pt-4">
|
||||
<label for="installCommand" class="text-base font-bold text-stone-100"
|
||||
>{$t('application.install_command')}</label
|
||||
>
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="installCommand"
|
||||
id="installCommand"
|
||||
@ -616,6 +659,7 @@
|
||||
>{$t('application.build_command')}</label
|
||||
>
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="buildCommand"
|
||||
id="buildCommand"
|
||||
@ -628,6 +672,7 @@
|
||||
>{$t('application.start_command')}</label
|
||||
>
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="startCommand"
|
||||
id="startCommand"
|
||||
@ -642,6 +687,7 @@
|
||||
>Dockerfile Location</label
|
||||
>
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="dockerFileLocation"
|
||||
id="dockerFileLocation"
|
||||
@ -649,7 +695,7 @@
|
||||
placeholder="default: /Dockerfile"
|
||||
/>
|
||||
<Explainer
|
||||
text="Does not rely on Base Directory. <br>Should be absolute path, like <span class='text-green-500 font-bold'>/data/Dockerfile</span> or <span class='text-green-500 font-bold'>/Dockerfile.</span>"
|
||||
text="Should be absolute path, like <span class='text-green-500 font-bold'>/data/Dockerfile</span> or <span class='text-green-500 font-bold'>/Dockerfile.</span>"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@ -657,6 +703,7 @@
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="denoMainFile" class="text-base font-bold text-stone-100">Main File</label>
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="denoMainFile"
|
||||
id="denoMainFile"
|
||||
@ -667,6 +714,7 @@
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="denoOptions" class="text-base font-bold text-stone-100">Arguments</label>
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="denoOptions"
|
||||
id="denoOptions"
|
||||
@ -687,6 +735,7 @@
|
||||
<Explainer text={$t('application.directory_to_use_explainer')} />
|
||||
</div>
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="baseDirectory"
|
||||
id="baseDirectory"
|
||||
@ -705,9 +754,11 @@
|
||||
</div>
|
||||
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="publishDirectory"
|
||||
id="publishDirectory"
|
||||
required={application.deploymentType === 'static'}
|
||||
bind:value={application.publishDirectory}
|
||||
placeholder=" {$t('forms.default')}: /"
|
||||
/>
|
||||
|
@ -68,7 +68,7 @@
|
||||
const data = await get(`/applications/${id}/logs/build?skip=${skip}`);
|
||||
builds = builds.concat(data.builds);
|
||||
return;
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
} else {
|
||||
|
@ -89,7 +89,7 @@
|
||||
loading = true;
|
||||
await post(`/databases/${id}`, { ...database, isRunning: $status.database.isRunning });
|
||||
generateDbDetails();
|
||||
toast.push('Settings saved.');
|
||||
toast.push('Configuration saved.');
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
|
@ -73,7 +73,7 @@
|
||||
try {
|
||||
await del(`/databases/${database.id}`, { id: database.id });
|
||||
return await goto('/databases');
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
@ -87,7 +87,7 @@
|
||||
try {
|
||||
await post(`/databases/${database.id}/stop`, {});
|
||||
return window.location.reload();
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -97,7 +97,7 @@
|
||||
try {
|
||||
await post(`/databases/${database.id}/start`, {});
|
||||
return window.location.reload();
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,7 @@
|
||||
} else {
|
||||
await startProxy();
|
||||
}
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loadingProxy = false;
|
||||
@ -88,7 +88,7 @@
|
||||
try {
|
||||
await post(`/destinations/${id}/stop`, { engine: destination.engine });
|
||||
return toast.push($t('destination.coolify_proxy_stopped'));
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -96,7 +96,7 @@
|
||||
try {
|
||||
await post(`/destinations/${id}/start`, { engine: destination.engine });
|
||||
return toast.push($t('destination.coolify_proxy_started'));
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@
|
||||
engine: destination.engine,
|
||||
fqdn: settings.fqdn
|
||||
});
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 5000);
|
||||
|
@ -82,7 +82,7 @@ import { appSession } from '$lib/store';
|
||||
} else {
|
||||
await startProxy();
|
||||
}
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -92,7 +92,7 @@ import { appSession } from '$lib/store';
|
||||
const engine = generateRemoteEngine(destination);
|
||||
await post(`/destinations/${id}/stop.json`, { engine });
|
||||
return toast.push($t('destination.coolify_proxy_stopped'));
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -101,7 +101,7 @@ import { appSession } from '$lib/store';
|
||||
const engine = generateRemoteEngine(destination);
|
||||
await post(`/destinations/${id}/start.json`, { engine });
|
||||
return toast.push($t('destination.coolify_proxy_started'));
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -115,7 +115,7 @@ import { appSession } from '$lib/store';
|
||||
engine: destination.engine,
|
||||
fqdn: settings.fqdn
|
||||
});
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 5000);
|
||||
|
@ -45,7 +45,7 @@
|
||||
});
|
||||
await post(`/services/${id}`, { ...service });
|
||||
$disabledButton = false;
|
||||
toast.push('Settings saved.');
|
||||
toast.push('Configuration saved.');
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
|
@ -61,7 +61,7 @@
|
||||
ownMysql
|
||||
});
|
||||
service.wordpress.ownMysql = ownMysql;
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -112,7 +112,7 @@ define('SUBDOMAIN_INSTALL', false);`
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="ftpPassword">Password</label>
|
||||
<CopyPasswordField id="ftpPassword" readonly disabled name="ftpPassword" value={ftpPassword} />
|
||||
<CopyPasswordField id="ftpPassword" isPasswordField readonly disabled name="ftpPassword" value={ftpPassword} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
|
@ -31,7 +31,7 @@
|
||||
}
|
||||
if (newStorage) toast.push('Storage saved.');
|
||||
else toast.push('Storage updated.');
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -40,7 +40,7 @@
|
||||
await del(`/services/${id}/storages`, { path: storage.path });
|
||||
dispatch('refresh');
|
||||
toast.push('Storage deleted.');
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
|
@ -109,6 +109,7 @@
|
||||
loading = true;
|
||||
try {
|
||||
await post(`/services/${service.id}/${service.type}/start`, {});
|
||||
return window.location.reload()
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
|
@ -87,7 +87,7 @@
|
||||
isDNSCheckEnabled
|
||||
});
|
||||
return toast.push(t.get('application.settings_saved'));
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
@ -134,7 +134,7 @@
|
||||
toast.push('DNS configuration is valid.');
|
||||
isWWW ? (isWWWDomainOK = true) : (isNonWWWDomainOK = true);
|
||||
return true;
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
errorNotification(error);
|
||||
isWWW ? (isWWWDomainOK = false) : (isNonWWWDomainOK = false);
|
||||
return false;
|
||||
@ -151,7 +151,7 @@
|
||||
const data = await get(`/settings`);
|
||||
$isTraefikUsed = data.settings.isTraefikUsed;
|
||||
return toast.push('Proxy migration started, it takes a few seconds.');
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading.proxyMigration = false;
|
||||
|
@ -11,7 +11,11 @@
|
||||
import { dev } from '$app/env';
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
$: selfHosted = source.htmlUrl !== 'https://github.com';
|
||||
|
||||
let loading = false;
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
try {
|
||||
@ -20,7 +24,7 @@
|
||||
htmlUrl: source.htmlUrl.replace(/\/$/, ''),
|
||||
apiUrl: source.apiUrl.replace(/\/$/, '')
|
||||
});
|
||||
toast.push('Settings saved.');
|
||||
toast.push('Configuration saved.');
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
@ -57,7 +61,8 @@
|
||||
name: source.name,
|
||||
htmlUrl: source.htmlUrl.replace(/\/$/, ''),
|
||||
apiUrl: source.apiUrl.replace(/\/$/, ''),
|
||||
organization: source.organization
|
||||
organization: source.organization,
|
||||
customPort: source.customPort
|
||||
});
|
||||
const { organization, htmlUrl } = source;
|
||||
const { fqdn } = settings;
|
||||
@ -113,8 +118,11 @@
|
||||
<div class="mx-auto max-w-4xl px-6">
|
||||
{#if !source.githubAppId}
|
||||
<form on:submit|preventDefault={newGithubApp} class="py-4">
|
||||
<div class="flex space-x-1 pb-5 font-bold">
|
||||
<div class="flex space-x-1 pb-7 font-bold">
|
||||
<div class="title">General</div>
|
||||
{#if !source.githubAppId}
|
||||
<button class="bg-orange-600" type="submit">Save</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2 px-10">
|
||||
<div class="grid grid-flow-row gap-2">
|
||||
@ -131,6 +139,20 @@
|
||||
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
||||
<input name="apiUrl" id="apiUrl" required bind:value={source.apiUrl} />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="customPort" class="text-base font-bold text-stone-100">Custom SSH Port</label>
|
||||
<input
|
||||
name="customPort"
|
||||
id="customPort"
|
||||
disabled={!selfHosted || source.githubAppId}
|
||||
readonly={!selfHosted || source.githubAppId}
|
||||
required
|
||||
value={source.customPort}
|
||||
/>
|
||||
<Explainer
|
||||
text="If you use a self-hosted version of Git, you can provide custom port for all the Git related actions."
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2">
|
||||
<div class="flex flex-col">
|
||||
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
|
||||
@ -148,11 +170,6 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if source.apiUrl && source.htmlUrl && source.name}
|
||||
<div class="text-center">
|
||||
<button class=" mt-8 bg-orange-600" type="submit">Create new GitHub App</button>
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
{:else if source.githubApp?.installationId}
|
||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||
@ -165,13 +182,10 @@
|
||||
class:hover:bg-orange-500={!loading}
|
||||
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
|
||||
>
|
||||
|
||||
<button
|
||||
><a
|
||||
class="no-underline"
|
||||
href={`${source.htmlUrl}/apps/${source.githubApp.name}/installations/new`}
|
||||
>{$t('source.change_app_settings', { name: 'GitHub' })}</a
|
||||
></button
|
||||
<a
|
||||
class="no-underline button justify-center flex items-center"
|
||||
href={`${source.htmlUrl}/apps/${source.githubApp.name}/installations/new`}
|
||||
>{$t('source.change_app_settings', { name: 'GitHub' })}</a
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
@ -184,11 +198,39 @@
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
|
||||
<input name="htmlUrl" id="htmlUrl" required bind:value={source.htmlUrl} />
|
||||
<input
|
||||
name="htmlUrl"
|
||||
id="htmlUrl"
|
||||
disabled={source.githubAppId}
|
||||
readonly={source.githubAppId}
|
||||
required
|
||||
bind:value={source.htmlUrl}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
||||
<input name="apiUrl" id="apiUrl" required bind:value={source.apiUrl} />
|
||||
<input
|
||||
name="apiUrl"
|
||||
id="apiUrl"
|
||||
required
|
||||
disabled={source.githubAppId}
|
||||
readonly={source.githubAppId}
|
||||
bind:value={source.apiUrl}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="customPort" class="text-base font-bold text-stone-100">Custom SSH Port</label>
|
||||
<input
|
||||
name="customPort"
|
||||
id="customPort"
|
||||
disabled={!selfHosted}
|
||||
readonly={!selfHosted}
|
||||
required
|
||||
value={source.customPort}
|
||||
/>
|
||||
<Explainer
|
||||
text="If you use a self-hosted version of Git, you can provide custom port for all the Git related actions."
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2">
|
||||
<div class="flex flex-col">
|
||||
@ -210,7 +252,9 @@
|
||||
{:else}
|
||||
<div class="text-center">
|
||||
<a href={`${source.htmlUrl}/apps/${source.githubApp.name}/installations/new`}>
|
||||
<button class=" bg-orange-600 hover:bg-orange-500 mt-8">Install Repositories</button></a
|
||||
<button class="box-selection bg-orange-600 hover:bg-orange-500 text-xl"
|
||||
>Install Repositories</button
|
||||
></a
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -12,8 +12,8 @@
|
||||
import { t } from '$lib/translations';
|
||||
import { errorNotification } from '$lib/common';
|
||||
import { appSession } from '$lib/store';
|
||||
import { goto } from '$app/navigation';
|
||||
const { id } = $page.params;
|
||||
|
||||
let url = settings.fqdn ? settings.fqdn : window.location.origin;
|
||||
|
||||
if (dev) {
|
||||
@ -31,6 +31,8 @@
|
||||
appSecret: null
|
||||
};
|
||||
}
|
||||
$: selfHosted = source.htmlUrl !== 'https://gitlab.com' ;
|
||||
|
||||
onMount(() => {
|
||||
oauthIdEl && oauthIdEl.focus();
|
||||
});
|
||||
@ -49,7 +51,8 @@
|
||||
oauthId: source.gitlabApp.oauthId,
|
||||
appId: source.gitlabApp.appId,
|
||||
appSecret: source.gitlabApp.appSecret,
|
||||
groupName: source.gitlabApp.groupName
|
||||
groupName: source.gitlabApp.groupName,
|
||||
customPort: source.customPort
|
||||
});
|
||||
const from = $page.url.searchParams.get('from');
|
||||
if (from) {
|
||||
@ -67,9 +70,10 @@
|
||||
await post(`/sources/${id}`, {
|
||||
name: source.name,
|
||||
htmlUrl: source.htmlUrl.replace(/\/$/, ''),
|
||||
apiUrl: source.apiUrl.replace(/\/$/, '')
|
||||
apiUrl: source.apiUrl.replace(/\/$/, ''),
|
||||
customPort: source.customPort
|
||||
});
|
||||
toast.push('Settings saved.');
|
||||
toast.push('Configuration saved.');
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
@ -106,7 +110,7 @@
|
||||
await post(`/sources/${id}/check`, {
|
||||
oauthId: source.gitlabApp?.oauthId
|
||||
});
|
||||
} catch ({ error }) {
|
||||
} catch (error) {
|
||||
source.gitlabApp.oauthId = null;
|
||||
oauthIdEl.focus();
|
||||
return errorNotification(error);
|
||||
@ -119,6 +123,10 @@
|
||||
window.open(`${source.htmlUrl}/-/profile/applications`);
|
||||
break;
|
||||
case 'group':
|
||||
if (!source.gitlabApp.groupName) {
|
||||
toast.push('Please enter a group name first.');
|
||||
return;
|
||||
}
|
||||
window.open(
|
||||
`${source.htmlUrl}/groups/${source.gitlabApp.groupName}/-/settings/applications`
|
||||
);
|
||||
@ -146,6 +154,8 @@
|
||||
<button on:click|preventDefault={changeSettings}
|
||||
>{$t('source.change_app_settings', { name: 'GitLab' })}</button
|
||||
>
|
||||
{:else}
|
||||
<button on:click|preventDefault|stopPropagation={newApp}>Create new GitLab App</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
@ -204,7 +214,7 @@
|
||||
required
|
||||
disabled={source.gitlabAppId}
|
||||
readonly={source.gitlabAppId}
|
||||
value={source.htmlUrl}
|
||||
bind:value={source.htmlUrl}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
@ -218,6 +228,20 @@
|
||||
value={source.apiUrl}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="customPort" class="text-base font-bold text-stone-100">Custom SSH Port</label>
|
||||
<input
|
||||
name="customPort"
|
||||
id="customPort"
|
||||
disabled={!selfHosted}
|
||||
readonly={!selfHosted}
|
||||
required
|
||||
bind:value={source.customPort}
|
||||
/>
|
||||
<Explainer
|
||||
text="If you use a self-hosted version of Git, you can provide custom port for all the Git related actions."
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-start">
|
||||
<div class="flex-col">
|
||||
<label for="oauthId" class="pt-2 text-base font-bold text-stone-100"
|
||||
|
@ -91,7 +91,7 @@ label {
|
||||
@apply inline-block w-64 text-xs tracking-tight md:text-sm;
|
||||
}
|
||||
|
||||
button {
|
||||
button, .button {
|
||||
@apply rounded bg-coolgray-200 p-1 px-2 py-1 text-xs font-bold outline-none transition-all duration-100 hover:bg-coolgray-500 disabled:cursor-not-allowed disabled:bg-coolblack disabled:text-stone-600;
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,20 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
||||
/** @type {import('vite').UserConfig} */
|
||||
export default {
|
||||
plugins: [sveltekit()],
|
||||
define: {
|
||||
'GITPOD_WORKSPACE_URL': JSON.stringify(process.env.GITPOD_WORKSPACE_URL)
|
||||
},
|
||||
server: {
|
||||
hmr: process.env.GITPOD_WORKSPACE_URL
|
||||
? {
|
||||
// Due to port fowarding, we have to replace
|
||||
// 'https' with the forwarded port, as this
|
||||
// is the URI created by Gitpod.
|
||||
host: process.env.GITPOD_WORKSPACE_URL.replace("https://", "3000-"),
|
||||
protocol: "wss",
|
||||
clientPort: 443
|
||||
}
|
||||
: true,
|
||||
fs: {
|
||||
allow: ['./src/lib/locales/']
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"version": "3.0.3",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "3.1.0",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"db:studio": "pnpm run --filter coolify-api db:studio",
|
||||
"db:push": "pnpm run --filter coolify-api db:push",
|
||||
"db:seed": "pnpm run --filter coolify-api db:seed",
|
||||
"db:migrate": "pnpm run --filter coolify-api db:migrate",
|
||||
"format": "run-p -l -n format:*",
|
||||
"format:api": "NODE_ENV=development pnpm run --filter coolify-api format",
|
||||
"lint": "run-p -l -n lint:*",
|
||||
@ -17,7 +18,8 @@
|
||||
"build": "NODE_ENV=production run-p -n build:*",
|
||||
"build:api": "NODE_ENV=production pnpm run --filter coolify-api build",
|
||||
"build:ui": "NODE_ENV=production pnpm run --filter coolify-ui build",
|
||||
"release:staging:amd": "cross-var docker buildx build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version --push ."
|
||||
"release:staging:amd": "cross-var docker buildx build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version --push .",
|
||||
"release:local":"rm -fr ./local-serve && mkdir ./local-serve && pnpm build && cp -Rp apps/api/build/* ./local-serve && cp -Rp apps/ui/build/ ./local-serve/public && cp -Rp apps/api/prisma/ ./local-serve/prisma && cp -Rp apps/api/package.json ./local-serve && cp .env ./local-serve && cd ./local-serve && pnpm install . && pnpm start"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-var": "1.1.0",
|
||||
|
43
pnpm-lock.yaml
generated
43
pnpm-lock.yaml
generated
@ -1837,7 +1837,7 @@ packages:
|
||||
clf-date: 0.2.0
|
||||
iserror: 0.0.2
|
||||
ms: 2.1.3
|
||||
on-finished: 2.3.0
|
||||
on-finished: 2.4.1
|
||||
parse-err: 0.0.12
|
||||
parse-request: 4.0.0
|
||||
transitivePeerDependencies:
|
||||
@ -2004,7 +2004,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/concat-map/0.0.1:
|
||||
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
dev: true
|
||||
|
||||
/console-control-strings/1.1.0:
|
||||
@ -2206,7 +2206,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/delayed-stream/1.0.0:
|
||||
resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=}
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: false
|
||||
|
||||
@ -2252,7 +2252,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/dezalgo/1.0.3:
|
||||
resolution: {integrity: sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=}
|
||||
resolution: {integrity: sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==}
|
||||
dependencies:
|
||||
asap: 2.0.6
|
||||
wrappy: 1.0.2
|
||||
@ -2346,7 +2346,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/ee-first/1.1.1:
|
||||
resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=}
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium/1.4.139:
|
||||
@ -3059,7 +3059,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/fs.realpath/1.0.0:
|
||||
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
/fsevents/2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
@ -3371,7 +3371,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/inflight/1.0.6:
|
||||
resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
wrappy: 1.0.2
|
||||
@ -3413,7 +3413,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/is-arrayish/0.2.1:
|
||||
resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=}
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
|
||||
/is-bigint/1.0.4:
|
||||
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
|
||||
@ -3568,7 +3568,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/is-uuid/1.0.2:
|
||||
resolution: {integrity: sha1-rRiY3fFUlHwlyOVJZvSGBOnK7MQ=}
|
||||
resolution: {integrity: sha512-tCByphFcJgf2qmiMo5hMCgNAquNSagOetVetDvBXswGkNfoyEMvGH1yDlF8cbZbKnbVBr4Y5/rlpMz9umxyBkQ==}
|
||||
dev: false
|
||||
|
||||
/is-valid-path/0.1.1:
|
||||
@ -3585,11 +3585,11 @@ packages:
|
||||
dev: true
|
||||
|
||||
/iserror/0.0.2:
|
||||
resolution: {integrity: sha1-vVNFH+L2aLnyQCwZZnh6qix8C/U=}
|
||||
resolution: {integrity: sha512-oKGGrFVaWwETimP3SiWwjDeY27ovZoyZPHtxblC4hCq9fXxed/jasx+ATWFFjCVSRZng8VTMsN1nDnGo6zMBSw==}
|
||||
dev: false
|
||||
|
||||
/isexe/2.0.0:
|
||||
resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=}
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
/js-cookie/3.0.1:
|
||||
resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==}
|
||||
@ -3817,7 +3817,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/lodash.omit/4.5.0:
|
||||
resolution: {integrity: sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=}
|
||||
resolution: {integrity: sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==}
|
||||
dev: false
|
||||
|
||||
/lodash.once/4.1.1:
|
||||
@ -3842,7 +3842,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/lower-case/1.1.4:
|
||||
resolution: {integrity: sha1-miyr0bno4K6ZOkv31YdcOcQujqw=}
|
||||
resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==}
|
||||
dev: false
|
||||
|
||||
/lowercase-keys/2.0.0:
|
||||
@ -3903,7 +3903,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/methods/1.1.2:
|
||||
resolution: {integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=}
|
||||
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
@ -4051,7 +4051,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/next-line/1.1.0:
|
||||
resolution: {integrity: sha1-/K5XhTBStqm66CCOQN19PC0wRgM=}
|
||||
resolution: {integrity: sha512-+I10J3wKNoKddNxn0CNpoZ3eTZuqxjNM3b1GImVx22+ePI+Y15P8g/j3WsbP0fhzzrFzrtjOAoq5NCCucswXOQ==}
|
||||
dev: false
|
||||
|
||||
/nice-try/1.0.5:
|
||||
@ -4219,13 +4219,6 @@ packages:
|
||||
resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==}
|
||||
dev: false
|
||||
|
||||
/on-finished/2.3.0:
|
||||
resolution: {integrity: sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
dev: false
|
||||
|
||||
/on-finished/2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@ -4234,7 +4227,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/once/1.4.0:
|
||||
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
@ -4824,7 +4817,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/requires-port/1.0.0:
|
||||
resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=}
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
dev: false
|
||||
|
||||
/resolve-alpn/1.2.1:
|
||||
@ -5726,7 +5719,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/wrappy/1.0.2:
|
||||
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
/xtend/4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
|
Loading…
x
Reference in New Issue
Block a user