mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-31 12:34:03 +00:00
Merge branch 'next' into main
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
<ul class="menu border bg-coolgray-100 border-coolgray-200 rounded p-2 space-y-2 sticky top-4">
|
||||
<li class="menu-title">
|
||||
<span>Configuration</span>
|
||||
<span>General</span>
|
||||
</li>
|
||||
{#if application.gitSource?.htmlUrl && application.repository && application.branch}
|
||||
<li>
|
||||
@@ -86,7 +86,7 @@
|
||||
<path
|
||||
d="M7 10h3v-3l-3.5 -3.5a6 6 0 0 1 8 8l6 6a2 2 0 0 1 -3 3l-6 -6a6 6 0 0 1 -8 -8l3.5 3.5"
|
||||
/>
|
||||
</svg>Build & Deploy</a
|
||||
</svg>Configuration</a
|
||||
>
|
||||
</li>
|
||||
<li
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full font-bold grid grid-cols-1 lg:grid-cols-4 gap-2 pb-2">
|
||||
<div class="w-full grid grid-cols-1 lg:grid-cols-4 gap-2 pb-2">
|
||||
<div class="flex flex-col">
|
||||
{#if index === 0 || length === 0}
|
||||
<label for="name" class="pb-2 uppercase">name</label>
|
||||
<label for="name" class="pb-2 uppercase font-bold">name</label>
|
||||
{/if}
|
||||
|
||||
<input
|
||||
@@ -50,7 +50,7 @@
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
{#if index === 0 || length === 0}
|
||||
<label for="value" class="pb-2 uppercase">value</label>
|
||||
<label for="value" class="pb-2 uppercase font-bold">value</label>
|
||||
{/if}
|
||||
|
||||
<CopyPasswordField
|
||||
@@ -63,9 +63,12 @@
|
||||
</div>
|
||||
<div class="flex lg:flex-col flex-row justify-start items-center pt-3 lg:pt-0">
|
||||
{#if index === 0 || length === 0}
|
||||
<label for="name" class="pb-2 uppercase lg:block hidden">Need during buildtime?</label>
|
||||
<label for="name" class="pb-2 uppercase lg:block hidden font-bold"
|
||||
>Need during buildtime?</label
|
||||
>
|
||||
{/if}
|
||||
<label for="name" class="pb-2 uppercase lg:hidden block">Need during buildtime?</label>
|
||||
<label for="name" class="pb-2 uppercase lg:hidden block font-bold">Need during buildtime?</label
|
||||
>
|
||||
|
||||
<div class="flex justify-center h-full items-center pt-0 lg:pt-0 pl-4 lg:pl-0">
|
||||
<button
|
||||
@@ -114,7 +117,7 @@
|
||||
</div>
|
||||
<div class="flex flex-row lg:flex-col lg:items-center items-start">
|
||||
{#if index === 0 || length === 0}
|
||||
<label for="name" class="pb-2 uppercase lg:block hidden">Actions</label>
|
||||
<label for="name" class="pb-5 uppercase lg:block hidden font-bold" />
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-center h-full items-center pt-3">
|
||||
|
||||
@@ -39,11 +39,11 @@
|
||||
|
||||
async function addNewSecret() {
|
||||
try {
|
||||
if (!name) return errorNotification({ message: 'Name is required.' });
|
||||
if (!value) return errorNotification({ message: 'Value is required.' });
|
||||
if (!name.trim()) return errorNotification({ message: 'Name is required.' });
|
||||
if (!value.trim()) return errorNotification({ message: 'Value is required.' });
|
||||
await post(`/applications/${id}/secrets`, {
|
||||
name,
|
||||
value,
|
||||
name: name.trim(),
|
||||
value: value.trim(),
|
||||
isBuildSecret
|
||||
});
|
||||
cleanupState();
|
||||
@@ -64,8 +64,8 @@
|
||||
if (isNewSecret) return;
|
||||
try {
|
||||
await put(`/applications/${id}/secrets`, {
|
||||
name,
|
||||
value,
|
||||
name: name.trim(),
|
||||
value: value.trim(),
|
||||
isBuildSecret: changeIsBuildSecret ? isBuildSecret : undefined
|
||||
});
|
||||
addToast({
|
||||
@@ -79,10 +79,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full font-bold grid grid-cols-1 lg:grid-cols-4 gap-2 pb-2">
|
||||
<div class="w-full grid grid-cols-1 lg:grid-cols-4 gap-2 pb-2">
|
||||
<div class="flex flex-col">
|
||||
{#if (index === 0 && !isNewSecret) || length === 0}
|
||||
<label for="name" class="pb-2 uppercase">name</label>
|
||||
<label for="name" class="pb-2 uppercase font-bold">name</label>
|
||||
{/if}
|
||||
|
||||
<input
|
||||
@@ -91,7 +91,7 @@
|
||||
required
|
||||
placeholder="EXAMPLE_VARIABLE"
|
||||
readonly={!isNewSecret}
|
||||
class=" w-full"
|
||||
class="w-full"
|
||||
class:bg-coolblack={!isNewSecret}
|
||||
class:border={!isNewSecret}
|
||||
class:border-dashed={!isNewSecret}
|
||||
@@ -101,7 +101,7 @@
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
{#if (index === 0 && !isNewSecret) || length === 0}
|
||||
<label for="value" class="pb-2 uppercase">value</label>
|
||||
<label for="value" class="pb-2 uppercase font-bold">value</label>
|
||||
{/if}
|
||||
|
||||
<CopyPasswordField
|
||||
@@ -114,9 +114,12 @@
|
||||
</div>
|
||||
<div class="flex lg:flex-col flex-row justify-start items-center pt-3 lg:pt-0">
|
||||
{#if (index === 0 && !isNewSecret) || length === 0}
|
||||
<label for="name" class="pb-2 uppercase lg:block hidden">Need during buildtime?</label>
|
||||
<label for="name" class="pb-2 uppercase lg:block hidden font-bold"
|
||||
>Need during buildtime?</label
|
||||
>
|
||||
{/if}
|
||||
<label for="name" class="pb-2 uppercase lg:hidden block">Need during buildtime?</label>
|
||||
<label for="name" class="pb-2 uppercase lg:hidden block font-bold">Need during buildtime?</label
|
||||
>
|
||||
|
||||
<div class="flex justify-center h-full items-center pt-0 lg:pt-0 pl-4 lg:pl-0">
|
||||
<button
|
||||
@@ -166,7 +169,7 @@
|
||||
</div>
|
||||
<div class="flex flex-row lg:flex-col lg:items-center items-start">
|
||||
{#if (index === 0 && !isNewSecret) || length === 0}
|
||||
<label for="name" class="pb-2 uppercase lg:block hidden">Actions</label>
|
||||
<label for="name" class="pb-5 uppercase lg:block hidden font-bold" />
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-center h-full items-center pt-3">
|
||||
|
||||
@@ -59,36 +59,55 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full font-bold grid gap-2">
|
||||
<div class="flex flex-col pb-2">
|
||||
|
||||
<div class="flex flex-col lg:flex-row lg:space-y-0 space-y-2">
|
||||
<div class="w-full lg:px-0 px-4">
|
||||
{#if storage.predefined}
|
||||
<div class="flex flex-col lg:flex-row gap-4 pb-2">
|
||||
<input disabled readonly class="w-full" value={storage.id} />
|
||||
<input disabled readonly class="w-full" bind:value={storage.path} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex gap-4 pb-2" class:pt-8={isNew}>
|
||||
{#if storage.applicationId}
|
||||
{#if storage.oldPath}
|
||||
<input
|
||||
disabled
|
||||
readonly
|
||||
class="w-full"
|
||||
value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}"
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
disabled
|
||||
readonly
|
||||
class="w-full"
|
||||
value="{storage.applicationId}{storage.path.replace(/\//gi, '-')}"
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
<input
|
||||
class="w-full lg:w-64"
|
||||
disabled={!isNew}
|
||||
readonly={!isNew}
|
||||
class="w-full"
|
||||
bind:value={storage.path}
|
||||
required
|
||||
placeholder="eg: /sqlite.db"
|
||||
placeholder="eg: /data"
|
||||
/>
|
||||
{#if isNew}
|
||||
<div class="flex items-center justify-center w-full lg:w-64">
|
||||
<button class="btn btn-sm btn-primary" on:click={() => saveStorage(true)}
|
||||
>{$t('forms.add')}</button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row items-center justify-center space-x-2 w-full lg:w-64">
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="btn btn-sm btn-primary" on:click={() => saveStorage(false)}
|
||||
>{$t('forms.set')}</button
|
||||
|
||||
<div class="flex items-center justify-center">
|
||||
{#if isNew}
|
||||
<div class="w-full lg:w-64">
|
||||
<button class="btn btn-sm btn-primary w-full" on:click={() => saveStorage(true)}
|
||||
>{$t('forms.add')}</button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex justify-center">
|
||||
<button class="btn btn-sm btn-error" on:click={removeStorage}
|
||||
>{$t('forms.remove')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
let statusInterval: any;
|
||||
let forceDelete = false;
|
||||
|
||||
let stopping = false;
|
||||
const { id } = $page.params;
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
||||
|
||||
@@ -138,17 +138,17 @@
|
||||
}
|
||||
async function stopApplication() {
|
||||
try {
|
||||
$status.application.initialLoading = true;
|
||||
stopping = true;
|
||||
await post(`/applications/${id}/stop`, {});
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
$status.application.initialLoading = false;
|
||||
stopping = false;
|
||||
await getStatus();
|
||||
}
|
||||
}
|
||||
async function getStatus() {
|
||||
if ($status.application.loading) return;
|
||||
if ($status.application.loading && stopping) return;
|
||||
$status.application.loading = true;
|
||||
const data = await get(`/applications/${id}/status`);
|
||||
|
||||
@@ -194,6 +194,8 @@
|
||||
onDestroy(() => {
|
||||
$status.application.initialLoading = true;
|
||||
$status.application.loading = false;
|
||||
$status.application.statuses = [];
|
||||
$status.application.overallStatus = 'stopped';
|
||||
$location = null;
|
||||
$isDeploymentEnabled = false;
|
||||
clearInterval(statusInterval);
|
||||
@@ -233,7 +235,7 @@
|
||||
class:text-red-500={$status.application.overallStatus === 'stopped'}
|
||||
>
|
||||
{$status.application.overallStatus === 'healthy'
|
||||
? 'Running'
|
||||
? 'Healthy'
|
||||
: $status.application.overallStatus === 'degraded'
|
||||
? 'Degraded'
|
||||
: 'Stopped'}
|
||||
@@ -242,14 +244,14 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if $page.url.pathname.startsWith(`/applications/${id}/configuration/`)}
|
||||
<div class="px-2">
|
||||
<div class="px-4">
|
||||
{#if forceDelete}
|
||||
<button
|
||||
on:click={() => deleteApplication(application.name, true)}
|
||||
disabled={!$appSession.isAdmin}
|
||||
class:bg-red-600={$appSession.isAdmin}
|
||||
class:hover:bg-red-500={$appSession.isAdmin}
|
||||
class="btn btn-sm btn-error text-sm"
|
||||
class="btn btn-sm btn-error hover:bg-red-700 text-sm w-64"
|
||||
>
|
||||
Force Delete Application
|
||||
</button>
|
||||
@@ -259,7 +261,7 @@
|
||||
disabled={!$appSession.isAdmin}
|
||||
class:bg-red-600={$appSession.isAdmin}
|
||||
class:hover:bg-red-500={$appSession.isAdmin}
|
||||
class="btn btn-sm btn-error text-sm"
|
||||
class="btn btn-sm btn-error hover:bg-red-700 text-sm w-64"
|
||||
>
|
||||
Delete Application
|
||||
</button>
|
||||
@@ -296,7 +298,29 @@
|
||||
Application Error
|
||||
</a>
|
||||
{/if}
|
||||
{#if $status.application.initialLoading}
|
||||
{#if stopping}
|
||||
<button class="btn btn-ghost btn-sm gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 animate-spin duration-500 ease-in-out"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
||||
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
||||
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
||||
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
||||
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
||||
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
||||
</svg>
|
||||
Stopping...
|
||||
</button>
|
||||
{:else if $status.application.initialLoading}
|
||||
<button class="btn btn-ghost btn-sm gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -319,27 +343,6 @@
|
||||
Loading...
|
||||
</button>
|
||||
{:else if $status.application.overallStatus === 'healthy'}
|
||||
<button
|
||||
on:click={stopApplication}
|
||||
type="submit"
|
||||
disabled={!$isDeploymentEnabled}
|
||||
class="btn btn-sm btn-error gap-2"
|
||||
>
|
||||
<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" />
|
||||
<rect x="6" y="5" width="4" height="14" rx="1" />
|
||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||
</svg> Stop
|
||||
</button>
|
||||
{#if application.buildPack !== 'compose'}
|
||||
<button
|
||||
on:click={restartApplication}
|
||||
@@ -387,17 +390,38 @@
|
||||
|
||||
Force Redeploy
|
||||
</button>
|
||||
<button
|
||||
on:click={stopApplication}
|
||||
type="submit"
|
||||
disabled={!$isDeploymentEnabled}
|
||||
class="btn btn-sm gap-2"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 text-error"
|
||||
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" />
|
||||
<rect x="6" y="5" width="4" height="14" rx="1" />
|
||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||
</svg> Stop
|
||||
</button>
|
||||
{:else if $isDeploymentEnabled && !$page.url.pathname.startsWith(`/applications/${id}/configuration/`)}
|
||||
{#if $status.application.overallStatus === 'degraded'}
|
||||
<button
|
||||
on:click={stopApplication}
|
||||
type="submit"
|
||||
disabled={!$isDeploymentEnabled}
|
||||
class="btn btn-sm btn-error gap-2"
|
||||
class="btn btn-sm gap-2"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 "
|
||||
class="w-6 h-6 text-error"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@@ -413,14 +437,13 @@
|
||||
{/if}
|
||||
<button
|
||||
class="btn btn-sm gap-2"
|
||||
class:btn-primary={$status.application.overallStatus !== 'degraded'}
|
||||
disabled={!$isDeploymentEnabled}
|
||||
on:click={() => handleDeploySubmit(false)}
|
||||
>
|
||||
{#if $status.application.overallStatus !== 'degraded'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="w-6 h-6 text-pink-500"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@@ -457,7 +480,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
{#if $location && $status.application.overallStatus === 'healthy'}
|
||||
<a href={$location} target="_blank" class="btn btn-sm gap-2 text-sm bg-primary"
|
||||
<a href={$location} target="_blank noreferrer" class="btn btn-sm gap-2 text-sm bg-primary"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
@@ -478,7 +501,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx-auto max-w-screen-2xl px-0 lg:px-2 grid grid-cols-1"
|
||||
class="mx-auto max-w-screen-2xl px-0 lg:px-10 grid grid-cols-1"
|
||||
class:lg:grid-cols-4={!$page.url.pathname.startsWith(`/applications/${id}/configuration/`)}
|
||||
>
|
||||
{#if !$page.url.pathname.startsWith(`/applications/${id}/configuration/`)}
|
||||
|
||||
@@ -92,27 +92,9 @@
|
||||
label: branch.name
|
||||
}));
|
||||
}
|
||||
async function isBranchAlreadyUsed(event: any) {
|
||||
async function selectBranch(event: any) {
|
||||
selected.branch = event.detail.value;
|
||||
try {
|
||||
// const data = await get(
|
||||
// `/applications/${id}/configuration/repository?repository=${selected.repository}&branch=${selected.branch}`
|
||||
// );
|
||||
// if (data.used) {
|
||||
// const sure = confirm($t('application.configuration.branch_already_in_use'));
|
||||
// if (sure) {
|
||||
// selected.autodeploy = false;
|
||||
// showSave = true;
|
||||
// return true;
|
||||
// }
|
||||
// showSave = false;
|
||||
// return true;
|
||||
// }
|
||||
showSave = true;
|
||||
} catch (error) {
|
||||
showSave = false;
|
||||
return errorNotification(error);
|
||||
}
|
||||
showSave = true;
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
@@ -178,7 +160,7 @@
|
||||
isWaiting={loading.branches}
|
||||
showIndicator={selected.repository && !loading.branches}
|
||||
id="branches"
|
||||
on:select={isBranchAlreadyUsed}
|
||||
on:select={selectBranch}
|
||||
items={branchSelectOptions}
|
||||
isDisabled={loading.branches || !selected.repository}
|
||||
isClearable={false}
|
||||
@@ -186,10 +168,9 @@
|
||||
</div></div>
|
||||
<div class="pt-5 flex-col flex justify-center items-center space-y-4">
|
||||
<button
|
||||
class="btn btn-wide"
|
||||
class="btn btn-wide btn-primary"
|
||||
type="submit"
|
||||
disabled={!showSave}
|
||||
class:bg-applications={showSave}
|
||||
>{$t('forms.save')}</button
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -195,27 +195,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function isBranchAlreadyUsed(event) {
|
||||
async function selectBranch(event: any) {
|
||||
selected.branch = event.detail;
|
||||
try {
|
||||
// const data = await get(
|
||||
// `/applications/${id}/configuration/repository?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
|
||||
// );
|
||||
// if (data.used) {
|
||||
// const sure = confirm($t('application.configuration.branch_already_in_use'));
|
||||
// if (sure) {
|
||||
// autodeploy = false;
|
||||
// showSave = true;
|
||||
// return true;
|
||||
// }
|
||||
// showSave = false;
|
||||
// return true;
|
||||
// }
|
||||
showSave = true;
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
showSave = true;
|
||||
}
|
||||
|
||||
async function checkSSHKey(sshkeyUrl: any) {
|
||||
try {
|
||||
return await post(sshkeyUrl, {});
|
||||
@@ -394,7 +378,7 @@
|
||||
showIndicator={!loading.branches}
|
||||
isWaiting={loading.branches}
|
||||
isDisabled={loading.branches || !selected.project}
|
||||
on:select={isBranchAlreadyUsed}
|
||||
on:select={selectBranch}
|
||||
on:clear={() => {
|
||||
showSave = false;
|
||||
selected.branch = null;
|
||||
@@ -414,7 +398,6 @@
|
||||
class="btn btn-wide"
|
||||
type="submit"
|
||||
disabled={!showSave || loading.save}
|
||||
class:bg-applications={showSave && !loading.save}
|
||||
>{loading.save ? $t('forms.saving') : $t('forms.save')}</button
|
||||
>
|
||||
{#if tryAgain}
|
||||
@@ -423,7 +406,7 @@
|
||||
configuration <a href={`/sources/${application.gitSource.id}`}>here.</a>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-sm w-40 bg-green-600"
|
||||
class="btn btn-sm w-40 btn-primary"
|
||||
on:click|stopPropagation|preventDefault={() => window.location.reload()}
|
||||
>
|
||||
Try again
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
if (branch[0] === 'tree' && branch[1]) {
|
||||
branchName = branch[1];
|
||||
}
|
||||
if (branch.length === 1) {
|
||||
branchName = branch[0]
|
||||
}
|
||||
}
|
||||
if (host === 'gitlab.com') {
|
||||
host = 'gitlab.com/api/v4';
|
||||
@@ -46,6 +49,9 @@
|
||||
if (branch[1] === 'tree' && branch[2]) {
|
||||
branchName = branch[2];
|
||||
}
|
||||
if (branch.length === 1) {
|
||||
branchName = branch[0]
|
||||
}
|
||||
}
|
||||
const apiUrl = `${protocol}://${host}`;
|
||||
if (type === 'github') {
|
||||
@@ -165,7 +171,7 @@
|
||||
placeholder="eg: https://github.com/coollabsio/nodejs-example/tree/main"
|
||||
bind:value={publicRepositoryLink}
|
||||
/>
|
||||
<button class="btn bg-orange-600" type="submit">
|
||||
<button class="btn btn-primary" disabled={loading.branches} type="submit" class:loading={loading.branches}>
|
||||
Load Repository
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,11 @@
|
||||
{$t('application.configuration.no_configurable_destination')}
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<a href="/destinations/new" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
|
||||
<a
|
||||
href={`/destinations/new?from=/applications/${id}/configuration/destination`}
|
||||
sveltekit:prefetch
|
||||
class="add-icon bg-sky-600 hover:bg-sky-500"
|
||||
>
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -88,31 +92,111 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row mx-auto">
|
||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row mx-auto gap-4">
|
||||
{#each ownDestinations as destination}
|
||||
<div class="p-2">
|
||||
<form on:submit|preventDefault={() => handleSubmit(destination.id)}>
|
||||
<button type="submit" class="box-selection hover:bg-sky-700 font-bold">
|
||||
<div class="font-bold text-xl text-center truncate">{destination.name}</div>
|
||||
<div class="text-center truncate">{destination.network}</div>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<button
|
||||
on:click={() => handleSubmit(destination.id)}
|
||||
class="box-selection hover:bg-primary font-bold relative"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
|
||||
/>
|
||||
<path d="M5 10h3v3h-3z" />
|
||||
<path d="M8 10h3v3h-3z" />
|
||||
<path d="M11 10h3v3h-3z" />
|
||||
<path d="M8 7h3v3h-3z" />
|
||||
<path d="M11 7h3v3h-3z" />
|
||||
<path d="M11 4h3v3h-3z" />
|
||||
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
|
||||
<line x1="10" y1="16" x2="10" y2="16.01" />
|
||||
</svg>
|
||||
{#if destination.remoteEngine}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="absolute top-0 left-9 -m-2 h-6 w-6 text-sky-500 rotate-45"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="3"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<line x1="12" y1="18" x2="12.01" y2="18" />
|
||||
<path d="M9.172 15.172a4 4 0 0 1 5.656 0" />
|
||||
<path d="M6.343 12.343a8 8 0 0 1 11.314 0" />
|
||||
<path d="M3.515 9.515c4.686 -4.687 12.284 -4.687 17 0" />
|
||||
</svg>
|
||||
{/if}
|
||||
<div class="font-bold text-xl text-center truncate">{destination.name}</div>
|
||||
<div class="text-center truncate">{destination.network}</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{#if otherDestinations.length > 0 && $appSession.teamId === '0'}
|
||||
<div class="px-6 pb-5 pt-10 title">Other Destinations</div>
|
||||
{/if}
|
||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row mx-auto">
|
||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row mx-auto gap-4">
|
||||
{#each otherDestinations as destination}
|
||||
<div class="p-2">
|
||||
<form on:submit|preventDefault={() => handleSubmit(destination.id)}>
|
||||
<button type="submit" class="box-selection hover:bg-sky-700 font-bold">
|
||||
<div class="font-bold text-xl text-center truncate">{destination.name}</div>
|
||||
<div class="text-center truncate">{destination.network}</div>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<button
|
||||
class="box-selection hover:bg-sky-700 font-bold relative"
|
||||
on:click={() => handleSubmit(destination.id)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
|
||||
/>
|
||||
<path d="M5 10h3v3h-3z" />
|
||||
<path d="M8 10h3v3h-3z" />
|
||||
<path d="M11 10h3v3h-3z" />
|
||||
<path d="M8 7h3v3h-3z" />
|
||||
<path d="M11 7h3v3h-3z" />
|
||||
<path d="M11 4h3v3h-3z" />
|
||||
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
|
||||
<line x1="10" y1="16" x2="10" y2="16.01" />
|
||||
</svg>
|
||||
{#if destination.remoteEngine}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="absolute top-0 left-9 -m-2 h-6 w-6 text-sky-500 rotate-45"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="3"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<line x1="12" y1="18" x2="12.01" y2="18" />
|
||||
<path d="M9.172 15.172a4 4 0 0 1 5.656 0" />
|
||||
<path d="M6.343 12.343a8 8 0 0 1 11.314 0" />
|
||||
<path d="M3.515 9.515c4.686 -4.687 12.284 -4.687 17 0" />
|
||||
</svg>
|
||||
{/if}
|
||||
<div class="font-bold text-xl text-center truncate">{destination.name}</div>
|
||||
<div class="text-center truncate">{destination.network}</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
||||
try {
|
||||
const { application } = stuff;
|
||||
if (application?.destinationDockerId && !url.searchParams.get('from')) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: `/applications/${params.id}`
|
||||
};
|
||||
}
|
||||
const response = await get(`/settings`);
|
||||
return {
|
||||
props: {
|
||||
...response
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 500,
|
||||
error: new Error(`Could not load ${url}`)
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let registries: any;
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { get, post } from '$lib/api';
|
||||
import { errorNotification } from '$lib/common';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from');
|
||||
|
||||
async function handleSubmit(registryId: any) {
|
||||
try {
|
||||
await post(`/applications/${id}/configuration/registry`, { registryId });
|
||||
return await goto(from || `/applications/${id}`);
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col justify-center w-full">
|
||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row mx-auto gap-4">
|
||||
{#each registries.public as registry}
|
||||
<button
|
||||
on:click={() => handleSubmit(registry.id)}
|
||||
class="box-selection hover:bg-primary relative"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
|
||||
/>
|
||||
<path d="M5 10h3v3h-3z" />
|
||||
<path d="M8 10h3v3h-3z" />
|
||||
<path d="M11 10h3v3h-3z" />
|
||||
<path d="M8 7h3v3h-3z" />
|
||||
<path d="M11 7h3v3h-3z" />
|
||||
<path d="M11 4h3v3h-3z" />
|
||||
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
|
||||
<line x1="10" y1="16" x2="10" y2="16.01" />
|
||||
</svg>
|
||||
|
||||
<div class="font-bold text-xl text-center truncate">{registry.name}</div>
|
||||
<div class="text-center truncate">{registry.url}</div>
|
||||
<div>public</div>
|
||||
</button>
|
||||
{/each}
|
||||
{#each registries.private as registry}
|
||||
<button
|
||||
on:click={() => handleSubmit(registry.id)}
|
||||
class="box-selection hover:bg-primary relative"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
|
||||
/>
|
||||
<path d="M5 10h3v3h-3z" />
|
||||
<path d="M8 10h3v3h-3z" />
|
||||
<path d="M11 10h3v3h-3z" />
|
||||
<path d="M8 7h3v3h-3z" />
|
||||
<path d="M11 7h3v3h-3z" />
|
||||
<path d="M11 4h3v3h-3z" />
|
||||
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
|
||||
<line x1="10" y1="16" x2="10" y2="16.01" />
|
||||
</svg>
|
||||
|
||||
<div class="font-bold text-xl text-center truncate">{registry.name}</div>
|
||||
<div class="text-center truncate">{registry.url}</div>
|
||||
<div>private</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,7 +68,9 @@
|
||||
</script>
|
||||
|
||||
<div class="max-w-screen-2xl mx-auto px-9">
|
||||
<div class="title pb-8">Git App</div>
|
||||
{#if !filteredSources}
|
||||
<div class="title pb-8">Git App</div>
|
||||
{/if}
|
||||
<div class="flex flex-wrap justify-center">
|
||||
{#if !filteredSources}
|
||||
<div class="flex-col">
|
||||
@@ -76,10 +78,7 @@
|
||||
{$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">
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -141,7 +140,7 @@
|
||||
<button
|
||||
disabled={source.gitlabApp && !source.gitlabAppId}
|
||||
type="submit"
|
||||
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group w-full lg:w-96"
|
||||
class="disabled:opacity-95 disabled:text-white box-selection hover:btn-primary group w-full lg:w-96"
|
||||
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
||||
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
||||
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
||||
@@ -244,7 +243,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row items-center">
|
||||
<div class="title py-4">Public Repository</div>
|
||||
<div class="title py-4 pr-4">Public Repository</div>
|
||||
<DocLink url="https://docs.coollabs.io/coolify/applications/#public-repository" />
|
||||
</div>
|
||||
<PublicRepository />
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
disabled={!$appSession.isAdmin}
|
||||
class:bg-red-600={$appSession.isAdmin}
|
||||
class:hover:bg-red-500={$appSession.isAdmin}
|
||||
class="btn btn-sm btn-error text-sm"
|
||||
class="btn btn-lg btn-error hover:bg-red-700 text-sm w-64"
|
||||
>
|
||||
Force Delete Application
|
||||
</button>
|
||||
@@ -71,7 +71,7 @@
|
||||
on:click={() => deleteApplication(application.name, false)}
|
||||
type="submit"
|
||||
disabled={!$appSession.isAdmin}
|
||||
class="btn btn-lg btn-error hover:bg-red-700 text-sm"
|
||||
class="btn btn-lg btn-error hover:bg-red-700 text-sm w-64"
|
||||
>
|
||||
Delete Application
|
||||
</button>
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
let debug = application.settings.debug;
|
||||
let previews = application.settings.previews;
|
||||
let dualCerts = application.settings.dualCerts;
|
||||
let autodeploy = application.settings.autodeploy;
|
||||
@@ -52,9 +51,6 @@
|
||||
let isDBBranching = application.settings.isDBBranching;
|
||||
|
||||
async function changeSettings(name: any) {
|
||||
if (name === 'debug') {
|
||||
debug = !debug;
|
||||
}
|
||||
if (name === 'previews') {
|
||||
previews = !previews;
|
||||
}
|
||||
@@ -77,7 +73,6 @@
|
||||
try {
|
||||
await post(`/applications/${id}/settings`, {
|
||||
previews,
|
||||
debug,
|
||||
dualCerts,
|
||||
isBot,
|
||||
autodeploy,
|
||||
@@ -90,9 +85,6 @@
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
if (name === 'debug') {
|
||||
debug = !debug;
|
||||
}
|
||||
if (name === 'previews') {
|
||||
previews = !previews;
|
||||
}
|
||||
@@ -132,29 +124,21 @@
|
||||
description={$t('application.enable_auto_deploy_webhooks')}
|
||||
/>
|
||||
</div>
|
||||
{#if !application.settings.isBot}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
id="previews"
|
||||
isCenter={false}
|
||||
bind:setting={previews}
|
||||
on:click={() => changeSettings('previews')}
|
||||
title={$t('application.enable_mr_pr_previews')}
|
||||
description={$t('application.enable_preview_deploy_mr_pr_requests')}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
No features available for this application
|
||||
{/if}
|
||||
{#if !application.settings.isBot && !application.settings.isPublicRepository}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
id="previews"
|
||||
isCenter={false}
|
||||
bind:setting={previews}
|
||||
on:click={() => changeSettings('previews')}
|
||||
title={$t('application.enable_mr_pr_previews')}
|
||||
description={$t('application.enable_preview_deploy_mr_pr_requests')}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-2 items-center w-full">
|
||||
<Setting
|
||||
id="debug"
|
||||
isCenter={false}
|
||||
bind:setting={debug}
|
||||
on:click={() => changeSettings('debug')}
|
||||
title={$t('application.debug_logs')}
|
||||
description={$t('application.enable_debug_log_during_build')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,26 +61,29 @@
|
||||
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
||||
let statues: any = {};
|
||||
let loading = false;
|
||||
let loading = {
|
||||
save: false,
|
||||
reloadCompose: false
|
||||
};
|
||||
let fqdnEl: any = null;
|
||||
let forceSave = false;
|
||||
let isPublicRepository = application.settings.isPublicRepository;
|
||||
let apiUrl = application.gitSource.apiUrl;
|
||||
let isPublicRepository = application.settings?.isPublicRepository;
|
||||
let apiUrl = application.gitSource?.apiUrl;
|
||||
let branch = application.branch;
|
||||
let repository = application.repository;
|
||||
let debug = application.settings.debug;
|
||||
let previews = application.settings.previews;
|
||||
let dualCerts = application.settings.dualCerts;
|
||||
let isCustomSSL = application.settings.isCustomSSL;
|
||||
let autodeploy = application.settings.autodeploy;
|
||||
let isBot = application.settings.isBot;
|
||||
let isDBBranching = application.settings.isDBBranching;
|
||||
let htmlUrl = application.gitSource.htmlUrl;
|
||||
let debug = application.settings?.debug;
|
||||
let previews = application.settings?.previews;
|
||||
let dualCerts = application.settings?.dualCerts;
|
||||
let isCustomSSL = application.settings?.isCustomSSL;
|
||||
let autodeploy = application.settings?.autodeploy;
|
||||
let isBot = application.settings?.isBot;
|
||||
let isDBBranching = application.settings?.isDBBranching;
|
||||
let htmlUrl = application.gitSource?.htmlUrl;
|
||||
|
||||
let dockerComposeFile = JSON.parse(application.dockerComposeFile) || null;
|
||||
let dockerComposeServices: any[] = [];
|
||||
let dockerComposeFileLocation = application.dockerComposeFileLocation;
|
||||
let dockerComposeConfiguration = JSON.parse(application.dockerComposeConfiguration) || {};
|
||||
let originalDockerComposeFileLocation = application.dockerComposeFileLocation;
|
||||
|
||||
let baseDatabaseBranch: any = application?.connectedDatabase?.hostedDatabaseDBName || null;
|
||||
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||
@@ -102,7 +105,6 @@
|
||||
label: 'Uvicorn'
|
||||
}
|
||||
];
|
||||
|
||||
function normalizeDockerServices(services: any[]) {
|
||||
const tempdockerComposeServices = [];
|
||||
for (const [name, data] of Object.entries(services)) {
|
||||
@@ -237,12 +239,16 @@
|
||||
}
|
||||
}
|
||||
async function handleSubmit(toast: boolean = true) {
|
||||
if (loading) return;
|
||||
if (toast) loading = true;
|
||||
if (loading.save) return;
|
||||
if (toast) loading.save = true;
|
||||
try {
|
||||
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||
if (application.deploymentType)
|
||||
if (application.deploymentType) {
|
||||
application.deploymentType = application.deploymentType.toLowerCase();
|
||||
}
|
||||
if (originalDockerComposeFileLocation !== application.dockerComposeFileLocation) {
|
||||
await reloadCompose();
|
||||
}
|
||||
if (!isBot) {
|
||||
await post(`/applications/${id}/check`, {
|
||||
fqdn: application.fqdn,
|
||||
@@ -299,7 +305,7 @@
|
||||
}
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
loading.save = false;
|
||||
}
|
||||
}
|
||||
async function selectWSGI(event: any) {
|
||||
@@ -361,6 +367,11 @@
|
||||
});
|
||||
}
|
||||
async function reloadCompose() {
|
||||
if (loading.reloadCompose) return;
|
||||
loading.reloadCompose = true;
|
||||
const composeLocation = application.dockerComposeFileLocation.startsWith('/')
|
||||
? application.dockerComposeFileLocation
|
||||
: `/${application.dockerComposeFileLocation}`;
|
||||
try {
|
||||
if (application.gitSource.type === 'github') {
|
||||
const headers = isPublicRepository
|
||||
@@ -369,9 +380,10 @@
|
||||
Authorization: `token ${$appSession.tokens.github}`
|
||||
};
|
||||
const data = await get(
|
||||
`${apiUrl}/repos/${repository}/contents/${dockerComposeFileLocation}?ref=${branch}`,
|
||||
`${apiUrl}/repos/${repository}/contents/${composeLocation}?ref=${branch}`,
|
||||
{
|
||||
...headers,
|
||||
'If-None-Match': '',
|
||||
Accept: 'application/vnd.github.v2.json'
|
||||
}
|
||||
);
|
||||
@@ -401,7 +413,7 @@
|
||||
});
|
||||
const dockerComposeFileYml = files.find(
|
||||
(file: { name: string; type: string }) =>
|
||||
file.name === dockerComposeFileLocation && file.type === 'blob'
|
||||
file.name === composeLocation && file.type === 'blob'
|
||||
);
|
||||
const id = dockerComposeFileYml.id;
|
||||
|
||||
@@ -420,13 +432,20 @@
|
||||
await handleSubmit(false);
|
||||
}
|
||||
}
|
||||
|
||||
originalDockerComposeFileLocation = application.dockerComposeFileLocation;
|
||||
addToast({
|
||||
message: 'Compose file reloaded.',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
if (error.message === 'Not Found') {
|
||||
error.message = `Can't find ${application.dockerComposeFileLocation} file.`;
|
||||
errorNotification(error);
|
||||
throw error;
|
||||
}
|
||||
errorNotification(error);
|
||||
} finally {
|
||||
loading.reloadCompose = false;
|
||||
}
|
||||
}
|
||||
$: if ($status.application.statuses) {
|
||||
@@ -459,15 +478,15 @@
|
||||
<form on:submit|preventDefault={() => handleSubmit()}>
|
||||
<div class="mx-auto w-full">
|
||||
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
|
||||
<div class="title font-bold pb-3 ">General</div>
|
||||
<div class="title font-bold pb-3">General</div>
|
||||
{#if $appSession.isAdmin}
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
type="submit"
|
||||
class:loading
|
||||
class:loading={loading.save}
|
||||
class:bg-orange-600={forceSave}
|
||||
class:hover:bg-orange-400={forceSave}
|
||||
disabled={loading}>{$t('forms.save')}</button
|
||||
disabled={loading.save}>{$t('forms.save')}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -482,14 +501,14 @@
|
||||
<input
|
||||
disabled={isDisabled || application.settings.isPublicRepository}
|
||||
class="w-full"
|
||||
value={application.gitSource.name}
|
||||
value={application.gitSource?.name}
|
||||
/>
|
||||
{:else}
|
||||
<a
|
||||
href={`/applications/${id}/configuration/source?from=/applications/${id}`}
|
||||
class="no-underline"
|
||||
><input
|
||||
value={application.gitSource.name}
|
||||
value={application.gitSource?.name}
|
||||
id="gitSource"
|
||||
class="cursor-pointer hover:bg-coolgray-500 w-full"
|
||||
/></a
|
||||
@@ -533,6 +552,27 @@
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="registry">Docker Registry</label>
|
||||
{#if isDisabled}
|
||||
<input
|
||||
class="capitalize w-full"
|
||||
disabled={isDisabled}
|
||||
value={application.dockerRegistry.name}
|
||||
/>
|
||||
{:else}
|
||||
<a
|
||||
href={`/applications/${id}/configuration/registry?from=/applications/${id}`}
|
||||
class="no-underline"
|
||||
>
|
||||
<input
|
||||
value={application.dockerRegistry.name}
|
||||
id="registry"
|
||||
class="cursor-pointer hover:bg-coolgray-500 capitalize w-full"
|
||||
/></a
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="buildPack">{$t('application.build_pack')} </label>
|
||||
{#if isDisabled}
|
||||
@@ -586,13 +626,13 @@
|
||||
<input
|
||||
bind:this={fqdnEl}
|
||||
class="w-full"
|
||||
required={!application.settings.isBot}
|
||||
required={!application.settings?.isBot}
|
||||
readonly={isDisabled}
|
||||
disabled={isDisabled}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
class:border={!application.settings.isBot && !application.fqdn}
|
||||
class:border-red-500={!application.settings.isBot && !application.fqdn}
|
||||
class:border={!application.settings?.isBot && !application.fqdn}
|
||||
class:border-red-500={!application.settings?.isBot && !application.fqdn}
|
||||
bind:value={application.fqdn}
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
placeholder="eg: https://coollabs.io"
|
||||
@@ -661,7 +701,7 @@
|
||||
</div>
|
||||
{#if application.buildPack !== 'compose'}
|
||||
<div class="title font-bold pb-3 pt-10 border-b border-coolgray-500 mb-6">
|
||||
Build & Deploy
|
||||
Configuration
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2 px-4 pr-5">
|
||||
{#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'}
|
||||
@@ -731,7 +771,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if $features.beta}
|
||||
{#if !application.settings.isBot && !application.settings.isPublicRepository}
|
||||
{#if !application.settings?.isBot && !application.settings?.isPublicRepository}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
id="isDBBranching"
|
||||
@@ -855,8 +895,8 @@
|
||||
>
|
||||
<input
|
||||
class="w-full"
|
||||
readonly={!isDisabled}
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="exposePort"
|
||||
id="exposePort"
|
||||
bind:value={application.exposePort}
|
||||
@@ -1010,12 +1050,34 @@
|
||||
<div class="title font-bold pb-3 pt-10 border-b border-coolgray-500 mb-6">
|
||||
Stack <Beta />
|
||||
{#if $appSession.isAdmin}
|
||||
<button class="btn btn-sm btn-primary" on:click|preventDefault={reloadCompose}
|
||||
>Reload Docker Compose File</button
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
class:loading={loading.reloadCompose}
|
||||
disabled={loading.reloadCompose}
|
||||
on:click|preventDefault={reloadCompose}>Reload Docker Compose File</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2">
|
||||
<div class="grid grid-cols-2 items-center px-8 pb-4">
|
||||
<label for="dockerComposeFileLocation"
|
||||
>Docker Compose File Location
|
||||
<Explainer
|
||||
explanation="You can specify a custom docker compose file location. <br> Should be absolute path, like <span class='text-settings font-bold'>/data/docker-compose.yml</span> or <span class='text-settings font-bold'>/docker-compose.yml.</span>"
|
||||
/>
|
||||
</label>
|
||||
<div>
|
||||
<input
|
||||
class="w-full"
|
||||
disabled={isDisabled}
|
||||
readonly={!$appSession.isAdmin}
|
||||
name="dockerComposeFileLocation"
|
||||
id="dockerComposeFileLocation"
|
||||
bind:value={application.dockerComposeFileLocation}
|
||||
placeholder="eg: /docker-compose.yml"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#each dockerComposeServices as service}
|
||||
<div
|
||||
class="grid items-center bg-coolgray-100 rounded border border-coolgray-300 p-2 px-4"
|
||||
|
||||
@@ -61,12 +61,27 @@
|
||||
fromDb = from;
|
||||
|
||||
streamInterval = setInterval(async () => {
|
||||
const nextSequence = logs[logs.length - 1]?.time || 0;
|
||||
if (status !== 'running' && status !== 'queued') {
|
||||
loading = false;
|
||||
try {
|
||||
const data = await get(
|
||||
`/applications/${id}/logs/build/${$selectedBuildId}?sequence=${nextSequence}`
|
||||
);
|
||||
status = data.status;
|
||||
currentStatus = status;
|
||||
fromDb = data.fromDb;
|
||||
|
||||
logs = logs.concat(
|
||||
data.logs.map((log: any) => ({ ...log, line: cleanAnsiCodes(log.line) }))
|
||||
);
|
||||
loading = false;
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
clearInterval(streamInterval);
|
||||
return;
|
||||
}
|
||||
const nextSequence = logs[logs.length - 1]?.time || 0;
|
||||
try {
|
||||
const data = await get(
|
||||
`/applications/${id}/logs/build/${$selectedBuildId}?sequence=${nextSequence}`
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
import { day } from '$lib/dayjs';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
const { id } = $page.params;
|
||||
|
||||
let debug = application.settings.debug;
|
||||
let loadBuildLogsInterval: any = null;
|
||||
|
||||
let skip = 0;
|
||||
@@ -104,42 +104,74 @@
|
||||
return 'text-white';
|
||||
}
|
||||
}
|
||||
async function changeSettings(name: any) {
|
||||
if (name === 'debug') {
|
||||
debug = !debug;
|
||||
}
|
||||
try {
|
||||
await post(`/applications/${id}/settings`, {
|
||||
debug,
|
||||
branch: application.branch,
|
||||
projectId: application.projectId
|
||||
});
|
||||
return addToast({
|
||||
message: $t('application.settings_saved'),
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
if (name === 'debug') {
|
||||
debug = !debug;
|
||||
}
|
||||
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mx-auto w-full">
|
||||
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
|
||||
<div class="mx-auto w-full lg:px-0 px-1">
|
||||
<div class="flex lg:flex-row flex-col border-b border-coolgray-500 mb-6 space-x-2">
|
||||
<div class="flex flex-row">
|
||||
<div class="title font-bold pb-3 pr-3">Build Logs</div>
|
||||
<button class="btn btn-sm bg-error" on:click={resetQueue}>Reset Build Queue</button>
|
||||
</div>
|
||||
<div class=" flex-1" />
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer">
|
||||
<span class="label-text text-white pr-4 font-bold">Enable Debug Logs</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={debug}
|
||||
class="checkbox checkbox-success"
|
||||
on:click={() => changeSettings('debug')}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block flex-col justify-start space-x-5 flex flex-col-reverse lg:flex-row">
|
||||
<div class="justify-start space-x-5 flex flex-col-reverse lg:flex-row">
|
||||
<div class="flex-1 md:w-96">
|
||||
{#if $selectedBuildId}
|
||||
{#key $selectedBuildId}
|
||||
<svelte:component this={BuildLog} />
|
||||
{/key}
|
||||
{:else if buildCount === 0}
|
||||
Not build logs found.
|
||||
{:else}
|
||||
{#if buildCount === 0}
|
||||
Not build logs found.
|
||||
{:else}
|
||||
Select a build to see the logs.
|
||||
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mb-4 min-w-[16rem] space-y-2 md:mb-0 ">
|
||||
<div class="top-4 md:sticky">
|
||||
<div class="flex space-x-2 pb-2">
|
||||
<button
|
||||
disabled={noMoreBuilds}
|
||||
class:btn-primary={!noMoreBuilds}
|
||||
class=" btn btn-sm w-full"
|
||||
on:click={loadMoreBuilds}>{$t('application.build.load_more')}</button
|
||||
>
|
||||
</div>
|
||||
<div class="flex space-x-2 pb-2">
|
||||
<button
|
||||
disabled={noMoreBuilds}
|
||||
class:btn-primary={!noMoreBuilds}
|
||||
class=" btn btn-sm w-full"
|
||||
on:click={loadMoreBuilds}>{$t('application.build.load_more')}</button
|
||||
>
|
||||
</div>
|
||||
{#each builds as build, index (build.id)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
id={`building-${build.id}`}
|
||||
on:click={() => loadBuild(build.id)}
|
||||
@@ -187,4 +219,4 @@
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { get } from '$lib/api';
|
||||
import { t } from '$lib/translations';
|
||||
import { errorNotification } from '$lib/common';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||
|
||||
let application: any = {};
|
||||
let logsLoading = false;
|
||||
@@ -137,12 +135,7 @@
|
||||
{:else}
|
||||
<div class="relative w-full">
|
||||
<div class="flex justify-start sticky space-x-2 pb-2">
|
||||
<button
|
||||
on:click={followBuild}
|
||||
class="btn btn-sm bg-coollabs"
|
||||
class:bg-coolgray-300={followingLogs}
|
||||
class:text-applications={followingLogs}
|
||||
>
|
||||
<button on:click={followBuild} class="btn btn-sm " class:bg-coollabs={followingLogs}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 mr-2"
|
||||
@@ -162,8 +155,9 @@
|
||||
{followingLogs ? 'Following Logs...' : 'Follow Logs'}
|
||||
</button>
|
||||
{#if loadLogsInterval}
|
||||
<button id="streaming" class="btn btn-sm bg-transparent border-none loading" />
|
||||
<Tooltip triggeredBy="#streaming">Streaming logs</Tooltip>
|
||||
<button id="streaming" class="btn btn-sm bg-transparent border-none loading"
|
||||
>Streaming logs</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -216,7 +216,7 @@
|
||||
|
||||
<div class="flex justify-end items-end space-x-2 h-10">
|
||||
{#if preview.customDomain}
|
||||
<a id="openpreview" href={preview.customDomain} target="_blank" class="icons">
|
||||
<a id="openpreview" href={preview.customDomain} target="_blank noreferrer" class="icons">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
|
||||
@@ -48,10 +48,10 @@
|
||||
.map((secret) => {
|
||||
const [name, ...rest] = secret.split('=');
|
||||
const value = rest.join('=');
|
||||
const cleanValue = value?.replaceAll('"', '') || '';
|
||||
const cleanValue = (value?.replaceAll('"', '') || '').trim();
|
||||
return {
|
||||
name,
|
||||
value: cleanValue,
|
||||
name: name.trim(),
|
||||
value: cleanValue.trim(),
|
||||
createSecret: !secrets.find((secret: any) => name === secret.name)
|
||||
};
|
||||
});
|
||||
@@ -60,6 +60,7 @@
|
||||
batchSecretsPairs.map(({ name, value, createSecret }) =>
|
||||
limit(async () => {
|
||||
try {
|
||||
if (!name || !value) return;
|
||||
if (createSecret) {
|
||||
await post(`/applications/${id}/secrets`, {
|
||||
name,
|
||||
@@ -87,10 +88,6 @@
|
||||
);
|
||||
batchSecrets = '';
|
||||
await refreshSecrets();
|
||||
addToast({
|
||||
message: 'Secrets saved.',
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ params, stuff, url }) => {
|
||||
try {
|
||||
const { application } = stuff;
|
||||
const response = await get(`/applications/${params.id}/storages`);
|
||||
return {
|
||||
props: {
|
||||
application,
|
||||
...response
|
||||
}
|
||||
};
|
||||
@@ -19,12 +21,31 @@
|
||||
|
||||
<script lang="ts">
|
||||
export let persistentStorages: any;
|
||||
export let application: any;
|
||||
import { page } from '$app/stores';
|
||||
import Storage from './_Storage.svelte';
|
||||
import { get } from '$lib/api';
|
||||
import { t } from '$lib/translations';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
|
||||
let composeJson = JSON.parse(application?.dockerComposeFile || '{}');
|
||||
let predefinedVolumes: any[] = [];
|
||||
if (composeJson?.services) {
|
||||
for (const [_, service] of Object.entries(composeJson.services)) {
|
||||
if (service?.volumes) {
|
||||
for (const [_, volumeName] of Object.entries(service.volumes)) {
|
||||
let [volume, target] = volumeName.split(':');
|
||||
if (!target) {
|
||||
target = volume;
|
||||
volume = `${application.id}${volume.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||
} else {
|
||||
volume = `${application.id}${volume.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||
}
|
||||
predefinedVolumes.push({ id: volume, path: target, predefined: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const { id } = $page.params;
|
||||
async function refreshStorage() {
|
||||
const data = await get(`/applications/${id}/storages`);
|
||||
@@ -34,20 +55,40 @@
|
||||
|
||||
<div class="w-full">
|
||||
<div class="mx-auto w-full">
|
||||
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
|
||||
<div class="title font-bold pb-3">
|
||||
Persistent Volumes <Explainer
|
||||
position="dropdown-bottom"
|
||||
explanation={$t('application.storage.persistent_storage_explainer')}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
|
||||
<div class="title font-bold pb-3">Persistent Volumes</div>
|
||||
</div>
|
||||
<label for="name" class="pb-2 uppercase font-bold">name</label>
|
||||
{#if predefinedVolumes.length > 0}
|
||||
<div class="title">Predefined Volumes</div>
|
||||
<div class="w-full lg:px-0 px-4">
|
||||
<div class="grid grid-col-1 lg:grid-cols-2 py-2 gap-2">
|
||||
<div class="font-bold uppercase">Volume Id</div>
|
||||
<div class="font-bold uppercase">Mount Dir</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gap-4">
|
||||
{#each predefinedVolumes as storage}
|
||||
{#key storage.id}
|
||||
<Storage on:refresh={refreshStorage} {storage} />
|
||||
{/key}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if persistentStorages.length > 0}
|
||||
<div class="title" class:pt-10={predefinedVolumes.length > 0}>Custom Volumes</div>
|
||||
{/if}
|
||||
{#each persistentStorages as storage}
|
||||
{#key storage.id}
|
||||
<Storage on:refresh={refreshStorage} {storage} />
|
||||
{/key}
|
||||
{/each}
|
||||
<div class="title pt-10">
|
||||
Add New Volume <Explainer
|
||||
position="dropdown-bottom"
|
||||
explanation={$t('application.storage.persistent_storage_explainer')}
|
||||
/>
|
||||
</div>
|
||||
<Storage on:refresh={refreshStorage} isNew />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user