mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-18 01:52:20 +00:00
Update notification service
This commit is contained in:
@@ -30,6 +30,7 @@ import {
|
||||
ChevronRight,
|
||||
Settings2,
|
||||
HelpCircle,
|
||||
Usb,
|
||||
} from "lucide-react"
|
||||
|
||||
interface CategoryCheck {
|
||||
@@ -414,13 +415,44 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu
|
||||
) => {
|
||||
if (!checks || Object.keys(checks).length === 0) return null
|
||||
|
||||
// Sort checks: non-disk entries first, then disk entries sorted by device name
|
||||
const sortedEntries = Object.entries(checks)
|
||||
.filter(([, checkData]) => checkData.installed !== false)
|
||||
.sort(([keyA, dataA], [keyB, dataB]) => {
|
||||
const isDiskA = dataA.is_disk_entry === true
|
||||
const isDiskB = dataB.is_disk_entry === true
|
||||
if (isDiskA && !isDiskB) return 1
|
||||
if (!isDiskA && isDiskB) return -1
|
||||
if (isDiskA && isDiskB) {
|
||||
// Sort disks by device name
|
||||
const deviceA = dataA.device || keyA
|
||||
const deviceB = dataB.device || keyB
|
||||
return deviceA.localeCompare(deviceB)
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="mt-2 space-y-0.5">
|
||||
{Object.entries(checks)
|
||||
.filter(([, checkData]) => checkData.installed !== false)
|
||||
.map(([checkKey, checkData]) => {
|
||||
{sortedEntries.map(([checkKey, checkData]) => {
|
||||
const isDismissable = checkData.dismissable === true
|
||||
const checkStatus = checkData.status?.toUpperCase() || "OK"
|
||||
const isDiskEntry = checkData.is_disk_entry === true
|
||||
|
||||
// For disk entries, format label specially
|
||||
let displayLabel = formatCheckLabel(checkKey)
|
||||
let diskIcon = null
|
||||
if (isDiskEntry) {
|
||||
displayLabel = checkData.device || checkKey.replace(/_/g, '/')
|
||||
const diskType = checkData.disk_type || ''
|
||||
if (diskType === 'USB') {
|
||||
diskIcon = <Usb className="h-3 w-3 text-orange-400 mr-1" />
|
||||
} else if (diskType === 'NVMe') {
|
||||
diskIcon = <HardDrive className="h-3 w-3 text-blue-400 mr-1" />
|
||||
} else {
|
||||
diskIcon = <HardDrive className="h-3 w-3 text-muted-foreground mr-1" />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -429,7 +461,15 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu
|
||||
>
|
||||
<div className="flex items-start gap-1.5 sm:gap-2 min-w-0 flex-1">
|
||||
<span className="mt-0.5 shrink-0">{getStatusIcon(checkData.dismissed ? "INFO" : checkData.status, "sm")}</span>
|
||||
<span className="font-medium shrink-0">{formatCheckLabel(checkKey)}</span>
|
||||
<span className="font-medium shrink-0 flex items-center">
|
||||
{diskIcon}
|
||||
{displayLabel}
|
||||
{isDiskEntry && checkData.disk_type && (
|
||||
<Badge variant="outline" className="ml-1.5 text-[8px] px-1 py-0 h-3.5 shrink-0">
|
||||
{checkData.disk_type}
|
||||
</Badge>
|
||||
)}
|
||||
</span>
|
||||
<span className="text-muted-foreground break-words whitespace-pre-wrap min-w-0">{checkData.detail}</span>
|
||||
{checkData.dismissed && (
|
||||
<Badge variant="outline" className="text-[9px] px-1 py-0 h-4 shrink-0 text-blue-400 border-blue-400/30">
|
||||
@@ -459,6 +499,7 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -169,8 +169,8 @@ const generateLatencyReport = (report: ReportData) => {
|
||||
endTime: new Date(report.data[report.data.length - 1].timestamp * 1000).toLocaleString(),
|
||||
} : null
|
||||
|
||||
// Build history table rows for gateway mode (last 24 records)
|
||||
const historyTableRows = report.data.slice(-24).map((d, i) => `
|
||||
// Build history table rows for gateway mode (last 20 records)
|
||||
const historyTableRows = report.data.slice(-20).map((d, i) => `
|
||||
<tr${d.packet_loss && d.packet_loss > 0 ? ' class="warn"' : ''}>
|
||||
<td>${i + 1}</td>
|
||||
<td>${new Date(d.timestamp * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</td>
|
||||
@@ -614,7 +614,7 @@ const generateLatencyReport = (report: ReportData) => {
|
||||
${!report.isRealtime && report.data.length > 0 ? `
|
||||
<!-- 5. Detailed History (for Gateway) -->
|
||||
<div class="section">
|
||||
<div class="section-title">5. Latency History (Last ${Math.min(24, report.data.length)} Records)</div>
|
||||
<div class="section-title">5. Latency History (Last ${Math.min(20, report.data.length)} Records)</div>
|
||||
<table class="chk-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -1016,34 +1016,59 @@ export function StorageOverview() {
|
||||
className="sm:hidden border border-white/10 rounded-lg p-4 cursor-pointer bg-white/5 transition-colors"
|
||||
onClick={() => handleDiskClick(disk)}
|
||||
>
|
||||
<div className="space-y-2 mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Usb className="h-5 w-5 text-orange-400 flex-shrink-0" />
|
||||
<h3 className="font-semibold">/dev/{disk.name}</h3>
|
||||
<Badge className="bg-orange-500/10 text-orange-400 border-orange-500/20 text-[10px] px-1.5">USB</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3 pl-7">
|
||||
{disk.model && disk.model !== "Unknown" && (
|
||||
<p className="text-sm text-muted-foreground truncate flex-1 min-w-0">{disk.model}</p>
|
||||
)}
|
||||
<div className="flex items-center gap-3 flex-shrink-0">
|
||||
<div className="space-y-3">
|
||||
{/* Header row */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Usb className="h-5 w-5 text-orange-400 flex-shrink-0" />
|
||||
<h3 className="font-semibold">/dev/{disk.name}</h3>
|
||||
<Badge className="bg-orange-500/10 text-orange-400 border-orange-500/20 text-[10px] px-1.5">USB</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{disk.temperature > 0 && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Thermometer className={`h-4 w-4 ${getTempColor(disk.temperature, disk.name, disk.rotation_rate)}`} />
|
||||
<span className={`text-sm font-medium ${getTempColor(disk.temperature, disk.name, disk.rotation_rate)}`}>
|
||||
<Thermometer className={`h-3.5 w-3.5 ${getTempColor(disk.temperature, disk.name, disk.rotation_rate)}`} />
|
||||
<span className={`text-xs font-medium ${getTempColor(disk.temperature, disk.name, disk.rotation_rate)}`}>
|
||||
{disk.temperature}°C
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{getHealthBadge(disk.health)}
|
||||
{(disk.observations_count ?? 0) > 0 && (
|
||||
<Badge className="bg-blue-500/10 text-blue-400 border-blue-500/20 gap-1 text-[10px] px-1.5 py-0">
|
||||
<Info className="h-3 w-3" />
|
||||
{disk.observations_count}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Model if available */}
|
||||
{disk.model && disk.model !== "Unknown" && (
|
||||
<p className="text-sm text-muted-foreground truncate pl-7">{disk.model}</p>
|
||||
)}
|
||||
|
||||
{/* Info grid - 2 columns */}
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-2 pl-7 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Size</span>
|
||||
<p className="font-medium">{disk.size || "N/A"}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">SMART Status</span>
|
||||
<p className="font-medium">{disk.smart_status || "N/A"}</p>
|
||||
</div>
|
||||
{disk.serial && disk.serial !== "Unknown" && (
|
||||
<div className="col-span-2">
|
||||
<span className="text-muted-foreground">Serial</span>
|
||||
<p className="font-medium text-xs truncate">{disk.serial}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Observations badge if any */}
|
||||
{(disk.observations_count ?? 0) > 0 && (
|
||||
<div className="pl-7">
|
||||
<Badge className="bg-blue-500/10 text-blue-400 border-blue-500/20 gap-1 text-[10px] px-1.5 py-0">
|
||||
<Info className="h-3 w-3" />
|
||||
{disk.observations_count} observation{disk.observations_count > 1 ? 's' : ''}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user