* New Version: 3.0.0
This commit is contained in:
Andras Bacsai
2022-07-06 11:02:36 +02:00
committed by GitHub
parent 9137e8bc32
commit 87ba4560ad
491 changed files with 16824 additions and 20459 deletions

21
apps/ui/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,21 @@
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare namespace App {
// interface Locals {}
// interface Platform {}
// interface Session {}
interface Stuff {
service: any;
application: any;
isRunning: boolean;
appId: string;
readOnly: boolean;
source: string;
settings: string;
database: Record<string, any>;
versions: string;
privatePort: string;
}
}

12
apps/ui/src/app.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body>
<div id="svelte">%sveltekit.body%</div>
</body>
</html>

4
apps/ui/src/hooks.ts Normal file
View File

@@ -0,0 +1,4 @@
export async function handle({ event, resolve }) {
const response = await resolve(event, { ssr: false });
return response;
}

112
apps/ui/src/lib/api.ts Normal file
View File

@@ -0,0 +1,112 @@
import { browser, dev } from '$app/env';
import Cookies from 'js-cookie';
import { toast } from '@zerodevx/svelte-toast';
export function getAPIUrl() {
return dev ? 'http://localhost:3001' : 'http://localhost:3000';
}
async function send({
method,
path,
data = {},
headers,
timeout = 120000
}: {
method: string;
path: string;
data?: any;
headers?: any;
timeout?: number;
}): Promise<Record<string, unknown>> {
const token = Cookies.get('token');
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const opts: any = { method, headers: {}, body: null, signal: controller.signal };
if (Object.keys(data).length > 0) {
const parsedData = data;
for (const [key, value] of Object.entries(data)) {
if (value === '') {
parsedData[key] = null;
}
}
if (parsedData) {
opts.headers['Content-Type'] = 'application/json';
opts.body = JSON.stringify(parsedData);
}
}
if (headers) {
opts.headers = {
...opts.headers,
...headers
};
}
if (token && !path.startsWith('https://')) {
opts.headers = {
...opts.headers,
Authorization: `Bearer ${token}`
};
}
if (!path.startsWith('https://')) {
path = `/api/v1${path}`;
}
if (dev && !path.startsWith('https://')) {
path = `http://localhost:3001${path}`;
}
const response = await fetch(`${path}`, opts);
clearTimeout(id);
const contentType = response.headers.get('content-type');
let responseData = {};
if (contentType) {
if (contentType?.indexOf('application/json') !== -1) {
responseData = await response.json();
} else if (contentType?.indexOf('text/plain') !== -1) {
responseData = await response.text();
} else {
return {};
}
} else {
return {};
}
if (!response.ok) {
if (response.status === 401 && !path.startsWith('https://api.github')) {
Cookies.remove('token');
}
throw responseData;
}
return responseData;
}
export function get(path: string, headers?: Record<string, unknown>): Promise<Record<string, any>> {
return send({ method: 'GET', path, headers });
}
export function del(
path: string,
data: Record<string, unknown>,
headers?: Record<string, unknown>
): Promise<Record<string, any>> {
return send({ method: 'DELETE', path, data, headers });
}
export function post(
path: string,
data: Record<string, unknown>,
headers?: Record<string, unknown>
): Promise<Record<string, any>> {
return send({ method: 'POST', path, data, headers });
}
export function put(
path: string,
data: Record<string, unknown>,
headers?: Record<string, unknown>
): Promise<Record<string, any>> {
return send({ method: 'PUT', path, data, headers });
}

420
apps/ui/src/lib/common.ts Normal file
View File

@@ -0,0 +1,420 @@
import { toast } from '@zerodevx/svelte-toast';
export const asyncSleep = (delay: number) =>
new Promise((resolve) => setTimeout(resolve, delay));
export function errorNotification(error: any): void {
if (error.message) {
toast.push(error.message);
} else {
toast.push('Ooops, something is not okay, are you okay?');
}
console.error(JSON.stringify(error));
}
export function getDomain(domain: string) {
return domain?.replace('https://', '').replace('http://', '');
}
export function dashify(str: string, options?: any): string {
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();
}
export const dateOptions: any = {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: false
};
export const staticDeployments = [
'react',
'vuejs',
'static',
'svelte',
'gatsby',
'php',
'astro',
'eleventy'
];
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel'];
export function generateRemoteEngine(destination: any) {
return `ssh://${destination.user}@${destination.ipAddress}:${destination.port}`;
}
export function changeQueryParams(buildId: string) {
const queryParams = new URLSearchParams(window.location.search);
queryParams.set('buildId', buildId);
// @ts-ignore
return history.pushState(null, null, '?' + queryParams.toString());
}
// export const supportedDatabaseTypesAndVersions = [
// {
// name: 'mongodb',
// fancyName: 'MongoDB',
// baseImage: 'bitnami/mongodb',
// versions: ['5.0', '4.4', '4.2']
// },
// { name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0', '5.7'] },
// {
// name: 'mariadb',
// fancyName: 'MariaDB',
// baseImage: 'bitnami/mariadb',
// versions: ['10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
// },
// {
// name: 'postgresql',
// fancyName: 'PostgreSQL',
// baseImage: 'bitnami/postgresql',
// versions: ['14.2.0', '13.6.0', '12.10.0 ', '11.15.0', '10.20.0']
// },
// {
// name: 'redis',
// fancyName: 'Redis',
// baseImage: 'bitnami/redis',
// versions: ['6.2', '6.0', '5.0']
// },
// { name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
// ];
// export const supportedServiceTypesAndVersions = [
// {
// name: 'plausibleanalytics',
// fancyName: 'Plausible Analytics',
// baseImage: 'plausible/analytics',
// images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
// versions: ['latest', 'stable'],
// recommendedVersion: 'stable',
// ports: {
// main: 8000
// }
// },
// {
// name: 'nocodb',
// fancyName: 'NocoDB',
// baseImage: 'nocodb/nocodb',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 8080
// }
// },
// {
// name: 'minio',
// fancyName: 'MinIO',
// baseImage: 'minio/minio',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 9001
// }
// },
// {
// name: 'vscodeserver',
// fancyName: 'VSCode Server',
// baseImage: 'codercom/code-server',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 8080
// }
// },
// {
// name: 'wordpress',
// fancyName: 'Wordpress',
// baseImage: 'wordpress',
// images: ['bitnami/mysql:5.7'],
// versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
// recommendedVersion: 'latest',
// ports: {
// main: 80
// }
// },
// {
// name: 'vaultwarden',
// fancyName: 'Vaultwarden',
// baseImage: 'vaultwarden/server',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 80
// }
// },
// {
// name: 'languagetool',
// fancyName: 'LanguageTool',
// baseImage: 'silviof/docker-languagetool',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 8010
// }
// },
// {
// name: 'n8n',
// fancyName: 'n8n',
// baseImage: 'n8nio/n8n',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 5678
// }
// },
// {
// name: 'uptimekuma',
// fancyName: 'Uptime Kuma',
// baseImage: 'louislam/uptime-kuma',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 3001
// }
// },
// {
// name: 'ghost',
// fancyName: 'Ghost',
// baseImage: 'bitnami/ghost',
// images: ['bitnami/mariadb'],
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 2368
// }
// },
// {
// name: 'meilisearch',
// fancyName: 'Meilisearch',
// baseImage: 'getmeili/meilisearch',
// images: [],
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 7700
// }
// },
// {
// name: 'umami',
// fancyName: 'Umami',
// baseImage: 'ghcr.io/mikecao/umami',
// images: ['postgres:12-alpine'],
// versions: ['postgresql-latest'],
// recommendedVersion: 'postgresql-latest',
// ports: {
// main: 3000
// }
// },
// {
// name: 'hasura',
// fancyName: 'Hasura',
// baseImage: 'hasura/graphql-engine',
// images: ['postgres:12-alpine'],
// versions: ['latest', 'v2.5.1'],
// recommendedVersion: 'v2.5.1',
// ports: {
// main: 8080
// }
// },
// {
// name: 'fider',
// fancyName: 'Fider',
// baseImage: 'getfider/fider',
// images: ['postgres:12-alpine'],
// versions: ['stable'],
// recommendedVersion: 'stable',
// ports: {
// main: 3000
// }
// // },
// // {
// // name: 'appwrite',
// // fancyName: 'AppWrite',
// // baseImage: 'appwrite/appwrite',
// // images: ['appwrite/influxdb', 'appwrite/telegraf', 'mariadb:10.7', 'redis:6.0-alpine3.12'],
// // versions: ['latest', '0.13.0'],
// // recommendedVersion: '0.13.0',
// // ports: {
// // main: 3000
// // }
// // }
// }
// ];
export const getServiceMainPort = (service: string) => {
const serviceType = supportedServiceTypesAndVersions.find((s) => s.name === service);
if (serviceType) {
return serviceType.ports.main;
}
return null;
};
export const supportedServiceTypesAndVersions = [
{
name: 'plausibleanalytics',
fancyName: 'Plausible Analytics',
baseImage: 'plausible/analytics',
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
versions: ['latest', 'stable'],
recommendedVersion: 'stable',
ports: {
main: 8000
}
},
{
name: 'nocodb',
fancyName: 'NocoDB',
baseImage: 'nocodb/nocodb',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 8080
}
},
{
name: 'minio',
fancyName: 'MinIO',
baseImage: 'minio/minio',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 9001
}
},
{
name: 'vscodeserver',
fancyName: 'VSCode Server',
baseImage: 'codercom/code-server',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 8080
}
},
{
name: 'wordpress',
fancyName: 'Wordpress',
baseImage: 'wordpress',
images: ['bitnami/mysql:5.7'],
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
recommendedVersion: 'latest',
ports: {
main: 80
}
},
{
name: 'vaultwarden',
fancyName: 'Vaultwarden',
baseImage: 'vaultwarden/server',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 80
}
},
{
name: 'languagetool',
fancyName: 'LanguageTool',
baseImage: 'silviof/docker-languagetool',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 8010
}
},
{
name: 'n8n',
fancyName: 'n8n',
baseImage: 'n8nio/n8n',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 5678
}
},
{
name: 'uptimekuma',
fancyName: 'Uptime Kuma',
baseImage: 'louislam/uptime-kuma',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 3001
}
},
{
name: 'ghost',
fancyName: 'Ghost',
baseImage: 'bitnami/ghost',
images: ['bitnami/mariadb'],
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 2368
}
},
{
name: 'meilisearch',
fancyName: 'Meilisearch',
baseImage: 'getmeili/meilisearch',
images: [],
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 7700
}
},
{
name: 'umami',
fancyName: 'Umami',
baseImage: 'ghcr.io/mikecao/umami',
images: ['postgres:12-alpine'],
versions: ['postgresql-latest'],
recommendedVersion: 'postgresql-latest',
ports: {
main: 3000
}
},
{
name: 'hasura',
fancyName: 'Hasura',
baseImage: 'hasura/graphql-engine',
images: ['postgres:12-alpine'],
versions: ['latest', 'v2.5.1'],
recommendedVersion: 'v2.5.1',
ports: {
main: 8080
}
},
{
name: 'fider',
fancyName: 'Fider',
baseImage: 'getfider/fider',
images: ['postgres:12-alpine'],
versions: ['stable'],
recommendedVersion: 'stable',
ports: {
main: 3000
}
}
];
export function handlerNotFoundLoad(error: any, url: URL) {
if (error?.status === 404) {
return {
status: 302,
redirect: '/'
};
}
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}

View File

