v1.0.23 (#68)
# Features - Build environment variables for NodeJS builds - Initial monorepo support (more tests needed!) # Fixes - Fix wrong redirects - Logout fix for the session manager
This commit is contained in:
parent
2d0f22b379
commit
b4c836afbd
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source, hassle-free, self-hostable Heroku & Netlify alternative.",
|
"description": "An open-source, hassle-free, self-hostable Heroku & Netlify alternative.",
|
||||||
"version": "1.0.22",
|
"version": "1.0.23",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:docker:start": "docker-compose -f docker-compose-dev.yml up -d",
|
"dev:docker:start": "docker-compose -f docker-compose-dev.yml up -d",
|
||||||
@ -14,8 +14,8 @@
|
|||||||
"format": "prettier --write ."
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-node": "1.0.0-next.26",
|
"@sveltejs/adapter-node": "1.0.0-next.33",
|
||||||
"@sveltejs/kit": "1.0.0-next.115",
|
"@sveltejs/kit": "1.0.0-next.125",
|
||||||
"@types/dockerode": "^3.2.3",
|
"@types/dockerode": "^3.2.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.26.1",
|
"@typescript-eslint/eslint-plugin": "^4.26.1",
|
||||||
"@typescript-eslint/parser": "^4.26.1",
|
"@typescript-eslint/parser": "^4.26.1",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"prettier-plugin-svelte": "^2.3.0",
|
"prettier-plugin-svelte": "^2.3.0",
|
||||||
"svelte": "^3.38.2",
|
"svelte": "^3.38.2",
|
||||||
"svelte-preprocess": "^4.7.3",
|
"svelte-preprocess": "^4.7.3",
|
||||||
"tailwindcss": "2.2.0",
|
"tailwindcss": "2.2.4",
|
||||||
"tslib": "^2.2.0",
|
"tslib": "^2.2.0",
|
||||||
"typescript": "^4.3.2",
|
"typescript": "^4.3.2",
|
||||||
"vite": "^2.3.6"
|
"vite": "^2.3.6"
|
||||||
|
2630
pnpm-lock.yaml
generated
2630
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { VITE_GITHUB_APP_NAME } from '$lib/consts';
|
import { VITE_GITHUB_APP_NAME } from '$lib/consts';
|
||||||
import { application, isPullRequestPermissionsGranted } from '$store';
|
import { application, isPullRequestPermissionsGranted, originalDomain } from '$store';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import TooltipInfo from '$components/TooltipInfo.svelte';
|
import TooltipInfo from '$components/TooltipInfo.svelte';
|
||||||
import { request } from '$lib/request';
|
import { request } from '$lib/request';
|
||||||
@ -122,7 +122,7 @@
|
|||||||
async function setPreviewDeployment() {
|
async function setPreviewDeployment() {
|
||||||
if ($application.general.isPreviewDeploymentEnabled) {
|
if ($application.general.isPreviewDeploymentEnabled) {
|
||||||
const result = window.confirm(
|
const result = window.confirm(
|
||||||
"Are you sure? It will delete all PR deployments - it's NOT reversible!"
|
"DANGER ZONE! It will delete all PR deployments. It's NOT reversible! Are you sure?"
|
||||||
);
|
);
|
||||||
if (result) {
|
if (result) {
|
||||||
loading.previewDeployment = true;
|
loading.previewDeployment = true;
|
||||||
@ -194,9 +194,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!$application.publish.domain) domainInput.focus();
|
if (!$application.publish.domain) {
|
||||||
|
domainInput.focus();
|
||||||
|
} else {
|
||||||
|
$originalDomain = $application.publish.domain;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -378,7 +381,9 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{#if loading.previewDeployment}
|
{#if loading.previewDeployment}
|
||||||
<div class="absolute left-0 bottom-0 -mb-4 -ml-2 text-xs font-bold">{$application.general.isPreviewDeploymentEnabled ? 'Enabling...' : 'Disabling...' }</div>
|
<div class="absolute left-0 bottom-0 -mb-4 -ml-2 text-xs font-bold">
|
||||||
|
{$application.general.isPreviewDeploymentEnabled ? 'Enabling...' : 'Disabling...'}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@ -438,6 +443,10 @@
|
|||||||
<input
|
<input
|
||||||
bind:this={domainInput}
|
bind:this={domainInput}
|
||||||
class="border-2"
|
class="border-2"
|
||||||
|
disabled={$page.path !== '/application/new'}
|
||||||
|
class:cursor-not-allowed={$page.path !== '/application/new'}
|
||||||
|
class:bg-warmGray-900={$page.path !== '/application/new'}
|
||||||
|
class:hover:bg-warmGray-900={$page.path !== '/application/new'}
|
||||||
class:placeholder-red-500={$application.publish.domain == null ||
|
class:placeholder-red-500={$application.publish.domain == null ||
|
||||||
$application.publish.domain == ''}
|
$application.publish.domain == ''}
|
||||||
class:border-red-500={$application.publish.domain == null ||
|
class:border-red-500={$application.publish.domain == null ||
|
||||||
@ -455,7 +464,15 @@
|
|||||||
}/api`}
|
}/api`}
|
||||||
/></label
|
/></label
|
||||||
>
|
>
|
||||||
<input id="Path" bind:value={$application.publish.path} placeholder="/" />
|
<input
|
||||||
|
id="Path"
|
||||||
|
bind:value={$application.publish.path}
|
||||||
|
disabled={$page.path !== '/application/new'}
|
||||||
|
class:cursor-not-allowed={$page.path !== '/application/new'}
|
||||||
|
class:bg-warmGray-900={$page.path !== '/application/new'}
|
||||||
|
class:hover:bg-warmGray-900={$page.path !== '/application/new'}
|
||||||
|
placeholder="/"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label for="Port" class:text-warmGray-800={!buildpacks[$application.build.pack].port.active}
|
<label for="Port" class:text-warmGray-800={!buildpacks[$application.build.pack].port.active}
|
||||||
@ -590,5 +607,4 @@
|
|||||||
.buildpack {
|
.buildpack {
|
||||||
@apply px-6 py-2 mx-2 my-2 bg-warmGray-800 w-48 ease-in-out hover:scale-105 text-center rounded border-2 border-transparent border-dashed cursor-pointer transition duration-100;
|
@apply px-6 py-2 mx-2 my-2 bg-warmGray-800 w-48 ease-in-out hover:scale-105 text-center rounded border-2 border-transparent border-dashed cursor-pointer transition duration-100;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -19,37 +19,29 @@
|
|||||||
async function getPRDeployments() {
|
async function getPRDeployments() {
|
||||||
const { configuration } = await request(`/api/v1/application/config`, $session, {
|
const { configuration } = await request(`/api/v1/application/config`, $session, {
|
||||||
body: {
|
body: {
|
||||||
name: $application.repository.name,
|
nickname: $application.general.nickname
|
||||||
organization: $application.repository.organization,
|
|
||||||
branch: $application.repository.branch
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
|
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
|
||||||
}
|
}
|
||||||
async function removePR(prConfiguration) {
|
async function removePR(prConfiguration) {
|
||||||
const result = window.confirm("Are you sure? It's NOT reversible!");
|
const result = window.confirm("DANGER ZONE! It's NOT reversible! Are you sure?");
|
||||||
if (result) {
|
if (result) {
|
||||||
await request(`/api/v1/application/remove`, $session, {
|
await request(`/api/v1/application/remove`, $session, {
|
||||||
body: {
|
body: {
|
||||||
organization: prConfiguration.repository.organization,
|
nickname: prConfiguration.general.nickname
|
||||||
name: prConfiguration.repository.name,
|
|
||||||
branch: prConfiguration.repository.branch,
|
|
||||||
domain: prConfiguration.publish.domain
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
browser && toast.push('PR deployment removed.');
|
browser && toast.push('PR deployment removed.');
|
||||||
const { configuration } = await request(`/api/v1/application/config`, $session, {
|
const { configuration } = await request(`/api/v1/application/config`, $session, {
|
||||||
body: {
|
body: {
|
||||||
name: prConfiguration.repository.name,
|
nickname: prConfiguration.general.nickname
|
||||||
organization: prConfiguration.repository.organization,
|
|
||||||
branch: prConfiguration.repository.branch
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
|
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="text-2xl font-bold border-gradient w-48">Pull Requests</div>
|
<div class="text-2xl font-bold border-gradient w-48">Pull Requests</div>
|
||||||
|
@ -1,27 +1,29 @@
|
|||||||
<script>
|
<script>
|
||||||
import { application } from "$store";
|
import { application } from '$store';
|
||||||
|
import BuildEnv from '../BuildEnv.svelte';
|
||||||
|
|
||||||
let secret = {
|
let secret = {
|
||||||
name: null,
|
name: null,
|
||||||
value: null,
|
value: null,
|
||||||
|
isBuild: false
|
||||||
};
|
};
|
||||||
let foundSecret = null;
|
let foundSecret = null;
|
||||||
async function saveSecret() {
|
async function saveSecret() {
|
||||||
if (secret.name && secret.value) {
|
if (secret.name && secret.value) {
|
||||||
const found = $application.publish.secrets.find(
|
const found = $application.publish.secrets.find((s) => s.name === secret.name);
|
||||||
s => s.name === secret.name,
|
|
||||||
);
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
$application.publish.secrets = [
|
$application.publish.secrets = [
|
||||||
...$application.publish.secrets,
|
...$application.publish.secrets,
|
||||||
{
|
{
|
||||||
name: secret.name,
|
name: secret.name,
|
||||||
value: secret.value,
|
value: secret.value,
|
||||||
},
|
isBuild: secret.isBuild
|
||||||
|
}
|
||||||
];
|
];
|
||||||
secret = {
|
secret = {
|
||||||
name: null,
|
name: null,
|
||||||
value: null,
|
value: null,
|
||||||
|
isBuild: false
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
foundSecret = found;
|
foundSecret = found;
|
||||||
@ -30,49 +32,95 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function removeSecret(name) {
|
async function removeSecret(name) {
|
||||||
foundSecret = null
|
foundSecret = null;
|
||||||
$application.publish.secrets = [
|
$application.publish.secrets = [...$application.publish.secrets.filter((s) => s.name !== name)];
|
||||||
...$application.publish.secrets.filter(s => s.name !== name),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="text-2xl font-bold border-gradient w-24">Secrets</div>
|
<div class="text-2xl font-bold border-gradient w-24">Secrets</div>
|
||||||
<div class="max-w-xl mx-auto text-center pt-4">
|
<div class="max-w-3xl mx-auto text-center pt-4">
|
||||||
<div class="text-left text-base font-bold tracking-tight text-warmGray-400">
|
|
||||||
New Secret
|
|
||||||
</div>
|
|
||||||
<div class="flex space-x-4">
|
<div class="flex space-x-4">
|
||||||
<input id="secretName" bind:value="{secret.name}" placeholder="Name" class="w-64 border-2 border-transparent" />
|
<div class="grid grid-flow-row">
|
||||||
<input id="secretValue" bind:value="{secret.value}" placeholder="Value" class="w-64 border-2 border-transparent" />
|
<label for="secretName">Secret Name</label>
|
||||||
<button class="icon hover:text-green-500" on:click="{saveSecret}">
|
<input
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
id="secretName"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
bind:value={secret.name}
|
||||||
|
placeholder="Name"
|
||||||
|
class="w-64 border-2 border-transparent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-flow-row">
|
||||||
|
<label for="secretValue">Secret Value</label>
|
||||||
|
<input
|
||||||
|
id="secretValue"
|
||||||
|
bind:value={secret.value}
|
||||||
|
placeholder="Value"
|
||||||
|
class="w-64 border-2 border-transparent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-flow-row">
|
||||||
|
<label for="buildVariable">Is build variable?</label>
|
||||||
|
<div class="mt-2 w-full">
|
||||||
|
<BuildEnv {secret} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6">
|
||||||
|
<button class="icon hover:text-green-500" on:click={saveSecret}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if $application.publish.secrets.length > 0}
|
{#if $application.publish.secrets.length > 0}
|
||||||
<div class="py-4">
|
<div class="pt-1">
|
||||||
{#each $application.publish.secrets as s}
|
{#each $application.publish.secrets as secret}
|
||||||
<div class="flex space-x-4">
|
<div class="flex space-x-4 space-y-2">
|
||||||
<input
|
<input
|
||||||
id="{s.name}"
|
id={secret.name}
|
||||||
value="{s.name}"
|
value={secret.name}
|
||||||
disabled
|
disabled
|
||||||
class="border-2 bg-transparent border-transparent w-64"
|
class="border-2 bg-transparent border-transparent w-64 hover:bg-transparent"
|
||||||
class:border-red-600="{foundSecret && foundSecret.name === s.name}"
|
class:border-red-600={foundSecret && foundSecret.name === secret.name}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
id="{s.createdAt}"
|
id={secret.createdAt}
|
||||||
value="SAVED"
|
value="SAVED"
|
||||||
disabled
|
disabled
|
||||||
class="border-2 bg-transparent border-transparent w-64"
|
class="border-2 bg-transparent border-transparent w-64 hover:bg-transparent"
|
||||||
|
/>
|
||||||
|
<div class="flex justify-center items-center px-12">
|
||||||
|
<BuildEnv {secret} readOnly />
|
||||||
|
</div>
|
||||||
|
<button class="icon hover:text-red-500" on:click={() => removeSecret(secret.name)}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||||
/>
|
/>
|
||||||
<button class="icon hover:text-red-500" on:click="{() => removeSecret(s.name)}">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
55
src/components/Application/BuildEnv.svelte
Normal file
55
src/components/Application/BuildEnv.svelte
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<script>
|
||||||
|
export let secret;
|
||||||
|
export let readOnly = false;
|
||||||
|
function isBuildSet() {
|
||||||
|
if (!readOnly) secret.isBuild = !secret.isBuild;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="buildVariable"
|
||||||
|
type="button"
|
||||||
|
aria-pressed="false"
|
||||||
|
on:click={isBuildSet}
|
||||||
|
class="relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-100"
|
||||||
|
class:bg-green-600={secret.isBuild}
|
||||||
|
class:bg-warmGray-700={!secret.isBuild}
|
||||||
|
class:opacity-50={readOnly}
|
||||||
|
class:cursor-not-allowed={readOnly}
|
||||||
|
>
|
||||||
|
<span class="sr-only">Use setting</span>
|
||||||
|
<span
|
||||||
|
class="pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transition ease-in-out duration-200 transform"
|
||||||
|
class:translate-x-5={secret.isBuild}
|
||||||
|
class:translate-x-0={!secret.isBuild}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class=" ease-in duration-200 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
|
||||||
|
class:opacity-0={secret.isBuild}
|
||||||
|
class:opacity-100={!secret.isBuild}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<svg class="bg-white h-3 w-3 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="ease-out duration-100 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
|
||||||
|
aria-hidden="true"
|
||||||
|
class:opacity-100={secret.isBuild}
|
||||||
|
class:opacity-0={!secret.isBuild}
|
||||||
|
>
|
||||||
|
<svg class="bg-white h-3 w-3 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>
|
||||||
|
</button>
|
@ -19,6 +19,7 @@
|
|||||||
import Tabs from '$components/Application/Tabs.svelte';
|
import Tabs from '$components/Application/Tabs.svelte';
|
||||||
import Repositories from '$components/Application/Repositories.svelte';
|
import Repositories from '$components/Application/Repositories.svelte';
|
||||||
import Login from '$components/Application/Login.svelte';
|
import Login from '$components/Application/Login.svelte';
|
||||||
|
import { dashify } from '$lib/common';
|
||||||
let loading = {
|
let loading = {
|
||||||
github: false,
|
github: false,
|
||||||
branches: false
|
branches: false
|
||||||
@ -26,15 +27,7 @@
|
|||||||
let branches = [];
|
let branches = [];
|
||||||
let relogin = false;
|
let relogin = false;
|
||||||
let permissions = {};
|
let permissions = {};
|
||||||
function dashify(str: string, options?: any) {
|
|
||||||
if (typeof str !== 'string') return str;
|
|
||||||
return str
|
|
||||||
.trim()
|
|
||||||
.replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-'))
|
|
||||||
.replace(/^-+|-+$/g, '')
|
|
||||||
.replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m))
|
|
||||||
.toLowerCase();
|
|
||||||
}
|
|
||||||
async function getGithubRepos(id, page) {
|
async function getGithubRepos(id, page) {
|
||||||
return await request(
|
return await request(
|
||||||
`https://api.github.com/user/installations/${id}/repositories?per_page=100&page=${page}`,
|
`https://api.github.com/user/installations/${id}/repositories?per_page=100&page=${page}`,
|
||||||
@ -174,7 +167,6 @@
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div in:fade={{ duration: 100 }}>
|
<div in:fade={{ duration: 100 }}>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { application, initialApplication, initConf } from '$store';
|
import { application, initialApplication, initConf, originalDomain } from '$store';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
import Tooltip from '$components/Tooltip.svelte';
|
import Tooltip from '$components/Tooltip.svelte';
|
||||||
@ -9,15 +9,12 @@
|
|||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
async function removeApplication() {
|
async function removeApplication() {
|
||||||
const result = window.confirm(
|
const result = window.confirm(
|
||||||
"Are you sure? It will delete all deployments, including PR's - it's NOT reversible!"
|
"DANGER ZONE! It will delete all deployments, including PR's. It's NOT reversible! Are you sure?"
|
||||||
);
|
);
|
||||||
if (result) {
|
if (result) {
|
||||||
await request(`/api/v1/application/remove`, $session, {
|
await request(`/api/v1/application/remove`, $session, {
|
||||||
body: {
|
body: {
|
||||||
organization: $application.repository.organization,
|
nickname: $application.general.nickname
|
||||||
name: $application.repository.name,
|
|
||||||
branch: $application.repository.branch,
|
|
||||||
domain: $application.publish.domain
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,16 +43,14 @@
|
|||||||
$initConf = JSON.parse(JSON.stringify($application));
|
$initConf = JSON.parse(JSON.stringify($application));
|
||||||
if (browser) {
|
if (browser) {
|
||||||
toast.push('Application deployment queued.');
|
toast.push('Application deployment queued.');
|
||||||
goto(
|
goto(`/application/${$application.general.nickname}/logs/${$application.general.deployId}`, {
|
||||||
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/logs/${$application.general.deployId}`,
|
replaceState: true
|
||||||
{ replaceState: true }
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
browser && toast.push(error.error || error || 'Ooops something went wrong.');
|
// browser && toast.push(error.error || error || 'Ooops something went wrong.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="flex text-white justify-end items-center m-4 fixed right-0 top-0 space-x-4 z-50">
|
<nav class="flex text-white justify-end items-center m-4 fixed right-0 top-0 space-x-4 z-50">
|
||||||
@ -131,10 +126,7 @@
|
|||||||
class:cursor-not-allowed={$page.path === '/application/new'}
|
class:cursor-not-allowed={$page.path === '/application/new'}
|
||||||
class:text-blue-400={/logs\/*/.test($page.path)}
|
class:text-blue-400={/logs\/*/.test($page.path)}
|
||||||
class:bg-warmGray-700={/logs\/*/.test($page.path)}
|
class:bg-warmGray-700={/logs\/*/.test($page.path)}
|
||||||
on:click={() =>
|
on:click={() => goto(`/application/${$application.general.nickname}/logs`)}
|
||||||
goto(
|
|
||||||
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/logs`
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="w-6"
|
class="w-6"
|
||||||
@ -160,10 +152,7 @@
|
|||||||
$page.path === '/application/new'}
|
$page.path === '/application/new'}
|
||||||
class:bg-warmGray-700={$page.path.endsWith('configuration') ||
|
class:bg-warmGray-700={$page.path.endsWith('configuration') ||
|
||||||
$page.path === '/application/new'}
|
$page.path === '/application/new'}
|
||||||
on:click={() =>
|
on:click={() => goto(`/application/${$application.general.nickname}/configuration`)}
|
||||||
goto(
|
|
||||||
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/configuration`
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="w-6"
|
class="w-6"
|
||||||
|
@ -27,28 +27,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function load() {
|
async function load() {
|
||||||
const found = $dashboard?.applications?.deployed.find((deployment) => {
|
|
||||||
if (
|
|
||||||
deployment.configuration.repository.organization === $application.repository.organization &&
|
|
||||||
deployment.configuration.repository.name === $application.repository.name &&
|
|
||||||
deployment.configuration.repository.branch === $application.repository.branch
|
|
||||||
) {
|
|
||||||
return deployment;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (found) {
|
|
||||||
$application = { ...found.configuration };
|
|
||||||
if ($page.path === '/application/new') {
|
|
||||||
if (browser) {
|
|
||||||
toast.push('This repository & branch is already defined. Redirecting...');
|
|
||||||
goto(
|
|
||||||
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/configuration`,
|
|
||||||
{ replaceState: true }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($page.path === '/application/new') {
|
if ($page.path === '/application/new') {
|
||||||
try {
|
try {
|
||||||
const dir = await request(
|
const dir = await request(
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { docker } from '$lib/api/docker';
|
import { docker } from '$lib/api/docker';
|
||||||
import Deployment from '$models/Deployment';
|
import Deployment from '$models/Deployment';
|
||||||
import { execShellAsync } from '../common';
|
import { execShellAsync } from '../common';
|
||||||
|
import crypto from 'crypto';
|
||||||
export async function deleteSameDeployments(configuration) {
|
export async function deleteSameDeployments(configuration, originalDomain = null) {
|
||||||
await (
|
await (
|
||||||
await docker.engine.listServices()
|
await docker.engine.listServices()
|
||||||
)
|
)
|
||||||
@ -12,7 +12,7 @@ export async function deleteSameDeployments(configuration) {
|
|||||||
if (
|
if (
|
||||||
running.repository.id === configuration.repository.id &&
|
running.repository.id === configuration.repository.id &&
|
||||||
running.repository.branch === configuration.repository.branch &&
|
running.repository.branch === configuration.repository.branch &&
|
||||||
running.publish.domain === configuration.publish.domain
|
running.publish.domain === originalDomain || configuration.publish.domain
|
||||||
) {
|
) {
|
||||||
await execShellAsync(`docker stack rm ${s.Spec.Labels['com.docker.stack.namespace']}`);
|
await execShellAsync(`docker stack rm ${s.Spec.Labels['com.docker.stack.namespace']}`);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,8 @@ import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-
|
|||||||
import { docker } from '$lib/api/docker';
|
import { docker } from '$lib/api/docker';
|
||||||
import { baseServiceConfiguration } from './common';
|
import { baseServiceConfiguration } from './common';
|
||||||
import { execShellAsync } from '../common';
|
import { execShellAsync } from '../common';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import Configuration from '$models/Configuration';
|
||||||
function getUniq() {
|
function getUniq() {
|
||||||
return uniqueNamesGenerator({ dictionaries: [adjectives, animals, colors], length: 2 });
|
return uniqueNamesGenerator({ dictionaries: [adjectives, animals, colors], length: 2 });
|
||||||
}
|
}
|
||||||
@ -12,7 +13,7 @@ function getUniq() {
|
|||||||
export function setDefaultConfiguration(configuration) {
|
export function setDefaultConfiguration(configuration) {
|
||||||
const nickname = configuration.general.nickname || getUniq();
|
const nickname = configuration.general.nickname || getUniq();
|
||||||
const deployId = cuid();
|
const deployId = cuid();
|
||||||
const shaBase = JSON.stringify({ repository: configuration.repository });
|
const shaBase = JSON.stringify({ path: configuration.publish.path, domain: configuration.publish.domain });
|
||||||
const sha256 = crypto.createHash('sha256').update(shaBase).digest('hex');
|
const sha256 = crypto.createHash('sha256').update(shaBase).digest('hex');
|
||||||
|
|
||||||
configuration.build.container.name = sha256.slice(0, 15);
|
configuration.build.container.name = sha256.slice(0, 15);
|
||||||
@ -51,7 +52,7 @@ export function setDefaultConfiguration(configuration) {
|
|||||||
if (configuration.publish.directory.startsWith('/'))
|
if (configuration.publish.directory.startsWith('/'))
|
||||||
configuration.publish.directory = configuration.publish.directory.replace('/', '');
|
configuration.publish.directory = configuration.publish.directory.replace('/', '');
|
||||||
|
|
||||||
if (configuration.build.pack === 'static' || configuration.build.pack === 'nodejs') {
|
if (configuration.build.pack === 'nodejs') {
|
||||||
if (!configuration.build.command.installation)
|
if (!configuration.build.command.installation)
|
||||||
configuration.build.command.installation = 'yarn install';
|
configuration.build.command.installation = 'yarn install';
|
||||||
}
|
}
|
||||||
@ -81,34 +82,37 @@ export function setDefaultConfiguration(configuration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function precheckDeployment(configuration) {
|
export async function precheckDeployment(configuration) {
|
||||||
const services = (await docker.engine.listServices()).filter(
|
const services = await Configuration.find({
|
||||||
(r) =>
|
'publish.domain': configuration.publish.domain,
|
||||||
r.Spec.Labels.managedBy === 'coolify' &&
|
'publish.path': configuration.publish.path
|
||||||
r.Spec.Labels.type === 'application' &&
|
})
|
||||||
JSON.parse(r.Spec.Labels.configuration).publish.domain === configuration.publish.domain
|
// const services = (await docker.engine.listServices()).filter(
|
||||||
);
|
// (r) =>
|
||||||
|
// r.Spec.Labels.managedBy === 'coolify' &&
|
||||||
|
// r.Spec.Labels.type === 'application' &&
|
||||||
|
// JSON.parse(r.Spec.Labels.configuration).publish.domain === configuration.publish.domain
|
||||||
|
// );
|
||||||
let foundService = false;
|
let foundService = false;
|
||||||
let configChanged = false;
|
let configChanged = false;
|
||||||
let imageChanged = false;
|
let imageChanged = false;
|
||||||
let forceUpdate = false;
|
let forceUpdate = false;
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
const running = JSON.parse(service.Spec.Labels.configuration);
|
// const running = JSON.parse(service.Spec.Labels.configuration);
|
||||||
if (running) {
|
|
||||||
if (
|
if (
|
||||||
running.repository.id === configuration.repository.id &&
|
service.repository.id === configuration.repository.id &&
|
||||||
running.repository.branch === configuration.repository.branch
|
service.repository.branch === configuration.repository.branch
|
||||||
) {
|
) {
|
||||||
foundService = true;
|
foundService = true;
|
||||||
// Base service configuration changed
|
// Base service configuration changed
|
||||||
if (
|
if (
|
||||||
!running.build.container.baseSHA ||
|
!service.build.container.baseSHA ||
|
||||||
running.build.container.baseSHA !== configuration.build.container.baseSHA
|
service.build.container.baseSHA !== configuration.build.container.baseSHA
|
||||||
) {
|
) {
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
// If the deployment is in error state, forceUpdate
|
// If the deployment is in error state, forceUpdate
|
||||||
const state = await execShellAsync(
|
const state = await execShellAsync(
|
||||||
`docker stack ps ${running.build.container.name} --format '{{ json . }}'`
|
`docker stack ps ${service.build.container.name} --format '{{ json . }}'`
|
||||||
);
|
);
|
||||||
const isError = state
|
const isError = state
|
||||||
.split('\n')
|
.split('\n')
|
||||||
@ -116,7 +120,7 @@ export async function precheckDeployment(configuration) {
|
|||||||
.map((s) => JSON.parse(s))
|
.map((s) => JSON.parse(s))
|
||||||
.filter(
|
.filter(
|
||||||
(n) =>
|
(n) =>
|
||||||
n.DesiredState !== 'Running' && n.Image.split(':')[1] === running.build.container.tag
|
n.DesiredState !== 'Running' && n.Image.split(':')[1] === service.build.container.tag
|
||||||
);
|
);
|
||||||
if (isError.length > 0) {
|
if (isError.length > 0) {
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
@ -145,7 +149,7 @@ export async function precheckDeployment(configuration) {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const runningWithoutContainer = JSON.parse(JSON.stringify(running));
|
const runningWithoutContainer = JSON.parse(JSON.stringify(service));
|
||||||
delete runningWithoutContainer.build.container;
|
delete runningWithoutContainer.build.container;
|
||||||
|
|
||||||
const configurationWithoutContainer = JSON.parse(JSON.stringify(configuration));
|
const configurationWithoutContainer = JSON.parse(JSON.stringify(configuration));
|
||||||
@ -162,16 +166,16 @@ export async function precheckDeployment(configuration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If only the image changed
|
// If only the image changed
|
||||||
if (running.build.container.tag !== configuration.build.container.tag) imageChanged = true;
|
if (service.build.container.tag !== configuration.build.container.tag) imageChanged = true;
|
||||||
// If build pack changed, forceUpdate the service
|
// If build pack changed, forceUpdate the service
|
||||||
if (running.build.pack !== configuration.build.pack) forceUpdate = true;
|
if (service.build.pack !== configuration.build.pack) forceUpdate = true;
|
||||||
if (
|
if (
|
||||||
configuration.general.isPreviewDeploymentEnabled &&
|
configuration.general.isPreviewDeploymentEnabled &&
|
||||||
configuration.general.pullRequest !== 0
|
configuration.general.pullRequest !== 0
|
||||||
)
|
)
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (forceUpdate) {
|
if (forceUpdate) {
|
||||||
imageChanged = false;
|
imageChanged = false;
|
||||||
|
@ -5,7 +5,7 @@ import { deleteSameDeployments, purgeImagesContainers } from './cleanup';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import { delay, execShellAsync } from '../common';
|
import { delay, execShellAsync } from '../common';
|
||||||
|
|
||||||
export default async function (configuration, imageChanged) {
|
export default async function (configuration, nextStep) {
|
||||||
const generateEnvs = {};
|
const generateEnvs = {};
|
||||||
for (const secret of configuration.publish.secrets) {
|
for (const secret of configuration.publish.secrets) {
|
||||||
generateEnvs[secret.name] = secret.value;
|
generateEnvs[secret.name] = secret.value;
|
||||||
@ -56,23 +56,25 @@ export default async function (configuration, imageChanged) {
|
|||||||
};
|
};
|
||||||
await saveAppLog('### Publishing.', configuration);
|
await saveAppLog('### Publishing.', configuration);
|
||||||
await fs.writeFile(`${configuration.general.workdir}/stack.yml`, yaml.dump(stack));
|
await fs.writeFile(`${configuration.general.workdir}/stack.yml`, yaml.dump(stack));
|
||||||
if (imageChanged) {
|
if (nextStep === 2) {
|
||||||
// console.log('image changed')
|
// console.log('image changed')
|
||||||
await execShellAsync(
|
await execShellAsync(
|
||||||
`docker service update --image ${containerName}:${containerTag} ${containerName}_${containerName}`
|
`docker service update --image ${containerName}:${containerTag} ${containerName}_${containerName}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// console.log('new deployment or force deployment or config changed')
|
// console.log('new deployment or force deployment or config changed')
|
||||||
await deleteSameDeployments(configuration);
|
// if (originalDomain !== configuration.publish.domain) {
|
||||||
|
// await deleteSameDeployments(configuration, originalDomain);
|
||||||
|
// } else {
|
||||||
|
// await deleteSameDeployments(configuration);
|
||||||
|
// }
|
||||||
|
// await deleteSameDeployments(configuration);
|
||||||
await execShellAsync(
|
await execShellAsync(
|
||||||
`cat ${configuration.general.workdir}/stack.yml | docker stack deploy --prune -c - ${containerName}`
|
`cat ${configuration.general.workdir}/stack.yml | docker stack deploy --prune -c - ${containerName}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
async function purgeImagesAsync(found) {
|
|
||||||
await delay(10000);
|
|
||||||
await purgeImagesContainers(found);
|
|
||||||
}
|
|
||||||
//purgeImagesAsync(configuration);
|
|
||||||
|
|
||||||
await saveAppLog('### Published done!', configuration);
|
await saveAppLog('### Published done!', configuration);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { docker, streamEvents } from '$lib/api/docker';
|
import { docker, streamEvents } from '$lib/api/docker';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
const buildImageNodeDocker = (configuration, prodBuild) => {
|
const buildImageNodeDocker = (configuration, prodBuild, generateEnvs) => {
|
||||||
return [
|
return [
|
||||||
'FROM node:lts',
|
'FROM node:lts',
|
||||||
|
...generateEnvs,
|
||||||
'WORKDIR /usr/src/app',
|
'WORKDIR /usr/src/app',
|
||||||
`COPY ${configuration.build.directory}/package*.json ./`,
|
`COPY ${configuration.build.directory}/package*.json ./`,
|
||||||
configuration.build.command.installation && `RUN ${configuration.build.command.installation}`,
|
configuration.build.command.installation && `RUN ${configuration.build.command.installation}`,
|
||||||
@ -13,15 +14,26 @@ const buildImageNodeDocker = (configuration, prodBuild) => {
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
};
|
};
|
||||||
export async function buildImage(configuration, cacheBuild?: boolean, prodBuild?: boolean) {
|
export async function buildImage(configuration, cacheBuild?: boolean, prodBuild?: boolean) {
|
||||||
|
// TODO: Edit secrets
|
||||||
|
// TODO: Add secret from .env file / json
|
||||||
|
const generateEnvs = [];
|
||||||
|
const dotEnv = []
|
||||||
|
for (const secret of configuration.publish.secrets) {
|
||||||
|
dotEnv.push(`${secret.name}=${secret.value}`)
|
||||||
|
if (secret.isBuild) generateEnvs.push(`ENV ${secret.name}=${secret.value}`)
|
||||||
|
}
|
||||||
|
await fs.writeFile(
|
||||||
|
`${configuration.general.workdir}/.env`,
|
||||||
|
dotEnv.join('\n')
|
||||||
|
)
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
`${configuration.general.workdir}/Dockerfile`,
|
`${configuration.general.workdir}/Dockerfile`,
|
||||||
buildImageNodeDocker(configuration, prodBuild)
|
buildImageNodeDocker(configuration, prodBuild, generateEnvs)
|
||||||
);
|
);
|
||||||
const stream = await docker.engine.buildImage(
|
const stream = await docker.engine.buildImage(
|
||||||
{ src: ['.'], context: configuration.general.workdir },
|
{ src: ['.'], context: configuration.general.workdir },
|
||||||
{
|
{
|
||||||
t: `${configuration.build.container.name}:${
|
t: `${configuration.build.container.name}:${cacheBuild
|
||||||
cacheBuild
|
|
||||||
? `${configuration.build.container.tag}-cache`
|
? `${configuration.build.container.tag}-cache`
|
||||||
: configuration.build.container.tag
|
: configuration.build.container.tag
|
||||||
}`
|
}`
|
||||||
|
76
src/lib/api/applications/preChecks.ts
Normal file
76
src/lib/api/applications/preChecks.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import Configuration from "$models/Configuration";
|
||||||
|
import { compareObjects, execShellAsync } from "../common";
|
||||||
|
|
||||||
|
export default async function (configuration) {
|
||||||
|
/*
|
||||||
|
0 => nothing changed, no need to redeploy
|
||||||
|
1 => force update
|
||||||
|
2 => configuration changed
|
||||||
|
3 => continue normally
|
||||||
|
*/
|
||||||
|
const currentConfiguration = await Configuration.findOne({
|
||||||
|
'general.nickname': configuration.general.nickname
|
||||||
|
})
|
||||||
|
if (currentConfiguration) {
|
||||||
|
// Base service configuration changed
|
||||||
|
if (
|
||||||
|
!currentConfiguration.build.container.baseSHA ||
|
||||||
|
currentConfiguration.build.container.baseSHA !== configuration.build.container.baseSHA
|
||||||
|
) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the deployment is in error state, forceUpdate
|
||||||
|
try {
|
||||||
|
const state = await execShellAsync(
|
||||||
|
`docker stack ps ${currentConfiguration.build.container.name} --format '{{ json . }}'`
|
||||||
|
);
|
||||||
|
const isError = state
|
||||||
|
.split('\n')
|
||||||
|
.filter((n) => n)
|
||||||
|
.map((s) => JSON.parse(s))
|
||||||
|
.filter(
|
||||||
|
(n) =>
|
||||||
|
n.DesiredState !== 'Running' && n.Image.split(':')[1] === currentConfiguration.build.container.tag
|
||||||
|
);
|
||||||
|
if (isError.length > 0) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
} catch(error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// If previewDeployments enabled
|
||||||
|
if (
|
||||||
|
currentConfiguration.general.isPreviewDeploymentEnabled &&
|
||||||
|
currentConfiguration.general.pullRequest !== 0
|
||||||
|
) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
// If build pack changed, forceUpdate the service
|
||||||
|
if (currentConfiguration.build.pack !== configuration.build.pack) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentConfigurationCompare = JSON.parse(JSON.stringify(currentConfiguration));
|
||||||
|
const configurationCompare = JSON.parse(JSON.stringify(configuration));
|
||||||
|
delete currentConfigurationCompare.build.container;
|
||||||
|
delete configurationCompare.build.container;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!compareObjects(currentConfigurationCompare.build, configurationCompare.build) ||
|
||||||
|
!compareObjects(currentConfigurationCompare.publish, configurationCompare.publish) ||
|
||||||
|
currentConfigurationCompare.general.isPreviewDeploymentEnabled !==
|
||||||
|
configurationCompare.general.isPreviewDeploymentEnabled
|
||||||
|
) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentConfiguration.build.container.tag !== configuration.build.container.tag) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 3
|
||||||
|
}
|
44
src/lib/api/applications/preTasks.ts
Normal file
44
src/lib/api/applications/preTasks.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import Configuration from "$models/Configuration";
|
||||||
|
import Deployment from "$models/Deployment";
|
||||||
|
|
||||||
|
export default async function (configuration) {
|
||||||
|
// Check if deployment is already queued
|
||||||
|
const alreadyQueued = await Deployment.find({
|
||||||
|
path: configuration.publish.path,
|
||||||
|
domain: configuration.publish.domain,
|
||||||
|
progress: { $in: ['queued', 'inprogress'] }
|
||||||
|
});
|
||||||
|
if (alreadyQueued.length > 0) {
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
success: false,
|
||||||
|
message: 'Deployment already queued.'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const { id, organization, name, branch } = configuration.repository;
|
||||||
|
const { domain, path } = configuration.publish;
|
||||||
|
const { deployId, nickname } = configuration.general;
|
||||||
|
// Save new deployment
|
||||||
|
await new Deployment({
|
||||||
|
repoId: id,
|
||||||
|
branch,
|
||||||
|
deployId,
|
||||||
|
domain,
|
||||||
|
organization,
|
||||||
|
name,
|
||||||
|
nickname
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
await Configuration.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
'publish.domain': domain,
|
||||||
|
'publish.path': path,
|
||||||
|
'general.pullRequest': { $in: [null, 0] }
|
||||||
|
},
|
||||||
|
{ ...configuration },
|
||||||
|
{ upsert: true, new: true }
|
||||||
|
);
|
||||||
|
return
|
||||||
|
}
|
@ -1,26 +1,28 @@
|
|||||||
import Deployment from '$models/Deployment';
|
import Deployment from '$models/Deployment';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import buildContainer from './buildContainer';
|
import buildContainer from './buildContainer';
|
||||||
|
import { purgeImagesContainers } from './cleanup';
|
||||||
import { updateServiceLabels } from './configuration';
|
import { updateServiceLabels } from './configuration';
|
||||||
import copyFiles from './copyFiles';
|
import copyFiles from './copyFiles';
|
||||||
import deploy from './deploy';
|
import deploy from './deploy';
|
||||||
import { saveAppLog } from './logging';
|
import { saveAppLog } from './logging';
|
||||||
|
|
||||||
export default async function (configuration, imageChanged) {
|
export default async function (configuration, nextStep) {
|
||||||
const { id, organization, name, branch } = configuration.repository;
|
const { id, organization, name, branch } = configuration.repository;
|
||||||
const { domain } = configuration.publish;
|
const { domain } = configuration.publish;
|
||||||
const { deployId } = configuration.general;
|
const { deployId } = configuration.general;
|
||||||
try {
|
try {
|
||||||
await saveAppLog(`${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} Queued.`, configuration);
|
await saveAppLog(`### Successfully queued.`, configuration);
|
||||||
await copyFiles(configuration);
|
await copyFiles(configuration);
|
||||||
await buildContainer(configuration);
|
await buildContainer(configuration);
|
||||||
await deploy(configuration, imageChanged);
|
await deploy(configuration, nextStep);
|
||||||
await Deployment.findOneAndUpdate(
|
await Deployment.findOneAndUpdate(
|
||||||
{ repoId: id, branch, deployId, organization, name, domain },
|
{ repoId: id, branch, deployId, organization, name, domain },
|
||||||
{ repoId: id, branch, deployId, organization, name, domain, progress: 'done' }
|
{ repoId: id, branch, deployId, organization, name, domain, progress: 'done' }
|
||||||
);
|
);
|
||||||
|
|
||||||
await updateServiceLabels(configuration);
|
await updateServiceLabels(configuration);
|
||||||
|
await purgeImagesContainers(configuration);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await Deployment.findOneAndUpdate(
|
await Deployment.findOneAndUpdate(
|
||||||
{ repoId: id, branch, deployId, organization, name, domain },
|
{ repoId: id, branch, deployId, organization, name, domain },
|
||||||
|
@ -46,3 +46,27 @@ export function delay(t) {
|
|||||||
}, t);
|
}, t);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function compareObjects(a, b) {
|
||||||
|
if (a === b) return true;
|
||||||
|
|
||||||
|
if (typeof a != 'object' || typeof b != 'object' || a == null || b == null) return false;
|
||||||
|
|
||||||
|
const keysA = Object.keys(a),
|
||||||
|
keysB = Object.keys(b);
|
||||||
|
|
||||||
|
if (keysA.length != keysB.length) return false;
|
||||||
|
|
||||||
|
for (const key of keysA) {
|
||||||
|
if (!keysB.includes(key)) return false;
|
||||||
|
|
||||||
|
if (typeof a[key] === 'function' || typeof b[key] === 'function') {
|
||||||
|
if (a[key].toString() != b[key].toString()) return false;
|
||||||
|
} else {
|
||||||
|
if (!compareObjects(a[key], b[key])) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
9
src/lib/common.ts
Normal file
9
src/lib/common.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export function dashify(str: string, options?: any) {
|
||||||
|
if (typeof str !== 'string') return str;
|
||||||
|
return str
|
||||||
|
.trim()
|
||||||
|
.replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-'))
|
||||||
|
.replace(/^-+|-+$/g, '')
|
||||||
|
.replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m))
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
@ -45,7 +45,11 @@ const ConfigurationSchema = new Schema({
|
|||||||
domain: { type: String, required: true },
|
domain: { type: String, required: true },
|
||||||
path: { type: String },
|
path: { type: String },
|
||||||
port: { type: Number },
|
port: { type: Number },
|
||||||
secrets: { type: Array }
|
secrets: [{
|
||||||
|
name: { type: String },
|
||||||
|
value: { type: String },
|
||||||
|
isBuild: { type: Boolean, default: false },
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { setDefaultConfiguration } from '$lib/api/applications/configuration';
|
import { setDefaultConfiguration } from '$lib/api/applications/configuration';
|
||||||
import { saveServerLog } from '$lib/api/applications/logging';
|
import { saveServerLog } from '$lib/api/applications/logging';
|
||||||
import { docker } from '$lib/api/docker';
|
|
||||||
import Configuration from '$models/Configuration';
|
import Configuration from '$models/Configuration';
|
||||||
import type { Request } from '@sveltejs/kit';
|
import type { Request } from '@sveltejs/kit';
|
||||||
|
|
||||||
@ -8,16 +7,16 @@ export async function post(request: Request) {
|
|||||||
try {
|
try {
|
||||||
const { DOMAIN } = process.env;
|
const { DOMAIN } = process.env;
|
||||||
const configuration = setDefaultConfiguration(request.body);
|
const configuration = setDefaultConfiguration(request.body);
|
||||||
const configurationFound = await Configuration.find({
|
const sameDomainAndPath = await Configuration.find({
|
||||||
'repository.id': { $ne: configuration.repository.id },
|
'publish.path': configuration.publish.path,
|
||||||
'publish.domain': configuration.publish.domain
|
'publish.domain': configuration.publish.domain
|
||||||
}).select('-_id -__v -createdAt -updatedAt');
|
}).select('-_id -__v -createdAt -updatedAt');
|
||||||
if (configurationFound.length > 0 || configuration.publish.domain === DOMAIN) {
|
if (sameDomainAndPath.length > 1 || configuration.publish.domain === DOMAIN) {
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Domain already in use.'
|
message: 'Domain/path are already in use.'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -3,14 +3,11 @@ import Configuration from '$models/Configuration';
|
|||||||
import type { Request } from '@sveltejs/kit';
|
import type { Request } from '@sveltejs/kit';
|
||||||
|
|
||||||
export async function post(request: Request) {
|
export async function post(request: Request) {
|
||||||
const { name, organization, branch }: any = request.body || {};
|
const { nickname }: any = request.body || {};
|
||||||
if (name && organization && branch) {
|
if (nickname) {
|
||||||
const configurationFound = await Configuration.find({
|
const configurationFound = await Configuration.find({
|
||||||
'repository.name': name,
|
'general.nickname': nickname
|
||||||
'repository.organization': organization,
|
|
||||||
'repository.branch': branch
|
|
||||||
}).select('-_id -__v -createdAt -updatedAt');
|
}).select('-_id -__v -createdAt -updatedAt');
|
||||||
|
|
||||||
if (configurationFound) {
|
if (configurationFound) {
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
@ -28,22 +25,8 @@ export async function post(request: Request) {
|
|||||||
const configuration = r.Spec.Labels.configuration
|
const configuration = r.Spec.Labels.configuration
|
||||||
? JSON.parse(r.Spec.Labels.configuration)
|
? JSON.parse(r.Spec.Labels.configuration)
|
||||||
: null;
|
: null;
|
||||||
if (branch) {
|
|
||||||
if (
|
if (configuration.general.nickname === nickname) return r;
|
||||||
configuration.repository.name === name &&
|
|
||||||
configuration.repository.organization === organization &&
|
|
||||||
configuration.repository.branch === branch
|
|
||||||
) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
configuration.repository.name === name &&
|
|
||||||
configuration.repository.organization === organization
|
|
||||||
) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ import cloneRepository from '$lib/api/applications/cloneRepository';
|
|||||||
import { cleanupTmp } from '$lib/api/common';
|
import { cleanupTmp } from '$lib/api/common';
|
||||||
import queueAndBuild from '$lib/api/applications/queueAndBuild';
|
import queueAndBuild from '$lib/api/applications/queueAndBuild';
|
||||||
import Configuration from '$models/Configuration';
|
import Configuration from '$models/Configuration';
|
||||||
|
import preChecks from '$lib/api/applications/preChecks';
|
||||||
|
import preTasks from '$lib/api/applications/preTasks';
|
||||||
|
|
||||||
export async function post(request: Request) {
|
export async function post(request: Request) {
|
||||||
const configuration = setDefaultConfiguration(request.body);
|
const configuration = setDefaultConfiguration(request.body);
|
||||||
@ -18,10 +20,8 @@ export async function post(request: Request) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await cloneRepository(configuration);
|
await cloneRepository(configuration);
|
||||||
const { foundService, imageChanged, configChanged, forceUpdate } = await precheckDeployment(
|
const nextStep = await preChecks(configuration);
|
||||||
configuration
|
if (nextStep === 0) {
|
||||||
);
|
|
||||||
if (foundService && !forceUpdate && !imageChanged && !configChanged) {
|
|
||||||
cleanupTmp(configuration.general.workdir);
|
cleanupTmp(configuration.general.workdir);
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
@ -31,50 +31,9 @@ export async function post(request: Request) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const alreadyQueued = await Deployment.find({
|
await preTasks(configuration)
|
||||||
repoId: configuration.repository.id,
|
|
||||||
branch: configuration.repository.branch,
|
|
||||||
organization: configuration.repository.organization,
|
|
||||||
name: configuration.repository.name,
|
|
||||||
domain: configuration.publish.domain,
|
|
||||||
progress: { $in: ['queued', 'inprogress'] }
|
|
||||||
});
|
|
||||||
if (alreadyQueued.length > 0) {
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
success: false,
|
|
||||||
message: 'Already in the queue.'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const { id, organization, name, branch } = configuration.repository;
|
|
||||||
const { domain } = configuration.publish;
|
|
||||||
const { deployId, nickname, pullRequest } = configuration.general;
|
|
||||||
|
|
||||||
await new Deployment({
|
queueAndBuild(configuration, nextStep);
|
||||||
repoId: id,
|
|
||||||
branch,
|
|
||||||
deployId,
|
|
||||||
domain,
|
|
||||||
organization,
|
|
||||||
name,
|
|
||||||
nickname
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
await Configuration.findOneAndUpdate(
|
|
||||||
{
|
|
||||||
'repository.id': id,
|
|
||||||
'repository.organization': organization,
|
|
||||||
'repository.name': name,
|
|
||||||
'repository.branch': branch,
|
|
||||||
'general.pullRequest': { $in: [null, 0] }
|
|
||||||
},
|
|
||||||
{ ...configuration },
|
|
||||||
{ upsert: true, new: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
queueAndBuild(configuration, imageChanged);
|
|
||||||
return {
|
return {
|
||||||
status: 201,
|
status: 201,
|
||||||
body: {
|
body: {
|
||||||
@ -86,23 +45,7 @@ export async function post(request: Request) {
|
|||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
await Deployment.findOneAndUpdate(
|
await Deployment.findOneAndUpdate({ nickname: configuration.general.nickname }, { $set: { progress: 'failed' } });
|
||||||
{
|
|
||||||
repoId: configuration.repository.id,
|
|
||||||
branch: configuration.repository.branch,
|
|
||||||
organization: configuration.repository.organization,
|
|
||||||
name: configuration.repository.name,
|
|
||||||
domain: configuration.publish.domain
|
|
||||||
},
|
|
||||||
{
|
|
||||||
repoId: configuration.repository.id,
|
|
||||||
branch: configuration.repository.branch,
|
|
||||||
organization: configuration.repository.organization,
|
|
||||||
name: configuration.repository.name,
|
|
||||||
domain: configuration.publish.domain,
|
|
||||||
progress: 'failed'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
status: 500,
|
status: 500,
|
||||||
body: {
|
body: {
|
||||||
|
@ -4,63 +4,47 @@ import ApplicationLog from '$models/ApplicationLog';
|
|||||||
import { delay, execShellAsync } from '$lib/api/common';
|
import { delay, execShellAsync } from '$lib/api/common';
|
||||||
import Configuration from '$models/Configuration';
|
import Configuration from '$models/Configuration';
|
||||||
|
|
||||||
async function purgeImagesAsync(found) {
|
|
||||||
await delay(10000);
|
|
||||||
await purgeImagesContainers(found, true);
|
|
||||||
}
|
|
||||||
export async function post(request: Request) {
|
export async function post(request: Request) {
|
||||||
const { organization, name, branch, domain } = request.body;
|
const { nickname } = request.body;
|
||||||
try {
|
try {
|
||||||
const configurationFound = await Configuration.findOne({
|
const configurationFound = await Configuration.findOne({
|
||||||
'repository.organization': organization,
|
'general.nickname': nickname
|
||||||
'repository.name': name,
|
|
||||||
'repository.branch': branch,
|
|
||||||
'publish.domain': domain
|
|
||||||
});
|
});
|
||||||
if (configurationFound) {
|
if (configurationFound) {
|
||||||
const id = configurationFound._id;
|
const id = configurationFound._id;
|
||||||
if (configurationFound?.general?.pullRequest === 0) {
|
if (configurationFound?.general?.pullRequest === 0) {
|
||||||
// Main deployment deletion request; deleting main + PRs
|
// Main deployment deletion request; deleting main + PRs
|
||||||
const allConfiguration = await Configuration.find({
|
const allConfiguration = await Configuration.find({
|
||||||
'repository.name': name,
|
'publish.domain': { $regex: `.*${configurationFound.publish.domain}`, $options: 'i' },
|
||||||
'repository.organization': organization,
|
'publish.path': configurationFound.publish.path
|
||||||
'repository.branch': branch
|
|
||||||
});
|
});
|
||||||
for (const config of allConfiguration) {
|
for (const config of allConfiguration) {
|
||||||
await Configuration.findOneAndRemove({
|
|
||||||
'repository.name': config.repository.name,
|
|
||||||
'repository.organization': config.repository.organization,
|
|
||||||
'repository.branch': config.repository.branch
|
|
||||||
});
|
|
||||||
await execShellAsync(`docker stack rm ${config.build.container.name}`);
|
await execShellAsync(`docker stack rm ${config.build.container.name}`);
|
||||||
}
|
}
|
||||||
const deploys = await Deployment.find({ organization, branch, name });
|
await Configuration.deleteMany({
|
||||||
|
'publish.domain': { $regex: `.*${configurationFound.publish.domain}`, $options: 'i' },
|
||||||
|
'publish.path': configurationFound.publish.path
|
||||||
|
});
|
||||||
|
const deploys = await Deployment.find({ nickname });
|
||||||
for (const deploy of deploys) {
|
for (const deploy of deploys) {
|
||||||
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
|
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
|
||||||
await Deployment.deleteMany({ deployId: deploy.deployId });
|
await Deployment.deleteMany({ deployId: deploy.deployId });
|
||||||
}
|
}
|
||||||
|
|
||||||
purgeImagesAsync(configurationFound);
|
|
||||||
} else {
|
} else {
|
||||||
// Delete only PRs
|
// Delete only PRs
|
||||||
await Configuration.findByIdAndRemove(id);
|
await Configuration.findByIdAndRemove(id);
|
||||||
await execShellAsync(`docker stack rm ${configurationFound.build.container.name}`);
|
await execShellAsync(`docker stack rm ${configurationFound.build.container.name}`);
|
||||||
const deploys = await Deployment.find({ organization, branch, name, domain });
|
const deploys = await Deployment.find({ nickname });
|
||||||
for (const deploy of deploys) {
|
for (const deploy of deploys) {
|
||||||
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
|
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
|
||||||
await Deployment.deleteMany({ deployId: deploy.deployId });
|
await Deployment.deleteMany({ deployId: deploy.deployId });
|
||||||
}
|
}
|
||||||
purgeImagesAsync(configurationFound);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {}
|
||||||
organization,
|
|
||||||
name,
|
|
||||||
branch
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
@ -1,7 +1,6 @@
|
|||||||
import type { Request } from '@sveltejs/kit';
|
import type { Request } from '@sveltejs/kit';
|
||||||
export async function del(request: Request) {
|
export async function del(request: Request) {
|
||||||
request.locals.session.destroy = true;
|
request.locals.session.destroy()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
body: {
|
body: {
|
||||||
ok: true
|
ok: true
|
||||||
|
@ -10,8 +10,6 @@ export async function post(request: Request) {
|
|||||||
let { baseURL, remoteDB, database, wordpressExtraConfiguration } = request.body;
|
let { baseURL, remoteDB, database, wordpressExtraConfiguration } = request.body;
|
||||||
const traefikURL = baseURL;
|
const traefikURL = baseURL;
|
||||||
baseURL = `https://${baseURL}`;
|
baseURL = `https://${baseURL}`;
|
||||||
console.log({ baseURL, remoteDB, database, wordpressExtraConfiguration });
|
|
||||||
|
|
||||||
const workdir = '/tmp/wordpress';
|
const workdir = '/tmp/wordpress';
|
||||||
const deployId = `wp-${generator.generate({ length: 5, numbers: true, strict: true })}`;
|
const deployId = `wp-${generator.generate({ length: 5, numbers: true, strict: true })}`;
|
||||||
const defaultDatabaseName = generator.generate({ length: 12, numbers: true, strict: true });
|
const defaultDatabaseName = generator.generate({ length: 12, numbers: true, strict: true });
|
||||||
|
@ -39,6 +39,7 @@ export async function post(request: Request) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Monorepo support here. Find all configurations by id and update all deployments! Tough!
|
||||||
try {
|
try {
|
||||||
const applications = await Configuration.find({
|
const applications = await Configuration.find({
|
||||||
'repository.id': request.body.repository.id
|
'repository.id': request.body.repository.id
|
||||||
|
@ -3,18 +3,20 @@
|
|||||||
* @type {import('@sveltejs/kit').Load}
|
* @type {import('@sveltejs/kit').Load}
|
||||||
*/
|
*/
|
||||||
export async function load(session) {
|
export async function load(session) {
|
||||||
if (!browser && !process.env.VITE_GITHUB_APP_CLIENTID) {
|
if (!browser) {
|
||||||
|
if (!import.meta.env.VITE_GITHUB_APP_CLIENTID) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
redirect: '/dashboard/services'
|
redirect: '/dashboard/services'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { application, initialApplication, initConf, dashboard, prApplication } from '$store';
|
import { application, initialApplication, initConf, dashboard, prApplication, originalDomain } from '$store';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import Loading from '$components/Loading.svelte';
|
import Loading from '$components/Loading.svelte';
|
||||||
import Navbar from '$components/Application/Navbar.svelte';
|
import Navbar from '$components/Application/Navbar.svelte';
|
||||||
@ -23,17 +25,12 @@
|
|||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
import { request } from '$lib/request';
|
import { request } from '$lib/request';
|
||||||
|
|
||||||
$application.repository.organization = $page.params.organization;
|
$application.general.nickname = $page.params.nickname;
|
||||||
$application.repository.name = $page.params.name;
|
|
||||||
$application.repository.branch = $page.params.branch;
|
|
||||||
|
|
||||||
async function setConfiguration() {
|
async function setConfiguration() {
|
||||||
try {
|
try {
|
||||||
const { configuration } = await request(`/api/v1/application/config`, $session, {
|
const { configuration } = await request(`/api/v1/application/config`, $session, {
|
||||||
body: {
|
body: {
|
||||||
name: $application.repository.name,
|
nickname: $application.general.nickname
|
||||||
organization: $application.repository.organization,
|
|
||||||
branch: $application.repository.branch
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
|
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
|
||||||
@ -49,12 +46,8 @@
|
|||||||
await setConfiguration();
|
await setConfiguration();
|
||||||
} else {
|
} else {
|
||||||
const found = $dashboard.applications.deployed.find((app) => {
|
const found = $dashboard.applications.deployed.find((app) => {
|
||||||
const { organization, name, branch } = app.configuration.repository;
|
const { domain } = app.configuration.publish;
|
||||||
if (
|
if (domain === $application.publish.domain) {
|
||||||
organization === $application.repository.organization &&
|
|
||||||
name === $application.repository.name &&
|
|
||||||
branch === $application.repository.branch
|
|
||||||
) {
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -65,6 +58,8 @@
|
|||||||
await setConfiguration();
|
await setConfiguration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$originalDomain = $application.publish.domain
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$application = JSON.parse(JSON.stringify(initialApplication));
|
$application = JSON.parse(JSON.stringify(initialApplication));
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,14 @@
|
|||||||
* @type {import('@sveltejs/kit').Load}
|
* @type {import('@sveltejs/kit').Load}
|
||||||
*/
|
*/
|
||||||
export async function load(session) {
|
export async function load(session) {
|
||||||
if (!browser && !process.env.VITE_GITHUB_APP_CLIENTID) {
|
if (!browser) {
|
||||||
|
if (!import.meta.env.VITE_GITHUB_APP_CLIENTID) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
redirect: '/dashboard/services'
|
redirect: '/dashboard/services'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
initDashboard: await request('/api/v1/dashboard', session)
|
initDashboard: await request('/api/v1/dashboard', session)
|
||||||
@ -23,6 +25,7 @@
|
|||||||
import { dashboard, dateOptions, settings } from '$store';
|
import { dashboard, dateOptions, settings } from '$store';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
|
import { dashify } from '$lib/common';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -59,9 +62,7 @@
|
|||||||
<div
|
<div
|
||||||
class="relative rounded-xl p-6 bg-warmGray-800 border-2 border-dashed border-transparent hover:border-green-500 text-white shadow-md cursor-pointer ease-in-out hover:scale-105 duration-100 group"
|
class="relative rounded-xl p-6 bg-warmGray-800 border-2 border-dashed border-transparent hover:border-green-500 text-white shadow-md cursor-pointer ease-in-out hover:scale-105 duration-100 group"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
goto(
|
goto(`/application/${application.configuration.general.nickname}/configuration`);
|
||||||
`/application/${application.configuration.repository.organization}/${application.configuration.repository.name}/${application.configuration.repository.branch}/configuration`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
@ -32,6 +32,7 @@ export const dateOptions: DateTimeFormatOptions = {
|
|||||||
|
|
||||||
export const githubRepositories = writable([]);
|
export const githubRepositories = writable([]);
|
||||||
export const githubInstallations = writable<GithubInstallations>([]);
|
export const githubInstallations = writable<GithubInstallations>([]);
|
||||||
|
export const originalDomain = writable(null)
|
||||||
export const application = writable<Application>({
|
export const application = writable<Application>({
|
||||||
github: {
|
github: {
|
||||||
installation: {
|
installation: {
|
||||||
@ -81,9 +82,7 @@ export const application = writable<Application>({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
export const prApplication = writable([]);
|
export const prApplication = writable([]);
|
||||||
|
|
||||||
export const initConf = writable({});
|
export const initConf = writable({});
|
||||||
|
|
||||||
export const initialApplication: Application = {
|
export const initialApplication: Application = {
|
||||||
github: {
|
github: {
|
||||||
installation: {
|
installation: {
|
||||||
@ -181,5 +180,4 @@ export const newWordpressService = writable({
|
|||||||
},
|
},
|
||||||
wordpressExtraConfiguration: null
|
wordpressExtraConfiguration: null
|
||||||
});
|
});
|
||||||
|
|
||||||
export const isPullRequestPermissionsGranted = writable(false);
|
export const isPullRequestPermissionsGranted = writable(false);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user