feat: multi-source disk entries with chips display

diskUsage now carries Sources []string instead of a single Source.
collectDiskUsage accumulates all sources (samba, nfs, manual) per path.
NFS no longer adds a redundant label to used_by.

Dashboard and Settings render one chip per source. When a path
is shared via SMB and NFS, both chips appear.

API breaking: disks[].source replaced by disks[].sources[].
Version: 0.2.1 -> 0.3.0
This commit is contained in:
2026-07-06 00:40:41 -04:00
parent a24145e07a
commit 508890a232
5 changed files with 79 additions and 31 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ export interface DiskUsage {
free_bytes: number;
used_bytes: number;
used_percent: number;
source: "system" | "mount" | "samba" | "nfs" | "manual";
sources: ("manual" | "samba" | "nfs")[];
used_by?: string[];
available: boolean;
error?: string;
+29 -4
View File
@@ -2,6 +2,18 @@ import { useEffect, useState } from "react";
import { api, formatBytes, SystemStatus } from "../api";
import { useDirty } from "../DirtyContext";
const SOURCE_COLORS: Record<string, string> = {
samba: "bg-emerald-500/20 text-emerald-300",
nfs: "bg-amber-500/20 text-amber-300",
manual: "bg-cyan-500/20 text-cyan-300",
};
const SOURCE_LABELS: Record<string, string> = {
samba: "SMB",
nfs: "NFS",
manual: "Vigilado",
};
export default function Dashboard() {
const [status, setStatus] = useState<SystemStatus | null>(null);
const { modules } = useDirty();
@@ -11,7 +23,7 @@ export default function Dashboard() {
}, []);
const visibleDisks = status?.disks.filter(
(d) => d.source === "manual" || d.source === "samba" || d.source === "nfs"
(d) => d.sources.includes("manual") || d.sources.includes("samba") || d.sources.includes("nfs")
) ?? [];
return (
@@ -27,9 +39,22 @@ export default function Dashboard() {
<div className="space-y-4">
{visibleDisks.map((d) => (
<div key={d.path}>
<div className="mb-1 flex justify-between text-sm">
<span className="text-slate-300">{d.path}</span>
<span className="text-slate-400">
<div className="mb-1 flex flex-wrap items-center justify-between gap-x-3 gap-y-1 text-sm">
<div className="flex flex-wrap items-center gap-2">
<span className="text-slate-300">{d.path}</span>
{d.sources.map((s) => (
<span
key={s}
className={`rounded-full px-2 py-0.5 text-xs ${SOURCE_COLORS[s] ?? "bg-slate-700 text-slate-300"}`}
>
{SOURCE_LABELS[s] ?? s}
</span>
))}
{d.used_by && d.used_by.length > 0 && (
<span className="text-xs text-slate-500">{d.used_by.join(", ")}</span>
)}
</div>
<span className="text-slate-400 text-xs">
{formatBytes(d.used_bytes)} / {formatBytes(d.total_bytes)}
</span>
</div>
+10 -5
View File
@@ -23,9 +23,14 @@ function DiskUsageItem({ disk }: { disk: SystemStatus["disks"][number] }) {
<div className="mb-1 flex flex-wrap items-center justify-between gap-x-3 gap-y-1 text-sm">
<div className="flex flex-wrap items-center gap-2">
<span className="text-slate-300">{disk.path}</span>
<span className={`rounded-full px-2 py-0.5 text-xs ${SOURCE_COLORS[disk.source] ?? "bg-slate-700 text-slate-300"}`}>
{SOURCE_LABELS[disk.source] ?? disk.source}
</span>
{disk.sources.map((s) => (
<span
key={s}
className={`rounded-full px-2 py-0.5 text-xs ${SOURCE_COLORS[s] ?? "bg-slate-700 text-slate-300"}`}
>
{SOURCE_LABELS[s] ?? s}
</span>
))}
{disk.used_by && disk.used_by.length > 0 && (
<div className="flex flex-wrap gap-1">
{disk.used_by.map((name) => (
@@ -125,8 +130,8 @@ export default function Settings() {
setWatched(prev => prev.filter(m => m.id !== id));
}
const manualDisks = status?.disks.filter(d => d.source === "manual") ?? [];
const shareDisks = status?.disks.filter(d => d.source === "samba" || d.source === "nfs") ?? [];
const manualDisks = status?.disks.filter(d => d.sources.includes("manual")) ?? [];
const shareDisks = status?.disks.filter(d => d.sources.includes("samba") || d.sources.includes("nfs")) ?? [];
return (
<div className="space-y-6">