@@ -0,0 +1,141 @@
<script lang="ts">
import { browser } from '$app/env';
import { toast } from '@zerodevx/svelte-toast';
let showPassword = false;
export let value: string;
export let disabled = false;
export let isPasswordField = false;
export let readonly = false;
export let textarea = false;
export let required = false;
export let pattern: string|null|undefined = null;
export let id: string;
export let name: string;
export let placeholder = '';
let disabledClass = 'bg-coolback disabled:bg-coolblack';
let isHttps = browser && window.location.protocol === 'https:';
function copyToClipboard() {
if (isHttps && navigator.clipboard) {
navigator.clipboard.writeText(value);
toast.push('Copied to clipboard.');
}
}
</script>
<div class="relative">
{#if !isPasswordField || showPassword}
{#if textarea}
<textarea
rows="5"
class={disabledClass}
class:pr-10={true}
class:pr-20={value && isHttps}
{placeholder}
type="text"
{id}
{pattern}
{required}
{readonly}
{disabled}
{name}>{value}</textarea
>
{:else}
<input
class={disabledClass}
type="text"
class:pr-10={true}
class:pr-20={value && isHttps}
{id}
{name}
{required}
{pattern}
{readonly}
bind:value
{disabled}
{placeholder}
/>
{/if}
{:else}
<input
class={disabledClass}
class:pr-10={true}
class:pr-20={value && isHttps}
type="password"
{id}
{name}
{readonly}
{pattern}
{required}
bind:value
{disabled}
{placeholder}
/>
{/if}
<div class="absolute top-0 right-0 m-3 cursor-pointer text-stone-600 hover:text-white">
<div class="flex space-x-2">
{#if isPasswordField}
<div on:click={() => (showPassword = !showPassword)}>
{#if showPassword}
<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="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
/>
</svg>
{:else}
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
{/if}
</div>
{/if}
{#if value && isHttps}
<div on:click={copyToClipboard}>
<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="8" y="8" width="12" height="12" rx="2" />
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
</svg>
</div>
{/if}
</div>
</div>
</div>

View File

@@ -0,0 +1,17 @@
<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" />
<line x1="4" y1="7" x2="20" y2="7" />
<line x1="10" y1="11" x2="10" y2="17" />
<line x1="14" y1="11" x2="14" y2="17" />
<path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" />
<path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" />
</svg>

After

Width:  |  Height:  |  Size: 486 B

View File

@@ -0,0 +1,6 @@
<script lang="ts">
export let text: string;
export let customClass = 'max-w-[24rem]';
</script>
<div class="p-2 text-xs text-stone-400 {customClass}">{@html text}</div>

View File

@@ -0,0 +1,67 @@
<script lang="ts">
export let fullscreen = true;
export let cover = false;
</script>
{#if fullscreen}
<div
class="main fixed left-0 top-0 flex h-full flex-wrap content-center"
class:bg-coolblack={cover}
>
<span class="loader" />
</div>
{:else}
<div class="main h-64 py-24 left-0 top-0 flex flex-wrap content-center mx-auto">
<span class="loader" />
</div>
{/if}
<style lang="postcss">
.loader {
width: 8px;
height: 40px;
border-radius: 4px;
display: block;
margin: 20px auto;
position: relative;
background: currentColor;
color: #fff;
box-sizing: border-box;
animation: animloader 0.3s linear infinite alternate;
}
.loader::after,
.loader::before {
content: '';
width: 8px;
height: 4px;
border-radius: 4px;
background: currentColor;
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 20px;
box-sizing: border-box;
animation: animloader2 0.3s linear infinite alternate;
}
.loader::before {
left: -20px;
animation-delay: 0s;
}
@keyframes animloader {
0% {
height: 48px;
}
100% {
height: 4px;
}
}
@keyframes animloader2 {
0% {
height: 4px;
}
100% {
height: 48px;
}
}
</style>

View File

@@ -0,0 +1,45 @@
<script>
export let position = 'absolute';
</script>
<div class="lds-ripple {position} 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>

View File

@@ -0,0 +1,35 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
let timeout: NodeJS.Timeout | undefined;
const progress = tweened(0, {
duration: 2000,
easing: cubicOut
});
onMount(() => {
timeout = setTimeout(() => {
progress.set(0.7);
}, 500);
});
onDestroy(() => {
clearTimeout(timeout);
});
</script>
<div class="progress-bar">
<div class="progress-sliver" style={`--width: ${$progress * 100}%`} />
</div>
<style lang="postcss">
.progress-bar {
height: 0.2rem;
@apply fixed top-0 left-0 right-0;
}
.progress-sliver {
width: var(--width);
@apply h-full bg-coollabs;
}
</style>

View File

@@ -0,0 +1,73 @@
<script lang="ts">
import Explainer from '$lib/components/Explainer.svelte';
export let setting: any;
export let title: any;
export let description: any;
export let isCenter = true;
export let disabled = false;
export let dataTooltip: any = null;
export let loading = false;
</script>
<div class="flex items-center py-4 pr-8">
<div class="flex w-96 flex-col">
<div class="text-xs font-bold text-stone-100 md:text-base">{title}</div>
<Explainer text={description} />
</div>
</div>
<div
class:tooltip={dataTooltip}
class:text-center={isCenter}
data-tooltip={dataTooltip}
class="flex justify-center"
>
<div
type="button"
on:click
aria-pressed="false"
class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
class:opacity-50={disabled || loading}
class:bg-green-600={!loading && setting}
class:bg-stone-700={!loading && !setting}
class:bg-yellow-500={loading}
>
<span class="sr-only">Use setting</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={setting}
class:translate-x-0={!setting}
>
<span
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
class:opacity-0={setting}
class:opacity-100={!setting}
class:animate-spin={loading}
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={setting}
class:opacity-0={!setting}
class:animate-spin={loading}
>
<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>
</div>

View File

@@ -0,0 +1,39 @@
<script lang="ts">
export let trend: string;
</script>
{#if trend === 'up'}
<span class="-mt-1 inline-flex px-2 text-green-500">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
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" />
<line x1="17" y1="7" x2="7" y2="17" />
<polyline points="8 7 17 7 17 16" />
</svg></span
>
{:else if trend === 'down'}
<span class="text-red-500 px-2 inline-flex -mt-1">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-8 h-8"
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" />
<line x1="7" y1="7" x2="17" y2="17" />
<polyline points="17 8 17 17 8 17" />
</svg>
</span>
{/if}

View File

@@ -0,0 +1,183 @@
<script lang="ts">
import { dev } from '$app/env';
import { get, post } from '$lib/api';
import { appSession, features } from '$lib/store';
import { toast } from '@zerodevx/svelte-toast';
import { asyncSleep, errorNotification } from '$lib/common';
import { onMount } from 'svelte';
let isUpdateAvailable = false;
let updateStatus: any = {
found: false,
loading: false,
success: null
};
let latestVersion = 'latest';
async function update() {
updateStatus.loading = true;
try {
if (dev) {
console.log(`updating to ${latestVersion}`);
await asyncSleep(4000);
return window.location.reload();
} else {
await post(`/update`, { type: 'update', latestVersion });
toast.push('Update completed.<br><br>Waiting for the new version to start...');
let reachable = false;
let tries = 0;
do {
await asyncSleep(4000);
try {
await get(`/undead`);
reachable = true;
} catch (error) {
reachable = false;
}
if (reachable) break;
tries++;
} while (!reachable || tries < 120);
toast.push('New version reachable. Reloading...');
updateStatus.loading = false;
updateStatus.success = true;
await asyncSleep(3000);
return window.location.reload();
}
} catch (error) {
updateStatus.success = false;
updateStatus.loading = false;
return errorNotification(error);
}
}
onMount(async () => {
if ($appSession.userId) {
const overrideVersion = $features.latestVersion;
if ($appSession.teamId === '0') {
try {
const data = await get(`/update`);
if (overrideVersion || data?.isUpdateAvailable) {
latestVersion = overrideVersion || data.latestVersion;
if (overrideVersion) {
isUpdateAvailable = true;
} else {
isUpdateAvailable = data.isUpdateAvailable;
}
}
} catch (error) {
return errorNotification(error);
}
}
}
});
</script>
<div class="flex flex-col space-y-4 py-2">
{#if $appSession.teamId === '0'}
{#if isUpdateAvailable}
<button
disabled={updateStatus.success === false}
title="Update available"
on:click={update}
class="icons tooltip-right bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
>
{#if updateStatus.loading}
<svg
xmlns="http://www.w3.org/2000/svg"
class="lds-heart h-9 w-8"
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="M19.5 13.572l-7.5 7.428l-7.5 -7.428m0 0a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572"
/>
</svg>
{:else if updateStatus.success === null}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-9 w-8"
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="12" y1="8" x2="8" y2="12" />
<line x1="12" y1="8" x2="12" y2="16" />
<line x1="16" y1="12" x2="12" y2="8" />
</svg>
{:else if updateStatus.success}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-9 w-8"
><path
fill="#DD2E44"
d="M11.626 7.488c-.112.112-.197.247-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269 1.562-1.562-.971-6.627-5.656-11.313-4.687-4.686-9.752-7.218-11.315-5.656z"
/><path
fill="#EA596E"
d="M13 12L.416 32.506l-.282.635.011.011c-.208.403.14 1.223.853 1.937.232.232.473.408.709.557L17 17l-4-5z"
/><path
fill="#A0041E"
d="M23.012 13.066c4.67 4.672 7.263 9.652 5.789 11.124-1.473 1.474-6.453-1.118-11.126-5.788-4.671-4.672-7.263-9.654-5.79-11.127 1.474-1.473 6.454 1.119 11.127 5.791z"
/><path
fill="#AA8DD8"
d="M18.59 13.609c-.199.161-.459.245-.734.215-.868-.094-1.598-.396-2.109-.873-.541-.505-.808-1.183-.735-1.862.128-1.192 1.324-2.286 3.363-2.066.793.085 1.147-.17 1.159-.292.014-.121-.277-.446-1.07-.532-.868-.094-1.598-.396-2.11-.873-.541-.505-.809-1.183-.735-1.862.13-1.192 1.325-2.286 3.362-2.065.578.062.883-.057 1.012-.134.103-.063.144-.123.148-.158.012-.121-.275-.446-1.07-.532-.549-.06-.947-.552-.886-1.102.059-.549.55-.946 1.101-.886 2.037.219 2.973 1.542 2.844 2.735-.13 1.194-1.325 2.286-3.364 2.067-.578-.063-.88.057-1.01.134-.103.062-.145.123-.149.157-.013.122.276.446 1.071.532 2.037.22 2.973 1.542 2.844 2.735-.129 1.192-1.324 2.286-3.362 2.065-.578-.062-.882.058-1.012.134-.104.064-.144.124-.148.158-.013.121.276.446 1.07.532.548.06.947.553.886 1.102-.028.274-.167.511-.366.671z"
/><path
fill="#77B255"
d="M30.661 22.857c1.973-.557 3.334.323 3.658 1.478.324 1.154-.378 2.615-2.35 3.17-.77.216-1.001.584-.97.701.034.118.425.312 1.193.095 1.972-.555 3.333.325 3.657 1.479.326 1.155-.378 2.614-2.351 3.17-.769.216-1.001.585-.967.702.033.117.423.311 1.192.095.53-.149 1.084.16 1.233.691.148.532-.161 1.084-.693 1.234-1.971.555-3.333-.323-3.659-1.479-.324-1.154.379-2.613 2.353-3.169.77-.217 1.001-.584.967-.702-.032-.117-.422-.312-1.19-.096-1.974.556-3.334-.322-3.659-1.479-.325-1.154.378-2.613 2.351-3.17.768-.215.999-.585.967-.701-.034-.118-.423-.312-1.192-.096-.532.15-1.083-.16-1.233-.691-.149-.53.161-1.082.693-1.232z"
/><path
fill="#AA8DD8"
d="M23.001 20.16c-.294 0-.584-.129-.782-.375-.345-.432-.274-1.061.156-1.406.218-.175 5.418-4.259 12.767-3.208.547.078.927.584.849 1.131-.078.546-.58.93-1.132.848-6.493-.922-11.187 2.754-11.233 2.791-.186.148-.406.219-.625.219z"
/><path
fill="#77B255"
d="M5.754 16c-.095 0-.192-.014-.288-.042-.529-.159-.829-.716-.67-1.245 1.133-3.773 2.16-9.794.898-11.364-.141-.178-.354-.353-.842-.316-.938.072-.849 2.051-.848 2.071.042.551-.372 1.031-.922 1.072-.559.034-1.031-.372-1.072-.923-.103-1.379.326-4.035 2.692-4.214 1.056-.08 1.933.287 2.552 1.057 2.371 2.951-.036 11.506-.542 13.192-.13.433-.528.712-.958.712z"
/><circle fill="#5C913B" cx="25.5" cy="9.5" r="1.5" /><circle
fill="#9266CC"
cx="2"
cy="18"
r="2"
/><circle fill="#5C913B" cx="32.5" cy="19.5" r="1.5" /><circle
fill="#5C913B"
cx="23.5"
cy="31.5"
r="1.5"
/><circle fill="#FFCC4D" cx="28" cy="4" r="2" /><circle
fill="#FFCC4D"
cx="32.5"
cy="8.5"
r="1.5"
/><circle fill="#FFCC4D" cx="29.5" cy="12.5" r="1.5" /><circle
fill="#FFCC4D"
cx="7.5"
cy="23.5"
r="1.5"
/></svg
>
{:else}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-9 w-8"
><path
fill="#FFCC4D"
d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18"
/><path
fill="#664500"
d="M22 27c0 2.763-1.791 3-4 3-2.21 0-4-.237-4-3 0-2.761 1.79-6 4-6 2.209 0 4 3.239 4 6zm8-12c-.124 0-.25-.023-.371-.072-5.229-2.091-7.372-5.241-7.461-5.374-.307-.46-.183-1.081.277-1.387.459-.306 1.077-.184 1.385.274.019.027 1.93 2.785 6.541 4.629.513.206.763.787.558 1.3-.157.392-.533.63-.929.63zM6 15c-.397 0-.772-.238-.929-.629-.205-.513.044-1.095.557-1.3 4.612-1.844 6.523-4.602 6.542-4.629.308-.456.929-.577 1.387-.27.457.308.581.925.275 1.383-.089.133-2.232 3.283-7.46 5.374C6.25 14.977 6.124 15 6 15z"
/><path fill="#5DADEC" d="M24 16h4v19l-4-.046V16zM8 35l4-.046V16H8v19z" /><path
fill="#664500"
d="M14.999 18c-.15 0-.303-.034-.446-.105-3.512-1.756-7.07-.018-7.105 0-.495.249-1.095.046-1.342-.447-.247-.494-.047-1.095.447-1.342.182-.09 4.498-2.197 8.895 0 .494.247.694.848.447 1.342-.176.35-.529.552-.896.552zm14 0c-.15 0-.303-.034-.446-.105-3.513-1.756-7.07-.018-7.105 0-.494.248-1.094.047-1.342-.447-.247-.494-.047-1.095.447-1.342.182-.09 4.501-2.196 8.895 0 .494.247.694.848.447 1.342-.176.35-.529.552-.896.552z"
/><ellipse fill="#5DADEC" cx="18" cy="34" rx="18" ry="2" /><ellipse
fill="#E75A70"
cx="18"
cy="27"
rx="3"
ry="2"
/></svg
>
{/if}
</button>
{/if}
{/if}
</div>

View File

@@ -0,0 +1,147 @@
<script lang="ts">
let usage = {
cpu: {
load: [0, 0, 0],
count: 0,
usage: 0
},
memory: {
totalMemMb: 0,
freeMemMb: 0,
usedMemMb: 0,
freeMemPercentage: 0
},
disk: {
freePercentage: 0,
totalGb: 0,
usedGb: 0
}
};
let usageInterval: any;
let loading = {
usage: false
};
import { appSession } from '$lib/store';
import { onDestroy, onMount } from 'svelte';
import { get } from '$lib/api';
import { errorNotification } from '$lib/common';
import Trend from './Trend.svelte';
async function getStatus() {
if (loading.usage) return;
loading.usage = true;
const data = await get('/usage');
usage = data.usage;
loading.usage = false;
}
onDestroy(() => {
clearInterval(usageInterval);
});
onMount(async () => {
try {
if ($appSession.teamId === '0') {
await getStatus();
usageInterval = setInterval(async () => {
await getStatus();
}, 1000);
}
} catch (error) {
return errorNotification(error);
}
});
let warning = {
memory: false,
cpu: false,
disk: false
};
let trends = {
memory: 'stable',
cpu: 'stable',
disk: 'stable'
};
</script>
{#if $appSession.teamId === '0'}
<div class="px-6 text-2xl font-bold">Server Usage</div>
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class="truncate text-sm font-medium text-white">Total Memory</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{(usage?.memory.totalMemMb).toFixed(0)}<span class="text-sm">MB</span>
</dd>
</div>
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class="truncate text-sm font-medium text-white">Used Memory</dt>
<dd class="mt-1 text-3xl font-semibold text-white ">
{(usage?.memory.usedMemMb).toFixed(0)}<span class="text-sm">MB</span>
</dd>
</div>
<div
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
class:bg-red-500={warning.memory}
>
<dt class="truncate text-sm font-medium text-white">Free Memory</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{usage?.memory.freeMemPercentage}<span class="text-sm">%</span>
{#if !warning.memory}
<Trend trend={trends.memory} />
{/if}
</dd>
</div>
</dl>
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class="truncate text-sm font-medium text-white">Total CPUs</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{usage?.cpu.count}
</dd>
</div>
<div
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
class:bg-red-500={warning.cpu}
>
<dt class="truncate text-sm font-medium text-white">CPU Usage</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{usage?.cpu.usage}<span class="text-sm">%</span>
{#if !warning.cpu}
<Trend trend={trends.cpu} />
{/if}
</dd>
</div>
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class="truncate text-sm font-medium text-white">Load Average (5/10/30mins)</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{usage?.cpu.load.join('/')}
</dd>
</div>
</dl>
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class="truncate text-sm font-medium text-white">Total Disk</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{usage?.disk.totalGb}<span class="text-sm">GB</span>
</dd>
</div>
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class="truncate text-sm font-medium text-white">Used Disk</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{usage?.disk.usedGb}<span class="text-sm">GB</span>
</dd>
</div>
<div
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
class:bg-red-500={warning.disk}
>
<dt class="truncate text-sm font-medium text-white">Free Disk</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{usage?.disk.freePercentage}<span class="text-sm">%</span>
{#if !warning.disk}
<Trend trend={trends.disk} />
{/if}
</dd>
</div>
</dl>
<div class="px-6 pt-20 text-2xl font-bold">Resources</div>
{/if}

View File

@@ -0,0 +1,21 @@
<svg
class="absolute top-0 left-0 -m-6 h-14 w-14"
viewBox="0 0 256 256"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
id="a"
fill="#302649"
fill-rule="evenodd"
clip-rule="evenodd"
d="M163.008 18.929c1.944 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53l-28.198-95.29a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.225 180.225 0 00-52.01 17.557l43.52-142.281c1.99-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.086 1.157a16.004 16.004 0 016.487 4.806z"
/>
<path
id="flame"
fill="#EF661E"
fill-rule="evenodd"
clip-rule="evenodd"
d="M168.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.349-11.349 10.743 0 10.731 9.373 10.721 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1,73 @@
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-8 h-16 w-16">
<g
><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#3A4D54"
d="M73.8 50.8h11.3v11.5h5.7c2.6 0 5.3-.5 7.8-1.3 1.2-.4 2.6-1 3.8-1.7-1.6-2.1-2.4-4.7-2.6-7.3-.3-3.5.4-8.1 2.8-10.8l1.2-1.4 1.4 1.1c3.6 2.9 6.5 6.8 7.1 11.4 4.3-1.3 9.3-1 13.1 1.2l1.5.9-.8 1.6c-3.2 6.2-9.9 8.2-16.4 7.8-9.8 24.3-31 35.8-56.8 35.8-13.3 0-25.5-5-32.5-16.8l-.1-.2-1-2.1c-2.4-5.2-3.1-10.9-2.6-16.6l.2-1.7h9.6v-11.4h11.3v-11.2h22.5v-11.3h13.5v22.5z"
/><path
fill="#00AADA"
d="M110.4 55.1c.8-5.9-3.6-10.5-6.4-12.7-3.1 3.6-3.6 13.2 1.3 17.2-2.8 2.4-8.5 4.7-14.5 4.7h-72.2c-.6 6.2.5 11.9 3 16.8l.8 1.5c.5.9 1.1 1.7 1.7 2.6 3 .2 5.7.3 8.2.2 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5 1.1-8.3 1.3h-.6000000000000001c-1.3.1-2.7.1-4.2.1-1.6 0-3.1 0-4.9-.1 6 6.8 15.4 10.8 27.2 10.8 25 0 46.2-11.1 55.5-35.9 6.7.7 13.1-1 16-6.7-4.5-2.7-10.5-1.8-13.9-.1z"
/><path
fill="#28B8EB"
d="M110.4 55.1c.8-5.9-3.6-10.5-6.4-12.7-3.1 3.6-3.6 13.2 1.3 17.2-2.8 2.4-8.5 4.7-14.5 4.7h-68c-.3 9.5 3.2 16.7 9.5 21 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.5 1.4l-.1-.1c8.5 4.4 20.8 4.3 35-1.1 15.8-6.1 30.6-17.7 40.9-30.9-.2.1-.4.1-.5.2z"
/><path
fill="#028BB8"
d="M18.7 71.8c.4 3.3 1.4 6.4 2.9 9.3l.8 1.5c.5.9 1.1 1.7 1.7 2.6 3 .2 5.7.3 8.2.2 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.5 1.4h-.4c-1.3.1-2.7.1-4.1.1-1.6 0-3.2 0-4.9-.1 6 6.8 15.5 10.8 27.3 10.8 21.4 0 40-8.1 50.8-26h-85.1v-.1z"
/><path
fill="#019BC6"
d="M23.5 71.8c1.3 5.8 4.3 10.4 8.8 13.5 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.6 1.4 8.5 4.4 20.8 4.3 34.9-1.1 8.5-3.3 16.8-8.2 24.2-14.1h-70.6z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#00ACD3"
d="M28.4 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zM39.6 41.5h9.8v9.8h-9.8v-9.8zm.9.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#23C2EE"
d="M39.6 52.7h9.8v9.8h-9.8v-9.8zm.9.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#00ACD3"
d="M50.9 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#23C2EE"
d="M50.9 41.5h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zM62.2 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#00ACD3"
d="M62.2 41.5h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#23C2EE"
d="M62.2 30.2h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#00ACD3"
d="M73.5 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#D4EEF1"
d="M48.8 78.3c1.5 0 2.7 1.2 2.7 2.7 0 1.5-1.2 2.7-2.7 2.7-1.5 0-2.7-1.2-2.7-2.7 0-1.5 1.2-2.7 2.7-2.7"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#3A4D54"
d="M48.8 79.1c.2 0 .5 0 .7.1-.2.1-.4.4-.4.7 0 .4.4.8.8.8.3 0 .6-.2.7-.4.1.2.1.5.1.7 0 1.1-.9 1.9-1.9 1.9-1.1 0-1.9-.9-1.9-1.9 0-1 .8-1.9 1.9-1.9M1.1 72.8h125.4c-2.7-.7-8.6-1.6-7.7-5.2-5 5.7-16.9 4-20 1.2-3.4 4.9-23 3-24.3-.8-4.2 5-17.3 5-21.5 0-1.4 3.8-21 5.7-24.3.8-3 2.8-15 4.5-20-1.2 1.1 3.5-4.9 4.5-7.6 5.2"
/><path
fill="#BFDBE0"
d="M56 97.8c-6.7-3.2-10.3-7.5-12.4-12.2-2.5.7-5.5 1.2-8.9 1.4-1.3.1-2.7.1-4.1.1-1.7 0-3.4 0-5.2-.1 6 6 13.6 10.7 27.5 10.8h3.1z"
/><path
fill="#D4EEF1"
d="M46.1 89.9c-.9-1.3-1.8-2.8-2.5-4.3-2.5.7-5.5 1.2-8.9 1.4 2.3 1.2 5.7 2.4 11.4 2.9z"
/></g
>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,6 @@
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-8 h-16 w-16">
<path fill="transparent" d="M18 0h92v128H18z" /><path
d="M55.3 36.3h.4c1.1 0 1.5.9 1.5 2.3v41.8c0 1.8-.4 3-1.6 3l-4.8-.1c-1.2 0-1.6-1-1.6-3V45.5l-2.1.5c-1 0-1.5-.8-1.5-2.2v-3c0-1.2.4-2 1.4-2.2l8.3-2.2zm16 36.1l.1 3 .6 1.3.6.6.8.1h2.2c1 0 1.7.8 1.7 2v1.9c0 1.2-.6 2-1.8 2h-3.2l-2.3-.1c-.7-.2-1.4-.5-2.2-1a5.7 5.7 0 01-2-1.9c-.4-.8-.8-1.9-1-3.2-.4-1.4-.5-3-.5-4.8v-16h-1.5c-1.1 0-1.6-1-1.6-2.4v-1.7c0-1.4.5-2.3 1.6-2.3h1.5v-.1l.6-12.3c0-1.5.5-2.5 1.6-2.5h3.1c1.2 0 1.6 1 1.6 2.5v12.3h3.6c1.1 0 1.6 1 1.6 2.4V54c0 1.4-.5 2.3-1.6 2.3h-3.6v16.2zm9.4 15.7c0-2 .3-3.2 1.5-3.2.2 0 .4 0 1.4.4l1.1.3.2-.1.4-.7c.3-.6.4-1.6.4-3l-.6-3.3L79 52.7v-.9c0-1.2.3-2 1.2-2H84c.5 0 1 .3 1.3.6.3.4.5.9.6 1.6l3.4 18 2.6-17.8c.1-.8.3-1.3.6-1.7.3-.4.8-.6 1.3-.6h2.6c1 0 1.4.8 1.4 2l-.1.8L92 82.2c-.5 2.5-1 4.4-1.5 5.7a6.6 6.6 0 01-2 3c-.9.6-1.9.9-3.1.9h-.3c-2 0-3.3-.6-4.1-1.7-.3-.4-.4-1-.4-2zM31.3 38.8l8.2-2.1h.5c1 0 1.4.8 1.4 2.2v41.9c0 1.8-.4 2.9-1.6 2.9h-4.7c-1.2 0-1.6-1.1-1.6-3v-35l-2 .6c-1.2 0-1.6-.9-1.6-2.2v-3c0-1.2.4-2 1.4-2.3z"
fill="#FFF"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,6 @@
<svg class="absolute top-0 left-0 -m-4 h-10 w-10" viewBox="0 0 128 128">
<path
fill="#64328B"
d="M64,0C28.7,0,0,28.7,0,64v0c0,35.3,28.7,64,64,64s64-28.7,64-64v0C128,28.7,99.3,0,64,0z M13.2,64L64,114.8 C35.9,114.8,13.2,92.1,13.2,64z M75.4,113.5l-60.9-61C19.7,30,39.9,13.2,64,13.2c16.6,0,31.3,7.9,40.5,20.2l-7.5,7.2 C89.7,30.2,77.7,23.5,64,23.5c-17.6,0-32.5,11.2-38.1,26.8C33.1,57,75.4,98.8,78.1,102c12.7-4.7,22.3-15.5,25.4-28.9H81.9v-9.4 l33,0.2C114.8,88.2,98,108.4,75.4,113.5z"
/>
</svg>

After

Width:  |  Height:  |  Size: 494 B

View File

@@ -0,0 +1,10 @@
<svg
class="absolute top-0 left-0 -m-4 h-10 w-10"
viewBox="0 0 50 52"
xmlns="http://www.w3.org/2000/svg"
><title>Logomark</title><path
d="M49.626 11.564a.809.809 0 0 1 .028.209v10.972a.8.8 0 0 1-.402.694l-9.209 5.302V39.25c0 .286-.152.55-.4.694L20.42 51.01c-.044.025-.092.041-.14.058-.018.006-.035.017-.054.022a.805.805 0 0 1-.41 0c-.022-.006-.042-.018-.063-.026-.044-.016-.09-.03-.132-.054L.402 39.944A.801.801 0 0 1 0 39.25V6.334c0-.072.01-.142.028-.21.006-.023.02-.044.028-.067.015-.042.029-.085.051-.124.015-.026.037-.047.055-.071.023-.032.044-.065.071-.093.023-.023.053-.04.079-.06.029-.024.055-.05.088-.069h.001l9.61-5.533a.802.802 0 0 1 .8 0l9.61 5.533h.002c.032.02.059.045.088.068.026.02.055.038.078.06.028.029.048.062.072.094.017.024.04.045.054.071.023.04.036.082.052.124.008.023.022.044.028.068a.809.809 0 0 1 .028.209v20.559l8.008-4.611v-10.51c0-.07.01-.141.028-.208.007-.024.02-.045.028-.068.016-.042.03-.085.052-.124.015-.026.037-.047.054-.071.024-.032.044-.065.072-.093.023-.023.052-.04.078-.06.03-.024.056-.05.088-.069h.001l9.611-5.533a.801.801 0 0 1 .8 0l9.61 5.533c.034.02.06.045.09.068.025.02.054.038.077.06.028.029.048.062.072.094.018.024.04.045.054.071.023.039.036.082.052.124.009.023.022.044.028.068zm-1.574 10.718v-9.124l-3.363 1.936-4.646 2.675v9.124l8.01-4.611zm-9.61 16.505v-9.13l-4.57 2.61-13.05 7.448v9.216l17.62-10.144zM1.602 7.719v31.068L19.22 48.93v-9.214l-9.204-5.209-.003-.002-.004-.002c-.031-.018-.057-.044-.086-.066-.025-.02-.054-.036-.076-.058l-.002-.003c-.026-.025-.044-.056-.066-.084-.02-.027-.044-.05-.06-.078l-.001-.003c-.018-.03-.029-.066-.042-.1-.013-.03-.03-.058-.038-.09v-.001c-.01-.038-.012-.078-.016-.117-.004-.03-.012-.06-.012-.09v-.002-21.481L4.965 9.654 1.602 7.72zm8.81-5.994L2.405 6.334l8.005 4.609 8.006-4.61-8.006-4.608zm4.164 28.764l4.645-2.674V7.719l-3.363 1.936-4.646 2.675v20.096l3.364-1.937zM39.243 7.164l-8.006 4.609 8.006 4.609 8.005-4.61-8.005-4.608zm-.801 10.605l-4.646-2.675-3.363-1.936v9.124l4.645 2.674 3.364 1.937v-9.124zM20.02 38.33l11.743-6.704 5.87-3.35-8-4.606-9.211 5.303-8.395 4.833 7.993 4.524z"
fill="#FF2D20"
fill-rule="evenodd"
/></svg
>

After

Width:  |  Height:  |  Size: 2.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1,5 @@
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 fill-current text-blue-500" viewBox="0 0 128 128">
<path
d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64c11.2 0 21.7-2.9 30.8-7.9L48.4 55.3v36.6h-6.8V41.8h6.8l50.5 75.8C116.4 106.2 128 86.5 128 64c0-35.3-28.7-64-64-64zm22.1 84.6l-7.5-11.3V41.8h7.5v42.8z"
/>
</svg>

After

Width:  |  Height:  |  Size: 312 B

View File

@@ -0,0 +1,14 @@
<svg
class="absolute top-0 left-0 -m-4 h-10 w-10 text-green-500"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
focusable="false"
data-prefix="fab"
data-icon="node-js"
role="img"
viewBox="0 0 448 512"
><path
fill="currentColor"
d="M224 508c-6.7 0-13.5-1.8-19.4-5.2l-61.7-36.5c-9.2-5.2-4.7-7-1.7-8 12.3-4.3 14.8-5.2 27.9-12.7 1.4-.8 3.2-.5 4.6.4l47.4 28.1c1.7 1 4.1 1 5.7 0l184.7-106.6c1.7-1 2.8-3 2.8-5V149.3c0-2.1-1.1-4-2.9-5.1L226.8 37.7c-1.7-1-4-1-5.7 0L36.6 144.3c-1.8 1-2.9 3-2.9 5.1v213.1c0 2 1.1 4 2.9 4.9l50.6 29.2c27.5 13.7 44.3-2.4 44.3-18.7V167.5c0-3 2.4-5.3 5.4-5.3h23.4c2.9 0 5.4 2.3 5.4 5.3V378c0 36.6-20 57.6-54.7 57.6-10.7 0-19.1 0-42.5-11.6l-48.4-27.9C8.1 389.2.7 376.3.7 362.4V149.3c0-13.8 7.4-26.8 19.4-33.7L204.6 9c11.7-6.6 27.2-6.6 38.8 0l184.7 106.7c12 6.9 19.4 19.8 19.4 33.7v213.1c0 13.8-7.4 26.7-19.4 33.7L243.4 502.8c-5.9 3.4-12.6 5.2-19.4 5.2zm149.1-210.1c0-39.9-27-50.5-83.7-58-57.4-7.6-63.2-11.5-63.2-24.9 0-11.1 4.9-25.9 47.4-25.9 37.9 0 51.9 8.2 57.7 33.8.5 2.4 2.7 4.2 5.2 4.2h24c1.5 0 2.9-.6 3.9-1.7s1.5-2.6 1.4-4.1c-3.7-44.1-33-64.6-92.2-64.6-52.7 0-84.1 22.2-84.1 59.5 0 40.4 31.3 51.6 81.8 56.6 60.5 5.9 65.2 14.8 65.2 26.7 0 20.6-16.6 29.4-55.5 29.4-48.9 0-59.6-12.3-63.2-36.6-.4-2.6-2.6-4.5-5.3-4.5h-23.9c-3 0-5.3 2.4-5.3 5.3 0 31.1 16.9 68.2 97.8 68.2 58.4-.1 92-23.2 92-63.4z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,20 @@
<svg
class="absolute top-0 left-0 -m-4 h-10 w-10"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 400 400"
>
<g fill-rule="nonzero" transform="translate(0 50)" fill="none">
<path
d="M227.92099 83.45116l-13.6889 24.10141-46.8148-82.44693L23.7037 278.17052h97.3037c0 13.31084 10.61252 24.10142 23.70371 24.10142H23.70371c-8.46771 0-16.29145-4.59601-20.5246-12.05272-4.23315-7.4567-4.23272-16.64312.00114-24.0994L146.89383 13.05492c4.23415-7.45738 12.0596-12.05138 20.5284-12.05138 8.46878 0 16.29423 4.594 20.52839 12.05138l39.97037 70.39623z"
fill="#00C58E"
/>
<path
d="M331.6642 266.11981l-90.05432-158.56724-13.6889-24.10141-13.68888 24.10141-90.04445 158.56724c-4.23385 7.45629-4.23428 16.64271-.00113 24.09941 4.23314 7.4567 12.05689 12.05272 20.5246 12.05272h166.4c8.46946 0 16.29644-4.591 20.532-12.04837 4.23555-7.45736 4.23606-16.64592.00132-24.10376h.01976zM144.7111 278.17052L227.921 131.65399l83.19012 146.51653h-166.4z"
fill="#FFF"
/>
<path
d="M396.04938 290.22123c-4.23344 7.45557-12.05656 12.0507-20.52345 12.0507H311.1111c13.0912 0 23.7037-10.79057 23.7037-24.10141h40.66173L260.09877 74.98553l-18.4889 32.56704L227.921 83.45116l11.65432-20.51634c4.23416-7.45738 12.0596-12.05138 20.5284-12.05138 8.46879 0 16.29423 4.594 20.52839 12.05138l115.41728 203.185c4.23426 7.457 4.23426 16.6444 0 24.1014z"
fill="#108775"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,6 @@
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-6 h-14 w-14 text-white">
<path
fill="#6181B6"
d="M64 33.039c-33.74 0-61.094 13.862-61.094 30.961s27.354 30.961 61.094 30.961 61.094-13.862 61.094-30.961-27.354-30.961-61.094-30.961zm-15.897 36.993c-1.458 1.364-3.077 1.927-4.86 2.507-1.783.581-4.052.461-6.811.461h-6.253l-1.733 10h-7.301l6.515-34h14.04c4.224 0 7.305 1.215 9.242 3.432 1.937 2.217 2.519 5.364 1.747 9.337-.319 1.637-.856 3.159-1.614 4.515-.759 1.357-1.75 2.624-2.972 3.748zm21.311 2.968l2.881-14.42c.328-1.688.208-2.942-.361-3.555-.57-.614-1.782-1.025-3.635-1.025h-5.79l-3.731 19h-7.244l6.515-33h7.244l-1.732 9h6.453c4.061 0 6.861.815 8.402 2.231s2.003 3.356 1.387 6.528l-3.031 15.241h-7.358zm40.259-11.178c-.318 1.637-.856 3.133-1.613 4.488-.758 1.357-1.748 2.598-2.971 3.722-1.458 1.364-3.078 1.927-4.86 2.507-1.782.581-4.053.461-6.812.461h-6.253l-1.732 10h-7.301l6.514-34h14.041c4.224 0 7.305 1.215 9.241 3.432 1.935 2.217 2.518 5.418 1.746 9.39zM95.919 54h-5.001l-2.727 14h4.442c2.942 0 5.136-.29 6.576-1.4 1.442-1.108 2.413-2.828 2.918-5.421.484-2.491.264-4.434-.66-5.458-.925-1.024-2.774-1.721-5.548-1.721zM38.934 54h-5.002l-2.727 14h4.441c2.943 0 5.136-.29 6.577-1.4 1.441-1.108 2.413-2.828 2.917-5.421.484-2.491.264-4.434-.66-5.458s-2.772-1.721-5.546-1.721z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,50 @@
<svg class="absolute top-0 left-0 -m-6 h-14 w-14" viewBox="0 0 128 128">
<linearGradient
id="a"
gradientUnits="userSpaceOnUse"
x1="70.252"
y1="1237.476"
x2="170.659"
y2="1151.089"
gradientTransform="matrix(.563 0 0 -.568 -29.215 707.817)"
><stop offset="0" stop-color="#5A9FD4" /><stop
offset="1"
stop-color="#306998"
/></linearGradient
><path
fill="url(#a)"
d="M63.391 1.988c-4.222.02-8.252.379-11.8 1.007-10.45 1.846-12.346 5.71-12.346 12.837v9.411h24.693v3.137h-33.961c-7.176 0-13.46 4.313-15.426 12.521-2.268 9.405-2.368 15.275 0 25.096 1.755 7.311 5.947 12.519 13.124 12.519h8.491v-11.282c0-8.151 7.051-15.34 15.426-15.34h24.665c6.866 0 12.346-5.654 12.346-12.548v-23.513c0-6.693-5.646-11.72-12.346-12.837-4.244-.706-8.645-1.027-12.866-1.008zm-13.354 7.569c2.55 0 4.634 2.117 4.634 4.721 0 2.593-2.083 4.69-4.634 4.69-2.56 0-4.633-2.097-4.633-4.69-.001-2.604 2.073-4.721 4.633-4.721z"
/><linearGradient
id="b"
gradientUnits="userSpaceOnUse"
x1="209.474"
y1="1098.811"
x2="173.62"
y2="1149.537"
gradientTransform="matrix(.563 0 0 -.568 -29.215 707.817)"
><stop offset="0" stop-color="#FFD43B" /><stop
offset="1"
stop-color="#FFE873"
/></linearGradient
><path
fill="url(#b)"
d="M91.682 28.38v10.966c0 8.5-7.208 15.655-15.426 15.655h-24.665c-6.756 0-12.346 5.783-12.346 12.549v23.515c0 6.691 5.818 10.628 12.346 12.547 7.816 2.297 15.312 2.713 24.665 0 6.216-1.801 12.346-5.423 12.346-12.547v-9.412h-24.664v-3.138h37.012c7.176 0 9.852-5.005 12.348-12.519 2.578-7.735 2.467-15.174 0-25.096-1.774-7.145-5.161-12.521-12.348-12.521h-9.268zm-13.873 59.547c2.561 0 4.634 2.097 4.634 4.692 0 2.602-2.074 4.719-4.634 4.719-2.55 0-4.633-2.117-4.633-4.719 0-2.595 2.083-4.692 4.633-4.692z"
/><radialGradient
id="c"
cx="1825.678"
cy="444.45"
r="26.743"
gradientTransform="matrix(0 -.24 -1.055 0 532.979 557.576)"
gradientUnits="userSpaceOnUse"
><stop offset="0" stop-color="#B8B8B8" stop-opacity=".498" /><stop
offset="1"
stop-color="#7F7F7F"
stop-opacity="0"
/></radialGradient
><path
opacity=".444"
fill="url(#c)"
enable-background="new"
d="M97.309 119.597c0 3.543-14.816 6.416-33.091 6.416-18.276 0-33.092-2.873-33.092-6.416 0-3.544 14.815-6.417 33.092-6.417 18.275 0 33.091 2.872 33.091 6.417z"
/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,7 @@
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 text-blue-500" viewBox="0 0 128 128">
<g fill="#61DAFB"
><circle cx="64" cy="64" r="11.4" /><path
d="M107.3 45.2c-2.2-.8-4.5-1.6-6.9-2.3.6-2.4 1.1-4.8 1.5-7.1 2.1-13.2-.2-22.5-6.6-26.1-1.9-1.1-4-1.6-6.4-1.6-7 0-15.9 5.2-24.9 13.9-9-8.7-17.9-13.9-24.9-13.9-2.4 0-4.5.5-6.4 1.6-6.4 3.7-8.7 13-6.6 26.1.4 2.3.9 4.7 1.5 7.1-2.4.7-4.7 1.4-6.9 2.3-12.5 4.8-19.3 11.4-19.3 18.8s6.9 14 19.3 18.8c2.2.8 4.5 1.6 6.9 2.3-.6 2.4-1.1 4.8-1.5 7.1-2.1 13.2.2 22.5 6.6 26.1 1.9 1.1 4 1.6 6.4 1.6 7.1 0 16-5.2 24.9-13.9 9 8.7 17.9 13.9 24.9 13.9 2.4 0 4.5-.5 6.4-1.6 6.4-3.7 8.7-13 6.6-26.1-.4-2.3-.9-4.7-1.5-7.1 2.4-.7 4.7-1.4 6.9-2.3 12.5-4.8 19.3-11.4 19.3-18.8s-6.8-14-19.3-18.8zm-14.8-30.5c4.1 2.4 5.5 9.8 3.8 20.3-.3 2.1-.8 4.3-1.4 6.6-5.2-1.2-10.7-2-16.5-2.5-3.4-4.8-6.9-9.1-10.4-13 7.4-7.3 14.9-12.3 21-12.3 1.3 0 2.5.3 3.5.9zm-11.2 59.3c-1.8 3.2-3.9 6.4-6.1 9.6-3.7.3-7.4.4-11.2.4-3.9 0-7.6-.1-11.2-.4-2.2-3.2-4.2-6.4-6-9.6-1.9-3.3-3.7-6.7-5.3-10 1.6-3.3 3.4-6.7 5.3-10 1.8-3.2 3.9-6.4 6.1-9.6 3.7-.3 7.4-.4 11.2-.4 3.9 0 7.6.1 11.2.4 2.2 3.2 4.2 6.4 6 9.6 1.9 3.3 3.7 6.7 5.3 10-1.7 3.3-3.4 6.6-5.3 10zm8.3-3.3c1.5 3.5 2.7 6.9 3.8 10.3-3.4.8-7 1.4-10.8 1.9 1.2-1.9 2.5-3.9 3.6-6 1.2-2.1 2.3-4.2 3.4-6.2zm-25.6 27.1c-2.4-2.6-4.7-5.4-6.9-8.3 2.3.1 4.6.2 6.9.2 2.3 0 4.6-.1 6.9-.2-2.2 2.9-4.5 5.7-6.9 8.3zm-18.6-15c-3.8-.5-7.4-1.1-10.8-1.9 1.1-3.3 2.3-6.8 3.8-10.3 1.1 2 2.2 4.1 3.4 6.1 1.2 2.2 2.4 4.1 3.6 6.1zm-7-25.5c-1.5-3.5-2.7-6.9-3.8-10.3 3.4-.8 7-1.4 10.8-1.9-1.2 1.9-2.5 3.9-3.6 6-1.2 2.1-2.3 4.2-3.4 6.2zm25.6-27.1c2.4 2.6 4.7 5.4 6.9 8.3-2.3-.1-4.6-.2-6.9-.2-2.3 0-4.6.1-6.9.2 2.2-2.9 4.5-5.7 6.9-8.3zm22.2 21l-3.6-6c3.8.5 7.4 1.1 10.8 1.9-1.1 3.3-2.3 6.8-3.8 10.3-1.1-2.1-2.2-4.2-3.4-6.2zm-54.5-16.2c-1.7-10.5-.3-17.9 3.8-20.3 1-.6 2.2-.9 3.5-.9 6 0 13.5 4.9 21 12.3-3.5 3.8-7 8.2-10.4 13-5.8.5-11.3 1.4-16.5 2.5-.6-2.3-1-4.5-1.4-6.6zm-24.7 29c0-4.7 5.7-9.7 15.7-13.4 2-.8 4.2-1.5 6.4-2.1 1.6 5 3.6 10.3 6 15.6-2.4 5.3-4.5 10.5-6 15.5-13.8-4-22.1-10-22.1-15.6zm28.5 49.3c-4.1-2.4-5.5-9.8-3.8-20.3.3-2.1.8-4.3 1.4-6.6 5.2 1.2 10.7 2 16.5 2.5 3.4 4.8 6.9 9.1 10.4 13-7.4 7.3-14.9 12.3-21 12.3-1.3 0-2.5-.3-3.5-.9zm60.8-20.3c1.7 10.5.3 17.9-3.8 20.3-1 .6-2.2.9-3.5.9-6 0-13.5-4.9-21-12.3 3.5-3.8 7-8.2 10.4-13 5.8-.5 11.3-1.4 16.5-2.5.6 2.3 1 4.5 1.4 6.6zm9-15.6c-2 .8-4.2 1.5-6.4 2.1-1.6-5-3.6-10.3-6-15.6 2.4-5.3 4.5-10.5 6-15.5 13.8 4 22.1 10 22.1 15.6 0 4.7-5.8 9.7-15.7 13.4z"
/></g
>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -0,0 +1,28 @@
<svg
class="absolute top-0 left-0 -m-4 h-10 w-10 text-white"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
><g clip-path="url(#HTML5_Clip0_4)"
><path
d="M30.216 0L27.6454 28.7967L16.0907 32L4.56783 28.8012L2 0H30.216Z"
fill="#E44D26"
/><path
d="M16.108 29.5515L25.4447 26.963L27.6415 2.35497H16.108V29.5515Z"
fill="#F16529"
/><path
d="M11.1109 9.4197H16.108V5.88731H7.25053L7.33509 6.83499L8.20327 16.5692H16.108V13.0369H11.4338L11.1109 9.4197Z"
fill="#EBEBEB"
/><path
d="M11.907 18.3354H8.36111L8.856 23.8818L16.0917 25.8904L16.108 25.8859V22.2108L16.0925 22.2149L12.1585 21.1527L11.907 18.3354Z"
fill="#EBEBEB"
/><path
d="M16.0958 16.5692H20.4455L20.0354 21.1504L16.0958 22.2138V25.8887L23.3373 23.8817L23.3904 23.285L24.2205 13.9855L24.3067 13.0369H16.0958V16.5692Z"
fill="white"
/><path
d="M16.0958 9.41105V9.41969H24.6281L24.6989 8.62572L24.8599 6.83499L24.9444 5.88731H16.0958V9.41105Z"
fill="white"
/></g
><defs><clipPath id="HTML5_Clip0_4"><rect width="32" height="32" fill="white" /></clipPath></defs
></svg
>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,21 @@
<svg
class="absolute top-0 left-0 -m-4 h-10 w-10"
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 98.1 118"
style="enable-background:new 0 0 98.1 118;"
xml:space="preserve"
>
<path
fill="#FF3E00"
d="M91.8,15.6C80.9-0.1,59.2-4.7,43.6,5.2L16.1,22.8C8.6,27.5,3.4,35.2,1.9,43.9c-1.3,7.3-0.2,14.8,3.3,21.3 c-2.4,3.6-4,7.6-4.7,11.8c-1.6,8.9,0.5,18.1,5.7,25.4c11,15.7,32.6,20.3,48.2,10.4l27.5-17.5c7.5-4.7,12.7-12.4,14.2-21.1 c1.3-7.3,0.2-14.8-3.3-21.3c2.4-3.6,4-7.6,4.7-11.8C99.2,32.1,97.1,22.9,91.8,15.6"
/>
<path
fill="#FFFFFF"
d="M40.9,103.9c-8.9,2.3-18.2-1.2-23.4-8.7c-3.2-4.4-4.4-9.9-3.5-15.3c0.2-0.9,0.4-1.7,0.6-2.6l0.5-1.6l1.4,1 c3.3,2.4,6.9,4.2,10.8,5.4l1,0.3l-0.1,1c-0.1,1.4,0.3,2.9,1.1,4.1c1.6,2.3,4.4,3.4,7.1,2.7c0.6-0.2,1.2-0.4,1.7-0.7L65.5,72 c1.4-0.9,2.3-2.2,2.6-3.8c0.3-1.6-0.1-3.3-1-4.6c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7l-10.5,6.7 c-1.7,1.1-3.6,1.9-5.6,2.4c-8.9,2.3-18.2-1.2-23.4-8.7c-3.1-4.4-4.4-9.9-3.4-15.3c0.9-5.2,4.1-9.9,8.6-12.7l27.5-17.5 c1.7-1.1,3.6-1.9,5.6-2.5c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.2,0.9-0.4,1.7-0.7,2.6l-0.5,1.6l-1.4-1 c-3.3-2.4-6.9-4.2-10.8-5.4l-1-0.3l0.1-1c0.1-1.4-0.3-2.9-1.1-4.1c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7L32.4,46.1 c-1.4,0.9-2.3,2.2-2.6,3.8s0.1,3.3,1,4.6c1.6,2.3,4.4,3.3,7.1,2.6c0.6-0.2,1.2-0.4,1.7-0.7l10.5-6.7c1.7-1.1,3.6-1.9,5.6-2.5 c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.9,5.2-4.1,9.9-8.6,12.7l-27.5,17.5C44.8,102.5,42.9,103.3,40.9,103.9"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,12 @@
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 text-green-500" viewBox="0 0 128 128">
<path
d="m-2.3125e-8 8.9337 49.854 0.1586 14.167 24.47 14.432-24.47 49.547-0.1577-63.834 110.14zm126.98 0.6374-24.36 0.0207-38.476 66.052-38.453-66.052-24.749-0.0194 63.211 107.89zm-25.149-0.008-22.745 0.16758l-15.053 24.647-14.817-24.647-22.794-0.1679 37.731 64.476zM25.997 9.3929l23.002 0.0087M25.997 9.3929l23.002 0.0087"
fill="none"
/><path
d="m25.997 9.3929 23.002 0.0087l15.036 24.958 14.983-24.956 22.982-0.0057-37.85 65.655z"
fill="#35495e"
/><path
d="m0.91068 9.5686 25.066-0.1711 38.151 65.658 37.852-65.654 25.11 0.0263-62.966 108.06z"
fill="#41b883"
/>
</svg>

After

Width:  |  Height:  |  Size: 676 B

View File

@@ -0,0 +1,13 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-10 w-10' : 'mx-auto w-8 h-8'}
viewBox="0 0 9 8"
xmlns="http://www.w3.org/2000/svg"
><path d="m0 7h1v1h-1z" fill="#f00" /><path
d="m0 0h1v7h-1zm2 0h1v8h-1zm2 0h1v8h-1zm2 0h1v8h-1zm2 3.25h1v1.5h-1z"
fill="#fc0"
/></svg
>

View File

@@ -0,0 +1,18 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute
? 'absolute top-0 left-0 -m-5 h-10 w-10 fill-current text-red-500'
: 'mx-auto w-8 h-8 text-red-500'}
id="CouchDB"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 128 128"
><g id="original"
><path
d="M101.4,77.2c0,5-2.7,7.5-7.6,7.7H33.9c-4.9,0-7.6-2.5-7.6-7.7,0-5,2.7-7.5,7.6-7.7H94.1C99,69.7,101.4,72.2,101.4,77.2ZM94.1,88.7H33.9c-4.9,0-7.6,2.4-7.6,7.7,0,5,2.7,7.4,7.6,7.7H94.1c4.9,0,7.6-2.5,7.6-7.7C101.4,91.1,99,88.7,94.1,88.7Zm18.6-42.1h0c-4.9,0-7.6,2.5-7.6,7.4V96.1c0,5,2.7,7.5,7.6,7.7h0c7.4-.2,11.3-7.7,11.3-22.9V62C124,51.8,120.1,46.8,112.7,46.6Zm-97.4,0h0C7.9,46.8,4,51.8,4,62V80.9c0,15.2,3.9,22.7,11.3,22.9h0c4.9,0,7.6-2.4,7.6-7.7V54.3C22.7,49.3,20.2,46.8,15.3,46.6Zm97.4-3.8c0-12.7-6.6-18.7-18.6-18.9H33.9c-12.2.2-18.6,6.5-18.6,18.9h0c7.4,0,11.3,4,11.3,11.5s3.9,11.4,11.3,11.4H90.4c7.3,0,11.3-3.9,11.3-11.4-.3-7.7,3.9-11.2,11-11.5Z"
/></g
></svg
>

View File

@@ -0,0 +1,24 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
id="Layer_1"
data-name="Layer 1"
viewBox="0 0 309.88 252.72"
class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-12 w-12 ' : 'mx-auto w-8 h-8'}
>
<defs>
<style>
.cls-1 {
fill: #fff;
}
</style>
</defs>
<path
class="cls-1"
d="M316,10.05a4.2,4.2,0,0,0-2.84-1c-2.84,0-6.5,1.92-8.46,3l-.79.4a26.81,26.81,0,0,1-10.57,2.66c-3.76.12-7,.34-11.22.77-25,2.58-36.15,21.74-46.89,40.27-5.84,10.08-11.88,20.5-20.16,28.57a55.71,55.71,0,0,1-5.46,4.63c-8.57,6.39-19.33,10.9-27.74,14.12-8.07,3.08-16.86,5.85-25.37,8.53-7.78,2.45-15.14,4.76-21.9,7.28-3.05,1.13-5.64,2-7.93,2.76-6.15,2-10.6,3.53-17.08,8-2.53,1.73-5.07,3.6-6.8,5a71.26,71.26,0,0,0-13.54,14.27A84.81,84.81,0,0,1,77.88,163c-1.36,1.34-3.8,2-7.43,2-4.27,0-9.43-.88-14.91-1.81s-11.46-2-16.46-2c-4.07,0-7.17.66-9.5,2,0,0-3.9,2.28-5.56,5.23l1.62.73a33.56,33.56,0,0,1,6.93,5,33.68,33.68,0,0,0,7.19,5.12A6.37,6.37,0,0,1,42,180.72c-.69,1-1.69,2.29-2.74,3.67-5.77,7.55-9.13,12.32-7.2,14.92a6,6,0,0,0,3,.68c12.59,0,19.34-3.27,27.9-7.41,2.47-1.2,5-2.44,8-3.7,5-2.17,10.38-5.63,16.08-9.29,7.55-4.85,15.36-9.87,22.92-12.3a62.3,62.3,0,0,1,19.23-2.7c8,0,16.42,1.07,24.54,2.11,6.06.78,12.32,1.58,18.47,2,2.39.14,4.6.21,6.76.21a78.48,78.48,0,0,0,8.61-.45l.68-.24c4.32-2.65,6.34-8.34,8.29-13.84,1.26-3.54,2.32-6.72,4-8.74a2.06,2.06,0,0,1,.33-.27.4.4,0,0,1,.49.08.25.25,0,0,1,0,.16c-1,21.51-9.67,35.16-18.42,47.3L177,199.14s8.18,0,12.84-1.8c17-5.08,29.84-16.28,39.18-34.14a144.39,144.39,0,0,0,6.16-14.09c.16-.4,1.64-1.14,1.49.93,0,.61-.08,1.29-.13,2h0c0,.42-.06.85-.08,1.28-.25,3-1,9.34-1,9.34l5.25-2.81c12.66-8,22.42-24.14,29.82-49.25,3.09-10.46,5.34-20.85,7.33-30,2.38-11,4.43-20.43,6.78-24.09,3.69-5.74,9.32-9.62,14.77-13.39.75-.51,1.49-1,2.22-1.54,6.86-4.81,13.67-10.36,15.16-20.71l0-.23C317.93,12.92,317,11,316,10.05Z"
transform="translate(-7.45 -9.1)"
/>
</svg>

View File

@@ -0,0 +1,90 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
viewBox="0 0 128 128"
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#439934"
d="M88.038 42.812c1.605 4.643 2.761 9.383 3.141 14.296.472 6.095.256 12.147-1.029 18.142-.035.165-.109.32-.164.48-.403.001-.814-.049-1.208.012-3.329.523-6.655 1.065-9.981 1.604-3.438.557-6.881 1.092-10.313 1.687-1.216.21-2.721-.041-3.212 1.641-.014.046-.154.054-.235.08l.166-10.051-.169-24.252 1.602-.275c2.62-.429 5.24-.864 7.862-1.281 3.129-.497 6.261-.98 9.392-1.465 1.381-.215 2.764-.412 4.148-.618z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#45A538"
d="M61.729 110.054c-1.69-1.453-3.439-2.842-5.059-4.37-8.717-8.222-15.093-17.899-18.233-29.566-.865-3.211-1.442-6.474-1.627-9.792-.13-2.322-.318-4.665-.154-6.975.437-6.144 1.325-12.229 3.127-18.147l.099-.138c.175.233.427.439.516.702 1.759 5.18 3.505 10.364 5.242 15.551 5.458 16.3 10.909 32.604 16.376 48.9.107.318.384.579.583.866l-.87 2.969z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#46A037"
d="M88.038 42.812c-1.384.206-2.768.403-4.149.616-3.131.485-6.263.968-9.392 1.465-2.622.417-5.242.852-7.862 1.281l-1.602.275-.012-1.045c-.053-.859-.144-1.717-.154-2.576-.069-5.478-.112-10.956-.18-16.434-.042-3.429-.105-6.857-.175-10.285-.043-2.13-.089-4.261-.185-6.388-.052-1.143-.236-2.28-.311-3.423-.042-.657.016-1.319.029-1.979.817 1.583 1.616 3.178 2.456 4.749 1.327 2.484 3.441 4.314 5.344 6.311 7.523 7.892 12.864 17.068 16.193 27.433z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#409433"
d="M65.036 80.753c.081-.026.222-.034.235-.08.491-1.682 1.996-1.431 3.212-1.641 3.432-.594 6.875-1.13 10.313-1.687 3.326-.539 6.652-1.081 9.981-1.604.394-.062.805-.011 1.208-.012-.622 2.22-1.112 4.488-1.901 6.647-.896 2.449-1.98 4.839-3.131 7.182a49.142 49.142 0 01-6.353 9.763c-1.919 2.308-4.058 4.441-6.202 6.548-1.185 1.165-2.582 2.114-3.882 3.161l-.337-.23-1.214-1.038-1.256-2.753a41.402 41.402 0 01-1.394-9.838l.023-.561.171-2.426c.057-.828.133-1.655.168-2.485.129-2.982.241-5.964.359-8.946z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#4FAA41"
d="M65.036 80.753c-.118 2.982-.23 5.964-.357 8.947-.035.83-.111 1.657-.168 2.485l-.765.289c-1.699-5.002-3.399-9.951-5.062-14.913-2.75-8.209-5.467-16.431-8.213-24.642a4498.887 4498.887 0 00-6.7-19.867c-.105-.31-.407-.552-.617-.826l4.896-9.002c.168.292.39.565.496.879a6167.476 6167.476 0 016.768 20.118c2.916 8.73 5.814 17.467 8.728 26.198.116.349.308.671.491 1.062l.67-.78-.167 10.052z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#4AA73C"
d="M43.155 32.227c.21.274.511.516.617.826a4498.887 4498.887 0 016.7 19.867c2.746 8.211 5.463 16.433 8.213 24.642 1.662 4.961 3.362 9.911 5.062 14.913l.765-.289-.171 2.426-.155.559c-.266 2.656-.49 5.318-.814 7.968-.163 1.328-.509 2.632-.772 3.947-.198-.287-.476-.548-.583-.866-5.467-16.297-10.918-32.6-16.376-48.9a3888.972 3888.972 0 00-5.242-15.551c-.089-.263-.34-.469-.516-.702l3.272-8.84z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#57AE47"
d="M65.202 70.702l-.67.78c-.183-.391-.375-.714-.491-1.062-2.913-8.731-5.812-17.468-8.728-26.198a6167.476 6167.476 0 00-6.768-20.118c-.105-.314-.327-.588-.496-.879l6.055-7.965c.191.255.463.482.562.769 1.681 4.921 3.347 9.848 5.003 14.778 1.547 4.604 3.071 9.215 4.636 13.813.105.308.47.526.714.786l.012 1.045c.058 8.082.115 16.167.171 24.251z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#60B24F"
d="M65.021 45.404c-.244-.26-.609-.478-.714-.786-1.565-4.598-3.089-9.209-4.636-13.813-1.656-4.93-3.322-9.856-5.003-14.778-.099-.287-.371-.514-.562-.769 1.969-1.928 3.877-3.925 5.925-5.764 1.821-1.634 3.285-3.386 3.352-5.968.003-.107.059-.214.145-.514l.519 1.306c-.013.661-.072 1.322-.029 1.979.075 1.143.259 2.28.311 3.423.096 2.127.142 4.258.185 6.388.069 3.428.132 6.856.175 10.285.067 5.478.111 10.956.18 16.434.008.861.098 1.718.152 2.577z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#A9AA88"
d="M62.598 107.085c.263-1.315.609-2.62.772-3.947.325-2.649.548-5.312.814-7.968l.066-.01.066.011a41.402 41.402 0 001.394 9.838c-.176.232-.425.439-.518.701-.727 2.05-1.412 4.116-2.143 6.166-.1.28-.378.498-.574.744l-.747-2.566.87-2.969z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#B6B598"
d="M62.476 112.621c.196-.246.475-.464.574-.744.731-2.05 1.417-4.115 2.143-6.166.093-.262.341-.469.518-.701l1.255 2.754c-.248.352-.59.669-.728 1.061l-2.404 7.059c-.099.283-.437.483-.663.722l-.695-3.985z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#C2C1A7"
d="M63.171 116.605c.227-.238.564-.439.663-.722l2.404-7.059c.137-.391.48-.709.728-1.061l1.215 1.037c-.587.58-.913 1.25-.717 2.097l-.369 1.208c-.168.207-.411.387-.494.624-.839 2.403-1.64 4.819-2.485 7.222-.107.305-.404.544-.614.812-.109-1.387-.22-2.771-.331-4.158z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#CECDB7"
d="M63.503 120.763c.209-.269.506-.508.614-.812.845-2.402 1.646-4.818 2.485-7.222.083-.236.325-.417.494-.624l-.509 5.545c-.136.157-.333.294-.398.477-.575 1.614-1.117 3.24-1.694 4.854-.119.333-.347.627-.525.938-.158-.207-.441-.407-.454-.623-.051-.841-.016-1.688-.013-2.533z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#DBDAC7"
d="M63.969 123.919c.178-.312.406-.606.525-.938.578-1.613 1.119-3.239 1.694-4.854.065-.183.263-.319.398-.477l.012 3.64-1.218 3.124-1.411-.495z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#EBE9DC"
d="M65.38 124.415l1.218-3.124.251 3.696-1.469-.572z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#CECDB7"
d="M67.464 110.898c-.196-.847.129-1.518.717-2.097l.337.23-1.054 1.867z"
/><path
fill-rule="evenodd"
clip-rule="evenodd"
fill="#4FAA41"
d="M64.316 95.172l-.066-.011-.066.01.155-.559-.023.56z"
/>
</svg>

View File

@@ -0,0 +1,17 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-10 w-10' : 'mx-auto w-8 h-8'}
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
viewBox="0 0 25.6 25.6"
><path
d="M179.076 94.886c-3.568-.1-6.336.268-8.656 1.25-.668.27-1.74.27-1.828 1.116.357.355.4.936.713 1.428.535.893 1.473 2.096 2.32 2.72l2.855 2.053c1.74 1.07 3.703 1.695 5.398 2.766.982.625 1.963 1.428 2.945 2.098.5.357.803.938 1.428 1.16v-.135c-.312-.4-.402-.98-.713-1.428l-1.34-1.293c-1.293-1.74-2.9-3.258-4.64-4.506-1.428-.982-4.55-2.32-5.13-3.97l-.088-.1c.98-.1 2.14-.447 3.078-.715 1.518-.4 2.9-.312 4.46-.713l2.143-.625v-.4c-.803-.803-1.383-1.874-2.23-2.632-2.275-1.963-4.775-3.882-7.363-5.488-1.383-.892-3.168-1.473-4.64-2.23-.537-.268-1.428-.402-1.74-.848-.805-.98-1.25-2.275-1.83-3.436l-3.658-7.763c-.803-1.74-1.295-3.48-2.275-5.086-4.596-7.585-9.594-12.18-17.268-16.687-1.65-.937-3.613-1.34-5.7-1.83l-3.346-.18c-.715-.312-1.428-1.16-2.053-1.562-2.543-1.606-9.102-5.086-10.977-.5-1.205 2.9 1.785 5.755 2.8 7.228.76 1.026 1.74 2.186 2.277 3.346.3.758.4 1.562.713 2.365.713 1.963 1.383 4.15 2.32 5.98.5.937 1.025 1.92 1.65 2.767.357.5.982.714 1.115 1.517-.625.893-.668 2.23-1.025 3.347-1.607 5.042-.982 11.288 1.293 15 .715 1.115 2.4 3.57 4.686 2.632 2.008-.803 1.56-3.346 2.14-5.577.135-.535.045-.892.312-1.25v.1l1.83 3.703c1.383 2.186 3.793 4.462 5.8 5.98 1.07.803 1.918 2.187 3.256 2.677v-.135h-.088c-.268-.4-.67-.58-1.027-.892-.803-.803-1.695-1.785-2.32-2.677-1.873-2.498-3.523-5.265-4.996-8.12-.715-1.383-1.34-2.9-1.918-4.283-.27-.536-.27-1.34-.715-1.606-.67.98-1.65 1.83-2.143 3.034-.848 1.918-.936 4.283-1.248 6.737-.18.045-.1 0-.18.1-1.426-.356-1.918-1.83-2.453-3.078-1.338-3.168-1.562-8.254-.402-11.913.312-.937 1.652-3.882 1.117-4.774-.27-.848-1.16-1.338-1.652-2.008-.58-.848-1.203-1.918-1.605-2.855-1.07-2.5-1.605-5.265-2.766-7.764-.537-1.16-1.473-2.365-2.232-3.435-.848-1.205-1.783-2.053-2.453-3.48-.223-.5-.535-1.294-.178-1.83.088-.357.268-.5.623-.58.58-.5 2.232.134 2.812.4 1.65.67 3.033 1.294 4.416 2.23.625.446 1.295 1.294 2.098 1.518h.938c1.428.312 3.033.1 4.37.5 2.365.76 4.506 1.874 6.426 3.08 5.844 3.703 10.664 8.968 13.92 15.26.535 1.026.758 1.963 1.25 3.034.938 2.187 2.098 4.417 3.033 6.56.938 2.097 1.83 4.24 3.168 5.98.67.937 3.346 1.427 4.55 1.918.893.4 2.275.76 3.08 1.25 1.516.937 3.033 2.008 4.46 3.034.713.534 2.945 1.65 3.078 2.54zm-45.5-38.772a7.09 7.09 0 0 0-1.828.223v.1h.088c.357.714.982 1.205 1.428 1.83l1.027 2.142.088-.1c.625-.446.938-1.16.938-2.23-.268-.312-.312-.625-.535-.937-.268-.446-.848-.67-1.206-1.026z"
transform="matrix(.390229 0 0 .38781 -46.300037 -16.856717)"
fill-rule="evenodd"
fill="#00678c"
/></svg
>

View File

@@ -0,0 +1,56 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-10 w-10' : 'mx-auto w-8 h-8'}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 432.071 445.383"
xml:space="preserve"
>
<g id="orginal" style="fill-rule:nonzero;clip-rule:nonzero;stroke:#000000;stroke-miterlimit:4;" />
<g
id="Layer_x0020_3"
style="fill-rule:nonzero;clip-rule:nonzero;fill:none;stroke:#FFFFFF;stroke-width:12.4651;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;"
>
<path
style="fill:#000000;stroke:#000000;stroke-width:37.3953;stroke-linecap:butt;stroke-linejoin:miter;"
d="M323.205,324.227c2.833-23.601,1.984-27.062,19.563-23.239l4.463,0.392c13.517,0.615,31.199-2.174,41.587-7c22.362-10.376,35.622-27.7,13.572-23.148c-50.297,10.376-53.755-6.655-53.755-6.655c53.111-78.803,75.313-178.836,56.149-203.322 C352.514-5.534,262.036,26.049,260.522,26.869l-0.482,0.089c-9.938-2.062-21.06-3.294-33.554-3.496c-22.761-0.374-40.032,5.967-53.133,15.904c0,0-161.408-66.498-153.899,83.628c1.597,31.936,45.777,241.655,98.47,178.31 c19.259-23.163,37.871-42.748,37.871-42.748c9.242,6.14,20.307,9.272,31.912,8.147l0.897-0.765c-0.281,2.876-0.157,5.689,0.359,9.019c-13.572,15.167-9.584,17.83-36.723,23.416c-27.457,5.659-11.326,15.734-0.797,18.367c12.768,3.193,42.305,7.716,62.268-20.224 l-0.795,3.188c5.325,4.26,4.965,30.619,5.72,49.452c0.756,18.834,2.017,36.409,5.856,46.771c3.839,10.36,8.369,37.05,44.036,29.406c29.809-6.388,52.6-15.582,54.677-101.107"
/>
<path
style="fill:#336791;stroke:none;"
d="M402.395,271.23c-50.302,10.376-53.76-6.655-53.76-6.655c53.111-78.808,75.313-178.843,56.153-203.326c-52.27-66.785-142.752-35.2-144.262-34.38l-0.486,0.087c-9.938-2.063-21.06-3.292-33.56-3.496c-22.761-0.373-40.026,5.967-53.127,15.902 c0,0-161.411-66.495-153.904,83.63c1.597,31.938,45.776,241.657,98.471,178.312c19.26-23.163,37.869-42.748,37.869-42.748c9.243,6.14,20.308,9.272,31.908,8.147l0.901-0.765c-0.28,2.876-0.152,5.689,0.361,9.019c-13.575,15.167-9.586,17.83-36.723,23.416 c-27.459,5.659-11.328,15.734-0.796,18.367c12.768,3.193,42.307,7.716,62.266-20.224l-0.796,3.188c5.319,4.26,9.054,27.711,8.428,48.969c-0.626,21.259-1.044,35.854,3.147,47.254c4.191,11.4,8.368,37.05,44.042,29.406c29.809-6.388,45.256-22.942,47.405-50.555 c1.525-19.631,4.976-16.729,5.194-34.28l2.768-8.309c3.192-26.611,0.507-35.196,18.872-31.203l4.463,0.392c13.517,0.615,31.208-2.174,41.591-7c22.358-10.376,35.618-27.7,13.573-23.148z"
/>
<path
d="M215.866,286.484c-1.385,49.516,0.348,99.377,5.193,111.495c4.848,12.118,15.223,35.688,50.9,28.045c29.806-6.39,40.651-18.756,45.357-46.051c3.466-20.082,10.148-75.854,11.005-87.281"
/>
<path
d="M173.104,38.256c0,0-161.521-66.016-154.012,84.109c1.597,31.938,45.779,241.664,98.473,178.316c19.256-23.166,36.671-41.335,36.671-41.335"
/>
<path
d="M260.349,26.207c-5.591,1.753,89.848-34.889,144.087,34.417c19.159,24.484-3.043,124.519-56.153,203.329"
/>
<path
style="stroke-linejoin:bevel;"
d="M348.282,263.953c0,0,3.461,17.036,53.764,6.653c22.04-4.552,8.776,12.774-13.577,23.155c-18.345,8.514-59.474,10.696-60.146-1.069c-1.729-30.355,21.647-21.133,19.96-28.739c-1.525-6.85-11.979-13.573-18.894-30.338 c-6.037-14.633-82.796-126.849,21.287-110.183c3.813-0.789-27.146-99.002-124.553-100.599c-97.385-1.597-94.19,119.762-94.19,119.762"
/>
<path
d="M188.604,274.334c-13.577,15.166-9.584,17.829-36.723,23.417c-27.459,5.66-11.326,15.733-0.797,18.365c12.768,3.195,42.307,7.718,62.266-20.229c6.078-8.509-0.036-22.086-8.385-25.547c-4.034-1.671-9.428-3.765-16.361,3.994z"
/>
<path
d="M187.715,274.069c-1.368-8.917,2.93-19.528,7.536-31.942c6.922-18.626,22.893-37.255,10.117-96.339c-9.523-44.029-73.396-9.163-73.436-3.193c-0.039,5.968,2.889,30.26-1.067,58.548c-5.162,36.913,23.488,68.132,56.479,64.938"
/>
<path
style="fill:#FFFFFF;stroke-width:4.155;stroke-linecap:butt;stroke-linejoin:miter;"
d="M172.517,141.7c-0.288,2.039,3.733,7.48,8.976,8.207c5.234,0.73,9.714-3.522,9.998-5.559c0.284-2.039-3.732-4.285-8.977-5.015c-5.237-0.731-9.719,0.333-9.996,2.367z"
/>
<path
style="fill:#FFFFFF;stroke-width:2.0775;stroke-linecap:butt;stroke-linejoin:miter;"
d="M331.941,137.543c0.284,2.039-3.732,7.48-8.976,8.207c-5.238,0.73-9.718-3.522-10.005-5.559c-0.277-2.039,3.74-4.285,8.979-5.015c5.239-0.73,9.718,0.333,10.002,2.368z"
/>
<path
d="M350.676,123.432c0.863,15.994-3.445,26.888-3.988,43.914c-0.804,24.748,11.799,53.074-7.191,81.435"
/>
<path style="stroke-width:3;" d="M0,60.232" />
</g>
</svg>

View File

@@ -0,0 +1,34 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-10 w-10' : 'mx-auto w-8 h-8'}
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
><defs
><path
id="a"
d="m45.536 38.764c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.813s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z"
/><path
id="b"
d="m45.536 28.733c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.935c2.332-.837 3.14-.867 5.126-.14s12.35 4.853 14.312 5.57 2.037 1.31.024 2.36z"
/></defs
><g transform="matrix(.848327 0 0 .848327 -7.883573 -9.449691)"
><use fill="#a41e11" xlink:href="#a" /><path
d="m45.536 34.95c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.936c2.332-.836 3.14-.867 5.126-.14s12.35 4.852 14.31 5.582 2.037 1.31.024 2.36z"
fill="#d82c20"
/><use fill="#a41e11" xlink:href="#a" y="-6.218" /><use fill="#d82c20" xlink:href="#b" /><path
d="m45.536 26.098c-2.013 1.05-12.44 5.337-14.66 6.495s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.815s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z"
fill="#a41e11"
/><use fill="#d82c20" xlink:href="#b" y="-6.449" /><g fill="#fff"
><path
d="m29.096 20.712-1.182-1.965-3.774-.34 2.816-1.016-.845-1.56 2.636 1.03 2.486-.814-.672 1.612 2.534.95-3.268.34zm-6.296 3.912 8.74-1.342-2.64 3.872z"
/><ellipse cx="20.444" cy="21.402" rx="4.672" ry="1.811" /></g
><path d="m42.132 21.138-5.17 2.042-.004-4.087z" fill="#7a0c00" /><path
d="m36.963 23.18-.56.22-5.166-2.042 5.723-2.264z"
fill="#ad2115"
/></g
></svg
>

View File

@@ -0,0 +1,121 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
viewBox="0 0 700 240"
xmlns="http://www.w3.org/2000/svg"
class={isAbsolute ? 'w-36 absolute top-0 left-0 -m-3 -mt-5' : 'w-28 mx-auto'}
><path fill="#FDBC3D" d="m90.694 107.498-.981.39-20.608 8.23 6.332 6.547z" /><path
fill="#8EC63F"
d="M61.139 77.914 46.632 93 56.9 103.547c8.649-7.169 17.832-10.502 18.653-10.789L61.139 77.914z"
/><path fill="#208ECB" d="M61.139 77.914 46.367 63.247l-14.228 14.8 14.493 14.952z" /><path
fill="#273C8B"
d="m40.767 57.48-6.943 2.79a38.381 38.381 0 0 0-11.742 7.418L32.14 78.047l14.228-14.8-5.601-5.768z"
/><path
fill="#EE4649"
d="m119.074 138.128-.243-.25-5.653 5.675c1.897-1.516 4.287-3.66 5.896-5.425z"
/><path
fill="#F6944E"
d="m102.088 150.087 3.709-1.875a46.26 46.26 0 0 0 7.381-4.659l5.653-5.676-14.311-15.285-14.493 15.072 12.061 12.423z"
/><path fill="#FFC951" d="m90.279 107.926-14.842 14.74 14.589 14.998 14.493-15.072z" /><path
fill="#F6CC18"
d="m69.087 116.125-11.256 4.493c-3.301.973-6.096 2.843-8.434 5.081l11.548 11.892 14.493-14.926-6.35-6.54z"
/><path
fill="#C5D82D"
d="m56.886 103.559-10.253-10.56L32 107.926l11.784 11.991c3.304-6.888 8.174-12.272 13.103-16.358z"
/><path fill="#0D77B3" d="m32.14 78.047-14.507 14.94 14.365 14.939 14.634-14.927z" /><path
fill="#2A377E"
d="M32.14 78.047 22.08 67.688a38.573 38.573 0 0 0-11.093 18.455l6.645 6.843 14.506-14.94z"
/><path
fill="#DA2128"
d="m94.826 162.454-4.87 5.017 14.808 15.397c-.632-1.942-1.606-4.438-2.58-6.307l-7.357-14.107z"
/><path
fill="#F8A561"
d="m91.24 155.575 10.832-5.48-12.046-12.43-14.506 14.939 14.436 14.867 4.87-5.017z"
/><path fill="#FDBC3D" d="m75.437 122.665-14.493 14.926 14.576 15.013 14.506-14.94z" /><path
fill="#FAD412"
d="M49.397 125.7c-6.71 6.472-9.664 16.047-9.664 16.047-.3-4.606.06-8.83.907-12.698l-8.513 8.742 14.311 14.74 14.506-14.94-11.547-11.892z"
/><path
fill="#C4D52D"
d="m43.783 119.917-11.785-11.991-13.29 13.687 3.708 6.178 9.71 10 8.52-8.775a42.699 42.699 0 0 1 3.137-9.099z"
/><path
fill="#1B80C1"
d="m17.633 92.986-7.638 7.72c.65 5.1 2.35 10.3 5.193 15.04l3.52 5.867 13.29-13.687-14.365-14.94z"
/><path
fill="#1A4685"
d="M10.989 86.143c-1.22 4.667-1.597 9.683-.993 14.563l7.638-7.72-6.645-6.843z"
/><path
fill="#B12026"
d="m89.956 197.35 12.502 13.022c4.143-8.355 5.148-18.255 2.307-27.504l-.302-.311-14.507 14.793z"
/><path fill="#E42028" d="M89.956 167.47 75.52 182.484l14.436 14.867 14.506-14.793z" /><path
fill="#F16B4E"
d="m75.52 152.604-14.576 14.867 14.576 15.012 14.436-15.012z"
/><path fill="#FAD412" d="m60.944 137.591-14.506 14.94 14.506 14.94 14.576-14.867z" /><path
fill="#FFC951"
d="m32.127 137.792-2.293 2.36 10.933 18.22 5.671-5.841z"
/><path fill="#FFC951" d="m22.416 127.79 7.418 12.363 2.293-2.361z" /><path
fill="#981C20"
d="M102.458 210.371 89.955 197.35 75.45 212.29l12.918 13.304a36.951 36.951 0 0 0 14.09-15.222z"
/><path
fill="#C92039"
d="m75.52 182.483-12.59 12.823 6.423 10.704 6.097 6.28 14.506-14.94z"
/><path fill="#F05B41" d="m60.944 167.47-9.096 9.369 11.081 18.467 12.59-12.823z" /><path
fill="#F6CC18"
d="m46.438 152.53-5.671 5.842 11.081 18.467 9.096-9.368z"
/><path
fill="#7A1319"
d="m74.01 213.772 8.904 14.838 4.104-2.237c.429-.233.934-.533 1.35-.78L75.45 212.29l-1.44 1.482z"
/><path fill="#981C20" d="m69.353 206.01 4.658 7.762 1.44-1.482z" /><path
fill="#15796E"
d="m147.842 48.094 10.653-10.971a41.81 41.81 0 0 0 .943-6.94l-11.414-11.755-14.48 14.94 14.298 14.726z"
/><path fill="#29B364" d="m133.53 33.354 14.494-14.926-2.737-2.965-20.95 8.422z" /><path
fill="#21A29F"
d="M151.819 52.189c3.057-4.334 5.434-9.932 6.677-15.066l-10.653 10.971 3.976 4.095z"
/><path
fill="#12827F"
d="M159.438 30.183c.307-6.28-.783-12.862-3.488-19.006l-1.41.567-6.516 6.684 11.414 11.755zM154.54 11.744l-9.253 3.72 2.737 2.964z"
/><path fill="#0C6355" d="m133.336 63.034 14.506-14.94-14.311-14.713-14.493 14.926z" /><path
fill="#1B974D"
d="m104.532 33.368 14.506 14.94 14.48-14.94-9.2-9.476-17.363 6.98z"
/><path fill="#16669F" d="m106.955 30.872-3.485 1.401 1.062 1.095z" /><path
fill="#44BFBD"
d="M135.9 65.674A41.696 41.696 0 0 0 151.82 52.19l-3.977-4.095-14.506 14.94 2.564 2.64z"
/><path
fill="#0D5650"
d="m115.71 74.76 11.052-4.956 6.574-6.77-14.298-14.727-14.506 14.94z"
/><path fill="#3FAF49" d="m119.038 48.307-14.506-14.94-14.576 14.868 14.563 14.999z" /><path
fill="#0D77B3"
d="m104.532 33.368-1.062-1.095-20.97 8.43 7.456 7.532z"
/><path
fill="#0C6355"
d="M134.766 66.217c.352-.157.789-.376 1.134-.543l-2.564-2.64-6.574 6.77 8.004-3.587z"
/><path fill="#12827F" d="m115.71 74.76-11.178-11.513-14.506 14.94 5.47 5.633z" /><path
fill="#4EB648"
d="M104.532 63.247 89.956 48.235 75.52 63.247l14.493 14.927z"
/><path fill="#16669F" d="M89.956 48.235 82.5 40.703l-20.868 8.388L75.52 63.247z" /><path
fill="#FBB139"
d="M129.526 119.012c1.902-7.144 2.108-15.019.353-22.538l-11.048 11.379 10.695 11.16z"
/><path
fill="#E2B523"
d="m110.62 99.542 8.21 8.311 11.049-11.38a46.303 46.303 0 0 0-1.186-4.149l-18.074 7.218z"
/><path fill="#189590" d="M90.026 78.186 76.128 92.501l19.367-8.681z" /><path
fill="#8EC63F"
d="m76.083 92.521 13.943-14.335-14.506-14.94-14.381 14.668 14.413 14.844z"
/><path
fill="#0D77B3"
d="M75.52 63.247 61.633 49.09l-2.264.91-13.002 13.246L61.14 77.914z"
/><path fill="#1953A2" d="m59.37 50.002-18.603 7.477 5.6 5.768z" /><path
fill="#ED3551"
d="M119.324 137.84c.885-.988 2.15-2.59 2.942-3.646l-3.17 3.41.228.236z"
/><path
fill="#F8A561"
d="m118.83 137.877 3.437-3.683a46.268 46.268 0 0 0 7.259-15.182l-10.695-11.159-14.311 14.74 14.31 15.284z"
/><path
fill="#E9B520"
d="m90.279 107.926 14.24 14.666 14.312-14.739-8.212-8.311-19.925 7.956z"
/><path
fill="#EE4649"
d="m118.83 137.877.244.251c.085-.095.166-.193.25-.288l-.228-.235-.265.272z"
/></svg
>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<img
alt="ghost logo"
class={isAbsolute ? 'w-12 absolute top-0 left-0 -m-3 -mt-5' : 'w-8 mx-auto'}
src="/ghost.png"
/>

View File

@@ -0,0 +1,26 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
viewBox="0 0 81 84"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_5273_21928)">
<path
d="M79.7186 28.6019C82.1218 21.073 80.6778 6.03601 76.0158 0.487861C75.4073 -0.238064 74.2624 -0.134361 73.757 0.664158L68.0121 9.72786C66.5887 11.5427 64.0308 11.9575 62.1124 10.6923C55.8827 6.59601 48.4359 4.21082 40.4322 4.21082C32.4285 4.21082 24.9817 6.59601 18.752 10.6923C16.8336 11.9575 14.2757 11.5323 12.8523 9.72786L7.10738 0.664158C6.60199 -0.134361 5.45712 -0.238064 4.84859 0.487861C0.186621 6.03601 -1.25735 21.073 1.14583 28.6019C1.94002 31.1012 2.16693 33.7456 1.69248 36.3279C1.22834 38.879 0.753897 41.9693 0.753897 44.1056C0.753897 66.1323 18.5251 84.0004 40.4322 84.0004C62.3497 84.0004 80.1105 66.1427 80.1105 44.1056C80.1105 41.959 79.6464 38.879 79.1719 36.3279C78.6975 33.7456 78.9244 31.1012 79.7186 28.6019ZM40.4322 75.0819C23.4965 75.0819 9.71684 61.2271 9.71684 44.199C9.71684 43.639 9.73747 43.0893 9.7581 42.5397C10.3769 30.9353 17.3802 21.0108 27.3024 16.2819C31.2836 14.3738 35.7393 13.316 40.4322 13.316C45.1251 13.316 49.5808 14.3842 53.5724 16.2923C63.4945 21.0212 70.4978 30.9456 71.1166 42.5397C71.1476 43.0893 71.1579 43.639 71.1579 44.199C71.1476 61.2271 57.3679 75.0819 40.4322 75.0819Z"
fill="#1EB4D4"
/>
<path
d="M53.7371 56.083L45.8881 42.4044L39.153 30.997C38.9983 30.7274 38.7095 30.5615 38.3898 30.5615H31.9538C31.634 30.5615 31.3452 30.7378 31.1905 31.0074C31.0358 31.2874 31.0358 31.6296 31.2008 31.8993L37.6368 42.7881L28.9936 56.0415C28.8183 56.3111 28.7977 56.6637 28.9524 56.9541C29.1071 57.2444 29.4062 57.4207 29.7259 57.4207H36.2032C36.5023 57.4207 36.7808 57.2652 36.9458 57.0163L41.6181 49.6741L45.8056 56.9748C45.9603 57.2548 46.2594 57.4207 46.5688 57.4207H52.9533C53.273 57.4207 53.5618 57.2548 53.7165 56.9748C53.9022 56.6948 53.9022 56.363 53.7371 56.083Z"
fill="#1EB4D4"
/>
</g>
<defs>
<clipPath id="clip0_5273_21928">
<rect width="81" height="84" fill="white" />
</clipPath>
</defs>
</svg>

View File

@@ -0,0 +1,27 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
fill="none"
viewBox="0 0 140 140"
data-lt-extension-installed="true"
><g clip-path="url(#clip0)"
><path
fill="#fff"
fill-rule="evenodd"
d="M140 43.602c0-1.662.001-3.324-.01-4.987-.008-1.4-.024-2.8-.062-4.2-.082-3.05-.262-6.126-.805-9.142-.55-3.06-1.448-5.907-2.864-8.688A29.227 29.227 0 0 0 123.476 3.81c-2.783-1.416-5.634-2.314-8.697-2.864-3.016-.542-6.094-.722-9.144-.804-1.4-.038-2.801-.054-4.202-.063C99.77.068 98.107.07 96.444.07L77.135 0H62.694L43.726.07c-1.666 0-3.332-.002-4.998.008-1.404.01-2.807.025-4.21.063-3.058.082-6.142.262-9.166.805-3.067.55-5.922 1.447-8.709 2.862a29.293 29.293 0 0 0-7.419 5.377 29.223 29.223 0 0 0-5.389 7.4c-1.42 2.78-2.32 5.63-2.871 8.691-.543 3.016-.723 6.091-.806 9.14-.038 1.4-.054 2.8-.062 4.2C.086 40.277 0 42.342 0 44.004v33.3l.086 19.102c0 1.665 0 3.33.01 4.994a200.6 200.6 0 0 0 .062 4.205c.083 3.054.263 6.135.807 9.155.551 3.064 1.451 5.916 2.87 8.7a29.294 29.294 0 0 0 12.807 12.794c2.788 1.418 5.645 2.317 8.714 2.868 3.022.542 6.105.722 9.162.804 1.403.038 2.806.054 4.21.063 1.666.01 3.332.009 4.998.009l19.14.001h14.477l19.101-.001c1.663 0 3.326.001 4.989-.009a202.92 202.92 0 0 0 4.202-.063c3.052-.082 6.13-.262 9.148-.805 3.061-.551 5.911-1.45 8.692-2.867a29.215 29.215 0 0 0 7.405-5.384 29.22 29.22 0 0 0 5.378-7.409c1.417-2.785 2.315-5.639 2.866-8.704.542-3.02.722-6.099.804-9.152.038-1.402.054-2.804.062-4.205.011-1.665.01-3.33.01-4.993l-.001-19.103V62.694L140 43.602"
clip-rule="evenodd"
/><path
fill="#000"
fill-rule="evenodd"
d="M39.375 40.188h8.313a6.25 6.25 0 0 1 6.25 6.25v24.25h16.25v8.75h-18.75a6.25 6.25 0 0 1-6.25-6.25v-24.25h-5.813v-8.75zm63.563 6.25v6.5h-8.75v-4h-6.876v30.5h-8.75v-30.5h-6.874v4h-8.75v-6.5a6.25 6.25 0 0 1 6.25-6.25h27.5a6.25 6.25 0 0 1 6.25 6.25z"
clip-rule="evenodd"
/><path
fill="#239AFF"
d="M35.319 102.906l-8.138-5.812c2.39-3.347 4.857-5.936 7.452-7.753 2.884-2.018 5.948-3.091 9.117-3.091 2.942 0 5.491.714 7.768 2.08a17.622 17.622 0 0 1 2.615 1.94c.589.518 1.009.926 1.903 1.82 1.355 1.354 1.917 1.851 2.591 2.255.731.439 1.503.655 2.623.655 1.121 0 1.896-.217 2.631-.657.677-.405 1.245-.905 2.6-2.257l.012-.012c.89-.888 1.314-1.299 1.902-1.817a17.643 17.643 0 0 1 2.61-1.933c2.273-1.362 4.814-2.074 7.745-2.074s5.472.712 7.745 2.074c.916.55 1.758 1.183 2.61 1.933.589.518 1.013.929 1.902 1.817l.013.012c1.354 1.352 1.922 1.852 2.599 2.257.735.44 1.51.657 2.631.657.998 0 2.1-.386 3.383-1.284 1.572-1.1 3.272-2.886 5.048-5.372l8.138 5.812c-2.391 3.347-4.857 5.936-7.452 7.753-2.884 2.018-5.948 3.091-9.117 3.091-2.941 0-5.49-.713-7.769-2.078a17.627 17.627 0 0 1-2.619-1.938c-.59-.519-1.015-.93-1.906-1.82l-.013-.013c-1.351-1.348-1.917-1.846-2.59-2.25-.728-.436-1.494-.651-2.603-.651-1.109 0-1.875.215-2.603.651-.673.404-1.239.902-2.59 2.25l-.012.013c-.892.89-1.317 1.301-1.907 1.82-.855.752-1.7 1.388-2.62 1.938C66.74 104.287 64.192 105 61.25 105c-2.942 0-5.49-.714-7.768-2.08a17.654 17.654 0 0 1-2.615-1.939c-.588-.519-1.009-.927-1.902-1.82-1.355-1.355-1.918-1.852-2.592-2.256-.731-.439-1.503-.655-2.623-.655-.998 0-2.1.386-3.383 1.284-1.572 1.1-3.272 2.886-5.048 5.372z"
/></g
><defs><clipPath id="clip0"><path fill="#fff" d="M0 0h140v140H0z" /></clipPath></defs></svg
>

View File

@@ -0,0 +1,45 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
viewBox="0 0 127 74"
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
xmlns="http://www.w3.org/2000/svg"
><path
d="M.825 73.993l23.244-59.47A21.85 21.85 0 0144.42.625h14.014L35.19 60.096a21.85 21.85 0 01-20.352 13.897H.825z"
fill="url(#meilisearch_logo_svg__paint0_linear_0_6)"
/><path
d="M34.925 73.993l23.243-59.47A21.85 21.85 0 0178.52.626h14.013L69.29 60.096a21.85 21.85 0 01-20.351 13.897H34.925z"
fill="url(#meilisearch_logo_svg__paint1_linear_0_6)"
/><path
d="M69.026 73.993l23.244-59.47A21.85 21.85 0 01112.621.626h14.014l-23.244 59.47a21.851 21.851 0 01-20.352 13.897H69.026z"
fill="url(#meilisearch_logo_svg__paint2_linear_0_6)"
/><defs
><linearGradient
id="meilisearch_logo_svg__paint0_linear_0_6"
x1="126.635"
y1="-4.978"
x2="0.825"
y2="66.098"
gradientUnits="userSpaceOnUse"
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
><linearGradient
id="meilisearch_logo_svg__paint1_linear_0_6"
x1="126.635"
y1="-4.978"
x2="0.825"
y2="66.098"
gradientUnits="userSpaceOnUse"
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
><linearGradient
id="meilisearch_logo_svg__paint2_linear_0_6"
x1="126.635"
y1="-4.978"
x2="0.825"
y2="66.098"
gradientUnits="userSpaceOnUse"
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
></defs
></svg
>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<img
alt="minio logo"
class={isAbsolute ? 'w-7 absolute top-0 left-0 -m-3 -mt-5' : 'w-4 mx-auto'}
src="/minio.png"
/>

View File

@@ -0,0 +1,24 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'w-12 h-12 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
viewBox="0 0 220 105"
>
<g>
<path
fill="#FF6D5A"
d="M183.9,0.2c-9.8,0-18,6.7-20.3,15.8h-29.2c-11.5,0-20.8,9.3-20.8,20.8c0,5.7-4.7,10.4-10.4,10.4H99
c-2.3-9.1-10.5-15.8-20.3-15.8c-9.8,0-18,6.7-20.3,15.8H41.7c-2.3-9.1-10.5-15.8-20.3-15.8c-11.6,0-21,9.4-21,21
c0,11.6,9.4,21,21,21c9.8,0,18-6.7,20.3-15.8h16.7c2.3,9.1,10.5,15.8,20.3,15.8c9.7,0,17.9-6.6,20.3-15.6h4.2
c5.7,0,10.4,4.7,10.4,10.4c0,11.5,9.3,20.8,20.8,20.8h6.8c2.3,9.1,10.5,15.8,20.3,15.8c11.6,0,21-9.4,21-21c0-11.6-9.4-21-21-21
c-9.8,0-18,6.7-20.3,15.8h-6.8c-5.7,0-10.4-4.7-10.4-10.4c0-6.3-2.8-11.9-7.2-15.7c4.4-3.8,7.2-9.4,7.2-15.7
c0-5.7,4.7-10.4,10.4-10.4h29.2c2.3,9.1,10.5,15.8,20.3,15.8c11.6,0,21-9.4,21-21C204.9,9.6,195.5,0.2,183.9,0.2z M21.4,63
c-5.8,0-10.6-4.8-10.6-10.6s4.8-10.6,10.6-10.6S32,46.6,32,52.4S27.3,63,21.4,63z M78.7,63c-5.8,0-10.6-4.8-10.6-10.6
s4.8-10.6,10.6-10.6s10.6,4.8,10.6,10.6S84.6,63,78.7,63z M161.5,73.2c5.8,0,10.6,4.8,10.6,10.6s-4.8,10.6-10.6,10.6
s-10.6-4.8-10.6-10.6C150.9,77.9,155.7,73.2,161.5,73.2z M183.9,31.8c-5.8,0-10.6-4.8-10.6-10.6s4.8-10.6,10.6-10.6
s10.6,4.8,10.6,10.6C194.5,27,189.8,31.8,183.9,31.8z"
/>
</g>
</svg>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<img
alt="nocodb logo"
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
src="/nocodb.png"
/>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<img
alt="plausible logo"
class={isAbsolute ? 'w-9 absolute top-0 left-0 -m-4' : 'w-6 mx-auto'}
src="/plausible.png"
/>

View File

@@ -0,0 +1,83 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
version="1.0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 856.000000 856.000000"
preserveAspectRatio="xMidYMid meet"
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
>
<metadata> Created by potrace 1.11, written by Peter Selinger 2001-2013 </metadata>
<g
transform="translate(0.000000,856.000000) scale(0.100000,-0.100000)"
fill="currentColor"
stroke="none"
>
<path
d="M4027 8163 c-2 -2 -28 -5 -58 -7 -50 -4 -94 -9 -179 -22 -19 -2 -48
-6 -65 -9 -47 -6 -236 -44 -280 -55 -22 -6 -49 -12 -60 -15 -34 -6 -58 -13
-130 -36 -38 -13 -72 -23 -75 -24 -29 -6 -194 -66 -264 -96 -49 -22 -95 -39
-102 -39 -7 0 -19 -7 -28 -15 -8 -9 -18 -15 -21 -14 -7 1 -197 -92 -205 -101
-3 -3 -21 -13 -40 -24 -79 -42 -123 -69 -226 -137 -94 -62 -246 -173 -280
-204 -6 -5 -29 -25 -52 -43 -136 -111 -329 -305 -457 -462 -21 -25 -41 -47
-44 -50 -4 -3 -22 -26 -39 -52 -18 -25 -38 -52 -45 -60 -34 -35 -207 -308
-259 -408 -13 -25 -25 -47 -28 -50 -11 -11 -121 -250 -159 -346 -42 -105 -114
-321 -126 -374 l-7 -30 -263 0 c-245 0 -268 -2 -321 -21 -94 -35 -171 -122
-191 -216 -9 -39 -8 -852 0 -938 9 -87 16 -150 23 -195 3 -19 6 -48 8 -65 3
-29 14 -97 22 -140 3 -11 7 -36 10 -55 3 -19 9 -51 14 -70 5 -19 11 -46 14
-60 29 -138 104 -401 145 -505 5 -11 23 -58 42 -105 18 -47 42 -105 52 -130
11 -25 21 -49 22 -55 3 -10 109 -224 164 -330 18 -33 50 -89 71 -124 22 -34
40 -64 40 -66 0 -8 104 -161 114 -167 6 -4 7 -8 3 -8 -4 0 4 -12 18 -27 14
-15 25 -32 25 -36 0 -5 6 -14 13 -21 6 -7 21 -25 32 -41 11 -15 34 -44 50 -64
17 -21 41 -52 55 -70 13 -18 33 -43 45 -56 11 -13 42 -49 70 -81 100 -118 359
-369 483 -469 34 -27 62 -53 62 -57 0 -5 6 -8 13 -8 7 0 19 -9 27 -20 8 -11
19 -20 26 -20 6 0 19 -9 29 -20 10 -11 22 -20 27 -20 5 0 23 -13 41 -30 18
-16 37 -30 44 -30 6 0 13 -4 15 -8 3 -8 186 -132 194 -132 2 0 27 -15 56 -34
132 -83 377 -207 558 -280 36 -15 74 -31 85 -36 62 -26 220 -81 320 -109 79
-23 191 -53 214 -57 14 -3 28 -7 31 -9 4 -2 20 -7 36 -9 16 -3 40 -8 54 -11
14 -3 36 -8 50 -11 14 -2 36 -7 50 -10 13 -3 40 -8 60 -10 19 -2 46 -7 60 -10
54 -10 171 -25 320 -40 90 -9 613 -12 636 -4 11 5 28 4 37 -1 9 -6 17 -6 17
-1 0 4 10 8 23 9 29 0 154 12 192 18 17 3 46 7 65 9 70 10 131 20 183 32 16 3
38 7 50 9 45 7 165 36 252 60 50 14 100 28 112 30 12 3 34 10 48 15 14 5 25 7
25 4 0 -4 6 -2 13 3 6 6 30 16 52 22 22 7 47 15 55 18 8 4 17 7 20 7 10 2 179
68 240 94 96 40 342 159 395 191 17 10 53 30 80 46 28 15 81 47 118 71 37 24
72 44 76 44 5 0 11 3 13 8 2 4 30 25 63 47 33 22 62 42 65 45 3 3 50 38 105
79 55 40 105 79 110 85 6 6 24 22 40 34 85 65 465 430 465 447 0 3 8 13 18 23
9 10 35 40 57 66 22 27 47 56 55 65 8 9 42 52 74 96 32 44 71 96 85 115 140
183 358 576 461 830 12 30 28 69 36 85 24 56 123 355 117 355 -3 0 -1 6 5 13
6 6 14 30 18 52 10 48 9 46 17 65 5 13 37 155 52 230 9 42 35 195 40 231 34
235 40 357 40 804 l0 420 -24 44 c-46 87 -143 157 -231 166 -19 2 -144 4 -276
4 l-242 1 -36 118 c-21 64 -46 139 -56 166 -11 27 -20 52 -20 57 0 5 -11 33
-25 63 -14 30 -25 58 -25 61 0 18 -152 329 -162 333 -5 2 -8 10 -8 18 0 8 -4
14 -10 14 -5 0 -9 3 -8 8 3 9 -40 82 -128 217 -63 97 -98 145 -187 259 -133
171 -380 420 -559 564 -71 56 -132 102 -138 102 -5 0 -10 3 -10 8 0 4 -25 23
-55 42 -30 19 -55 38 -55 43 0 4 -6 7 -13 7 -7 0 -22 8 -33 18 -11 9 -37 26
-59 37 -21 11 -44 25 -50 30 -41 37 -413 220 -540 266 -27 9 -61 22 -75 27
-14 5 -28 10 -32 11 -4 1 -28 10 -53 21 -25 11 -46 19 -48 18 -2 -1 -109 29
-137 40 -13 4 -32 9 -65 16 -5 1 -16 5 -22 9 -7 5 -13 6 -13 3 0 -2 -15 0 -32
5 -18 5 -44 11 -58 14 -14 3 -36 7 -50 10 -14 3 -50 9 -80 15 -30 6 -64 12
-75 14 -11 2 -45 6 -75 10 -30 4 -71 9 -90 12 -19 3 -53 6 -75 7 -22 1 -44 5
-50 8 -11 7 -542 9 -548 2z m57 -404 c7 10 436 8 511 -3 22 -3 60 -8 85 -11
25 -2 56 -6 70 -9 14 -2 43 -7 65 -10 38 -5 58 -9 115 -21 14 -3 34 -7 45 -9
11 -2 58 -14 105 -26 47 -12 92 -23 100 -25 35 -7 279 -94 308 -109 17 -9 34
-16 37 -16 3 1 20 -6 38 -14 17 -8 68 -31 112 -51 44 -20 82 -35 84 -35 2 1 7
-3 10 -8 3 -5 43 -28 88 -51 45 -23 87 -48 93 -56 7 -8 17 -15 22 -15 12 0
192 -121 196 -132 2 -4 8 -8 13 -8 10 0 119 -86 220 -172 102 -87 256 -244
349 -357 25 -30 53 -63 63 -73 9 -10 17 -22 17 -28 0 -5 3 -10 8 -10 4 0 25
-27 46 -60 22 -33 43 -60 48 -60 4 0 8 -5 8 -11 0 -6 11 -25 25 -43 14 -18 25
-38 25 -44 0 -7 4 -12 8 -12 5 0 16 -15 25 -32 9 -18 30 -55 47 -83 46 -77
161 -305 154 -305 -4 0 -2 -6 4 -12 6 -7 23 -47 40 -88 16 -41 33 -84 37 -95
5 -11 9 -22 10 -25 0 -3 11 -36 24 -73 13 -38 21 -70 19 -73 -3 -2 -1386 -3
-3075 -2 l-3071 3 38 110 c47 137 117 301 182 425 62 118 167 295 191 320 9
11 17 22 17 25 0 7 39 63 58 83 6 7 26 35 44 60 18 26 37 52 43 57 6 6 34 37
61 70 48 59 271 286 329 335 17 14 53 43 80 65 28 22 52 42 55 45 3 3 21 17
40 30 19 14 40 28 45 32 40 32 105 78 109 78 3 0 28 16 55 35 26 19 53 35 58
35 5 0 18 8 29 18 17 15 53 35 216 119 118 60 412 176 422 166 3 -4 6 -2 6 4
0 6 12 13 28 16 15 3 52 12 82 21 30 9 63 19 73 21 10 2 27 7 37 10 10 3 29 8
42 10 13 3 48 10 78 16 30 7 61 12 68 12 6 0 12 4 12 9 0 5 5 6 10 3 6 -4 34
-2 63 4 51 11 71 13 197 26 36 4 67 9 69 11 2 2 10 -1 17 -7 8 -6 14 -7 18 0z"
/>
</g>
</svg>

View File

@@ -0,0 +1,159 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-10 h-10 mx-auto'}
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 640 640"
width="640"
height="640"
><defs
><path
d="M407.55 916.24C471.25 916.24 522.89 967.88 522.89 1031.57C522.89 1113.88 522.89 1245.44 522.89 1327.74C522.89 1391.44 471.25 1443.08 407.55 1443.08C325.25 1443.08 193.68 1443.08 111.38 1443.08C47.69 1443.08 -3.95 1391.44 -3.95 1327.74C-3.95 1245.44 -3.95 1113.88 -3.95 1031.57C-3.95 967.88 47.69 916.24 111.38 916.24C193.68 916.24 325.25 916.24 407.55 916.24Z"
id="a1LdTs1gvU"
/><linearGradient
id="gradientcoH7TNh19"
gradientUnits="userSpaceOnUse"
x1="256.07"
y1="1132.14"
x2="609.11"
y2="1480.42"
><stop style="stop-color: #c2efd2;stop-opacity: 1" offset="0%" /><stop
style="stop-color: #8ff0e5;stop-opacity: 1"
offset="100%"
/></linearGradient
><path
d="M-467.41 394.63C-467.41 554.76 -597.42 684.76 -757.55 684.76C-917.68 684.76 -1047.69 554.76 -1047.69 394.63C-1047.69 234.5 -917.68 104.49 -757.55 104.49C-597.42 104.49 -467.41 234.5 -467.41 394.63Z"
id="a1uaEBd4xM"
/><path
d="M-96.99 -586.14C-57.24 -619.85 -5.79 -604.75 19.26 -580.46C31.43 -568.66 56.57 -546.36 40.97 -491.67C32.76 -462.87 10.41 -436.4 -26.05 -412.27C-15.07 -377.85 -5.6 -344.76 2.36 -313C14.29 -265.36 13.55 -189.67 -26.05 -155.4C-67.27 -119.73 -166.91 -104.09 -234.24 -103.09C-301.57 -102.1 -406.19 -113.09 -461.6 -155.4C-517.01 -197.7 -512.24 -257.07 -498.04 -313C-488.58 -350.28 -476.43 -383.38 -461.6 -412.27C-505.54 -441.3 -530.54 -467.76 -536.6 -491.67C-545.68 -527.54 -530.93 -565.61 -501.12 -586.14C-471.31 -606.67 -435.18 -606.9 -400.45 -586.14C-377.3 -572.3 -354.79 -542.13 -332.92 -495.62C-287.85 -505.25 -254.96 -509.57 -234.24 -508.6C-214.74 -507.68 -186.57 -503.36 -149.72 -495.62C-135.81 -537.95 -118.23 -568.12 -96.99 -586.14Z"
id="f8p7QlEjN3"
/><linearGradient
id="gradienta4Tg99ZOOp"
gradientUnits="userSpaceOnUse"
x1="-440.25"
y1="-388.59"
x2="-100.49"
y2="-147.33"
><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
style="stop-color: #7ae6a1;stop-opacity: 1"
offset="100%"
/></linearGradient
><path
d="M-86.03 -10.69C-61.35 -10.69 -41.34 9.32 -41.34 34.01C-41.34 119.07 -41.34 329.58 -41.34 414.65C-41.34 439.33 -61.35 459.34 -86.03 459.34C-136.01 459.34 -241.25 459.34 -291.23 459.34C-315.92 459.34 -335.93 439.33 -335.93 414.65C-335.93 329.58 -335.93 119.07 -335.93 34.01C-335.93 9.32 -315.92 -10.69 -291.23 -10.69C-241.25 -10.69 -136.01 -10.69 -86.03 -10.69Z"
id="d32ZZRxd1S"
/><linearGradient
id="gradientb1JxIe4xUm"
gradientUnits="userSpaceOnUse"
x1="-791.65"
y1="-33.27"
x2="892.1"
y2="418.94"
><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
style="stop-color: #5ae98f;stop-opacity: 1"
offset="100%"
/></linearGradient
><path
d="M-257.95 458.12C-247.92 449.62 -234.93 453.43 -228.61 459.56C-225.54 462.54 -219.19 468.17 -223.13 481.97C-225.2 489.24 -230.84 495.92 -240.05 502.01C-237.27 510.7 -234.88 519.06 -232.88 527.07C-229.86 539.1 -230.05 558.21 -240.05 566.86C-250.45 575.86 -275.6 579.81 -292.6 580.06C-309.6 580.31 -336.01 577.54 -349.99 566.86C-363.98 556.18 -362.77 541.19 -359.19 527.07C-356.8 517.66 -353.73 509.31 -349.99 502.01C-361.08 494.69 -367.39 488.01 -368.92 481.97C-371.22 472.92 -367.49 463.31 -359.97 458.12C-352.44 452.94 -343.32 452.88 -334.56 458.12C-328.71 461.62 -323.03 469.23 -317.51 480.97C-306.13 478.54 -297.83 477.45 -292.6 477.7C-287.68 477.93 -280.56 479.02 -271.26 480.97C-267.75 470.29 -263.32 462.67 -257.95 458.12Z"
id="b19LRRbPrG"
/><path
d="M490.4 235.64C544.09 358.38 544.09 435.34 490.4 466.5C409.85 513.24 199.96 527.49 139.54 455.64C99.26 407.74 99.26 334.4 139.54 235.64C180.5 168.18 238.71 134.45 314.17 134.45C389.64 134.45 448.38 168.18 490.4 235.64Z"
id="bN5StdyPU"
/><linearGradient
id="gradientb1HT15TsY0"
gradientUnits="userSpaceOnUse"
x1="259.78"
y1="261.15"
x2="463.85"
y2="456.49"
><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
style="stop-color: #86e6a9;stop-opacity: 1"
offset="100%"
/></linearGradient
><path
d="M393.81 -775.89C428.26 -748.09 439.99 -725.54 429 -708.22C412.51 -682.24 353.16 -646.07 324.5 -657.93C305.39 -665.83 294.22 -687.32 290.97 -722.41C292.69 -748.43 304.61 -767.19 326.73 -778.69C348.85 -790.19 371.21 -789.26 393.81 -775.89Z"
id="arh6miPP2"
/><linearGradient
id="gradientc2g6rBSAiq"
gradientUnits="userSpaceOnUse"
x1="330.1"
y1="-733.26"
x2="419.69"
y2="-707.1"
><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
style="stop-color: #86e6a9;stop-opacity: 1"
offset="100%"
/></linearGradient
><path
d="M675.36 -369.24C669.97 -325.31 657.02 -303.43 636.51 -303.61C605.74 -303.87 543.67 -335.15 538.59 -365.74C535.2 -386.14 547.54 -406.99 575.61 -428.29C598.61 -440.58 620.83 -440.37 642.29 -427.67C663.74 -414.97 674.77 -395.49 675.36 -369.24Z"
id="a2VENFzCvL"
/><linearGradient
id="gradientc18GuJy4sZ"
gradientUnits="userSpaceOnUse"
x1="605.5"
y1="-400.8"
x2="630.64"
y2="-310.92"
><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
style="stop-color: #86e6a9;stop-opacity: 1"
offset="100%"
/></linearGradient
></defs
><g
><g
><g><use xlink:href="#a1LdTs1gvU" opacity="1" fill="url(#gradientcoH7TNh19)" /></g><g
><use xlink:href="#a1uaEBd4xM" opacity="1" fill="#ebf0ed" fill-opacity="1" /></g
><g
><use xlink:href="#f8p7QlEjN3" opacity="1" fill="url(#gradienta4Tg99ZOOp)" /><g
><use
xlink:href="#f8p7QlEjN3"
opacity="1"
fill-opacity="0"
stroke="#ffffff"
stroke-width="98"
stroke-opacity="0.57"
/></g
></g
><g
><use xlink:href="#d32ZZRxd1S" opacity="1" fill="url(#gradientb1JxIe4xUm)" /><g
><use
xlink:href="#d32ZZRxd1S"
opacity="1"
fill-opacity="0"
stroke="#f2f2f2"
stroke-width="60"
stroke-opacity="0.51"
/></g
></g
><g
><use xlink:href="#b19LRRbPrG" opacity="1" fill="#d8ad9a" fill-opacity="1" /><g
><use
xlink:href="#b19LRRbPrG"
opacity="1"
fill-opacity="0"
stroke="#ffffff"
stroke-width="17"
stroke-opacity="1"
/></g
></g
><g
><use xlink:href="#bN5StdyPU" opacity="1" fill="url(#gradientb1HT15TsY0)" /><g
><use
xlink:href="#bN5StdyPU"
opacity="1"
fill-opacity="0"
stroke="#f2f2f2"
stroke-width="200"
stroke-opacity="0.51"
/></g
></g
><g><use xlink:href="#arh6miPP2" opacity="1" fill="url(#gradientc2g6rBSAiq)" /></g><g
><use xlink:href="#a2VENFzCvL" opacity="1" fill="url(#gradientc18GuJy4sZ)" /></g
></g
></g
></svg
>

View File

@@ -0,0 +1,22 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
viewBox="0 0 128 128"
>
<path
d="M3.656 45.043s-3.027-2.191.61-5.113l8.468-7.594s2.426-2.559 4.989-.328l78.175 59.328v28.45s-.039 4.468-5.757 3.976zm0 0"
fill="#2489ca"
/><path
d="M23.809 63.379L3.656 81.742s-2.07 1.543 0 4.305l9.356 8.527s2.222 2.395 5.508-.328l21.359-16.238zm0 0"
fill="#1070b3"
/><path
d="M59.184 63.531l36.953-28.285-.239-28.297S94.32.773 89.055 3.99L39.879 48.851zm0 0"
fill="#0877b9"
/><path
d="M90.14 123.797c2.145 2.203 4.747 1.48 4.747 1.48l28.797-14.222c3.687-2.52 3.171-5.645 3.171-5.645V20.465c0-3.735-3.812-5.024-3.812-5.024L98.082 3.38c-5.453-3.379-9.027.61-9.027.61s4.593-3.317 6.843 2.96v112.317c0 .773-.164 1.53-.492 2.214-.656 1.332-2.086 2.57-5.504 2.051zm0 0"
fill="#3c99d4"
/>
</svg>

View File

@@ -0,0 +1,35 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
id="Icon"
x="0px"
y="0px"
viewBox="0 0 1024 1024"
style="enable-background:new 0 0 1024 1024;"
xml:space="preserve"
>
<style type="text/css">
.st0 {
fill: #175ddc;
}
.st1 {
fill: #ffffff;
}
</style>
<path
id="Background"
class="st0"
d="M1024,864c0,88.4-71.6,160-160,160H160C71.6,1024,0,952.4,0,864V160C0,71.6,71.6,0,160,0h704 c88.4,0,160,71.6,160,160V864z"
/>
<path
id="Identity"
class="st1"
d="M829.8,128.6c-6.5-6.5-14.2-9.7-23-9.7H217.2c-8.9,0-16.5,3.2-23,9.7c-6.5,6.5-9.7,14.2-9.7,23 v393.1c0,29.3,5.7,58.4,17.1,87.3c11.4,28.8,25.6,54.4,42.5,76.8c16.9,22.3,37,44.1,60.4,65.3c23.4,21.2,45,38.7,64.7,52.7 c19.8,14,40.4,27.2,61.9,39.7c21.5,12.5,36.8,20.9,45.8,25.3c9,4.4,16.3,7.9,21.7,10.2c4.1,2,8.5,3.1,13.3,3.1c4.8,0,9.2-1,13.3-3.1 c5.5-2.4,12.7-5.8,21.8-10.2c9-4.4,24.3-12.9,45.8-25.3c21.5-12.5,42.1-25.7,61.9-39.7c19.8-14,41.4-31.6,64.8-52.7 c23.4-21.2,43.5-42.9,60.4-65.3c16.9-22.4,31-47.9,42.5-76.8c11.4-28.8,17.1-57.9,17.1-87.3V151.7 C839.6,142.8,836.3,135.1,829.8,128.6z M753.8,548.4c0,142.3-241.8,264.9-241.8,264.9V203.1h241.8 C753.8,203.1,753.8,406.1,753.8,548.4z"
/>
</svg>

View File

@@ -0,0 +1,12 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'} viewBox="0 0 128 128">
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="white"
d="M64.094 126.224c34.275-.052 62.021-27.933 62.021-62.325 0-33.833-27.618-61.697-60.613-62.286C30.85.995 1.894 29.113 1.885 63.21c-.01 35.079 27.612 63.064 62.209 63.014zM63.993 4.63c32.907-.011 59.126 26.725 59.116 60.28-.011 31.679-26.925 58.18-59.092 58.187-32.771.007-59.125-26.563-59.124-59.608.002-32.193 26.766-58.848 59.1-58.859zM39.157 35.896c.538 1.793-.968 2.417-2.569 2.542-1.685.13-3.369.257-5.325.406 6.456 19.234 12.815 38.183 19.325 57.573.464-.759.655-.973.739-1.223 3.574-10.682 7.168-21.357 10.651-32.069.318-.977.16-2.271-.188-3.275-1.843-5.32-4.051-10.524-5.667-15.908-1.105-3.686-2.571-6.071-6.928-5.644-.742.073-1.648-1.524-2.479-2.349 1.005-.6 2.003-1.704 3.017-1.719a849.593 849.593 0 0126.618.008c1.018.017 2.016 1.15 3.021 1.765-.88.804-1.639 2.01-2.668 2.321-1.651.498-3.482.404-5.458.58l19.349 57.56c2.931-9.736 5.658-18.676 8.31-27.639 2.366-8.001.956-15.473-3.322-22.52-1.286-2.119-2.866-4.175-3.595-6.486-.828-2.629-1.516-5.622-1.077-8.259.745-4.469 4.174-6.688 8.814-7.113C74.333.881 34.431 9.317 19.728 34.922c5.66-.261 11.064-.604 16.472-.678 1.022-.013 2.717.851 2.957 1.652zm10.117 77.971c-.118.345-.125.729-.218 1.302 10.943 3.034 21.675 2.815 32.659-.886l-16.78-45.96c-5.37 15.611-10.52 30.575-15.661 45.544zm-8.456-2.078l-25.281-69.35c-11.405 22.278-2.729 56.268 25.281 69.35zm76.428-44.562c.802-10.534-2.832-25.119-5.97-27.125-.35 3.875-.106 8.186-1.218 12.114-2.617 9.255-5.817 18.349-8.899 27.468-3.35 9.912-6.832 19.779-10.257 29.666 16.092-9.539 24.935-23.618 26.344-42.123z"
/>
</svg>

View File

@@ -0,0 +1,4 @@
{
"fr": "Français",
"en": "English"
}

View File

@@ -0,0 +1,341 @@
{
"layout": {
"update_done": "Update completed.",
"wait_new_version_startup": "Waiting for the new version to start...",
"new_version": "New version reachable. Reloading...",
"switch_to_a_different_team": "Switch to a different team...",
"update_available": "Update available"
},
"error": {
"you_can_find_your_way_back": "You can find your way back",
"here": "here",
"you_are_lost": "Ooops you are lost! But don't be afraid!"
},
"index": {
"dashboard": "Dashboard",
"applications": "Applications",
"destinations": "Destinations",
"git_sources": "Git Sources",
"databases": "Databases",
"services": "Services",
"teams": "Teams",
"not_implemented_yet": "Not implemented yet",
"database": "Database",
"settings": "Settings",
"global_settings": "Global Settings",
"secret": "Secret",
"team": "Team",
"logout": "Logout"
},
"login": {
"already_logged_in": "Already logged in...",
"authenticating": "Authenticating...",
"login": "Login"
},
"forms": {
"password": "Password",
"email": "Email address",
"passwords_not_match": "Passwords do not match.",
"password_again": "Password again",
"save": "Save",
"saving": "Saving...",
"name": "Name",
"value": "Value",
"action": "Action",
"is_required": "is required.",
"add": "Add",
"set": "Set",
"remove": "Remove",
"path": "Path",
"confirm_continue": "Are you sure to continue?",
"must_be_stopped_to_modify": "Must be stopped to modify.",
"port": "Port",
"default": "default",
"base_directory": "Base Directory",
"publish_directory": "Publish Directory",
"generated_automatically_after_start": "Generated automatically after start",
"roots_password": "Root's Password",
"root_user": "Root User",
"eg": "eg",
"user": "User",
"loading": "Loading...",
"version": "Version",
"host": "Host",
"already_used_for": "<span class=\"text-red-500\">{{type}}</span> already used for",
"configuration": "Configuration",
"engine": "Engine",
"network": "Network",
"ip_address": "IP Address",
"ssh_private_key": "SSH Private Key",
"type": "Type",
"html_url": "HTML URL",
"api_url": "API URL",
"organization": "Organization",
"new_password": "New password",
"super_secure_new_password": "Super secure new password",
"submit": "Submit",
"default_email_address": "Default Email Address",
"default_password": "Default Password",
"username": "Username",
"root_db_user": "Root DB User",
"root_db_password": "Root DB Password",
"api_port": "API Port",
"verifying": "Verifying",
"verify_emails_without_smtp": "Verify emails without SMTP",
"extra_config": "Extra Config",
"select_a_service": "Select a Service",
"select_a_service_version": "Select a Service version",
"removing": "Removing...",
"remove_domain": "Remove domain",
"public_port_range": "Public Port Range",
"public_port_range_explainer": "Ports used to expose databases/services/internal services.<br> Add them to your firewall (if applicable).<br><br>You can specify a range of ports, eg: <span class='text-yellow-500 font-bold'>9000-9100</span>",
"no_actions_available": "No actions available",
"admin_api_key": "Admin API key"
},
"register": {
"register": "Register",
"registering": "Registering...",
"first_user": "You are registering the first user. It will be the administrator of your Coolify instance."
},
"reset": {
"reset_password": "Reset",
"invalid_secret_key": "Invalid secret key.",
"secret_key": "Secret Key",
"find_path_secret_key": "You can find it in ~/coolify/.env (COOLIFY_SECRET_KEY)"
},
"application": {
"configuration": {
"buildpack": {
"choose_this_one": "Choose this one..."
},
"branch_already_in_use": "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?",
"no_repositories_configured": "No repositories configured for your Git Application.",
"configure_it_now": "Configure it now",
"loading_repositories": "Loading repositories ...",
"select_a_repository": "Please select a repository",
"loading_branches": "Loading branches ...",
"select_a_repository_first": "Please select a repository first",
"select_a_branch": "Please select a branch",
"loading_groups": "Loading groups...",
"select_a_group": "Please select a group",
"loading_projects": "Loading projects...",
"select_a_project": "Please select a project",
"no_projects_found": "No projects found",
"no_branches_found": "No branches found",
"configure_build_pack": "Configure Build Pack",
"scanning_repository_suggest_build_pack": "Scanning repository to suggest a build pack for you...",
"found_lock_file": "Found lock file for {{packageManager}}.<br>Using it for predefined commands commands.",
"configure_destination": "Configure Destination",
"no_configurable_destination": "No configurable Destination found",
"select_a_repository_project": "Select a Repository / Project",
"select_a_git_source": "Select a Git Source",
"no_configurable_git": "No configurable Git Source found",
"configuration_missing": "Configuration missing"
},
"build": {
"queued_waiting_exec": "Queued and waiting for execution.",
"build_logs_of": "Build logs of",
"running": "Running",
"queued": "Queued",
"finished_in": "Finished in",
"load_more": "Load More",
"no_logs": "No logs found",
"waiting_logs": "Waiting for the logs..."
},
"preview": {
"need_during_buildtime": "Need during buildtime?",
"setup_secret_app_first": "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.",
"values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments.",
"redeploy": "Redeploy",
"no_previews_available": "No previews available"
},
"secrets": {
"secret_saved": "Secret saved.",
"use_isbuildsecret": "Use isBuildSecret",
"secrets_for": "Secrets for"
},
"storage": {
"path_is_required": "Path is required.",
"storage_saved": "Storage saved.",
"storage_updated": "Storage updated.",
"storage_deleted": "Storage deleted.",
"persistent_storage_explainer": "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."
},
"deployment_queued": "Deployment queued.",
"confirm_to_delete": "Are you sure you would like to delete '{{name}}'?",
"stop_application": "Stop application",
"permission_denied_stop_application": "You do not have permission to stop the application.",
"rebuild_application": "Rebuild application",
"permission_denied_rebuild_application": "You do not have permission to rebuild application.",
"build_and_start_application": "Build and start application",
"permission_denied_build_and_start_application": "You do not have permission to Build and start application.",
"configurations": "Configurations",
"secret": "Secrets",
"persistent_storage": "Persistent Storage",
"previews": "Previews",
"logs": "Application Logs",
"build_logs": "Build Logs",
"delete_application": "Delete application",
"permission_denied_delete_application": "You do not have permission to delete this application",
"domain_already_in_use": "Domain {{domain}} is already used.",
"dns_not_set_error": "DNS not set correctly or propogated for {{domain}}.<br><br>Please check your DNS settings.",
"domain_required": "Domain is required.",
"settings_saved": "Settings saved.",
"dns_not_set_partial_error": "DNS not set",
"domain_not_valid": "Could not resolve domain or it's not pointing to the server IP address.<br><br>Please check your DNS configuration and try again.",
"git_source": "Git Source",
"git_repository": "Git Repository",
"build_pack": "Build Pack",
"base_image": "Deployment Image",
"base_image_explainer": "Image that will be used for the deployment.",
"base_build_image": "Build Image",
"base_build_image_explainer": "Image that will be used during the build process.",
"destination": "Destination",
"application": "Application",
"url_fqdn": "URL (FQDN)",
"domain_fqdn": "Domain (FQDN)",
"https_explainer": "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>",
"ssl_www_and_non_www": "Generate SSL for www and non-www?",
"ssl_explainer": "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.",
"install_command": "Install Command",
"build_command": "Build Command",
"start_command": "Start Command",
"directory_to_use_explainer": "Directory to use as the base for all commands.<br>Could be useful with <span class='text-green-500 font-bold'>monorepos</span>.",
"publish_directory_explainer": "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>.",
"features": "Features",
"enable_automatic_deployment": "Enable Automatic Deployment",
"enable_auto_deploy_webhooks": "Enable automatic deployment through webhooks.",
"enable_mr_pr_previews": "Enable MR/PR Previews",
"expose_a_port": "Expose a port",
"enable_preview_deploy_mr_pr_requests": "Enable preview deployments from pull or merge requests.",
"debug_logs": "Debug Logs",
"enable_debug_log_during_build": "Enable debug logs during build phase.<br><span class='text-red-500 font-bold'>Sensitive information</span> could be visible and saved in logs.",
"cant_activate_auto_deploy_without_repo": "Cannot activate automatic deployments until only one application is defined for this repository / branch.",
"no_applications_found": "No applications found",
"secret__batch_dot_env": "Paste .env file",
"batch_secrets": "Batch add secrets"
},
"general": "General",
"database": {
"default_database": "Default Database",
"generated_automatically_after_set_to_public": "Generated automatically after set to public",
"connection_string": "Connection String",
"set_public": "Set it public",
"warning_database_public": "Your database will be reachable over the internet. <br>Take security seriously in this case!",
"change_append_only_mode": "Change append only mode",
"warning_append_only": "Useful if you would like to restore redis data from a backup.<br><span class='font-bold text-white'>Database restart is required.</span>",
"select_database_type": "Select a Database type",
"select_database_version": "Select a Database version",
"confirm_stop": "Are you sure you would like to stop {{name}}?",
"stop_database": "Stop database",
"permission_denied_stop_database": "You do not have permission to stop the database.",
"start_database": "Start database",
"permission_denied_start_database": "You do not have permission to start the database.",
"delete_database": "Delete Database",
"permission_denied_delete_database": "You do not have permission to delete a Database",
"no_databases_found": "No databases found",
"logs": "Database Logs"
},
"destination": {
"delete_destination": "Delete Destination",
"permission_denied_delete_destination": "You do not have permission to delete this destination",
"add_to_coolify": "Add to Coolify",
"coolify_proxy_stopped": "Coolify Proxy stopped!",
"coolify_proxy_started": "Coolify Proxy started!",
"confirm_restart_proxy": "Are you sure you want to restart the proxy? Everything will be reconfigured in ~10 secs.",
"coolify_proxy_restarting": "Coolify Proxy restarting...",
"restarting_please_wait": "Restarting... please wait...",
"force_restart_proxy": "Force restart proxy",
"use_coolify_proxy": "Use Coolify Proxy?",
"no_destination_found": "No destination found",
"new_error_network_already_exists": "Network {{network}} already configured for another team!",
"new": {
"saving_and_configuring_proxy": "Saving and configuring proxy...",
"install_proxy": "This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker).<br><br>Databases will have their own proxy.",
"add_new_destination": "Add New Destination",
"predefined_destinations": "Predefined destinations"
}
},
"sources": {
"local_docker": "Local Docker",
"remote_docker": "Remote Docker",
"organization_explainer": "Fill it if you would like to use an organization's as your Git Source. Otherwise your user will be used."
},
"source": {
"new": {
"git_source": "Add New Git Source",
"official_providers": "Official providers"
},
"no_git_sources_found": "No git sources found",
"delete_git_source": "Delete Git Source",
"permission_denied": "You do not have permission to delete a Git Source",
"create_new_app": "Create new {{name}} App",
"change_app_settings": "Change {{name}} App Settings",
"install_repositories": "Install Repositories",
"application_id": "Application ID",
"group_name": "Group Name",
"oauth_id": "OAuth ID",
"oauth_id_explainer": "The OAuth ID is the unique identifier of the GitLab application. <br>You can find it <span class='font-bold text-orange-600' >in the URL</span> of your GitLab OAuth Application.",
"register_oauth_gitlab": "Register new OAuth application on GitLab",
"gitlab": {
"self_hosted": "Instance-wide application (self-hosted)",
"user_owned": "User owned application",
"group_owned": "Group owned application",
"gitlab_application_type": "GitLab Application Type",
"already_configured": "GitLab App is already configured."
},
"github": {
"redirecting": "Redirecting to Github..."
}
},
"services": {
"all_email_verified": "All emails are verified. You can login now.",
"generate_www_non_www_ssl": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-pink-600'>both DNS entries</span> set in advance.<br><br>Service needs to be restarted."
},
"service": {
"stop_service": "Stop Service",
"permission_denied_stop_service": "You do not have permission to stop the service.",
"start_service": "Start Service",
"permission_denied_start_service": "You do not have permission to start the service.",
"delete_service": "Delete Service",
"permission_denied_delete_service": "You do not have permission to delete a service.",
"no_service": "No services found",
"logs": "Service Logs"
},
"setting": {
"change_language": "Change Language",
"permission_denied": "You do not have permission to do this. \\nAsk an admin to modify your permissions.",
"domain_removed": "Domain removed",
"ssl_explainer": "If you specify <span class='text-yellow-500 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-yellow-500 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa.<br><br><span class='text-yellow-500 font-bold'>WARNING:</span> If you change an already set domain, it will brake webhooks and other integrations! You need to manually update them.",
"must_remove_domain_before_changing": "Must remove the domain before you can change this setting.",
"registration_allowed": "Registration allowed?",
"registration_allowed_explainer": "Allow further registrations to the application. <br>It's turned off after the first registration.",
"coolify_proxy_settings": "Coolify Proxy Settings",
"credential_stat_explainer": "Credentials for <a class=\"text-white font-bold\" href=\"{{link}}\" target=\"_blank\">stats</a> page.",
"auto_update_enabled": "Auto update enabled?",
"auto_update_enabled_explainer": "Enable automatic updates for Coolify. It will be done automatically behind the scenes, if there is no build process running.",
"generate_www_non_www_ssl": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-yellow-500'>both DNS entries</span> set in advance.",
"is_dns_check_enabled": "DNS check enabled?",
"is_dns_check_enabled_explainer": "You can disable DNS check before creating SSL certificates.<br><br>Turning it off is useful when Coolify is behind a reverse proxy or tunnel."
},
"team": {
"pending_invitations": "Pending invitations",
"accept": "Accept",
"delete": "Delete",
"member": "member(s)",
"root": "(root)",
"invited_with_permissions": "Invited to <span class=\"font-bold text-pink-600\">{{teamName}}</span> with <span class=\"font-bold text-rose-600\">{{permission}}</span> permission.",
"members": "Members",
"root_team_explainer": "This is the <span class='text-red-500 font-bold'>root</span> team. That means members of this group can manage instance wide settings and have all the priviliges in Coolify (imagine like root user on Linux).",
"permission": "Permission",
"you": "(You)",
"promote_to": "Promote to {{grade}}",
"revoke_invitation": "Revoke invitation",
"pending_invitation": "Pending invitation",
"invite_new_member": "Invite new member",
"send_invitation": "Send invitation",
"invite_only_register_explainer": "You can only invite registered users.",
"admin": "Admin",
"read": "Read"
}
}

View File

@@ -0,0 +1,323 @@
{
"application": {
"application": "Application",
"build": {
"build_logs_of": "Créer des journaux de",
"finished_in": "Fini en",
"load_more": "Charger plus",
"no_logs": "Aucun journal trouvé",
"queued": "En file d'attente",
"queued_waiting_exec": "En file d'attente et en attente d'exécution.",
"running": "Fonctionnement",
"waiting_logs": "En attente des logs..."
},
"build_and_start_application": "Build et démarrer l'application",
"build_command": "Commande Build",
"build_logs": "Créer des journaux",
"build_pack": "Pack de Build",
"cant_activate_auto_deploy_without_repo": "Impossible d'activer les déploiements automatiques tant qu'une seule application n'est pas définie pour ce dépôt/branche.",
"configuration": {
"branch_already_in_use": "Cette branche est déjà utilisée par une autre application. \nLes webhooks ne fonctionneront pas dans ce cas pour les deux applications. \nÊtes-vous sûr de vouloir l'utiliser ?",
"buildpack": {
"choose_this_one": "Choisir celui-ci..."
},
"configuration_missing": "Configuration manquante",
"configure_build_pack": "Configurer le pack de build",
"configure_destination": "Configurer la destination",
"configure_it_now": "Configurez-le maintenant",
"found_lock_file": "Fichier .lock trouvé pour <span class=\"font-bold text-orange-500 pl-1\">{{packageManager}}</span>. \nL'utiliser pour les commandes prédéfinies.",
"loading_branches": "Chargement des branches...",
"loading_groups": "Chargement des groupes...",
"loading_projects": "Chargement des projets...",
"loading_repositories": "Chargement des dépôts Git...",
"no_branches_found": "Aucune branche trouvée",
"no_configurable_destination": "Aucune destination configurable trouvée",
"no_configurable_git": "Aucune source Git configurable trouvée",
"no_projects_found": "Aucun projet trouvé",
"no_repositories_configured": "Aucun dépôt Git configuré pour votre application.",
"scanning_repository_suggest_build_pack": "Analyse du dépôt pour vous suggérer un pack de Build...",
"select_a_branch": "Veuillez sélectionner une branche",
"select_a_git_source": "Sélectionnez une source Git",
"select_a_group": "Veuillez sélectionner un groupe",
"select_a_project": "Veuillez sélectionner un projet",
"select_a_repository": "Veuillez sélectionner un dépôt",
"select_a_repository_first": "Veuillez d'abord sélectionner un dépôt",
"select_a_repository_project": "Sélectionnez un dépôt / projet"
},
"configurations": "Configurations",
"confirm_to_delete": "Voulez-vous vraiment supprimer '{{name}}'?",
"debug_logs": "Journaux de débogage",
"delete_application": "Supprimer l'application",
"deployment_queued": "Déploiement en file d'attente.",
"destination": "Destination",
"directory_to_use_explainer": "Répertoire à utiliser comme base pour toutes les commandes.<br>Pourrait être utile avec <span class='text-green-500 font-bold'>monorepos</span>.",
"dns_not_set_error": "DNS non défini ou propagé pour {{domain}}.<br><br>Veuillez vérifier vos paramètres DNS.",
"dns_not_set_partial_error": "DNS non défini",
"domain_already_in_use": "Le domaine {{domain}} est déjà utilisé.",
"domain_fqdn": "Domaine (FQDN)",
"url_fqdn": "URL (FQDN)",
"enable_auto_deploy_webhooks": "Activez le déploiement automatique via des webhooks.",
"enable_automatic_deployment": "Activer le déploiement automatique",
"enable_debug_log_during_build": "Activez les journaux de débogage pendant la phase de build.<br><span class='text-red-500 font-bold'>Les informations sensibles</span> peuvent être visibles et enregistrées dans les journaux.",
"enable_mr_pr_previews": "Activer les aperçus MR/PR",
"enable_preview_deploy_mr_pr_requests": "Activez les déploiements de prévisualisation à partir de demandes d'extraction ou de fusion.",
"expose_a_port": "Exposer un port",
"features": "Caractéristiques",
"git_repository": "Dépôt Git",
"git_source": "Source Git",
"https_explainer": "Si vous spécifiez <span class='text-green-500 font-bold'>https</span>, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.<br>Si vous spécifiez <span class='text-green-500 font-bold'>www</span>, l'application sera redirigée (302) à partir de non-www et vice versa \n.<br><br>Pour modifier le domaine, vous devez d'abord arrêter l'application.<br><br><span class='text-white font-bold'>Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.</span>",
"install_command": "Commande d'installation",
"logs": "Journaux des applications",
"no_applications_found": "Aucune application trouvée",
"permission_denied_build_and_start_application": "Vous n'êtes pas autorisé à créer et à démarrer l'application.",
"permission_denied_delete_application": "Vous n'êtes pas autorisé à supprimer cette application",
"permission_denied_rebuild_application": "Vous n'êtes pas autorisé à re-build l'application.",
"permission_denied_stop_application": "Vous n'êtes pas autorisé à arrêter l'application.",
"persistent_storage": "Stockage persistant",
"preview": {
"need_during_buildtime": "Besoin pendant la build ?",
"no_previews_available": "Aucun aperçu disponible",
"redeploy": "Redéployer",
"setup_secret_app_first": "Vous pouvez ajouter des secrets aux déploiements PR/MR. \nVeuillez d'abord ajouter des secrets à l'application. \n<br>Utile pour créer des environnements <span class='text-green-500 font-bold'>de mise en scène</span>.",
"values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements <span class='text-green-500 font-bold'>de mise en scène</span>."
},
"previews": "Aperçus",
"publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n<br> Par exemple : <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> ou <span \nclass='text-green-500 font-bold'>public</span>.",
"rebuild_application": "Re-build l'application",
"secret": "secrets",
"secrets": {
"secret_saved": "Secret enregistré.",
"secrets_for": "secrets pour",
"use_isbuildsecret": "Utiliser isBuildSecret"
},
"settings_saved": "Paramètres sauvegardés.",
"ssl_explainer": "Il générera des certificats pour www et non-www. \n<br>Vous devez avoir <span class='font-bold text-green-500'>les deux entrées DNS</span> définies à l'avance.<br><br>Utile si vous prévoyez d'avoir des visiteurs sur les deux.",
"ssl_www_and_non_www": "Générer SSL pour www et non-www ?",
"start_command": "Démarrer la commande",
"stop_application": "Arrêter l'application",
"storage": {
"path_is_required": "Le chemin est requis.",
"persistent_storage_explainer": "Vous pouvez spécifier n'importe quel dossier que vous souhaitez conserver dans les déploiements. \n<br>Ceci est utile pour stocker des données telles qu'une base de données (SQLite) ou un cache.",
"storage_deleted": "Stockage supprimé.",
"storage_saved": "Stockage enregistré.",
"storage_updated": "Stockage mis à jour."
}
},
"database": {
"change_append_only_mode": "Changer le mode d'ajout uniquement",
"confirm_stop": "Êtes-vous sûr de vouloir arrêter {{name}} ?",
"connection_string": "Connexion string",
"default_database": "Base de données par défaut",
"delete_database": "Supprimer la base de données",
"generated_automatically_after_set_to_public": "Généré automatiquement après avoir été défini sur public",
"no_databases_found": "Aucune base de données trouvée",
"permission_denied_delete_database": "Vous n'êtes pas autorisé à supprimer une base de données",
"permission_denied_start_database": "Vous n'êtes pas autorisé à démarrer la base de données.",
"permission_denied_stop_database": "Vous n'êtes pas autorisé à arrêter la base de données.",
"select_database_type": "Sélectionnez un type de base de données",
"select_database_version": "Sélectionnez une version de la base de données",
"set_public": "Rendre public",
"start_database": "Démarrer la base de données",
"stop_database": "Arrêter la base de données",
"warning_append_only": "Utile si vous souhaitez restaurer des données Redis à partir d'une sauvegarde.<br><span class='font-bold text-white'>Le redémarrage de la base de données est nécessaire.</span>",
"warning_database_public": "Votre base de données sera accessible depuis Internet. \n<br>Prenez la sécurité au sérieux dans ce cas!"
},
"destination": {
"add_to_coolify": "Ajouter à Coolify",
"confirm_restart_proxy": "Voulez-vous vraiment redémarrer le proxy? \nTout sera reconfiguré en ~10 secondes.",
"coolify_proxy_restarting": "Redémarrage du Proxy Coolify...",
"coolify_proxy_started": "Proxy Coolify démarré!",
"coolify_proxy_stopped": "Proxy Coolify arrêté!",
"delete_destination": "Supprimer le destinataire",
"force_restart_proxy": "Forcer le redémarrage du proxy",
"new": {
"add_new_destination": "Ajouter une nouvelle destination",
"install_proxy": "Cela installera un proxy sur la destination pour vous permettre d'accéder à vos applications et services sans aucune configuration manuelle (recommandé pour Docker).<br><br>Les bases de données auront leur propre proxy.",
"predefined_destinations": "Destinations prédéfinies",
"saving_and_configuring_proxy": "Enregistrement et configuration du proxy..."
},
"new_error_network_already_exists": "Réseau {{network}} déjà configuré pour une autre équipe !",
"no_destination_found": "Aucune destination trouvée",
"permission_denied_delete_destination": "Vous n'êtes pas autorisé à supprimer cette destination",
"restarting_please_wait": "Redémarrage... veuillez patienter...",
"use_coolify_proxy": "Utiliser le Proxy Coolify ?"
},
"error": {
"here": "ici",
"you_are_lost": "Oups vous êtes perdu ! \nMais n'ayez pas peur !",
"you_can_find_your_way_back": "Tu peux retrouver ton chemin"
},
"forms": {
"action": "action",
"add": "Ajouter",
"already_used_for": "<span class=\"text-red-500\">{{type}}</span> déjà utilisé pour",
"api_port": "Port API",
"api_url": "URL de l'API",
"base_directory": "Répertoire de base",
"configuration": "Configuration",
"confirm_continue": "Êtes-vous sûr de continuer ?",
"default": "défaut",
"default_email_address": "Adresse e-mail par défaut",
"default_password": "Mot de passe par défaut",
"eg": "ex",
"email": "Adresse e-mail",
"engine": "Moteur",
"extra_config": "Configuration supplémentaire",
"generated_automatically_after_start": "Généré automatiquement après le démarrage",
"host": "Hôte",
"html_url": "URL HTML",
"ip_address": "Adresse IP",
"is_required": "est requis.",
"loading": "Chargement...",
"must_be_stopped_to_modify": "Doit être arrêté pour être modifié.",
"name": "Nom",
"network": "Réseau",
"new_password": "Nouveau mot de passe",
"no_actions_available": "Aucune action disponible",
"organization": "Organisation",
"password": "Mot de passe",
"password_again": "Mot de passe à nouveau",
"passwords_not_match": "Les mots de passe ne correspondent pas.",
"path": "Chemin",
"port": "Port",
"public_port_range": "Gamme de ports publics",
"public_port_range_explainer": "Ports utilisés pour exposer les bases de données/services/services internes.<br> Ajoutez-les à votre pare-feu (le cas échéant).<br><br>Vous pouvez spécifier une plage de ports, par exemple : <span class='text-yellow-500 \nfont-bold'>9000-9100</span>",
"publish_directory": "Publier le répertoire",
"remove": "Retirer",
"remove_domain": "Supprimer le domaine",
"removing": "Suppression...",
"root_db_password": "Mot de passe root de la base de données",
"root_db_user": "Utilisateur root de la base de données",
"root_user": "Utilisateur root",
"roots_password": "Mot de passe de l'utilisateur root",
"save": "sauvegarder",
"saving": "Sauvegarde...",
"select_a_service": "Sélectionnez un service",
"select_a_service_version": "Sélectionnez une version de service",
"set": "Régler",
"ssh_private_key": "Clé privée SSH",
"submit": "Nous faire parvenir",
"super_secure_new_password": "Nouveau mot de passe super sécurisé",
"type": "Taper",
"user": "Utilisateur",
"username": "Nom d'utilisateur",
"value": "Valeur",
"verify_emails_without_smtp": "Vérifier les e-mails sans SMTP",
"verifying": "Vérification",
"version": "Version"
},
"general": "Général",
"index": {
"applications": "Applications",
"dashboard": "Tableau de bord",
"database": "Base de données",
"databases": "Bases de données",
"destinations": "Destinations",
"git_sources": "Sources Git",
"global_settings": "Paramètres globaux",
"logout": "Se déconnecter",
"not_implemented_yet": "Pas encore implémenté",
"secret": "Secret",
"services": "Services",
"settings": "Réglages",
"team": "Équipe",
"teams": "Équipes"
},
"layout": {
"new_version": "Nouvelle version accessible. \nRechargement...",
"switch_to_a_different_team": "Changer d'équipe...",
"update_available": "Mise à jour disponible",
"update_done": "Mise à jour terminée.",
"wait_new_version_startup": "En attendant le lancement de la nouvelle version..."
},
"login": {
"already_logged_in": "Déjà connecté...",
"authenticating": "Authentification...",
"login": "Connexion"
},
"register": {
"first_user": "Vous enregistrez le premier utilisateur. \nCe sera l'administrateur de votre instance Coolify.",
"register": "S'inscrire"
},
"reset": {
"find_path_secret_key": "Vous pouvez le trouver dans ~/coolify/.env (COOLIFY_SECRET_KEY)",
"invalid_secret_key": "Clé secrète invalide.",
"reset_password": "Réinitialiser",
"secret_key": "Clef secrète"
},
"service": {
"delete_service": "Supprimer le service",
"no_service": "Aucun service trouvé",
"permission_denied_delete_service": "Vous n'êtes pas autorisé à supprimer un service.",
"permission_denied_start_service": "Vous n'êtes pas autorisé à démarrer le service.",
"permission_denied_stop_service": "Vous n'êtes pas autorisé à arrêter le service.",
"start_service": "Démarrer le service",
"stop_service": "Stopper le service"
},
"services": {
"all_email_verified": "Tous les e-mails sont vérifiés. \nVous pouvez vous connecter maintenant.",
"generate_www_non_www_ssl": "Il générera des certificats pour www et non-www. \n<br>Vous devez avoir <span class='font-bold text-pink-600'>les deux entrées DNS</span> définies à l'avance.<br><br>Le service devra être redémarré."
},
"setting": {
"coolify_proxy_settings": "Paramètres du proxy Coolify",
"credential_stat_explainer": "Identifiants pour la page <a class=\"text-white font-bold\" href=\"{{link}}\" target=\"_blank\">statistiques</a>.",
"domain_removed": "Domaine supprimé",
"must_remove_domain_before_changing": "Vous devez supprimer le domaine avant de pouvoir modifier ce paramètre.",
"permission_denied": "Vous n'avez pas la permission de faire cela. \n\\nDemandez à un administrateur de modifier vos autorisations.",
"registration_allowed": "Inscription autorisée ?",
"registration_allowed_explainer": "Autoriser d'autres inscriptions à l'application. \n<br>Il est désactivé après la première inscription.",
"ssl_explainer": "Si vous spécifiez <span class='text-yellow-500 font-bold'>https</span>, Coolify sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.<br>Si vous spécifiez <span class='text-yellow-500 font-bold'>www</span>, Coolify sera redirigé (302) à partir de non-www et vice versa."
},
"source": {
"application_id": "ID d'application",
"change_app_settings": "Modifier les paramètres de l'application {{name}}",
"create_new_app": "Créer une nouvelle application {{name}}",
"delete_git_source": "Supprimer la source Git",
"github": {
"redirecting": "Redirection vers Github..."
},
"gitlab": {
"already_configured": "L'application GitLab est déjà configurée.",
"gitlab_application_type": "Type d'application GitLab",
"group_owned": "Application détenue par le groupe",
"self_hosted": "Application à l'échelle de l'instance (auto-hébergée)",
"user_owned": "Application appartenant à l'utilisateur"
},
"group_name": "Nom de groupe",
"install_repositories": "Installer les dépôts",
"new": {
"git_source": "Ajouter une nouvelle source Git",
"official_providers": "Fournisseurs officiels"
},
"no_git_sources_found": "Aucune source git trouvée",
"oauth_id": "ID OAuth",
"oauth_id_explainer": "L'identifiant OAuth est l'identifiant unique de l'application GitLab. \n<br>Vous pouvez le trouver <span class='font-bold text-orange-600' >dans l'URL</span> de votre application GitLab OAuth.",
"permission_denied": "Vous n'êtes pas autorisé à supprimer une source Git",
"register_oauth_gitlab": "Enregistrer une nouvelle application OAuth sur GitLab"
},
"sources": {
"local_docker": "Docker local",
"organization_explainer": "Remplissez-le si vous souhaitez utiliser une organisation comme source Git. \nSinon, votre utilisateur sera utilisé.",
"remote_docker": "Station d'accueil à distance"
},
"team": {
"accept": "J'accepte",
"admin": "Administrateur",
"delete": "Supprimer",
"invite_new_member": "Inviter un nouveau membre",
"invite_only_register_explainer": "Vous ne pouvez inviter que des utilisateurs enregistrés pour le moment - sera bientôt prolongé.",
"invited_with_permissions": "Invité à <span class=\"font-bold text-pink-600\">{{teamName}}</span> avec <span class=\"font-bold text-rose-600\">{{permission}}</span \n> autorisation.",
"member": "membre(s)",
"members": "Membres",
"pending_invitation": "Invitation en attente",
"pending_invitations": "Invitations en attente",
"permission": "Autorisation",
"promote_to": "Promouvoir à {{grade}}",
"read": "Lire",
"revoke_invitation": "Révoquer l'invitation",
"root": "(suprême)",
"root_team_explainer": "Il s'agit de l'équipe <span class='text-red-500 font-bold'>suprême</span>. \nCela signifie que les membres de ce groupe peuvent gérer les paramètres à l'échelle de l'instance et avoir tous les privilèges dans Coolify (imaginez comme un utilisateur root sous Linux).",
"send_invitation": "Envoyer une invitation",
"you": "(Toi)"
}
}

60
apps/ui/src/lib/store.ts Normal file
View File

@@ -0,0 +1,60 @@
import { browser } from '$app/env';
import { writable, readable, type Writable, type Readable } from 'svelte/store';
// import { version as currentVersion } from '../../package.json';
interface AppSession {
version: string
userId: string | null,
teamId: string | null,
permission: string,
isAdmin: boolean,
whiteLabeled: boolean,
whiteLabeledDetails: {
icon: string | null,
},
tokens: {
github: string | null,
gitlab: string | null,
}
}
export const loginEmail: Writable<string | undefined> = writable()
export const appSession: Writable<AppSession> = writable({
version: '3.0.0',
userId: null,
teamId: null,
permission: 'read',
isAdmin: false,
whiteLabeled: false,
whiteLabeledDetails: {
icon: null
},
tokens: {
github: null,
gitlab: null
}
});
export const isTraefikUsed: Writable<boolean> = writable(false);
export const disabledButton: Writable<boolean> = writable(false);
export const status: Writable<any> = writable({
application: {
isRunning: false,
isExited: false,
loading: false,
initialLoading: true
},
service: {
initialLoading: true,
loading: false,
isRunning: false
},
database: {
initialLoading: true,
loading: false,
isRunning: false
}
});
export const features = readable({
beta: window.localStorage.getItem('beta') === 'true',
latestVersion: window.localStorage.getItem('latestVersion')
});

View File

@@ -0,0 +1,335 @@
function defaultBuildAndDeploy(packageManager: string) {
return {
installCommand:
packageManager === 'npm' ? `${packageManager} install` : `${packageManager} install`,
buildCommand:
packageManager === 'npm' ? `${packageManager} run build` : `${packageManager} build`,
startCommand:
packageManager === 'npm' ? `${packageManager} run start` : `${packageManager} start`
};
}
export function findBuildPack(pack: string, packageManager = 'npm') {
const metaData = buildPacks.find((b) => b.name === pack);
if (pack === 'node') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
buildCommand: null,
publishDirectory: null,
port: null
};
}
if (pack === 'static') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: 80
};
}
if (pack === 'docker') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: null
};
}
if (pack === 'svelte') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: 'public',
port: 80
};
}
if (pack === 'nestjs') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
startCommand:
packageManager === 'npm' ? 'npm run start:prod' : `${packageManager} run start:prod`,
publishDirectory: null,
port: 3000
};
}
if (pack === 'react') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: 'build',
port: 80
};
}
if (pack === 'nextjs') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: null,
port: 3000
};
}
if (pack === 'gatsby') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: 'public',
port: 80
};
}
if (pack === 'vuejs') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: 'dist',
port: 80
};
}
if (pack === 'nuxtjs') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: null,
port: 3000
};
}
if (pack === 'preact') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: 'build',
port: 80
};
}
if (pack === 'php') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: 80
};
}
if (pack === 'rust') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: 3000
};
}
if (pack === 'astro') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: `dist`,
port: 80
};
}
if (pack === 'eleventy') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: `_site`,
port: 80
};
}
if (pack === 'python') {
return {
...metaData,
startCommand: null,
port: 8000
};
}
if (pack === 'deno') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: 8000
};
}
if (pack === 'laravel') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: 80
};
}
return {
name: 'node',
fancyName: 'Node.js',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700',
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: null
};
}
export const buildPacks = [
{
name: 'node',
fancyName: 'Node.js',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
},
{
name: 'static',
fancyName: 'Static',
hoverColor: 'hover:bg-orange-700',
color: 'bg-orange-700'
},
{
name: 'php',
fancyName: 'PHP',
hoverColor: 'hover:bg-indigo-700',
color: 'bg-indigo-700'
},
{
name: 'laravel',
fancyName: 'Laravel',
hoverColor: 'hover:bg-indigo-700',
color: 'bg-indigo-700'
},
{
name: 'docker',
fancyName: 'Docker',
hoverColor: 'hover:bg-sky-700',
color: 'bg-sky-700'
},
{
name: 'svelte',
fancyName: 'Svelte',
hoverColor: 'hover:bg-orange-700',
color: 'bg-orange-700'
},
{
name: 'vuejs',
fancyName: 'VueJS',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
},
{
name: 'nuxtjs',
fancyName: 'NuxtJS',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
},
{
name: 'gatsby',
fancyName: 'Gatsby',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
},
{
name: 'astro',
fancyName: 'Astro',
hoverColor: 'hover:bg-pink-700',
color: 'bg-pink-700'
},
{
name: 'eleventy',
fancyName: 'Eleventy',
hoverColor: 'hover:bg-red-700',
color: 'bg-red-700'
},
{
name: 'react',
fancyName: 'React',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
},
{
name: 'preact',
fancyName: 'Preact',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
},
{
name: 'nextjs',
fancyName: 'NextJS',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
},
{
name: 'nestjs',
fancyName: 'NestJS',
hoverColor: 'hover:bg-red-700',
color: 'bg-red-700'
},
{
name: 'rust',
fancyName: 'Rust',
hoverColor: 'hover:bg-pink-700',
color: 'bg-pink-700'
},
{
name: 'python',
fancyName: 'Python',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
},
{
name: 'deno',
fancyName: 'Deno',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
}
];
export const scanningTemplates = {
'@sveltejs/kit': {
buildPack: 'nodejs'
},
astro: {
buildPack: 'astro'
},
'@11ty/eleventy': {
buildPack: 'eleventy'
},
svelte: {
buildPack: 'svelte'
},
'@nestjs/core': {
buildPack: 'nestjs'
},
next: {
buildPack: 'nextjs'
},
nuxt: {
buildPack: 'nuxtjs'
},
'react-scripts': {
buildPack: 'react'
},
'parcel-bundler': {
buildPack: 'static'
},
'@vue/cli-service': {
buildPack: 'vuejs'
},
vuejs: {
buildPack: 'vuejs'
},
gatsby: {
buildPack: 'gatsby'
},
'preact-cli': {
buildPack: 'react'
}
};

