feat(setup): reveal toggle for secret fields (the device web password)
The admin needs the device web password (to reach a controller/camera's own web UI), and it's already stored + sent to this admin-only view (redactSecrets strips only the machine secrets relay/push pw, NOT webPassword — by design, per the SECRET_CONFIG_KEYS comment). But the form rendered every `secret` field as a masked password input with no way to unmask it, so the value was present yet unreadable. Add a per-field show/hide eye toggle on `secret` inputs. No new exposure: the field is already admin-gated and the value already reaches the client; this just makes the intended-visible credential readable/copyable. Machine secrets are redacted server-side and never arrive, so there's nothing there to reveal. i18n sq+en.
This commit is contained in:
@@ -387,6 +387,13 @@ function DeviceForm({
|
|||||||
const [printResult, setPrintResult] = useState<PrintTestResult | null>(null);
|
const [printResult, setPrintResult] = useState<PrintTestResult | null>(null);
|
||||||
const [printTesting, setPrintTesting] = useState(false);
|
const [printTesting, setPrintTesting] = useState(false);
|
||||||
const [printError, setPrintError] = useState<string | null>(null);
|
const [printError, setPrintError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Which `secret` fields are currently unmasked. The device web password is an
|
||||||
|
// operational credential the admin legitimately needs (to reach the device's web
|
||||||
|
// UI) — it's stored + sent to this admin-only view; a per-field reveal toggle just
|
||||||
|
// makes the already-present value readable. (Machine secrets — relay/push pw — are
|
||||||
|
// redacted server-side and never reach here, so there's nothing to reveal.)
|
||||||
|
const [revealed, setRevealed] = useState<Record<string, boolean>>({});
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [saveError, setSaveError] = useState<string | null>(null);
|
const [saveError, setSaveError] = useState<string | null>(null);
|
||||||
const [found, setFound] = useState<DiscoveredDevice[] | null>(null);
|
const [found, setFound] = useState<DiscoveredDevice[] | null>(null);
|
||||||
@@ -688,10 +695,36 @@ function DeviceForm({
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
) : f.type === "secret" ? (
|
||||||
|
// Secret field with a reveal toggle: the device web password is shown
|
||||||
|
// here (admin-only view) so an admin can read/copy it to reach the
|
||||||
|
// device's own web UI. Masked by default; click the eye to reveal.
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<input
|
||||||
|
className="input flex-1"
|
||||||
|
type={revealed[f.key] ? "text" : "password"}
|
||||||
|
value={(config[f.key] ?? (f.default as string | number | undefined) ?? "") as string | number}
|
||||||
|
placeholder={f.help}
|
||||||
|
onChange={(e) => {
|
||||||
|
const v = e.target.value;
|
||||||
|
setConfig((c) => ({ ...c, [f.key]: v }));
|
||||||
|
resetStatus();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm"
|
||||||
|
aria-label={revealed[f.key] ? t("setup.hideSecret") : t("setup.revealSecret")}
|
||||||
|
title={revealed[f.key] ? t("setup.hideSecret") : t("setup.revealSecret")}
|
||||||
|
onClick={() => setRevealed((r) => ({ ...r, [f.key]: !r[f.key] }))}
|
||||||
|
>
|
||||||
|
{revealed[f.key] ? "🙈" : "👁"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<input
|
<input
|
||||||
className="input"
|
className="input"
|
||||||
type={f.type === "secret" ? "password" : f.type === "number" || f.type === "port" ? "number" : "text"}
|
type={f.type === "number" || f.type === "port" ? "number" : "text"}
|
||||||
value={(config[f.key] ?? (f.default as string | number | undefined) ?? "") as string | number}
|
value={(config[f.key] ?? (f.default as string | number | undefined) ?? "") as string | number}
|
||||||
placeholder={f.help}
|
placeholder={f.help}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|||||||
@@ -413,6 +413,9 @@ export const en: Catalog = {
|
|||||||
"Sends a test slip to the printer now. ‘Connected’ only opens the link — this confirms the printer actually feeds paper.",
|
"Sends a test slip to the printer now. ‘Connected’ only opens the link — this confirms the printer actually feeds paper.",
|
||||||
printOk: "✓ Test slip sent ({{ms}} ms). Check the printer.",
|
printOk: "✓ Test slip sent ({{ms}} ms). Check the printer.",
|
||||||
"printFail.print-failed": "The printer rejected the job (out of paper, cover open, or the link dropped).",
|
"printFail.print-failed": "The printer rejected the job (out of paper, cover open, or the link dropped).",
|
||||||
|
// Reveal/hide toggle for a secret field (e.g. the device web password).
|
||||||
|
revealSecret: "Show password",
|
||||||
|
hideSecret: "Hide password",
|
||||||
alarmUrlTitle: "Alarm Server settings (enter these in the camera)",
|
alarmUrlTitle: "Alarm Server settings (enter these in the camera)",
|
||||||
alarmUrlHint:
|
alarmUrlHint:
|
||||||
"Enter these in the camera at Configuration → Event → … → Alarm Settings (or Notify Surveillance Center). The camera POSTs every event here — no polling.",
|
"Enter these in the camera at Configuration → Event → … → Alarm Settings (or Notify Surveillance Center). The camera POSTs every event here — no polling.",
|
||||||
|
|||||||
@@ -423,6 +423,9 @@ export const sq = {
|
|||||||
"Dërgon një fletë prove te printeri tani. ‘I lidhur’ vetëm hap lidhjen — kjo konfirmon se printeri vërtet nxjerr letër.",
|
"Dërgon një fletë prove te printeri tani. ‘I lidhur’ vetëm hap lidhjen — kjo konfirmon se printeri vërtet nxjerr letër.",
|
||||||
printOk: "✓ Fleta e provës u dërgua ({{ms}} ms). Kontrollo printerin.",
|
printOk: "✓ Fleta e provës u dërgua ({{ms}} ms). Kontrollo printerin.",
|
||||||
"printFail.print-failed": "Printeri nuk pranoi punën (pa letër, kapaku hapur, ose lidhja ra).",
|
"printFail.print-failed": "Printeri nuk pranoi punën (pa letër, kapaku hapur, ose lidhja ra).",
|
||||||
|
// Reveal/hide toggle for a secret field (e.g. the device web password).
|
||||||
|
revealSecret: "Shfaq fjalëkalimin",
|
||||||
|
hideSecret: "Fshih fjalëkalimin",
|
||||||
// Alarm Server push settings — generated for the camera's Event → Alarm Server form.
|
// Alarm Server push settings — generated for the camera's Event → Alarm Server form.
|
||||||
alarmUrlTitle: "Cilësimet e Alarm Server (vendosi te kamera)",
|
alarmUrlTitle: "Cilësimet e Alarm Server (vendosi te kamera)",
|
||||||
alarmUrlHint:
|
alarmUrlHint:
|
||||||
|
|||||||
Reference in New Issue
Block a user