mirror of
https://github.com/hansjone/netx.git
synced 2026-10-11 00:32:39 +08:00
feat(web): poll and display UME CLI connect test results
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e58716c613
commit
39aad258e6
6 changed files with 113 additions and 4 deletions
|
|
@ -7,6 +7,7 @@ import {
|
||||||
fetchCliMeta,
|
fetchCliMeta,
|
||||||
fetchCliProfiles,
|
fetchCliProfiles,
|
||||||
fetchManagedNeMeta,
|
fetchManagedNeMeta,
|
||||||
|
fetchUmeCliOverride,
|
||||||
postUmeConnectTest,
|
postUmeConnectTest,
|
||||||
} from "../services/api";
|
} from "../services/api";
|
||||||
import { HopProxyFields, emptyHopProxyFields, type HopProxyFieldsState } from "./HopProxyFields";
|
import { HopProxyFields, emptyHopProxyFields, type HopProxyFieldsState } from "./HopProxyFields";
|
||||||
|
|
@ -14,7 +15,16 @@ import { HelpHint } from "./HelpHint";
|
||||||
import { queryKeys } from "../constants/queryKeys";
|
import { queryKeys } from "../constants/queryKeys";
|
||||||
import { useI18n } from "../i18n";
|
import { useI18n } from "../i18n";
|
||||||
import { useToast } from "../hooks/useToast";
|
import { useToast } from "../hooks/useToast";
|
||||||
import type { CliConnectProfileItem } from "../types";
|
import type { CliConnectProfileItem, UmeCliOverrideItem } from "../types";
|
||||||
|
import { formatSystemTime } from "../utils/time";
|
||||||
|
|
||||||
|
function connectPillLevel(status: string): "up" | "down" | "unknown" | "warn" {
|
||||||
|
const s = String(status || "").toLowerCase();
|
||||||
|
if (s === "pass" || s === "ok") return "up";
|
||||||
|
if (s === "fail" || s === "error") return "down";
|
||||||
|
if (s === "testing") return "warn";
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
type ProfileForm = {
|
type ProfileForm = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -73,10 +83,29 @@ export function UmeCliConnectPanel() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [form, setForm] = useState<ProfileForm>(emptyForm);
|
const [form, setForm] = useState<ProfileForm>(emptyForm);
|
||||||
const [sampleUmeNeId, setSampleUmeNeId] = useState("");
|
const [sampleUmeNeId, setSampleUmeNeId] = useState("");
|
||||||
|
const [testPending, setTestPending] = useState(false);
|
||||||
|
|
||||||
|
const testNeId = sampleUmeNeId.trim();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTestPending(false);
|
||||||
|
}, [testNeId]);
|
||||||
|
|
||||||
const metaQuery = useQuery({ queryKey: queryKeys.cliMeta, queryFn: fetchCliMeta });
|
const metaQuery = useQuery({ queryKey: queryKeys.cliMeta, queryFn: fetchCliMeta });
|
||||||
const neMetaQuery = useQuery({ queryKey: queryKeys.managedNeMeta, queryFn: fetchManagedNeMeta });
|
const neMetaQuery = useQuery({ queryKey: queryKeys.managedNeMeta, queryFn: fetchManagedNeMeta });
|
||||||
const profilesQuery = useQuery({ queryKey: queryKeys.cliProfiles, queryFn: fetchCliProfiles });
|
const profilesQuery = useQuery({ queryKey: queryKeys.cliProfiles, queryFn: fetchCliProfiles });
|
||||||
|
const overrideQuery = useQuery({
|
||||||
|
queryKey: queryKeys.umeCliOverride(testNeId),
|
||||||
|
queryFn: () => fetchUmeCliOverride(testNeId),
|
||||||
|
enabled: Boolean(testNeId),
|
||||||
|
refetchInterval: (q) => {
|
||||||
|
const status = String(q.state.data?.connect_status || "").toLowerCase();
|
||||||
|
return status === "testing" || testPending ? 2000 : false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const overrideResult: UmeCliOverrideItem | null = overrideQuery.data ?? null;
|
||||||
|
const overrideStatus = String(overrideResult?.connect_status || "").toLowerCase();
|
||||||
|
const overrideTesting = overrideStatus === "testing" || (testPending && !overrideResult?.connect_tested_at);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const items = profilesQuery.data?.items || [];
|
const items = profilesQuery.data?.items || [];
|
||||||
|
|
@ -130,12 +159,28 @@ export function UmeCliConnectPanel() {
|
||||||
if (!id) throw new Error(t("ume.cli.sampleNeRequired"));
|
if (!id) throw new Error(t("ume.cli.sampleNeRequired"));
|
||||||
return postUmeConnectTest([id]);
|
return postUmeConnectTest([id]);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
|
const id = sampleUmeNeId.trim();
|
||||||
|
setTestPending(true);
|
||||||
showOk(t("ume.cli.connectTestSubmitted"));
|
showOk(t("ume.cli.connectTestSubmitted"));
|
||||||
|
if (id) {
|
||||||
|
await queryClient.invalidateQueries({ queryKey: queryKeys.umeCliOverride(id) });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (e: Error) => {
|
||||||
|
setTestPending(false);
|
||||||
|
showError(e.message);
|
||||||
},
|
},
|
||||||
onError: (e: Error) => showError(e.message),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!testPending) return;
|
||||||
|
if (!overrideResult) return;
|
||||||
|
if (overrideStatus === "testing") return;
|
||||||
|
setTestPending(false);
|
||||||
|
void queryClient.invalidateQueries({ queryKey: queryKeys.cliTargetsAll });
|
||||||
|
}, [testPending, overrideResult, overrideStatus, queryClient]);
|
||||||
|
|
||||||
const deviceTypes = neMetaQuery.data?.device_types ?? [];
|
const deviceTypes = neMetaQuery.data?.device_types ?? [];
|
||||||
const vendors = neMetaQuery.data?.vendors ?? [];
|
const vendors = neMetaQuery.data?.vendors ?? [];
|
||||||
const cliReady = Boolean(metaQuery.data?.cli_profile_ready);
|
const cliReady = Boolean(metaQuery.data?.cli_profile_ready);
|
||||||
|
|
@ -244,9 +289,48 @@ export function UmeCliConnectPanel() {
|
||||||
style={{ minWidth: 320 }}
|
style={{ minWidth: 320 }}
|
||||||
/>
|
/>
|
||||||
<button type="button" onClick={() => connectTestMutation.mutate()} disabled={connectTestMutation.isPending}>
|
<button type="button" onClick={() => connectTestMutation.mutate()} disabled={connectTestMutation.isPending}>
|
||||||
{t("managedNe.connect.run")}
|
{connectTestMutation.isPending || overrideTesting
|
||||||
|
? t("managedNe.connect.running")
|
||||||
|
: t("managedNe.connect.run")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{testNeId ? (
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<div className="form-label">{t("ume.cli.connectTestResult")}</div>
|
||||||
|
{overrideQuery.isLoading && !overrideResult ? (
|
||||||
|
<p className="form-field-hint">{t("ume.cli.connectTestWaiting")}</p>
|
||||||
|
) : overrideTesting ? (
|
||||||
|
<p className="form-field-hint">
|
||||||
|
<span className="inline-spinner" aria-hidden /> {t("ume.cli.connectTestRunning")}
|
||||||
|
</p>
|
||||||
|
) : overrideResult ? (
|
||||||
|
<>
|
||||||
|
<p style={{ marginTop: 8 }}>
|
||||||
|
<span className={`conn-pill conn-pill--${connectPillLevel(overrideResult.connect_status)}`}>
|
||||||
|
{overrideResult.connect_status}
|
||||||
|
</span>
|
||||||
|
{overrideResult.connect_message ? (
|
||||||
|
<span className="connect-detail-summary"> — {overrideResult.connect_message}</span>
|
||||||
|
) : null}
|
||||||
|
{overrideResult.connect_tested_at ? (
|
||||||
|
<span className="muted" style={{ marginLeft: 8 }}>
|
||||||
|
{formatSystemTime(overrideResult.connect_tested_at, { assumeUtcNaive: true })}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</p>
|
||||||
|
<pre className="connect-log">
|
||||||
|
{overrideResult.connect_detail?.trim() ||
|
||||||
|
overrideResult.connect_message?.trim() ||
|
||||||
|
t("managedNe.connectDetailEmpty")}
|
||||||
|
</pre>
|
||||||
|
</>
|
||||||
|
) : testPending ? (
|
||||||
|
<p className="form-field-hint">{t("ume.cli.connectTestWaiting")}</p>
|
||||||
|
) : (
|
||||||
|
<p className="form-field-hint">{t("managedNe.connectDetailEmpty")}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{(profilesQuery.data?.items || []).length > 1 ? (
|
{(profilesQuery.data?.items || []).length > 1 ? (
|
||||||
<div style={{ marginTop: 16 }}>
|
<div style={{ marginTop: 16 }}>
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,10 @@ export const queryKeys = {
|
||||||
["neCollectionRuns", jobId, page, status, keyword] as const,
|
["neCollectionRuns", jobId, page, status, keyword] as const,
|
||||||
cliMeta: ["cliMeta"] as const,
|
cliMeta: ["cliMeta"] as const,
|
||||||
cliProfiles: ["cliProfiles"] as const,
|
cliProfiles: ["cliProfiles"] as const,
|
||||||
|
cliTargetsAll: ["cliTargets"] as const,
|
||||||
cliTargets: (keyword: string, page: number, pageSize: number) =>
|
cliTargets: (keyword: string, page: number, pageSize: number) =>
|
||||||
["cliTargets", keyword, page, pageSize] as const,
|
["cliTargets", keyword, page, pageSize] as const,
|
||||||
|
umeCliOverride: (umeNeId: string) => ["umeCliOverride", umeNeId] as const,
|
||||||
umeCurrentAlarms: (
|
umeCurrentAlarms: (
|
||||||
severity: string,
|
severity: string,
|
||||||
cleared: string,
|
cleared: string,
|
||||||
|
|
|
||||||
|
|
@ -488,6 +488,9 @@ const en = {
|
||||||
sampleNePh: "ume_ne_id (copy from NE list below)",
|
sampleNePh: "ume_ne_id (copy from NE list below)",
|
||||||
sampleNeRequired: "ume_ne_id is required",
|
sampleNeRequired: "ume_ne_id is required",
|
||||||
connectTestSubmitted: "Connectivity test submitted",
|
connectTestSubmitted: "Connectivity test submitted",
|
||||||
|
connectTestWaiting: "Waiting for test result…",
|
||||||
|
connectTestRunning: "Connectivity test in progress…",
|
||||||
|
connectTestResult: "Test result",
|
||||||
existingProfiles: "Existing profiles",
|
existingProfiles: "Existing profiles",
|
||||||
default: "default",
|
default: "default",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -486,6 +486,9 @@ const zh = {
|
||||||
sampleNePh: "ume_ne_id(从下方网元清单复制)",
|
sampleNePh: "ume_ne_id(从下方网元清单复制)",
|
||||||
sampleNeRequired: "请填写 ume_ne_id",
|
sampleNeRequired: "请填写 ume_ne_id",
|
||||||
connectTestSubmitted: "已提交连通性测试",
|
connectTestSubmitted: "已提交连通性测试",
|
||||||
|
connectTestWaiting: "等待测试结果…",
|
||||||
|
connectTestRunning: "连通性测试中…",
|
||||||
|
connectTestResult: "测试结果",
|
||||||
existingProfiles: "已有配置",
|
existingProfiles: "已有配置",
|
||||||
default: "默认",
|
default: "默认",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import type {
|
||||||
CliConnectProfileItem,
|
CliConnectProfileItem,
|
||||||
CliMeta,
|
CliMeta,
|
||||||
CliTargetListResponse,
|
CliTargetListResponse,
|
||||||
|
UmeCliOverrideItem,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
const parseApiResponse = async (res: Response): Promise<Record<string, unknown>> => {
|
const parseApiResponse = async (res: Response): Promise<Record<string, unknown>> => {
|
||||||
|
|
@ -331,6 +332,9 @@ export const fetchCliTargets = (params: {
|
||||||
export const postUmeConnectTest = (umeNeIds: string[]) =>
|
export const postUmeConnectTest = (umeNeIds: string[]) =>
|
||||||
apiPost<{ ok: boolean; submitted: number }>("/v1/cli/ume-connect-test", { ume_ne_ids: umeNeIds });
|
apiPost<{ ok: boolean; submitted: number }>("/v1/cli/ume-connect-test", { ume_ne_ids: umeNeIds });
|
||||||
|
|
||||||
|
export const fetchUmeCliOverride = (umeNeId: string) =>
|
||||||
|
apiGet<UmeCliOverrideItem | null>(`/v1/cli/ume-overrides/${encodeURIComponent(umeNeId)}`);
|
||||||
|
|
||||||
export const fetchUmeCurrentAlarms = (params: {
|
export const fetchUmeCurrentAlarms = (params: {
|
||||||
severity: string;
|
severity: string;
|
||||||
isCleared: string;
|
isCleared: string;
|
||||||
|
|
|
||||||
|
|
@ -268,6 +268,19 @@ export type CliTargetListResponse = {
|
||||||
items: CliTargetItem[];
|
items: CliTargetItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UmeCliOverrideItem = {
|
||||||
|
ume_ne_id: string;
|
||||||
|
profile_id: string | null;
|
||||||
|
username_override: string;
|
||||||
|
device_type_override: string;
|
||||||
|
vendor_override: string;
|
||||||
|
connect_status: string;
|
||||||
|
connect_message: string;
|
||||||
|
connect_detail: string;
|
||||||
|
connect_tested_at: string | null;
|
||||||
|
updated_at: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type EligibleNeItem = {
|
export type EligibleNeItem = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue