Merge github.com:coollabsio/coolify into exposePort

This commit is contained in:
Aaron Styles
2022-05-03 16:14:58 +10:00
83 changed files with 2833 additions and 400 deletions

View File

@@ -394,7 +394,7 @@
>
<div class="border border-coolgray-500 h-8" />
<a
href={!$disabledButton ? `/applications/${id}/logs` : null}
href={!$disabledButton && isRunning ? `/applications/${id}/logs` : null}
sveltekit:prefetch
class="hover:text-sky-500 rounded"
class:text-sky-500={$page.url.pathname === `/applications/${id}/logs`}
@@ -402,7 +402,7 @@
>
<button
title={$t('application.logs')}
disabled={$disabledButton}
disabled={$disabledButton || !isRunning}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip={$t('application.logs')}
>

View File

@@ -0,0 +1,75 @@
import { asyncExecShell, getEngine, removeDestinationDocker, saveBuildLog } from '$lib/common';
import { buildQueue } from '$lib/queues';
import type { RequestHandler } from '@sveltejs/kit';
import * as db from '$lib/database';
export const post: RequestHandler = async (event) => {
const { buildId, applicationId } = await event.request.json();
if (!buildId) {
return {
status: 500,
body: {
message: 'Build ID not found.'
}
};
}
try {
let count = 0;
await new Promise<void>(async (resolve, reject) => {
const job = await buildQueue.getJob(buildId);
const {
destinationDocker: { engine }
} = job.data;
const host = getEngine(engine);
let interval = setInterval(async () => {
const { status } = await db.prisma.build.findUnique({ where: { id: buildId } });
if (status === 'failed') {
clearInterval(interval);
return resolve();
}
if (count > 1200) {
clearInterval(interval);
reject(new Error('Could not cancel build.'));
}
try {
const { stdout: buildContainers } = await asyncExecShell(
`DOCKER_HOST=${host} docker container ls --filter "label=coolify.buildId=${buildId}" --format '{{json .}}'`
);
if (buildContainers) {
const containersArray = buildContainers.trim().split('\n');
for (const container of containersArray) {
const containerObj = JSON.parse(container);
const id = containerObj.ID;
if (!containerObj.Names.startsWith(`${applicationId}`)) {
await removeDestinationDocker({ id, engine });
clearInterval(interval);
await saveBuildLog({
line: 'Canceled by user!',
buildId: job.data.build_id,
applicationId: job.data.id
});
}
}
}
count++;
} catch (error) {}
}, 100);
resolve();
});
return {
status: 200,
body: {
message: 'Build canceled.'
}
};
} catch (error) {
return {
status: 500,
body: {
message: error.message
}
};
}
};

View File

@@ -185,7 +185,7 @@
? $t('application.configuration.loading_repositories')
: $t('application.configuration.select_a_repository')}
id="repository"
showIndicator={true}
showIndicator={!loading.repositories}
isWaiting={loading.repositories}
on:select={loadBranches}
items={reposSelectOptions}
@@ -202,7 +202,7 @@
? $t('application.configuration.select_a_repository_first')
: $t('application.configuration.select_a_branch')}
isWaiting={loading.branches}
showIndicator={selected.repository}
showIndicator={selected.repository && !loading.branches}
id="branches"
on:select={isBranchAlreadyUsed}
items={branchSelectOptions}

View File

