mirror of
https://github.com/hansjone/netx.git
synced 2026-10-12 04:10:47 +08:00
Fix cutover create-task form inputs and clarify steps.
Avoid nested labels so name and mapping controls work; separate name, collect tasks, and optional baseline/mapping. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
335c3fcbeb
commit
f3a8f80822
3 changed files with 200 additions and 78 deletions
|
|
@ -453,9 +453,23 @@ const en = {
|
||||||
hintPort:
|
hintPort:
|
||||||
"Metric focus: port status (interface_brief / admin·phy·prot). Baseline → pick ports → evaluate board.",
|
"Metric focus: port status (interface_brief / admin·phy·prot). Baseline → pick ports → evaluate board.",
|
||||||
create: "Create task",
|
create: "Create task",
|
||||||
createHint: "Pick old/new business-monitor tasks; optional port mapping and baseline batches.",
|
createHint: "Name the task and bind old/new business-monitor collect tasks. Mapping and baselines can be set later.",
|
||||||
createProject: "Create task",
|
createProject: "Create task",
|
||||||
projectName: "Task name",
|
projectName: "Task name",
|
||||||
|
projectNamePh: "e.g. CN1/CN2 night-1 cutover",
|
||||||
|
sectionName: "1. Task name",
|
||||||
|
sectionTasks: "2. Bind collect tasks (required)",
|
||||||
|
sectionTasksHint: "Pick the old-device and new-device business-monitor tasks.",
|
||||||
|
sectionOptional: "3. Port mapping & baselines (optional)",
|
||||||
|
sectionOptionalHint: "Skip for now if needed; set later under Baseline. Batches load after a collect task is chosen.",
|
||||||
|
sectionPair: "Bound collect tasks",
|
||||||
|
sectionPairHint: "Collect tasks are fixed at create time; adjust mapping and baselines here.",
|
||||||
|
sectionBaseline: "Baseline batches",
|
||||||
|
sectionBaselineHint: "Old baseline feeds the expected-port picker; new baseline is optional.",
|
||||||
|
optionalNone: "None",
|
||||||
|
portMappingHint: "From Business compare port mappings; leave empty if none yet.",
|
||||||
|
baselineNeedTask: "Select the matching collect task first",
|
||||||
|
baselinePickHint: "Choose from that task’s collect batches",
|
||||||
oldTask: "Old-device collect task",
|
oldTask: "Old-device collect task",
|
||||||
newTask: "New-device collect task",
|
newTask: "New-device collect task",
|
||||||
portMapping: "Port mapping",
|
portMapping: "Port mapping",
|
||||||
|
|
|
||||||
|
|
@ -450,9 +450,23 @@ const zh = {
|
||||||
hint: "与业务对比独立:相对基线漂移 + 本批预期 + 老/新双端判定。复用采集任务与端口映射。",
|
hint: "与业务对比独立:相对基线漂移 + 本批预期 + 老/新双端判定。复用采集任务与端口映射。",
|
||||||
hintPort: "当前监控项:端口状态(interface_brief / admin·phy·prot)。选基线 → 勾选本批端口 → 判定看板。",
|
hintPort: "当前监控项:端口状态(interface_brief / admin·phy·prot)。选基线 → 勾选本批端口 → 判定看板。",
|
||||||
create: "创建任务",
|
create: "创建任务",
|
||||||
createHint: "选择老/新设备的业务监控任务,可选端口映射与基线批次。",
|
createHint: "先填任务名并绑定老/新业务监控采集任务;映射与基线可稍后在详情里补。",
|
||||||
createProject: "创建任务",
|
createProject: "创建任务",
|
||||||
projectName: "任务名称",
|
projectName: "任务名称",
|
||||||
|
projectNamePh: "例如:CN1/CN2 第1夜割接",
|
||||||
|
sectionName: "1. 任务名称",
|
||||||
|
sectionTasks: "2. 绑定采集任务(必填)",
|
||||||
|
sectionTasksHint: "分别选择老设备、新设备上的业务监控任务;两侧不能为空。",
|
||||||
|
sectionOptional: "3. 端口映射与基线(可选)",
|
||||||
|
sectionOptionalHint: "可先跳过,创建后再到「基线配置」里设置。选了采集任务后才会加载对应批次。",
|
||||||
|
sectionPair: "已绑定采集任务",
|
||||||
|
sectionPairHint: "采集任务在创建时绑定;此处只调整映射与基线。",
|
||||||
|
sectionBaseline: "基线批次",
|
||||||
|
sectionBaselineHint: "老侧基线用于列出本批预期端口;新侧基线可选。",
|
||||||
|
optionalNone: "不选",
|
||||||
|
portMappingHint: "来自「业务对比」中的端口映射;没有可先不选。",
|
||||||
|
baselineNeedTask: "请先选择对应采集任务",
|
||||||
|
baselinePickHint: "从该任务的历史采集批次中选择",
|
||||||
oldTask: "老设备采集任务",
|
oldTask: "老设备采集任务",
|
||||||
newTask: "新设备采集任务",
|
newTask: "新设备采集任务",
|
||||||
portMapping: "端口映射",
|
portMapping: "端口映射",
|
||||||
|
|
|
||||||
|
|
@ -784,80 +784,124 @@ export function BizMigrationPage() {
|
||||||
<Modal.Heading>{t("bizMigration.create")}</Modal.Heading>
|
<Modal.Heading>{t("bizMigration.create")}</Modal.Heading>
|
||||||
<Modal.CloseTrigger />
|
<Modal.CloseTrigger />
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body className="flex flex-col gap-3">
|
<Modal.Body className="flex flex-col gap-4">
|
||||||
<p className="muted">{t("bizMigration.createHint")}</p>
|
<p className="muted" style={{ margin: 0 }}>
|
||||||
<div className="form-grid">
|
{t("bizMigration.createHint")}
|
||||||
<label className="form-grid__full">
|
</p>
|
||||||
<span>{t("bizMigration.projectName")}</span>
|
|
||||||
<Input value={createName} onValueChange={setCreateName} size="sm" />
|
<div className="flex flex-col gap-2">
|
||||||
</label>
|
<strong style={{ fontSize: 13 }}>{t("bizMigration.sectionName")}</strong>
|
||||||
<label>
|
<Input
|
||||||
<span>{t("bizMigration.oldTask")}</span>
|
value={createName}
|
||||||
|
onChange={(e) => setCreateName(e.target.value)}
|
||||||
|
placeholder={t("bizMigration.projectNamePh")}
|
||||||
|
aria-label={t("bizMigration.projectName")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<strong style={{ fontSize: 13 }}>{t("bizMigration.sectionTasks")}</strong>
|
||||||
|
<p className="muted" style={{ margin: 0, fontSize: 12 }}>
|
||||||
|
{t("bizMigration.sectionTasksHint")}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "1fr 1fr",
|
||||||
|
gap: 12,
|
||||||
|
alignItems: "start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
|
label={t("bizMigration.oldTask")}
|
||||||
value={createOldTaskId}
|
value={createOldTaskId}
|
||||||
onChange={(e) => setCreateOldTaskId(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setCreateOldTaskId(e.target.value);
|
||||||
|
setCreateOldBaselineId("");
|
||||||
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
aria-label={t("bizMigration.oldTask")}
|
|
||||||
>
|
>
|
||||||
{taskOptions}
|
{taskOptions}
|
||||||
</FieldSelect>
|
</FieldSelect>
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<span>{t("bizMigration.newTask")}</span>
|
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
|
label={t("bizMigration.newTask")}
|
||||||
value={createNewTaskId}
|
value={createNewTaskId}
|
||||||
onChange={(e) => setCreateNewTaskId(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setCreateNewTaskId(e.target.value);
|
||||||
|
setCreateNewBaselineId("");
|
||||||
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
aria-label={t("bizMigration.newTask")}
|
|
||||||
>
|
>
|
||||||
{taskOptions}
|
{taskOptions}
|
||||||
</FieldSelect>
|
</FieldSelect>
|
||||||
</label>
|
</div>
|
||||||
<label>
|
{!tasks.length ? (
|
||||||
<span>{t("bizMigration.portMapping")}</span>
|
<p className="muted" style={{ margin: 0, fontSize: 12 }}>
|
||||||
<FieldSelect
|
{t("bizMigration.needBizStateTasks")}{" "}
|
||||||
value={createMappingId}
|
<Link to="/network/tasks/biz-state" onClick={closeCreate}>
|
||||||
onChange={(e) => setCreateMappingId(e.target.value)}
|
{t("bizMigration.openBizState")}
|
||||||
fullWidth
|
</Link>
|
||||||
aria-label={t("bizMigration.portMapping")}
|
</p>
|
||||||
>
|
) : null}
|
||||||
<option value="">—</option>
|
</div>
|
||||||
{mappings.map((m) => (
|
|
||||||
<option key={m.id} value={m.id}>
|
<div className="flex flex-col gap-2">
|
||||||
{m.name}
|
<strong style={{ fontSize: 13 }}>{t("bizMigration.sectionOptional")}</strong>
|
||||||
</option>
|
<p className="muted" style={{ margin: 0, fontSize: 12 }}>
|
||||||
))}
|
{t("bizMigration.sectionOptionalHint")}
|
||||||
</FieldSelect>
|
</p>
|
||||||
</label>
|
<FieldSelect
|
||||||
<label>
|
label={t("bizMigration.portMapping")}
|
||||||
<span>{t("bizMigration.oldBaseline")}</span>
|
value={createMappingId}
|
||||||
|
onChange={(e) => setCreateMappingId(e.target.value)}
|
||||||
|
fullWidth
|
||||||
|
hint={t("bizMigration.portMappingHint")}
|
||||||
|
>
|
||||||
|
<option value="">{t("bizMigration.optionalNone")}</option>
|
||||||
|
{mappings.map((m) => (
|
||||||
|
<option key={m.id} value={m.id}>
|
||||||
|
{m.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</FieldSelect>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "1fr 1fr",
|
||||||
|
gap: 12,
|
||||||
|
alignItems: "start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
|
label={t("bizMigration.oldBaseline")}
|
||||||
value={createOldBaselineId}
|
value={createOldBaselineId}
|
||||||
onChange={(e) => setCreateOldBaselineId(e.target.value)}
|
onChange={(e) => setCreateOldBaselineId(e.target.value)}
|
||||||
fullWidth
|
fullWidth
|
||||||
aria-label={t("bizMigration.oldBaseline")}
|
disabled={!createOldTaskId}
|
||||||
|
hint={
|
||||||
|
createOldTaskId
|
||||||
|
? t("bizMigration.baselinePickHint")
|
||||||
|
: t("bizMigration.baselineNeedTask")
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{batchOptions(createOldBatches)}
|
{batchOptions(createOldBatches)}
|
||||||
</FieldSelect>
|
</FieldSelect>
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<span>{t("bizMigration.newBaseline")}</span>
|
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
|
label={t("bizMigration.newBaseline")}
|
||||||
value={createNewBaselineId}
|
value={createNewBaselineId}
|
||||||
onChange={(e) => setCreateNewBaselineId(e.target.value)}
|
onChange={(e) => setCreateNewBaselineId(e.target.value)}
|
||||||
fullWidth
|
fullWidth
|
||||||
aria-label={t("bizMigration.newBaseline")}
|
disabled={!createNewTaskId}
|
||||||
|
hint={
|
||||||
|
createNewTaskId
|
||||||
|
? t("bizMigration.baselinePickHint")
|
||||||
|
: t("bizMigration.baselineNeedTask")
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{batchOptions(createNewBatches)}
|
{batchOptions(createNewBatches)}
|
||||||
</FieldSelect>
|
</FieldSelect>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!tasks.length ? (
|
|
||||||
<p className="muted">
|
|
||||||
{t("bizMigration.needBizStateTasks")}{" "}
|
|
||||||
<Link to="/network/tasks/biz-state">{t("bizMigration.openBizState")}</Link>
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<Button size="sm" variant="secondary" onPress={closeCreate}>
|
<Button size="sm" variant="secondary" onPress={closeCreate}>
|
||||||
|
|
@ -943,47 +987,97 @@ export function BizMigrationPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{detailTab === "setup" ? (
|
{detailTab === "setup" ? (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="form-grid">
|
<div className="flex flex-col gap-2">
|
||||||
<label>
|
<strong style={{ fontSize: 13 }}>{t("bizMigration.sectionPair")}</strong>
|
||||||
<span>{t("bizMigration.portMapping")}</span>
|
<p className="muted" style={{ margin: 0, fontSize: 12 }}>
|
||||||
<FieldSelect
|
{t("bizMigration.sectionPairHint")}
|
||||||
value={mappingId}
|
</p>
|
||||||
onChange={(e) => setMappingId(e.target.value)}
|
<div
|
||||||
fullWidth
|
style={{
|
||||||
aria-label={t("bizMigration.portMapping")}
|
display: "grid",
|
||||||
>
|
gridTemplateColumns: "1fr 1fr",
|
||||||
<option value="">—</option>
|
gap: 12,
|
||||||
{mappings.map((m) => (
|
}}
|
||||||
<option key={m.id} value={m.id}>
|
>
|
||||||
{m.name}
|
<div>
|
||||||
</option>
|
<div className="muted" style={{ fontSize: 12, marginBottom: 4 }}>
|
||||||
))}
|
{t("bizMigration.oldTask")}
|
||||||
</FieldSelect>
|
</div>
|
||||||
</label>
|
<div className="pt-list-task-name">
|
||||||
<label>
|
{project.old_task?.ne_name || project.old_task_id.slice(0, 8)}
|
||||||
<span>{t("bizMigration.oldBaseline")}</span>
|
</div>
|
||||||
|
<div className="muted" style={{ fontSize: 12 }}>
|
||||||
|
{project.old_task?.ne_ip || "—"}
|
||||||
|
{project.old_task?.note ? ` · ${project.old_task.note}` : ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="muted" style={{ fontSize: 12, marginBottom: 4 }}>
|
||||||
|
{t("bizMigration.newTask")}
|
||||||
|
</div>
|
||||||
|
<div className="pt-list-task-name">
|
||||||
|
{project.new_task?.ne_name || project.new_task_id.slice(0, 8)}
|
||||||
|
</div>
|
||||||
|
<div className="muted" style={{ fontSize: 12 }}>
|
||||||
|
{project.new_task?.ne_ip || "—"}
|
||||||
|
{project.new_task?.note ? ` · ${project.new_task.note}` : ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<strong style={{ fontSize: 13 }}>{t("bizMigration.portMapping")}</strong>
|
||||||
|
<FieldSelect
|
||||||
|
value={mappingId}
|
||||||
|
onChange={(e) => setMappingId(e.target.value)}
|
||||||
|
fullWidth
|
||||||
|
hint={t("bizMigration.portMappingHint")}
|
||||||
|
>
|
||||||
|
<option value="">{t("bizMigration.optionalNone")}</option>
|
||||||
|
{mappings.map((m) => (
|
||||||
|
<option key={m.id} value={m.id}>
|
||||||
|
{m.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</FieldSelect>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<strong style={{ fontSize: 13 }}>{t("bizMigration.sectionBaseline")}</strong>
|
||||||
|
<p className="muted" style={{ margin: 0, fontSize: 12 }}>
|
||||||
|
{t("bizMigration.sectionBaselineHint")}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "1fr 1fr",
|
||||||
|
gap: 12,
|
||||||
|
alignItems: "start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
|
label={t("bizMigration.oldBaseline")}
|
||||||
value={oldBaselineId}
|
value={oldBaselineId}
|
||||||
onChange={(e) => setOldBaselineId(e.target.value)}
|
onChange={(e) => setOldBaselineId(e.target.value)}
|
||||||
fullWidth
|
fullWidth
|
||||||
aria-label={t("bizMigration.oldBaseline")}
|
hint={t("bizMigration.baselinePickHint")}
|
||||||
>
|
>
|
||||||
{batchOptions(oldBatches)}
|
{batchOptions(oldBatches)}
|
||||||
</FieldSelect>
|
</FieldSelect>
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<span>{t("bizMigration.newBaseline")}</span>
|
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
|
label={t("bizMigration.newBaseline")}
|
||||||
value={newBaselineId}
|
value={newBaselineId}
|
||||||
onChange={(e) => setNewBaselineId(e.target.value)}
|
onChange={(e) => setNewBaselineId(e.target.value)}
|
||||||
fullWidth
|
fullWidth
|
||||||
aria-label={t("bizMigration.newBaseline")}
|
hint={t("bizMigration.baselinePickHint")}
|
||||||
>
|
>
|
||||||
{batchOptions(newBatches)}
|
{batchOptions(newBatches)}
|
||||||
</FieldSelect>
|
</FieldSelect>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="btn-row">
|
<div className="btn-row">
|
||||||
<Button size="sm" variant="primary" isDisabled={busy} onPress={() => void onSaveBaseline()}>
|
<Button size="sm" variant="primary" isDisabled={busy} onPress={() => void onSaveBaseline()}>
|
||||||
{t("bizMigration.saveBaseline")}
|
{t("bizMigration.saveBaseline")}
|
||||||
|
|
@ -1002,7 +1096,7 @@ export function BizMigrationPage() {
|
||||||
<Input
|
<Input
|
||||||
size="sm"
|
size="sm"
|
||||||
value={portFilter}
|
value={portFilter}
|
||||||
onValueChange={setPortFilter}
|
onChange={(e) => setPortFilter(e.target.value)}
|
||||||
placeholder={t("bizMigration.portFilterPh")}
|
placeholder={t("bizMigration.portFilterPh")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1060,11 +1154,11 @@ export function BizMigrationPage() {
|
||||||
|
|
||||||
<div className="btn-row" style={{ flexWrap: "wrap", gap: 8, alignItems: "end" }}>
|
<div className="btn-row" style={{ flexWrap: "wrap", gap: 8, alignItems: "end" }}>
|
||||||
<Input
|
<Input
|
||||||
label={t("bizMigration.batchLabel")}
|
|
||||||
value={batchLabel}
|
value={batchLabel}
|
||||||
onValueChange={setBatchLabel}
|
onChange={(e) => setBatchLabel(e.target.value)}
|
||||||
size="sm"
|
size="sm"
|
||||||
placeholder={t("bizMigration.defaultBatchLabel")}
|
placeholder={t("bizMigration.defaultBatchLabel")}
|
||||||
|
aria-label={t("bizMigration.batchLabel")}
|
||||||
style={{ minWidth: 160 }}
|
style={{ minWidth: 160 }}
|
||||||
/>
|
/>
|
||||||
<Button size="sm" variant="primary" isDisabled={busy} onPress={() => void onCreateBatch()}>
|
<Button size="sm" variant="primary" isDisabled={busy} onPress={() => void onCreateBatch()}>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue