78
src/routes/databases/[id]/_Databases/_CouchDb.svelte
Normal file
78
src/routes/databases/[id]/_Databases/_CouchDb.svelte
Normal 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>
|
||||
209
src/routes/databases/[id]/_Databases/_Databases.svelte
Normal file
209
src/routes/databases/[id]/_Databases/_Databases.svelte
Normal 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>
|
||||
37
src/routes/databases/[id]/_Databases/_MongoDB.svelte
Normal file
37
src/routes/databases/[id]/_Databases/_MongoDB.svelte
Normal 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>
|
||||
78
src/routes/databases/[id]/_Databases/_MySQL.svelte
Normal file
78
src/routes/databases/[id]/_Databases/_MySQL.svelte
Normal 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>
|
||||
51
src/routes/databases/[id]/_Databases/_PostgreSQL.svelte
Normal file
51
src/routes/databases/[id]/_Databases/_PostgreSQL.svelte
Normal 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>
|
||||
64
src/routes/databases/[id]/_Databases/_Redis.svelte
Normal file
64
src/routes/databases/[id]/_Databases/_Redis.svelte
Normal 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>
|
||||
182
src/routes/databases/[id]/__layout.svelte
Normal file
182
src/routes/databases/[id]/__layout.svelte
Normal 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 />
|
||||
19
src/routes/databases/[id]/configuration/destination.json.ts
Normal file
19
src/routes/databases/[id]/configuration/destination.json.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
93
src/routes/databases/[id]/configuration/destination.svelte
Normal file
93
src/routes/databases/[id]/configuration/destination.svelte
Normal 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>
|
||||
32
src/routes/databases/[id]/configuration/type.json.ts
Normal file
32
src/routes/databases/[id]/configuration/type.json.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
81
src/routes/databases/[id]/configuration/type.svelte
Normal file
81
src/routes/databases/[id]/configuration/type.svelte
Normal 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>
|
||||
36
src/routes/databases/[id]/configuration/version.json.ts
Normal file
36
src/routes/databases/[id]/configuration/version.json.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
63
src/routes/databases/[id]/configuration/version.svelte
Normal file
63
src/routes/databases/[id]/configuration/version.svelte
Normal 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>
|
||||
22
src/routes/databases/[id]/delete.json.ts
Normal file
22
src/routes/databases/[id]/delete.json.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
71
src/routes/databases/[id]/index.json.ts
Normal file
71
src/routes/databases/[id]/index.json.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
67
src/routes/databases/[id]/index.svelte
Normal file
67
src/routes/databases/[id]/index.svelte
Normal 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} />
|
||||
34
src/routes/databases/[id]/settings.json.ts
Normal file
34
src/routes/databases/[id]/settings.json.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
82
src/routes/databases/[id]/start.json.ts
Normal file
82
src/routes/databases/[id]/start.json.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
25
src/routes/databases/[id]/stop.json.ts
Normal file
25
src/routes/databases/[id]/stop.json.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
20
src/routes/databases/index.json.ts
Normal file
20
src/routes/databases/index.json.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
87
src/routes/databases/index.svelte
Normal file
87
src/routes/databases/index.svelte
Normal 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>
|
||||
Reference in New Issue
Block a user