"use client"; import { useState } from "react"; import Image from "next/image"; import { api } from "~/trpc/react"; import type { Script } from "~/types/script"; import type { Server } from "~/types/server"; import { DiffViewer } from "./DiffViewer"; import { TextViewer } from "./TextViewer"; import { ExecutionModeModal } from "./ExecutionModeModal"; import { ConfirmationModal } from "./ConfirmationModal"; import { ScriptVersionModal } from "./ScriptVersionModal"; import { TypeBadge, UpdateableBadge, PrivilegedBadge, NoteBadge, } from "./Badge"; import { Button } from "./ui/button"; import { useRegisterModal } from "./modal/ModalStackProvider"; interface ScriptDetailModalProps { script: Script | null; isOpen: boolean; onClose: () => void; onInstallScript?: ( scriptPath: string, scriptName: string, mode?: "local" | "ssh", server?: Server, envVars?: Record, ) => void; } export function ScriptDetailModal({ script, isOpen, onClose, onInstallScript, }: ScriptDetailModalProps) { useRegisterModal(isOpen, { id: "script-detail-modal", allowEscape: true, onClose, }); const [imageError, setImageError] = useState(false); const [isLoading, setIsLoading] = useState(false); const [loadMessage, setLoadMessage] = useState(null); const [diffViewerOpen, setDiffViewerOpen] = useState(false); const [selectedDiffFile, setSelectedDiffFile] = useState(null); const [textViewerOpen, setTextViewerOpen] = useState(false); const [executionModeOpen, setExecutionModeOpen] = useState(false); const [versionModalOpen, setVersionModalOpen] = useState(false); const [selectedVersionType, setSelectedVersionType] = useState( null, ); const [isDeleting, setIsDeleting] = useState(false); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); // Check if script files exist locally const { data: scriptFilesData, refetch: refetchScriptFiles, isLoading: scriptFilesLoading, } = api.scripts.checkScriptFiles.useQuery( { slug: script?.slug ?? "" }, { enabled: !!script && isOpen }, ); // Compare local and remote script content (run in parallel, not dependent on scriptFilesData) const { data: comparisonData, refetch: refetchComparison, isLoading: comparisonLoading, } = api.scripts.compareScriptContent.useQuery( { slug: script?.slug ?? "" }, { enabled: !!script && isOpen, refetchOnMount: true, staleTime: 0, }, ); // Load script mutation const loadScriptMutation = api.scripts.loadScript.useMutation({ onSuccess: (data) => { setIsLoading(false); if (data.success) { const message = "message" in data ? data.message : "Script loaded successfully"; setLoadMessage(`[SUCCESS] ${message}`); // Refetch script files status and comparison data to update the UI void refetchScriptFiles(); void refetchComparison(); } else { const error = "error" in data ? data.error : "Failed to load script"; setLoadMessage(`[ERROR] ${error}`); } // Clear message after 5 seconds setTimeout(() => setLoadMessage(null), 5000); }, onError: (error) => { setIsLoading(false); setLoadMessage(`[ERROR] ${error.message}`); setTimeout(() => setLoadMessage(null), 5000); }, }); // Delete script mutation const deleteScriptMutation = api.scripts.deleteScript.useMutation({ onSuccess: (data) => { setIsDeleting(false); if (data.success) { const message = "message" in data ? data.message : "Script deleted successfully"; setLoadMessage(`[SUCCESS] ${message}`); // Refetch script files status and comparison data to update the UI void refetchScriptFiles(); void refetchComparison(); } else { const error = "error" in data ? data.error : "Failed to delete script"; setLoadMessage(`[ERROR] ${error}`); } // Clear message after 5 seconds setTimeout(() => setLoadMessage(null), 5000); }, onError: (error) => { setIsDeleting(false); setLoadMessage(`[ERROR] ${error.message}`); setTimeout(() => setLoadMessage(null), 5000); }, }); if (!isOpen || !script) return null; const handleImageError = () => { setImageError(true); }; const handleBackdropClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) { onClose(); } }; const handleLoadScript = async () => { if (!script) return; setIsLoading(true); setLoadMessage(null); loadScriptMutation.mutate({ slug: script.slug }); }; const handleInstallScript = () => { if (!script) return; // Check if script has multiple variants (default and alpine) const installMethods = script.install_methods || []; const hasMultipleVariants = installMethods.filter( (method) => method.type === "default" || method.type === "alpine", ).length > 1; if (hasMultipleVariants) { // Show version selection modal first setVersionModalOpen(true); } else { // Only one variant, proceed directly to execution mode // Use the first available method or default to 'default' type const defaultMethod = installMethods.find( (method) => method.type === "default", ); const firstMethod = installMethods[0]; setSelectedVersionType( defaultMethod?.type ?? firstMethod?.type ?? "default", ); setExecutionModeOpen(true); } }; const handleVersionSelect = (versionType: string) => { setSelectedVersionType(versionType); setVersionModalOpen(false); setExecutionModeOpen(true); }; const handleExecuteScript = (mode: "local" | "ssh", server?: Server, envVars?: Record) => { if (!script || !onInstallScript) return; // Find the script path based on selected version type const versionType = selectedVersionType ?? "default"; const scriptMethod = script.install_methods?.find( (method) => method.type === versionType && method.script, ) ?? script.install_methods?.find((method) => method.script); if (scriptMethod?.script) { const scriptPath = `scripts/${scriptMethod.script}`; const scriptName = script.name; // Pass execution mode, server info, and envVars to the parent onInstallScript(scriptPath, scriptName, mode, server, envVars); onClose(); // Close the modal when starting installation } }; const handleViewScript = () => { setTextViewerOpen(true); }; const handleDeleteScript = () => { if (!script) return; setDeleteConfirmOpen(true); }; const handleConfirmDelete = () => { if (!script) return; setDeleteConfirmOpen(false); setIsDeleting(true); setLoadMessage(null); deleteScriptMutation.mutate({ slug: script.slug }); }; return (
{/* Header */}
{script.logo && !imageError ? ( {`${script.name} ) : (
{script.name.charAt(0).toUpperCase()}
)} {/* Interface Port*/} {script.interface_port && (
Port: {script.interface_port}
)}
{/* Close Button */}
{/* Action Buttons */}
{/* Install Button - only show if script files exist */} {scriptFilesData?.success && scriptFilesData.ctExists && onInstallScript && ( )} {/* View Button - only show if script files exist */} {scriptFilesData?.success && (scriptFilesData.ctExists || scriptFilesData.installExists) && ( )} {/* Load/Update Script Button */} {(() => { const hasLocalFiles = scriptFilesData?.success && (scriptFilesData.ctExists || scriptFilesData.installExists); const hasDifferences = comparisonData?.success && comparisonData.hasDifferences; const isUpToDate = hasLocalFiles && !hasDifferences; if (!hasLocalFiles) { // No local files - show Load Script button return ( ); } else if (isUpToDate) { // Local files exist and are up to date - show disabled Update button return ( ); } else { // Local files exist but have differences - show Update button return ( ); } })()} {/* Delete Button - only show if script files exist */} {scriptFilesData?.success && (scriptFilesData.ctExists || scriptFilesData.installExists) && ( )}
{/* Content */}
{/* Script Files Status */} {(scriptFilesLoading || comparisonLoading) && (
Loading script status...
)} {scriptFilesData?.success && !scriptFilesLoading && (() => { // Determine script type from the first install method const firstScript = script?.install_methods?.[0]?.script; let scriptType = "Script"; if (firstScript?.startsWith("ct/")) { scriptType = "CT Script"; } else if (firstScript?.startsWith("tools/")) { scriptType = "Tools Script"; } else if (firstScript?.startsWith("vm/")) { scriptType = "VM Script"; } else if (firstScript?.startsWith("vw/")) { scriptType = "VW Script"; } return (
{scriptType}:{" "} {scriptFilesData.ctExists ? "Available" : "Not loaded"}
Install Script:{" "} {scriptFilesData.installExists ? "Available" : "Not loaded"}
{scriptFilesData?.success && (scriptFilesData.ctExists || scriptFilesData.installExists) && (
{comparisonData?.success ? ( <>
Status:{" "} {comparisonData.hasDifferences ? "Update available" : "Up to date"} ) : comparisonLoading ? ( <>
Checking for updates... ) : comparisonData?.error ? ( <>
Error: {comparisonData.error} ) : ( <>
Status: Unknown )}
)}
{scriptFilesData.files.length > 0 && (
Files: {scriptFilesData.files.join(", ")}
)}
); })()} {/* Load Message */} {loadMessage && (
{loadMessage}
)} {/* Description */}

Description

{script.description}

{/* Basic Information */}

Basic Information

Slug
{script.slug}
Date Created
{script.date_created}
Categories
{script.categories.join(", ")}
{script.interface_port && (
Interface Port
{script.interface_port}
)} {script.config_path && (
Config Path
{script.config_path}
)}

Links

{script.website && ( )} {script.documentation && ( )}
{/* Install Methods - Hide for PVE and ADDON types as they typically don't have install methods */} {script.install_methods.length > 0 && script.type !== "pve" && script.type !== "addon" && (

Install Methods

{script.install_methods.map((method, index) => (

{method.type}

{method.script}
CPU
{method.resources.cpu} cores
RAM
{method.resources.ram} MB
HDD
{method.resources.hdd} GB
OS
{method.resources.os} {method.resources.version}
))}
)} {/* Default Credentials */} {(script.default_credentials.username ?? script.default_credentials.password) && (

Default Credentials

{script.default_credentials.username && (
Username
{script.default_credentials.username}
)} {script.default_credentials.password && (
Password
{script.default_credentials.password}
)}
)} {/* Notes */} {script.notes.length > 0 && (

Notes

    {script.notes.map((note, index) => { // Handle both object and string note formats const noteText = typeof note === "string" ? note : note.text; const noteType = typeof note === "string" ? "info" : note.type; return (
  • {noteType} {noteText}
  • ); })}
)}
{/* Diff Viewer Modal */} {selectedDiffFile && ( { setDiffViewerOpen(false); setSelectedDiffFile(null); }} /> )} {/* Text Viewer Modal */} {script && ( method.script && (method.script.startsWith("ct/") || method.script.startsWith("vm/") || method.script.startsWith("tools/")), ) ?.script?.split("/") .pop() ?? `${script.slug}.sh` } script={script} isOpen={textViewerOpen} onClose={() => setTextViewerOpen(false)} /> )} {/* Version Selection Modal */} {script && ( setVersionModalOpen(false)} onSelectVersion={handleVersionSelect} /> )} {/* Execution Mode Modal */} {script && ( setExecutionModeOpen(false)} onExecute={handleExecuteScript} /> )} {/* Delete Confirmation Modal */} {script && ( setDeleteConfirmOpen(false)} onConfirm={handleConfirmDelete} title="Delete Script" message={`Are you sure you want to delete all downloaded files for "${script.name}"? This action cannot be undone.`} variant="simple" confirmButtonText="Delete" cancelButtonText="Cancel" /> )}
); }