@@ -1,7 +1,7 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
import { ErrorHandler } from '$lib/database';
import { ErrorHandler, generatePassword } from '$lib/database';
export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
@@ -34,6 +34,30 @@ export const post: RequestHandler = async (event) => {
try {
await db.configureBuildPack({ id, buildPack });
// Generate default secrets
if (buildPack === 'laravel') {
let found = await db.isSecretExists({ id, name: 'APP_ENV', isPRMRSecret: false });
if (!found) {
await db.createSecret({
id,
name: 'APP_ENV',
value: 'production',
isBuildSecret: false,
isPRMRSecret: false
});
}
found = await db.isSecretExists({ id, name: 'APP_KEY', isPRMRSecret: false });
if (!found) {
await db.createSecret({
id,
name: 'APP_KEY',
value: generatePassword(32),
isBuildSecret: false,
isPRMRSecret: false
});
}
}
return { status: 201 };
} catch (error) {
return ErrorHandler(error);

View File

@@ -85,13 +85,14 @@
const composerPHP = files.find(
(file) => file.name === 'composer.json' && file.type === 'blob'
);
const laravel = files.find((file) => file.name === 'artisan' && file.type === 'blob');
if (yarnLock) packageManager = 'yarn';
if (pnpmLock) packageManager = 'pnpm';
if (dockerfile) {
foundConfig = findBuildPack('docker', packageManager);
} else if (packageJson) {
} else if (packageJson && !laravel) {
const path = packageJson.path;
const data = await get(
`${apiUrl}/v4/projects/${projectId}/repository/files/${path}/raw?ref=${branch}`,
@@ -107,8 +108,10 @@
foundConfig = findBuildPack('python');
} else if (indexHtml) {
foundConfig = findBuildPack('static', packageManager);
} else if (indexPHP || composerPHP) {
} else if ((indexPHP || composerPHP) && !laravel) {
foundConfig = findBuildPack('php');
} else if (laravel) {
foundConfig = findBuildPack('laravel');
} else {
foundConfig = findBuildPack('node', packageManager);
}
@@ -134,13 +137,14 @@
const composerPHP = files.find(
(file) => file.name === 'composer.json' && file.type === 'file'
);
const laravel = files.find((file) => file.name === 'artisan' && file.type === 'file');
if (yarnLock) packageManager = 'yarn';
if (pnpmLock) packageManager = 'pnpm';
if (dockerfile) {
foundConfig = findBuildPack('docker', packageManager);
} else if (packageJson) {
} else if (packageJson && !laravel) {
const data = await get(`${packageJson.git_url}`, {
Authorization: `Bearer ${$gitTokens.githubToken}`,
Accept: 'application/vnd.github.v2.raw'
@@ -153,8 +157,10 @@
foundConfig = findBuildPack('python');
} else if (indexHtml) {
foundConfig = findBuildPack('static', packageManager);
} else if (indexPHP || composerPHP) {
} else if ((indexPHP || composerPHP) && !laravel) {
foundConfig = findBuildPack('php');
} else if (laravel) {
foundConfig = findBuildPack('laravel');
} else {
foundConfig = findBuildPack('node', packageManager);
}
@@ -225,7 +231,7 @@
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">
{#each buildPacks as buildPack}
<div class="p-2">
<BuildPack {buildPack} {scanning} bind:foundConfig />
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
</div>
{/each}
</div>

View File

@@ -46,15 +46,23 @@ export const post: RequestHandler = async (event) => {
}
});
if (pullmergeRequestId) {
await buildQueue.add(buildId, {
build_id: buildId,
type: 'manual',
...applicationFound,
sourceBranch: branch,
pullmergeRequestId
});
await buildQueue.add(
buildId,
{
build_id: buildId,
type: 'manual',
...applicationFound,
sourceBranch: branch,
pullmergeRequestId
},
{ jobId: buildId }
);
} else {
await buildQueue.add(buildId, { build_id: buildId, type: 'manual', ...applicationFound });
await buildQueue.add(
buildId,
{ build_id: buildId, type: 'manual', ...applicationFound },
{ jobId: buildId }
);
}
return {
status: 200,

View File

@@ -61,7 +61,9 @@ export const post: RequestHandler = async (event) => {
pythonVariable,
dockerFileLocation,
denoMainFile,
denoOptions
denoOptions,
baseImage,
baseBuildImage
} = await event.request.json();
if (port) port = Number(port);
if (exposePort) {
@@ -99,6 +101,8 @@ export const post: RequestHandler = async (event) => {
dockerFileLocation,
denoMainFile,
denoOptions,
baseImage,
baseBuildImage,
...defaultConfiguration
});
return { status: 201 };

View File

@@ -33,6 +33,8 @@
gitlabApp: Prisma.GitlabApp;
gitSource: Prisma.GitSource;
destinationDocker: Prisma.DestinationDocker;
baseImages: Array<{ value: string; label: string }>;
baseBuildImages: Array<{ value: string; label: string }>;
};
export let isRunning;
import { page, session } from '$app/stores';
@@ -72,11 +74,14 @@
label: 'Gunicorn'
}
];
function containerClass() {
if (!$session.isAdmin || isRunning) {
return 'text-white border border-dashed border-coolgray-300 bg-transparent font-thin px-0';
}
}
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
application.fqdn = `http://${cuid()}.demo.coolify.io`;
}
onMount(() => {
domainEl.focus();
});
@@ -143,6 +148,14 @@
async function selectWSGI(event) {
application.pythonWSGI = event.detail.value;
}
async function selectBaseImage(event) {
application.baseImage = event.detail.value;
await handleSubmit();
}
async function selectBaseBuildImage(event) {
application.baseBuildImage = event.detail.value;
await handleSubmit();
}
</script>
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
@@ -315,6 +328,49 @@
/>
</div>
</div>
<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={!$session.isAdmin || isRunning}
containerClasses={containerClass()}
id="baseImages"
showIndicator={!isRunning}
items={application.baseImages}
on:select={selectBaseImage}
value={application.baseImage}
isClearable={false}
/>
</div>
<Explainer text={$t('application.base_image_explainer')} />
</div>
{#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"
>{$t('application.base_build_image')}</label
>
<div class="custom-select-wrapper">
<Select
isDisabled={!$session.isAdmin || isRunning}
containerClasses={containerClass()}
id="baseBuildImages"
showIndicator={!isRunning}
items={application.baseBuildImages}
on:select={selectBaseBuildImage}
value={application.baseBuildImage}
isClearable={false}
/>
</div>
{#if application.buildPack === 'laravel'}
<Explainer text="For building frontend assets with webpack." />
{:else}
<Explainer text={$t('application.base_build_image_explainer')} />
{/if}
</div>
{/if}
</div>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">{$t('application.application')}</div>
@@ -506,21 +562,23 @@
/>
</div>
{/if}
<div class="grid grid-cols-2 items-center">
<div class="flex-col">
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
>{$t('forms.base_directory')}</label
>
<Explainer text={$t('application.directory_to_use_explainer')} />
{#if application.buildPack !== 'laravel'}
<div class="grid grid-cols-2 items-center">
<div class="flex-col">
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
>{$t('forms.base_directory')}</label
>
<Explainer text={$t('application.directory_to_use_explainer')} />
</div>
<input
readonly={!$session.isAdmin}
name="baseDirectory"
id="baseDirectory"
bind:value={application.baseDirectory}
placeholder="{$t('forms.default')}: /"
/>
</div>
<input
readonly={!$session.isAdmin}
name="baseDirectory"
id="baseDirectory"
bind:value={application.baseDirectory}
placeholder="{$t('forms.default')}: /"
/>
</div>
{/if}
{#if !notNodeDeployments.includes(application.buildPack)}
<div class="grid grid-cols-2 items-center">
<div class="flex-col">

View File

@@ -8,7 +8,7 @@
import Loading from '$lib/components/Loading.svelte';
import LoadingLogs from '../_Loading.svelte';
import { get } from '$lib/api';
import { get, post } from '$lib/api';
import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
@@ -20,6 +20,8 @@
let followingInterval;
let logsEl;
let cancelInprogress = false;
const { id } = $page.params;
const cleanAnsiCodes = (str: string) => str.replace(/\x1B\[(\d+)m/g, '');
@@ -67,6 +69,19 @@
return errorNotification(error);
}
}
async function cancelBuild() {
if (cancelInprogress) return;
try {
cancelInprogress = true;
await post(`/applications/${id}/cancel.json`, {
buildId,
applicationId: id
});
} catch (error) {
console.log(error);
return errorNotification(error);
}
}
onDestroy(() => {
clearInterval(streamInterval);
clearInterval(followingInterval);
@@ -90,7 +105,7 @@
<div class="flex justify-end sticky top-0 p-2">
<button
on:click={followBuild}
class="bg-transparent"
class="bg-transparent hover:text-green-500 hover:bg-coolgray-500"
data-tooltip="Follow logs"
class:text-green-500={followingBuild}
>
@@ -111,7 +126,30 @@
<line x1="16" y1="12" x2="12" y2="16" />
</svg>
</button>
{#if currentStatus === 'running'}
<button
on:click={cancelBuild}
class="bg-transparent hover:text-red-500 hover:bg-coolgray-500"
data-tooltip="Cancel build"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="12" cy="12" r="9" />
<path d="M10 10l4 4m0 -4l-4 4" />
</svg>
</button>
{/if}
</div>
<div
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
bind:this={logsEl}

View File

@@ -98,12 +98,12 @@
}
</script>
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Application Logs
</div>
<span class="text-xs">{application.name} </span>
<span class="text-xs">{application.name}</span>
</div>
{#if application.fqdn}

View File

@@ -22,6 +22,7 @@
import Astro from '$lib/components/svg/applications/Astro.svelte';
import Eleventy from '$lib/components/svg/applications/Eleventy.svelte';
import Deno from '$lib/components/svg/applications/Deno.svelte';
import Laravel from '$lib/components/svg/applications/Laravel.svelte';
async function newApplication() {
const { id } = await post('/applications/new', {});
@@ -104,6 +105,8 @@
<Eleventy />
{:else if application.buildPack.toLowerCase() === 'deno'}
<Deno />
{:else if application.buildPack.toLowerCase() === 'laravel'}
<Laravel />
{/if}
{/if}
@@ -162,6 +165,8 @@
<Eleventy />
{:else if application.buildPack.toLowerCase() === 'deno'}
<Deno />
{:else if application.buildPack.toLowerCase() === 'laravel'}
<Laravel />
{/if}
{/if}