mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +00:00
Show linked node addresses as text with an edit button
- Each linked node shows its Monitor address as plain text, marked Automatic when it was detected - A pencil button opens the address field with Save and Cancel only when it needs changing - The node cards keep the dialog background so the editable fields stand out - The Add button keeps white text when disabled - New texts translated into all languages
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import useSWR, { mutate as refreshCache } from "swr"
|
||||
import { ArrowLeft, ChevronDown, DatabaseBackup, ExternalLink, Globe, Plus, Server, Trash2 } from "lucide-react"
|
||||
import { ArrowLeft, ChevronDown, DatabaseBackup, ExternalLink, Globe, Pencil, Plus, Server, Trash2 } from "lucide-react"
|
||||
import { Button } from "./ui/button"
|
||||
import { Input } from "./ui/input"
|
||||
import { Label } from "./ui/label"
|
||||
@@ -280,6 +280,7 @@ function ManageLinksDialog({ open, onOpenChange }: { open: boolean; onOpenChange
|
||||
const { data, mutate } = useSWR<MonitorLinks>("/api/monitor-links?all=1", fetcher, { revalidateOnFocus: false })
|
||||
const [form, setForm] = useState({ name: "", url: "", product: "pve" })
|
||||
const [addresses, setAddresses] = useState<Record<string, string>>({})
|
||||
const [editing, setEditing] = useState<string | null>(null)
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [error, setError] = useState("")
|
||||
|
||||
@@ -304,7 +305,7 @@ function ManageLinksDialog({ open, onOpenChange }: { open: boolean; onOpenChange
|
||||
<DialogDescription>{t("monitorLinks.dialogDescription")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3 rounded-lg border border-border bg-card p-4">
|
||||
<div className="space-y-3 rounded-lg border border-border p-4">
|
||||
<p className="text-sm font-medium">{t("monitorLinks.addTitle")}</p>
|
||||
<div className="grid gap-3 sm:grid-cols-[1fr_1.5fr_auto]">
|
||||
<div className="space-y-1.5">
|
||||
@@ -327,7 +328,7 @@ function ManageLinksDialog({ open, onOpenChange }: { open: boolean; onOpenChange
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<Button size="sm" className="bg-blue-600 text-white hover:bg-blue-700" disabled={busy || !form.url.trim()} onClick={() => run(async () => {
|
||||
<Button size="sm" className="bg-blue-600 text-white hover:bg-blue-700 disabled:bg-blue-600/50 disabled:text-white disabled:opacity-100" disabled={busy || !form.url.trim()} onClick={() => run(async () => {
|
||||
await send("/api/monitor-links", "POST", form)
|
||||
setForm({ name: "", url: "", product: "pve" })
|
||||
})}><Plus className="mr-2 h-4 w-4" />{t("monitorLinks.add")}</Button>
|
||||
@@ -338,8 +339,12 @@ function ManageLinksDialog({ open, onOpenChange }: { open: boolean; onOpenChange
|
||||
<div className="space-y-3">
|
||||
{data?.links.length === 0 && <p className="text-sm text-muted-foreground">{t("monitorLinks.empty")}</p>}
|
||||
{data?.links.map(link => {
|
||||
const address = addresses[link.key] ?? (link.manual || link.custom_url ? link.url || "" : "")
|
||||
return <div key={link.key} className={`space-y-2 rounded-lg border border-border bg-card p-3 ${link.hidden ? "opacity-60" : ""}`}>
|
||||
const own = link.manual || link.custom_url
|
||||
const shown = link.url || ""
|
||||
const draft = addresses[link.key] ?? (own ? shown : "")
|
||||
const isEditing = editing === link.key
|
||||
// A read-only card is a panel; while its address is edited it lets the field stand out.
|
||||
return <div key={link.key} className={`space-y-2 rounded-lg border border-border p-3 ${isEditing ? "" : "bg-card"} ${link.hidden ? "opacity-60" : ""}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<ProductIcon product={link.product} className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="min-w-0 flex-1 truncate font-medium">{link.name}</span>
|
||||
@@ -354,16 +359,29 @@ function ManageLinksDialog({ open, onOpenChange }: { open: boolean; onOpenChange
|
||||
<Trash2 className="h-4 w-4 text-red-500" />
|
||||
</Button>}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Input value={address} disabled={busy} aria-label={t("monitorLinks.address")}
|
||||
placeholder={link.manual ? "" : `${t("monitorLinks.autoAddress")}${link.url ? `: ${link.url}` : ""}`}
|
||||
onChange={event => setAddresses({ ...addresses, [link.key]: event.target.value })} />
|
||||
<Button variant="outline" size="sm" className="h-10" disabled={busy || addresses[link.key] === undefined}
|
||||
onClick={() => run(async () => {
|
||||
await send(`/api/monitor-links/${encodeURIComponent(link.key)}`, "PATCH", { url: addresses[link.key] })
|
||||
setAddresses(({ [link.key]: _, ...rest }) => rest)
|
||||
})}>{t("monitorLinks.save")}</Button>
|
||||
</div>
|
||||
{isEditing
|
||||
? <div className="flex gap-2">
|
||||
<Input value={draft} disabled={busy} autoFocus aria-label={t("monitorLinks.address")}
|
||||
placeholder={link.manual ? "" : `${t("monitorLinks.autoAddress")}${shown ? `: ${shown}` : ""}`}
|
||||
onChange={event => setAddresses({ ...addresses, [link.key]: event.target.value })} />
|
||||
<Button size="sm" className="h-10 bg-blue-600 text-white hover:bg-blue-700 disabled:bg-blue-600/50 disabled:text-white disabled:opacity-100"
|
||||
disabled={busy || addresses[link.key] === undefined || (link.manual && !draft.trim())}
|
||||
onClick={() => run(async () => {
|
||||
await send(`/api/monitor-links/${encodeURIComponent(link.key)}`, "PATCH", { url: addresses[link.key] })
|
||||
setAddresses(({ [link.key]: _, ...rest }) => rest)
|
||||
setEditing(null)
|
||||
})}>{t("monitorLinks.save")}</Button>
|
||||
<Button variant="outline" size="sm" className="h-10" disabled={busy}
|
||||
onClick={() => { setAddresses(({ [link.key]: _, ...rest }) => rest); setEditing(null) }}>{t("monitorLinks.cancel")}</Button>
|
||||
</div>
|
||||
: <div className="flex items-center gap-2">
|
||||
<span className="min-w-0 flex-1 truncate font-mono text-sm text-foreground">{shown || "—"}</span>
|
||||
{!link.manual && !link.custom_url && <span className="shrink-0 rounded border border-border px-1.5 py-0.5 text-[11px] text-muted-foreground">{t("monitorLinks.autoAddress")}</span>}
|
||||
<Button variant="ghost" size="sm" disabled={busy} aria-label={t("monitorLinks.changeAddress")} title={t("monitorLinks.changeAddress")}
|
||||
onClick={() => setEditing(link.key)}>
|
||||
<Pencil className="h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</div>}
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -7379,6 +7379,8 @@
|
||||
"knownTitle": "Verknüpfte Knoten hinzufügen?",
|
||||
"knownBody": "{name} verknüpft diese Monitore. Wählen Sie aus, welche Sie diesem Monitor hinzufügen möchten.",
|
||||
"addSelected": "Auswahl hinzufügen",
|
||||
"pbsWebInterface": "PBS-Weboberfläche"
|
||||
"pbsWebInterface": "PBS-Weboberfläche",
|
||||
"changeAddress": "Adresse ändern",
|
||||
"cancel": "Abbrechen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6587,6 +6587,8 @@
|
||||
"knownTitle": "Add linked nodes?",
|
||||
"knownBody": "{name} links these Monitors. Choose the ones to add to this Monitor.",
|
||||
"addSelected": "Add selected",
|
||||
"pbsWebInterface": "PBS web interface"
|
||||
"pbsWebInterface": "PBS web interface",
|
||||
"changeAddress": "Change address",
|
||||
"cancel": "Cancel"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7379,6 +7379,8 @@
|
||||
"knownTitle": "¿Añadir nodos enlazados?",
|
||||
"knownBody": "{name} enlaza estos Monitores. Elige los que quieres añadir a este Monitor.",
|
||||
"addSelected": "Añadir seleccionados",
|
||||
"pbsWebInterface": "Interfaz web de PBS"
|
||||
"pbsWebInterface": "Interfaz web de PBS",
|
||||
"changeAddress": "Cambiar dirección",
|
||||
"cancel": "Cancelar"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7379,6 +7379,8 @@
|
||||
"knownTitle": "Ajouter des nœuds liés ?",
|
||||
"knownBody": "{name} relie ces moniteurs. Choisissez ceux à ajouter à ce moniteur.",
|
||||
"addSelected": "Ajouter la sélection",
|
||||
"pbsWebInterface": "Interface web de PBS"
|
||||
"pbsWebInterface": "Interface web de PBS",
|
||||
"changeAddress": "Modifier l'adresse",
|
||||
"cancel": "Annuler"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7379,6 +7379,8 @@
|
||||
"knownTitle": "Aggiungere nodi collegati?",
|
||||
"knownBody": "{name} collega questi monitor. Scegli quelli da aggiungere a questo monitor.",
|
||||
"addSelected": "Aggiungi selezionati",
|
||||
"pbsWebInterface": "Interfaccia web di PBS"
|
||||
"pbsWebInterface": "Interfaccia web di PBS",
|
||||
"changeAddress": "Cambia indirizzo",
|
||||
"cancel": "Annulla"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7379,6 +7379,8 @@
|
||||
"knownTitle": "Adicionar nós vinculados?",
|
||||
"knownBody": "{name} liga estes Monitores. Escolha os que quer adicionar a este Monitor.",
|
||||
"addSelected": "Adicionar selecionados",
|
||||
"pbsWebInterface": "Interface web do PBS"
|
||||
"pbsWebInterface": "Interface web do PBS",
|
||||
"changeAddress": "Alterar endereço",
|
||||
"cancel": "Cancelar"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7378,6 +7378,8 @@
|
||||
"knownTitle": "Pridať prepojené uzly?",
|
||||
"knownBody": "{name} prepája tieto monitory. Vyberte tie, ktoré chcete pridať do tohto monitora.",
|
||||
"addSelected": "Pridať vybrané",
|
||||
"pbsWebInterface": "Webové rozhranie PBS"
|
||||
"pbsWebInterface": "Webové rozhranie PBS",
|
||||
"changeAddress": "Zmeniť adresu",
|
||||
"cancel": "Zrušiť"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7379,6 +7379,8 @@
|
||||
"knownTitle": "Lägga till länkade noder?",
|
||||
"knownBody": "{name} länkar dessa monitorer. Välj vilka du vill lägga till på denna monitor.",
|
||||
"addSelected": "Lägg till valda",
|
||||
"pbsWebInterface": "PBS webbgränssnitt"
|
||||
"pbsWebInterface": "PBS webbgränssnitt",
|
||||
"changeAddress": "Ändra adress",
|
||||
"cancel": "Avbryt"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user