View File

@@ -0,0 +1,25 @@
import i18n from 'sveltekit-i18n';
import lang from './lang.json';
/** @type {import('sveltekit-i18n').Config} */
export const config = {
fallbackLocale: 'en',
translations: {
en: { lang },
fr: { lang }
},
loaders: [
{
locale: 'en',
key: '',
loader: async () => (await import('./locales/en.json')).default
},
{
locale: 'fr',
key: '',
loader: async () => (await import('./locales/fr.json')).default
}
]
};
export const { t, locales, locale, loadTranslations } = new i18n(config);

View File

@@ -0,0 +1,31 @@
<script context="module" lang="ts">
export function load({ error, status }: { error: any; status: any }) {
console.log(error);
return {
props: {
error,
status
}
};
}
</script>
<script lang="ts">
import { t } from '$lib/translations';
export let status: string;
export let error: any;
</script>
<div class="mx-auto flex h-screen flex-col items-center justify-center px-4">
<div class="pb-10 text-7xl font-bold">{status}</div>
<div class="text-3xl font-bold">{$t('error.you_are_lost')}</div>
<div class="text-xl">
{$t('error.you_can_find_your_way_back')}
<a href="/" class="font-bold uppercase text-sky-400">{$t('error.here')}</a>
</div>
<div class="py-10 text-xs font-bold">
<pre
class="w-full whitespace-pre-wrap break-words text-left text-xs tracking-tighter">{error.message} {error.stack}</pre>
</div>
</div>

