Add ProxMenux beta 1.2.1.3

This commit is contained in:
MacRimi
2026-05-22 18:24:03 +02:00
parent 95d0667077
commit 840385272c
9 changed files with 586 additions and 26 deletions
@@ -0,0 +1,227 @@
"use client"
import { useEffect, useState } from "react"
import { Boxes, Info, Loader2, Settings2, CheckCircle2 } from "lucide-react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card"
import { Badge } from "./ui/badge"
import { fetchApi } from "../lib/api-config"
interface DetectionResponse {
success: boolean
enabled?: boolean
message?: string
purged?: number
}
export function LxcUpdateDetection() {
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [enabled, setEnabled] = useState<boolean>(true)
const [pending, setPending] = useState<boolean>(true)
const [editMode, setEditMode] = useState(false)
const [error, setError] = useState<string | null>(null)
const [saved, setSaved] = useState(false)
const [lastPurged, setLastPurged] = useState<number | null>(null)
useEffect(() => {
let cancelled = false
fetchApi<DetectionResponse>("/api/lxc-updates/detection")
.then(data => {
if (cancelled) return
if (data.success && typeof data.enabled === "boolean") {
setEnabled(data.enabled)
setPending(data.enabled)
} else {
setError(data.message || "Failed to load setting")
}
})
.catch(e => {
if (!cancelled) setError(String(e))
})
.finally(() => {
if (!cancelled) setLoading(false)
})
return () => {
cancelled = true
}
}, [])
const hasChanges = pending !== enabled
function handleEdit() {
setEditMode(true)
setError(null)
setSaved(false)
setLastPurged(null)
}
function handleCancel() {
setPending(enabled)
setEditMode(false)
setError(null)
setLastPurged(null)
}
async function handleSave() {
if (!hasChanges) {
setEditMode(false)
return
}
setSaving(true)
setError(null)
setSaved(false)
setLastPurged(null)
try {
const data = await fetchApi<DetectionResponse>("/api/lxc-updates/detection", {
method: "POST",
body: JSON.stringify({ enabled: pending }),
})
if (!data.success) {
setError(data.message || "Failed to save setting")
return
}
setEnabled(pending)
setEditMode(false)
setSaved(true)
setTimeout(() => setSaved(false), 3000)
if (!pending && typeof data.purged === "number" && data.purged > 0) {
setLastPurged(data.purged)
}
// Notify the Notifications section so it hides/shows the
// lxc_updates_available toggle in real time.
if (typeof window !== "undefined") {
window.dispatchEvent(
new CustomEvent("proxmenux:lxc-detection-changed", { detail: { enabled: pending } }),
)
}
} catch (e) {
setError(String(e))
} finally {
setSaving(false)
}
}
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Boxes className="h-5 w-5 text-purple-500" />
<CardTitle>LXC Update Detection</CardTitle>
{enabled ? (
<Badge variant="outline" className="text-[10px] border-green-500/30 text-green-500">
Active
</Badge>
) : (
<Badge variant="outline" className="text-[10px] border-muted-foreground/30 text-muted-foreground">
Disabled
</Badge>
)}
</div>
<div className="flex items-center gap-2">
{saved && (
<span className="flex items-center gap-1 text-xs text-green-500">
<CheckCircle2 className="h-3.5 w-3.5" />
Saved
</span>
)}
{error && !editMode && (
<span
className="flex items-center gap-1 text-xs text-red-500 max-w-[40ch] truncate"
title={error}
>
Save failed: {error}
</span>
)}
{editMode ? (
<>
<button
className="h-7 px-3 text-xs rounded-md border border-border bg-background hover:bg-muted transition-colors text-muted-foreground"
onClick={handleCancel}
disabled={saving}
>
Cancel
</button>
<button
className="h-7 px-3 text-xs rounded-md bg-blue-600 hover:bg-blue-700 text-white transition-colors disabled:opacity-50 flex items-center gap-1.5"
onClick={handleSave}
disabled={saving || !hasChanges}
>
{saving ? <Loader2 className="h-3 w-3 animate-spin" /> : <CheckCircle2 className="h-3 w-3" />}
Save
</button>
</>
) : (
<button
className="h-7 px-3 text-xs rounded-md border border-border bg-background hover:bg-muted transition-colors flex items-center gap-1.5"
onClick={handleEdit}
disabled={loading}
>
<Settings2 className="h-3 w-3" />
Edit
</button>
)}
</div>
</div>
<CardDescription>
Periodically check running Debian/Ubuntu/Alpine LXC containers for pending package updates
(<code>apt list --upgradable</code> / <code>apk list -u</code>) and surface them on the dashboard. The
corresponding notification toggle in <strong>Notifications Services</strong> appears only while detection
is enabled.
</CardDescription>
</CardHeader>
<CardContent className="space-y-5">
{/* ── Enable/Disable ── */}
<div className="flex items-center justify-between py-2 px-1">
<div className="flex items-center gap-2">
<Boxes
className={`h-4 w-4 ${pending ? "text-purple-500" : "text-muted-foreground"}`}
/>
<div>
<span className="text-sm font-medium">Enable LXC update detection</span>
<p className="text-[11px] text-muted-foreground">
When OFF, ProxMenux stops scanning your CTs (no <code>pct exec</code> calls), removes existing LXC
entries from the managed-installs registry, and hides the related notification toggle. Default is
ON.
</p>
</div>
</div>
<button
className={`relative w-10 h-5 rounded-full transition-colors ${
pending ? "bg-blue-600" : "bg-muted-foreground/20 border border-muted-foreground/40"
} ${!editMode ? "opacity-60 cursor-not-allowed" : "cursor-pointer"}`}
onClick={() => editMode && setPending(p => !p)}
disabled={!editMode || saving}
role="switch"
aria-checked={pending}
aria-label="Enable LXC update detection"
>
<span
className={`absolute top-0.5 left-0.5 h-4 w-4 rounded-full bg-white shadow transition-transform ${
pending ? "translate-x-5" : "translate-x-0"
}`}
/>
</button>
</div>
{lastPurged !== null && lastPurged > 0 && (
<div className="flex items-start gap-2 p-3 rounded-lg bg-muted/50 border border-border">
<Info className="h-3.5 w-3.5 text-blue-400 shrink-0 mt-0.5" />
<p className="text-[11px] text-muted-foreground leading-relaxed">
{lastPurged} LXC entries removed from the registry. Re-enabling detection will repopulate them on the
next scan cycle.
</p>
</div>
)}
{error && editMode && (
<div className="flex items-start gap-2 p-3 rounded-lg bg-amber-500/10 border border-amber-500/30">
<Info className="h-3.5 w-3.5 text-amber-400 shrink-0 mt-0.5" />
<p className="text-[11px] text-amber-500 leading-relaxed break-all">{error}</p>
</div>
)}
</CardContent>
</Card>
)
}
+68 -7
View File
@@ -351,6 +351,12 @@ export function NotificationSettings() {
error: string
}>({ status: "idle", fallback_commands: [], error: "" })
const [systemHostname, setSystemHostname] = useState<string>("")
// Mirrors the dedicated toggle from Settings → LXC Update Detection.
// When false, the per-event toggle for `lxc_updates_available` is hidden
// from every channel's category list (its DB preference is preserved).
// Updated on mount via fetch and on the fly via a CustomEvent dispatched
// by <LxcUpdateDetection /> when the user flips the switch.
const [lxcDetectionEnabled, setLxcDetectionEnabled] = useState<boolean>(true)
// Load system hostname for display name placeholder
const loadSystemHostname = useCallback(async () => {
@@ -433,6 +439,43 @@ export function NotificationSettings() {
loadSystemHostname()
}, [loadConfig, loadStatus, loadSystemHostname])
// Track the LXC update-detection toggle so we can conditionally hide
// the `lxc_updates_available` per-event toggle inside every channel's
// category list. Fetched once on mount; live updates ride on a custom
// event dispatched by <LxcUpdateDetection /> whenever the user flips
// the switch upstream.
useEffect(() => {
let cancelled = false
fetchApi<{ success: boolean; enabled?: boolean }>("/api/lxc-updates/detection")
.then(data => {
if (cancelled) return
if (data.success && typeof data.enabled === "boolean") {
setLxcDetectionEnabled(data.enabled)
}
})
.catch(() => {
// Default-true on fetch failure — matches the backend default and
// avoids hiding a notification toggle the user might rely on if
// the settings endpoint is transiently unreachable.
})
const handler = (e: Event) => {
const detail = (e as CustomEvent).detail
if (detail && typeof detail.enabled === "boolean") {
setLxcDetectionEnabled(detail.enabled)
}
}
if (typeof window !== "undefined") {
window.addEventListener("proxmenux:lxc-detection-changed", handler)
}
return () => {
cancelled = true
if (typeof window !== "undefined") {
window.removeEventListener("proxmenux:lxc-detection-changed", handler)
}
}
}, [])
useEffect(() => {
if (showHistory) loadHistory()
}, [showHistory, loadHistory])
@@ -634,7 +677,16 @@ export function NotificationSettings() {
{EVENT_CATEGORIES.filter(cat => cat.key !== "other").map(cat => {
const isEnabled = overrides.categories[cat.key] ?? true
const isExpanded = expandedCategories.has(`${chName}.${cat.key}`)
const eventsForGroup = evtByGroup[cat.key] || []
// Hide the LXC update toggle when the user has disabled the
// dedicated detection setting upstream. The backend still
// returns the event type in the catalog (so its stored
// preference survives), but we filter it out of every
// channel's UI list so the operator never sees a notification
// toggle whose underlying scan is paused.
const rawEventsForGroup = evtByGroup[cat.key] || []
const eventsForGroup = lxcDetectionEnabled
? rawEventsForGroup
: rawEventsForGroup.filter(e => e.type !== "lxc_updates_available")
const enabledCount = eventsForGroup.filter(
e => (overrides.events?.[e.type] ?? e.default_enabled)
).length
@@ -1779,14 +1831,23 @@ export function NotificationSettings() {
<div>
<div className="flex items-center justify-between py-1">
<button
className="flex items-center gap-2 text-xs text-muted-foreground hover:text-foreground transition-colors"
className="flex items-center gap-2 text-sm text-foreground hover:bg-muted/60 rounded-md px-2 py-1.5 -mx-2 transition-colors"
onClick={() => setShowAdvanced(!showAdvanced)}
>
{showAdvanced ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
<span className="font-medium uppercase tracking-wider">Advanced: AI Enhancement</span>
{config.ai_enabled && (
<Badge variant="outline" className="text-[9px] border-purple-500/30 text-purple-400 ml-1">
ON
{showAdvanced ? (
<ChevronUp className="h-4 w-4 text-muted-foreground" />
) : (
<ChevronDown className="h-4 w-4 text-muted-foreground" />
)}
<Sparkles className="h-4 w-4 text-purple-400" />
<span className="font-medium">AI Enhancement</span>
{config.ai_enabled ? (
<Badge variant="outline" className="text-[10px] border-purple-500/40 text-purple-400 ml-1">
Active
</Badge>
) : (
<Badge variant="outline" className="text-[10px] border-border text-muted-foreground ml-1">
Optional
</Badge>
)}
</button>
+7
View File
@@ -5,6 +5,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/
import { Wrench, Package, Ruler, HeartPulse, Cpu, MemoryStick, HardDrive, CircleDot, Network, Server, Settings2, FileText, RefreshCw, Shield, AlertTriangle, Info, Loader2, Check, Database, CloudOff, Code, X, Copy, Sparkles, ArrowUpCircle } from "lucide-react"
import { NotificationSettings } from "./notification-settings"
import { HealthThresholds } from "./health-thresholds"
import { LxcUpdateDetection } from "./lxc-update-detection"
import { ScriptTerminalModal } from "./script-terminal-modal"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
import { Switch } from "./ui/switch"
@@ -1194,6 +1195,12 @@ export function Settings() {
values configured here drive what triggers the notifications below. */}
<HealthThresholds />
{/* LXC Update Detection — gates the per-CT apt/apk scan. When OFF,
the matching toggle in NotificationSettings is hidden (the
preference is preserved in the DB and reappears when detection
is re-enabled). */}
<LxcUpdateDetection />
{/* Notification Settings */}
<NotificationSettings />