mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 03:10:46 +08:00
Enable BGP neighbor out profiles and show binding details on double-click.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
dd8d46aebc
commit
bfac0b53c8
5 changed files with 149 additions and 11 deletions
|
|
@ -239,6 +239,9 @@ const en = {
|
|||
bindTitle: "Select bindings",
|
||||
unbound: "Not bound",
|
||||
bindHintRequired: "Bind params before collect",
|
||||
paramsDblClickHint: "Double-click to view all bindings",
|
||||
paramsDetailTitle: "Bound parameters",
|
||||
paramsDetailCount: "{{n}} binding(s)",
|
||||
discoverLoading: "Discovering…",
|
||||
discoverEmpty: "No candidates discovered",
|
||||
selectAllVrfs: "Select all",
|
||||
|
|
@ -1564,6 +1567,7 @@ const en = {
|
|||
name: "Name",
|
||||
vendor: "Vendor",
|
||||
deviceType: "Device type",
|
||||
execPolicy: "CLI exec policy",
|
||||
source: "Source",
|
||||
ip: "Management IP",
|
||||
port: "Port",
|
||||
|
|
@ -1576,6 +1580,12 @@ const en = {
|
|||
testedAt: "Tested at",
|
||||
actions: "Actions",
|
||||
},
|
||||
execPolicy: {
|
||||
readonly: "Read-only (show/display/ping)",
|
||||
linuxShell: "Linux shell (single line)",
|
||||
unrestricted: "Lab open (single line)",
|
||||
hint: "Applies to MCP/API execManagedNe only. Default is read-only; shell / lab-open require a Linux device type.",
|
||||
},
|
||||
source: {
|
||||
manual: "Manual",
|
||||
ume_sync: "UME sync",
|
||||
|
|
|
|||
|
|
@ -239,6 +239,9 @@ const zh = {
|
|||
bindTitle: "选择参数关联",
|
||||
unbound: "未关联",
|
||||
bindHintRequired: "需关联参数后才可采集",
|
||||
paramsDblClickHint: "双击查看全部关联明细",
|
||||
paramsDetailTitle: "参数关联明细",
|
||||
paramsDetailCount: "共 {{n}} 条",
|
||||
discoverLoading: "正在发现…",
|
||||
discoverEmpty: "未发现可用选项",
|
||||
selectAllVrfs: "全选",
|
||||
|
|
@ -1551,6 +1554,7 @@ const zh = {
|
|||
name: "名称",
|
||||
vendor: "厂商",
|
||||
deviceType: "设备类型",
|
||||
execPolicy: "CLI 执行策略",
|
||||
source: "来源",
|
||||
ip: "管理地址",
|
||||
port: "端口",
|
||||
|
|
@ -1563,6 +1567,12 @@ const zh = {
|
|||
testedAt: "测试时间",
|
||||
actions: "操作",
|
||||
},
|
||||
execPolicy: {
|
||||
readonly: "只读(show/display/ping)",
|
||||
linuxShell: "Linux shell(单行)",
|
||||
unrestricted: "实验室全开(单行)",
|
||||
hint: "仅影响 MCP/API 的 execManagedNe。默认只读;仅 Linux 设备类型可选 shell / 实验室全开。",
|
||||
},
|
||||
source: {
|
||||
manual: "手工",
|
||||
ume_sync: "UME 同步",
|
||||
|
|
|
|||
|
|
@ -11331,14 +11331,39 @@ html.login-page--paused .login-page__flare {
|
|||
|
||||
.bs-profiles-table .bs-params-scroll {
|
||||
max-width: 100%;
|
||||
max-height: 4.5em;
|
||||
overflow: auto;
|
||||
max-height: 3.2em;
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
line-height: 1.35;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.bs-profiles-table .bs-params-scroll--clickable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.bs-profiles-table .bs-params-scroll--clickable:hover {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.bs-params-detail-list {
|
||||
max-height: min(50vh, 420px);
|
||||
}
|
||||
|
||||
.bs-params-detail-item {
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid rgba(148, 163, 184, 0.2);
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.bs-params-detail-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.bs-profiles-table .bs-actions-cell {
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
|
|
|
|||
|
|
@ -247,6 +247,12 @@ export function BizStatePage() {
|
|||
const [discoverLoading, setDiscoverLoading] = useState(false);
|
||||
const [discoverError, setDiscoverError] = useState("");
|
||||
|
||||
// Bound-params detail modal (double-click params cell)
|
||||
const [paramsDetail, setParamsDetail] = useState<{
|
||||
title: string;
|
||||
lines: string[];
|
||||
} | null>(null);
|
||||
|
||||
// batch workbook modal (summary + lazy-paged metric sheets)
|
||||
const [batchDetail, setBatchDetail] = useState<any>(null);
|
||||
const [sheetId, setSheetId] = useState("");
|
||||
|
|
@ -1391,11 +1397,41 @@ export function BizStatePage() {
|
|||
(x: any) => x.source_profile_id === prof.profile_id,
|
||||
);
|
||||
const enabled = Boolean(it?.enabled);
|
||||
const binds = it?.bindings || [];
|
||||
const binds = (it?.bindings || []) as {
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
}[];
|
||||
const needsBind = (prof.placeholders || []).length > 0;
|
||||
const phNames = (prof.placeholders || []).map((p) => p.name);
|
||||
let bindLines: string[] = [];
|
||||
if (needsBind && binds.length) {
|
||||
if (phNames.length >= 2) {
|
||||
const byPh: Record<string, string[]> = {};
|
||||
for (const b of binds) {
|
||||
const name = String(b.placeholder || "");
|
||||
const val = String(b.value || "");
|
||||
if (!name || !val) continue;
|
||||
(byPh[name] ||= []).push(val);
|
||||
}
|
||||
const counts = phNames.map((n) => (byPh[n] || []).length);
|
||||
const n = counts.length ? Math.min(...counts) : 0;
|
||||
for (let i = 0; i < n; i++) {
|
||||
bindLines.push(
|
||||
phNames.map((name) => `${name}=${(byPh[name] || [])[i] || ""}`).join(" · "),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const ph = phNames[0] || "value";
|
||||
bindLines = binds
|
||||
.filter((b) => String(b.value || "").trim())
|
||||
.map((b) => `${ph}=${String(b.value)}`);
|
||||
}
|
||||
}
|
||||
const bindHint = needsBind
|
||||
? binds.length
|
||||
? binds.map((b: any) => b.value).join(", ")
|
||||
? bindLines.length
|
||||
? bindLines.length <= 2
|
||||
? bindLines.join("; ")
|
||||
: `${bindLines.slice(0, 2).join("; ")} … (+${bindLines.length - 2})`
|
||||
: t("bizState.unbound")
|
||||
: "—";
|
||||
return (
|
||||
|
|
@ -1418,8 +1454,21 @@ export function BizStatePage() {
|
|||
<td className="bs-params-cell">
|
||||
{needsBind ? (
|
||||
<div
|
||||
className={`bs-params-scroll${!binds.length ? " bs-params-warn" : ""}`}
|
||||
title={bindHint}
|
||||
className={`bs-params-scroll${
|
||||
!bindLines.length ? " bs-params-warn" : ""
|
||||
}${bindLines.length ? " bs-params-scroll--clickable" : ""}`}
|
||||
title={
|
||||
bindLines.length
|
||||
? `${bindHint}\n${t("bizState.paramsDblClickHint")}`
|
||||
: bindHint
|
||||
}
|
||||
onDoubleClick={() => {
|
||||
if (!bindLines.length) return;
|
||||
setParamsDetail({
|
||||
title: prof.title,
|
||||
lines: bindLines,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{bindHint}
|
||||
</div>
|
||||
|
|
@ -1580,6 +1629,46 @@ export function BizStatePage() {
|
|||
</Modal.Footer>
|
||||
</AppModalShell>
|
||||
|
||||
{/* Bound params detail — double-click params cell */}
|
||||
<AppModalShell
|
||||
open={Boolean(paramsDetail)}
|
||||
onClose={() => setParamsDetail(null)}
|
||||
dismissible
|
||||
size="md"
|
||||
className="bs-bind-modal"
|
||||
>
|
||||
<Modal.Header>
|
||||
<Modal.Heading>
|
||||
{t("bizState.paramsDetailTitle")}
|
||||
{paramsDetail?.title ? ` · ${paramsDetail.title}` : ""}
|
||||
</Modal.Heading>
|
||||
<Modal.CloseTrigger />
|
||||
</Modal.Header>
|
||||
<Modal.Body className="flex flex-col gap-2">
|
||||
{paramsDetail?.lines?.length ? (
|
||||
<>
|
||||
<p className="muted">
|
||||
{t("bizState.paramsDetailCount", { n: paramsDetail.lines.length })}
|
||||
</p>
|
||||
<div className="bs-bind-list bs-bind-list--modal bs-params-detail-list">
|
||||
{paramsDetail.lines.map((line, idx) => (
|
||||
<div key={`${idx}-${line}`} className="bs-params-detail-item">
|
||||
<code>{line}</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className="muted">{t("bizState.unbound")}</p>
|
||||
)}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button size="sm" variant="ghost" onPress={() => setParamsDetail(null)}>
|
||||
{t("bizState.cancel")}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</AppModalShell>
|
||||
|
||||
{/* VRF discover / bind — blocking modal above task dialog */}
|
||||
<AppModalShell
|
||||
open={Boolean(bindItemId)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue