add: databases WIP
This commit is contained in:
parent
c05af7e7aa
commit
9ba19c386f
|
@ -1,18 +1,40 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
import { Progress } from '@/components/ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
|
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
|
||||||
import { useWorkspace } from '@/hooks/useWorkspace';
|
import { useWorkspace } from '@/hooks/useWorkspace';
|
||||||
|
import { deployDatabase } from '@/lib/deploy/database';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
|
import Image from 'next/image';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
interface DatabaseNewSteps {
|
interface DatabaseNewSteps {
|
||||||
display: string;
|
display: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IDatabaseProvider {
|
||||||
|
id: string;
|
||||||
|
display: string;
|
||||||
|
image: {
|
||||||
|
alt: string;
|
||||||
|
src: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDatabaseConfig {
|
||||||
|
name: string;
|
||||||
|
provider: IDatabaseProvider;
|
||||||
|
user: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const steps: DatabaseNewSteps[] = [
|
const steps: DatabaseNewSteps[] = [
|
||||||
{
|
{
|
||||||
display: 'Database Type',
|
display: 'Database Type',
|
||||||
|
@ -20,15 +42,49 @@ const steps: DatabaseNewSteps[] = [
|
||||||
{
|
{
|
||||||
display: 'Configuration',
|
display: 'Configuration',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
display: 'Name & User',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
display: 'Review & Deploy',
|
display: 'Review & Deploy',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const databaseProviders: IDatabaseProvider[] = [
|
||||||
|
{
|
||||||
|
id: 'vitess',
|
||||||
|
display: 'Vitess',
|
||||||
|
image: {
|
||||||
|
alt: 'Vitess',
|
||||||
|
src: '/vitess.png',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'redis',
|
||||||
|
display: 'Redis',
|
||||||
|
image: {
|
||||||
|
alt: 'Redis',
|
||||||
|
src: '/redis.svg',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function DatabaseNewForm() {
|
export default function DatabaseNewForm() {
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
const [currentSteps, setCurrentSteps] = useState<number>(1);
|
const [currentSteps, setCurrentSteps] = useState<number>(1);
|
||||||
|
|
||||||
|
// TODO: Generate all data, but leave the user the choice to modify
|
||||||
|
const defaultDatabaseConfig: IDatabaseConfig = {
|
||||||
|
name: 'my-new-cool-db',
|
||||||
|
provider: databaseProviders[0],
|
||||||
|
user: {
|
||||||
|
username: 'my-new-super-user',
|
||||||
|
password: 'a-super-strong-generated-password',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const [databaseConfig, setDatabaseConfig] = useState<IDatabaseConfig>(defaultDatabaseConfig);
|
||||||
|
|
||||||
const { id } = useWorkspace();
|
const { id } = useWorkspace();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -59,15 +115,80 @@ export default function DatabaseNewForm() {
|
||||||
|
|
||||||
{currentSteps == 1 && (
|
{currentSteps == 1 && (
|
||||||
<div>
|
<div>
|
||||||
{/* <RadioGroup defaultValue={'vitess'} className="flex flex-col gap-2 mb-4" onValueChange={}>
|
<RadioGroup
|
||||||
<div className={cn('flex flex-row items-center gap-4 border-[1px] rounded-lg py-2 px-4', provider.value == newApplication.serviceProvider ? 'border-[#3A7BFE]' : 'border-gray-300')}>
|
defaultValue={'vitess'}
|
||||||
<RadioGroupItem value={'vitess'} id={'vitess'} />
|
className="flex flex-col gap-2 mb-4"
|
||||||
<label htmlFor={'vitess'} className="flex flex-row items-center gap-4">
|
onValueChange={(id) => {
|
||||||
<img src={provider.image} alt={'Vitess'} className="w-8 h-8 rounded-full" />
|
setDatabaseConfig((prev) => ({ ...prev, provider: databaseProviders.filter((provider) => provider.id == id)[0] }));
|
||||||
Vitess
|
}}
|
||||||
|
>
|
||||||
|
{databaseProviders.map((provider) => (
|
||||||
|
<div className={cn('flex flex-row items-center gap-4 border-[1px] rounded-lg py-2 px-4', databaseConfig?.provider.id == provider.id ? 'border-[#3A7BFE]' : 'border-gray-300')}>
|
||||||
|
<RadioGroupItem value={provider.id} id={provider.id} />
|
||||||
|
<label htmlFor={provider.id} className="flex flex-row items-center gap-4">
|
||||||
|
<div className="w-8 h-8 rounded-full relative">
|
||||||
|
<Image src={provider.image.src} alt={provider.image.alt} fill />
|
||||||
|
</div>
|
||||||
|
{provider.display}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</RadioGroup> */}
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentSteps == 2 && (
|
||||||
|
<div>
|
||||||
|
<p>WIP for now 1CPU 2048Mb RAM</p>
|
||||||
|
<p>Storage 1 GB</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentSteps == 3 && (
|
||||||
|
<div>
|
||||||
|
<Label>Database Name</Label>
|
||||||
|
<Input
|
||||||
|
className="mb-2"
|
||||||
|
type="text"
|
||||||
|
placeholder="super-cool-database"
|
||||||
|
value={databaseConfig.name}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDatabaseConfig((prev) => ({ ...prev, name: e.target.value }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Label>Username</Label>
|
||||||
|
<Input
|
||||||
|
className="mb-2"
|
||||||
|
type="text"
|
||||||
|
placeholder="root"
|
||||||
|
value={databaseConfig.user.username}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDatabaseConfig((prev) => ({ ...prev, user: { ...prev.user, username: e.target.value } }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Label>Password</Label>
|
||||||
|
<Input
|
||||||
|
className="mb-2"
|
||||||
|
type="password"
|
||||||
|
placeholder="Strong Password"
|
||||||
|
value={databaseConfig.user.password}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDatabaseConfig((prev) => ({ ...prev, user: { ...prev.user, password: e.target.value } }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentSteps == 4 && (
|
||||||
|
<div>
|
||||||
|
<h2>Review your new database information :</h2>
|
||||||
|
<p>Type : {databaseConfig.provider.display}</p>
|
||||||
|
<p>Name : {databaseConfig.name}</p>
|
||||||
|
<p>Username : {databaseConfig.user.username}</p>
|
||||||
|
<p>Password : {databaseConfig.user.password}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -82,7 +203,14 @@ export default function DatabaseNewForm() {
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
{currentSteps == steps.length ? (
|
{currentSteps == steps.length ? (
|
||||||
<Button className="bg-[#3A7BFE]" onClick={() => {}}>
|
<Button
|
||||||
|
className="bg-[#3A7BFE]"
|
||||||
|
onClick={() => {
|
||||||
|
deployDatabase(databaseConfig).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
Deploy
|
Deploy
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const labelVariants = cva(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Label = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
|
VariantProps<typeof labelVariants>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(labelVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Label }
|
|
@ -0,0 +1,16 @@
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
import { IDatabaseConfig } from "@/app/(deploy)/[workspace]/databases/database-new-form";
|
||||||
|
|
||||||
|
export async function deployDatabase(config: IDatabaseConfig) {
|
||||||
|
|
||||||
|
return fetch("http://127.0.0.1:8080/databases", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(config),
|
||||||
|
}).then((res) => res.json()).catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,73 +0,0 @@
|
||||||
import prisma from "@/lib/prisma";
|
|
||||||
import { Database, DatabaseProvider } from "@prisma/client";
|
|
||||||
|
|
||||||
type DatabaseType = DatabaseProvider;
|
|
||||||
|
|
||||||
export class CreateDatabase {
|
|
||||||
|
|
||||||
private workspaceId: string;
|
|
||||||
private id: string;
|
|
||||||
public name: string;
|
|
||||||
public type: DatabaseType;
|
|
||||||
|
|
||||||
public username: string;
|
|
||||||
private password: string;
|
|
||||||
private host: string | undefined;
|
|
||||||
private port: number | undefined;
|
|
||||||
|
|
||||||
constructor(workspaceId: string, name: string, type: DatabaseType) {
|
|
||||||
this.id = "new-database-uuid"; // TODO: Generate UUID
|
|
||||||
this.username = "root"; // TODO: Generate random username
|
|
||||||
this.password = "toor"; // TODO: Generate random password
|
|
||||||
|
|
||||||
this.workspaceId = workspaceId;
|
|
||||||
this.name = name;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deploying the database to the cluster
|
|
||||||
public async deploy(): Promise<Database> {
|
|
||||||
throw new Error("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Saving the newly created database to the database
|
|
||||||
protected async save(): Promise<Database> {
|
|
||||||
// Checking if the connection details are provided
|
|
||||||
if(!this.host || !this.port) throw new MissingConnectionDetails();
|
|
||||||
|
|
||||||
const database = await prisma.database.create({
|
|
||||||
data: {
|
|
||||||
id: this.id,
|
|
||||||
provider: this.type,
|
|
||||||
name: this.name,
|
|
||||||
workspaceId: this.workspaceId,
|
|
||||||
username: this.username,
|
|
||||||
password: this.password,
|
|
||||||
|
|
||||||
host: this.host,
|
|
||||||
port: this.port
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return database;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class DatabaseError extends Error {
|
|
||||||
constructor(message: string) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SavingDatabaseError extends DatabaseError {
|
|
||||||
constructor() {
|
|
||||||
super("Error saving the database");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MissingConnectionDetails extends DatabaseError {
|
|
||||||
constructor() {
|
|
||||||
super("Missing connection details");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { Database } from "@prisma/client";
|
|
||||||
import { CreateDatabase, SavingDatabaseError } from "./database";
|
|
||||||
|
|
||||||
export class CreateVitessDatabase extends CreateDatabase {
|
|
||||||
constructor(workspaceId: string, name: string) {
|
|
||||||
super(workspaceId, name, "VITESS");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deploy(): Promise<Database> {
|
|
||||||
try {
|
|
||||||
// Saving the database to the database
|
|
||||||
const database = await this.save();
|
|
||||||
return database;
|
|
||||||
} catch (error) {
|
|
||||||
throw new SavingDatabaseError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,6 +14,7 @@
|
||||||
"@radix-ui/react-avatar": "^1.0.4",
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-progress": "^1.0.3",
|
"@radix-ui/react-progress": "^1.0.3",
|
||||||
"@radix-ui/react-radio-group": "^1.1.3",
|
"@radix-ui/react-radio-group": "^1.1.3",
|
||||||
|
@ -915,6 +916,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-label": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popover": {
|
"node_modules/@radix-ui/react-popover": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz",
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"@radix-ui/react-avatar": "^1.0.4",
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-progress": "^1.0.3",
|
"@radix-ui/react-progress": "^1.0.3",
|
||||||
"@radix-ui/react-radio-group": "^1.1.3",
|
"@radix-ui/react-radio-group": "^1.1.3",
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 -18 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><path d="M245.97 168.943c-13.662 7.121-84.434 36.22-99.501 44.075-15.067 7.856-23.437 7.78-35.34 2.09-11.902-5.69-87.216-36.112-100.783-42.597C3.566 169.271 0 166.535 0 163.951v-25.876s98.05-21.345 113.879-27.024c15.828-5.679 21.32-5.884 34.79-.95 13.472 4.936 94.018 19.468 107.331 24.344l-.006 25.51c.002 2.558-3.07 5.364-10.024 8.988" fill="#912626"/><path d="M245.965 143.22c-13.661 7.118-84.431 36.218-99.498 44.072-15.066 7.857-23.436 7.78-35.338 2.09-11.903-5.686-87.214-36.113-100.78-42.594-13.566-6.485-13.85-10.948-.524-16.166 13.326-5.22 88.224-34.605 104.055-40.284 15.828-5.677 21.319-5.884 34.789-.948 13.471 4.934 83.819 32.935 97.13 37.81 13.316 4.881 13.827 8.9.166 16.02" fill="#C6302B"/><path d="M245.97 127.074c-13.662 7.122-84.434 36.22-99.501 44.078-15.067 7.853-23.437 7.777-35.34 2.087-11.903-5.687-87.216-36.112-100.783-42.597C3.566 127.402 0 124.67 0 122.085V96.206s98.05-21.344 113.879-27.023c15.828-5.679 21.32-5.885 34.79-.95C162.142 73.168 242.688 87.697 256 92.574l-.006 25.513c.002 2.557-3.07 5.363-10.024 8.987" fill="#912626"/><path d="M245.965 101.351c-13.661 7.12-84.431 36.218-99.498 44.075-15.066 7.854-23.436 7.777-35.338 2.087-11.903-5.686-87.214-36.112-100.78-42.594-13.566-6.483-13.85-10.947-.524-16.167C23.151 83.535 98.05 54.148 113.88 48.47c15.828-5.678 21.319-5.884 34.789-.949 13.471 4.934 83.819 32.933 97.13 37.81 13.316 4.88 13.827 8.9.166 16.02" fill="#C6302B"/><path d="M245.97 83.653c-13.662 7.12-84.434 36.22-99.501 44.078-15.067 7.854-23.437 7.777-35.34 2.087-11.903-5.687-87.216-36.113-100.783-42.595C3.566 83.98 0 81.247 0 78.665v-25.88s98.05-21.343 113.879-27.021c15.828-5.68 21.32-5.884 34.79-.95C162.142 29.749 242.688 44.278 256 49.155l-.006 25.512c.002 2.555-3.07 5.361-10.024 8.986" fill="#912626"/><path d="M245.965 57.93c-13.661 7.12-84.431 36.22-99.498 44.074-15.066 7.854-23.436 7.777-35.338 2.09C99.227 98.404 23.915 67.98 10.35 61.497-3.217 55.015-3.5 50.55 9.825 45.331 23.151 40.113 98.05 10.73 113.88 5.05c15.828-5.679 21.319-5.883 34.789-.948 13.471 4.935 83.819 32.934 97.13 37.811 13.316 4.876 13.827 8.897.166 16.017" fill="#C6302B"/><path d="M159.283 32.757l-22.01 2.285-4.927 11.856-7.958-13.23-25.415-2.284 18.964-6.839-5.69-10.498 17.755 6.944 16.738-5.48-4.524 10.855 17.067 6.391M131.032 90.275L89.955 73.238l58.86-9.035-17.783 26.072M74.082 39.347c17.375 0 31.46 5.46 31.46 12.194 0 6.736-14.085 12.195-31.46 12.195s-31.46-5.46-31.46-12.195c0-6.734 14.085-12.194 31.46-12.194" fill="#FFF"/><path d="M185.295 35.998l34.836 13.766-34.806 13.753-.03-27.52" fill="#621B1C"/><path d="M146.755 51.243l38.54-15.245.03 27.519-3.779 1.478-34.791-13.752" fill="#9A2928"/></svg>
|
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
Loading…
Reference in New Issue