mirror of
https://github.com/hansjone/netx.git
synced 2026-10-16 07:40:45 +08:00
Compare template editor: compact field table, nav matches pane height.
- Value map collapses to a "+ / N 条" toggle that expands a detail row, instead of a scope select + textarea on every field row. - Unused mode/tolerance/normalize cells show "—" instead of disabled inputs; the not_worse rank input moves into the tolerance column. - Key/compare rows get a left accent; sheet options sit in one row with hints moved to tooltips. - Sheet nav stretches to the pane height and scrolls its own list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
51cfb41237
commit
e8f77440ca
5 changed files with 96 additions and 42 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
import { Button, Input, Modal } from "@heroui/react";
|
import { Button, Input, Modal } from "@heroui/react";
|
||||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
import { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||||
import type { KeyboardEvent as ReactKeyboardEvent } from "react";
|
import type { KeyboardEvent as ReactKeyboardEvent } from "react";
|
||||||
import {
|
import {
|
||||||
parseTemplateImport, parseIfaceNormText, formatIfaceNormText, parseValueMapText, formatValueMapText,
|
parseTemplateImport, parseIfaceNormText, formatIfaceNormText, parseValueMapText, formatValueMapText,
|
||||||
|
|
@ -639,16 +639,13 @@ const FILTER_OPS = [
|
||||||
|
|
||||||
/** Value-map editor for one field: text is parsed on blur so half-typed lines never clobber the rule. */
|
/** Value-map editor for one field: text is parsed on blur so half-typed lines never clobber the rule. */
|
||||||
function ValueMapCell({
|
function ValueMapCell({
|
||||||
field, value, scope, onScope, onCommit, rank, onRank, showRank, t,
|
field, value, scope, onScope, onCommit, t,
|
||||||
}: {
|
}: {
|
||||||
field: string;
|
field: string;
|
||||||
value: Record<string, string>;
|
value: Record<string, string>;
|
||||||
scope: Side;
|
scope: Side;
|
||||||
onScope: (s: Side) => void;
|
onScope: (s: Side) => void;
|
||||||
onCommit: (map: Record<string, string>) => void;
|
onCommit: (map: Record<string, string>) => void;
|
||||||
rank: string[];
|
|
||||||
onRank: (rank: string[]) => void;
|
|
||||||
showRank: boolean;
|
|
||||||
t: (k: string, vars?: Record<string, string | number>) => string;
|
t: (k: string, vars?: Record<string, string | number>) => string;
|
||||||
}) {
|
}) {
|
||||||
const external = formatValueMapText(value);
|
const external = formatValueMapText(value);
|
||||||
|
|
@ -660,14 +657,6 @@ function ValueMapCell({
|
||||||
}, [external, scope]);
|
}, [external, scope]);
|
||||||
return (
|
return (
|
||||||
<div className="ct-valuemap">
|
<div className="ct-valuemap">
|
||||||
{showRank ? (
|
|
||||||
<Input
|
|
||||||
aria-label={`${field} ${t("bizCompare.rankLabel")}`}
|
|
||||||
placeholder={t("bizCompare.rankPlaceholder")}
|
|
||||||
value={rank.join(",")}
|
|
||||||
onChange={(e) => onRank(e.target.value.split(",").map((x) => x.trim()).filter(Boolean))}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<select
|
<select
|
||||||
className="mt-select"
|
className="mt-select"
|
||||||
aria-label={`${field} ${t("bizCompare.valueMapScope")}`}
|
aria-label={`${field} ${t("bizCompare.valueMapScope")}`}
|
||||||
|
|
@ -682,8 +671,9 @@ function ValueMapCell({
|
||||||
className={`bs-cmp-mapping__editor ct-valuemap__text${error ? " is-invalid" : ""}`}
|
className={`bs-cmp-mapping__editor ct-valuemap__text${error ? " is-invalid" : ""}`}
|
||||||
aria-label={`${field} ${t("bizCompare.valueMap")}`}
|
aria-label={`${field} ${t("bizCompare.valueMap")}`}
|
||||||
placeholder={t("bizCompare.valueMapPlaceholder")}
|
placeholder={t("bizCompare.valueMapPlaceholder")}
|
||||||
rows={2}
|
rows={3}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
|
autoFocus
|
||||||
value={text}
|
value={text}
|
||||||
onChange={(e) => setText(e.target.value)}
|
onChange={(e) => setText(e.target.value)}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
|
|
@ -899,6 +889,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
const [tplSheets, setTplSheets] = useState<MetricSheet[]>([]);
|
const [tplSheets, setTplSheets] = useState<MetricSheet[]>([]);
|
||||||
const [tplSheetIdx, setTplSheetIdx] = useState(0);
|
const [tplSheetIdx, setTplSheetIdx] = useState(0);
|
||||||
const [valueMapScope, setValueMapScope] = useState<Record<string, Side>>({});
|
const [valueMapScope, setValueMapScope] = useState<Record<string, Side>>({});
|
||||||
|
const [vmOpen, setVmOpen] = useState<string | null>(null);
|
||||||
const [tplPaneTab, setTplPaneTab] = useState<"fields" | "filters">("fields");
|
const [tplPaneTab, setTplPaneTab] = useState<"fields" | "filters">("fields");
|
||||||
const [showMetricPicker, setShowMetricPicker] = useState(true);
|
const [showMetricPicker, setShowMetricPicker] = useState(true);
|
||||||
|
|
||||||
|
|
@ -1618,6 +1609,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
setTplSheetIdx(0);
|
setTplSheetIdx(0);
|
||||||
setTplPaneTab("fields");
|
setTplPaneTab("fields");
|
||||||
setValueMapScope({});
|
setValueMapScope({});
|
||||||
|
setVmOpen(null);
|
||||||
setShowMetricPicker(true);
|
setShowMetricPicker(true);
|
||||||
setTplOpen(true);
|
setTplOpen(true);
|
||||||
};
|
};
|
||||||
|
|
@ -1633,6 +1625,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
setTplSheetIdx(0);
|
setTplSheetIdx(0);
|
||||||
setTplPaneTab("fields");
|
setTplPaneTab("fields");
|
||||||
setValueMapScope({});
|
setValueMapScope({});
|
||||||
|
setVmOpen(null);
|
||||||
setShowMetricPicker(!sheets.length);
|
setShowMetricPicker(!sheets.length);
|
||||||
setTplOpen(true);
|
setTplOpen(true);
|
||||||
};
|
};
|
||||||
|
|
@ -2909,6 +2902,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
className={`ct-editor__nav-item${tplSheetIdx === i ? " is-active" : ""}`}
|
className={`ct-editor__nav-item${tplSheetIdx === i ? " is-active" : ""}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTplSheetIdx(i);
|
setTplSheetIdx(i);
|
||||||
|
setVmOpen(null);
|
||||||
setTplPaneTab("fields");
|
setTplPaneTab("fields");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -2998,21 +2992,21 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FieldSelect label={t("bizCompare.portPolicy")} value={activeTplSheet.ignore_port_changes == null ? "auto" : activeTplSheet.ignore_port_changes ? "ignore" : "strict"} onChange={(e) => updateActiveSheet({ignore_port_changes: e.target.value === "auto" ? null : e.target.value === "ignore"})} hint={t("bizCompare.portPolicyHint")}>
|
|
||||||
<option value="auto">{t("bizCompare.portPolicyAuto")}</option><option value="strict">{t("bizCompare.portPolicyStrict")}</option><option value="ignore">{t("bizCompare.portPolicyIgnore")}</option>
|
|
||||||
</FieldSelect>
|
|
||||||
<div className="ct-sheet-options">
|
<div className="ct-sheet-options">
|
||||||
<FieldSelect label={t("bizCompare.unmappedPortPolicy")} value={activeTplSheet.unmapped_port_policy || "by_name"} hint={t("bizCompare.unmappedPortPolicyHint")}
|
<FieldSelect label={t("bizCompare.portPolicy")} title={t("bizCompare.portPolicyHint")} value={activeTplSheet.ignore_port_changes == null ? "auto" : activeTplSheet.ignore_port_changes ? "ignore" : "strict"} onChange={(e) => updateActiveSheet({ignore_port_changes: e.target.value === "auto" ? null : e.target.value === "ignore"})}>
|
||||||
|
<option value="auto">{t("bizCompare.portPolicyAuto")}</option><option value="strict">{t("bizCompare.portPolicyStrict")}</option><option value="ignore">{t("bizCompare.portPolicyIgnore")}</option>
|
||||||
|
</FieldSelect>
|
||||||
|
<FieldSelect label={t("bizCompare.unmappedPortPolicy")} title={t("bizCompare.unmappedPortPolicyHint")} value={activeTplSheet.unmapped_port_policy || "by_name"}
|
||||||
onChange={(e) => updateActiveSheet({ unmapped_port_policy: e.target.value as "by_name" | "ignore_port" })}>
|
onChange={(e) => updateActiveSheet({ unmapped_port_policy: e.target.value as "by_name" | "ignore_port" })}>
|
||||||
<option value="by_name">{t("bizCompare.unmappedByName")}</option>
|
<option value="by_name">{t("bizCompare.unmappedByName")}</option>
|
||||||
<option value="ignore_port">{t("bizCompare.unmappedIgnorePort")}</option>
|
<option value="ignore_port">{t("bizCompare.unmappedIgnorePort")}</option>
|
||||||
</FieldSelect>
|
</FieldSelect>
|
||||||
<FieldSelect label={t("bizCompare.duplicatePairing")} value={activeTplSheet.duplicate_pairing || "sorted"} hint={t("bizCompare.duplicatePairingHint")}
|
<FieldSelect label={t("bizCompare.duplicatePairing")} title={t("bizCompare.duplicatePairingHint")} value={activeTplSheet.duplicate_pairing || "sorted"}
|
||||||
onChange={(e) => updateActiveSheet({ duplicate_pairing: e.target.value as "sorted" | "ordered" })}>
|
onChange={(e) => updateActiveSheet({ duplicate_pairing: e.target.value as "sorted" | "ordered" })}>
|
||||||
<option value="sorted">{t("bizCompare.pairSorted")}</option>
|
<option value="sorted">{t("bizCompare.pairSorted")}</option>
|
||||||
<option value="ordered">{t("bizCompare.pairOrdered")}</option>
|
<option value="ordered">{t("bizCompare.pairOrdered")}</option>
|
||||||
</FieldSelect>
|
</FieldSelect>
|
||||||
<label className="ct-sheet-options__check">
|
<label className="ct-sheet-options__check" title={t("bizCompare.builtinVendorCanonicalHint")}>
|
||||||
<input type="checkbox" checked={activeTplSheet.builtin_vendor_canonical !== false}
|
<input type="checkbox" checked={activeTplSheet.builtin_vendor_canonical !== false}
|
||||||
onChange={(e) => updateActiveSheet({ builtin_vendor_canonical: e.target.checked ? undefined : false })} />
|
onChange={(e) => updateActiveSheet({ builtin_vendor_canonical: e.target.checked ? undefined : false })} />
|
||||||
<span>{t("bizCompare.builtinVendorCanonical")}</span>
|
<span>{t("bizCompare.builtinVendorCanonical")}</span>
|
||||||
|
|
@ -3027,7 +3021,6 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
/>
|
/>
|
||||||
)) : (
|
)) : (
|
||||||
<>
|
<>
|
||||||
<p className="muted ct-field-hint">{t("bizCompare.fieldsHintShort")}</p>
|
|
||||||
<div className="pt-list-table-wrap ct-field-table-wrap">
|
<div className="pt-list-table-wrap ct-field-table-wrap">
|
||||||
<table className="data-table pt-list-table ct-field-table">
|
<table className="data-table pt-list-table ct-field-table">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
@ -3035,7 +3028,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
<th>{t("bizCompare.field")}</th>
|
<th>{t("bizCompare.field")}</th>
|
||||||
<th title={t("bizCompare.keyFields")}>{t("bizCompare.keyFields")}</th>
|
<th title={t("bizCompare.keyFields")}>{t("bizCompare.keyFields")}</th>
|
||||||
<th title={t("bizCompare.ifaceFields")}>{t("bizCompare.ifaceFields")}</th>
|
<th title={t("bizCompare.ifaceFields")}>{t("bizCompare.ifaceFields")}</th>
|
||||||
<th title={t("bizCompare.compareFields")}>{t("bizCompare.compareFields")}</th>
|
<th title={t("bizCompare.fieldsHintShort")}>{t("bizCompare.compareFields")}</th>
|
||||||
<th>{t("bizCompare.compareMode")}</th>
|
<th>{t("bizCompare.compareMode")}</th>
|
||||||
<th>{t("bizCompare.tolerance")}</th>
|
<th>{t("bizCompare.tolerance")}</th>
|
||||||
<th>{t("bizCompare.displayField")}</th>
|
<th>{t("bizCompare.displayField")}</th>
|
||||||
|
|
@ -3056,8 +3049,11 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
const norm = rawNorm === "strip" ? "none" : rawNorm;
|
const norm = rawNorm === "strip" ? "none" : rawNorm;
|
||||||
const needsTol = ["numeric", "percent", "numeric_min", "numeric_max"].includes(cmpMode);
|
const needsTol = ["numeric", "percent", "numeric_min", "numeric_max"].includes(cmpMode);
|
||||||
const vmScope = valueMapScope[f.name] || "both";
|
const vmScope = valueMapScope[f.name] || "both";
|
||||||
|
const vmCount = (["both", "before", "after"] as Side[]).reduce((n, sc) => n + Object.keys(valueMapFor(activeTplSheet, f.name, sc)).length, 0);
|
||||||
|
const vmExpanded = vmOpen === f.name;
|
||||||
return (
|
return (
|
||||||
<tr key={f.name}>
|
<Fragment key={f.name}>
|
||||||
|
<tr className={`${isKey ? "is-key" : isCompare ? "is-compare" : ""}${vmExpanded ? " is-expanded" : ""}`}>
|
||||||
<td>
|
<td>
|
||||||
<code className="ct-field-name">{f.name}</code>
|
<code className="ct-field-name">{f.name}</code>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -3111,11 +3107,11 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
{!isCompare ? <span className="ct-na">—</span> : (
|
||||||
<select
|
<select
|
||||||
className="mt-select"
|
className="mt-select"
|
||||||
value={cmpMode === "ignore" ? "eq" : cmpMode}
|
value={cmpMode === "ignore" ? "eq" : cmpMode}
|
||||||
aria-label={`${f.name} ${t("bizCompare.compareMode")}`}
|
aria-label={`${f.name} ${t("bizCompare.compareMode")}`}
|
||||||
disabled={!isCompare}
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const mode = e.target.value;
|
const mode = e.target.value;
|
||||||
updateActiveSheet({
|
updateActiveSheet({
|
||||||
|
|
@ -3134,8 +3130,18 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
<option value="numeric_min">{t("bizCompare.modeNumericMin")}</option>
|
<option value="numeric_min">{t("bizCompare.modeNumericMin")}</option>
|
||||||
<option value="numeric_max">{t("bizCompare.modeNumericMax")}</option>
|
<option value="numeric_max">{t("bizCompare.modeNumericMax")}</option>
|
||||||
</select>
|
</select>
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
{isCompare && cmpMode === "not_worse" ? (
|
||||||
|
<Input
|
||||||
|
aria-label={`${f.name} ${t("bizCompare.rankLabel")}`}
|
||||||
|
placeholder={t("bizCompare.rankPlaceholder")}
|
||||||
|
title={t("bizCompare.rankLabel")}
|
||||||
|
value={(rule.rank || []).join(",")}
|
||||||
|
onChange={(e) => updateActiveSheet({ field_rules: upsertFieldRule(activeTplSheet, f.name, { compare: "not_worse", rank: e.target.value.split(",").map((x) => x.trim()).filter(Boolean) }) })}
|
||||||
|
/>
|
||||||
|
) : !isCompare || !needsTol ? <span className="ct-na">—</span> : (
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
|
|
@ -3149,7 +3155,6 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
placeholder={["percent", "numeric_min", "numeric_max"].includes(cmpMode) ? "%" : ""}
|
placeholder={["percent", "numeric_min", "numeric_max"].includes(cmpMode) ? "%" : ""}
|
||||||
disabled={!isCompare || !needsTol}
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const raw = e.target.value.trim();
|
const raw = e.target.value.trim();
|
||||||
const tol = raw === "" ? undefined : Number(raw);
|
const tol = raw === "" ? undefined : Number(raw);
|
||||||
|
|
@ -3163,6 +3168,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
|
|
@ -3182,11 +3188,11 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
{!isCompare && !isKey ? <span className="ct-na">—</span> : (
|
||||||
<select
|
<select
|
||||||
className="mt-select"
|
className="mt-select"
|
||||||
value={norm}
|
value={norm}
|
||||||
aria-label={`${f.name} ${t("bizCompare.normalizeField")}`}
|
aria-label={`${f.name} ${t("bizCompare.normalizeField")}`}
|
||||||
disabled={!isCompare && !isKey}
|
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateActiveSheet({
|
updateActiveSheet({
|
||||||
field_rules: upsertFieldRule(activeTplSheet, f.name, {
|
field_rules: upsertFieldRule(activeTplSheet, f.name, {
|
||||||
|
|
@ -3203,21 +3209,35 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
{t("bizCompare.normalizeEmpty")}
|
{t("bizCompare.normalizeEmpty")}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<ValueMapCell
|
<button
|
||||||
field={f.name}
|
type="button"
|
||||||
t={t}
|
className={`ct-vm-toggle${vmCount ? " has-value" : ""}`}
|
||||||
scope={vmScope}
|
aria-expanded={vmExpanded}
|
||||||
onScope={(sc) => setValueMapScope((prev) => ({ ...prev, [f.name]: sc }))}
|
aria-label={`${f.name} ${t("bizCompare.valueMapEdit")}`}
|
||||||
value={valueMapFor(activeTplSheet, f.name, vmScope)}
|
onClick={() => setVmOpen(vmExpanded ? null : f.name)}
|
||||||
onCommit={(map) => updateActiveSheet({ field_rules: setValueMapFor(activeTplSheet, f.name, vmScope, map) })}
|
>
|
||||||
showRank={cmpMode === "not_worse"}
|
{vmCount ? t("bizCompare.valueMapCount", { n: String(vmCount) }) : "+"}
|
||||||
rank={rule.rank || []}
|
</button>
|
||||||
onRank={(rank) => updateActiveSheet({ field_rules: upsertFieldRule(activeTplSheet, f.name, { compare: "not_worse", rank }) })}
|
|
||||||
/>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{vmExpanded ? (
|
||||||
|
<tr className="ct-field-table__detail">
|
||||||
|
<td colSpan={9}>
|
||||||
|
<ValueMapCell
|
||||||
|
field={f.name}
|
||||||
|
t={t}
|
||||||
|
scope={vmScope}
|
||||||
|
onScope={(sc) => setValueMapScope((prev) => ({ ...prev, [f.name]: sc }))}
|
||||||
|
value={valueMapFor(activeTplSheet, f.name, vmScope)}
|
||||||
|
onCommit={(map) => updateActiveSheet({ field_rules: setValueMapFor(activeTplSheet, f.name, vmScope, map) })}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : null}
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!activeTplFields.length ? (
|
{!activeTplFields.length ? (
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,8 @@ const bizCompare = {
|
||||||
valueMap: "Value map",
|
valueMap: "Value map",
|
||||||
valueMapScope: "value map scope",
|
valueMapScope: "value map scope",
|
||||||
valueMapPlaceholder: "One per line: source,canonical\nestab,established",
|
valueMapPlaceholder: "One per line: source,canonical\nestab,established",
|
||||||
|
valueMapEdit: "edit value map",
|
||||||
|
valueMapCount: "{{n}} rules",
|
||||||
rankLabel: "best→worst",
|
rankLabel: "best→worst",
|
||||||
rankPlaceholder: "up,down (earlier is better)",
|
rankPlaceholder: "up,down (earlier is better)",
|
||||||
modeNotWorse: "Not worse",
|
modeNotWorse: "Not worse",
|
||||||
|
|
@ -153,7 +155,8 @@ const bizCompare = {
|
||||||
pairSorted: "Pair by sorted value (recommended)",
|
pairSorted: "Pair by sorted value (recommended)",
|
||||||
pairOrdered: "Pair by collection order (legacy)",
|
pairOrdered: "Pair by collection order (legacy)",
|
||||||
duplicatePairingHint: "When one key has several rows (ECMP, multipath), CLI ordering changes must not create false differences.",
|
duplicatePairingHint: "When one key has several rows (ECMP, multipath), CLI ordering changes must not create false differences.",
|
||||||
builtinVendorCanonical: "Built-in per-vendor interface names (GigabitEthernet↔GE …)",
|
builtinVendorCanonical: "Built-in vendor interface names",
|
||||||
|
builtinVendorCanonicalHint: "Unify vendor interface names (GigabitEthernet↔GE …)",
|
||||||
mappingNeedBatches: "Pick the before and after batches first",
|
mappingNeedBatches: "Pick the before and after batches first",
|
||||||
preflight: {
|
preflight: {
|
||||||
"run": "Validate mapping",
|
"run": "Validate mapping",
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,8 @@ const bizCompare = {
|
||||||
valueMap: "值映射",
|
valueMap: "值映射",
|
||||||
valueMapScope: "值映射范围",
|
valueMapScope: "值映射范围",
|
||||||
valueMapPlaceholder: "每行:原值,规范值\nestab,established",
|
valueMapPlaceholder: "每行:原值,规范值\nestab,established",
|
||||||
|
valueMapEdit: "编辑值映射",
|
||||||
|
valueMapCount: "{{n}} 条",
|
||||||
rankLabel: "好→坏顺序",
|
rankLabel: "好→坏顺序",
|
||||||
rankPlaceholder: "up,down(靠前更好)",
|
rankPlaceholder: "up,down(靠前更好)",
|
||||||
modeNotWorse: "不劣于",
|
modeNotWorse: "不劣于",
|
||||||
|
|
@ -152,7 +154,8 @@ const bizCompare = {
|
||||||
pairSorted: "按取值排序后配对(推荐)",
|
pairSorted: "按取值排序后配对(推荐)",
|
||||||
pairOrdered: "按采集顺序配对(旧行为)",
|
pairOrdered: "按采集顺序配对(旧行为)",
|
||||||
duplicatePairingHint: "ECMP、多路径等同一个键出现多行时,CLI 输出顺序变化不应产生假差异。",
|
duplicatePairingHint: "ECMP、多路径等同一个键出现多行时,CLI 输出顺序变化不应产生假差异。",
|
||||||
builtinVendorCanonical: "启用内置厂商接口名称统一(GigabitEthernet↔GE 等)",
|
builtinVendorCanonical: "内置厂商接口名称统一",
|
||||||
|
builtinVendorCanonicalHint: "GigabitEthernet↔GE 等厂商接口名自动统一",
|
||||||
mappingNeedBatches: "请先选择前后批次",
|
mappingNeedBatches: "请先选择前后批次",
|
||||||
preflight: {
|
preflight: {
|
||||||
"run": "校验映射",
|
"run": "校验映射",
|
||||||
|
|
|
||||||
|
|
@ -905,7 +905,9 @@
|
||||||
.bs-template-editor .ct-editor__norm summary { cursor: pointer; color: #c4ddfa; }
|
.bs-template-editor .ct-editor__norm summary { cursor: pointer; color: #c4ddfa; }
|
||||||
.bs-template-editor .ct-editor__norm-area { width: 100%; max-width: 100%; resize: vertical; font: 12px/1.6 ui-monospace, Consolas, monospace; }
|
.bs-template-editor .ct-editor__norm-area { width: 100%; max-width: 100%; resize: vertical; font: 12px/1.6 ui-monospace, Consolas, monospace; }
|
||||||
.bs-template-editor .ct-editor__main { grid-template-columns: 190px minmax(0, 1fr); min-width: 0; }
|
.bs-template-editor .ct-editor__main { grid-template-columns: 190px minmax(0, 1fr); min-width: 0; }
|
||||||
.bs-template-editor .ct-editor__nav { max-height: 560px; }
|
/* Nav takes the pane's height (contain: size keeps it from driving the row) and scrolls its own list. */
|
||||||
|
.bs-template-editor .ct-editor__nav { max-height: none; contain: size; overflow: hidden; }
|
||||||
|
.bs-template-editor .ct-editor__nav-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
||||||
.bs-template-editor .ct-editor__nav-item { border-radius: 3px; font-size: 12px; }
|
.bs-template-editor .ct-editor__nav-item { border-radius: 3px; font-size: 12px; }
|
||||||
.bs-template-editor .ct-editor__nav-item.is-active { background: #1b2c43; border-color: #526f94; }
|
.bs-template-editor .ct-editor__nav-item.is-active { background: #1b2c43; border-color: #526f94; }
|
||||||
.bs-template-editor .ct-editor__meta { font: 11px/1.6 ui-monospace, Consolas, monospace; overflow-wrap: anywhere; }
|
.bs-template-editor .ct-editor__meta { font: 11px/1.6 ui-monospace, Consolas, monospace; overflow-wrap: anywhere; }
|
||||||
|
|
@ -914,7 +916,29 @@
|
||||||
.bs-template-editor .ct-field-table { min-width: 800px; }
|
.bs-template-editor .ct-field-table { min-width: 800px; }
|
||||||
.bs-template-editor .ct-field-table td { font-size: 12px; }
|
.bs-template-editor .ct-field-table td { font-size: 12px; }
|
||||||
.bs-template-editor .ct-field-table th { font-size: 11px; }
|
.bs-template-editor .ct-field-table th { font-size: 11px; }
|
||||||
.bs-template-editor .ct-field-table .input { max-width: 100px; }
|
.bs-template-editor .ct-field-table .input { max-width: 110px; height: 28px; padding: 2px 8px; font-size: 12px; }
|
||||||
|
.bs-template-editor .ct-field-table td { padding-top: 5px; padding-bottom: 5px; height: 38px; }
|
||||||
|
.bs-template-editor .ct-field-table .mt-select { width: 112px; height: 28px; padding: 2px 6px; }
|
||||||
|
.bs-template-editor .ct-field-table input[type="checkbox"] { width: 15px; height: 15px; cursor: pointer; }
|
||||||
|
.bs-template-editor .ct-field-table input[type="checkbox"]:disabled { cursor: default; opacity: 0.45; }
|
||||||
|
.bs-template-editor .ct-field-table tbody tr > td:first-child { box-shadow: inset 2px 0 0 transparent; }
|
||||||
|
.bs-template-editor .ct-field-table tbody tr.is-key > td:first-child { box-shadow: inset 2px 0 0 #6f8db3; }
|
||||||
|
.bs-template-editor .ct-field-table tbody tr.is-compare > td:first-child { box-shadow: inset 2px 0 0 var(--nx-accent); }
|
||||||
|
.bs-template-editor .ct-field-table tbody tr.is-compare > td { background: rgb(var(--nx-accent-rgb) / 0.05); }
|
||||||
|
.bs-template-editor .ct-field-table tbody tr.is-expanded > td { border-bottom-color: transparent; }
|
||||||
|
.bs-template-editor .ct-na { color: var(--nx-text-muted); opacity: 0.6; }
|
||||||
|
.bs-template-editor .ct-vm-toggle { min-width: 52px; height: 26px; padding: 0 10px; border: 1px dashed var(--nx-line); border-radius: 13px; background: transparent; color: var(--nx-text-muted); font-size: 12px; cursor: pointer; }
|
||||||
|
.bs-template-editor .ct-vm-toggle:hover { border-color: #526f94; color: #9cc7f7; }
|
||||||
|
.bs-template-editor .ct-vm-toggle.has-value { border-style: solid; border-color: #3d5878; background: #16263a; color: #9cc7f7; }
|
||||||
|
.bs-template-editor .ct-vm-toggle[aria-expanded="true"] { border-style: solid; border-color: var(--nx-accent); color: #9cc7f7; }
|
||||||
|
.bs-template-editor .ct-field-table__detail > td { background: var(--nx-surface-1); padding: 8px 12px 10px; text-align: left !important; white-space: normal !important; height: auto; }
|
||||||
|
.bs-template-editor .ct-field-table__detail .ct-valuemap { flex-direction: row; align-items: flex-start; gap: 8px; }
|
||||||
|
.bs-template-editor .ct-field-table__detail .ct-valuemap > .mt-select { flex: 0 0 112px; }
|
||||||
|
.bs-template-editor .ct-field-table__detail .ct-valuemap__text { flex: 1 1 auto; min-height: 64px; max-width: 520px; resize: vertical; font: 12px/1.6 ui-monospace, Consolas, monospace; }
|
||||||
|
.bs-template-editor .ct-sheet-options { display: grid; grid-template-columns: repeat(3, minmax(0, 220px)) auto; align-items: end; gap: 10px 14px; padding: 10px 12px; border: 1px solid var(--nx-line); border-radius: 4px; background: var(--nx-surface-1); }
|
||||||
|
.bs-template-editor .ct-sheet-options .ui-field__label { font-size: 11px; color: var(--nx-text-muted); }
|
||||||
|
.bs-template-editor .ct-sheet-options .ui-field__select { height: 30px; font-size: 12px; }
|
||||||
|
.bs-template-editor .ct-sheet-options__check { height: 30px; color: var(--nx-text-muted); white-space: nowrap; }
|
||||||
.bs-template-editor .ct-filter-row { min-width: 0; }
|
.bs-template-editor .ct-filter-row { min-width: 0; }
|
||||||
.bs-template-editor .ct-filter-row .mt-select { min-width: 0; max-width: 100%; }
|
.bs-template-editor .ct-filter-row .mt-select { min-width: 0; max-width: 100%; }
|
||||||
.bs-template-editor .ct-complex-filters pre { max-height: 360px; overflow: auto; background: var(--nx-surface-1); border: 1px solid var(--nx-line); border-radius: 4px; padding: 10px; font: 11px/1.6 ui-monospace, Consolas, monospace; }
|
.bs-template-editor .ct-complex-filters pre { max-height: 360px; overflow: auto; background: var(--nx-surface-1); border: 1px solid var(--nx-line); border-radius: 4px; padding: 10px; font: 11px/1.6 ui-monospace, Consolas, monospace; }
|
||||||
|
|
@ -925,7 +949,8 @@
|
||||||
.bs-template-editor .ct-editor__main { grid-template-columns: minmax(0, 1fr); }
|
.bs-template-editor .ct-editor__main { grid-template-columns: minmax(0, 1fr); }
|
||||||
.bs-template-editor .ct-editor__nav-list { flex-direction: row; overflow-x: auto; }
|
.bs-template-editor .ct-editor__nav-list { flex-direction: row; overflow-x: auto; }
|
||||||
.bs-template-editor .ct-editor__nav-item { flex: 0 0 160px; }
|
.bs-template-editor .ct-editor__nav-item { flex: 0 0 160px; }
|
||||||
.bs-template-editor .ct-editor__nav { max-height: none; }
|
.bs-template-editor .ct-editor__nav { max-height: none; contain: none; }
|
||||||
|
.bs-template-editor .ct-sheet-options { grid-template-columns: minmax(0, 1fr); }
|
||||||
}
|
}
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.bs-template-editor .ct-editor__pane { padding: 10px; }
|
.bs-template-editor .ct-editor__pane { padding: 10px; }
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ try {
|
||||||
await stateRow.locator('input[type="checkbox"]').nth(2).check();
|
await stateRow.locator('input[type="checkbox"]').nth(2).check();
|
||||||
await editor().getByLabel("state 方式", { exact: true }).selectOption("not_worse");
|
await editor().getByLabel("state 方式", { exact: true }).selectOption("not_worse");
|
||||||
await editor().getByLabel("state 好→坏顺序", { exact: true }).fill("up,down");
|
await editor().getByLabel("state 好→坏顺序", { exact: true }).fill("up,down");
|
||||||
|
await editor().getByRole("button", { name: "state 编辑值映射", exact: true }).click();
|
||||||
await editor().getByLabel("state 值映射", { exact: true }).fill("estab,established\n*down,down");
|
await editor().getByLabel("state 值映射", { exact: true }).fill("estab,established\n*down,down");
|
||||||
await editor().getByLabel("state 值映射", { exact: true }).blur();
|
await editor().getByLabel("state 值映射", { exact: true }).blur();
|
||||||
await editor().getByLabel("state 值映射范围", { exact: true }).selectOption("before");
|
await editor().getByLabel("state 值映射范围", { exact: true }).selectOption("before");
|
||||||
|
|
@ -185,6 +186,8 @@ try {
|
||||||
// ... and come back unchanged when the template is opened again.
|
// ... and come back unchanged when the template is opened again.
|
||||||
await openEditor();
|
await openEditor();
|
||||||
await editor().getByRole("tab", { name: /^字段规则/ }).click();
|
await editor().getByRole("tab", { name: /^字段规则/ }).click();
|
||||||
|
assert.equal(await editor().getByRole("button", { name: "state 编辑值映射", exact: true }).textContent(), "3 条");
|
||||||
|
await editor().getByRole("button", { name: "state 编辑值映射", exact: true }).click();
|
||||||
assert.equal(await editor().getByLabel("state 值映射", { exact: true }).inputValue(), "estab,established\n*down,down");
|
assert.equal(await editor().getByLabel("state 值映射", { exact: true }).inputValue(), "estab,established\n*down,down");
|
||||||
assert.equal(await editor().getByLabel("未映射端口", { exact: true }).inputValue(), "ignore_port");
|
assert.equal(await editor().getByLabel("未映射端口", { exact: true }).inputValue(), "ignore_port");
|
||||||
await page.keyboard.press("Escape");
|
await page.keyboard.press("Escape");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue