'use client'; import { useState, useRef, useEffect } from 'react'; import { ScriptsGrid } from './_components/ScriptsGrid'; import { DownloadedScriptsTab } from './_components/DownloadedScriptsTab'; import { InstalledScriptsTab } from './_components/InstalledScriptsTab'; import { BackupsTab } from './_components/BackupsTab'; import { ResyncButton } from './_components/ResyncButton'; import { Terminal } from './_components/Terminal'; import { ServerSettingsButton } from './_components/ServerSettingsButton'; import { SettingsButton } from './_components/SettingsButton'; import { HelpButton } from './_components/HelpButton'; import { VersionDisplay } from './_components/VersionDisplay'; import { ThemeToggle } from './_components/ThemeToggle'; import { Button } from './_components/ui/button'; import { ContextualHelpIcon } from './_components/ContextualHelpIcon'; import { ReleaseNotesModal, getLastSeenVersion } from './_components/ReleaseNotesModal'; import { Footer } from './_components/Footer'; import { Package, HardDrive, FolderOpen, LogOut, Archive } from 'lucide-react'; import { api } from '~/trpc/react'; import { useAuth } from './_components/AuthProvider'; export default function Home() { const { isAuthenticated, logout } = useAuth(); const [runningScript, setRunningScript] = useState<{ path: string; name: string; mode?: 'local' | 'ssh'; server?: any } | null>(null); const [activeTab, setActiveTab] = useState<'scripts' | 'downloaded' | 'installed' | 'backups'>(() => { if (typeof window !== 'undefined') { const savedTab = localStorage.getItem('activeTab') as 'scripts' | 'downloaded' | 'installed' | 'backups'; return savedTab || 'scripts'; } return 'scripts'; }); const [releaseNotesOpen, setReleaseNotesOpen] = useState(false); const [highlightVersion, setHighlightVersion] = useState(undefined); const terminalRef = useRef(null); // Fetch data for script counts const { data: scriptCardsData } = api.scripts.getScriptCardsWithCategories.useQuery(); const { data: localScriptsData } = api.scripts.getAllDownloadedScripts.useQuery(); const { data: installedScriptsData } = api.installedScripts.getAllInstalledScripts.useQuery(); const { data: backupsData } = api.backups.getAllBackupsGrouped.useQuery(); const { data: versionData } = api.version.getCurrentVersion.useQuery(); // Save active tab to localStorage whenever it changes useEffect(() => { if (typeof window !== 'undefined') { localStorage.setItem('activeTab', activeTab); } }, [activeTab]); // Auto-show release notes modal after update useEffect(() => { if (versionData?.success && versionData.version) { const currentVersion = versionData.version; const lastSeenVersion = getLastSeenVersion(); // If we have a current version and either no last seen version or versions don't match if (currentVersion && (!lastSeenVersion || currentVersion !== lastSeenVersion)) { setHighlightVersion(currentVersion); setReleaseNotesOpen(true); } } }, [versionData]); const handleOpenReleaseNotes = () => { setHighlightVersion(undefined); setReleaseNotesOpen(true); }; const handleCloseReleaseNotes = () => { setReleaseNotesOpen(false); setHighlightVersion(undefined); }; // Calculate script counts const scriptCounts = { available: (() => { if (!scriptCardsData?.success) return 0; // Deduplicate scripts using Map by slug (same logic as ScriptsGrid.tsx) const scriptMap = new Map(); scriptCardsData.cards?.forEach(script => { if (script?.name && script?.slug) { // Use slug as unique identifier, only keep first occurrence if (!scriptMap.has(script.slug)) { scriptMap.set(script.slug, script); } } }); return scriptMap.size; })(), downloaded: (() => { if (!scriptCardsData?.success || !localScriptsData?.scripts) return 0; // First deduplicate GitHub scripts using Map by slug const scriptMap = new Map(); scriptCardsData.cards?.forEach(script => { if (script?.name && script?.slug) { if (!scriptMap.has(script.slug)) { scriptMap.set(script.slug, script); } } }); const deduplicatedGithubScripts = Array.from(scriptMap.values()); const localScripts = localScriptsData.scripts ?? []; // Count scripts that are both in deduplicated GitHub data and have local versions return deduplicatedGithubScripts.filter(script => { if (!script?.name) return false; return localScripts.some(local => { if (!local?.name) return false; const localName = local.name.replace(/\.sh$/, ''); return localName.toLowerCase() === script.name.toLowerCase() || localName.toLowerCase() === (script.slug ?? '').toLowerCase(); }); }).length; })(), installed: installedScriptsData?.scripts?.length ?? 0, backups: backupsData?.success ? backupsData.backups.length : 0 }; const scrollToTerminal = () => { if (terminalRef.current) { // Get the element's position and scroll with a small offset for better mobile experience const elementTop = terminalRef.current.offsetTop; const offset = window.innerWidth < 768 ? 20 : 0; // Small offset on mobile window.scrollTo({ top: elementTop - offset, behavior: 'smooth' }); } }; const handleRunScript = (scriptPath: string, scriptName: string, mode?: 'local' | 'ssh', server?: any) => { setRunningScript({ path: scriptPath, name: scriptName, mode, server }); // Scroll to terminal after a short delay to ensure it's rendered setTimeout(scrollToTerminal, 100); }; const handleCloseTerminal = () => { setRunningScript(null); }; return (
{/* Header */}

PVE Scripts Management

{isAuthenticated && ( )}

Manage and execute Proxmox helper scripts locally with live output streaming

{/* Controls */}
{/* Tab Navigation */}
{/* Running Script Terminal */} {runningScript && (
)} {/* Tab Content */} {activeTab === 'scripts' && ( )} {activeTab === 'downloaded' && ( )} {activeTab === 'installed' && ( )} {activeTab === 'backups' && ( )}
{/* Footer */}
); }