App dialogs
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"install": "sh test"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
+1
-1
@@ -185,7 +185,7 @@
|
||||
--popover-foreground: oklch(0.9108 0.0427 269.5516);
|
||||
--primary: oklch(0.7182 0.1105 239.784);
|
||||
--primary-foreground: oklch(0.3233 0.1025 253.8852);
|
||||
--secondary: oklch(0.5544 0.0407 257.4166);
|
||||
--secondary: oklch(0.38 0.06 258);
|
||||
--secondary-foreground: oklch(0.9288 0.0126 255.5078);
|
||||
--muted: oklch(0.3923 0.0613 254.3914);
|
||||
--muted-foreground: oklch(0.7584 0.0419 263.598);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { Plus } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useState } from "react";
|
||||
import Session from "@/lib/session";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export default function AddAppDialog({
|
||||
session,
|
||||
}: {
|
||||
session: Session | null;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [name, setName] = useState("");
|
||||
const [ip, setIp] = useState("");
|
||||
const [location, setLocation] = useState<string | undefined>(undefined);
|
||||
const [coordinates, setCoordinates] = useState<[number, number] | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger
|
||||
className={cn(buttonVariants({ variant: "default" }))}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<Plus className="m-0 p-0" /> New
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-[85vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add Application</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Label className="mt-2">
|
||||
Application Name<span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Sentryx chat, My Project, etc"
|
||||
required
|
||||
onInput={(e) => setName(e.currentTarget.value)}
|
||||
/>
|
||||
|
||||
<Label className="mt-2">
|
||||
Application service<span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="my-app.service"
|
||||
required
|
||||
onInput={(e) => setIp(e.currentTarget.value)}
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
disabled={name == "" || ip == ""}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
Add App
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export default function AppComponent({
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="w-full transition-transform duration-500 hover:scale-102 hover:cursor-pointer !select-none border-accent border-2"
|
||||
className="w-full transition-transform duration-500 hover:scale-102 hover:cursor-pointer !select-none border-secondary border-2"
|
||||
onClick={() => {
|
||||
redirect(`/servers/${index}`);
|
||||
}}
|
||||
|
||||
@@ -10,6 +10,8 @@ import AppComponent from "./AppComponent";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "sonner";
|
||||
import AddAppDialog from "./AddAppDialog";
|
||||
import InstallAppDialog from "./IntallAppDialog";
|
||||
|
||||
interface Server2 extends Server {
|
||||
services: Service[],
|
||||
@@ -40,12 +42,13 @@ export default function ClientPage({ serverId }: { serverId: string }) {
|
||||
<Card>
|
||||
<CardHeader className="flex">
|
||||
<div>
|
||||
<CardTitle>Sentryx Apps</CardTitle>
|
||||
<CardTitle>Sentryx Applications</CardTitle>
|
||||
<CardDescription>Your Sentryx apps on this server</CardDescription>
|
||||
</div>
|
||||
<Button className="ml-auto" onClick={() => {
|
||||
toast.warning("Functionality not added yet");
|
||||
}}>Add</Button>
|
||||
<div className="ml-auto w-max flex gap-3">
|
||||
<InstallAppDialog session={session} />
|
||||
<AddAppDialog session={session} />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { HardDriveDownload, Plus } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useState } from "react";
|
||||
import Session from "@/lib/session";
|
||||
import { toast } from "sonner";
|
||||
import { FileUpload } from "@/components/FileUpload";
|
||||
|
||||
export default function InstallAppDialog({
|
||||
session,
|
||||
}: {
|
||||
session: Session | null;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [name, setName] = useState("");
|
||||
const [script, setScript] = useState<{ install?: string } | undefined>({});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger
|
||||
className={cn(buttonVariants({ variant: "secondary" }))}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<HardDriveDownload className="m-0 p-0" /> Install
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-[85vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Install Application</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<Label className="mt-2">
|
||||
Application Name<span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Sentryx chat, My Project, etc"
|
||||
required
|
||||
onInput={(e) => setName(e.currentTarget.value)}
|
||||
/>
|
||||
|
||||
<Label className="mt-2">
|
||||
Application script<span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<FileUpload setSelected={(contents) => {
|
||||
try {
|
||||
setScript(JSON.parse(contents))
|
||||
console.log(contents)
|
||||
} catch {
|
||||
setScript(undefined)
|
||||
}
|
||||
}} />
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
disabled={name == "" || !script || !(script.install)}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
Install
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useState, useRef } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { cn } from "@/lib/utils" // optional utility for combining classes
|
||||
|
||||
export function FileUpload({setSelected} : {setSelected: (contents: string) => void}) {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null)
|
||||
const [fileName, setFileName] = useState<string | null>(null)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [isUnsupported, setIsUnsupported] = useState(false)
|
||||
|
||||
const handleFiles = (files: FileList | null) => {
|
||||
const file = files?.[0]
|
||||
if (!file) return
|
||||
|
||||
setIsUnsupported(!file.name.endsWith('.json'));
|
||||
if (isUnsupported) return
|
||||
|
||||
setFileName(file.name)
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
setSelected(e.target?.result as string)
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}
|
||||
|
||||
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault()
|
||||
setIsDragging(false)
|
||||
handleFiles(e.dataTransfer.files)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
onChange={(e) => handleFiles(e.target.files)}
|
||||
className="hidden"
|
||||
/>
|
||||
|
||||
<div
|
||||
onClick={() => inputRef.current?.click()}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault()
|
||||
setIsDragging(true)
|
||||
}}
|
||||
onDragLeave={() => setIsDragging(false)}
|
||||
onDrop={handleDrop}
|
||||
className={cn(
|
||||
"flex h-32 cursor-pointer items-center justify-center rounded-md border-2 border-dashed transition-colors",
|
||||
isDragging ? "border-primary bg-muted" : "border-muted",
|
||||
isUnsupported && "border-destructive"
|
||||
)}
|
||||
>
|
||||
<span className="text-muted-foreground">
|
||||
{isUnsupported ? "Unsupported file type" : (fileName || "Click or drag file to upload")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user