Merge pull request #532 from coollabsio/next

v3.3.0
This commit is contained in:
Andras Bacsai 2022-08-12 16:22:57 +02:00 committed by GitHub
commit ff8849a907
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 852 additions and 628 deletions

View File

@ -1,10 +1,11 @@
import { parentPort } from 'node:worker_threads';
import { prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, executeDockerCmd } from '../lib/common';
import { prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, executeDockerCmd, listSettings } from '../lib/common';
import { checkContainer } from '../lib/docker';
(async () => {
if (parentPort) {
try {
const { arch } = await listSettings();
// Coolify Proxy local
const engine = '/var/run/docker.sock';
const localDocker = await prisma.destinationDocker.findFirst({
@ -30,7 +31,7 @@ import { checkContainer } from '../lib/docker';
for (const database of databasesWithPublicPort) {
const { destinationDockerId, destinationDocker, publicPort, id } = database;
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
const { privatePort } = generateDatabaseConfiguration(database);
const { privatePort } = generateDatabaseConfiguration(database, arch);
// Remove HAProxy
const found = await checkContainer({
dockerId: localDocker.id, container: `haproxy-for-${publicPort}`

View File

@ -17,7 +17,7 @@ import { checkContainer, removeContainer } from './docker';
import { day } from './dayjs';
import * as serviceFields from './serviceFields'
export const version = '3.2.3';
export const version = '3.3.0';
export const isDev = process.env.NODE_ENV === 'development';
const algorithm = 'aes-256-ctr';
@ -460,28 +460,50 @@ export const supportedDatabaseTypesAndVersions = [
name: 'mongodb',
fancyName: 'MongoDB',
baseImage: 'bitnami/mongodb',
versions: ['5.0', '4.4', '4.2']
baseImageARM: 'mongo',
versions: ['5.0', '4.4', '4.2'],
versionsARM: ['5.0', '4.4', '4.2']
},
{
name: 'mysql',
fancyName: 'MySQL',
baseImage: 'bitnami/mysql',
baseImageARM: 'mysql',
versions: ['8.0', '5.7'],
versionsARM: ['8.0', '5.7']
},
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0', '5.7'] },
{
name: 'mariadb',
fancyName: 'MariaDB',
baseImage: 'bitnami/mariadb',
versions: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
baseImageARM: 'mariadb',
versions: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2'],
versionsARM: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
},
{
name: 'postgresql',
fancyName: 'PostgreSQL',
baseImage: 'bitnami/postgresql',
versions: ['14.4.0', '13.6.0', '12.10.0', '11.15.0', '10.20.0']
baseImageARM: 'postgres',
versions: ['14.5.0', '13.8.0', '12.12.0', '11.17.0', '10.22.0'],
versionsARM: ['14.5', '13.8', '12.12', '11.17', '10.22']
},
{
name: 'redis',
fancyName: 'Redis',
baseImage: 'bitnami/redis',
versions: ['7.0', '6.2', '6.0', '5.0']
baseImageARM: 'redis',
versions: ['7.0', '6.2', '6.0', '5.0'],
versionsARM: ['7.0', '6.2', '6.0', '5.0']
},
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.2'] }
{
name: 'couchdb',
fancyName: 'CouchDB',
baseImage: 'bitnami/couchdb',
baseImageARM: 'couchdb',
versions: ['3.2.2', '3.1.2', '2.3.1'],
versionsARM: ['3.2.2', '3.1.2', '2.3.1']
}
];
export async function getFreeSSHLocalPort(id: string): Promise<number> {
@ -674,10 +696,11 @@ export function generatePassword(length = 24, symbols = false): string {
});
}
export function generateDatabaseConfiguration(database: any):
export function generateDatabaseConfiguration(database: any, arch: string):
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
@ -691,16 +714,20 @@ export function generateDatabaseConfiguration(database: any):
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
MONGODB_ROOT_USER: string;
MONGODB_ROOT_PASSWORD: string;
MONGO_INITDB_ROOT_USERNAME?: string;
MONGO_INITDB_ROOT_PASSWORD?: string;
MONGODB_ROOT_USER?: string;
MONGODB_ROOT_PASSWORD?: string;
};
}
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
@ -714,6 +741,7 @@ export function generateDatabaseConfiguration(database: any):
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
@ -726,6 +754,19 @@ export function generateDatabaseConfiguration(database: any):
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
POSTGRES_USER: string;
POSTGRES_PASSWORD: string;
POSTGRES_DB: string;
};
}
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
@ -736,6 +777,7 @@ export function generateDatabaseConfiguration(database: any):
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
@ -754,9 +796,9 @@ export function generateDatabaseConfiguration(database: any):
type,
settings: { appendOnly }
} = database;
const baseImage = getDatabaseImage(type);
const baseImage = getDatabaseImage(type, arch);
if (type === 'mysql') {
return {
const configuration = {
privatePort: 3306,
environmentVariables: {
MYSQL_USER: dbUser,
@ -768,9 +810,13 @@ export function generateDatabaseConfiguration(database: any):
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/mysql/data`,
ulimits: {}
};
}
if (isARM(arch)) {
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
}
return configuration
} else if (type === 'mariadb') {
return {
const configuration = {
privatePort: 3306,
environmentVariables: {
MARIADB_ROOT_USER: rootUser,
@ -783,8 +829,12 @@ export function generateDatabaseConfiguration(database: any):
volume: `${id}-${type}-data:/bitnami/mariadb`,
ulimits: {}
};
if (isARM(arch)) {
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
}
return configuration
} else if (type === 'mongodb') {
return {
const configuration = {
privatePort: 27017,
environmentVariables: {
MONGODB_ROOT_USER: rootUser,
@ -794,8 +844,16 @@ export function generateDatabaseConfiguration(database: any):
volume: `${id}-${type}-data:/bitnami/mongodb`,
ulimits: {}
};
if (isARM(arch)) {
configuration.environmentVariables = {
MONGO_INITDB_ROOT_USERNAME: rootUser,
MONGO_INITDB_ROOT_PASSWORD: rootUserPassword
}
configuration.volume = `${id}-${type}-data:/data/db`;
}
return configuration
} else if (type === 'postgresql') {
return {
const configuration = {
privatePort: 5432,
environmentVariables: {
POSTGRESQL_POSTGRES_PASSWORD: rootUserPassword,
@ -806,10 +864,15 @@ export function generateDatabaseConfiguration(database: any):
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/postgresql`,
ulimits: {}
};
}
if (isARM(arch)) {
configuration.volume = `${id}-${type}-data:/var/lib/postgresql`;
}
return configuration
} else if (type === 'redis') {
return {
const configuration = {
privatePort: 6379,
command: undefined,
environmentVariables: {
REDIS_PASSWORD: dbUserPassword,
REDIS_AOF_ENABLED: appendOnly ? 'yes' : 'no'
@ -818,8 +881,13 @@ export function generateDatabaseConfiguration(database: any):
volume: `${id}-${type}-data:/bitnami/redis/data`,
ulimits: {}
};
if (isARM(arch)) {
configuration.volume = `${id}-${type}-data:/data`;
configuration.command = `/usr/local/bin/redis-server --appendonly ${appendOnly ? 'yes' : 'no'} --requirepass ${dbUserPassword}`;
}
return configuration
} else if (type === 'couchdb') {
return {
const configuration = {
privatePort: 5984,
environmentVariables: {
COUCHDB_PASSWORD: dbUserPassword,
@ -829,20 +897,35 @@ export function generateDatabaseConfiguration(database: any):
volume: `${id}-${type}-data:/bitnami/couchdb`,
ulimits: {}
};
if (isARM(arch)) {
configuration.volume = `${id}-${type}-data:/opt/couchdb/data`;
}
return configuration
}
}
export function getDatabaseImage(type: string): string {
export function isARM(arch) {
if (arch === 'arm' || arch === 'arm64') {
return true
}
return false
}
export function getDatabaseImage(type: string, arch: string): string {
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
if (found) {
if (isARM(arch)) {
return found.baseImageARM || found.baseImage
}
return found.baseImage;
}
return '';
}
export function getDatabaseVersions(type: string): string[] {
export function getDatabaseVersions(type: string, arch: string): string[] {
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
if (found) {
if (isARM(arch)) {
return found.versionsARM || found.versions
}
return found.versions;
}
return [];

View File

@ -3,7 +3,7 @@ import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify';
import yaml from 'js-yaml';
import fs from 'fs/promises';
import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, isARM, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
import { checkContainer } from '../../../../lib/docker';
import { day } from '../../../../lib/dayjs';
@ -93,14 +93,15 @@ export async function getDatabase(request: FastifyRequest<OnlyId>) {
if (!database) {
throw { status: 404, message: 'Database not found.' }
}
const { arch } = await listSettings();
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
const configuration = generateDatabaseConfiguration(database);
const configuration = generateDatabaseConfiguration(database, arch);
const settings = await listSettings();
return {
privatePort: configuration?.privatePort,
database,
versions: await getDatabaseVersions(database.type),
versions: await getDatabaseVersions(database.type, arch),
settings
};
} catch ({ status, message }) {
@ -137,8 +138,10 @@ export async function getVersions(request: FastifyRequest<OnlyId>) {
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
const { arch } = await listSettings();
const versions = getDatabaseVersions(type, arch);
return {
versions: supportedDatabaseTypesAndVersions.find((name) => name.name === type).versions
versions
}
} catch ({ status, message }) {
return errorHandler({ status, message })
@ -219,6 +222,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
const { arch } = await listSettings();
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
const {
@ -228,8 +232,8 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
publicPort,
settings: { isPublic }
} = database;
const { privatePort, environmentVariables, image, volume, ulimits } =
generateDatabaseConfiguration(database);
const { privatePort, command, environmentVariables, image, volume, ulimits } =
generateDatabaseConfiguration(database, arch);
const network = destinationDockerId && destinationDocker.network;
const volumeName = volume.split(':')[0];
@ -243,6 +247,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
[id]: {
container_name: id,
image,
command,
networks: [network],
environment: environmentVariables,
volumes: [volume],
@ -270,6 +275,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
}
}
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {
@ -282,6 +288,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
return {};
} catch (error) {
console.log(error)
throw {
error
};
@ -440,11 +447,12 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
const { arch } = await listSettings();
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database;
const { privatePort } = generateDatabaseConfiguration(database);
const { privatePort } = generateDatabaseConfiguration(database, arch);
if (destinationDockerId) {
if (isPublic) {

View File

@ -96,34 +96,19 @@ export async function showDashboard(request: FastifyRequest) {
try {
const userId = request.user.userId;
const teamId = request.user.teamId;
const applicationsCount = await prisma.application.count({
const applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
});
const sourcesCount = await prisma.gitSource.count({
const databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
});
const destinationsCount = await prisma.destinationDocker.count({
const services = await prisma.service.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
});
const teamsCount = await prisma.permission.count({ where: { userId } });
const databasesCount = await prisma.database.count({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
});
const servicesCount = await prisma.service.count({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
});
const teams = await prisma.permission.findMany({
where: { userId },
include: { team: { include: { _count: { select: { users: true } } } } }
});
return {
teams,
applicationsCount,
sourcesCount,
destinationsCount,
teamsCount,
databasesCount,
servicesCount,
applications,
databases,
services,
};
} catch ({ status, message }) {
return errorHandler({ status, message })

View File

@ -232,35 +232,6 @@ export function changeQueryParams(buildId: string) {
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 getServiceMainPort = (service: string) => {
const serviceType = supportedServiceTypesAndVersions.find((s) => s.name === service);
if (serviceType) {

View File

@ -1,10 +1,16 @@
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let type = 'info';
</script>
<div
on:mouseover={() => dispatch('pause')}
on:focus={() => dispatch('pause')}
on:mouseout={() => dispatch('resume')}
on:blur={() => dispatch('resume')}
class="alert shadow-lg text-white rounded"
class:alert-success={type === 'success'}
class:bg-coollabs={type === 'success'}
class:alert-error={type === 'error'}
class:alert-info={type === 'info'}
>

View File

@ -2,14 +2,18 @@
import { fade } from 'svelte/transition';
import Toast from './Toast.svelte';
import { toasts } from '$lib/store';
import { pauseToast, resumeToast, toasts } from '$lib/store';
</script>
{#if $toasts}
<section>
<article class="toast toast-bottom toast-end rounded-none" role="alert" transition:fade>
<article class="toast toast-top toast-end rounded-none" role="alert" transition:fade>
{#each $toasts as toast (toast.id)}
<Toast type={toast.type}>{@html toast.message}</Toast>
<Toast
type={toast.type}
on:resume={() => resumeToast(toast.id)}
on:pause={() => pauseToast(toast.id)}>{@html toast.message}</Toast
>
{/each}
</article>
</section>

View File

@ -22,11 +22,10 @@
usage: false,
cleanup: false
};
import { addToast, appSession } from '$lib/store';
import { appSession } from '$lib/store';
import { onDestroy, onMount } from 'svelte';
import { get, post } from '$lib/api';
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;
@ -49,118 +48,66 @@
return errorNotification(error);
}
});
let warning = {
memory: false,
cpu: false,
disk: false
};
let trends = {
memory: 'stable',
cpu: 'stable',
disk: 'stable'
};
async function manuallyCleanupStorage() {
try {
loading.cleanup = true
await post('/internal/cleanup', {});
return addToast({
message: "Cleanup done.",
type:"success"
})
} catch(error) {
return errorNotification(error);
} finally {
loading.cleanup = false
}
}
</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">
<div class="pb-4">
<div class="title">Hardware Details</div>
<div class="text-center p-8 ">
<div>
<div class="stat w-64">
<div class="stat-title">Total Memory</div>
<div class="stat-value">
{(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 ">
</div>
<div class="stat w-64">
<div class="stat-title">Used Memory</div>
<div class="stat-value">
{(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">
</div>
<div class="stat w-64">
<div class="stat-title">Free Memory</div>
<div class="stat-value">
{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">
</div>
</div>
<div class="py-10">
<div class="stat w-64">
<div class="stat-title">Total CPUs</div>
<div class="stat-value">
{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">
</div>
<div class="stat w-64">
<div class="stat-title">CPU Usage</div>
<div class="stat-value">
{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">
<div class="stat w-64">
<div class="stat-title">Load Average (5,10,30mins)</div>
<div class="stat-value">{usage?.cpu.load}</div>
</div>
</div>
<div>
<div class="stat w-64">
<div class="stat-title">Total Disk</div>
<div class="stat-value">
{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">
</div>
<div class="stat w-64">
<div class="stat-title">Used Disk</div>
<div class="stat-value">
{usage?.disk.usedGb}<span class="text-sm">GB</span>
</dd>
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
>Cleanup Storage</button
>
</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}
<div class="stat w-64">
<div class="stat-title">Free Disk</div>
<div class="stat-value">{usage?.disk.freePercentage}<span class="text-sm">%</span></div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,41 @@
<script lang="ts">
import * as Icons from '$lib/components/svg/applications';
export let application: any;
export let isAbsolute = true;
</script>
{#if application.buildPack.toLowerCase() === 'rust'}
<Icons.Rust {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'node'}
<Icons.Nodejs {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'react'}
<Icons.React {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'svelte'}
<Icons.Svelte {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'vuejs'}
<Icons.Vuejs {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'php'}
<Icons.Php {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'python'}
<Icons.Python {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'static'}
<Icons.Static {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'nestjs'}
<Icons.Nestjs {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'nuxtjs'}
<Icons.Nuxtjs {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'nextjs'}
<Icons.Nextjs {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'gatsby'}
<Icons.Gatsby {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'docker'}
<Icons.Docker {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'astro'}
<Icons.Astro {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'eleventy'}
<Icons.Eleventy {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'deno'}
<Icons.Deno {isAbsolute} />
{:else if application.buildPack.toLowerCase() === 'laravel'}
<Icons.Laravel {isAbsolute} />
{/if}

View File

@ -1,5 +1,9 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class="absolute top-0 left-0 -m-6 h-14 w-14"
class={isAbsolute ? 'absolute top-0 left-0 -m-6 h-14 w-14' : 'mx-auto w-8 h-8'}
viewBox="0 0 256 256"
fill="none"
xmlns="http://www.w3.org/2000/svg"

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,5 +1,11 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class="text-white-500 absolute top-0 left-0 -m-4 h-10 w-10"
class={isAbsolute
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-white-500'
: 'mx-auto w-8 h-8 text-white-500'}
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
focusable="false"

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -1,4 +1,11 @@
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-8 h-16 w-16">
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
viewBox="0 0 128 128"
class={isAbsolute ? 'absolute top-0 left-0 -m-8 h-16 w-16' : 'mx-auto w-8 h-8'}
>
<g
><path
fill-rule="evenodd"

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -1,4 +1,11 @@
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-8 h-16 w-16">
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
viewBox="0 0 128 128"
class={isAbsolute ? 'absolute top-0 left-0 -m-8 h-16 w-16' : 'mx-auto w-8 h-8'}
>
<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"

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,4 +1,11 @@
<svg class="absolute top-0 left-0 -m-4 h-10 w-10" viewBox="0 0 128 128">
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
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"

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 593 B

View File

@ -1,5 +1,9 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class="absolute top-0 left-0 -m-4 h-10 w-10"
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
viewBox="0 0 50 52"
xmlns="http://www.w3.org/2000/svg"
><title>Logomark</title><path

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -1,4 +1,13 @@
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 fill-current text-blue-500" viewBox="0 0 128 128">
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class={isAbsolute
? 'absolute top-0 left-0 -m-4 h-10 w-10 fill-current text-blue-500'
: 'mx-auto w-8 h-8 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"
/>

Before

Width:  |  Height:  |  Size: 312 B

After

Width:  |  Height:  |  Size: 442 B

View File

@ -1,5 +1,9 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class="absolute top-0 left-0 -m-4 h-10 w-10 text-green-500"
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10 text-green-500' : 'mx-auto w-8 h-8 text-green-500'}
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
focusable="false"

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,5 +1,9 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class="absolute top-0 left-0 -m-4 h-10 w-10"
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 400 400"
>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,4 +1,13 @@
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-6 h-14 w-14 text-white">
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
viewBox="0 0 128 128"
class={isAbsolute
? 'absolute top-0 left-0 -m-6 h-14 w-14 text-white'
: 'mx-auto w-8 h-8 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"

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,4 +1,11 @@
<svg class="absolute top-0 left-0 -m-6 h-14 w-14" viewBox="0 0 128 128">
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class={isAbsolute ? 'absolute top-0 left-0 -m-6 h-14 w-14' : 'mx-auto w-8 h-8'}
viewBox="0 0 128 128"
>
<linearGradient
id="a"
gradientUnits="userSpaceOnUse"

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,4 +1,13 @@
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 text-blue-500" viewBox="0 0 128 128">
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class={isAbsolute
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-blue-500'
: 'mx-auto w-8 h-8 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"

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -1,5 +1,11 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class="absolute top-0 left-0 -m-4 h-10 w-10 text-white"
class={isAbsolute
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-white'
: 'mx-auto w-8 h-8 text-white'}
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,5 +1,9 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class="absolute top-0 left-0 -m-4 h-10 w-10"
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,4 +1,13 @@
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 text-green-500" viewBox="0 0 128 128">
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class={isAbsolute
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-green-500'
: 'mx-auto w-8 h-8 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"

Before

Width:  |  Height:  |  Size: 676 B

After

Width:  |  Height:  |  Size: 794 B

View File

@ -0,0 +1,19 @@
//@ts-nocheck
export { default as Rust } from './Rust.svelte';
export { default as Nodejs } from './Nodejs.svelte';
export { default as React } from './React.svelte';
export { default as Svelte } from './Svelte.svelte';
export { default as Vuejs } from './Vuejs.svelte';
export { default as Php } from './PHP.svelte';
export { default as Python } from './Python.svelte';
export { default as Static } from './Static.svelte';
export { default as Nestjs } from './Nestjs.svelte';
export { default as Nuxtjs } from './Nuxtjs.svelte';
export { default as Nextjs } from './Nextjs.svelte';
export { default as Gatsby } from './Gatsby.svelte';
export { default as Docker } from './Docker.svelte';
export { default as Astro } from './Astro.svelte';
export { default as Eleventy } from './Eleventy.svelte';
export { default as Deno } from './Deno.svelte';
export { default as Laravel } from './Laravel.svelte';

View File

@ -0,0 +1,19 @@
<script lang="ts">
import * as Icons from '$lib/components/svg/databases';
export let type: any;
export let isAbsolute = false;
</script>
{#if type === 'mysql'}
<Icons.MySQL {isAbsolute} />
{:else if type === 'postgresql'}
<Icons.PostgreSQL {isAbsolute} />
{:else if type === 'mongodb'}
<Icons.MongoDB {isAbsolute} />
{:else if type === 'mariadb'}
<Icons.MariaDB {isAbsolute} />
{:else if type === 'redis'}
<Icons.Redis {isAbsolute} />
{:else if type === 'couchdb'}
<Icons.CouchDB {isAbsolute} />
{/if}

View File

@ -0,0 +1,10 @@
//@ts-nocheck
export { default as Clickhouse } from './Clickhouse.svelte';
export { default as CouchDB } from './CouchDB.svelte';
export { default as MariaDB } from './MariaDB.svelte';
export { default as MongoDB } from './MongoDB.svelte';
export { default as MySQL } from './MySQL.svelte';
export { default as PostgreSQL } from './PostgreSQL.svelte';
export { default as Redis } from './Redis.svelte';

View File

@ -1,36 +1,37 @@
<script lang="ts">
export let type: string;
export let isAbsolute = true;
import * as Icons from '$lib/components/svg/services';
</script>
{#if type === 'plausibleanalytics'}
<Icons.PlausibleAnalytics isAbsolute />
<Icons.PlausibleAnalytics {isAbsolute} />
{:else if type === 'nocodb'}
<Icons.NocoDb isAbsolute />
<Icons.NocoDb {isAbsolute} />
{:else if type === 'minio'}
<Icons.MinIo isAbsolute />
<Icons.MinIo {isAbsolute} />
{:else if type === 'vscodeserver'}
<Icons.VsCodeServer isAbsolute />
<Icons.VsCodeServer {isAbsolute} />
{:else if type === 'wordpress'}
<Icons.Wordpress isAbsolute />
<Icons.Wordpress {isAbsolute} />
{:else if type === 'vaultwarden'}
<Icons.VaultWarden isAbsolute />
<Icons.VaultWarden {isAbsolute} />
{:else if type === 'languagetool'}
<Icons.LanguageTool isAbsolute />
<Icons.LanguageTool {isAbsolute} />
{:else if type === 'n8n'}
<Icons.N8n isAbsolute />
<Icons.N8n {isAbsolute} />
{:else if type === 'uptimekuma'}
<Icons.UptimeKuma isAbsolute />
<Icons.UptimeKuma {isAbsolute} />
{:else if type === 'ghost'}
<Icons.Ghost isAbsolute />
<Icons.Ghost {isAbsolute} />
{:else if type === 'meilisearch'}
<Icons.MeiliSearch isAbsolute />
<Icons.MeiliSearch {isAbsolute} />
{:else if type === 'umami'}
<Icons.Umami isAbsolute />
<Icons.Umami {isAbsolute} />
{:else if type === 'hasura'}
<Icons.Hasura isAbsolute />
<Icons.Hasura {isAbsolute} />
{:else if type === 'fider'}
<Icons.Fider isAbsolute />
<Icons.Fider {isAbsolute} />
{:else if type === 'moodle'}
<Icons.Moodle isAbsolute />
<Icons.Moodle {isAbsolute} />
{/if}

View File

@ -1,3 +1,4 @@
import cuid from 'cuid';
import { writable, readable, type Writable } from 'svelte/store';
interface AppSession {
@ -21,7 +22,7 @@ interface AddToast {
type?: "info" | "success" | "error",
message: string,
timeout?: number | undefined
}
}
export const loginEmail: Writable<string | undefined> = writable()
export const appSession: Writable<AppSession> = writable({
ipv4: null,
@ -76,8 +77,8 @@ export const setLocation = (resource: any) => {
.replace('https://', `https://${resource.exposePort}-`)
.replace(/\/$/, '');
return location.set(newURL)
} else if (CODESANDBOX_HOST){
const newURL = `https://${CODESANDBOX_HOST.replace(/\$PORT/,resource.exposePort)}`
} else if (CODESANDBOX_HOST) {
const newURL = `https://${CODESANDBOX_HOST.replace(/\$PORT/, resource.exposePort)}`
return location.set(newURL)
}
return location.set(resource.fqdn)
@ -85,26 +86,36 @@ export const setLocation = (resource: any) => {
export const toasts: any = writable([])
export const dismissToast = (id: number) => {
export const dismissToast = (id: string) => {
toasts.update((all: any) => all.filter((t: any) => t.id !== id))
}
export const pauseToast = (id: string) => {
toasts.update((all: any) => {
const index = all.findIndex((t: any) => t.id === id);
if (index > -1) clearTimeout(all[index].timeoutInterval);
return all;
})
}
export const resumeToast = (id: string) => {
toasts.update((all: any) => {
const index = all.findIndex((t: any) => t.id === id);
if (index > -1) {
all[index].timeoutInterval = setTimeout(() => {
dismissToast(id)
}, all[index].timeout)
}
return all;
})
}
export const addToast = (toast: AddToast) => {
// Create a unique ID so we can easily find/remove it
// if it is dismissible/has a timeout.
const id = Math.floor(Math.random() * 10000)
// Setup some sensible defaults for a toast.
const id = cuid();
const defaults = {
id,
type: 'info',
timeout: 2000,
}
// Push the toast to the top of the list of toasts
const t = { ...defaults, ...toast }
let t: any = { ...defaults, ...toast }
if (t.timeout) t.timeoutInterval = setTimeout(() => dismissToast(id), t.timeout)
toasts.update((all: any) => [t, ...all])
// If toast is dismissible, dismiss it after "timeout" amount of time.
if (t.timeout) setTimeout(() => dismissToast(id), t.timeout)
}

View File

@ -177,7 +177,7 @@
forceSave = false;
addToast({
message: 'Configuration saved.',
type: 'success',
type: 'success'
});
} catch (error) {
console.log(error);
@ -289,29 +289,21 @@
<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 class="text-center">
<div class="stat w-64">
<div class="stat-title">Used Memory / Memory Limit</div>
<div class="stat-value text-xl">{usage?.MemUsage}</div>
</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 class="stat w-64">
<div class="stat-title">Used CPU</div>
<div class="stat-value text-xl">{usage?.CPUPerc}</div>
</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 class="stat w-64">
<div class="stat-title">Network IO</div>
<div class="stat-value text-xl">{usage?.NetIO}</div>
</div>
</dl>
</div>
</div>
<div class="mx-auto max-w-4xl px-6">
@ -324,11 +316,10 @@
class="btn btn-sm"
type="submit"
class:bg-applications={!loading}
class:loading={loading}
class:loading
class:bg-orange-600={forceSave}
class:hover:bg-orange-400={forceSave}
disabled={loading}
>{$t('forms.save')}</button
disabled={loading}>{$t('forms.save')}</button
>
{/if}
</div>

View File

@ -26,24 +26,8 @@
import { t } from '$lib/translations';
import { getDomain } from '$lib/common';
import { appSession } from '$lib/store';
import ApplicationsIcons from '$lib/components/svg/applications/ApplicationIcons.svelte';
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;
@ -63,10 +47,7 @@
<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="btn btn-square btn-sm bg-applications"
>
<button on:click={newApplication} class="btn btn-square btn-sm bg-applications">
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
@ -96,41 +77,7 @@
<a href="/applications/{application.id}" class="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}
<ApplicationsIcons {application} />
{/if}
<div class="truncate text-center text-xl font-bold">{application.name}</div>
@ -167,41 +114,7 @@
<a href="/applications/{application.id}" class="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}
<ApplicationsIcons {application} />
{/if}
<div class="truncate text-center text-xl font-bold">{application.name}</div>

View File

@ -58,7 +58,9 @@
}
async function changeSettings(name: any) {
if (publicLoading || !$status.database.isRunning) return;
if (name !== 'appendOnly') {
if (publicLoading || !$status.database.isRunning || name !== 'appendOnly') return;
}
publicLoading = true;
let data = {
isPublic,
@ -111,7 +113,7 @@
<button
type="submit"
class="btn btn-sm"
class:loading={loading}
class:loading
class:bg-databases={!loading}
disabled={loading}>{$t('forms.save')}</button
>

View File

@ -31,18 +31,11 @@
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';
import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte';
async function handleSubmit(type: any) {
try {
await post(`/databases/${id}/configuration/type`, { type });
@ -62,21 +55,8 @@
<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}
<DatabaseIcons type={type.name} isAbsolute={true} />
{type.fancyName}
</button>
</form>
</div>

View File

@ -48,7 +48,7 @@
});
</script>
<div class="flex items-center space-x-2 p-6 text-2xl font-bold">
<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Configuration
@ -60,29 +60,21 @@
<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 class="text-center">
<div class="stat w-64">
<div class="stat-title">Used Memory / Memory Limit</div>
<div class="stat-value text-xl">{usage?.MemUsage}</div>
</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 class="stat w-64">
<div class="stat-title">Used CPU</div>
<div class="stat-value text-xl">{usage?.CPUPerc}</div>
</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 class="stat w-64">
<div class="stat-title">Network IO</div>
<div class="stat-value text-xl">{usage?.NetIO}</div>
</div>
</dl>
</div>
</div>
<Databases bind:database {privatePort}/>
<Databases bind:database {privatePort} />

View File

@ -19,17 +19,11 @@
<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';
import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte';
async function newDatabase() {
const { id } = await post('/databases/new', {});
@ -50,10 +44,7 @@
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('index.databases')}</div>
<button
on:click={newDatabase}
class="btn btn-square btn-sm bg-databases"
>
<button on:click={newDatabase} class="btn btn-square btn-sm bg-databases">
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
@ -82,21 +73,7 @@
{#each ownDatabases as database}
<a href="/databases/{database.id}" class="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}
<DatabaseIcons type={database.type} isAbsolute={true} />
<div class="truncate text-center text-xl font-bold">
{database.name}
</div>
@ -121,21 +98,7 @@
{#each otherDatabases as database}
<a href="/databases/{database.id}" class="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}
<DatabaseIcons type={database.type} isAbsolute={true} />
<div class="truncate text-center text-xl font-bold">
{database.name}
</div>

View File

@ -20,91 +20,286 @@
</script>
<script lang="ts">
import { get } from '$lib/api';
import { get, post } from '$lib/api';
import Usage from '$lib/components/Usage.svelte';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import { addToast, appSession } from '$lib/store';
export let applicationsCount: number = 0;
export let sourcesCount: number = 0;
export let destinationsCount: number = 0;
export let teamsCount: number = 0;
export let databasesCount: number = 0;
export let servicesCount: number = 0;
import ApplicationsIcons from '$lib/components/svg/applications/ApplicationIcons.svelte';
import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte';
import ServiceIcons from '$lib/components/svg/services/ServiceIcons.svelte';
let loading = {
cleanup: false
};
export let applications: any;
export let databases: any;
export let services: any;
async function getStatus(resources: any) {
try {
const { id, buildPack, dualCerts } = resources;
let isRunning = false;
if (buildPack) {
const response = await get(`/applications/${id}/status`);
isRunning = response.isRunning;
} else if (typeof dualCerts !== 'undefined') {
const response = await get(`/services/${id}/status`);
isRunning = response.isRunning;
} else {
const response = await get(`/databases/${id}/status`);
isRunning = response.isRunning;
}
if (isRunning) {
return 'Running';
} else {
return 'Stopped';
}
} catch (error) {
return 'Error';
}
}
async function manuallyCleanupStorage() {
try {
loading.cleanup = true;
await post('/internal/cleanup', {});
return addToast({
message: 'Cleanup done.',
type: 'success'
});
} catch (error) {
return errorNotification(error);
} finally {
loading.cleanup = false;
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('index.dashboard')}</div>
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
>Cleanup Storage</button
>
</div>
<div class="mt-10 pb-12 tracking-tight sm:pb-16">
<div class="mx-auto max-w-4xl">
<div class="mx-auto px-10">
<div class="flex flex-col justify-center xl:flex-row">
<div>
<div class="title">Resources</div>
<div class="flex items-start justify-center p-8">
<table class="rounded-none text-base">
<tbody>
{#each applications as application}
<tr>
<td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(application)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status}
{#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
{/if}
{/await}
<div class="inline-flex">{application.name}</div>
</td>
<td class="px-10 inline-flex">
<ApplicationsIcons {application} isAbsolute={false} />
</td>
<td class="px-10">
<div class="badge badge-outline text-xs border-applications rounded text-white">
Application
</div></td
>
<td>
{#if application.fqdn}
<a
href={application.fqdn}
target="_blank"
class="icons bg-transparent text-sm inline-flex"
><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={`/applications/${application.id}`}
class="icons bg-transparent text-sm inline-flex"
>
<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>
</a>
</td>
</tr>
{/each}
{#each services as service}
<tr>
<td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(service)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status}
{#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
{/if}
{/await}
<div class="inline-flex">{service.name}</div>
</td>
<td class="px-10 inline-flex">
<ServiceIcons type={service.type} isAbsolute={false} />
</td>
<td class="px-10"
><div class="badge badge-outline text-xs border-services rounded text-white">
Service
</div>
</td>
<td>
{#if service.fqdn}
<a
href={service.fqdn}
target="_blank"
class="icons bg-transparent text-sm inline-flex"
><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={`/services/${service.id}`}
class="icons bg-transparent text-sm inline-flex"
>
<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>
</a>
</td>
</tr>
{/each}
{#each databases as database}
<tr>
<td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(database)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status}
{#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
{/if}
{/await}
<div class="inline-flex">{database.name}</div>
</td>
<td class="px-10 inline-flex">
<DatabaseIcons type={database.type} />
</td>
<td class="px-10">
<div class="badge badge-outline text-xs border-databases rounded text-white">
Database
</div>
</td>
<td>
<a
href={`/databases/${database.id}`}
class="icons bg-transparent text-sm inline-flex ml-11"
>
<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>
</a>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
{#if $appSession.teamId === '0'}
<Usage />
<dl class="mt-5 grid grid-cols-1 gap-5 px-2 sm:grid-cols-3">
<a
href="/applications"
sveltekit:prefetch
class="overflow-hidden rounded px-4 py-5 text-center text-green-500 no-underline transition-all duration-100 hover:bg-green-500 hover:text-white sm:p-6 sm:text-left"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.applications')}</dt>
<dd class="mt-1 text-3xl font-semibold ">
{applicationsCount}
</dd>
</a>
<a
href="/destinations"
sveltekit:prefetch
class="overflow-hidden rounded px-4 py-5 text-center text-sky-500 no-underline transition-all duration-100 hover:bg-sky-500 hover:text-white sm:p-6 sm:text-left"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.destinations')}</dt>
<dd class="mt-1 text-3xl font-semibold ">
{destinationsCount}
</dd>
</a>
<a
href="/sources"
sveltekit:prefetch
class="overflow-hidden rounded px-4 py-5 text-center text-orange-500 no-underline transition-all duration-100 hover:bg-orange-500 hover:text-white sm:p-6 sm:text-left"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.git_sources')}</dt>
<dd class="mt-1 text-3xl font-semibold">
{sourcesCount}
</dd>
</a>
</dl>
<dl class="mt-5 grid grid-cols-1 gap-5 px-2 sm:grid-cols-3">
<a
href="/databases"
sveltekit:prefetch
class="overflow-hidden rounded px-4 py-5 text-center text-purple-500 no-underline transition-all duration-100 hover:bg-purple-500 hover:text-white sm:p-6 sm:text-left"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.databases')}</dt>
<dd class="mt-1 text-3xl font-semibold ">
{databasesCount}
</dd>
</a>
<a
href="/services"
sveltekit:prefetch
class="overflow-hidden rounded px-4 py-5 text-center text-pink-500 no-underline transition-all duration-100 hover:bg-pink-500 hover:text-white sm:p-6 sm:text-left"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.services')}</dt>
<dd class="mt-1 text-3xl font-semibold ">
{servicesCount}
</dd>
</a>
<a
href="/iam"
sveltekit:prefetch
class="overflow-hidden rounded px-4 py-5 text-center text-cyan-500 no-underline transition-all duration-100 hover:bg-cyan-500 hover:text-white sm:p-6 sm:text-left"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.teams')}</dt>
<dd class="mt-1 text-3xl font-semibold ">
{teamsCount}
</dd>
</a>
</dl>
{/if}
</div>
</div>
</div>

View File

@ -32,7 +32,7 @@
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import Services from '../_Services.svelte';
import ServiceIcons from '$lib/components/svg/services/ServiceIcons.svelte';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@ -56,7 +56,7 @@
<div class="p-2">
<form on:submit|preventDefault={() => handleSubmit(type.name)}>
<button type="submit" class="box-selection relative text-xl font-bold hover:bg-pink-600">
<Services type={type.name} />
<ServiceIcons type={type.name} />
{type.fancyName}
</button>
</form>

View File

@ -61,29 +61,21 @@
</div>
<div class="mx-auto max-w-4xl px-6 py-4">
<div class="text-2xl font-bold">Service 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 class="text-center">
<div class="stat w-64">
<div class="stat-title">Used Memory / Memory Limit</div>
<div class="stat-value text-xl">{usage?.MemUsage}</div>
</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 class="stat w-64">
<div class="stat-title">Used CPU</div>
<div class="stat-value text-xl">{usage?.CPUPerc}</div>
</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 class="stat w-64">
<div class="stat-title">Network IO</div>
<div class="stat-value text-xl">{usage?.NetIO}</div>
</div>
</dl>
</div>
</div>
<Services bind:service bind:readOnly bind:settings />

View File

@ -24,9 +24,8 @@
import { t } from '$lib/translations';
import { appSession } from '$lib/store';
import * as Icons from '$lib/components/svg/services';
import { getDomain } from '$lib/common';
import Services from './[id]/_Services.svelte';
import ServiceIcons from '$lib/components/svg/services/ServiceIcons.svelte';
async function newService() {
const { id } = await post('/services/new', {});
@ -46,10 +45,7 @@
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('index.services')}</div>
<button
on:click={newService}
class="btn btn-square btn-sm bg-services"
>
<button on:click={newService} class="btn btn-square btn-sm bg-services">
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
@ -78,7 +74,7 @@
{#each ownServices as service}
<a href="/services/{service.id}" class=" p-2 no-underline">
<div class="box-selection group relative hover:bg-pink-600">
<Services type={service.type} />
<ServiceIcons type={service.type} />
<div class="truncate text-center text-xl font-bold">
{service.name}
</div>

View File

@ -34,31 +34,6 @@
}
}
async function installRepositories(source: any) {
const { htmlUrl } = source;
let endpoint = 'apps'
if (htmlUrl !== 'https://github.com') {
endpoint = 'github-apps'
}
const left = screen.width / 2 - 1020 / 2;
const top = screen.height / 2 - 1000 / 2;
const newWindow = open(
`${htmlUrl}/${endpoint}/${source.githubApp.name}/installations/new`,
'GitHub',
'resizable=1, scrollbars=1, fullscreen=0, height=1000, width=1020,top=' +
top +
', left=' +
left +
', toolbar=0, menubar=0, status=0'
);
const timer = setInterval(() => {
if (newWindow?.closed) {
clearInterval(timer);
window.location.reload();
}
}, 100);
}
async function newGithubApp() {
loading = true;
try {
@ -183,7 +158,7 @@
>
<a
class="btn btn-sm"
href={`${source.htmlUrl}/apps/${source.githubApp.name}/installations/new`}
href={`${source.htmlUrl}/${source.htmlUrl === 'https://github.com' ? 'apps' : 'github-apps'}/${source.githubApp.name}/installations/new`}
>{$t('source.change_app_settings', { name: 'GitHub' })}</a
>
{/if}
@ -254,8 +229,8 @@
</form>
{:else}
<div class="text-center">
<a href={`${source.htmlUrl}/apps/${source.githubApp.name}/installations/new`}>
<button class="box-selection bg-sources text-xl"
<a href={`${source.htmlUrl}/${source.htmlUrl === 'https://github.com' ? 'apps' : 'github-apps'}/${source.githubApp.name}/installations/new`}>
<button class="box-selection bg-sources text-xl font-bold"
>Install Repositories</button
></a
>

View File

@ -193,3 +193,12 @@ .sub-menu {
.sub-menu-active {
@apply bg-coolgray-500 text-white;
}
.table tbody td,
.table tbody th,
.table thead th{
background-color: transparent;
}
.table *{
border: none;
}

View File

@ -1,7 +1,7 @@
{
"name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "3.2.3",
"version": "3.3.0",
"license": "Apache-2.0",
"repository": "github:coollabsio/coolify",
"scripts": {