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
	 Andras Bacsai
					Andras Bacsai