diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index 23d0dfe..e803aaa 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -259,7 +259,10 @@ export function VirtualMachines() { const [currentView, setCurrentView] = useState<"main" | "metrics">("main") const [showAdditionalInfo, setShowAdditionalInfo] = useState(false) const [showNotes, setShowNotes] = useState(false) - const [selectedMetric, setSelectedMetric] = useState(null) // undeclared variable fix + const [isEditingNotes, setIsEditingNotes] = useState(false) + const [editedNotes, setEditedNotes] = useState("") + const [savingNotes, setSavingNotes] = useState(false) + const [selectedMetric, setSelectedMetric] = useState(null) useEffect(() => { const fetchLXCIPs = async () => { @@ -294,6 +297,9 @@ export function VirtualMachines() { setSelectedVM(vm) setCurrentView("main") setShowAdditionalInfo(false) + setShowNotes(false) + setIsEditingNotes(false) + setEditedNotes("") setDetailsLoading(true) try { const response = await fetch(`/api/vms/${vm.vmid}`) @@ -468,6 +474,84 @@ export function VirtualMachines() { ) } + const isHTML = (str: string): boolean => { + const htmlRegex = /<\/?[a-z][\s\S]*>/i + return htmlRegex.test(str) + } + + const processDescription = (description: string): { html: string; isHtml: boolean; error: boolean } => { + try { + // Try to decode + const decoded = decodeURIComponent(description.replace(/%0A/g, "\n")) + + // Check if it contains HTML + if (isHTML(decoded)) { + return { html: decoded, isHtml: true, error: false } + } + + // If it's plain text, convert \n to
+ return { html: decoded.replace(/\n/g, "
"), isHtml: false, error: false } + } catch (error) { + // If decoding fails, return the original content + console.error("Error decoding description:", error) + return { html: description, isHtml: false, error: true } + } + } + + const handleEditNotes = () => { + if (vmDetails?.config?.description) { + try { + const decoded = decodeURIComponent(vmDetails.config.description.replace(/%0A/g, "\n")) + setEditedNotes(decoded) + } catch (error) { + setEditedNotes(vmDetails.config.description) + } + } + setIsEditingNotes(true) + } + + const handleSaveNotes = async () => { + if (!selectedVM || !vmDetails) return + + setSavingNotes(true) + try { + const response = await fetch(`/api/vms/${selectedVM.vmid}/config`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + description: encodeURIComponent(editedNotes).replace(/\n/g, "%0A"), + }), + }) + + if (response.ok) { + // Update local state + setVMDetails({ + ...vmDetails, + config: { + ...vmDetails.config, + description: encodeURIComponent(editedNotes).replace(/\n/g, "%0A"), + }, + }) + setIsEditingNotes(false) + } else { + console.error("Failed to save notes") + alert("Failed to save notes. Please try again.") + } + } catch (error) { + console.error("Error saving notes:", error) + alert("Error saving notes. Please try again.") + } finally { + setSavingNotes(false) + } + } + + const handleCancelEditNotes = () => { + setIsEditingNotes(false) + setEditedNotes("") + } + return (
@@ -787,6 +871,8 @@ export function VirtualMachines() { setSelectedMetric(null) setShowAdditionalInfo(false) setShowNotes(false) + setIsEditingNotes(false) + setEditedNotes("") }} > @@ -936,7 +1022,6 @@ export function VirtualMachines() {
- {/* ... existing RESOURCES card and additional info ... */} {detailsLoading ? (
Loading configuration...
) : vmDetails?.config ? ( @@ -1021,18 +1106,69 @@ export function VirtualMachines() { {showNotes && vmDetails.config.description && (
-

- Notes -

+
+

+ Notes +

+ {!isEditingNotes && ( + + )} +
-
"), - }} - /> + {isEditingNotes ? ( +
+