View File

@@ -0,0 +1,373 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ url }) => {
try {
if (Cookies.get('token')) {
const response = await get(`/user`);
return {
props: {
...response
},
stuff: {
...response
}
};
} else {
if (url.pathname !== '/login' && url.pathname !== '/register') {
return {
status: 302,
redirect: '/login'
};
}
return {};
}
} catch (error: any) {
if (error?.code?.startsWith('FAST_JWT') || error.status === 401) {
Cookies.remove('token');
if (url.pathname !== '/login') {
return {
status: 302,
redirect: '/login'
};
}
}
if (url.pathname !== '/login') {
return {
status: 302,
redirect: '/login'
};
}
return {
status: 500,
error: new Error(error)
};
}
};
</script>
<script lang="ts">
export let settings: any;
export let userId: string;
export let teamId: string;
export let permission: string;
export let isAdmin: boolean;
import '../tailwind.css';
import Cookies from 'js-cookie';
import { fade } from 'svelte/transition';
import { SvelteToast } from '@zerodevx/svelte-toast';
import { navigating, page } from '$app/stores';
import { get } from '$lib/api';
import UpdateAvailable from '$lib/components/UpdateAvailable.svelte';
import PageLoader from '$lib/components/PageLoader.svelte';
import { errorNotification } from '$lib/common';
import { appSession, isTraefikUsed } from '$lib/store';
if (userId) $appSession.userId = userId;
if (teamId) $appSession.teamId = teamId;
if (permission) $appSession.permission = permission;
if (isAdmin) $appSession.isAdmin = isAdmin;
$isTraefikUsed = settings?.isTraefikUsed || false;
async function logout() {
try {
Cookies.remove('token');
return window.location.replace('/login');
} catch (error) {
return errorNotification(error);
}
}
</script>
<svelte:head>
<title>Coolify</title>
{#if !$appSession.whiteLabeled}
<link rel="icon" href="/favicon.png" />
{:else if $appSession.whiteLabeledDetails.icon}
<link rel="icon" href={$appSession.whiteLabeledDetails.icon} />
{/if}
</svelte:head>
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
{#if $navigating}
<div out:fade={{ delay: 100 }}>
<PageLoader />
</div>
{/if}
{#if $appSession.userId}
<nav class="nav-main">
<div class="flex h-screen w-full flex-col items-center transition-all duration-100">
{#if !$appSession.whiteLabeled}
<div class="my-4 h-10 w-10"><img src="/favicon.png" alt="coolLabs logo" /></div>
{/if}
<div class="flex flex-col space-y-2 py-2" class:mt-2={$appSession.whiteLabeled}>
<a
sveltekit:prefetch
href="/"
class="icons tooltip-right bg-coolgray-200 hover:text-white"
class:text-white={$page.url.pathname === '/'}
class:bg-coolgray-500={$page.url.pathname === '/'}
data-tooltip="Dashboard"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
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="M19 8.71l-5.333 -4.148a2.666 2.666 0 0 0 -3.274 0l-5.334 4.148a2.665 2.665 0 0 0 -1.029 2.105v7.2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-7.2c0 -.823 -.38 -1.6 -1.03 -2.105"
/>
<path d="M16 15c-2.21 1.333 -5.792 1.333 -8 0" />
</svg>
</a>
<div class="border-t border-stone-700" />
<a
sveltekit:prefetch
href="/applications"
class="icons tooltip-green-500 tooltip-right bg-coolgray-200 hover:text-green-500"
class:text-green-500={$page.url.pathname.startsWith('/applications') ||
$page.url.pathname.startsWith('/new/application')}
class:bg-coolgray-500={$page.url.pathname.startsWith('/applications') ||
$page.url.pathname.startsWith('/new/application')}
data-tooltip="Applications"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
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="4" width="6" height="6" rx="1" />
<rect x="4" y="14" width="6" height="6" rx="1" />
<rect x="14" y="14" width="6" height="6" rx="1" />
<line x1="14" y1="7" x2="20" y2="7" />
<line x1="17" y1="4" x2="17" y2="10" />
</svg>
</a>
<a
sveltekit:prefetch
href="/sources"
class="icons tooltip-orange-500 tooltip-right bg-coolgray-200 hover:text-orange-500"
class:text-orange-500={$page.url.pathname.startsWith('/sources') ||
$page.url.pathname.startsWith('/new/source')}
class:bg-coolgray-500={$page.url.pathname.startsWith('/sources') ||
$page.url.pathname.startsWith('/new/source')}
data-tooltip="Git Sources"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
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="6" cy="6" r="2" />
<circle cx="18" cy="18" r="2" />
<path d="M11 6h5a2 2 0 0 1 2 2v8" />
<polyline points="14 9 11 6 14 3" />
<path d="M13 18h-5a2 2 0 0 1 -2 -2v-8" />
<polyline points="10 15 13 18 10 21" />
</svg>
</a>
<a
sveltekit:prefetch
href="/destinations"
class="icons tooltip-sky-500 tooltip-right bg-coolgray-200 hover:text-sky-500"
class:text-sky-500={$page.url.pathname.startsWith('/destinations') ||
$page.url.pathname.startsWith('/new/destination')}
class:bg-coolgray-500={$page.url.pathname.startsWith('/destinations') ||
$page.url.pathname.startsWith('/new/destination')}
data-tooltip="Destinations"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
/>
<path d="M5 10h3v3h-3z" />
<path d="M8 10h3v3h-3z" />
<path d="M11 10h3v3h-3z" />
<path d="M8 7h3v3h-3z" />
<path d="M11 7h3v3h-3z" />
<path d="M11 4h3v3h-3z" />
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
<line x1="10" y1="16" x2="10" y2="16.01" />
</svg>
</a>
<div class="border-t border-stone-700" />
<a
sveltekit:prefetch
href="/databases"
class="icons tooltip-purple-500 tooltip-right bg-coolgray-200 hover:text-purple-500"
class:text-purple-500={$page.url.pathname.startsWith('/databases') ||
$page.url.pathname.startsWith('/new/database')}
class:bg-coolgray-500={$page.url.pathname.startsWith('/databases') ||
$page.url.pathname.startsWith('/new/database')}
data-tooltip="Databases"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
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>
</a>
<a
sveltekit:prefetch
href="/services"
class="icons tooltip-pink-500 tooltip-right bg-coolgray-200 hover:text-pink-500"
class:text-pink-500={$page.url.pathname.startsWith('/services') ||
$page.url.pathname.startsWith('/new/service')}
class:bg-coolgray-500={$page.url.pathname.startsWith('/services') ||
$page.url.pathname.startsWith('/new/service')}
data-tooltip="Services"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
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 18a4.6 4.4 0 0 1 0 -9a5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7h-12" />
</svg>
</a>
</div>
<div class="flex-1" />
<UpdateAvailable />
<div class="flex flex-col space-y-2 py-2">
<a
sveltekit:prefetch
href="/iam"
class="icons tooltip-fuchsia-500 tooltip-right bg-coolgray-200 hover:text-fuchsia-500"
class:text-fuchsia-500={$page.url.pathname.startsWith('/iam')}
class:bg-coolgray-500={$page.url.pathname.startsWith('/iam')}
data-tooltip="IAM"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
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="9" cy="7" r="4" />
<path d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
<path d="M21 21v-2a4 4 0 0 0 -3 -3.85" />
</svg>
</a>
{#if $appSession.teamId === '0'}
<a
sveltekit:prefetch
href="/settings"
class="icons tooltip-yellow-500 tooltip-right bg-coolgray-200 hover:text-yellow-500"
class:text-yellow-500={$page.url.pathname.startsWith('/settings')}
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
data-tooltip="Settings"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
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="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z"
/>
<circle cx="12" cy="12" r="3" />
</svg>
</a>
{/if}
<div
class="icons tooltip-red-500 tooltip-right bg-coolgray-200 hover:text-red-500"
data-tooltip="Logout"
on:click={logout}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="ml-1 h-7 w-7"
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="M14 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2"
/>
<path d="M7 12h14l-3 -3m0 6l3 -3" />
</svg>
</div>
<div
class="w-full text-center font-bold text-stone-400 hover:bg-coolgray-200 hover:text-white"
>
<a
class="text-[10px] no-underline"
href={`https://github.com/coollabsio/coolify/releases/tag/v${$appSession.version}`}
target="_blank">v{$appSession.version}</a
>
</div>
</div>
</div>
</nav>
{#if $appSession.whiteLabeled}
<span class="fixed bottom-0 left-[50px] z-50 m-2 px-4 text-xs text-stone-700"
>Powered by <a href="https://coolify.io" target="_blank">Coolify</a></span
>
{/if}
{/if}
<main>
<slot />
</main>

View File

@@ -0,0 +1,172 @@
<script lang="ts">
export let name = '';
export let value = '';
export let isBuildSecret = false;
export let isNewSecret = false;
export let isPRMRSecret = false;
export let PRMRSecret: any = {};
if (isPRMRSecret) value = PRMRSecret.value;
import { page } from '$app/stores';
import { del } from '$lib/api';
import { errorNotification } from '$lib/common';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { t } from '$lib/translations';
import { toast } from '@zerodevx/svelte-toast';
import { createEventDispatcher } from 'svelte';
import { saveSecret } from './utils';
const dispatch = createEventDispatcher();
const { id } = $page.params;
async function removeSecret() {
try {
await del(`/applications/${id}/secrets`, { name });
dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
isBuildSecret = false;
}
toast.push('Secret removed.');
} catch (error) {
return errorNotification(error);
}
}
async function createSecret(isNew: any) {
try {
await saveSecret({
isNew,
name,
value,
isBuildSecret,
isPRMRSecret,
isNewSecret,
applicationId: id
});
if (isNewSecret) {
name = '';
value = '';
isBuildSecret = false;
}
dispatch('refresh');
toast.push('Secret saved.');
} catch (error) {
console.log(error)
return errorNotification(error);
}
}
async function setSecretValue() {
if (!isPRMRSecret) {
isBuildSecret = !isBuildSecret;
if (!isNewSecret) {
await saveSecret({
isNew: isNewSecret,
name,
value,
isBuildSecret,
isPRMRSecret,
isNewSecret,
applicationId: id
});
toast.push('Secret saved.');
}
}
}
</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={isPRMRSecret}
class:cursor-not-allowed={isPRMRSecret}
class:cursor-pointer={!isPRMRSecret}
>
<span class="sr-only">{$t('application.secrets.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={() => createSecret(true)}
>{$t('forms.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={() => createSecret(false)}>{$t('forms.set')}</button>
</div>
{#if !isPRMRSecret}
<div class="flex justify-center items-end">
<button class="bg-red-600 hover:bg-red-500" on:click={removeSecret}
>{$t('forms.remove')}</button
>
</div>
{/if}
</div>
{/if}
</td>

View File

@@ -0,0 +1,73 @@
<script lang="ts">
import Explainer from '$lib/components/Explainer.svelte';
export let setting: any;
export let title: any;
export let description: any;
export let isCenter = true;
export let disabled = false;
export let dataTooltip: any = null;
export let loading = false;
</script>
<div class="flex items-center py-4 pr-8">
<div class="flex w-96 flex-col">
<div class="text-xs font-bold text-stone-100 md:text-base">{title}</div>
<Explainer text={description} />
</div>
</div>
<div
class:tooltip={dataTooltip}
class:text-center={isCenter}
data-tooltip={dataTooltip}
class="flex justify-center"
>
<div
type="button"
on:click
aria-pressed="false"
class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
class:opacity-50={disabled || loading}
class:bg-green-600={!loading && setting}
class:bg-stone-700={!loading && !setting}
class:bg-yellow-500={loading}
>
<span class="sr-only">Use setting</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={setting}
class:translate-x-0={!setting}
>
<span
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
class:opacity-0={setting}
class:opacity-100={!setting}
class:animate-spin={loading}
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={setting}
class:opacity-0={!setting}
class:animate-spin={loading}
>
<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>
</div>

View File

@@ -0,0 +1,77 @@
<script lang="ts">
export let isNew = false;
export let storage: any = {
id: null,
path: null
};
import { del, post } from '$lib/api';
import { page } from '$app/stores';
import { createEventDispatcher } from 'svelte';
import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
const { id } = $page.params;
const dispatch = createEventDispatcher();
async function saveStorage(newStorage = false) {
try {
if (!storage.path) return errorNotification($t('application.storage.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}/storages`, {
path: storage.path,
storageId: storage.id,
newStorage
});
dispatch('refresh');
if (isNew) {
storage.path = null;
storage.id = null;
}
if (newStorage) toast.push($t('application.storage.storage_saved'));
else toast.push($t('application.storage.storage_updated'));
} catch ({ error }) {
return errorNotification(error);
}
}
async function removeStorage() {
try {
await del(`/applications/${id}/storages`, { path: storage.path });
dispatch('refresh');
toast.push($t('application.storage.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)}
>{$t('forms.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)}>{$t('forms.set')}</button>
</div>
<div class="flex justify-center items-end">
<button class="bg-red-600 hover:bg-red-500" on:click={removeStorage}
>{$t('forms.remove')}</button
>
</div>
</div>
{/if}
</td>

View File

@@ -0,0 +1,497 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
function checkConfiguration(application: any): string | null {
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 }) => {
try {
const response = await get(`/applications/${params.id}`);
let { application, appId, settings, isQueueActive } = response;
if (!application || Object.entries(application).length === 0) {
return {
status: 302,
redirect: '/applications'
};
}
const configurationPhase = checkConfiguration(application);
if (
configurationPhase &&
url.pathname !== `/applications/${params.id}/configuration/${configurationPhase}`
) {
return {
status: 302,
redirect: `/applications/${params.id}/configuration/${configurationPhase}`
};
}
return {
props: {
isQueueActive,
application
},
stuff: {
application,
appId,
settings
}
};
} catch (error) {
return handlerNotFoundLoad(error, url);
}
};
</script>
<script lang="ts">
export let application: any;
export let isQueueActive: any;
import { page } from '$app/stores';
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
import { del, get, post } from '$lib/api';
import { goto } from '$app/navigation';
import { toast } from '@zerodevx/svelte-toast';
import { onDestroy, onMount } from 'svelte';
import { t } from '$lib/translations';
import { appSession, disabledButton, status } from '$lib/store';
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
import Loading from '$lib/components/Loading.svelte';
let loading = false;
let statusInterval: any;
$disabledButton =
!$appSession.isAdmin ||
!application.fqdn ||
!application.gitSource ||
!application.repository ||
!application.destinationDocker ||
!application.buildPack;
const { id } = $page.params;
async function handleDeploySubmit() {
try {
const { buildId } = await post(`/applications/${id}/deploy`, { ...application });
toast.push($t('application.deployment_queued'));
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: string) {
const sure = confirm($t('application.confirm_to_delete', { name }));
if (sure) {
loading = true;
try {
await del(`/applications/${id}`, { id });
return await goto(`/applications`);
} catch (error) {
return errorNotification(error);
}
}
}
async function stopApplication() {
try {
loading = true;
await post(`/applications/${id}/stop`, {});
return window.location.reload();
} catch ({ error }) {
return errorNotification(error);
}
}
async function getStatus() {
if ($status.application.loading) return;
$status.application.loading = true;
const data = await get(`/applications/${id}`);
$status.application.isRunning = data.isRunning;
$status.application.isExited = data.isExited;
$status.application.loading = false;
$status.application.initialLoading = false;
}
onDestroy(() => {
$status.application.initialLoading = true;
clearInterval(statusInterval);
});
onMount(async () => {
$status.application.isRunning = false;
$status.application.isExited = false;
$status.application.loading = false;
if (application.gitSourceId && application.destinationDockerId && application.fqdn) {
await getStatus();
statusInterval = setInterval(async () => {
await getStatus();
}, 2000);
}
});
</script>
<nav class="nav-side">
{#if loading}
<Loading fullscreen cover />
{:else}
{#if $status.application.isExited}
<a
href={!$disabledButton ? `/applications/${id}/logs` : null}
class=" icons bg-transparent tooltip-bottom text-sm flex items-center text-red-500 tooltip-red-500"
data-tooltip="Application exited with an error!"
sveltekit:prefetch
>
<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="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
/>
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
</a>
{/if}
{#if $status.application.initialLoading}
<button
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
>
<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="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
<line x1="11" y1="19.94" x2="11" y2="19.95" />
</svg>
</button>
{:else if $status.application.isRunning}
<button
on:click={stopApplication}
title="Stop application"
type="submit"
disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
data-tooltip={$appSession.isAdmin
? $t('application.stop_application')
: $t('application.permission_denied_stop_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={$disabledButton || !isQueueActive}
class:hover:text-green-500={isQueueActive}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2"
data-tooltip={$appSession.isAdmin
? isQueueActive
? 'Rebuild application'
: 'Autoupdate inprogress. Cannot 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={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
data-tooltip={$appSession.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-coolgray-500 h-8" />
<a
href={!$disabledButton ? `/applications/${id}` : null}
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"
disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm"
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={!$disabledButton ? `/applications/${id}/secrets` : null}
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="Secret"
disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip="Secret"
>
<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={!$disabledButton ? `/applications/${id}/storages` : null}
sveltekit:prefetch
class="hover:text-pink-500 rounded"
class:text-pink-500={$page.url.pathname === `/applications/${id}/storages`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storages`}
>
<button
title="Persistent Storages"
disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip="Persistent Storages"
>
<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={!$disabledButton ? `/applications/${id}/previews` : null}
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"
disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm"
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-coolgray-500 h-8" />
<a
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
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={$t('application.logs')}
disabled={$disabledButton || !$status.application.isRunning}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip={$t('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={!$disabledButton ? `/applications/${id}/logs/build` : null}
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"
disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm"
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-coolgray-500 h-8" />
<button
on:click={() => deleteApplication(application.name)}
title={$t('application.delete_application')}
type="submit"
disabled={!$appSession.isAdmin}
class:hover:text-red-500={$appSession.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip={$appSession.isAdmin
? $t('application.delete_application')
: $t('application.permission_denied_delete_application')}
>
<DeleteIcon />
</button>
{/if}
</nav>
<slot />

View File

@@ -0,0 +1,52 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { post } from '$lib/api';
import { errorNotification } from '$lib/common';
import { findBuildPack } from '$lib/templates';
import { t } from '$lib/translations';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
export let buildPack: any;
export let foundConfig: any;
export let scanning: any;
export let packageManager: any;
async function handleSubmit(name: string) {
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}`, { ...tempBuildPack, buildPack: name });
}
await post(`/applications/${id}/configuration/buildpack`, { 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"
>{$t('application.configuration.buildpack.choose_this_one')}</span
>
{/if}
</button>
</form>

View File

@@ -0,0 +1,201 @@
<script lang="ts">
export let application: any;
//@ts-ignore
import Select from 'svelte-select';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { get, post } from '$lib/api';
import { onMount } from 'svelte';
import { appSession } from '$lib/store';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
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: any = [];
let branches: any = [];
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 ${$appSession.tokens.github}`
});
}
async function loadBranchesByPage(page = 0) {
return await get(`${apiUrl}/repos/${selected.repository}/branches?per_page=100&page=${page}`, {
Authorization: `token ${$appSession.tokens.github}`
});
}
let reposSelectOptions: any;
let branchSelectOptions: any;
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: any) => ({
value: repo.full_name,
label: repo.name
}));
}
async function loadBranches(event: any) {
branches = [];
selected.repository = event.detail.value;
selected.projectId = repositories.find(
(repo: any) => repo.full_name === selected.repository
).id;
let page = 1;
let branchCount = 0;
loading.branches = true;
const loadedBranches = await loadBranchesByPage();
branches = branches.concat(loadedBranches);
branchCount = branches.length;
if (branchCount === 100) {
while (branchCount === 100) {
page = page + 1;
const nextBranches = await loadBranchesByPage(page);
branches = branches.concat(nextBranches);
branchCount = nextBranches.length;
}
}
loading.branches = false;
branchSelectOptions = branches.map((branch: any) => ({
value: branch.name,
label: branch.name
}));
}
async function isBranchAlreadyUsed(event: any) {
selected.branch = event.detail.value;
try {
const data = await get(
`/applications/${id}/configuration/repository?repository=${selected.repository}&branch=${selected.branch}`
);
if (data.used) {
const sure = confirm($t('application.configuration.branch_already_in_use'));
if (sure) {
selected.autodeploy = false;
showSave = true;
return true;
}
showSave = false;
return true;
}
showSave = true;
} catch ({ error }) {
showSave = false;
return errorNotification(error);
}
}
onMount(async () => {
try {
if (!$appSession.tokens.github) {
const { token } = await get(`/applications/${id}/configuration/githubToken`);
$appSession.tokens.github = token;
}
await loadRepositories();
} catch (error: any) {
if (error.message === 'Bad credentials') {
const { token } = await get(`/applications/${id}/configuration/githubToken`);
$appSession.tokens.github = token;
return await loadRepositories();
}
return errorNotification(error);
}
});
async function handleSubmit() {
try {
await post(`/applications/${id}/configuration/repository`, { ...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">{$t('application.configuration.no_repositories_configured')}</div>
<a href={`/sources/${application.gitSource.id}`}
><button>{$t('application.configuration.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-row md:flex gap-4">
<div class="custom-select-wrapper">
<Select
placeholder={loading.repositories
? $t('application.configuration.loading_repositories')
: $t('application.configuration.select_a_repository')}
id="repository"
showIndicator={!loading.repositories}
isWaiting={loading.repositories}
on:select={loadBranches}
items={reposSelectOptions}
isDisabled={loading.repositories}
isClearable={false}
/>
</div>
<input class="hidden" bind:value={selected.projectId} name="projectId" />
<div class="custom-select-wrapper">
<Select
placeholder={loading.branches
? $t('application.configuration.loading_branches')
: !selected.repository
? $t('application.configuration.select_a_repository_first')
: $t('application.configuration.select_a_branch')}
isWaiting={loading.branches}
showIndicator={selected.repository && !loading.branches}
id="branches"
on:select={isBranchAlreadyUsed}
items={branchSelectOptions}
isDisabled={loading.branches || !selected.repository}
isClearable={false}
/>
</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}>{$t('forms.save')}</button
>
</div>
</form>
{/if}

View File

@@ -0,0 +1,412 @@
<script lang="ts">
export let application: any;
export let appId: any;
export let settings: any;
//@ts-ignore
import cuid from 'cuid';
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { dev } from '$app/env';
import { goto } from '$app/navigation';
import { del, get, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import { appSession } from '$lib/store';
import Select from 'svelte-select';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
let url = settings?.fqdn ? settings.fqdn : window.location.origin;
if (dev) url = `http://localhost:3001`;
const updateDeployKeyIdUrl = `/applications/${id}/configuration/deploykey`;
let loading = {
base: true,
projects: false,
branches: false,
save: false
};
let htmlUrl = application.gitSource.htmlUrl;
let apiUrl = application.gitSource.apiUrl;
let username: any = null;
let groups: any = [];
let projects: any = [];
let branches: any = [];
let showSave = false;
let autodeploy = application.settings.autodeploy || true;
let selected: any = {
group: undefined,
project: undefined,
branch: undefined
};
let search = {
project: '',
branch: ''
};
onMount(async () => {
if (!$appSession.tokens.gitlab) {
await getGitlabToken();
}
loading.base = true;
try {
const user = await get(`${apiUrl}/v4/user`, {
Authorization: `Bearer ${$appSession.tokens.gitlab}`
});
username = user.username;
} catch (error) {
return await getGitlabToken();
}
try {
groups = await get(`${apiUrl}/v4/groups?per_page=5000`, {
Authorization: `Bearer ${$appSession.tokens.gitlab}`
});
} catch (error: any) {
errorNotification(error);
throw new Error(error);
} finally {
loading.base = false;
}
});
function selectGroup(event: any) {
selected.group = event.detail;
selected.project = null;
selected.branch = null;
showSave = false;
loadProjects();
}
async function searchProjects(searchText: any) {
if (!selected.group) {
return;
}
search.project = searchText;
await loadProjects();
return projects;
}
function selectProject(event: any) {
selected.project = event.detail;
selected.branch = null;
showSave = false;
loadBranches();
}
async function getGitlabToken() {
return await new Promise<void>((resolve, reject) => {
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=${url}/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);
$appSession.tokens.gitlab = localStorage.getItem('gitLabToken');
localStorage.removeItem('gitLabToken');
resolve();
}
}, 100);
});
}
async function loadProjects() {
//@ts-ignore
const params: any = new URLSearchParams({
page: 1,
per_page: 25,
archived: false
});
if (search.project) {
params.append('search', search.project);
}
loading.projects = true;
if (username === selected.group.name) {
try {
params.append('min_access_level', 40);
projects = await get(`${apiUrl}/v4/users/${selected.group.name}/projects?${params}`, {
Authorization: `Bearer ${$appSession.tokens.gitlab}`
});
} catch (error) {
return errorNotification(error);
} finally {
loading.projects = false;
}
} else {
try {
projects = await get(`${apiUrl}/v4/groups/${selected.group.id}/projects?${params}`, {
Authorization: `Bearer ${$appSession.tokens.gitlab}`
});
} catch (error) {
return errorNotification(error);
} finally {
loading.projects = false;
}
}
}
async function searchBranches(searchText: any) {
if (!selected.project) {
return;
}
search.branch = searchText;
await loadBranches();
return branches;
}
function selectBranch(event: any) {
selected.branch = event.detail;
isBranchAlreadyUsed();
}
async function loadBranches() {
//@ts-ignore
const params = new URLSearchParams({
page: 1,
per_page: 100
});
if (search.branch) {
params.append('search', search.branch);
}
loading.branches = true;
try {
branches = await get(
`${apiUrl}/v4/projects/${selected.project.id}/repository/branches?${params}`,
{
Authorization: `Bearer ${$appSession.tokens.gitlab}`
}
);
} catch (error) {
return errorNotification(error);
} finally {
loading.branches = false;
}
}
async function isBranchAlreadyUsed() {
try {
const data = await get(
`/applications/${id}/configuration/repository?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
);
if (data.used) {
const sure = confirm($t('application.configuration.branch_already_in_use'));
if (sure) {
autodeploy = false;
showSave = true;
return true;
}
showSave = false;
return true;
}
showSave = true;
} catch ({ error }) {
return errorNotification(error);
}
}
async function checkSSHKey(sshkeyUrl: any) {
try {
return await post(sshkeyUrl, {});
} catch (error) {
return errorNotification(error);
}
}
async function setWebhook(url: any, webhookToken: any) {
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 ${$appSession.tokens.gitlab}`
}
);
} catch (error) {
return errorNotification(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`;
const webhookUrl = `${apiUrl}/v4/projects/${selected.project.id}/hooks`;
const webhookToken = cuid();
try {
if (!privateSshKey || !publicSshKey) {
const data: any = await checkSSHKey(sshkeyUrl);
publicSshKey = data.publicKey;
}
const deployKeys = await get(deployKeyUrl, {
Authorization: `Bearer ${$appSession.tokens.gitlab}`
});
const deployKeyFound = deployKeys.filter(
(dk: any) => dk.title === `${appId}-coolify-deploy-key`
);
if (deployKeyFound.length > 0) {
for (const deployKey of deployKeyFound) {
await del(
`${deployKeyUrl}/${deployKey.id}`,
{},
{
Authorization: `Bearer ${$appSession.tokens.gitlab}`
}
);
}
}
const { id } = await post(
deployKeyUrl,
{
title: `${appId}-coolify-deploy-key`,
key: publicSshKey,
can_push: false
},
{
Authorization: `Bearer ${$appSession.tokens.gitlab}`
}
);
await post(updateDeployKeyIdUrl, { deployKeyId: id });
} catch (error) {
return errorNotification(error);
} finally {
loading.save = false;
}
try {
await setWebhook(webhookUrl, webhookToken);
} catch (error) {
return errorNotification(error);
} finally {
loading.save = false;
}
const url = `/applications/${id}/configuration/repository`;
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);
} finally {
loading.save = false;
}
}
async function handleSubmit() {
try {
await post(`/applications/${id}/configuration/repository`, { ...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 ">
<div class="custom-select-wrapper">
<Select
placeholder={loading.base
? $t('application.configuration.loading_groups')
: $t('application.configuration.select_a_group')}
id="group"
showIndicator={!loading.base}
isWaiting={loading.base}
on:select={selectGroup}
on:clear={() => {
showSave = false;
projects = [];
branches = [];
selected.group = null;
selected.project = null;
selected.branch = null;
}}
value={selected.group}
isDisabled={loading.base}
isClearable={false}
items={groups}
labelIdentifier="full_name"
optionIdentifier="id"
/>
</div>
<div class="custom-select-wrapper">
<Select
placeholder={loading.projects
? $t('application.configuration.loading_projects')
: $t('application.configuration.select_a_project')}
noOptionsMessage={$t('application.configuration.no_projects_found')}
id="project"
showIndicator={!loading.projects}
isWaiting={loading.projects}
isDisabled={loading.projects || !selected.group}
on:select={selectProject}
on:clear={() => {
showSave = false;
branches = [];
selected.project = null;
selected.branch = null;
}}
value={selected.project}
isClearable={false}
items={projects}
loadOptions={searchProjects}
labelIdentifier="name"
optionIdentifier="id"
/>
</div>
<div class="custom-select-wrapper">
<Select
placeholder={loading.branches
? $t('application.configuration.loading_branches')
: $t('application.configuration.select_a_branch')}
noOptionsMessage={$t('application.configuration.no_branches_found')}
id="branch"
showIndicator={!loading.branches}
isWaiting={loading.branches}
isDisabled={loading.branches || !selected.project}
on:select={selectBranch}
on:clear={() => {
showSave = false;
selected.branch = null;
}}
value={selected.branch}
isClearable={false}
items={branches}
loadOptions={searchBranches}
labelIdentifier="name"
optionIdentifier="web_url"
/>
</div>
</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 ? $t('forms.saving') : $t('forms.save')}</button
>
</div>
</form>

View File

@@ -0,0 +1,273 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
try {
const { application } = stuff;
if (application?.buildPack && !url.searchParams.get('from')) {
return {
status: 302,
redirect: `/applications/${params.id}`
};
}
const response = await get(`/applications/${params.id}/configuration/buildpack`);
return {
props: {
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { get } from '$lib/api';
import { appSession } from '$lib/store';
import { browser } from '$app/env';
import { t } from '$lib/translations';
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/templates';
import { errorNotification } from '$lib/common';
import BuildPack from './_BuildPack.svelte';
const { id } = $page.params;
let scanning = true;
let foundConfig: any = null;
let packageManager = 'npm';
export let apiUrl: any;
export let projectId: any;
export let repository: any;
export let branch: any;
export let type: any;
export let application: any;
function checkPackageJSONContents({ key, json }: { key: any; json: any }) {
return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key);
}
function checkTemplates({ json, packageManager }: { json: any; packageManager: any }) {
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 ${$appSession.tokens.gitlab}`
});
const packageJson = files.find(
(file: { name: string; type: string }) =>
file.name === 'package.json' && file.type === 'blob'
);
const yarnLock = files.find(
(file: { name: string; type: string }) =>
file.name === 'yarn.lock' && file.type === 'blob'
);
const pnpmLock = files.find(
(file: { name: string; type: string }) =>
file.name === 'pnpm-lock.yaml' && file.type === 'blob'
);
const dockerfile = files.find(
(file: { name: string; type: string }) =>
file.name === 'Dockerfile' && file.type === 'blob'
);
const cargoToml = files.find(
(file: { name: string; type: string }) =>
file.name === 'Cargo.toml' && file.type === 'blob'
);
const requirementsTxt = files.find(
(file: { name: string; type: string }) =>
file.name === 'requirements.txt' && file.type === 'blob'
);
const indexHtml = files.find(
(file: { name: string; type: string }) =>
file.name === 'index.html' && file.type === 'blob'
);
const indexPHP = files.find(
(file: { name: string; type: string }) =>
file.name === 'index.php' && file.type === 'blob'
);
const composerPHP = files.find(
(file: { name: string; type: string }) =>
file.name === 'composer.json' && file.type === 'blob'
);
const laravel = files.find(
(file: { name: string; type: string }) => file.name === 'artisan' && file.type === 'blob'
);
if (yarnLock) packageManager = 'yarn';
if (pnpmLock) packageManager = 'pnpm';
if (dockerfile) {
foundConfig = findBuildPack('docker', packageManager);
} else if (packageJson && !laravel) {
const path = packageJson.path;
const data: any = await get(
`${apiUrl}/v4/projects/${projectId}/repository/files/${path}/raw?ref=${branch}`,
{
Authorization: `Bearer ${$appSession.tokens.gitlab}`
}
);
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 || composerPHP) && !laravel) {
foundConfig = findBuildPack('php');
} else if (laravel) {
foundConfig = findBuildPack('laravel');
} else {
foundConfig = findBuildPack('node', packageManager);
}
} else if (type === 'github') {
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
Authorization: `Bearer ${$appSession.tokens.github}`,
Accept: 'application/vnd.github.v2.json'
});
const packageJson = files.find(
(file: { name: string; type: string }) =>
file.name === 'package.json' && file.type === 'file'
);
const yarnLock = files.find(
(file: { name: string; type: string }) =>
file.name === 'yarn.lock' && file.type === 'file'
);
const pnpmLock = files.find(
(file: { name: string; type: string }) =>
file.name === 'pnpm-lock.yaml' && file.type === 'file'
);
const dockerfile = files.find(
(file: { name: string; type: string }) =>
file.name === 'Dockerfile' && file.type === 'file'
);
const cargoToml = files.find(
(file: { name: string; type: string }) =>
file.name === 'Cargo.toml' && file.type === 'file'
);
const requirementsTxt = files.find(
(file: { name: string; type: string }) =>
file.name === 'requirements.txt' && file.type === 'file'
);
const indexHtml = files.find(
(file: { name: string; type: string }) =>
file.name === 'index.html' && file.type === 'file'
);
const indexPHP = files.find(
(file: { name: string; type: string }) =>
file.name === 'index.php' && file.type === 'file'
);
const composerPHP = files.find(
(file: { name: string; type: string }) =>
file.name === 'composer.json' && file.type === 'file'
);
const laravel = files.find(
(file: { name: string; type: string }) => file.name === 'artisan' && file.type === 'file'
);
if (yarnLock) packageManager = 'yarn';
if (pnpmLock) packageManager = 'pnpm';
if (dockerfile) {
foundConfig = findBuildPack('docker', packageManager);
} else if (packageJson && !laravel) {
const data: any = await get(`${packageJson.git_url}`, {
Authorization: `Bearer ${$appSession.tokens.github}`,
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 || composerPHP) && !laravel) {
foundConfig = findBuildPack('php');
} else if (laravel) {
foundConfig = findBuildPack('laravel');
} else {
foundConfig = findBuildPack('node', packageManager);
}
}
} catch (error: any) {
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`);
$appSession.tokens.github = token;
return await scanRepository()
}
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">
{$t('application.configuration.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">
{$t('application.configuration.scanning_repository_suggest_build_pack')}
</div>
</div>
{:else}
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">
{#each buildPacks as buildPack}
<div class="p-2">
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
</div>
{/each}
</div>
{/if}

View File

@@ -0,0 +1,116 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
try {
const { application } = stuff;
if (application?.destinationDockerId && !url.searchParams.get('from')) {
return {
status: 302,
redirect: `/applications/${params.id}`
};
}
const response = await get(`/destinations`);
return {
props: {
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { appSession } from '$lib/store';
import { errorNotification } from '$lib/common';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
export let destinations: any;
const ownDestinations = destinations.filter((destination: any) => {
if (destination.teams[0].id === $appSession.teamId) {
return destination;
}
});
const otherDestinations = destinations.filter((destination: any) => {
if (destination.teams[0].id !== $appSession.teamId) {
return destination;
}
});
async function handleSubmit(destinationId: any) {
try {
await post(`/applications/${id}/configuration/destination`, { 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">
{$t('application.configuration.configure_destination')}
</div>
</div>
<div class="flex flex-col justify-center">
{#if !destinations || ownDestinations.length === 0}
<div class="flex-col">
<div class="pb-2">{$t('application.configuration.no_configurable_destination')}</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-col flex-wrap justify-center px-2 md:flex-row">
{#each ownDestinations as destination}
<div class="p-2">
<form on:submit|preventDefault={() => handleSubmit(destination.id)}>
<button type="submit" class="box-selection hover:bg-sky-700 font-bold">
<div class="font-bold text-xl text-center truncate">{destination.name}</div>
<div class="text-center truncate">{destination.network}</div>
</button>
</form>
</div>
{/each}
</div>
{#if otherDestinations.length > 0 && $appSession.teamId === '0'}
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Destinations</div>
{/if}
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
{#each otherDestinations as destination}
<div class="p-2">
<form on:submit|preventDefault={() => handleSubmit(destination.id)}>
<button type="submit" class="box-selection hover:bg-sky-700 font-bold">
<div class="font-bold text-xl text-center truncate">{destination.name}</div>
<div class="text-center truncate">{destination.network}</div>
</button>
</form>
</div>
{/each}
</div>
{/if}
</div>

View File

@@ -0,0 +1,50 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ params, url, stuff }) => {
try {
const { application, appId, settings } = stuff;
if (application?.branch && application?.repository && !url.searchParams.get('from')) {
return {
status: 302,
redirect: `/applications/${params.id}`
};
}
return {
props: {
application,
appId,
settings
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
import { t } from '$lib/translations';
export let application: any;
export let appId: string;
export let settings: any;
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">
{$t('application.configuration.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} {settings} />
{/if}
</div>

View File

@@ -0,0 +1,148 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
try {
const { application } = stuff;
if (application?.gitSourceId && !url.searchParams.get('from')) {
return {
status: 302,
redirect: `/applications/${params.id}`
};
}
const response = await get(`/sources`);
return {
props: {
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
import { page, session } from '$app/stores';
import { goto } from '$app/navigation';
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import { appSession } from '$lib/store';
const { id } = $page.params;
const from = $page.url.searchParams.get('from')
export let sources: any;
const filteredSources = sources.filter(
(source: any) =>
(source.type === 'github' && source.githubAppId && source.githubApp.installationId) ||
(source.type === 'gitlab' && source.gitlabAppId)
);
const ownSources = filteredSources.filter((source: any) => {
if (source.teams[0].id === $appSession.teamId) {
return source;
}
});
const otherSources = filteredSources.filter((source: any) => {
if (source.teams[0].id !== $appSession.teamId) {
return source;
}
});
async function handleSubmit(gitSourceId: string) {
try {
await post(`/applications/${id}/configuration/source`, { gitSourceId });
return await goto(from || `/applications/${id}/configuration/repository`);
} catch (error) {
return errorNotification(error);
}
}
async function newSource() {
const { id } = await post('/sources/new', {});
return await goto(`/sources/${id}`, { replaceState: true });
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">
{$t('application.configuration.select_a_git_source')}
</div>
</div>
<div class="flex flex-col justify-center">
{#if !filteredSources || ownSources.length === 0}
<div class="flex-col">
<div class="pb-2 text-center font-bold">{$t('application.configuration.no_configurable_git')}</div>
<div class="flex justify-center">
<a href="/sources/new?from={$page.url.pathname}" class="add-icon bg-orange-600 hover:bg-orange-500">
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
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-col flex-wrap justify-center px-2 md:flex-row">
{#each ownSources 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 otherSources.length > 0 && $appSession.teamId === '0'}
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Sources</div>
{/if}
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
{#each otherSources 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">
{$t('application.configuration.configuration_missing')}
</div>
{/if}
</button>
</form>
</div>
{/each}
</div>
{/if}
</div>

View File

@@ -0,0 +1,750 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, stuff, url }) => {
try {
if (stuff?.application?.id) {
return {
props: {
application: stuff.application
}
};
}
const response = await get(`/applications/${params.id}`);
return {
props: {
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
export let application: any;
import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
import Select from 'svelte-select';
import Explainer from '$lib/components/Explainer.svelte';
import { toast } from '@zerodevx/svelte-toast';
import { get, post } from '$lib/api';
import cuid from 'cuid';
import { browser } from '$app/env';
import { appSession, disabledButton, status } from '$lib/store';
import { t } from '$lib/translations';
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
import Setting from './_Setting.svelte';
const { id } = $page.params;
let domainEl: HTMLInputElement;
let loading = false;
let usageLoading = false;
let usage = {
MemUsage: 0,
CPUPerc: 0,
NetIO: 0
};
let usageInterval: any;
let forceSave = false;
let debug = application.settings.debug;
let previews = application.settings.previews;
let dualCerts = application.settings.dualCerts;
let autodeploy = application.settings.autodeploy;
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
let isNonWWWDomainOK = false;
let isWWWDomainOK = false;
$: isDisabled = !$appSession.isAdmin || $status.application.isRunning;
let wsgis = [
{
value: 'None',
label: 'None'
},
{
value: 'Gunicorn',
label: 'Gunicorn'
},
{
value: 'Uvicorn',
label: 'Uvicorn'
}
];
function containerClass() {
return 'text-white border border-dashed border-coolgray-300 bg-transparent font-thin px-0';
}
async function getUsage() {
if (usageLoading) return;
if (!$status.application.isRunning) return;
usageLoading = true;
const data = await get(`/applications/${id}/usage`);
usage = data.usage;
usageLoading = false;
}
onDestroy(() => {
clearInterval(usageInterval);
});
onMount(async () => {
if (window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
application.fqdn = `http://${cuid()}.demo.coolify.io`;
await handleSubmit();
}
domainEl.focus();
await getUsage();
usageInterval = setInterval(async () => {
await getUsage();
}, 1000);
});
async function changeSettings(name: any) {
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`, {
previews,
debug,
dualCerts,
autodeploy,
branch: application.branch,
projectId: application.projectId
});
return toast.push($t('application.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() {
if (loading) return;
loading = true;
try {
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
await post(`/applications/${id}/check`, {
fqdn: application.fqdn,
forceSave,
dualCerts,
exposePort: application.exposePort
});
await post(`/applications/${id}`, { ...application });
$disabledButton = false;
forceSave = false;
return toast.push('Configurations saved.');
} catch (error) {
console.log(error);
//@ts-ignore
if (error?.message.startsWith($t('application.dns_not_set_partial_error'))) {
forceSave = true;
if (dualCerts) {
isNonWWWDomainOK = await isDNSValid(getDomain(nonWWWDomain), false);
isWWWDomainOK = await isDNSValid(getDomain(`www.${nonWWWDomain}`), true);
} else {
const isWWW = getDomain(application.fqdn).includes('www.');
if (isWWW) {
isWWWDomainOK = await isDNSValid(getDomain(`www.${nonWWWDomain}`), true);
} else {
isNonWWWDomainOK = await isDNSValid(getDomain(nonWWWDomain), false);
}
}
}
return errorNotification(error);
} finally {
loading = false;
}
}
async function selectWSGI(event: any) {
application.pythonWSGI = event.detail.value;
}
async function selectBaseImage(event: any) {
application.baseImage = event.detail.value;
await handleSubmit();
}
async function selectBaseBuildImage(event: any) {
application.baseBuildImage = event.detail.value;
await handleSubmit();
}
async function isDNSValid(domain: any, isWWW: any) {
try {
await get(`/applications/${id}/check?domain=${domain}`);
toast.push('DNS configuration is valid.');
isWWW ? (isWWWDomainOK = true) : (isNonWWWDomainOK = true);
return true;
} catch ({ error }) {
errorNotification(error);
isWWW ? (isWWWDomainOK = false) : (isNonWWWDomainOK = false);
return false;
}
}
</script>
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Configuration
</div>
<span class="text-xs">{application.name} </span>
</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 py-4">
<div class="text-2xl font-bold">Application Usage</div>
<div class="mx-auto">
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class=" text-sm font-medium text-white">Used Memory / Memory Limit</dt>
<dd class="mt-1 text-xl font-semibold text-white">
{usage?.MemUsage}
</dd>
</div>
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class="truncate text-sm font-medium text-white">Used CPU</dt>
<dd class="mt-1 text-xl font-semibold text-white ">
{usage?.CPUPerc}
</dd>
</div>
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class="truncate text-sm font-medium text-white">Network IO</dt>
<dd class="mt-1 text-xl font-semibold text-white ">
{usage?.NetIO}
</dd>
</div>
</dl>
</div>
</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">{$t('general')}</div>
{#if $appSession.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
? $t('forms.saving')
: forceSave
? $t('forms.confirm_continue')
: $t('forms.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">{$t('forms.name')}</label>
<input
readonly={!isDisabled}
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"
>{$t('application.git_source')}</label
>
<a
href={!isDisabled
? `/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"
>{$t('application.git_repository')}</label
>
<a
href={!isDisabled
? `/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"
>{$t('application.build_pack')}</label
>
<a
href={!isDisabled
? `/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"
>{$t('application.destination')}</label
>
<div class="no-underline">
<input
value={application.destinationDocker.name}
id="destination"
disabled
class="bg-transparent"
/>
</div>
</div>
{#if application.buildPack !== 'docker'}
<div class="grid grid-cols-2 items-center">
<label for="baseImage" class="text-base font-bold text-stone-100"
>{$t('application.base_image')}</label
>
<div class="custom-select-wrapper">
<Select
{isDisabled}
containerClasses={isDisabled && containerClass()}
id="baseImages"
showIndicator={!$status.application.isRunning}
items={application.baseImages}
on:select={selectBaseImage}
value={application.baseImage}
isClearable={false}
/>
</div>
<Explainer text={$t('application.base_image_explainer')} />
</div>
{/if}
{#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'}
<div class="grid grid-cols-2 items-center pb-8">
<label for="baseBuildImage" class="text-base font-bold text-stone-100"
>{$t('application.base_build_image')}</label
>
<div class="custom-select-wrapper">
<Select
{isDisabled}
containerClasses={isDisabled && containerClass()}
id="baseBuildImages"
showIndicator={!$status.application.isRunning}
items={application.baseBuildImages}
on:select={selectBaseBuildImage}
value={application.baseBuildImage}
isClearable={false}
/>
</div>
{#if application.buildPack === 'laravel'}
<Explainer text="For building frontend assets with webpack." />
{:else}
<Explainer text={$t('application.base_build_image_explainer')} />
{/if}
</div>
{/if}
</div>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">{$t('application.application')}</div>
</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"
>{$t('application.url_fqdn')}</label
>
{#if browser && window.location.hostname === 'demo.coolify.io'}
<Explainer
text="<span class='text-white font-bold'>You can use the predefined random url name or enter your own domain name.</span>"
/>
{/if}
<Explainer text={$t('application.https_explainer')} />
</div>
<div>
<input
readonly={isDisabled}
disabled={isDisabled}
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"
/>
{#if forceSave}
<div class="flex-col space-y-2 pt-4 text-center">
{#if isNonWWWDomainOK}
<button
class="bg-green-600 hover:bg-green-500"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="bg-red-600 hover:bg-red-500"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
>
{/if}
{#if dualCerts}
{#if isWWWDomainOK}
<button
class="bg-green-600 hover:bg-green-500"
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="bg-red-600 hover:bg-red-500"
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
>
{/if}
{/if}
</div>
{/if}
</div>
</div>
<div class="grid grid-cols-2 items-center pb-8">
<Setting
dataTooltip={$t('forms.must_be_stopped_to_modify')}
disabled={$status.application.isRunning}
isCenter={false}
bind:setting={dualCerts}
title={$t('application.ssl_www_and_non_www')}
description={$t('application.ssl_explainer')}
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
/>
</div>
{#if application.buildPack === 'python'}
<div class="grid grid-cols-2 items-center">
<label for="pythonModule" class="text-base font-bold text-stone-100">WSGI / ASGI</label>
<div class="custom-select-wrapper">
<Select id="wsgi" items={wsgis} on:select={selectWSGI} value={application.pythonWSGI} />
</div>
</div>
<div class="grid grid-cols-2 items-center">
<label for="pythonModule" class="text-base font-bold text-stone-100">Module</label>
<input
readonly={!$appSession.isAdmin}
name="pythonModule"
id="pythonModule"
required
bind:value={application.pythonModule}
placeholder={application.pythonWSGI?.toLowerCase() !== 'none' ? 'main' : 'main.py'}
/>
</div>
{#if application.pythonWSGI?.toLowerCase() === 'gunicorn'}
<div class="grid grid-cols-2 items-center">
<label for="pythonVariable" class="text-base font-bold text-stone-100">Variable</label>
<input
readonly={!$appSession.isAdmin}
name="pythonVariable"
id="pythonVariable"
required
bind:value={application.pythonVariable}
placeholder="default: app"
/>
</div>
{/if}
{#if application.pythonWSGI?.toLowerCase() === 'uvicorn'}
<div class="grid grid-cols-2 items-center">
<label for="pythonVariable" class="text-base font-bold text-stone-100">Variable</label>
<input
readonly={!$appSession.isAdmin}
name="pythonVariable"
id="pythonVariable"
required
bind:value={application.pythonVariable}
placeholder="default: app"
/>
</div>
{/if}
{/if}
{#if !staticDeployments.includes(application.buildPack)}
<div class="grid grid-cols-2 items-center">
<label for="port" class="text-base font-bold text-stone-100">{$t('forms.port')}</label>
<input
readonly={!$appSession.isAdmin}
name="port"
id="port"
bind:value={application.port}
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
/>
</div>
{/if}
{#if application.buildPack !== 'docker'}
<div class="grid grid-cols-2 items-center">
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
<input
readonly={!$appSession.isAdmin && !$status.application.isRunning}
disabled={isDisabled}
name="exposePort"
id="exposePort"
bind:value={application.exposePort}
placeholder="12345"
/>
<Explainer
text={'You can expose your application to a port on the host system.<br><br>Useful if you would like to use your own reverse proxy or tunnel and also in development mode. Otherwise leave empty.'}
/>
</div>
{/if}
{#if !notNodeDeployments.includes(application.buildPack)}
<div class="grid grid-cols-2 items-center pt-4">
<label for="installCommand" class="text-base font-bold text-stone-100"
>{$t('application.install_command')}</label
>
<input
readonly={!$appSession.isAdmin}
name="installCommand"
id="installCommand"
bind:value={application.installCommand}
placeholder="{$t('forms.default')}: yarn install"
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="buildCommand" class="text-base font-bold text-stone-100"
>{$t('application.build_command')}</label
>
<input
readonly={!$appSession.isAdmin}
name="buildCommand"
id="buildCommand"
bind:value={application.buildCommand}
placeholder="{$t('forms.default')}: yarn build"
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="startCommand" class="text-base font-bold text-stone-100"
>{$t('application.start_command')}</label
>
<input
readonly={!$appSession.isAdmin}
name="startCommand"
id="startCommand"
bind:value={application.startCommand}
placeholder="{$t('forms.default')}: yarn start"
/>
</div>
{/if}
{#if application.buildPack === 'docker'}
<div class="grid grid-cols-2 items-center pt-4">
<label for="dockerFileLocation" class="text-base font-bold text-stone-100"
>Dockerfile Location</label
>
<input
readonly={!$appSession.isAdmin}
name="dockerFileLocation"
id="dockerFileLocation"
bind:value={application.dockerFileLocation}
placeholder="default: /Dockerfile"
/>
<Explainer
text="Does not rely on Base Directory. <br>Should be absolute path, like <span class='text-green-500 font-bold'>/data/Dockerfile</span> or <span class='text-green-500 font-bold'>/Dockerfile.</span>"
/>
</div>
{/if}
{#if application.buildPack === 'deno'}
<div class="grid grid-cols-2 items-center">
<label for="denoMainFile" class="text-base font-bold text-stone-100">Main File</label>
<input
readonly={!$appSession.isAdmin}
name="denoMainFile"
id="denoMainFile"
bind:value={application.denoMainFile}
placeholder="default: main.ts"
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="denoOptions" class="text-base font-bold text-stone-100">Arguments</label>
<input
readonly={!$appSession.isAdmin}
name="denoOptions"
id="denoOptions"
bind:value={application.denoOptions}
placeholder="eg: --allow-net --allow-hrtime --config path/to/file.json"
/>
<Explainer
text="List of arguments to pass to <span class='text-green-500 font-bold'>deno run</span> command. Could include permissions, configurations files, etc."
/>
</div>
{/if}
{#if application.buildPack !== 'laravel'}
<div class="grid grid-cols-2 items-center">
<div class="flex-col">
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
>{$t('forms.base_directory')}</label
>
<Explainer text={$t('application.directory_to_use_explainer')} />
</div>
<input
readonly={!$appSession.isAdmin}
name="baseDirectory"
id="baseDirectory"
bind:value={application.baseDirectory}
placeholder="{$t('forms.default')}: /"
/>
</div>
{/if}
{#if !notNodeDeployments.includes(application.buildPack)}
<div class="grid grid-cols-2 items-center">
<div class="flex-col">
<label for="publishDirectory" class="pt-2 text-base font-bold text-stone-100"
>{$t('forms.publish_directory')}</label
>
<Explainer text={$t('application.publish_directory_explainer')} />
</div>
<input
readonly={!$appSession.isAdmin}
name="publishDirectory"
id="publishDirectory"
bind:value={application.publishDirectory}
placeholder=" {$t('forms.default')}: /"
/>
</div>
{/if}
</div>
</form>
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">{$t('application.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={$t('application.enable_automatic_deployment')}
description={$t('application.enable_auto_deploy_webhooks')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={previews}
on:click={() => changeSettings('previews')}
title={$t('application.enable_mr_pr_previews')}
description={$t('application.enable_preview_deploy_mr_pr_requests')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={debug}
on:click={() => changeSettings('debug')}
title={$t('application.debug_logs')}
description={$t('application.enable_debug_log_during_build')}
/>
</div>
</div>
</div>

View File

@@ -0,0 +1,179 @@
<script lang="ts">
export let buildId: any;
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
const dispatch = createEventDispatcher();
import { page } from '$app/stores';
import Loading from '$lib/components/Loading.svelte';
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import LoadingLogs from '$lib/components/LoadingLogs.svelte';
import { errorNotification } from '$lib/common';
let logs: any = [];
let loading = true;
let currentStatus: any;
let streamInterval: any;
let followingBuild: any;
let followingInterval: any;
let logsEl: any;
let cancelInprogress = false;
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/${buildId}?sequence=${sequence}`
);
currentStatus = status;
logs = logs.concat(
responseLogs.map((log: any) => ({ ...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/${buildId}?sequence=${nextSequence}`
);
status = data.status;
currentStatus = status;
logs = logs.concat(
data.logs.map((log: any) => ({ ...log, line: cleanAnsiCodes(log.line) }))
);
dispatch('updateBuildStatus', { status });
} catch (error) {
return errorNotification(error);
}
}, 1000);
} catch (error) {
console.log(error);
return errorNotification(error);
}
}
async function cancelBuild() {
if (cancelInprogress) return;
try {
cancelInprogress = true;
await post(`/applications/${id}/cancel`, {
buildId,
applicationId: id
});
} 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">{$t('application.build.queued_waiting_exec')}</div>
{:else}
<div class="flex justify-end sticky top-0 p-2">
<button
on:click={followBuild}
class="bg-transparent hover:text-green-500 hover:bg-coolgray-500"
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>
{#if currentStatus === 'running'}
<button
on:click={cancelBuild}
class:animation-spin={cancelInprogress}
class="bg-transparent hover:text-red-500 hover:bg-coolgray-500"
data-tooltip="Cancel build"
>
{#if cancelInprogress}
Cancelling...
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="12" cy="12" r="9" />
<path d="M10 10l4 4m0 -4l-4 4" />
</svg>
{/if}
</button>
{/if}
</div>
{#if logs.length > 0}
<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>
{:else}
<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"
>
No logs found.
</div>
{/if}
{/if}
</div>
{/if}

View File

@@ -0,0 +1,218 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
try {
const response = await get(`/applications/${params.id}/logs/build?skip=0`);
return {
props: {
application: stuff.application,
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
export let builds: any;
export let application: any;
export let buildCount: any;
import { page } from '$app/stores';
import BuildLog from './_BuildLog.svelte';
import { get } from '$lib/api';
import { t } from '$lib/translations';
import { changeQueryParams, dateOptions, errorNotification } from '$lib/common';
let buildId: any;
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 }: { detail: any }) {
const { status } = detail;
if (status !== 'running') {
try {
const data = await get(`/applications/${id}/logs/build?buildId=${buildId}`);
builds = builds.filter((build: any) => {
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: any) => {
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?skip=${skip}`);
builds = builds.concat(data.builds);
return;
} catch ({ error }) {
return errorNotification(error);
}
} else {
noMoreBuilds = true;
}
}
function loadBuild(build: any) {
buildId = build;
return changeQueryParams(buildId);
}
</script>
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
{$t('application.build_logs')}
</div>
<span class="text-xs">{application.name} </span>
</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="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 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">
{build.branch || 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">{$t('application.build.running')}</div>
{:else if build.status === 'queued'}
<div class="font-bold">{$t('application.build.queued')}</div>
{:else}
<div>{build.since}</div>
<div>
{$t('application.build.finished_in')} <span class="font-bold">{build.took}s</span>
</div>
{/if}
</div>
</div>
{/each}
</div>
{#if !noMoreBuilds}
{#if buildCount > 5}
<div class="flex space-x-2">
<button disabled={noMoreBuilds} class="w-full" on:click={loadMoreBuilds}
>{$t('application.build.load_more')}</button
>
</div>
{/if}
{/if}
</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">{$t('application.build.no_logs')}</div>
{/if}

View File

@@ -0,0 +1,219 @@
<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 }) => {
try {
const response = await get(`/applications/${params.id}/logs`);
return {
props: {
application: stuff.application,
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
export let application: any;
import { page } from '$app/stores';
import { get } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import LoadingLogs from '$lib/components/LoadingLogs.svelte';
let loadLogsInterval: any = null;
let logs: any = [];
let lastLog: any = null;
let followingInterval: any;
let followingLogs: any;
let logsEl: any;
let position = 0;
const { id } = $page.params;
onMount(async () => {
loadAllLogs();
loadLogsInterval = setInterval(() => {
loadLogs();
}, 1000);
});
onDestroy(() => {
clearInterval(loadLogsInterval);
clearInterval(followingInterval);
});
async function loadAllLogs() {
try {
const data: any = await get(`/applications/${id}/logs`);
if (data?.logs) {
lastLog = data.logs[data.logs.length - 1];
logs = data.logs;
}
} catch (error) {
console.log(error);
return errorNotification(error);
}
}
async function loadLogs() {
try {
const newLogs: any = await get(
`/applications/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`
);
if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) {
logs = logs.concat(newLogs.logs);
lastLog = newLogs.logs[newLogs.logs.length - 1];
}
} catch (error) {
return errorNotification(error);
}
}
function detect() {
if (position < logsEl.scrollTop) {
position = logsEl.scrollTop;
} else {
if (followingLogs) {
clearInterval(followingInterval);
followingLogs = false;
}
position = logsEl.scrollTop;
}
}
function followBuild() {
followingLogs = !followingLogs;
if (followingLogs) {
followingInterval = setInterval(() => {
logsEl.scrollTop = logsEl.scrollHeight;
window.scrollTo(0, document.body.scrollHeight);
}, 1000);
} else {
clearInterval(followingInterval);
}
}
</script>
<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Application Logs
</div>
<span class="text-xs">{application.name}</span>
</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="flex flex-row justify-center space-x-2 px-10 pt-6">
{#if logs.length === 0}
<div class="text-xl font-bold tracking-tighter">{$t('application.build.waiting_logs')}</div>
{:else}
<div class="relative w-full">
<div class="text-right " />
{#if loadLogsInterval}
<LoadingLogs />
{/if}
<div class="flex justify-end sticky top-0 p-2 mx-1">
<button
on:click={followBuild}
class="bg-transparent"
data-tooltip="Follow logs"
class:text-green-500={followingLogs}
>
<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 w-full 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}
on:scroll={detect}
>
<div class="px-2 pr-14">
{#each logs as log}
{log + '\n'}
{/each}
</div>
</div>
</div>
{/if}
</div>

View File

@@ -0,0 +1,196 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, stuff, url }) => {
try {
const response = await get(`/applications/${params.id}/previews`);
return {
props: {
application: stuff.application,
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
export let containers: any;
export let application: any;
export let PRMRSecrets: any;
export let applicationSecrets: any;
import Secret from './_Secret.svelte';
import { get, post } from '$lib/api';
import { page } from '$app/stores';
import Explainer from '$lib/components/Explainer.svelte';
import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations';
import { goto } from '$app/navigation';
import { errorNotification, getDomain } from '$lib/common';
const { id } = $page.params;
async function refreshSecrets() {
const data = await get(`/applications/${id}/secrets`);
PRMRSecrets = [...data.secrets];
}
async function redeploy(container: any) {
try {
const { buildId } = await post(`/applications/${id}/deploy`, {
pullmergeRequestId: container.pullmergeRequestId,
branch: container.branch
});
toast.push('Deployment queued');
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);
}
}
</script>
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Preview Deployments
</div>
<span class="text-xs">{application.name} </span>
</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-6xl px-6 pt-4">
<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. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."}
/>
</div>
{#if applicationSecrets.length !== 0}
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">{$t('forms.name')}</th>
<th scope="col">{$t('forms.value')}</th>
<th scope="col" class="w-64 text-center"
>{$t('application.preview.need_during_buildtime')}</th
>
<th scope="col" class="w-96 text-center">{$t('forms.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>
{/if}
</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)}
>{$t('application.preview.redeploy')}</button
>
</div>
{/each}
{:else}
<div class="flex-col">
<div class="text-center font-bold text-xl">
{$t('application.preview.no_previews_available')}
</div>
</div>
{/if}
</div>
</div>

View File

@@ -0,0 +1,176 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, stuff, url }) => {
try {
const response = await get(`/applications/${params.id}/secrets`);
return {
props: {
application: stuff.application,
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
export let secrets: any;
export let application: any;
import pLimit from 'p-limit';
import Secret from './_Secret.svelte';
import { page } from '$app/stores';
import { t } from '$lib/translations';
import { get } from '$lib/api';
import { saveSecret } from './utils';
import { toast } from '@zerodevx/svelte-toast';
const limit = pLimit(1);
const { id } = $page.params;
let batchSecrets = '';
async function refreshSecrets() {
const data = await get(`/applications/${id}/secrets`);
secrets = [...data.secrets];
}
async function getValues(e: any) {
e.preventDefault();
const eachValuePair = batchSecrets.split('\n');
const batchSecretsPairs = eachValuePair
.filter((secret) => !secret.startsWith('#') && secret)
.map((secret) => {
const [name, value] = secret.split('=');
const cleanValue = value?.replaceAll('"', '') || '';
return {
name,
value: cleanValue,
isNew: !secrets.find((secret: any) => name === secret.name)
};
});
await Promise.all(
batchSecretsPairs.map(({ name, value, isNew }) =>
limit(() => saveSecret({ name, value, applicationId: id, isNew }))
)
);
batchSecrets = '';
await refreshSecrets();
toast.push('Secrets saved.');
}
</script>
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
{$t('application.secret')}
</div>
<span class="text-xs">{application.name} </span>
</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-6xl px-6 pt-4">
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">{$t('forms.name')}</th>
<th scope="col">{$t('forms.value')}</th>
<th scope="col" class="w-64 text-center"
>{$t('application.preview.need_during_buildtime')}</th
>
<th scope="col" class="w-96 text-center">{$t('forms.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>
<h2 class="title my-6 font-bold">Paste .env file</h2>
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
<textarea bind:value={batchSecrets} class="mb-2 min-h-[200px] w-full" />
<button
class="bg-green-600 hover:bg-green-500 disabled:text-white disabled:opacity-40"
type="submit">Batch add secrets</button
>
</form>
</div>

View File

@@ -0,0 +1,133 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ params, stuff, url }) => {
try {
const response = await get(`/applications/${params.id}/storages`);
return {
props: {
application: stuff.application,
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
export let application: any;
export let persistentStorages: any;
import { page } from '$app/stores';
import Storage from './_Storage.svelte';
import { get } from '$lib/api';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
const { id } = $page.params;
async function refreshStorage() {
const data = await get(`/applications/${id}/storages`);
persistentStorages = [...data.persistentStorages];
}
</script>
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Persistent Storage
</div>
<span class="text-xs">{application.name} </span>
</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-6xl rounded-xl px-6 pt-4">
<div class="flex justify-center py-4 text-center">
<Explainer customClass="w-full" text={$t('application.storage.persistent_storage_explainer')} />
</div>
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">{$t('forms.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>

View File

@@ -0,0 +1,43 @@
import { toast } from '@zerodevx/svelte-toast';
import { post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
type Props = {
isNew: boolean;
name: string;
value: string;
isBuildSecret?: boolean;
isPRMRSecret?: boolean;
isNewSecret?: boolean;
applicationId: string;
};
export async function saveSecret({
isNew,
name,
value,
isBuildSecret,
isPRMRSecret,
isNewSecret,
applicationId
}: Props): Promise<void> {
if (!name) return errorNotification(`${t.get('forms.name')} ${t.get('forms.is_required')}`);
if (!value) return errorNotification(`${t.get('forms.value')} ${t.get('forms.is_required')}`);
try {
await post(`/applications/${applicationId}/secrets`, {
name,
value,
isBuildSecret,
isPRMRSecret,
isNew: isNew || false
});
if (isNewSecret) {
name = '';
value = '';
isBuildSecret = false;
}
} catch (error) {
throw error
}
}

View File

@@ -0,0 +1,223 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async () => {
try {
const response = await get(`/applications`);
return {
props: {
...response
}
};
} catch (error: any) {
return {
status: 500,
error: new Error(error)
};
}
};
</script>
<script lang="ts">
export let applications: Array<any> = [];
let ownApplications: Array<any> = [];
let otherApplications: Array<any> = [];
import { get, post } from '$lib/api';
import { goto } from '$app/navigation';
import { t } from '$lib/translations';
import { getDomain } from '$lib/common';
import { appSession } from '$lib/store';
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';
import Deno from '$lib/components/svg/applications/Deno.svelte';
import Laravel from '$lib/components/svg/applications/Laravel.svelte';
ownApplications = applications.filter((application) => {
if (application.teams[0].id === $appSession.teamId) {
return application;
}
});
otherApplications = applications.filter((application) => {
if (application.teams[0].id !== $appSession.teamId) {
return application;
}
});
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 ">{$t('index.applications')}</div>
{#if $appSession.isAdmin}
<button
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
>
</button>
{/if}
</div>
<div class="flex-col justify-center">
{#if !applications || ownApplications.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
</div>
{/if}
{#if ownApplications.length > 0 || otherApplications.length > 0}
<div class="flex flex-col">
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
{#each ownApplications as application}
<a href="/applications/{application.id}" class="w-96 p-2 no-underline">
<div class="box-selection group relative hover:bg-green-600">
{#if application.buildPack}
{#if application.buildPack.toLowerCase() === 'rust'}
<Rust />
{:else if application.buildPack.toLowerCase() === 'node'}
<Nodejs />
{:else if application.buildPack.toLowerCase() === 'react'}
<React />
{:else if application.buildPack.toLowerCase() === 'svelte'}
<Svelte />
{:else if application.buildPack.toLowerCase() === 'vuejs'}
<Vuejs />
{:else if application.buildPack.toLowerCase() === 'php'}
<PHP />
{:else if application.buildPack.toLowerCase() === 'python'}
<Python />
{:else if application.buildPack.toLowerCase() === 'static'}
<Static />
{:else if application.buildPack.toLowerCase() === 'nestjs'}
<Nestjs />
{:else if application.buildPack.toLowerCase() === 'nuxtjs'}
<Nuxtjs />
{:else if application.buildPack.toLowerCase() === 'nextjs'}
<Nextjs />
{:else if application.buildPack.toLowerCase() === 'gatsby'}
<Gatsby />
{:else if application.buildPack.toLowerCase() === 'docker'}
<Docker />
{:else if application.buildPack.toLowerCase() === 'astro'}
<Astro />
{:else if application.buildPack.toLowerCase() === 'eleventy'}
<Eleventy />
{:else if application.buildPack.toLowerCase() === 'deno'}
<Deno />
{:else if application.buildPack.toLowerCase() === 'laravel'}
<Laravel />
{/if}
{/if}
<div class="truncate text-center text-xl font-bold">{application.name}</div>
{#if $appSession.teamId === '0' && otherApplications.length > 0}
<div class="truncate text-center">Team {application.teams[0].name}</div>
{/if}
{#if application.fqdn}
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
{/if}
{#if !application.gitSourceId}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Git Source Missing
</div>
{:else if !application.destinationDockerId}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Destination Missing
</div>
{:else if !application.fqdn}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
URL Missing
</div>
{/if}
</div>
</a>
{/each}
</div>
{#if otherApplications.length > 0 && $appSession.teamId === '0'}
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Applications</div>
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
{#each otherApplications as application}
<a href="/applications/{application.id}" class="w-96 p-2 no-underline">
<div class="box-selection group relative hover:bg-green-600">
{#if application.buildPack}
{#if application.buildPack.toLowerCase() === 'rust'}
<Rust />
{:else if application.buildPack.toLowerCase() === 'node'}
<Nodejs />
{:else if application.buildPack.toLowerCase() === 'react'}
<React />
{:else if application.buildPack.toLowerCase() === 'svelte'}
<Svelte />
{:else if application.buildPack.toLowerCase() === 'vuejs'}
<Vuejs />
{:else if application.buildPack.toLowerCase() === 'php'}
<PHP />
{:else if application.buildPack.toLowerCase() === 'python'}
<Python />
{:else if application.buildPack.toLowerCase() === 'static'}
<Static />
{:else if application.buildPack.toLowerCase() === 'nestjs'}
<Nestjs />
{:else if application.buildPack.toLowerCase() === 'nuxtjs'}
<Nuxtjs />
{:else if application.buildPack.toLowerCase() === 'nextjs'}
<Nextjs />
{:else if application.buildPack.toLowerCase() === 'gatsby'}
<Gatsby />
{:else if application.buildPack.toLowerCase() === 'docker'}
<Docker />
{:else if application.buildPack.toLowerCase() === 'astro'}
<Astro />
{:else if application.buildPack.toLowerCase() === 'eleventy'}
<Eleventy />
{:else if application.buildPack.toLowerCase() === 'deno'}
<Deno />
{:else if application.buildPack.toLowerCase() === 'laravel'}
<Laravel />
{/if}
{/if}
<div class="truncate text-center text-xl font-bold">{application.name}</div>
{#if $appSession.teamId === '0'}
<div class="truncate text-center">Team {application.teams[0].name}</div>
{/if}
{#if application.fqdn}
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
{/if}
{#if !application.gitSourceId || !application.destinationDockerId || !application.fqdn}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Configuration missing
</div>
{/if}
</div>
</a>
{/each}
</div>
{/if}
</div>
{/if}
</div>

View File

@@ -0,0 +1,29 @@
<script lang="ts">
export let database: any;
import Clickhouse from '$lib/components/svg/databases/Clickhouse.svelte';
import CouchDb from '$lib/components/svg/databases/CouchDB.svelte';
import MariaDb from '$lib/components/svg/databases/MariaDB.svelte';
import MongoDb from '$lib/components/svg/databases/MongoDB.svelte';
import MySql from '$lib/components/svg/databases/MySQL.svelte';
import PostgreSql from '$lib/components/svg/databases/PostgreSQL.svelte';
import Redis from '$lib/components/svg/databases/Redis.svelte';
</script>
<span class="relative">
{#if database.type === 'clickhouse'}
<Clickhouse />
{:else if database.type === 'couchdb'}
<CouchDb />
{:else if database.type === 'mongodb'}
<MongoDb />
{:else if database.type === 'mysql'}
<MySql />
{:else if database.type === 'mariadb'}
<MariaDb />
{:else if database.type === 'postgresql'}
<PostgreSql />
{:else if database.type === 'redis'}
<Redis />
{/if}
</span>

View File

@@ -0,0 +1,75 @@
<script lang="ts">
export let database: any;
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">CouchDB</div>
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="defaultDatabase" class="text-base font-bold text-stone-100"
>{$t('database.default_database')}</label
>
<CopyPasswordField
required
readonly={database.defaultDatabase}
disabled={database.defaultDatabase}
placeholder="{$t('forms.eg')}: mydb"
id="defaultDatabase"
name="defaultDatabase"
bind:value={database.defaultDatabase}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
<CopyPasswordField
readonly
disabled
placeholder={$t('forms.generated_automatically_after_start')}
id="dbUser"
name="dbUser"
value={database.dbUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.password')}</label
>
<CopyPasswordField
readonly
disabled
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="dbUserPassword"
name="dbUserPassword"
value={database.dbUserPassword}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUser" class="text-base font-bold text-stone-100">{$t('forms.root_user')}</label>
<CopyPasswordField
readonly
disabled
placeholder={$t('forms.generated_automatically_after_start')}
id="rootUser"
name="rootUser"
value={database.rootUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.roots_password')}</label
>
<CopyPasswordField
readonly
disabled
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="rootUserPassword"
name="rootUserPassword"
value={database.rootUserPassword}
/>
</div>
</div>

View File

@@ -0,0 +1,242 @@
<script lang="ts">
export let database: any;
export let privatePort: any;
export let settings: any;
import { page } from '$app/stores';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Setting from '$lib/components/Setting.svelte';
import MySql from './_MySQL.svelte';
import MongoDb from './_MongoDB.svelte';
import MariaDb from './_MariaDB.svelte';
import PostgreSql from './_PostgreSQL.svelte';
import Redis from './_Redis.svelte';
import CouchDb from './_CouchDb.svelte';
import { post } from '$lib/api';
import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations';
import { errorNotification, getDomain } from '$lib/common';
import { appSession, status } from '$lib/store';
const { id } = $page.params;
let loading = false;
let publicLoading = false;
let isPublic = database.settings.isPublic || false;
let appendOnly = database.settings.appendOnly;
let databaseDefault: any;
let databaseDbUser: any;
let databaseDbUserPassword: any;
generateDbDetails();
function generateDbDetails() {
databaseDefault = database.defaultDatabase;
databaseDbUser = database.dbUser;
databaseDbUserPassword = database.dbUserPassword;
if (database.type === 'mongodb') {
databaseDefault = '?readPreference=primary&ssl=false';
databaseDbUser = database.rootUser;
databaseDbUserPassword = database.rootUserPassword;
} else if (database.type === 'redis') {
databaseDefault = '';
databaseDbUser = '';
}
}
function generateUrl(): string {
return `${database.type}://${
databaseDbUser ? databaseDbUser + ':' : ''
}${databaseDbUserPassword}@${
isPublic ? (settings.fqdn ? getDomain(settings.fqdn) : window.location.hostname) : database.id
}:${isPublic ? database.publicPort : privatePort}/${databaseDefault}`;
}
async function changeSettings(name: any) {
if (publicLoading || !$status.database.isRunning) return;
publicLoading = true;
let data = {
isPublic,
appendOnly
};
if (name === 'isPublic') {
data.isPublic = !isPublic;
}
if (name === 'appendOnly') {
data.appendOnly = !appendOnly;
}
try {
const { publicPort } = await post(`/databases/${id}/settings`, {
isPublic: data.isPublic,
appendOnly: data.appendOnly
});
isPublic = data.isPublic;
appendOnly = data.appendOnly;
if (isPublic) {
database.publicPort = publicPort;
}
} catch (error) {
return errorNotification(error);
} finally {
publicLoading = false;
}
}
async function handleSubmit() {
try {
loading = true;
await post(`/databases/${id}`, { ...database, isRunning: $status.database.isRunning });
generateDbDetails();
toast.push('Settings saved.');
} catch (error) {
return errorNotification(error);
} finally {
loading = false;
}
}
</script>
<div class="mx-auto max-w-4xl px-6">
<form on:submit|preventDefault={handleSubmit} class="py-4">
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">{$t('general')}</div>
{#if $appSession.isAdmin}
<button
type="submit"
class:bg-purple-600={!loading}
class:hover:bg-purple-500={!loading}
disabled={loading}>{loading ? $t('forms.saving') : $t('forms.save')}</button
>
{/if}
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<input
readonly={!$appSession.isAdmin}
name="name"
id="name"
bind:value={database.name}
required
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="destination" class="text-base font-bold text-stone-100"
>{$t('application.destination')}</label
>
{#if database.destinationDockerId}
<div class="no-underline">
<input
value={database.destinationDocker.name}
id="destination"
disabled
readonly
class="bg-transparent "
/>
</div>
{/if}
</div>
<div class="grid grid-cols-2 items-center">
<label for="version" class="text-base font-bold text-stone-100">Version / Tag</label>
<a
href={$appSession.isAdmin && !$status.database.isRunning
? `/databases/${id}/configuration/version?from=/databases/${id}`
: ''}
class="no-underline"
>
<input
value={database.version}
disabled={$status.database.isRunning}
class:cursor-pointer={!$status.database.isRunning}
/></a
>
</div>
</div>
<div class="grid grid-flow-row gap-2 px-10 pt-2">
<div class="grid grid-cols-2 items-center">
<label for="host" class="text-base font-bold text-stone-100">{$t('forms.host')}</label>
<CopyPasswordField
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField={false}
readonly
disabled
id="host"
name="host"
value={database.id}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="publicPort" class="text-base font-bold text-stone-100">{$t('forms.port')}</label
>
<CopyPasswordField
placeholder={$t('database.generated_automatically_after_set_to_public')}
id="publicPort"
readonly
disabled
name="publicPort"
value={publicLoading ? 'Loading...' : isPublic ? database.publicPort : privatePort}
/>
</div>
</div>
<div class="grid grid-flow-row gap-2">
{#if database.type === 'mysql'}
<MySql bind:database />
{:else if database.type === 'postgresql'}
<PostgreSql bind:database />
{:else if database.type === 'mongodb'}
<MongoDb bind:database />
{:else if database.type === 'mariadb'}
<MariaDb bind:database />
{:else if database.type === 'redis'}
<Redis bind:database />
{:else if database.type === 'couchdb'}
<CouchDb {database} />
{/if}
<div class="grid grid-cols-2 items-center px-10 pb-8">
<label for="url" class="text-base font-bold text-stone-100"
>{$t('database.connection_string')}</label
>
<CopyPasswordField
textarea={true}
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField={false}
id="url"
name="url"
readonly
disabled
value={publicLoading || loading ? 'Loading...' : generateUrl()}
/>
</div>
</div>
</form>
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">{$t('application.features')}</div>
</div>
<div class="px-10 pb-10">
<div class="grid grid-cols-2 items-center">
<Setting
loading={publicLoading}
bind:setting={isPublic}
on:click={() => changeSettings('isPublic')}
title={$t('database.set_public')}
description={$t('database.warning_database_public')}
disabled={!$status.database.isRunning}
/>
</div>
{#if database.type === 'redis'}
<div class="grid grid-cols-2 items-center">
<Setting
bind:setting={appendOnly}
on:click={() => changeSettings('appendOnly')}
title={$t('database.change_append_only_mode')}
description={$t('database.warning_append_only')}
/>
</div>
{/if}
</div>
</div>

View File

@@ -0,0 +1,79 @@
<script lang="ts">
export let database: any;
import { status } from '$lib/store';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">MariaDB</div>
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="defaultDatabase" class="text-base font-bold text-stone-100"
>{$t('database.default_database')}</label
>
<CopyPasswordField
required
readonly={database.defaultDatabase}
disabled={database.defaultDatabase}
placeholder="{$t('forms.eg')}: mydb"
id="defaultDatabase"
name="defaultDatabase"
bind:value={database.defaultDatabase}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
<CopyPasswordField
readonly
disabled
placeholder={$t('forms.generated_automatically_after_start')}
id="dbUser"
name="dbUser"
value={database.dbUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.password')}</label
>
<CopyPasswordField
disabled={!$status.database.isRunning}
readonly={!$status.database.isRunning}
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="dbUserPassword"
name="dbUserPassword"
bind:value={database.dbUserPassword}
/>
<Explainer text="Could be changed while the database is running." />
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUser" class="text-base font-bold text-stone-100">{$t('forms.root_user')}</label>
<CopyPasswordField
readonly
disabled
placeholder={$t('forms.generated_automatically_after_start')}
id="rootUser"
name="rootUser"
value={database.rootUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.roots_password')}</label
>
<CopyPasswordField
disabled={!$status.database.isRunning}
readonly={!$status.database.isRunning}
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="rootUserPassword"
name="rootUserPassword"
bind:value={database.rootUserPassword}
/>
<Explainer text="Could be changed while the database is running." />
</div>
</div>

View File

@@ -0,0 +1,39 @@
<script lang="ts">
export let database: any;
import { status } from '$lib/store';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">MongoDB</div>
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="rootUser" class="text-base font-bold text-stone-100">{$t('forms.root_user')}</label>
<CopyPasswordField
placeholder={$t('forms.generated_automatically_after_start')}
id="rootUser"
readonly
disabled
name="rootUser"
value={database.rootUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.roots_password')}</label
>
<CopyPasswordField
disabled={!$status.database.isRunning}
readonly={!$status.database.isRunning}
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField={true}
id="rootUserPassword"
name="rootUserPassword"
bind:value={database.rootUserPassword}
/>
<Explainer text="Could be changed while the database is running." />
</div>
</div>

View File

@@ -0,0 +1,79 @@
<script lang="ts">
export let database: any;
import { status } from '$lib/store';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">MySQL</div>
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="defaultDatabase" class="text-base font-bold text-stone-100"
>{$t('database.default_database')}</label
>
<CopyPasswordField
required
readonly={database.defaultDatabase}
disabled={database.defaultDatabase}
placeholder="{$t('forms.eg')}: mydb"
id="defaultDatabase"
name="defaultDatabase"
bind:value={database.defaultDatabase}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
<CopyPasswordField
readonly
disabled
placeholder={$t('forms.generated_automatically_after_start')}
id="dbUser"
name="dbUser"
value={database.dbUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.password')}</label
>
<CopyPasswordField
disabled={!$status.database.isRunning}
readonly={!$status.database.isRunning}
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="dbUserPassword"
name="dbUserPassword"
bind:value={database.dbUserPassword}
/>
<Explainer text="Could be changed while the database is running." />
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUser" class="text-base font-bold text-stone-100">{$t('forms.root_user')}</label>
<CopyPasswordField
readonly
disabled
placeholder={$t('forms.generated_automatically_after_start')}
id="rootUser"
name="rootUser"
value={database.rootUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.roots_password')}</label
>
<CopyPasswordField
disabled={!$status.database.isRunning}
readonly={!$status.database.isRunning}
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="rootUserPassword"
name="rootUserPassword"
bind:value={database.rootUserPassword}
/>
<Explainer text="Could be changed while the database is running." />
</div>
</div>

View File

@@ -0,0 +1,68 @@
<script lang="ts">
export let database: any;
import { status } from '$lib/store';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">PostgreSQL</div>
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="defaultDatabase" class="text-base font-bold text-stone-100"
>{$t('database.default_database')}</label
>
<CopyPasswordField
required
readonly={database.defaultDatabase}
disabled={database.defaultDatabase}
placeholder="{$t('forms.eg')}: mydb"
id="defaultDatabase"
name="defaultDatabase"
bind:value={database.defaultDatabase}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUser" class="text-base font-bold text-stone-100"
>Root (postgres) User Password</label
>
<CopyPasswordField
disabled={!$status.database.isRunning}
readonly={!$status.database.isRunning}
placeholder="Generated automatically after start"
isPasswordField
id="rootUserPassword"
name="rootUserPassword"
bind:value={database.rootUserPassword}
/>
<Explainer text="Could be changed while the database is running." />
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
<CopyPasswordField
readonly
disabled
placeholder={$t('forms.generated_automatically_after_start')}
id="dbUser"
name="dbUser"
value={database.dbUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.password')}</label
>
<CopyPasswordField
disabled={!$status.database.isRunning}
readonly={!$status.database.isRunning}
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="dbUserPassword"
name="dbUserPassword"
bind:value={database.dbUserPassword}
/>
<Explainer text="Could be changed while the database is running." />
</div>
</div>

View File

@@ -0,0 +1,28 @@
<script lang="ts">
export let database: any;
import { status } from '$lib/store';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">Redis</div>
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.password')}</label
>
<CopyPasswordField
disabled={!$status.database.isRunning}
readonly={!$status.database.isRunning}
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="dbUserPassword"
name="dbUserPassword"
bind:value={database.dbUserPassword}
/>
<Explainer text="Could be changed while the database is running." />
</div>
</div>

View File

@@ -0,0 +1,297 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
function checkConfiguration(database: any): any {
let configurationPhase = null;
if (!database.type) {
configurationPhase = 'type';
} else if (!database.version) {
configurationPhase = 'version';
} else if (!database.destinationDockerId) {
configurationPhase = 'destination';
}
return configurationPhase;
}
export const load: Load = async ({ fetch, url, params }) => {
try {
const { id } = params;
const response = await get(`/databases/${id}`);
const { database, versions, privatePort, settings } = response;
if (id !== 'new' && (!database || Object.entries(database).length === 0)) {
return {
status: 302,
redirect: '/databases'
};
}
const configurationPhase = checkConfiguration(database);
if (
configurationPhase &&
url.pathname !== `/databases/${params.id}/configuration/${configurationPhase}`
) {
return {
status: 302,
redirect: `/databases/${params.id}/configuration/${configurationPhase}`
};
}
return {
props: {
database,
versions,
privatePort
},
stuff: {
database,
versions,
privatePort,
settings
}
};
} catch (error) {
return handlerNotFoundLoad(error, url);
}
};
</script>
<script lang="ts">
export let database: any;
import { del, get, post } from '$lib/api';
import { t } from '$lib/translations';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
import { appSession, status } from '$lib/store';
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
import Loading from '$lib/components/Loading.svelte';
import { onDestroy, onMount } from 'svelte';
const { id } = $page.params;
let loading = false;
let statusInterval: any = false;
async function deleteDatabase() {
const sure = confirm(`Are you sure you would like to delete '${database.name}'?`);
if (sure) {
loading = true;
try {
await del(`/databases/${database.id}`, { id: database.id });
return await goto('/databases');
} catch ({ error }) {
return errorNotification(error);
} finally {
loading = false;
}
}
}
async function stopDatabase() {
const sure = confirm($t('database.confirm_stop', { name: database.name }));
if (sure) {
loading = true;
try {
await post(`/databases/${database.id}/stop`, {});
return window.location.reload();
} catch ({ error }) {
return errorNotification(error);
}
}
}
async function startDatabase() {
loading = true;
try {
await post(`/databases/${database.id}/start`, {});
return window.location.reload();
} catch ({ error }) {
return errorNotification(error);
}
}
async function getStatus() {
if ($status.database.loading) return;
$status.database.loading = true;
const data = await get(`/databases/${id}`);
$status.database.isRunning = data.isRunning;
$status.database.initialLoading = false;
$status.database.loading = false;
}
onDestroy(() => {
$status.database.initialLoading = true;
clearInterval(statusInterval);
});
onMount(async () => {
$status.database.isRunning = false;
$status.database.loading = false;
if (
database.type &&
database.destinationDockerId &&
database.version &&
database.defaultDatabase
) {
await getStatus();
statusInterval = setInterval(async () => {
await getStatus();
}, 2000);
}
});
</script>
{#if id !== 'new'}
<nav class="nav-side">
{#if loading}
<Loading fullscreen cover />
{:else}
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
{#if $status.database.initialLoading}
<button
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
>
<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="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
<line x1="11" y1="19.94" x2="11" y2="19.95" />
</svg>
</button>
{:else if $status.database.isRunning}
<button
on:click={stopDatabase}
title={$t('database.stop_database')}
type="submit"
disabled={!$appSession.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
data-tooltip={$appSession.isAdmin
? $t('database.stop_database')
: $t('database.permission_denied_stop_database')}
>
<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>
{:else}
<button
on:click={startDatabase}
title={$t('database.start_database')}
type="submit"
disabled={!$appSession.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
data-tooltip={$appSession.isAdmin
? $t('database.start_database')
: $t('database.permission_denied_start_database')}
><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>
{/if}
{/if}
<div class="border border-stone-700 h-8" />
<a
href="/databases/{id}"
sveltekit:prefetch
class="hover:text-yellow-500 rounded"
class:text-yellow-500={$page.url.pathname === `/databases/${id}`}
class:bg-coolgray-500={$page.url.pathname === `/databases/${id}`}
>
<button
title={$t('application.configurations')}
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
data-tooltip={$t('application.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
>
<div class="border border-stone-700 h-8" />
<a
href={$status.database.isRunning ? `/databases/${id}/logs` : null}
sveltekit:prefetch
class="hover:text-pink-500 rounded"
class:text-pink-500={$page.url.pathname === `/databases/${id}/logs`}
class:bg-coolgray-500={$page.url.pathname === `/databases/${id}/logs`}
>
<button
title={$t('database.logs')}
disabled={!$status.database.isRunning}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip={$t('database.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
>
<button
on:click={deleteDatabase}
title={$t('database.delete_database')}
type="submit"
disabled={!$appSession.isAdmin}
class:hover:text-red-500={$appSession.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip={$appSession.isAdmin
? $t('database.delete_database')
: $t('database.permission_denied_delete_database')}><DeleteIcon /></button
>
{/if}
</nav>
{/if}
<slot />

View File

@@ -0,0 +1,91 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
try {
const { database } = stuff;
if (database?.destinationDockerId && !url.searchParams.get('from')) {
return {
status: 302,
redirect: `/database/${params.id}`
};
}
const response = await get(`/destinations`);
return {
props: {
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
export let destinations: any;
async function handleSubmit(destinationId: any) {
try {
await post(`/databases/${id}/configuration/destination`, {
destinationId
});
return await goto(from || `/databases/${id}`);
} catch (error) {
return errorNotification(error);
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">
{$t('application.configuration.configure_destination')}
</div>
</div>
<div class="flex justify-center">
{#if !destinations || destinations.length === 0}
<div class="flex-col">
<div class="pb-2">{$t('application.configuration.no_configurable_destination')}</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>

View File

@@ -0,0 +1,84 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
try {
const { database } = stuff;
if (database?.type && !url.searchParams.get('from')) {
return {
status: 302,
redirect: `/database/${params.id}`
};
}
const response = await get(`/databases/${params.id}/configuration/type`);
return {
props: {
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
export let types: any;
import { page } from '$app/stores';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
import Clickhouse from '$lib/components/svg/databases/Clickhouse.svelte';
import CouchDB from '$lib/components/svg/databases/CouchDB.svelte';
import MongoDB from '$lib/components/svg/databases/MongoDB.svelte';
import MariaDB from '$lib/components/svg/databases/MariaDB.svelte';
import MySQL from '$lib/components/svg/databases/MySQL.svelte';
import PostgreSQL from '$lib/components/svg/databases/PostgreSQL.svelte';
import Redis from '$lib/components/svg/databases/Redis.svelte';
import { goto } from '$app/navigation';
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
async function handleSubmit(type: any) {
try {
await post(`/databases/${id}/configuration/type`, { type });
return await goto(from || `/databases/${id}/configuration/version`);
} catch (error) {
return errorNotification(error);
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('database.select_database_type')}</div>
</div>
<div class="flex flex-wrap justify-center">
{#each types as type}
<div class="p-2">
<form on:submit|preventDefault={() => handleSubmit(type.name)}>
<button type="submit" class="box-selection relative text-xl font-bold hover:bg-purple-700">
{#if type.name === 'clickhouse'}
<Clickhouse isAbsolute />
{:else if type.name === 'couchdb'}
<CouchDB isAbsolute />
{:else if type.name === 'mongodb'}
<MongoDB isAbsolute />
{:else if type.name === 'mariadb'}
<MariaDB isAbsolute />
{:else if type.name === 'mysql'}
<MySQL isAbsolute />
{:else if type.name === 'postgresql'}
<PostgreSQL isAbsolute />
{:else if type.name === 'redis'}
<Redis isAbsolute />
{/if}{type.fancyName}
</button>
</form>
</div>
{/each}
</div>

View File

@@ -0,0 +1,70 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
try {
const { database } = stuff;
if (database?.version && !url.searchParams.get('from')) {
return {
status: 302,
redirect: `/database/${params.id}`
};
}
const response = await get(`/databases/${params.id}/configuration/version`);
return {
props: {
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
export let versions: any;
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
async function handleSubmit(version: any) {
try {
await post(`/databases/${id}/configuration/version`, { version });
return await goto(from || `/databases/${id}/configuration/destination`);
} catch (error) {
return errorNotification(error);
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('database.select_database_version')}</div>
</div>
{#if from}
<div class="pb-10 text-center">
Warning: you are about to change the version of this database.<br />This could cause problem
after you restart the database,
<span class="font-bold text-pink-600">like losing your data, incompatibility issues, etc</span
>.<br />Only do if you know what you are doing!
</div>
{/if}
<div class="flex flex-wrap justify-center">
{#each versions as version}
<div class="p-2">
<form on:submit|preventDefault={() => handleSubmit(version)}>
<button type="submit" class="box-selection text-xl font-bold hover:bg-purple-700"
>{version}</button
>
</form>
</div>
{/each}
</div>

View File

@@ -0,0 +1,89 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ stuff }) => {
return {
props: { ...stuff }
};
};
</script>
<script lang="ts">
import { page } from '$app/stores';
import { get } from '$lib/api';
import { onDestroy, onMount } from 'svelte';
import DatabaseLinks from './_DatabaseLinks.svelte';
import Databases from './_Databases/_Databases.svelte';
import { status } from '$lib/store';
export let database: any;
export let settings: any;
export let privatePort: any;
const { id } = $page.params;
let loading = {
usage: false
};
let usage = {
MemUsage: 0,
CPUPerc: 0,
NetIO: 0
};
let usageInterval: any;
async function getUsage() {
if (loading.usage) return;
if (!$status.database.isRunning) return;
loading.usage = true;
const data = await get(`/databases/${id}/usage`);
usage = data.usage;
loading.usage = false;
}
onDestroy(() => {
clearInterval(usageInterval);
});
onMount(async () => {
await getUsage();
usageInterval = setInterval(async () => {
await getUsage();
}, 1500);
});
</script>
<div class="flex items-center space-x-2 p-6 text-2xl font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Configuration
</div>
<span class="text-xs">{database.name}</span>
</div>
<DatabaseLinks {database} />
</div>
<div class="mx-auto max-w-4xl px-6 py-4">
<div class="text-2xl font-bold">Database Usage</div>
<div class="mx-auto">
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class=" text-sm font-medium text-white">Used Memory / Memory Limit</dt>
<dd class="mt-1 text-xl font-semibold text-white">
{usage?.MemUsage}
</dd>
</div>
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class="truncate text-sm font-medium text-white">Used CPU</dt>
<dd class="mt-1 text-xl font-semibold text-white ">
{usage?.CPUPerc}
</dd>
</div>
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
<dt class="truncate text-sm font-medium text-white">Network IO</dt>
<dd class="mt-1 text-xl font-semibold text-white ">
{usage?.NetIO}
</dd>
</div>
</dl>
</div>
</div>
<Databases bind:database {privatePort} {settings} />

View File

@@ -0,0 +1,41 @@
<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>

View File

@@ -0,0 +1,178 @@
<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 }) => {
try {
const response = await get(`/databases/${params.id}/logs`);
return {
props: {
database: stuff.database,
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
export let database: any;
import { page } from '$app/stores';
import LoadingLogs from './_Loading.svelte';
import { get } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
let loadLogsInterval: any = null;
let logs: any = [];
let lastLog: any = null;
let followingInterval: any;
let followingLogs: any;
let logsEl: any;
let position = 0;
const { id } = $page.params;
onMount(async () => {
loadAllLogs();
loadLogsInterval = setInterval(() => {
loadLogs();
}, 1000);
});
onDestroy(() => {
clearInterval(loadLogsInterval);
clearInterval(followingInterval);
});
async function loadAllLogs() {
try {
const data: any = await get(`/databases/${id}/logs`);
if (data?.logs) {
lastLog = data.logs[data.logs.length - 1];
logs = data.logs;
}
} catch (error) {
console.log(error);
return errorNotification(error);
}
}
async function loadLogs() {
try {
const newLogs: any = await get(
`/databases/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`
);
if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) {
logs = logs.concat(newLogs.logs);
lastLog = newLogs.logs[newLogs.logs.length - 1];
}
} catch (error) {
return errorNotification(error);
}
}
function detect() {
if (position < logsEl.scrollTop) {
position = logsEl.scrollTop;
} else {
if (followingLogs) {
clearInterval(followingInterval);
followingLogs = false;
}
position = logsEl.scrollTop;
}
}
function followBuild() {
followingLogs = !followingLogs;
if (followingLogs) {
followingInterval = setInterval(() => {
logsEl.scrollTop = logsEl.scrollHeight;
window.scrollTo(0, document.body.scrollHeight);
}, 1000);
} else {
clearInterval(followingInterval);
}
}
</script>
<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Database Logs
</div>
<span class="text-xs">{database.name}</span>
</div>
{#if database.fqdn}
<a
href={database.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}
</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">{$t('application.build.waiting_logs')}</div>
{:else}
<div class="relative w-full">
<div class="text-right " />
{#if loadLogsInterval}
<LoadingLogs />
{/if}
<div class="flex justify-end sticky top-0 p-2 mx-1">
<button
on:click={followBuild}
class="bg-transparent"
data-tooltip="Follow logs"
class:text-green-500={followingLogs}
>
<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 w-full 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}
on:scroll={detect}
>
<div class="px-2 pr-14">
{#each logs as log}
{log + '\n'}
{/each}
</div>
</div>
</div>
{/if}
</div>

View File

@@ -0,0 +1,153 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async () => {
try {
const response = await get(`/databases`);
return {
props: {
...response
}
};
} catch (error: any) {
return {
status: 500,
error: new Error(error)
};
}
};
</script>
<script lang="ts">
export let databases: any = [];
import Clickhouse from '$lib/components/svg/databases/Clickhouse.svelte';
import CouchDB from '$lib/components/svg/databases/CouchDB.svelte';
import MongoDB from '$lib/components/svg/databases/MongoDB.svelte';
import MariaDB from '$lib/components/svg/databases/MariaDB.svelte';
import MySQL from '$lib/components/svg/databases/MySQL.svelte';
import PostgreSQL from '$lib/components/svg/databases/PostgreSQL.svelte';
import Redis from '$lib/components/svg/databases/Redis.svelte';
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { appSession } from '$lib/store';
import { goto } from '$app/navigation';
async function newDatabase() {
const { id } = await post('/databases/new', {});
return await goto(`/databases/${id}`, { replaceState: true });
}
const ownDatabases = databases.filter((database: any) => {
if (database.teams[0].id === $appSession.teamId) {
return database;
}
});
const otherDatabases = databases.filter((database: any) => {
if (database.teams[0].id !== $appSession.teamId) {
return database;
}
});
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('index.databases')}</div>
<div on:click={newDatabase} class="add-icon cursor-pointer bg-purple-600 hover:bg-purple-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>
</div>
<div class="flex-col justify-center">
{#if !databases || ownDatabases.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>
</div>
{/if}
{#if ownDatabases.length > 0 || otherDatabases.length > 0}
<div class="flex flex-col">
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
{#each ownDatabases as database}
<a href="/databases/{database.id}" class="w-96 p-2 no-underline">
<div class="box-selection group relative hover:bg-purple-600">
{#if database.type === 'clickhouse'}
<Clickhouse isAbsolute />
{:else if database.type === 'couchdb'}
<CouchDB isAbsolute />
{:else if database.type === 'mongodb'}
<MongoDB isAbsolute />
{:else if database.type === 'mysql'}
<MySQL isAbsolute />
{:else if database.type === 'mariadb'}
<MariaDB isAbsolute />
{:else if database.type === 'postgresql'}
<PostgreSQL isAbsolute />
{:else if database.type === 'redis'}
<Redis isAbsolute />
{/if}
<div class="truncate text-center text-xl font-bold">
{database.name}
</div>
{#if $appSession.teamId === '0' && otherDatabases.length > 0}
<div class="truncate text-center">{database.teams[0].name}</div>
{/if}
{#if !database.type}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
{$t('application.configuration.configuration_missing')}
</div>
{/if}
</div>
</a>
{/each}
</div>
{#if otherDatabases.length > 0 && $appSession.teamId === '0'}
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Databases</div>
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
{#each otherDatabases as database}
<a href="/databases/{database.id}" class="w-96 p-2 no-underline">
<div class="box-selection group relative hover:bg-purple-600">
{#if database.type === 'clickhouse'}
<Clickhouse isAbsolute />
{:else if database.type === 'couchdb'}
<CouchDB isAbsolute />
{:else if database.type === 'mongodb'}
<MongoDB isAbsolute />
{:else if database.type === 'mariadb'}
<MariaDB isAbsolute />
{:else if database.type === 'mysql'}
<MySQL isAbsolute />
{:else if database.type === 'postgresql'}
<PostgreSQL isAbsolute />
{:else if database.type === 'redis'}
<Redis isAbsolute />
{/if}
<div class="truncate text-center text-xl font-bold">
{database.name}
</div>
{#if $appSession.teamId === '0'}
<div class="truncate text-center">{database.teams[0].name}</div>
{/if}
{#if !database.type}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Configuration missing
</div>
{:else}
<div class="text-center truncate">{database.type}</div>
{/if}
</div>
</a>
{/each}
</div>
{/if}
</div>
{/if}
</div>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
export let destination: any;
export let settings: any;
export let state: any;
import LocalDocker from './_LocalDocker.svelte';
import RemoteDocker from './_RemoteDocker.svelte';
</script>
<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Configuration
</div>
<span class="text-xs">{destination.name}</span>
</div>
</div>
<div class="mx-auto max-w-4xl px-6">
{#if destination.remoteEngine}
<RemoteDocker bind:destination {settings} {state} />
{:else}
<LocalDocker bind:destination {settings} {state} />
{/if}
</div>

View File

@@ -0,0 +1,202 @@
<script lang="ts">
export let destination: any;
export let settings: any;
export let state: any;
import { toast } from '@zerodevx/svelte-toast';
import { page } from '$app/stores';
import { post } from '$lib/api';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { onMount } from 'svelte';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import { appSession } from '$lib/store';
import Setting from '$lib/components/Setting.svelte';
const { id } = $page.params;
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
let loading = false;
let loadingProxy = false;
let restarting = false;
async function handleSubmit() {
loading = true;
try {
return await post(`/destinations/${id}`, { ...destination });
} catch (error) {
return errorNotification(error);
} finally {
loading = false;
}
}
onMount(async () => {
if (state === false && destination.isCoolifyProxyUsed === true) {
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
try {
await post(`/destinations/${id}/settings`, {
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
engine: destination.engine
});
await stopProxy();
} catch (error) {
return errorNotification(error);
}
} else if (state === true && destination.isCoolifyProxyUsed === false) {
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
try {
await post(`/destinations/${id}/settings`, {
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
engine: destination.engine
});
await startProxy();
} catch (error) {
return errorNotification(error);
}
}
});
async function changeProxySetting() {
if (!cannotDisable) {
const isProxyActivated = destination.isCoolifyProxyUsed;
if (isProxyActivated) {
const sure = confirm(
`Are you sure you want to ${
destination.isCoolifyProxyUsed ? 'disable' : 'enable'
} Coolify proxy? It will remove the proxy for all configured networks and all deployments on '${
destination.engine
}'! Nothing will be reachable if you do it!`
);
if (!sure) return;
}
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
try {
loadingProxy = true;
await post(`/destinations/${id}/settings`, {
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
engine: destination.engine
});
if (isProxyActivated) {
await stopProxy();
} else {
await startProxy();
}
} catch ({ error }) {
return errorNotification(error);
} finally {
loadingProxy = false;
}
}
}
async function stopProxy() {
try {
await post(`/destinations/${id}/stop`, { engine: destination.engine });
return toast.push($t('destination.coolify_proxy_stopped'));
} catch ({ error }) {
return errorNotification(error);
}
}
async function startProxy() {
try {
await post(`/destinations/${id}/start`, { engine: destination.engine });
return toast.push($t('destination.coolify_proxy_started'));
} catch ({ error }) {
return errorNotification(error);
}
}
async function forceRestartProxy() {
const sure = confirm($t('destination.confirm_restart_proxy'));
if (sure) {
try {
restarting = true;
toast.push($t('destination.coolify_proxy_restarting'));
await post(`/destinations/${id}/restart`, {
engine: destination.engine,
fqdn: settings.fqdn
});
} catch ({ error }) {
setTimeout(() => {
window.location.reload();
}, 5000);
} finally {
restarting = false;
}
}
}
</script>
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex space-x-1 pb-5">
<div class="title font-bold">{$t('forms.configuration')}</div>
{#if $appSession.isAdmin}
<button
type="submit"
class="bg-sky-600 hover:bg-sky-500"
class:bg-sky-600={!loading}
class:hover:bg-sky-500={!loading}
disabled={loading}
>{loading ? $t('forms.saving') : $t('forms.save')}
</button>
<button
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
disabled={restarting}
on:click|preventDefault={forceRestartProxy}
>{restarting
? $t('destination.restarting_please_wait')
: $t('destination.force_restart_proxy')}</button
>
{/if}
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
>Scan for applications</button
> -->
</div>
<div class="grid grid-cols-2 items-center px-10 ">
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<input
name="name"
placeholder={$t('forms.name')}
disabled={!$appSession.isAdmin}
readonly={!$appSession.isAdmin}
bind:value={destination.name}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="engine" class="text-base font-bold text-stone-100">{$t('forms.engine')}</label>
<CopyPasswordField
id="engine"
readonly
disabled
name="engine"
placeholder="{$t('forms.eg')}: /var/run/docker.sock"
value={destination.engine}
/>
</div>
<!-- <div class="flex items-center">
<label for="remoteEngine">Remote Engine?</label>
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
</div> -->
<div class="grid grid-cols-2 items-center px-10">
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
<CopyPasswordField
id="network"
readonly
disabled
name="network"
placeholder="{$t('forms.default')}: coolify"
value={destination.network}
/>
</div>
{#if $appSession.teamId === '0'}
<div class="grid grid-cols-2 items-center">
<Setting
loading={loadingProxy}
disabled={cannotDisable}
bind:setting={destination.isCoolifyProxyUsed}
on:click={changeProxySetting}
title={$t('destination.use_coolify_proxy')}
description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration. Databases will have their own proxy. <br><br>${
cannotDisable
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
: ''
}`}
/>
</div>
{/if}
</form>

View File

@@ -0,0 +1,57 @@
<script lang="ts">
import cuid from 'cuid';
import { t } from '$lib/translations';
import NewLocalDocker from './_NewLocalDocker.svelte';
import NewRemoteDocker from './_NewRemoteDocker.svelte';
let payload = {};
let selected = 'localDocker';
function setPredefined(type: any) {
selected = type;
switch (type) {
case 'localDocker':
payload = {
name: t.get('sources.local_docker'),
engine: '/var/run/docker.sock',
remoteEngine: false,
network: cuid(),
isCoolifyProxyUsed: true
};
break;
case 'remoteDocker':
payload = {
name: $t('sources.remote_docker'),
remoteEngine: true,
ipAddress: null,
user: 'root',
port: 22,
sshPrivateKey: null,
network: cuid(),
isCoolifyProxyUsed: true
};
break;
default:
break;
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('destination.new.add_new_destination')}</div>
</div>
<div class="flex-col space-y-2 pb-10 text-center">
<div class="text-xl font-bold text-white">{$t('destination.new.predefined_destinations')}</div>
<div class="flex justify-center space-x-2">
<button class="w-32" on:click={() => setPredefined('localDocker')}
>{$t('sources.local_docker')}</button
>
<!-- <button class="w-32" on:click={() => setPredefined('remoteDocker')}>Remote Docker</button> -->
<!-- <button class="w-32" on:click={() => setPredefined('kubernetes')}>Kubernetes</button> -->
</div>
</div>
{#if selected === 'localDocker'}
<NewLocalDocker {payload} />
{:else if selected === 'remoteDocker'}
<NewRemoteDocker {payload} />
{:else}
<div class="text-center font-bold text-4xl py-10">{$t('index.not_implemented_yet')}</div>
{/if}

Some files were not shown because too many files have changed in this diff Show More