netx/web/src/pages/ForceChangePasswordPage.tsx
oliver 393fd0e0b4 Switch CLI parsing to TextFSM and polish topology/workbench UX.
Use custom cli_templates plus community ntc-templates for LLDP and port traffic across ZTE/Huawei/Cisco/H3C/Juniper/Nokia, drop regex parsers, and surface Admin/Phy/Prot on port discover. Also batch-add topology NEs and tighten workbench branding.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 23:06:40 +08:00

104 lines
3.2 KiB
TypeScript

import { useState, type FormEvent } from "react";
import { useAuth } from "../auth/AuthContext";
import { useI18n } from "../i18n";
import { apiPost } from "../services/api";
export function ForceChangePasswordPage() {
const { t } = useI18n();
const { user, refreshMe, logout } = useAuth();
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirm, setConfirm] = useState("");
const [error, setError] = useState("");
const [busy, setBusy] = useState(false);
const onSubmit = async (e: FormEvent) => {
e.preventDefault();
setError("");
if (newPassword.length < 6) {
setError(t("auth.passwordTooShort"));
return;
}
if (newPassword !== confirm) {
setError(t("auth.passwordMismatch"));
return;
}
if (newPassword === oldPassword || newPassword === "admin123") {
setError(t("auth.passwordMustChange"));
return;
}
setBusy(true);
try {
await apiPost("/v1/auth/change-password", {
old_password: oldPassword,
new_password: newPassword,
});
await refreshMe();
} catch (err) {
setError(String(err instanceof Error ? err.message : err));
} finally {
setBusy(false);
}
};
return (
<div className="login-page">
<form className="login-card" onSubmit={(e) => void onSubmit(e)}>
<div className="login-card__brand">NETX</div>
<h1 className="login-card__title">{t("auth.forceChangeTitle")}</h1>
<p className="login-card__hint">
{t("auth.forceChangeHint", { user: user?.username || "admin" })}
</p>
<label className="login-card__label">
{t("auth.oldPassword")}
<input
type="password"
autoComplete="current-password"
autoFocus
value={oldPassword}
onChange={(e) => setOldPassword(e.target.value)}
disabled={busy}
required
/>
</label>
<label className="login-card__label">
{t("auth.newPassword")}
<input
type="password"
autoComplete="new-password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
disabled={busy}
required
minLength={6}
/>
</label>
<label className="login-card__label">
{t("auth.confirmPassword")}
<input
type="password"
autoComplete="new-password"
value={confirm}
onChange={(e) => setConfirm(e.target.value)}
disabled={busy}
required
minLength={6}
/>
</label>
{error ? <div className="login-card__error">{error}</div> : null}
<button type="submit" className="login-card__submit" disabled={busy}>
{busy ? t("auth.savingPassword") : t("auth.savePassword")}
</button>
<button
type="button"
className="login-card__submit"
style={{ background: "#64748b" }}
disabled={busy}
onClick={() => void logout()}
>
{t("auth.logout")}
</button>
</form>
</div>
);
}