"use client"; import { AppSidebar } from "@/components/app-sidebar"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from "@/components/ui/breadcrumb"; import { Separator } from "@/components/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, } from "@/components/ui/sidebar"; import { Button } from "@/components/ui/button"; import { Plus, Link, Home, Trash2, LayoutGrid, List, Pencil, Zap, } from "lucide-react"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "@/components/ui/pagination"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import Cookies from "js-cookie"; import { useState, useEffect } from "react"; import axios from "axios"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" import { StatusIndicator } from "@/components/status-indicator"; import { Toaster } from "@/components/ui/sonner" import { toast } from "sonner" interface Application { id: number; name: string; description?: string; icon?: string; publicURL: string; localURL?: string; server?: string; online: boolean; serverId: number; } interface Server { id: number; name: string; } interface ApplicationsResponse { applications: Application[]; servers: Server[]; maxPage: number; } export default function Dashboard() { const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [icon, setIcon] = useState(""); const [publicURL, setPublicURL] = useState(""); const [localURL, setLocalURL] = useState(""); const [serverId, setServerId] = useState(null); const [editName, setEditName] = useState(""); const [editDescription, setEditDescription] = useState(""); const [editIcon, setEditIcon] = useState(""); const [editPublicURL, setEditPublicURL] = useState(""); const [editLocalURL, setEditLocalURL] = useState(""); const [editId, setEditId] = useState(null); const [editServerId, setEditServerId] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [maxPage, setMaxPage] = useState(1); const [applications, setApplications] = useState([]); const [servers, setServers] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); const [isSearching, setIsSearching] = useState(false); const savedLayout = Cookies.get("layoutPreference-app"); const initialIsGridLayout = savedLayout === "grid"; const initialItemsPerPage = initialIsGridLayout ? 15 : 5; const [isGridLayout, setIsGridLayout] = useState(initialIsGridLayout); const [itemsPerPage, setItemsPerPage] = useState(initialItemsPerPage); const toggleLayout = () => { const newLayout = !isGridLayout; setIsGridLayout(newLayout); Cookies.set("layoutPreference-app", newLayout ? "grid" : "standard", { expires: 365, path: "/", sameSite: "strict", }); setItemsPerPage(newLayout ? 15 : 5); }; const add = async () => { try { await axios.post("/api/applications/add", { name, description, icon, publicURL, localURL, serverId, }); getApplications(); toast.success("Application added successfully"); } catch (error: any) { console.log(error.response?.data); toast.error("Failed to add application"); } }; const getApplications = async () => { try { setLoading(true); const response = await axios.post( "/api/applications/get", { page: currentPage, ITEMS_PER_PAGE: itemsPerPage } ); setApplications(response.data.applications); setServers(response.data.servers); setMaxPage(response.data.maxPage); setLoading(false); } catch (error: any) { console.log(error.response?.data); toast.error("Failed to get applications"); } }; useEffect(() => { getApplications(); }, [currentPage, itemsPerPage]); const handlePrevious = () => setCurrentPage((prev) => Math.max(1, prev - 1)); const handleNext = () => setCurrentPage((prev) => Math.min(maxPage, prev + 1)); const deleteApplication = async (id: number) => { try { await axios.post("/api/applications/delete", { id }); getApplications(); toast.success("Application deleted successfully"); } catch (error: any) { console.log(error.response?.data); toast.error("Failed to delete application"); } }; const openEditDialog = (app: Application) => { setEditId(app.id); setEditServerId(app.serverId); setEditName(app.name); setEditDescription(app.description || ""); setEditIcon(app.icon || ""); setEditLocalURL(app.localURL || ""); setEditPublicURL(app.publicURL || ""); }; const edit = async () => { if (!editId) return; try { await axios.put("/api/applications/edit", { id: editId, serverId: editServerId, name: editName, description: editDescription, icon: editIcon, publicURL: editPublicURL, localURL: editLocalURL, }); getApplications(); setEditId(null); toast.success("Application edited successfully"); } catch (error: any) { console.log(error.response.data); toast.error("Failed to edit application"); } }; const searchApplications = async () => { try { setIsSearching(true); const response = await axios.post<{ results: Application[] }>( "/api/applications/search", { searchterm: searchTerm } ); setApplications(response.data.results); setIsSearching(false); } catch (error: any) { console.error("Search error:", error.response?.data); setIsSearching(false); } }; useEffect(() => { const delayDebounce = setTimeout(() => { if (searchTerm.trim() === "") { getApplications(); } else { searchApplications(); } }, 300); return () => clearTimeout(delayDebounce); }, [searchTerm]); const generateIconURL = async () => { setIcon("https://cdn.jsdelivr.net/gh/selfhst/icons/png/" + name.toLowerCase() + ".png") } const generateEditIconURL = async () => { setEditIcon("https://cdn.jsdelivr.net/gh/selfhst/icons/png/" + editName.toLowerCase() + ".png") } return (
/ My Infrastructure Applications
Your Applications
{servers.length === 0 ? (

You must first add a server.

) : ( Add an application
setName(e.target.value)} />