Just v2
This commit is contained in:
Andras Bacsai
2022-02-10 15:47:44 +01:00
committed by GitHub
parent a64b095c13
commit 460ae85226
403 changed files with 22039 additions and 12465 deletions

View File

@@ -0,0 +1,78 @@
<script>
export let database;
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">CouchDB</div>
</div>
<div class="px-10">
<div class="grid grid-cols-3 items-center">
<label for="defaultDatabase">Default Database</label>
<div class="col-span-2 ">
<CopyPasswordField
required
readonly={database.defaultDatabase}
disabled={database.defaultDatabase}
placeholder="eg: mydb"
id="defaultDatabase"
name="defaultDatabase"
bind:value={database.defaultDatabase}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="dbUser">User</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
id="dbUser"
name="dbUser"
value={database.dbUser}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="dbUserPassword">Password</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
isPasswordField
id="dbUserPassword"
name="dbUserPassword"
value={database.dbUserPassword}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="rootUser">Root User</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
id="rootUser"
name="rootUser"
value={database.rootUser}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="rootUserPassword">Root's Password</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
isPasswordField
id="rootUserPassword"
name="rootUserPassword"
value={database.rootUserPassword}
/>
</div>
</div>
</div>

View File

@@ -0,0 +1,209 @@
<script lang="ts">
export let database;
export let privatePort;
export let settings;
import { page, session } from '$app/stores';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Setting from '$lib/components/Setting.svelte';
import { errorNotification } from '$lib/form';
import MySql from './_MySQL.svelte';
import MongoDb from './_MongoDB.svelte';
import PostgreSql from './_PostgreSQL.svelte';
import Redis from './_Redis.svelte';
import CouchDb from './_CouchDb.svelte';
import { browser } from '$app/env';
import { post } from '$lib/api';
import { getDomain } from '$lib/components/common';
const { id } = $page.params;
let loading = false;
let isPublic = database.settings.isPublic || false;
let appendOnly = database.settings.appendOnly;
let databaseDefault = database.defaultDatabase;
let databaseDbUser = database.dbUser;
let 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 = '';
}
let databaseUrl = generateUrl();
function generateUrl() {
return browser
? `${database.type}://${
databaseDbUser ? databaseDbUser + ':' : ''
}${databaseDbUserPassword}@${
isPublic
? settings.fqdn
? getDomain(settings.fqdn)
: window.location.hostname
: database.id
}:${isPublic ? database.publicPort : privatePort}/${databaseDefault}`
: 'Loading...';
}
async function changeSettings(name) {
if (name === 'isPublic') {
isPublic = !isPublic;
}
if (name === 'appendOnly') {
appendOnly = !appendOnly;
}
try {
await post(`/databases/${id}/settings.json`, { isPublic, appendOnly });
databaseUrl = generateUrl();
return;
} catch ({ error }) {
return errorNotification(error);
}
}
async function handleSubmit() {
try {
await post(`/databases/${id}.json`, { ...database });
return window.location.reload();
} catch ({ error }) {
return errorNotification(error);
}
}
</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">General</div>
{#if $session.isAdmin}
<button
type="submit"
class:bg-purple-600={!loading}
class:hover:bg-purple-500={!loading}
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
>
{/if}
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-3 items-center">
<label for="name">Name</label>
<div class="col-span-2 ">
<input
readonly={!$session.isAdmin}
name="name"
id="name"
bind:value={database.name}
required
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="destination">Destination</label>
<div class="col-span-2">
{#if database.destinationDockerId}
<div class="no-underline">
<input
value={database.destinationDocker.name}
id="destination"
disabled
readonly
class="bg-transparent "
/>
</div>
{/if}
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="version">Version</label>
<div class="col-span-2 ">
<input value={database.version} readonly disabled class="bg-transparent " />
</div>
</div>
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-3 items-center">
<label for="host">Host</label>
<div class="col-span-2 ">
<CopyPasswordField
placeholder="Generated automatically after start"
isPasswordField={false}
readonly
disabled
id="host"
name="host"
value={database.id}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="publicPort">Port</label>
<div class="col-span-2">
<CopyPasswordField
placeholder="Generated automatically after start"
id="publicPort"
readonly
disabled
name="publicPort"
value={isPublic ? database.publicPort : privatePort}
/>
</div>
</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 {database} />
{:else if database.type === 'redis'}
<Redis {database} />
{:else if database.type === 'couchdb'}
<CouchDb bind:database />
{/if}
<div class="grid grid-cols-3 items-center px-10 pb-8">
<label for="url">Connection String</label>
<div class="col-span-2 ">
<CopyPasswordField
textarea={true}
placeholder="Generated automatically after start"
isPasswordField={false}
id="url"
name="url"
readonly
disabled
value={databaseUrl}
/>
</div>
</div>
</div>
</form>
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">Features</div>
</div>
<div class="px-4 pb-10 sm:px-6">
<ul class="mt-2 divide-y divide-stone-800">
<Setting
bind:setting={isPublic}
on:click={() => changeSettings('isPublic')}
title="Set it public"
description="Your database will be reachable over the internet. <br>Take security seriously in this case!"
/>
</ul>
{#if database.type === 'redis'}
<ul class="mt-2 divide-y divide-stone-800">
<Setting
bind:setting={appendOnly}
on:click={() => changeSettings('appendOnly')}
title="Change append only mode"
description="Useful if you would like to restore redis data from a backup.<br><span class='font-bold text-white'>Database restart is required.</span>"
/>
</ul>
{/if}
</div>
</div>

View File

@@ -0,0 +1,37 @@
<script>
export let database;
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">MongoDB</div>
</div>
<div class="px-10">
<div class="grid grid-cols-3 items-center">
<label for="rootUser">Root User</label>
<div class="col-span-2 ">
<CopyPasswordField
placeholder="Generated automatically after start"
id="rootUser"
readonly
disabled
name="rootUser"
value={database.rootUser}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="rootUserPassword">Root's Password</label>
<div class="col-span-2 ">
<CopyPasswordField
placeholder="Generated automatically after start"
isPasswordField={true}
readonly
disabled
id="rootUserPassword"
name="rootUserPassword"
value={database.rootUserPassword}
/>
</div>
</div>
</div>

View File

@@ -0,0 +1,78 @@
<script>
export let database;
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">MySQL</div>
</div>
<div class=" px-10">
<div class="grid grid-cols-3 items-center">
<label for="defaultDatabase">Default Database</label>
<div class="col-span-2 ">
<CopyPasswordField
required
readonly={database.defaultDatabase}
disabled={database.defaultDatabase}
placeholder="eg: mydb"
id="defaultDatabase"
name="defaultDatabase"
bind:value={database.defaultDatabase}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="dbUser">User</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
id="dbUser"
name="dbUser"
value={database.dbUser}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="dbUserPassword">Password</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
isPasswordField
id="dbUserPassword"
name="dbUserPassword"
value={database.dbUserPassword}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="rootUser">Root User</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
id="rootUser"
name="rootUser"
value={database.rootUser}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="rootUserPassword">Root's Password</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
isPasswordField
id="rootUserPassword"
name="rootUserPassword"
value={database.rootUserPassword}
/>
</div>
</div>
</div>

View File

@@ -0,0 +1,51 @@
<script>
export let database;
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">PostgreSQL</div>
</div>
<div class="px-10">
<div class="grid grid-cols-3 items-center">
<label for="defaultDatabase">Default Database</label>
<div class="col-span-2 ">
<CopyPasswordField
required
readonly={database.defaultDatabase}
disabled={database.defaultDatabase}
placeholder="eg: mydb"
id="defaultDatabase"
name="defaultDatabase"
bind:value={database.defaultDatabase}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="dbUser">User</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
id="dbUser"
name="dbUser"
value={database.dbUser}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="dbUserPassword">Password</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
isPasswordField
id="dbUserPassword"
name="dbUserPassword"
value={database.dbUserPassword}
/>
</div>
</div>
</div>

View File

@@ -0,0 +1,64 @@
<script>
export let database;
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">Redis</div>
</div>
<div class="px-10">
<!-- <div class="grid grid-cols-3 items-center">
<label for="dbUser">User</label>
<div class="col-span-2 ">
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
id="dbUser"
name="dbUser"
bind:value={database.dbUser}
/>
</div>
</div> -->
<div class="grid grid-cols-3 items-center">
<label for="dbUserPassword">Password</label>
<div class="col-span-2 ">
<CopyPasswordField
disabled
readonly
placeholder="Generated automatically after start"
isPasswordField
id="dbUserPassword"
name="dbUserPassword"
value={database.dbUserPassword}
/>
</div>
</div>
<!-- <div class="grid grid-cols-3 items-center">
<label for="rootUser">Root User</label>
<div class="col-span-2 ">
<CopyPasswordField
disabled
readonly
placeholder="Generated automatically after start"
id="rootUser"
name="rootUser"
value={database.rootUser}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="rootUserPassword">Root's Password</label>
<div class="col-span-2 ">
<CopyPasswordField
disabled
readonly
placeholder="Generated automatically after start"
isPasswordField
id="rootUserPassword"
name="rootUserPassword"
value={database.rootUserPassword}
/>
</div>
</div> -->
</div>

View File

@@ -0,0 +1,182 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
function checkConfiguration(database): string {
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, params, url }) => {
const endpoint = `/databases/${params.id}.json`;
const res = await fetch(endpoint);
if (res.ok) {
const { database, state, versions, privatePort, settings } = await res.json();
if (!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,
state,
versions,
privatePort
},
stuff: {
database,
state,
versions,
privatePort,
settings
}
};
}
return {
status: 302,
redirect: '/databases'
};
};
</script>
<script>
import { session } from '$app/stores';
import { errorNotification } from '$lib/form';
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
import Loading from '$lib/components/Loading.svelte';
import { del, post } from '$lib/api';
import { goto } from '$app/navigation';
export let database;
export let state;
let loading = 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}/delete.json`, { id: database.id });
return await goto('/databases');
} catch ({ error }) {
return errorNotification(error);
} finally {
loading = false;
}
}
}
async function stopDatabase() {
const sure = confirm(`Are you sure you would like to stop '${database.name}'?`);
if (sure) {
loading = true;
try {
await post(`/databases/${database.id}/stop.json`, {});
return window.location.reload();
} catch ({ error }) {
return errorNotification(error);
} finally {
loading = false;
}
}
}
async function startDatabase() {
loading = true;
try {
await post(`/databases/${database.id}/start.json`, {});
return window.location.reload();
} catch ({ error }) {
return errorNotification(error);
} finally {
loading = false;
}
}
</script>
<nav class="nav-side">
{#if loading}
<Loading fullscreen cover />
{:else}
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
{#if state === 'running'}
<button
on:click={stopDatabase}
title="Stop database"
type="submit"
disabled={!$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:bg-purple-600 hover:text-white"
data-tooltip={$session.isAdmin
? 'Stop database'
: 'You do not have permission to stop the 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 if state === 'not started'}
<button
on:click={startDatabase}
title="Start database"
type="submit"
disabled={!$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:bg-purple-600 hover:text-white"
data-tooltip={$session.isAdmin
? 'Start database'
: 'You do not have permission to start the 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}
<button
on:click={deleteDatabase}
title="Delete Database"
type="submit"
disabled={!$session.isAdmin}
class:hover:text-red-500={$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip={$session.isAdmin
? 'Delete Database'
: 'You do not have permission to delete a Database'}><DeleteIcon /></button
>
{/if}
</nav>
<slot />

View File

@@ -0,0 +1,19 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { destinationId } = await event.request.json();
try {
await db.configureDestinationForDatabase({ id, destinationId });
return { status: 201 };
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,93 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
const { database } = stuff;
if (database?.destinationDockerId && !url.searchParams.get('from')) {
return {
status: 302,
redirect: `/database/${params.id}`
};
}
const endpoint = `/destinations.json`;
const res = await fetch(endpoint);
if (res.ok) {
return {
props: {
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
};
};
</script>
<script lang="ts">
import type Prisma from '@prisma/client';
import { page } from '$app/stores';
import { errorNotification } from '$lib/form';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
export let destinations: Prisma.DestinationDocker[];
async function handleSubmit(destinationId) {
try {
await post(`/databases/${id}/configuration/destination.json`, {
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">Configure Destination</div>
</div>
<div class="flex justify-center">
{#if !destinations || destinations.length === 0}
<div class="flex-col">
<div class="pb-2">No configurable Destination found</div>
<div class="flex justify-center">
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/></svg
>
</a>
</div>
</div>
{:else}
<div class="flex flex-wrap justify-center">
{#each destinations as destination}
<div class="p-2">
<form on:submit|preventDefault={() => handleSubmit(destination.id)}>
<button type="submit" class="box-selection hover:bg-sky-700 font-bold">
<div class="font-bold text-xl text-center truncate">{destination.name}</div>
<div class="text-center truncate">{destination.network}</div>
</button>
</form>
</div>
{/each}
</div>
{/if}
</div>

View File

@@ -0,0 +1,32 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
return {
status: 200,
body: {
types: supportedDatabaseTypesAndVersions
}
};
};
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { type } = await event.request.json();
try {
await db.configureDatabaseType({ id, type });
return {
status: 201
};
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,81 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
const { database } = stuff;
if (database?.type && !url.searchParams.get('from')) {
return {
status: 302,
redirect: `/databases/${params.id}`
};
}
const endpoint = `/databases/${params.id}/configuration/type.json`;
const res = await fetch(endpoint);
if (res.ok) {
return {
props: {
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
};
};
</script>
<script lang="ts">
import { page } from '$app/stores';
import { errorNotification } from '$lib/form';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
export let types;
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 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 { post } from '$lib/api';
async function handleSubmit(type) {
try {
await post(`/databases/${id}/configuration/type.json`, { 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">Select a 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 === '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,36 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { type } = await db.getDatabase({ id, teamId });
return {
status: 200,
body: {
versions: supportedDatabaseTypesAndVersions.find((name) => name.name === type).versions
}
};
};
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { version } = await event.request.json();
try {
await db.setDatabase({ id, version });
return {
status: 201
};
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,63 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
const { database } = stuff;
if (database?.version && !url.searchParams.get('from')) {
return {
status: 302,
redirect: `/databases/${params.id}`
};
}
const endpoint = `/databases/${params.id}/configuration/version.json`;
const res = await fetch(endpoint);
if (res.ok) {
return {
props: {
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
};
};
</script>
<script lang="ts">
import { page } from '$app/stores';
import { enhance, errorNotification } from '$lib/form';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
export let versions;
async function handleSubmit(version) {
try {
await post(`/databases/${id}/configuration/version.json`, { 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">Select a Database version</div>
</div>
<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,22 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler, stopDatabase } from '$lib/database';
import { deleteProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const del: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const database = await db.getDatabase({ id, teamId });
if (database.destinationDockerId) {
const everStarted = await stopDatabase(database);
if (everStarted) await deleteProxy({ id });
}
await db.removeDatabase({ id });
return { status: 200 };
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,71 @@
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { generateDatabaseConfiguration, getVersions, PrismaErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const database = await db.getDatabase({ id, teamId });
const { destinationDockerId, destinationDocker } = database;
let state = 'not started';
if (destinationDockerId) {
const host = getEngine(destinationDocker.engine);
try {
const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
);
if (JSON.parse(stdout).Running) {
state = 'running';
}
} catch (error) {
// if (!error.stderr.includes('No such object')) {
// console.log(error)
// }
}
}
const configuration = generateDatabaseConfiguration(database);
const settings = await db.listSettings();
return {
body: {
privatePort: configuration?.privatePort,
database,
state,
versions: getVersions(database.type),
settings
}
};
} catch (error) {
return PrismaErrorHandler(error);
}
};
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { name, defaultDatabase, dbUser, dbUserPassword, rootUser, rootUserPassword, version } =
await event.request.json();
try {
await db.updateDatabase({
id,
name,
defaultDatabase,
dbUser,
dbUserPassword,
rootUser,
rootUserPassword,
version
});
return { status: 201 };
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,67 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
import Databases from './_Databases/_Databases.svelte';
export const load: Load = async ({ fetch, params, stuff }) => {
if (stuff?.database?.id) {
return {
props: {
database: stuff.database,
versions: stuff.versions,
privatePort: stuff.privatePort,
settings: stuff.settings
}
};
}
const endpoint = `/databases/${params.id}.json`;
const res = await fetch(endpoint);
if (res.ok) {
return {
props: {
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${endpoint}`)
};
};
</script>
<script lang="ts">
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 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';
export let database;
export let settings;
export let privatePort;
</script>
<div class="flex items-center space-x-2 p-6 text-2xl font-bold">
<div class="md:max-w-64 truncate text-base tracking-tight md:block md:text-2xl">
{database.name}
</div>
<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 === 'postgresql'}
<PostgreSql />
{:else if database.type === 'redis'}
<Redis />
{/if}
</span>
</div>
<Databases bind:database {privatePort} {settings} />

