add: specs & database status
This commit is contained in:
parent
8f47633e86
commit
27e9e2d941
|
@ -3,12 +3,12 @@
|
|||
import { Database } from '@prisma/client';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import Image from 'next/image';
|
||||
import { databaseProviders } from './database-new-form';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { EllipsisVertical } from 'lucide-react';
|
||||
import { databaseProviders } from '@/lib/deploy/database-config';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface IDatabase extends Pick<Database, 'id' | 'name' | 'provider' | 'createdAt'> {}
|
||||
interface IDatabase extends Pick<Database, 'id' | 'name' | 'provider' | 'createdAt'> {
|
||||
status: any;
|
||||
}
|
||||
|
||||
export const columns: ColumnDef<IDatabase>[] = [
|
||||
{
|
||||
|
@ -16,8 +16,9 @@ export const columns: ColumnDef<IDatabase>[] = [
|
|||
header: 'Provider',
|
||||
cell(props) {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center relative">
|
||||
<Image src={databaseProviders.filter((pro) => pro.id.toUpperCase() === props.getValue())[0]?.image?.src} alt={databaseProviders.filter((pro) => pro.id.toUpperCase() === props.getValue())[0]?.image?.alt} width={32} height={32} />
|
||||
<span className={cn('absolute bottom-0 left-8 w-2 h-2 rounded-full', props.row.original.status.status.phase == 'Running' ? 'bg-emerald-400' : 'bg-red-400')}></span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { PostgresSpecsPage } from '@/components/deploy/database/provider/postgres';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
|
@ -8,6 +9,7 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
|||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
|
||||
import { useWorkspace } from '@/hooks/useWorkspace';
|
||||
import { deployDatabase } from '@/lib/deploy/database';
|
||||
import { databaseProviders, defaultDatabaseConfiguration, IDatabaseConfiguration } from '@/lib/deploy/database-config';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Plus } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
|
@ -18,25 +20,6 @@ interface DatabaseNewSteps {
|
|||
display: string;
|
||||
}
|
||||
|
||||
interface IDatabaseProvider {
|
||||
id: string;
|
||||
display: string;
|
||||
image: {
|
||||
alt: string;
|
||||
src: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IDatabaseConfig {
|
||||
workspaceId: string;
|
||||
name: string;
|
||||
provider: IDatabaseProvider;
|
||||
user: {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
|
||||
const steps: DatabaseNewSteps[] = [
|
||||
{
|
||||
display: 'Database Type',
|
||||
|
@ -52,55 +35,26 @@ const steps: DatabaseNewSteps[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export const databaseProviders: IDatabaseProvider[] = [
|
||||
{
|
||||
id: 'vitess',
|
||||
display: 'Vitess',
|
||||
image: {
|
||||
alt: 'Vitess',
|
||||
src: '/vitess.png',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'redis',
|
||||
display: 'Redis',
|
||||
image: {
|
||||
alt: 'Redis',
|
||||
src: '/redis.svg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'postgres',
|
||||
display: 'Postgres',
|
||||
image: {
|
||||
alt: 'Postgres',
|
||||
src: '/postgres.png',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default function DatabaseNewForm() {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [currentSteps, setCurrentSteps] = useState<number>(1);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// TODO: Generate all data randomly, but leave the user the choice to modify
|
||||
const { id } = useWorkspace();
|
||||
const defaultDatabaseConfig: IDatabaseConfig = {
|
||||
workspaceId: id,
|
||||
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 [databaseConfig, setDatabaseConfig] = useState<IDatabaseConfiguration>(defaultDatabaseConfiguration(id));
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={() => setOpen((open) => !open)}>
|
||||
<Sheet
|
||||
open={open}
|
||||
onOpenChange={() => {
|
||||
setOpen((open) => !open);
|
||||
if (!open) {
|
||||
setDatabaseConfig(defaultDatabaseConfiguration(id));
|
||||
setCurrentSteps(1);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SheetTrigger asChild>
|
||||
<Button className="flex gap-1 justify-center items-center bg-[#3A7BFE]">
|
||||
<Plus />
|
||||
|
@ -152,8 +106,9 @@ export default function DatabaseNewForm() {
|
|||
|
||||
{currentSteps == 2 && (
|
||||
<div>
|
||||
<p>WIP for now 1CPU 2048Mb RAM</p>
|
||||
<p>Storage 1 GB</p>
|
||||
{databaseConfig.provider.id == 'vitess' && <div>Vitess</div>}
|
||||
{databaseConfig.provider.id == 'redis' && <div>Redis</div>}
|
||||
{databaseConfig.provider.id == 'postgres' && <PostgresSpecsPage config={databaseConfig} setConfig={setDatabaseConfig} />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@ -197,11 +152,36 @@ export default function DatabaseNewForm() {
|
|||
|
||||
{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 className="flex flex-col gap-2">
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">Database Type</p>
|
||||
<p>{databaseConfig.provider.display}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">CPUs</p>
|
||||
<p>{databaseConfig.specs.cpu}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">RAM</p>
|
||||
<p>{databaseConfig.specs.memory}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">Storage</p>
|
||||
<p>{databaseConfig.specs.storage}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">Name</p>
|
||||
<p>{databaseConfig.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">Username</p>
|
||||
<p>{databaseConfig.user.username}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">Password</p>
|
||||
<p>{databaseConfig.user.password}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@ -224,7 +204,7 @@ export default function DatabaseNewForm() {
|
|||
console.log(res);
|
||||
setOpen(false);
|
||||
setCurrentSteps(1);
|
||||
setDatabaseConfig(defaultDatabaseConfig);
|
||||
setDatabaseConfig(defaultDatabaseConfiguration(id));
|
||||
router.refresh();
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
|
@ -11,6 +11,7 @@ import { EllipsisVertical, X } from 'lucide-react';
|
|||
import { Dialog, DialogClose, DialogContent, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { useState } from 'react';
|
||||
import { Database } from '@prisma/client';
|
||||
import { deleteDatabase } from '@/lib/deploy/database';
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
|
@ -19,6 +20,7 @@ interface DataTableProps<TData, TValue> {
|
|||
|
||||
export function DatabaseTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [destroyOpen, setDestroyOpen] = useState(false);
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
|
@ -27,7 +29,7 @@ export function DatabaseTable<TData, TValue>({ columns, data }: DataTableProps<T
|
|||
});
|
||||
|
||||
const router = useRouter();
|
||||
const { slug } = useWorkspace();
|
||||
const { slug, id } = useWorkspace();
|
||||
|
||||
return (
|
||||
<div className="rounded-lg w-full overflow-hidden">
|
||||
|
@ -106,7 +108,51 @@ export function DatabaseTable<TData, TValue>({ columns, data }: DataTableProps<T
|
|||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<DropdownMenuItem className="text-red-400">Destroy</DropdownMenuItem>
|
||||
|
||||
<Dialog open={destroyOpen} onOpenChange={setDestroyOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setDestroyOpen(true);
|
||||
}}
|
||||
className="text-red-400"
|
||||
>
|
||||
Destroy
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row justify-between mb-0">
|
||||
<h1 className="text-3xl font-semibold">Are you sure?</h1>
|
||||
<DialogClose asChild>
|
||||
<Button variant="ghost">
|
||||
<X size={17} />
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
<p className="text-gray-700">Deleting a database if irreversible. This action cannot be undone.</p>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
variant={'destructive'}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
deleteDatabase({ id: data[index].id, workspaceId: id })
|
||||
.then(() => {
|
||||
setDestroyOpen(false);
|
||||
router.refresh();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
|
|
|
@ -3,6 +3,7 @@ import DatabaseNewForm from './database-new-form';
|
|||
import prisma from '@/lib/prisma';
|
||||
import { DatabaseTable } from './database-table';
|
||||
import { columns } from './database-column';
|
||||
import { getDatabase } from '@/lib/deploy/database';
|
||||
|
||||
export default async function Databases({ params }: { params: { workspace: string } }) {
|
||||
const workspaceSlug = params.workspace;
|
||||
|
@ -15,6 +16,16 @@ export default async function Databases({ params }: { params: { workspace: strin
|
|||
},
|
||||
});
|
||||
|
||||
const status = await Promise.all(
|
||||
workspace.Database.map(async (database) => {
|
||||
const res = await getDatabase({
|
||||
id: database.id,
|
||||
workspaceId: workspace.id,
|
||||
}).catch(() => ({ status: { phase: 'unknown' } }));
|
||||
return { ...res, id: database.id };
|
||||
})
|
||||
);
|
||||
|
||||
if (workspace?.Database.length == 0) {
|
||||
return (
|
||||
<div className="mt-12">
|
||||
|
@ -47,7 +58,7 @@ export default async function Databases({ params }: { params: { workspace: strin
|
|||
</div>
|
||||
</div>
|
||||
))} */}
|
||||
<DatabaseTable columns={columns} data={workspace.Database} />
|
||||
<DatabaseTable columns={columns} data={workspace.Database.map((db) => ({ ...db, status: status.find((st) => st.id == db.id) }))} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
'use client';
|
||||
|
||||
import { IDatabaseConfiguration } from '@/lib/deploy/database-config';
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { IDatabaseSpecs } from '@/lib/deploy/database-config';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
|
||||
let specsTiers: IDatabaseSpecs[] = [
|
||||
{
|
||||
id: 'postgres-xs',
|
||||
cpu: 0.5,
|
||||
memory: 512,
|
||||
maxConnections: 5,
|
||||
},
|
||||
{
|
||||
id: 'postgres-s',
|
||||
cpu: 1,
|
||||
memory: 1024,
|
||||
maxConnections: 20,
|
||||
},
|
||||
{
|
||||
id: 'postgres-m',
|
||||
cpu: 2,
|
||||
memory: 2048,
|
||||
maxConnections: 50,
|
||||
},
|
||||
{
|
||||
id: 'postgres-l',
|
||||
cpu: 4,
|
||||
memory: 4096,
|
||||
maxConnections: 100,
|
||||
},
|
||||
{
|
||||
id: 'postgres-xl',
|
||||
cpu: 8,
|
||||
memory: 8192,
|
||||
maxConnections: 200,
|
||||
},
|
||||
];
|
||||
|
||||
let MIN_STORAGE = 10;
|
||||
let MAX_STORAGE = 100;
|
||||
|
||||
export function PostgresSpecsPage({ config, setConfig }: { config: IDatabaseConfiguration; setConfig: Dispatch<SetStateAction<IDatabaseConfiguration>> }) {
|
||||
const [specsTier, setSpecsTier] = useState<IDatabaseSpecs>(config.specs.cpu ? specsTiers.filter((tier) => tier.id == config.specs.id)[0] : specsTiers[0]);
|
||||
const [storage, setStorage] = useState<number>(config.specs.storage || MIN_STORAGE);
|
||||
|
||||
useEffect(() => {
|
||||
setConfig((config) => ({
|
||||
...config,
|
||||
specs: {
|
||||
...config.specs,
|
||||
...specsTier,
|
||||
storage,
|
||||
},
|
||||
}));
|
||||
}, [specsTier, storage]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RadioGroup
|
||||
defaultValue={'vitess'}
|
||||
className="flex flex-col gap-2 mb-4"
|
||||
value={specsTier.id}
|
||||
onValueChange={(id) => {
|
||||
setSpecsTier(specsTiers.filter((tier) => tier.id == id)[0]);
|
||||
}}
|
||||
>
|
||||
{specsTiers.map((specs) => (
|
||||
<div key={specs.id} className={cn('flex flex-row items-center gap-4 border-[1px] rounded-lg py-2 px-4', specsTier.id == specs.id ? 'border-[#3A7BFE]' : 'border-gray-300')}>
|
||||
<RadioGroupItem value={specs.id} id={specs.id} />
|
||||
<label htmlFor={specs.id} className="flex flex-row items-center gap-4">
|
||||
<div className="w-8 h-8 rounded-full flex justify-center items-center">
|
||||
<p className="text-xl">{specs.id.replace('postgres-', '').toUpperCase()}</p>
|
||||
</div>
|
||||
<p className="text-sm text-gray-700">
|
||||
{specs.cpu} CPU, {specs.memory} MB RAM, {specs.maxConnections} Max Connections
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
<div>
|
||||
<div>
|
||||
<label>Storage</label>
|
||||
<p>{storage} GB</p>
|
||||
</div>
|
||||
<Slider
|
||||
min={MIN_STORAGE / 10}
|
||||
max={MAX_STORAGE / 10}
|
||||
onValueChange={(e) => {
|
||||
setStorage(e[0] * 10);
|
||||
}}
|
||||
defaultValue={[storage / 10]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as SliderPrimitive from '@radix-ui/react-slider';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const Slider = React.forwardRef<React.ElementRef<typeof SliderPrimitive.Root>, React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>>(({ className, ...props }, ref) => (
|
||||
<SliderPrimitive.Root ref={ref} className={cn('relative flex w-full touch-none select-none items-center', className)} {...props}>
|
||||
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-[#3A7BFE]" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-[#3A7BFE] bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
));
|
||||
Slider.displayName = SliderPrimitive.Root.displayName;
|
||||
|
||||
export { Slider };
|
|
@ -0,0 +1,80 @@
|
|||
import { generateName } from "@/utils/name-generator";
|
||||
import { generatePassword } from "@/utils/password-generator";
|
||||
|
||||
export interface IDatabaseProvider {
|
||||
id: string;
|
||||
display: string;
|
||||
image: {
|
||||
alt: string;
|
||||
src: string;
|
||||
};
|
||||
versions?: IDatabaseProviderVersion[];
|
||||
}
|
||||
|
||||
export interface IDatabaseProviderVersion {
|
||||
id: string;
|
||||
display: string;
|
||||
}
|
||||
|
||||
export interface IDatabaseSpecs {
|
||||
id: string;
|
||||
cpu: number;
|
||||
memory?: number;
|
||||
storage?: number;
|
||||
maxConnections?: number;
|
||||
}
|
||||
|
||||
export interface IDatabaseConfiguration {
|
||||
workspaceId: string;
|
||||
name: string;
|
||||
provider: IDatabaseProvider;
|
||||
user: {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
specs: IDatabaseSpecs;
|
||||
}
|
||||
|
||||
export function defaultDatabaseConfiguration(workspaceId: string): IDatabaseConfiguration {
|
||||
return {
|
||||
workspaceId,
|
||||
name: generateName().toLowerCase(),
|
||||
provider: databaseProviders[0],
|
||||
user: {
|
||||
username: "deployadmin",
|
||||
password: generatePassword({ length: 20, numberOfDigits: 4, numberOfSpecialCharacters: 4 }),
|
||||
},
|
||||
specs: {
|
||||
cpu: 0,
|
||||
memory: 0,
|
||||
storage: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const databaseProviders: IDatabaseProvider[] = [
|
||||
{
|
||||
id: 'vitess',
|
||||
display: 'Vitess',
|
||||
image: {
|
||||
alt: 'Vitess',
|
||||
src: '/vitess.png',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'redis',
|
||||
display: 'Redis',
|
||||
image: {
|
||||
alt: 'Redis',
|
||||
src: '/redis.svg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'postgres',
|
||||
display: 'Postgres',
|
||||
image: {
|
||||
alt: 'Postgres',
|
||||
src: '/postgres.png',
|
||||
},
|
||||
},
|
||||
];
|
|
@ -1,12 +1,11 @@
|
|||
"use server";
|
||||
|
||||
import { IDatabaseConfig } from "@/app/(deploy)/[workspace]/databases/database-new-form";
|
||||
import { IDatabaseConfiguration } from "@/lib/deploy/database-config";
|
||||
import prisma from "../prisma";
|
||||
import { DatabaseProvider } from "@prisma/client";
|
||||
|
||||
export async function deployDatabase(config: IDatabaseConfig) {
|
||||
export async function deployDatabase(config: IDatabaseConfiguration) {
|
||||
|
||||
// TODO: Refactor using transactions
|
||||
const database = await prisma.database.create({
|
||||
data: {
|
||||
name: config.name,
|
||||
|
@ -35,11 +34,11 @@ export async function deployDatabase(config: IDatabaseConfig) {
|
|||
username: config.user.username,
|
||||
password: config.user.password,
|
||||
},
|
||||
specs: config.specs,
|
||||
}),
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
console.log(json)
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(json.message);
|
||||
|
@ -62,4 +61,65 @@ export async function deployDatabase(config: IDatabaseConfig) {
|
|||
})
|
||||
throw new Error("Failed to deploy database");
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDatabaseDeleteConfig {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
export async function deleteDatabase(config: IDatabaseDeleteConfig) {
|
||||
const database = await prisma.database.findUnique({
|
||||
where: {
|
||||
id: config.id
|
||||
}
|
||||
});
|
||||
|
||||
if (!database) {
|
||||
throw new Error("Database not found");
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`http://127.0.0.1:8080/${config.workspaceId}/databases/${config.id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (!res.ok && !(res.status === 404)) {
|
||||
throw new Error(json.error);
|
||||
}
|
||||
|
||||
await prisma.database.delete({
|
||||
where: {
|
||||
id: config.id,
|
||||
}
|
||||
});
|
||||
|
||||
return json;
|
||||
} catch(err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDatabaseGetConfig {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}
|
||||
export async function getDatabase(config: IDatabaseGetConfig) {
|
||||
try {
|
||||
const res = await fetch(`http://127.0.0.1:8080/${config.workspaceId}/databases/${config.id}`, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(json.error);
|
||||
}
|
||||
|
||||
return json;
|
||||
} catch(err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@
|
|||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@tanstack/react-table": "^8.16.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
|
@ -1279,6 +1280,39 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slider": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.1.2.tgz",
|
||||
"integrity": "sha512-NKs15MJylfzVsCagVSWKhGGLNR1W9qWs+HtgbmjjVUB3B9+lb3PYoXxVju3kOrpf0VKyVCtZp+iTwVoqpa1Chw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/number": "1.0.1",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-collection": "1.0.3",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-direction": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1",
|
||||
"@radix-ui/react-use-previous": "1.0.1",
|
||||
"@radix-ui/react-use-size": "1.0.1"
|
||||
},
|
||||
"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-slot": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@tanstack/react-table": "^8.16.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
var nameList = [
|
||||
'Time', 'Past', 'Future', 'Dev',
|
||||
'Fly', 'Flying', 'Soar', 'Soaring', 'Power', 'Falling',
|
||||
'Fall', 'Jump', 'Cliff', 'Mountain', 'Rend', 'Red', 'Blue',
|
||||
'Green', 'Yellow', 'Gold', 'Demon', 'Demonic', 'Panda', 'Cat',
|
||||
'Kitty', 'Kitten', 'Zero', 'Memory', 'Trooper', 'XX', 'Bandit',
|
||||
'Fear', 'Light', 'Glow', 'Tread', 'Deep', 'Deeper', 'Deepest',
|
||||
'Mine', 'Your', 'Worst', 'Enemy', 'Hostile', 'Force', 'Video',
|
||||
'Game', 'Donkey', 'Mule', 'Colt', 'Cult', 'Cultist', 'Magnum',
|
||||
'Gun', 'Assault', 'Recon', 'Trap', 'Trapper', 'Redeem', 'Code',
|
||||
'Script', 'Writer', 'Near', 'Close', 'Open', 'Cube', 'Circle',
|
||||
'Geo', 'Genome', 'Germ', 'Spaz', 'Shot', 'Echo', 'Beta', 'Alpha',
|
||||
'Gamma', 'Omega', 'Seal', 'Squid', 'Money', 'Cash', 'Lord', 'King',
|
||||
'Duke', 'Rest', 'Fire', 'Flame', 'Morrow', 'Break', 'Breaker', 'Numb',
|
||||
'Ice', 'Cold', 'Rotten', 'Sick', 'Sickly', 'Janitor', 'Camel', 'Rooster',
|
||||
'Sand', 'Desert', 'Dessert', 'Hurdle', 'Racer', 'Eraser', 'Erase', 'Big',
|
||||
'Small', 'Short', 'Tall', 'Sith', 'Bounty', 'Hunter', 'Cracked', 'Broken',
|
||||
'Sad', 'Happy', 'Joy', 'Joyful', 'Crimson', 'Destiny', 'Deceit', 'Lies',
|
||||
'Lie', 'Honest', 'Destined', 'Bloxxer', 'Hawk', 'Eagle', 'Hawker', 'Walker',
|
||||
'Zombie', 'Sarge', 'Capt', 'Captain', 'Punch', 'One', 'Two', 'Uno', 'Slice',
|
||||
'Slash', 'Melt', 'Melted', 'Melting', 'Fell', 'Wolf', 'Hound',
|
||||
'Legacy', 'Sharp', 'Dead', 'Mew', 'Chuckle', 'Bubba', 'Bubble', 'Sandwich', 'Smasher', 'Extreme', 'Multi', 'Universe', 'Ultimate', 'Death', 'Ready', 'Monkey', 'Elevator', 'Wrench', 'Grease', 'Head', 'Theme', 'Grand', 'Cool', 'Kid', 'Boy', 'Girl', 'Vortex', 'Paradox'
|
||||
];
|
||||
|
||||
export function generateName(): string {
|
||||
return nameList[Math.floor(Math.random() * nameList.length)] + "-" + nameList[Math.floor(Math.random() * nameList.length)];
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
let special = "!@$&*()_+";
|
||||
let digits = "0123456789";
|
||||
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
interface PasswordGeneratorConfiguration {
|
||||
length?: number;
|
||||
numberOfDigits?: number;
|
||||
numberOfSpecialCharacters?: number;
|
||||
}
|
||||
|
||||
export function generatePassword({ length = 16, numberOfDigits = 2, numberOfSpecialCharacters = 4 }: PasswordGeneratorConfiguration): string {
|
||||
let password = "";
|
||||
|
||||
for (let i = 0; i < length - numberOfDigits - numberOfSpecialCharacters; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * length);
|
||||
const randomLetter = Math.floor(Math.random() * letters.length);
|
||||
password = password.slice(0, randomIndex) + letters[randomLetter] + password.slice(randomIndex);
|
||||
}
|
||||
|
||||
for (let i = 0; i < numberOfDigits; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * length);
|
||||
const randomDigit = Math.floor(Math.random() * 10);
|
||||
password = password.slice(0, randomIndex) + digits[randomDigit] + password.slice(randomIndex);
|
||||
}
|
||||
|
||||
for (let i = 0; i < numberOfSpecialCharacters; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * length);
|
||||
const randomSpecial = Math.floor(Math.random() * special.length);
|
||||
password = password.slice(0, randomIndex) + special[randomSpecial] + password.slice(randomIndex);
|
||||
}
|
||||
|
||||
return password;
|
||||
}
|
Loading…
Reference in New Issue