mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-22 21:02:03 +00:00
Add Locale URL
This commit is contained in:
@@ -1,425 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
function checkConfiguration(application): string {
|
||||
let configurationPhase = null;
|
||||
if (!application.gitSourceId) {
|
||||
configurationPhase = 'source';
|
||||
} else if (!application.repository && !application.branch) {
|
||||
configurationPhase = 'repository';
|
||||
} else if (!application.destinationDockerId) {
|
||||
configurationPhase = 'destination';
|
||||
} else if (!application.buildPack) {
|
||||
configurationPhase = 'buildpack';
|
||||
}
|
||||
return configurationPhase;
|
||||
}
|
||||
export const load: Load = async ({ fetch, url, params }) => {
|
||||
const endpoint = `/applications/${params.id}.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
let { application, isRunning, appId, githubToken, gitlabToken } = await res.json();
|
||||
if (!application || Object.entries(application).length === 0) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/applications'
|
||||
};
|
||||
}
|
||||
if (application.gitSource?.githubAppId && !githubToken) {
|
||||
const response = await fetch(`/applications/${params.id}/configuration/githubToken.json`);
|
||||
if (response.ok) {
|
||||
const { token } = await response.json();
|
||||
githubToken = token;
|
||||
}
|
||||
}
|
||||
const configurationPhase = checkConfiguration(application);
|
||||
if (
|
||||
configurationPhase &&
|
||||
url.pathname !== `/applications/${params.id}/configuration/${configurationPhase}`
|
||||
) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: `/applications/${params.id}/configuration/${configurationPhase}`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
application,
|
||||
isRunning,
|
||||
githubToken,
|
||||
gitlabToken
|
||||
},
|
||||
stuff: {
|
||||
isRunning,
|
||||
application,
|
||||
appId
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/applications'
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
export let isRunning;
|
||||
export let githubToken;
|
||||
export let gitlabToken;
|
||||
import { page, session } from '$app/stores';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
import { del, post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
import { gitTokens } from '$lib/store';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
|
||||
if (githubToken) $gitTokens.githubToken = githubToken;
|
||||
if (gitlabToken) $gitTokens.gitlabToken = gitlabToken;
|
||||
|
||||
let loading = false;
|
||||
const { id } = $page.params;
|
||||
|
||||
async function handleDeploySubmit() {
|
||||
try {
|
||||
const { buildId } = await post(`/applications/${id}/deploy.json`, { ...application });
|
||||
toast.push('Deployment queued.');
|
||||
console.log($page.url);
|
||||
if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) {
|
||||
return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||
} else {
|
||||
return await goto(`/applications/${id}/logs/build?buildId=${buildId}`, {
|
||||
replaceState: true
|
||||
});
|
||||
}
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteApplication(name) {
|
||||
const sure = confirm(`Are you sure you would like to delete '${name}'?`);
|
||||
if (sure) {
|
||||
loading = true;
|
||||
try {
|
||||
await del(`/applications/${id}/delete.json`, { id });
|
||||
return await goto(`/applications`);
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
async function stopApplication() {
|
||||
try {
|
||||
loading = true;
|
||||
await post(`/applications/${id}/stop.json`, {});
|
||||
return window.location.reload();
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav class="nav-side">
|
||||
{#if loading}
|
||||
<Loading fullscreen cover />
|
||||
{:else}
|
||||
{#if application.fqdn && application.gitSource && application.repository && application.destinationDocker && application.buildPack}
|
||||
{#if isRunning}
|
||||
<button
|
||||
on:click={stopApplication}
|
||||
title="Stop application"
|
||||
type="submit"
|
||||
disabled={!$session.isAdmin}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? 'Stop application'
|
||||
: 'You do not have permission to stop the application.'}
|
||||
>
|
||||
<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>
|
||||
</button>
|
||||
<form on:submit|preventDefault={handleDeploySubmit}>
|
||||
<button
|
||||
title="Rebuild application"
|
||||
type="submit"
|
||||
disabled={!$session.isAdmin}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:text-green-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? 'Rebuild application'
|
||||
: 'You do not have permission to rebuild application.'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M16.3 5h.7a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h5l-2.82 -2.82m0 5.64l2.82 -2.82"
|
||||
transform="rotate(-45 12 12)"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{:else}
|
||||
<form on:submit|preventDefault={handleDeploySubmit}>
|
||||
<button
|
||||
title="Build and start application"
|
||||
type="submit"
|
||||
disabled={!$session.isAdmin}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? 'Build and start application'
|
||||
: 'You do not have permission to Build and start application.'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<div class="border border-stone-700 h-8" />
|
||||
<a
|
||||
href="/applications/{id}"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-yellow-500 rounded"
|
||||
class:text-yellow-500={$page.url.pathname === `/applications/${id}`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}`}
|
||||
>
|
||||
<button
|
||||
title="Configurations"
|
||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
||||
data-tooltip="Configurations"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-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="4" y="8" width="4" height="4" />
|
||||
<line x1="6" y1="4" x2="6" y2="8" />
|
||||
<line x1="6" y1="12" x2="6" y2="20" />
|
||||
<rect x="10" y="14" width="4" height="4" />
|
||||
<line x1="12" y1="4" x2="12" y2="14" />
|
||||
<line x1="12" y1="18" x2="12" y2="20" />
|
||||
<rect x="16" y="5" width="4" height="4" />
|
||||
<line x1="18" y1="4" x2="18" y2="5" />
|
||||
<line x1="18" y1="9" x2="18" y2="20" />
|
||||
</svg></button
|
||||
></a
|
||||
>
|
||||
<a
|
||||
href="/applications/{id}/secrets"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-pink-500 rounded"
|
||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||
>
|
||||
<button
|
||||
title="Secrets"
|
||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
||||
data-tooltip="Secrets"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
|
||||
/>
|
||||
<circle cx="12" cy="11" r="1" />
|
||||
<line x1="12" y1="12" x2="12" y2="14.5" />
|
||||
</svg></button
|
||||
></a
|
||||
>
|
||||
<a
|
||||
href="/applications/{id}/storage"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-pink-500 rounded"
|
||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||
>
|
||||
<button
|
||||
title="Persistent Storage"
|
||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
||||
data-tooltip="Persistent Storage"
|
||||
>
|
||||
<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" />
|
||||
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
||||
</svg>
|
||||
</button></a
|
||||
>
|
||||
<a
|
||||
href="/applications/{id}/previews"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-orange-500 rounded"
|
||||
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||
>
|
||||
<button
|
||||
title="Previews"
|
||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
||||
data-tooltip="Previews"
|
||||
>
|
||||
<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="7" cy="18" r="2" />
|
||||
<circle cx="7" cy="6" r="2" />
|
||||
<circle cx="17" cy="12" r="2" />
|
||||
<line x1="7" y1="8" x2="7" y2="16" />
|
||||
<path d="M7 8a4 4 0 0 0 4 4h4" />
|
||||
</svg></button
|
||||
></a
|
||||
>
|
||||
<div class="border border-stone-700 h-8" />
|
||||
<a
|
||||
href="/applications/{id}/logs"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-sky-500 rounded"
|
||||
class:text-sky-500={$page.url.pathname === `/applications/${id}/logs`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`}
|
||||
>
|
||||
<button
|
||||
title="Application Logs"
|
||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500 "
|
||||
data-tooltip="Application Logs"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
||||
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
||||
<line x1="3" y1="6" x2="3" y2="19" />
|
||||
<line x1="12" y1="6" x2="12" y2="19" />
|
||||
<line x1="21" y1="6" x2="21" y2="19" />
|
||||
</svg>
|
||||
</button></a
|
||||
>
|
||||
<a
|
||||
href="/applications/{id}/logs/build"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-red-500 rounded"
|
||||
class:text-red-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
||||
>
|
||||
<button
|
||||
title="Build Logs"
|
||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500 "
|
||||
data-tooltip="Build Logs"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-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="19" cy="13" r="2" />
|
||||
<circle cx="4" cy="17" r="2" />
|
||||
<circle cx="13" cy="17" r="2" />
|
||||
<line x1="13" y1="19" x2="4" y2="19" />
|
||||
<line x1="4" y1="15" x2="13" y2="15" />
|
||||
<path d="M8 12v-5h2a3 3 0 0 1 3 3v5" />
|
||||
<path d="M5 15v-2a1 1 0 0 1 1 -1h7" />
|
||||
<path d="M19 11v-7l-6 7" />
|
||||
</svg>
|
||||
</button></a
|
||||
>
|
||||
<div class="border border-stone-700 h-8" />
|
||||
{/if}
|
||||
|
||||
<button
|
||||
on:click={() => deleteApplication(application.name)}
|
||||
title="Delete application"
|
||||
type="submit"
|
||||
disabled={!$session.isAdmin}
|
||||
class:hover:text-red-500={$session.isAdmin}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
data-tooltip={$session.isAdmin
|
||||
? 'Delete application'
|
||||
: 'You do not have permission to delete this application'}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</button>
|
||||
{/if}
|
||||
</nav>
|
||||
<slot />
|
||||
@@ -1,51 +0,0 @@
|
||||
import { dev } from '$app/env';
|
||||
import { getDomain, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { promises as dns } from 'dns';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
let { fqdn, forceSave } = await event.request.json();
|
||||
fqdn = fqdn.toLowerCase();
|
||||
|
||||
try {
|
||||
const domain = getDomain(fqdn);
|
||||
const found = await db.isDomainConfigured({ id, fqdn });
|
||||
if (found) {
|
||||
throw {
|
||||
message: `Domain ${getDomain(fqdn).replace('www.', '')} is already used.`
|
||||
};
|
||||
}
|
||||
if (!dev && !forceSave) {
|
||||
let ip = [];
|
||||
let localIp = [];
|
||||
dns.setServers(['1.1.1.1', '8.8.8.8']);
|
||||
|
||||
try {
|
||||
localIp = await dns.resolve4(event.url.hostname);
|
||||
} catch (error) {}
|
||||
try {
|
||||
ip = await dns.resolve4(domain);
|
||||
} catch (error) {}
|
||||
|
||||
if (localIp?.length > 0) {
|
||||
if (ip?.length === 0 || !ip.includes(localIp[0])) {
|
||||
throw {
|
||||
message: `DNS not set or propogated for ${domain}.<br><br>Please check your DNS settings.`
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { post } from '$lib/api';
|
||||
import { findBuildPack } from '$lib/components/templates';
|
||||
import { errorNotification } from '$lib/form';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from');
|
||||
|
||||
export let buildPack;
|
||||
export let foundConfig;
|
||||
export let scanning;
|
||||
export let packageManager;
|
||||
|
||||
async function handleSubmit(name) {
|
||||
try {
|
||||
const tempBuildPack = JSON.parse(
|
||||
JSON.stringify(findBuildPack(buildPack.name, packageManager))
|
||||
);
|
||||
delete tempBuildPack.name;
|
||||
delete tempBuildPack.fancyName;
|
||||
delete tempBuildPack.color;
|
||||
delete tempBuildPack.hoverColor;
|
||||
|
||||
if (foundConfig.buildPack !== name) {
|
||||
await post(`/applications/${id}.json`, { ...tempBuildPack });
|
||||
}
|
||||
await post(`/applications/${id}/configuration/buildpack.json`, { buildPack: name });
|
||||
return await goto(from || `/applications/${id}`);
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={() => handleSubmit(buildPack.name)}>
|
||||
<button
|
||||
type="submit"
|
||||
class="box-selection relative flex text-xl font-bold {buildPack.hoverColor} {foundConfig?.name ===
|
||||
buildPack.name && buildPack.color}"
|
||||
><span>{buildPack.fancyName}</span>
|
||||
{#if !scanning && foundConfig?.name === buildPack.name}
|
||||
<span class="absolute bottom-0 pb-2 text-xs">Choose this one...</span>
|
||||
{/if}
|
||||
</button>
|
||||
</form>
|
||||
@@ -1,214 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
import Select from 'svelte-select';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { get, post } from '$lib/api';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { onMount } from 'svelte';
|
||||
import { gitTokens } from '$lib/store';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from');
|
||||
const to = $page.url.searchParams.get('to');
|
||||
|
||||
let htmlUrl = application.gitSource.htmlUrl;
|
||||
let apiUrl = application.gitSource.apiUrl;
|
||||
|
||||
let loading = {
|
||||
repositories: true,
|
||||
branches: false
|
||||
};
|
||||
let repositories = [];
|
||||
let branches = [];
|
||||
|
||||
let selected = {
|
||||
projectId: undefined,
|
||||
repository: undefined,
|
||||
branch: undefined,
|
||||
autodeploy: application.settings.autodeploy || true
|
||||
};
|
||||
let showSave = false;
|
||||
|
||||
async function loadRepositoriesByPage(page = 0) {
|
||||
return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, {
|
||||
Authorization: `token ${$gitTokens.githubToken}`
|
||||
});
|
||||
}
|
||||
|
||||
let reposSelectOptions;
|
||||
let branchSelectOptions;
|
||||
async function loadRepositories() {
|
||||
let page = 1;
|
||||
let reposCount = 0;
|
||||
const loadedRepos = await loadRepositoriesByPage();
|
||||
repositories = repositories.concat(loadedRepos.repositories);
|
||||
reposCount = loadedRepos.total_count;
|
||||
if (reposCount > repositories.length) {
|
||||
while (reposCount > repositories.length) {
|
||||
page = page + 1;
|
||||
const repos = await loadRepositoriesByPage(page);
|
||||
repositories = repositories.concat(repos.repositories);
|
||||
}
|
||||
}
|
||||
loading.repositories = false;
|
||||
reposSelectOptions = repositories.map((repo) => ({
|
||||
value: repo.full_name,
|
||||
label: repo.name
|
||||
}));
|
||||
}
|
||||
async function loadBranches(event) {
|
||||
selected.repository = event.detail.value;
|
||||
loading.branches = true;
|
||||
selected.branch = undefined;
|
||||
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
|
||||
try {
|
||||
branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
|
||||
Authorization: `token ${$gitTokens.githubToken}`
|
||||
});
|
||||
branchSelectOptions = branches.map((branch) => ({
|
||||
value: branch.name,
|
||||
label: branch.name
|
||||
}));
|
||||
return;
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading.branches = false;
|
||||
}
|
||||
}
|
||||
async function isBranchAlreadyUsed(event) {
|
||||
selected.branch = event.detail.value;
|
||||
try {
|
||||
const data = await get(
|
||||
`/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}`
|
||||
);
|
||||
if (data.used) {
|
||||
const sure = confirm(
|
||||
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
|
||||
);
|
||||
if (sure) {
|
||||
selected.autodeploy = false;
|
||||
showSave = true;
|
||||
return true;
|
||||
}
|
||||
showSave = false;
|
||||
return true;
|
||||
}
|
||||
showSave = true;
|
||||
} catch ({ error }) {
|
||||
showSave = false;
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
if (!$gitTokens.githubToken) {
|
||||
const { token } = await get(`/applications/${id}/configuration/githubToken.json`);
|
||||
$gitTokens.githubToken = token;
|
||||
}
|
||||
await loadRepositories();
|
||||
} catch (error) {
|
||||
if (
|
||||
error.error === 'invalid_token' ||
|
||||
error.error_description ===
|
||||
'Token is expired. You can either do re-authorization or token refresh.' ||
|
||||
error.message === '401 Unauthorized'
|
||||
) {
|
||||
if (application.gitSource.gitlabAppId) {
|
||||
let htmlUrl = application.gitSource.htmlUrl;
|
||||
const left = screen.width / 2 - 1020 / 2;
|
||||
const top = screen.height / 2 - 618 / 2;
|
||||
const newWindow = open(
|
||||
`${htmlUrl}/oauth/authorize?client_id=${application.gitSource.gitlabApp.appId}&redirect_uri=${window.location.origin}/webhooks/gitlab&response_type=code&scope=api+email+read_repository&state=${$page.params.id}`,
|
||||
'GitLab',
|
||||
'resizable=1, scrollbars=1, fullscreen=0, height=618, width=1020,top=' +
|
||||
top +
|
||||
', left=' +
|
||||
left +
|
||||
', toolbar=0, menubar=0, status=0'
|
||||
);
|
||||
const timer = setInterval(() => {
|
||||
if (newWindow?.closed) {
|
||||
clearInterval(timer);
|
||||
window.location.reload();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
if (error.message === 'Bad credentials') {
|
||||
const { token } = await get(`/applications/${id}/configuration/githubToken.json`);
|
||||
$gitTokens.githubToken = token;
|
||||
return await loadRepositories();
|
||||
}
|
||||
return errorNotification(error);
|
||||
}
|
||||
});
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await post(`/applications/${id}/configuration/repository.json`, { ...selected });
|
||||
if (to) {
|
||||
return await goto(`${to}?from=${from}`);
|
||||
}
|
||||
return await goto(from || `/applications/${id}/configuration/destination`);
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if repositories.length === 0 && loading.repositories === false}
|
||||
<div class="flex-col text-center">
|
||||
<div class="pb-4">No repositories configured for your Git Application.</div>
|
||||
<a href={`/sources/${application.gitSource.id}`}><button>Configure it now</button></a>
|
||||
</div>
|
||||
{:else}
|
||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
|
||||
<div class="flex-col space-y-3 md:space-y-0 space-x-1">
|
||||
<div class="flex gap-4">
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
placeholder={loading.repositories
|
||||
? 'Loading repositories ...'
|
||||
: 'Please select a repository'}
|
||||
id="repository"
|
||||
on:select={loadBranches}
|
||||
items={reposSelectOptions}
|
||||
isDisabled={loading.repositories}
|
||||
/>
|
||||
</div>
|
||||
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
placeholder={loading.branches
|
||||
? 'Loading branches ...'
|
||||
: !selected.repository
|
||||
? 'Please select a repository first'
|
||||
: 'Please select a branch'}
|
||||
id="repository"
|
||||
on:select={isBranchAlreadyUsed}
|
||||
items={branchSelectOptions}
|
||||
isDisabled={loading.branches || !selected.repository}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-5 flex-col flex justify-center items-center space-y-4">
|
||||
<button
|
||||
class="w-40"
|
||||
type="submit"
|
||||
disabled={!showSave}
|
||||
class:bg-orange-600={showSave}
|
||||
class:hover:bg-orange-500={showSave}>Save</button
|
||||
>
|
||||
<!-- <button class="w-40"
|
||||
><a
|
||||
class="no-underline"
|
||||
href="{apiUrl}/apps/{application.gitSource.githubApp.name}/installations/new"
|
||||
>Modify Repositories</a
|
||||
></button
|
||||
> -->
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
@@ -1,340 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
export let appId;
|
||||
import { page, session } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { dev } from '$app/env';
|
||||
import cuid from 'cuid';
|
||||
import { goto } from '$app/navigation';
|
||||
import { del, get, post, put } from '$lib/api';
|
||||
import { gitTokens } from '$lib/store';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from');
|
||||
|
||||
const updateDeployKeyIdUrl = `/applications/${id}/configuration/deploykey.json`;
|
||||
|
||||
let loading = {
|
||||
base: true,
|
||||
projects: false,
|
||||
branches: false,
|
||||
save: false
|
||||
};
|
||||
|
||||
let htmlUrl = application.gitSource.htmlUrl;
|
||||
let apiUrl = application.gitSource.apiUrl;
|
||||
|
||||
let username = null;
|
||||
let groups = [];
|
||||
let projects = [];
|
||||
let branches = [];
|
||||
let showSave = false;
|
||||
let autodeploy = application.settings.autodeploy || true;
|
||||
|
||||
let selected = {
|
||||
group: undefined,
|
||||
project: undefined,
|
||||
branch: undefined
|
||||
};
|
||||
onMount(async () => {
|
||||
if (!$gitTokens.gitlabToken) {
|
||||
getGitlabToken();
|
||||
} else {
|
||||
loading.base = true;
|
||||
try {
|
||||
const user = await get(`${apiUrl}/v4/user`, {
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
});
|
||||
username = user.username;
|
||||
} catch (error) {
|
||||
return getGitlabToken();
|
||||
}
|
||||
try {
|
||||
groups = await get(`${apiUrl}/v4/groups?per_page=5000`, {
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
});
|
||||
} catch (error) {
|
||||
errorNotification(error);
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
loading.base = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function getGitlabToken() {
|
||||
const left = screen.width / 2 - 1020 / 2;
|
||||
const top = screen.height / 2 - 618 / 2;
|
||||
const newWindow = open(
|
||||
`${htmlUrl}/oauth/authorize?client_id=${application.gitSource.gitlabApp.appId}&redirect_uri=${window.location.origin}/webhooks/gitlab&response_type=code&scope=api+email+read_repository&state=${$page.params.id}`,
|
||||
'GitLab',
|
||||
'resizable=1, scrollbars=1, fullscreen=0, height=618, width=1020,top=' +
|
||||
top +
|
||||
', left=' +
|
||||
left +
|
||||
', toolbar=0, menubar=0, status=0'
|
||||
);
|
||||
const timer = setInterval(() => {
|
||||
if (newWindow?.closed) {
|
||||
clearInterval(timer);
|
||||
window.location.reload();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async function loadProjects() {
|
||||
loading.projects = true;
|
||||
if (username === selected.group.name) {
|
||||
try {
|
||||
projects = await get(
|
||||
`${apiUrl}/v4/users/${selected.group.name}/projects?min_access_level=40&page=1&per_page=25&archived=false`,
|
||||
{
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
errorNotification(error);
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
loading.projects = false;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
projects = await get(
|
||||
`${apiUrl}/v4/groups/${selected.group.id}/projects?page=1&per_page=25&archived=false`,
|
||||
{
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
errorNotification(error);
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
loading.projects = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBranches() {
|
||||
loading.branches = true;
|
||||
try {
|
||||
branches = await get(
|
||||
`${apiUrl}/v4/projects/${selected.project.id}/repository/branches?per_page=100&page=1`,
|
||||
{
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
errorNotification(error);
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
loading.branches = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function isBranchAlreadyUsed() {
|
||||
try {
|
||||
const data = await get(
|
||||
`/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
|
||||
);
|
||||
if (data.used) {
|
||||
const sure = confirm(
|
||||
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
|
||||
);
|
||||
if (sure) {
|
||||
autodeploy = false;
|
||||
showSave = true;
|
||||
return true;
|
||||
}
|
||||
showSave = false;
|
||||
return true;
|
||||
}
|
||||
showSave = true;
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
async function checkSSHKey(sshkeyUrl) {
|
||||
try {
|
||||
return await post(sshkeyUrl, {});
|
||||
} catch (error) {
|
||||
errorNotification(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
async function setWebhook(url, webhookToken) {
|
||||
const host = dev
|
||||
? 'https://webhook.site/0e5beb2c-4e9b-40e2-a89e-32295e570c21'
|
||||
: `${window.location.origin}/webhooks/gitlab/events`;
|
||||
try {
|
||||
await post(
|
||||
url,
|
||||
{
|
||||
id: selected.project.id,
|
||||
url: host,
|
||||
token: webhookToken,
|
||||
push_events: true,
|
||||
enable_ssl_verification: true,
|
||||
merge_requests_events: true
|
||||
},
|
||||
{
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
errorNotification(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async function save() {
|
||||
loading.save = true;
|
||||
let privateSshKey = application.gitSource.gitlabApp.privateSshKey;
|
||||
let publicSshKey = application.gitSource.gitlabApp.publicSshKey;
|
||||
|
||||
const deployKeyUrl = `${apiUrl}/v4/projects/${selected.project.id}/deploy_keys`;
|
||||
const sshkeyUrl = `/applications/${id}/configuration/sshkey.json`;
|
||||
const webhookUrl = `${apiUrl}/v4/projects/${selected.project.id}/hooks`;
|
||||
const webhookToken = cuid();
|
||||
|
||||
try {
|
||||
if (!privateSshKey || !publicSshKey) {
|
||||
const { publicKey } = await checkSSHKey(sshkeyUrl);
|
||||
publicSshKey = publicKey;
|
||||
}
|
||||
const deployKeys = await get(deployKeyUrl, {
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
});
|
||||
const deployKeyFound = deployKeys.filter((dk) => dk.title === `${appId}-coolify-deploy-key`);
|
||||
if (deployKeyFound.length > 0) {
|
||||
for (const deployKey of deployKeyFound) {
|
||||
await del(
|
||||
`${deployKeyUrl}/${deployKey.id}`,
|
||||
{},
|
||||
{
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
const { id } = await post(
|
||||
deployKeyUrl,
|
||||
{
|
||||
title: `${appId}-coolify-deploy-key`,
|
||||
key: publicSshKey,
|
||||
can_push: false
|
||||
},
|
||||
{
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
await post(updateDeployKeyIdUrl, { deployKeyId: id });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
try {
|
||||
await setWebhook(webhookUrl, webhookToken);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
if (!dev) throw new Error(err);
|
||||
}
|
||||
|
||||
const url = `/applications/${id}/configuration/repository.json`;
|
||||
try {
|
||||
const repository = selected.project.path_with_namespace;
|
||||
await post(url, {
|
||||
repository,
|
||||
branch: selected.branch.name,
|
||||
projectId: selected.project.id,
|
||||
autodeploy,
|
||||
webhookToken
|
||||
});
|
||||
return await goto(from || `/applications/${id}/configuration/buildpack`);
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await post(`/applications/{id}/configuration/repository.json`, { ...selected });
|
||||
return await goto(from || `/applications/${id}/configuration/destination`);
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form on:submit={handleSubmit}>
|
||||
<div class="flex flex-col space-y-2 px-4 xl:flex-row xl:space-y-0 xl:space-x-2 ">
|
||||
{#if loading.base}
|
||||
<select name="group" disabled class="w-96">
|
||||
<option selected value="">Loading groups...</option>
|
||||
</select>
|
||||
{:else}
|
||||
<select name="group" class="w-96" bind:value={selected.group} on:change={loadProjects}>
|
||||
<option value="" disabled selected>Please select a group</option>
|
||||
{#each groups as group}
|
||||
<option value={group}>{group.full_name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
{#if loading.projects}
|
||||
<select name="project" disabled class="w-96">
|
||||
<option selected value="">Loading projects...</option>
|
||||
</select>
|
||||
{:else if !loading.projects && projects.length > 0}
|
||||
<select
|
||||
name="project"
|
||||
class="w-96"
|
||||
bind:value={selected.project}
|
||||
on:change={loadBranches}
|
||||
disabled={!selected.group}
|
||||
>
|
||||
<option value="" disabled selected>Please select a project</option>
|
||||
{#each projects as project}
|
||||
<option value={project}>{project.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else}
|
||||
<select name="project" disabled class="w-96">
|
||||
<option disabled selected value="">No projects found</option>
|
||||
</select>
|
||||
{/if}
|
||||
|
||||
{#if loading.branches}
|
||||
<select name="branch" disabled class="w-96">
|
||||
<option selected value="">Loading branches...</option>
|
||||
</select>
|
||||
{:else if !loading.branches && branches.length > 0}
|
||||
<select
|
||||
name="branch"
|
||||
class="w-96"
|
||||
bind:value={selected.branch}
|
||||
on:change={isBranchAlreadyUsed}
|
||||
disabled={!selected.project}
|
||||
>
|
||||
<option value="" disabled selected>Please select a branch</option>
|
||||
{#each branches as branch}
|
||||
<option value={branch}>{branch.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else}
|
||||
<select name="project" disabled class="w-96">
|
||||
<option disabled selected value="">No branches found</option>
|
||||
</select>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center space-y-4 pt-5">
|
||||
<button
|
||||
on:click|preventDefault={save}
|
||||
class="w-40"
|
||||
type="submit"
|
||||
disabled={!showSave || loading.save}
|
||||
class:bg-orange-600={showSave && !loading.save}
|
||||
class:hover:bg-orange-500={showSave && !loading.save}
|
||||
>{loading.save ? 'Saving...' : 'Save'}</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,41 +0,0 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
try {
|
||||
const application = await db.getApplication({ id, teamId });
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
type: application.gitSource.type,
|
||||
projectId: application.projectId,
|
||||
repository: application.repository,
|
||||
branch: application.branch,
|
||||
apiUrl: application.gitSource.apiUrl
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { buildPack } = await event.request.json();
|
||||
|
||||
try {
|
||||
await db.configureBuildPack({ id, buildPack });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,222 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
||||
const { application } = stuff;
|
||||
if (application?.buildPack && !url.searchParams.get('from')) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: `/applications/${params.id}`
|
||||
};
|
||||
}
|
||||
const endpoint = `/applications/${params.id}/configuration/buildpack.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
...(await res.json()),
|
||||
application
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${url}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/components/templates';
|
||||
import BuildPack from './_BuildPack.svelte';
|
||||
import { page, session } from '$app/stores';
|
||||
import { get } from '$lib/api';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { gitTokens } from '$lib/store';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
let scanning = true;
|
||||
let foundConfig = null;
|
||||
let packageManager = 'npm';
|
||||
|
||||
export let apiUrl;
|
||||
export let projectId;
|
||||
export let repository;
|
||||
export let branch;
|
||||
export let type;
|
||||
export let application;
|
||||
|
||||
function checkPackageJSONContents({ key, json }) {
|
||||
return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key);
|
||||
}
|
||||
function checkTemplates({ json, packageManager }) {
|
||||
for (const [key, value] of Object.entries(scanningTemplates)) {
|
||||
if (checkPackageJSONContents({ key, json })) {
|
||||
foundConfig = findBuildPack(value.buildPack, packageManager);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
async function scanRepository() {
|
||||
try {
|
||||
if (type === 'gitlab') {
|
||||
const files = await get(`${apiUrl}/v4/projects/${projectId}/repository/tree`, {
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
});
|
||||
const packageJson = files.find(
|
||||
(file) => file.name === 'package.json' && file.type === 'blob'
|
||||
);
|
||||
const yarnLock = files.find((file) => file.name === 'yarn.lock' && file.type === 'blob');
|
||||
const pnpmLock = files.find(
|
||||
(file) => file.name === 'pnpm-lock.yaml' && file.type === 'blob'
|
||||
);
|
||||
const dockerfile = files.find((file) => file.name === 'Dockerfile' && file.type === 'blob');
|
||||
const cargoToml = files.find((file) => file.name === 'Cargo.toml' && file.type === 'blob');
|
||||
const requirementsTxt = files.find(
|
||||
(file) => file.name === 'requirements.txt' && file.type === 'blob'
|
||||
);
|
||||
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'blob');
|
||||
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'blob');
|
||||
|
||||
if (yarnLock) packageManager = 'yarn';
|
||||
if (pnpmLock) packageManager = 'pnpm';
|
||||
|
||||
if (dockerfile) {
|
||||
foundConfig = findBuildPack('docker', packageManager);
|
||||
} else if (packageJson) {
|
||||
const path = packageJson.path;
|
||||
const data = await get(
|
||||
`${apiUrl}/v4/projects/${projectId}/repository/files/${path}/raw?ref=${branch}`,
|
||||
{
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
const json = JSON.parse(data) || {};
|
||||
checkTemplates({ json, packageManager });
|
||||
} else if (cargoToml) {
|
||||
foundConfig = findBuildPack('rust');
|
||||
} else if (requirementsTxt) {
|
||||
foundConfig = findBuildPack('python');
|
||||
} else if (indexHtml) {
|
||||
foundConfig = findBuildPack('static', packageManager);
|
||||
} else if (indexPHP) {
|
||||
foundConfig = findBuildPack('php');
|
||||
} else {
|
||||
foundConfig = findBuildPack('node', packageManager);
|
||||
}
|
||||
} else if (type === 'github') {
|
||||
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
|
||||
Authorization: `Bearer ${$gitTokens.githubToken}`,
|
||||
Accept: 'application/vnd.github.v2.json'
|
||||
});
|
||||
const packageJson = files.find(
|
||||
(file) => file.name === 'package.json' && file.type === 'file'
|
||||
);
|
||||
const yarnLock = files.find((file) => file.name === 'yarn.lock' && file.type === 'file');
|
||||
const pnpmLock = files.find(
|
||||
(file) => file.name === 'pnpm-lock.yaml' && file.type === 'file'
|
||||
);
|
||||
const dockerfile = files.find((file) => file.name === 'Dockerfile' && file.type === 'file');
|
||||
const cargoToml = files.find((file) => file.name === 'Cargo.toml' && file.type === 'file');
|
||||
const requirementsTxt = files.find(
|
||||
(file) => file.name === 'requirements.txt' && file.type === 'file'
|
||||
);
|
||||
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'file');
|
||||
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'file');
|
||||
|
||||
if (yarnLock) packageManager = 'yarn';
|
||||
if (pnpmLock) packageManager = 'pnpm';
|
||||
|
||||
if (dockerfile) {
|
||||
foundConfig = findBuildPack('docker', packageManager);
|
||||
} else if (packageJson) {
|
||||
const data = await get(`${packageJson.git_url}`, {
|
||||
Authorization: `Bearer ${$gitTokens.githubToken}`,
|
||||
Accept: 'application/vnd.github.v2.raw'
|
||||
});
|
||||
const json = JSON.parse(data) || {};
|
||||
checkTemplates({ json, packageManager });
|
||||
} else if (cargoToml) {
|
||||
foundConfig = findBuildPack('rust');
|
||||
} else if (requirementsTxt) {
|
||||
foundConfig = findBuildPack('python');
|
||||
} else if (indexHtml) {
|
||||
foundConfig = findBuildPack('static', packageManager);
|
||||
} else if (indexPHP) {
|
||||
foundConfig = findBuildPack('php');
|
||||
} else {
|
||||
foundConfig = findBuildPack('node', packageManager);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
scanning = true;
|
||||
if (
|
||||
error.error === 'invalid_token' ||
|
||||
error.error_description ===
|
||||
'Token is expired. You can either do re-authorization or token refresh.' ||
|
||||
error.message === '401 Unauthorized'
|
||||
) {
|
||||
if (application.gitSource.gitlabAppId) {
|
||||
let htmlUrl = application.gitSource.htmlUrl;
|
||||
const left = screen.width / 2 - 1020 / 2;
|
||||
const top = screen.height / 2 - 618 / 2;
|
||||
const newWindow = open(
|
||||
`${htmlUrl}/oauth/authorize?client_id=${application.gitSource.gitlabApp.appId}&redirect_uri=${window.location.origin}/webhooks/gitlab&response_type=code&scope=api+email+read_repository&state=${$page.params.id}`,
|
||||
'GitLab',
|
||||
'resizable=1, scrollbars=1, fullscreen=0, height=618, width=1020,top=' +
|
||||
top +
|
||||
', left=' +
|
||||
left +
|
||||
', toolbar=0, menubar=0, status=0'
|
||||
);
|
||||
const timer = setInterval(() => {
|
||||
if (newWindow?.closed) {
|
||||
clearInterval(timer);
|
||||
window.location.reload();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
if (error.message === 'Bad credentials') {
|
||||
const { token } = await get(`/applications/${id}/configuration/githubToken.json`);
|
||||
$gitTokens.githubToken = token;
|
||||
browser && window.location.reload();
|
||||
}
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
if (!foundConfig) foundConfig = findBuildPack('node', packageManager);
|
||||
scanning = false;
|
||||
}
|
||||
}
|
||||
onMount(async () => {
|
||||
await scanRepository();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">Configure Build Pack</div>
|
||||
</div>
|
||||
|
||||
{#if scanning}
|
||||
<div class="flex justify-center space-x-1 p-6 font-bold">
|
||||
<div class="text-xl tracking-tight">Scanning repository to suggest a build pack for you...</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#if packageManager === 'yarn' || packageManager === 'pnpm'}
|
||||
<div class="flex justify-center p-6">
|
||||
Found lock file for <span class="font-bold text-orange-500 pl-1">{packageManager}</span>.
|
||||
Using it for predefined commands commands.
|
||||
</div>
|
||||
{/if}
|
||||
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">
|
||||
{#each buildPacks as buildPack}
|
||||
<div class="p-2">
|
||||
<BuildPack {buildPack} {scanning} {packageManager} bind:foundConfig />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1,17 +0,0 @@
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { id } = event.params;
|
||||
let { deployKeyId } = await event.request.json();
|
||||
|
||||
deployKeyId = Number(deployKeyId);
|
||||
|
||||
try {
|
||||
await db.updateDeployKey({ id, deployKeyId });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { destinationId } = await event.request.json();
|
||||
|
||||
try {
|
||||
await db.configureDestinationForApplication({ id, destinationId });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,91 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
||||
const { application } = stuff;
|
||||
if (application?.destinationDockerId && !url.searchParams.get('from')) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: `/applications/${params.id}`
|
||||
};
|
||||
}
|
||||
const endpoint = `/destinations.json`;
|
||||
const res = await fetch(endpoint);
|
||||
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${url}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type Prisma from '@prisma/client';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { goto } from '$app/navigation';
|
||||
import { post } from '$lib/api';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from');
|
||||
|
||||
export let destinations: Prisma.DestinationDocker[];
|
||||
|
||||
async function handleSubmit(destinationId) {
|
||||
try {
|
||||
await post(`/applications/${id}/configuration/destination.json`, { destinationId });
|
||||
return await goto(from || `/applications/${id}/configuration/buildpack`);
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">Configure Destination</div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
{#if !destinations || destinations.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="pb-2">No configurable Destination found</div>
|
||||
<div class="flex justify-center">
|
||||
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||
/></svg
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-wrap justify-center">
|
||||
{#each destinations 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>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,45 +0,0 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
try {
|
||||
const application = await db.getApplication({ id, teamId });
|
||||
const payload = {
|
||||
iat: Math.round(new Date().getTime() / 1000),
|
||||
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||
iss: application.gitSource.githubApp.appId
|
||||
};
|
||||
const githubToken = jsonwebtoken.sign(payload, application.gitSource.githubApp.privateKey, {
|
||||
algorithm: 'RS256'
|
||||
});
|
||||
const response = await fetch(
|
||||
`${application.gitSource.apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${githubToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return {
|
||||
status: 201,
|
||||
body: { token: data.token },
|
||||
headers: {
|
||||
'Set-Cookie': `githubToken=${data.token}; Path=/; HttpOnly; Max-Age=15778800;`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
const repository = event.url.searchParams.get('repository')?.toLocaleLowerCase() || undefined;
|
||||
const branch = event.url.searchParams.get('branch')?.toLocaleLowerCase() || undefined;
|
||||
|
||||
try {
|
||||
const found = await db.isBranchAlreadyUsed({ repository, branch, id });
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
used: found ? true : false
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
let { repository, branch, projectId, webhookToken, autodeploy } = await event.request.json();
|
||||
|
||||
repository = repository.toLowerCase();
|
||||
branch = branch.toLowerCase();
|
||||
projectId = Number(projectId);
|
||||
|
||||
try {
|
||||
await db.configureGitRepository({
|
||||
id,
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
webhookToken,
|
||||
autodeploy
|
||||
});
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ params, url, stuff }) => {
|
||||
const { application, appId } = stuff;
|
||||
if (application?.branch && application?.repository && !url.searchParams.get('from')) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: `/applications/${params.id}`
|
||||
};
|
||||
}
|
||||
return {
|
||||
props: {
|
||||
application,
|
||||
appId
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
export let appId;
|
||||
|
||||
import GithubRepositories from './_GithubRepositories.svelte';
|
||||
import GitlabRepositories from './_GitlabRepositories.svelte';
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">Select a Repository / Project</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center">
|
||||
{#if application.gitSource.type === 'github'}
|
||||
<GithubRepositories {application} />
|
||||
{:else if application.gitSource.type === 'gitlab'}
|
||||
<GitlabRepositories {application} {appId} />
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,18 +0,0 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { gitSourceId } = await event.request.json();
|
||||
try {
|
||||
await db.configureGitsource({ id, gitSourceId });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,109 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
||||
const { application } = stuff;
|
||||
if (application?.gitSourceId && !url.searchParams.get('from')) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: `/applications/${params.id}`
|
||||
};
|
||||
}
|
||||
const endpoint = `/sources.json`;
|
||||
const res = await fetch(endpoint);
|
||||
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${url}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type Prisma from '@prisma/client';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { goto } from '$app/navigation';
|
||||
import { post } from '$lib/api';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from');
|
||||
|
||||
export let sources: Prisma.GitSource[] & {
|
||||
gitlabApp: Prisma.GitlabApp;
|
||||
githubApp: Prisma.GithubApp;
|
||||
};
|
||||
const filteredSources = sources.filter(
|
||||
(source) =>
|
||||
(source.type === 'github' && source.githubAppId && source.githubApp.installationId) ||
|
||||
(source.type === 'gitlab' && source.gitlabAppId)
|
||||
);
|
||||
async function handleSubmit(gitSourceId) {
|
||||
try {
|
||||
await post(`/applications/${id}/configuration/source.json`, { gitSourceId });
|
||||
return await goto(from || `/applications/${id}/configuration/repository`);
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">Select a Git Source</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
{#if !filteredSources || filteredSources.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="pb-2">No configurable Git Source found</div>
|
||||
<div class="flex justify-center">
|
||||
<a href="/new/source" sveltekit:prefetch class="add-icon bg-orange-600 hover:bg-orange-500">
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||
/></svg
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-wrap justify-center">
|
||||
{#each filteredSources as source}
|
||||
<div class="p-2">
|
||||
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
||||
<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"
|
||||
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
||||
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
||||
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
||||
>
|
||||
<div class="font-bold text-xl text-center truncate">{source.name}</div>
|
||||
{#if source.gitlabApp && !source.gitlabAppId}
|
||||
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
||||
Configuration missing
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { id } = event.params;
|
||||
try {
|
||||
return await db.getSshKey({ id });
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { id } = event.params;
|
||||
try {
|
||||
return await db.generateSshKey({ id });
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const del: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
try {
|
||||
await db.removeApplication({ id, teamId });
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
import * as db from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import cuid from 'cuid';
|
||||
import crypto from 'crypto';
|
||||
import { buildQueue } from '$lib/queues';
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { pullmergeRequestId = null, branch } = await event.request.json();
|
||||
try {
|
||||
const buildId = cuid();
|
||||
const applicationFound = await db.getApplication({ id, teamId });
|
||||
if (!applicationFound.configHash) {
|
||||
const configHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify({
|
||||
buildPack: applicationFound.buildPack,
|
||||
port: applicationFound.port,
|
||||
installCommand: applicationFound.installCommand,
|
||||
buildCommand: applicationFound.buildCommand,
|
||||
startCommand: applicationFound.startCommand
|
||||
})
|
||||
)
|
||||
.digest('hex');
|
||||
await db.prisma.application.update({ where: { id }, data: { configHash } });
|
||||
}
|
||||
await db.prisma.application.update({ where: { id }, data: { updatedAt: new Date() } });
|
||||
await db.prisma.build.create({
|
||||
data: {
|
||||
id: buildId,
|
||||
applicationId: id,
|
||||
destinationDockerId: applicationFound.destinationDocker.id,
|
||||
gitSourceId: applicationFound.gitSource.id,
|
||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
||||
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
|
||||
status: 'queued',
|
||||
type: 'manual'
|
||||
}
|
||||
});
|
||||
if (pullmergeRequestId) {
|
||||
await buildQueue.add(buildId, {
|
||||
build_id: buildId,
|
||||
type: 'manual',
|
||||
...applicationFound,
|
||||
sourceBranch: branch,
|
||||
pullmergeRequestId
|
||||
});
|
||||
} else {
|
||||
await buildQueue.add(buildId, { build_id: buildId, type: 'manual', ...applicationFound });
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
buildId
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import { get as getRequest } from '$lib/api';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
const appId = process.env['COOLIFY_APP_ID'];
|
||||
let isRunning = false;
|
||||
let githubToken = event.locals.cookies?.githubToken || null;
|
||||
let gitlabToken = event.locals.cookies?.gitlabToken || null;
|
||||
try {
|
||||
const application = await db.getApplication({ id, teamId });
|
||||
if (application.destinationDockerId) {
|
||||
isRunning = await checkContainer(application.destinationDocker.engine, id);
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
isRunning,
|
||||
application,
|
||||
appId,
|
||||
githubToken,
|
||||
gitlabToken
|
||||
},
|
||||
headers: {}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
let {
|
||||
name,
|
||||
buildPack,
|
||||
fqdn,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory
|
||||
} = await event.request.json();
|
||||
|
||||
if (port) port = Number(port);
|
||||
|
||||
try {
|
||||
await db.configureApplication({
|
||||
id,
|
||||
buildPack,
|
||||
name,
|
||||
fqdn,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory
|
||||
});
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,436 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, stuff }) => {
|
||||
if (stuff?.application?.id) {
|
||||
return {
|
||||
props: {
|
||||
application: stuff.application,
|
||||
isRunning: stuff.isRunning
|
||||
}
|
||||
};
|
||||
}
|
||||
const endpoint = `/applications/${params.id}.json`;
|
||||
const res = await fetch(endpoint);
|
||||
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${endpoint}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let application: Prisma.Application & {
|
||||
settings: Prisma.ApplicationSettings;
|
||||
gitlabApp: Prisma.GitlabApp;
|
||||
gitSource: Prisma.GitSource;
|
||||
destinationDocker: Prisma.DestinationDocker;
|
||||
};
|
||||
export let isRunning;
|
||||
import { page, session } from '$app/stores';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import Setting from '$lib/components/Setting.svelte';
|
||||
import type Prisma from '@prisma/client';
|
||||
import { notNodeDeployments, staticDeployments } from '$lib/components/common';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { post } from '$lib/api';
|
||||
import cuid from 'cuid';
|
||||
import { browser } from '$app/env';
|
||||
const { id } = $page.params;
|
||||
|
||||
let domainEl: HTMLInputElement;
|
||||
|
||||
let loading = false;
|
||||
let forceSave = false;
|
||||
let debug = application.settings.debug;
|
||||
let previews = application.settings.previews;
|
||||
let dualCerts = application.settings.dualCerts;
|
||||
let autodeploy = application.settings.autodeploy;
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
||||
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
domainEl.focus();
|
||||
});
|
||||
|
||||
async function changeSettings(name) {
|
||||
if (name === 'debug') {
|
||||
debug = !debug;
|
||||
}
|
||||
if (name === 'previews') {
|
||||
previews = !previews;
|
||||
}
|
||||
if (name === 'dualCerts') {
|
||||
dualCerts = !dualCerts;
|
||||
}
|
||||
if (name === 'autodeploy') {
|
||||
autodeploy = !autodeploy;
|
||||
}
|
||||
try {
|
||||
await post(`/applications/${id}/settings.json`, {
|
||||
previews,
|
||||
debug,
|
||||
dualCerts,
|
||||
autodeploy,
|
||||
branch: application.branch,
|
||||
projectId: application.projectId
|
||||
});
|
||||
return toast.push('Settings saved.');
|
||||
} catch ({ error }) {
|
||||
if (name === 'debug') {
|
||||
debug = !debug;
|
||||
}
|
||||
if (name === 'previews') {
|
||||
previews = !previews;
|
||||
}
|
||||
if (name === 'dualCerts') {
|
||||
dualCerts = !dualCerts;
|
||||
}
|
||||
if (name === 'autodeploy') {
|
||||
autodeploy = !autodeploy;
|
||||
}
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
try {
|
||||
await post(`/applications/${id}/check.json`, { fqdn: application.fqdn, forceSave });
|
||||
await post(`/applications/${id}.json`, { ...application });
|
||||
return window.location.reload();
|
||||
} catch ({ error }) {
|
||||
if (error.startsWith('DNS not set')) {
|
||||
forceSave = true;
|
||||
}
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||
{application.name}
|
||||
</div>
|
||||
{#if application.fqdn}
|
||||
<a
|
||||
href={application.fqdn}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||
<line x1="10" y1="14" x2="20" y2="4" />
|
||||
<polyline points="15 4 20 4 20 9" />
|
||||
</svg></a
|
||||
>
|
||||
{/if}
|
||||
<a
|
||||
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
|
||||
target="_blank"
|
||||
class="w-10"
|
||||
>
|
||||
{#if application.gitSource?.type === 'gitlab'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<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 application.gitSource?.type === 'github'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<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}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-4xl px-6">
|
||||
<!-- svelte-ignore missing-declaration -->
|
||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||
<div class="flex space-x-1 pb-5 font-bold">
|
||||
<div class="title">General</div>
|
||||
{#if $session.isAdmin}
|
||||
<button
|
||||
type="submit"
|
||||
class:bg-green-600={!loading}
|
||||
class:bg-orange-600={forceSave}
|
||||
class:hover:bg-green-500={!loading}
|
||||
class:hover:bg-orange-400={forceSave}
|
||||
disabled={loading}
|
||||
>{loading ? 'Saving...' : forceSave ? 'Are you sure to continue?' : 'Save'}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2 px-10">
|
||||
<div class="mt-2 grid grid-cols-2 items-center">
|
||||
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="name"
|
||||
id="name"
|
||||
bind:value={application.name}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="gitSource" class="text-base font-bold text-stone-100">Git Source</label>
|
||||
<a
|
||||
href={$session.isAdmin
|
||||
? `/applications/${id}/configuration/source?from=/applications/${id}`
|
||||
: ''}
|
||||
class="no-underline"
|
||||
><input
|
||||
value={application.gitSource.name}
|
||||
id="gitSource"
|
||||
disabled
|
||||
class="cursor-pointer hover:bg-coolgray-500"
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="repository" class="text-base font-bold text-stone-100">Git Repository</label>
|
||||
<a
|
||||
href={$session.isAdmin
|
||||
? `/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`
|
||||
: ''}
|
||||
class="no-underline"
|
||||
><input
|
||||
value="{application.repository}/{application.branch}"
|
||||
id="repository"
|
||||
disabled
|
||||
class="cursor-pointer hover:bg-coolgray-500"
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="buildPack" class="text-base font-bold text-stone-100">Build Pack</label>
|
||||
<a
|
||||
href={$session.isAdmin
|
||||
? `/applications/${id}/configuration/buildpack?from=/applications/${id}`
|
||||
: ''}
|
||||
class="no-underline "
|
||||
>
|
||||
<input
|
||||
value={application.buildPack}
|
||||
id="buildPack"
|
||||
disabled
|
||||
class="cursor-pointer hover:bg-coolgray-500"
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center pb-8">
|
||||
<label for="destination" class="text-base font-bold text-stone-100">Destination</label>
|
||||
<div class="no-underline">
|
||||
<input
|
||||
value={application.destinationDocker.name}
|
||||
id="destination"
|
||||
disabled
|
||||
class="bg-transparent "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">Application</div>
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2 px-10">
|
||||
<div class="grid grid-cols-2">
|
||||
<div class="flex-col">
|
||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</label>
|
||||
{#if browser && window.location.hostname === 'demo.coolify.io'}
|
||||
<Explainer
|
||||
text="<span class='text-white font-bold'>You can use the predefined random domain name or enter your own domain name.</span>"
|
||||
/>
|
||||
{/if}
|
||||
<Explainer
|
||||
text="If you specify <span class='text-green-500 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-500 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white font-bold'>You must set your DNS to point to the server IP in advance.</span>"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
readonly={!$session.isAdmin || isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
bind:this={domainEl}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
bind:value={application.fqdn}
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
placeholder="eg: https://coollabs.io"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center pb-8">
|
||||
<Setting
|
||||
dataTooltip="Must be stopped to modify."
|
||||
disabled={isRunning}
|
||||
isCenter={false}
|
||||
bind:setting={dualCerts}
|
||||
title="Generate SSL for www and non-www?"
|
||||
description="It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-green-500'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both."
|
||||
on:click={() => !isRunning && changeSettings('dualCerts')}
|
||||
/>
|
||||
</div>
|
||||
{#if !staticDeployments.includes(application.buildPack)}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="port" class="text-base font-bold text-stone-100">Port</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="port"
|
||||
id="port"
|
||||
bind:value={application.port}
|
||||
placeholder="default: 3000"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="installCommand" class="text-base font-bold text-stone-100"
|
||||
>Install Command</label
|
||||
>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="installCommand"
|
||||
id="installCommand"
|
||||
bind:value={application.installCommand}
|
||||
placeholder="default: yarn install"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="buildCommand" class="text-base font-bold text-stone-100">Build Command</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="buildCommand"
|
||||
id="buildCommand"
|
||||
bind:value={application.buildCommand}
|
||||
placeholder="default: yarn build"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="startCommand" class="text-base font-bold text-stone-100">Start Command</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="startCommand"
|
||||
id="startCommand"
|
||||
bind:value={application.startCommand}
|
||||
placeholder="default: yarn start"
|
||||
/>
|
||||
</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"
|
||||
>Base Directory</label
|
||||
>
|
||||
<Explainer
|
||||
text="Directory to use as the base for all commands.<br>Could be useful with <span class='text-green-500 font-bold'>monorepos</span>."
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="baseDirectory"
|
||||
id="baseDirectory"
|
||||
bind:value={application.baseDirectory}
|
||||
placeholder="default: /"
|
||||
/>
|
||||
</div>
|
||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<div class="flex-col">
|
||||
<label for="publishDirectory" class="pt-2 text-base font-bold text-stone-100"
|
||||
>Publish Directory</label
|
||||
>
|
||||
<Explainer
|
||||
text="Directory containing all the assets for deployment. <br> For example: <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> or <span class='text-green-500 font-bold'>public</span>."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="publishDirectory"
|
||||
id="publishDirectory"
|
||||
bind:value={application.publishDirectory}
|
||||
placeholder=" default: /"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
<div class="flex space-x-1 pb-5 font-bold">
|
||||
<div class="title">Features</div>
|
||||
</div>
|
||||
<div class="px-10 pb-10">
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
isCenter={false}
|
||||
bind:setting={autodeploy}
|
||||
on:click={() => changeSettings('autodeploy')}
|
||||
title="Enable Automatic Deployment"
|
||||
description="Enable automatic deployment through webhooks."
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
isCenter={false}
|
||||
bind:setting={previews}
|
||||
on:click={() => changeSettings('previews')}
|
||||
title="Enable MR/PR Previews"
|
||||
description="Enable preview deployments from pull or merge requests."
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
isCenter={false}
|
||||
bind:setting={debug}
|
||||
on:click={() => changeSettings('debug')}
|
||||
title="Debug Logs"
|
||||
description="Enable debug logs during build phase.<br><span class='text-red-500 font-bold'>Sensitive information</span> could be visible and saved in logs."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,41 +0,0 @@
|
||||
<div class="lds-ripple absolute left-0">
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.lds-ripple {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
left: -19px;
|
||||
top: -8px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.lds-ripple div {
|
||||
position: absolute;
|
||||
border: 4px solid #fff;
|
||||
opacity: 1;
|
||||
border-radius: 50%;
|
||||
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
}
|
||||
.lds-ripple div:nth-child(2) {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
@keyframes lds-ripple {
|
||||
0% {
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,124 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let buildId;
|
||||
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
import LoadingLogs from '../_Loading.svelte';
|
||||
import { get } from '$lib/api';
|
||||
import { errorNotification } from '$lib/form';
|
||||
|
||||
let logs = [];
|
||||
let loading = true;
|
||||
let currentStatus;
|
||||
let streamInterval;
|
||||
let followingBuild;
|
||||
let followingInterval;
|
||||
let logsEl;
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
const cleanAnsiCodes = (str: string) => str.replace(/\x1B\[(\d+)m/g, '');
|
||||
|
||||
function followBuild() {
|
||||
followingBuild = !followingBuild;
|
||||
if (followingBuild) {
|
||||
followingInterval = setInterval(() => {
|
||||
logsEl.scrollTop = logsEl.scrollHeight;
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}, 100);
|
||||
} else {
|
||||
window.clearInterval(followingInterval);
|
||||
}
|
||||
}
|
||||
async function streamLogs(sequence = 0) {
|
||||
try {
|
||||
let { logs: responseLogs, status } = await get(
|
||||
`/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${sequence}`
|
||||
);
|
||||
currentStatus = status;
|
||||
logs = logs.concat(responseLogs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
|
||||
loading = false;
|
||||
streamInterval = setInterval(async () => {
|
||||
if (status !== 'running' && status !== 'queued') {
|
||||
clearInterval(streamInterval);
|
||||
return;
|
||||
}
|
||||
const nextSequence = logs[logs.length - 1]?.time || 0;
|
||||
try {
|
||||
const data = await get(
|
||||
`/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${nextSequence}`
|
||||
);
|
||||
status = data.status;
|
||||
currentStatus = status;
|
||||
|
||||
logs = logs.concat(data.logs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
|
||||
dispatch('updateBuildStatus', { status });
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}, 1000);
|
||||
} catch ({ error }) {
|
||||
console.log(error);
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
onDestroy(() => {
|
||||
clearInterval(streamInterval);
|
||||
clearInterval(followingInterval);
|
||||
});
|
||||
onMount(async () => {
|
||||
window.scrollTo(0, 0);
|
||||
await streamLogs();
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<div class="relative ">
|
||||
{#if currentStatus === 'running'}
|
||||
<LoadingLogs />
|
||||
{/if}
|
||||
{#if currentStatus === 'queued'}
|
||||
<div class="text-center font-bold text-xl">Queued and waiting for execution.</div>
|
||||
{:else}
|
||||
<div class="flex justify-end sticky top-0 p-2">
|
||||
<button
|
||||
on:click={followBuild}
|
||||
class="bg-transparent"
|
||||
data-tooltip="Follow logs"
|
||||
class:text-green-500={followingBuild}
|
||||
>
|
||||
<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" />
|
||||
<line x1="8" y1="12" x2="12" y2="16" />
|
||||
<line x1="12" y1="8" x2="12" y2="16" />
|
||||
<line x1="16" y1="12" x2="12" y2="16" />
|
||||
</svg>
|
||||
</button>
|
||||
</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}
|
||||
>
|
||||
{#each logs as log}
|
||||
<div>{log.line + '\n'}</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { getTeam, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const buildId = event.url.searchParams.get('buildId');
|
||||
const sequence = Number(event.url.searchParams.get('sequence'));
|
||||
try {
|
||||
let logs = await db.prisma.buildLog.findMany({
|
||||
where: { buildId, time: { gt: sequence } },
|
||||
orderBy: { time: 'asc' }
|
||||
});
|
||||
const data = await db.prisma.build.findFirst({ where: { id: buildId } });
|
||||
|
||||
return {
|
||||
body: {
|
||||
logs,
|
||||
status: data?.status
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { dayjs } from '$lib/dayjs';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { id } = event.params;
|
||||
const buildId = event.url.searchParams.get('buildId');
|
||||
const skip = Number(event.url.searchParams.get('skip')) || 0;
|
||||
|
||||
let builds = [];
|
||||
try {
|
||||
const buildCount = await db.prisma.build.count({ where: { applicationId: id } });
|
||||
if (buildId) {
|
||||
builds = await db.prisma.build.findMany({ where: { applicationId: id, id: buildId } });
|
||||
} else {
|
||||
builds = await db.prisma.build.findMany({
|
||||
where: { applicationId: id },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 5,
|
||||
skip
|
||||
});
|
||||
}
|
||||
builds = builds.map((build) => {
|
||||
const updatedAt = dayjs(build.updatedAt).utc();
|
||||
build.took = updatedAt.diff(dayjs(build.createdAt)) / 1000;
|
||||
build.since = updatedAt.fromNow();
|
||||
return build;
|
||||
});
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
builds,
|
||||
buildCount
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,147 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
||||
let endpoint = `/applications/${params.id}/logs/build.json?skip=0`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
application: stuff.application,
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${url}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { dateOptions, getDomain } from '$lib/components/common';
|
||||
|
||||
import BuildLog from './_BuildLog.svelte';
|
||||
import { get } from '$lib/api';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
export let builds;
|
||||
export let application;
|
||||
export let buildCount;
|
||||
|
||||
let buildId;
|
||||
|
||||
let skip = 0;
|
||||
let noMoreBuilds = buildCount < 5 || buildCount <= skip;
|
||||
const { id } = $page.params;
|
||||
let preselectedBuildId = $page.url.searchParams.get('buildId');
|
||||
if (preselectedBuildId) buildId = preselectedBuildId;
|
||||
|
||||
async function updateBuildStatus({ detail }) {
|
||||
const { status } = detail;
|
||||
if (status !== 'running') {
|
||||
try {
|
||||
const data = await get(`/applications/${id}/logs/build.json?buildId=${buildId}`);
|
||||
builds = builds.filter((build) => {
|
||||
if (build.id === data.builds[0].id) {
|
||||
build.status = data.builds[0].status;
|
||||
build.took = data.builds[0].took;
|
||||
build.since = data.builds[0].since;
|
||||
}
|
||||
|
||||
return build;
|
||||
});
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
} else {
|
||||
builds = builds.filter((build) => {
|
||||
if (build.id === buildId) build.status = status;
|
||||
return build;
|
||||
});
|
||||
}
|
||||
}
|
||||
async function loadMoreBuilds() {
|
||||
if (buildCount >= skip) {
|
||||
skip = skip + 5;
|
||||
noMoreBuilds = buildCount >= skip;
|
||||
try {
|
||||
const data = await get(`/applications/${id}/logs/build.json?skip=${skip}`);
|
||||
builds = builds.concat(data.builds);
|
||||
return;
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
} else {
|
||||
noMoreBuilds = true;
|
||||
}
|
||||
}
|
||||
async function loadBuild(build) {
|
||||
buildId = build;
|
||||
await goto(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
Build logs of <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block flex-row justify-start space-x-2 px-5 pt-6 sm:px-10 md:flex">
|
||||
<div class="mb-4 min-w-[16rem] space-y-2 md:mb-0 ">
|
||||
<div class="top-4 md:sticky">
|
||||
{#each builds as build, index (build.id)}
|
||||
<div
|
||||
data-tooltip={new Intl.DateTimeFormat('default', dateOptions).format(
|
||||
new Date(build.createdAt)
|
||||
) + `\n${build.status}`}
|
||||
on:click={() => loadBuild(build.id)}
|
||||
class:rounded-tr={index === 0}
|
||||
class:rounded-br={index === builds.length - 1}
|
||||
class="tooltip-top flex cursor-pointer items-center justify-center border-l-2 border-transparent py-4 no-underline transition-all duration-100 hover:bg-coolgray-400 hover:shadow-xl "
|
||||
class:bg-coolgray-400={buildId === build.id}
|
||||
class:border-red-500={build.status === 'failed'}
|
||||
class:border-green-500={build.status === 'success'}
|
||||
class:border-yellow-500={build.status === 'running'}
|
||||
>
|
||||
<div class="flex-col px-2">
|
||||
<div class="text-sm font-bold">
|
||||
{application.branch}
|
||||
</div>
|
||||
<div class="text-xs">
|
||||
{build.type}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1" />
|
||||
|
||||
<div class="w-48 text-center text-xs">
|
||||
{#if build.status === 'running'}
|
||||
<div class="font-bold">Running</div>
|
||||
{:else if build.status === 'queued'}
|
||||
<div class="font-bold">Queued</div>
|
||||
{:else}
|
||||
<div>{build.since}</div>
|
||||
<div>Finished in <span class="font-bold">{build.took}s</span></div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button disabled={noMoreBuilds} class="w-full" on:click={loadMoreBuilds}>Load More</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 md:w-96">
|
||||
{#if buildId}
|
||||
{#key buildId}
|
||||
<svelte:component this={BuildLog} {buildId} on:updateBuildStatus={updateBuildStatus} />
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if buildCount === 0}
|
||||
<div class="text-center text-xl font-bold">No logs found</div>
|
||||
{/if}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { dayjs } from '$lib/dayjs';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
try {
|
||||
const { destinationDockerId, destinationDocker } = await db.prisma.application.findUnique({
|
||||
where: { id },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
if (destinationDockerId) {
|
||||
const docker = dockerInstance({ destinationDocker });
|
||||
try {
|
||||
const container = await docker.engine.getContainer(id);
|
||||
if (container) {
|
||||
return {
|
||||
body: {
|
||||
logs: (await container.logs({ stdout: true, stderr: true, timestamps: true }))
|
||||
.toString()
|
||||
.split('\n')
|
||||
.map((l) => l.slice(8))
|
||||
.filter((a) => a)
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
const { statusCode } = error;
|
||||
if (statusCode === 404) {
|
||||
return {
|
||||
body: {
|
||||
logs: []
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: 'No logs found.'
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,119 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
||||
let endpoint = `/applications/${params.id}/logs.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
application: stuff.application,
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${url}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
import { page } from '$app/stores';
|
||||
import LoadingLogs from './_Loading.svelte';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { get } from '$lib/api';
|
||||
import { errorNotification } from '$lib/form';
|
||||
|
||||
let loadLogsInterval = null;
|
||||
let logs = [];
|
||||
let followingBuild;
|
||||
let followingInterval;
|
||||
let logsEl;
|
||||
|
||||
const { id } = $page.params;
|
||||
onMount(async () => {
|
||||
loadLogs();
|
||||
loadLogsInterval = setInterval(() => {
|
||||
loadLogs();
|
||||
}, 1000);
|
||||
});
|
||||
onDestroy(() => {
|
||||
clearInterval(loadLogsInterval);
|
||||
clearInterval(followingInterval);
|
||||
});
|
||||
async function loadLogs() {
|
||||
try {
|
||||
const newLogs = await get(`/applications/${id}/logs.json`);
|
||||
logs = newLogs.logs;
|
||||
return;
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
|
||||
function followBuild() {
|
||||
followingBuild = !followingBuild;
|
||||
if (followingBuild) {
|
||||
followingInterval = setInterval(() => {
|
||||
logsEl.scrollTop = logsEl.scrollHeight;
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}, 100);
|
||||
} else {
|
||||
window.clearInterval(followingInterval);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
Application logs of <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row justify-center space-x-2 px-10 pt-6">
|
||||
{#if logs.length === 0}
|
||||
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
|
||||
{:else}
|
||||
<div class="relative">
|
||||
<LoadingLogs />
|
||||
<div class="flex justify-end sticky top-0 p-2">
|
||||
<button
|
||||
on:click={followBuild}
|
||||
class="bg-transparent"
|
||||
data-tooltip="Follow logs"
|
||||
class:text-green-500={followingBuild}
|
||||
>
|
||||
<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" />
|
||||
<line x1="8" y1="12" x2="12" y2="16" />
|
||||
<line x1="12" y1="8" x2="12" y2="16" />
|
||||
<line x1="16" y1="12" x2="12" y2="16" />
|
||||
</svg>
|
||||
</button>
|
||||
</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}
|
||||
>
|
||||
<div class="px-2">
|
||||
{#each logs as log}
|
||||
{log + '\n'}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,53 +0,0 @@
|
||||
import { getTeam, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { status, body, teamId } = await getUserDetails(event, false);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
try {
|
||||
const secrets = await db.listSecrets(id);
|
||||
const applicationSecrets = secrets.filter((secret) => !secret.isPRMRSecret);
|
||||
const PRMRSecrets = secrets.filter((secret) => secret.isPRMRSecret);
|
||||
const destinationDocker = await db.getDestinationByApplicationId({ id, teamId });
|
||||
const docker = dockerInstance({ destinationDocker });
|
||||
const listContainers = await docker.engine.listContainers({
|
||||
filters: { network: [destinationDocker.network] }
|
||||
});
|
||||
const containers = listContainers.filter((container) => {
|
||||
return (
|
||||
container.Labels['coolify.configuration'] &&
|
||||
container.Labels['coolify.type'] === 'standalone-application'
|
||||
);
|
||||
});
|
||||
const jsonContainers = containers
|
||||
.map((container) =>
|
||||
JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString())
|
||||
)
|
||||
.filter((container) => {
|
||||
return (
|
||||
container.type !== 'manual' &&
|
||||
container.type !== 'webhook_commit' &&
|
||||
container.applicationId === id
|
||||
);
|
||||
});
|
||||
return {
|
||||
body: {
|
||||
containers: jsonContainers,
|
||||
applicationSecrets: applicationSecrets.sort((a, b) => {
|
||||
return ('' + a.name).localeCompare(b.name);
|
||||
}),
|
||||
PRMRSecrets: PRMRSecrets.sort((a, b) => {
|
||||
return ('' + a.name).localeCompare(b.name);
|
||||
})
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,118 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, stuff }) => {
|
||||
let endpoint = `/applications/${params.id}/previews.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
application: stuff.application,
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${endpoint}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let containers;
|
||||
export let application;
|
||||
export let PRMRSecrets;
|
||||
export let applicationSecrets;
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import Secret from '../secrets/_Secret.svelte';
|
||||
import { get, post } from '$lib/api';
|
||||
import { page } from '$app/stores';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
|
||||
const { id } = $page.params;
|
||||
async function refreshSecrets() {
|
||||
const data = await get(`/applications/${id}/secrets.json`);
|
||||
PRMRSecrets = [...data.secrets];
|
||||
}
|
||||
async function redeploy(container) {
|
||||
try {
|
||||
await post(`/applications/${id}/deploy.json`, {
|
||||
pullmergeRequestId: container.pullmergeRequestId,
|
||||
branch: container.branch
|
||||
});
|
||||
toast.push('Application redeployed queued.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
Previews for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if applicationSecrets.length !== 0}
|
||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||
<table class="mx-auto border-separate text-left">
|
||||
<thead>
|
||||
<tr class="h-12">
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Value</th>
|
||||
<th scope="col" class="w-64 text-center">Need during buildtime?</th>
|
||||
<th scope="col" class="w-96 text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each applicationSecrets as secret}
|
||||
{#key secret.id}
|
||||
<tr>
|
||||
<Secret
|
||||
PRMRSecret={PRMRSecrets.find((s) => s.name === secret.name)}
|
||||
isPRMRSecret
|
||||
name={secret.name}
|
||||
value={secret.value}
|
||||
isBuildSecret={secret.isBuildSecret}
|
||||
on:refresh={refreshSecrets}
|
||||
/>
|
||||
</tr>
|
||||
{/key}
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex justify-center py-4 text-center">
|
||||
<Explainer
|
||||
customClass="w-full"
|
||||
text={applicationSecrets.length === 0
|
||||
? "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
|
||||
: "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."}
|
||||
/>
|
||||
</div>
|
||||
<div class="mx-auto max-w-4xl py-10">
|
||||
<div class="flex flex-wrap justify-center space-x-2">
|
||||
{#if containers.length > 0}
|
||||
{#each containers as container}
|
||||
<a href={container.fqdn} class="p-2 no-underline" target="_blank">
|
||||
<div class="box-selection text-center hover:border-transparent hover:bg-coolgray-200">
|
||||
<div class="truncate text-center text-xl font-bold">{getDomain(container.fqdn)}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="bg-coollabs hover:bg-coollabs-100" on:click={() => redeploy(container)}
|
||||
>Redeploy</button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="flex-col">
|
||||
<div class="text-center font-bold text-xl">No previews available</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,150 +0,0 @@
|
||||
<script>
|
||||
export let name = '';
|
||||
export let value = '';
|
||||
export let isBuildSecret = false;
|
||||
export let isNewSecret = false;
|
||||
export let isPRMRSecret = false;
|
||||
export let PRMRSecret = {};
|
||||
|
||||
if (isPRMRSecret) value = PRMRSecret.value;
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { del, post } from '$lib/api';
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const { id } = $page.params;
|
||||
async function removeSecret() {
|
||||
try {
|
||||
await del(`/applications/${id}/secrets.json`, { name });
|
||||
dispatch('refresh');
|
||||
if (isNewSecret) {
|
||||
name = '';
|
||||
value = '';
|
||||
isBuildSecret = false;
|
||||
}
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
async function saveSecret(isNew = false) {
|
||||
if (!name) return errorNotification('Name is required.');
|
||||
if (!value) return errorNotification('Value is required.');
|
||||
try {
|
||||
await post(`/applications/${id}/secrets.json`, {
|
||||
name,
|
||||
value,
|
||||
isBuildSecret,
|
||||
isPRMRSecret,
|
||||
isNew
|
||||
});
|
||||
dispatch('refresh');
|
||||
if (isNewSecret) {
|
||||
name = '';
|
||||
value = '';
|
||||
isBuildSecret = false;
|
||||
}
|
||||
toast.push('Secret saved.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
function setSecretValue() {
|
||||
if (isNewSecret) {
|
||||
isBuildSecret = !isBuildSecret;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<td>
|
||||
<input
|
||||
id={isNewSecret ? 'secretName' : 'secretNameNew'}
|
||||
bind:value={name}
|
||||
required
|
||||
placeholder="EXAMPLE_VARIABLE"
|
||||
class=" border border-dashed border-coolgray-300"
|
||||
readonly={!isNewSecret}
|
||||
class:bg-transparent={!isNewSecret}
|
||||
class:cursor-not-allowed={!isNewSecret}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<CopyPasswordField
|
||||
id={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
||||
name={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
||||
isPasswordField={true}
|
||||
bind:value
|
||||
required
|
||||
placeholder="J$#@UIO%HO#$U%H"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div
|
||||
type="button"
|
||||
on:click={setSecretValue}
|
||||
aria-pressed="false"
|
||||
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
||||
class:bg-green-600={isBuildSecret}
|
||||
class:bg-stone-700={!isBuildSecret}
|
||||
class:opacity-50={!isNewSecret}
|
||||
class:cursor-not-allowed={!isNewSecret}
|
||||
class:cursor-pointer={isNewSecret}
|
||||
>
|
||||
<span class="sr-only">Use isBuildSecret</span>
|
||||
<span
|
||||
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out"
|
||||
class:translate-x-5={isBuildSecret}
|
||||
class:translate-x-0={!isBuildSecret}
|
||||
>
|
||||
<span
|
||||
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
|
||||
class:opacity-0={isBuildSecret}
|
||||
class:opacity-100={!isBuildSecret}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
|
||||
<path
|
||||
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out"
|
||||
aria-hidden="true"
|
||||
class:opacity-100={isBuildSecret}
|
||||
class:opacity-0={!isBuildSecret}
|
||||
>
|
||||
<svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
|
||||
<path
|
||||
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{#if isNewSecret}
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveSecret(true)}>Add</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row justify-center space-x-2">
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="" on:click={() => saveSecret(false)}>Set</button>
|
||||
</div>
|
||||
{#if !isPRMRSecret}
|
||||
<div class="flex justify-center items-end">
|
||||
<button class="bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
@@ -1,70 +0,0 @@
|
||||
import { getTeam, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
try {
|
||||
const secrets = await (await db.listSecrets(id)).filter((secret) => !secret.isPRMRSecret);
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
secrets: secrets.sort((a, b) => {
|
||||
return ('' + a.name).localeCompare(b.name);
|
||||
})
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { name, value, isBuildSecret, isPRMRSecret, isNew } = await event.request.json();
|
||||
try {
|
||||
if (isNew) {
|
||||
const found = await db.isSecretExists({ id, name, isPRMRSecret });
|
||||
if (found) {
|
||||
throw {
|
||||
error: `Secret ${name} already exists.`
|
||||
};
|
||||
} else {
|
||||
await db.createSecret({ id, name, value, isBuildSecret, isPRMRSecret });
|
||||
return {
|
||||
status: 201
|
||||
};
|
||||
}
|
||||
} else {
|
||||
await db.updateSecret({ id, name, value, isBuildSecret, isPRMRSecret });
|
||||
return {
|
||||
status: 201
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
export const del: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { name } = await event.request.json();
|
||||
|
||||
try {
|
||||
await db.removeSecret({ id, name });
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, stuff }) => {
|
||||
let endpoint = `/applications/${params.id}/secrets.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
application: stuff.application,
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${endpoint}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let secrets;
|
||||
export let application;
|
||||
import Secret from './_Secret.svelte';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { page } from '$app/stores';
|
||||
import { get } from '$lib/api';
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
async function refreshSecrets() {
|
||||
const data = await get(`/applications/${id}/secrets.json`);
|
||||
secrets = [...data.secrets];
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
Secrets for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||
<table class="mx-auto border-separate text-left">
|
||||
<thead>
|
||||
<tr class="h-12">
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Value</th>
|
||||
<th scope="col" class="w-64 text-center">Need during buildtime?</th>
|
||||
<th scope="col" class="w-96 text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each secrets as secret}
|
||||
{#key secret.id}
|
||||
<tr>
|
||||
<Secret
|
||||
name={secret.name}
|
||||
value={secret.value}
|
||||
isBuildSecret={secret.isBuildSecret}
|
||||
on:refresh={refreshSecrets}
|
||||
/>
|
||||
</tr>
|
||||
{/key}
|
||||
{/each}
|
||||
<tr>
|
||||
<Secret isNewSecret on:refresh={refreshSecrets} />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1,26 +0,0 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { debug, previews, dualCerts, autodeploy, branch, projectId } = await event.request.json();
|
||||
|
||||
try {
|
||||
const isDouble = await db.checkDoubleBranch(branch, projectId);
|
||||
if (isDouble && autodeploy) {
|
||||
throw {
|
||||
message:
|
||||
'Cannot activate automatic deployments until only one application is defined for this repository / branch.'
|
||||
};
|
||||
}
|
||||
await db.setApplicationSettings({ id, debug, previews, dualCerts, autodeploy });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import { asyncExecShell, getEngine, getUserDetails, removeDestinationDocker } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const { destinationDocker, destinationDockerId, fqdn } = await db.getApplication({
|
||||
id,
|
||||
teamId
|
||||
});
|
||||
if (destinationDockerId) {
|
||||
const { engine } = destinationDocker;
|
||||
const found = await checkContainer(engine, id);
|
||||
if (found) {
|
||||
await removeDestinationDocker({ id, engine });
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let isNew = false;
|
||||
export let storage = {
|
||||
id: null,
|
||||
path: null
|
||||
};
|
||||
import { del, post } from '$lib/api';
|
||||
import { page } from '$app/stores';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
const { id } = $page.params;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
async function saveStorage(newStorage = false) {
|
||||
try {
|
||||
if (!storage.path) return errorNotification('Path is required.');
|
||||
storage.path = storage.path.startsWith('/') ? storage.path : `/${storage.path}`;
|
||||
storage.path = storage.path.endsWith('/') ? storage.path.slice(0, -1) : storage.path;
|
||||
storage.path.replace(/\/\//g, '/');
|
||||
await post(`/applications/${id}/storage.json`, {
|
||||
path: storage.path,
|
||||
storageId: storage.id,
|
||||
newStorage
|
||||
});
|
||||
dispatch('refresh');
|
||||
if (isNew) {
|
||||
storage.path = null;
|
||||
storage.id = null;
|
||||
}
|
||||
if (newStorage) toast.push('Storage saved.');
|
||||
else toast.push('Storage updated.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
async function removeStorage() {
|
||||
try {
|
||||
await del(`/applications/${id}/storage.json`, { path: storage.path });
|
||||
dispatch('refresh');
|
||||
toast.push('Storage deleted.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<td>
|
||||
<input
|
||||
bind:value={storage.path}
|
||||
required
|
||||
placeholder="eg: /sqlite.db"
|
||||
class=" border border-dashed border-coolgray-300"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{#if isNew}
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveStorage(true)}>Add</button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row justify-center space-x-2">
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="" on:click={() => saveStorage(false)}>Set</button>
|
||||
</div>
|
||||
<div class="flex justify-center items-end">
|
||||
<button class="bg-red-600 hover:bg-red-500" on:click={removeStorage}>Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
@@ -1,63 +0,0 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { status, body, teamId } = await getUserDetails(event, false);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
try {
|
||||
const persistentStorages = await db.getPersistentStorage(id);
|
||||
return {
|
||||
body: {
|
||||
persistentStorages
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { path, newStorage, storageId } = await event.request.json();
|
||||
try {
|
||||
if (newStorage) {
|
||||
await db.prisma.applicationPersistentStorage.create({
|
||||
data: { path, application: { connect: { id } } }
|
||||
});
|
||||
} else {
|
||||
await db.prisma.applicationPersistentStorage.update({
|
||||
where: { id: storageId },
|
||||
data: { path }
|
||||
});
|
||||
}
|
||||
return {
|
||||
status: 201
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const del: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { path } = await event.request.json();
|
||||
|
||||
try {
|
||||
await db.prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id, path } });
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, stuff }) => {
|
||||
let endpoint = `/applications/${params.id}/storage.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
application: stuff.application,
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${endpoint}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
|
||||
export let persistentStorages;
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { page } from '$app/stores';
|
||||
import Storage from './_Storage.svelte';
|
||||
import { get } from '$lib/api';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
|
||||
const { id } = $page.params;
|
||||
async function refreshStorage() {
|
||||
const data = await get(`/applications/${id}/storage.json`);
|
||||
persistentStorages = [...data.persistentStorages];
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
Persistent storage for <a href={application.fqdn} target="_blank"
|
||||
>{getDomain(application.fqdn)}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||
<div class="flex justify-center py-4 text-center">
|
||||
<Explainer
|
||||
customClass="w-full"
|
||||
text={'You can specify any folder that you want to be persistent across deployments. <br>This is useful for storing data such as a database (SQLite) or a cache.'}
|
||||
/>
|
||||
</div>
|
||||
<table class="mx-auto border-separate text-left">
|
||||
<thead>
|
||||
<tr class="h-12">
|
||||
<th scope="col">Path</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each persistentStorages as storage}
|
||||
{#key storage.id}
|
||||
<tr>
|
||||
<Storage on:refresh={refreshStorage} {storage} />
|
||||
</tr>
|
||||
{/key}
|
||||
{/each}
|
||||
<tr>
|
||||
<Storage on:refresh={refreshStorage} isNew />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1,66 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
import Rust from '$lib/components/svg/applications/Rust.svelte';
|
||||
import Nodejs from '$lib/components/svg/applications/Nodejs.svelte';
|
||||
import React from '$lib/components/svg/applications/React.svelte';
|
||||
import Svelte from '$lib/components/svg/applications/Svelte.svelte';
|
||||
import Vuejs from '$lib/components/svg/applications/Vuejs.svelte';
|
||||
import PHP from '$lib/components/svg/applications/PHP.svelte';
|
||||
import Python from '$lib/components/svg/applications/Python.svelte';
|
||||
import Static from '$lib/components/svg/applications/Static.svelte';
|
||||
import Nestjs from '$lib/components/svg/applications/Nestjs.svelte';
|
||||
import Nuxtjs from '$lib/components/svg/applications/Nuxtjs.svelte';
|
||||
import Nextjs from '$lib/components/svg/applications/Nextjs.svelte';
|
||||
import Gatsby from '$lib/components/svg/applications/Gatsby.svelte';
|
||||
import Docker from '$lib/components/svg/applications/Docker.svelte';
|
||||
import Astro from '$lib/components/svg/applications/Astro.svelte';
|
||||
import Eleventy from '$lib/components/svg/applications/Eleventy.svelte';
|
||||
|
||||
const buildPack = application?.buildPack?.toLowerCase();
|
||||
</script>
|
||||
|
||||
<a href="/applications/{application.id}" class="w-96 p-2 no-underline">
|
||||
<div class="box-selection group relative hover:bg-green-600">
|
||||
{#if buildPack === 'rust'}
|
||||
<Rust />
|
||||
{:else if buildPack === 'node'}
|
||||
<Nodejs />
|
||||
{:else if buildPack === 'react'}
|
||||
<React />
|
||||
{:else if buildPack === 'svelte'}
|
||||
<Svelte />
|
||||
{:else if buildPack === 'vuejs'}
|
||||
<Vuejs />
|
||||
{:else if buildPack === 'php'}
|
||||
<PHP />
|
||||
{:else if buildPack === 'python'}
|
||||
<Python />
|
||||
{:else if buildPack === 'static'}
|
||||
<Static />
|
||||
{:else if buildPack === 'nestjs'}
|
||||
<Nestjs />
|
||||
{:else if buildPack === 'nuxtjs'}
|
||||
<Nuxtjs />
|
||||
{:else if buildPack === 'nextjs'}
|
||||
<Nextjs />
|
||||
{:else if buildPack === 'gatsby'}
|
||||
<Gatsby />
|
||||
{:else if buildPack === 'docker'}
|
||||
<Docker />
|
||||
{:else if buildPack === 'astro'}
|
||||
<Astro />
|
||||
{:else if buildPack === 'eleventy'}
|
||||
<Eleventy />
|
||||
{/if}
|
||||
|
||||
<div class="truncate text-center text-xl font-bold">{application.name}</div>
|
||||
{#if application.fqdn}
|
||||
<div class="truncate text-center">{application.fqdn}</div>
|
||||
{/if}
|
||||
{#if !application.gitSourceId || !application.destinationDockerId}
|
||||
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||
Configuration missing
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
@@ -1,43 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let applications: Array<Application>;
|
||||
import { session } from '$app/stores';
|
||||
import Application from './_Application.svelte';
|
||||
import { post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
async function newApplication() {
|
||||
const { id } = await post('/applications/new', {});
|
||||
return await goto(`/applications/${id}`, { replaceState: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl ">Applications</div>
|
||||
{#if $session.isAdmin}
|
||||
<div on:click={newApplication} class="add-icon cursor-pointer bg-green-600 hover:bg-green-500">
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||
/></svg
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center">
|
||||
{#if !applications || applications.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="text-center text-xl font-bold">No applications found</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#each applications as application}
|
||||
<Application {application} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,21 +0,0 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
try {
|
||||
const applications = await db.listApplications(teamId);
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
applications
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
import { getUserDetails, uniqueName } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
const name = uniqueName();
|
||||
try {
|
||||
const { id } = await db.newApplication({ name, teamId });
|
||||
return { status: 201, body: { id } };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user