View File

@@ -0,0 +1,34 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { generateDatabaseConfiguration, PrismaErrorHandler } from '$lib/database';
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { status, body, teamId } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { isPublic, appendOnly = true } = await event.request.json();
try {
await db.setDatabase({ id, isPublic, appendOnly });
const database = await db.getDatabase({ id, teamId });
const { destinationDockerId, destinationDocker, publicPort } = database;
const { privatePort } = generateDatabaseConfiguration(database);
if (destinationDockerId) {
if (isPublic) {
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
} else {
await stopTcpHttpProxy(destinationDocker, publicPort);
}
}
return {
status: 201
};
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,82 @@
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { generateDatabaseConfiguration, PrismaErrorHandler } from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { makeLabelForStandaloneDatabase } from '$lib/buildPacks/common';
import { startTcpProxy } from '$lib/haproxy';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const database = await db.getDatabase({ id, teamId });
const {
type,
destinationDockerId,
destinationDocker,
publicPort,
settings: { isPublic }
} = database;
const { privatePort, environmentVariables, image, volume, ulimits } =
generateDatabaseConfiguration(database);
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const engine = destinationDocker.engine;
const volumeName = volume.split(':')[0];
const labels = await makeLabelForStandaloneDatabase({ id, image, volume });
const { workdir } = await createDirectories({ repository: type, buildId: id });
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image,
networks: [network],
environment: environmentVariables,
volumes: [volume],
ulimits,
labels,
restart: 'always'
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[volumeName]: {
external: true
}
}
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {
await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${volumeName}`);
} catch (error) {
console.log(error);
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
if (isPublic) await startTcpProxy(destinationDocker, id, publicPort, privatePort);
return {
status: 200
};
} catch (error) {
throw {
error
};
}
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,25 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler, stopDatabase } from '$lib/database';
import { stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const database = await db.getDatabase({ id, teamId });
const everStarted = await stopDatabase(database);
if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort);
await db.setDatabase({ id, isPublic: false });
return {
status: 200
};
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,20 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
try {
const databases = await db.listDatabases(teamId);
return {
status: 200,
body: {
databases
}
};
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,87 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch }) => {
const url = `/databases.json`;
const res = await fetch(url);
if (res.ok) {
return {
props: {
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
};
};
</script>
<script lang="ts">
export let databases;
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 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>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Databases</div>
<a href="/new/database" class="add-icon 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
>
</a>
</div>
<div class="flex flex-wrap justify-center">
{#if !databases || databases.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">No databases found</div>
</div>
{:else}
{#each databases as database}
<a href="/databases/{database.id}" class="no-underline p-2 w-96">
<div class="box-selection relative hover:bg-purple-600 group">
{#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 === 'postgresql'}
<PostgreSQL isAbsolute />
{:else if database.type === 'redis'}
<Redis isAbsolute />
{/if}
<div class="font-bold text-xl text-center truncate">
{database.name}
</div>
{#if !database.type}
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
Configuration missing
</div>
{:else}
<div class="text-center truncate">{database.type}</div>
{/if}
</div>
</a>
{/each}
{/if}
</div>