diff --git a/web/src/index.css b/web/src/index.css index 898963b..78c852c 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -3519,104 +3519,20 @@ pre { flex-shrink: 0; display: flex; flex-direction: column; - background: #f7f9fc; - border-right: 1px solid #d0d7e2; + background: #f8fafc; + border-right: 1px solid #d8dee8; min-height: 0; - transition: width 0.18s ease; -} - -.network-shell.is-nav-collapsed .network-nav { - width: 48px; -} - -.network-nav__head { - display: flex; - align-items: center; - gap: 8px; - padding: 10px 8px 8px 12px; - border-bottom: 1px solid #e2e8f0; - min-height: 44px; -} - -.network-shell.is-nav-collapsed .network-nav__head { - justify-content: center; - padding: 10px 4px; -} - -.network-nav__title { - flex: 1; - min-width: 0; - font-size: 13px; - font-weight: 700; - color: #0f172a; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.app-main .network-nav__collapse-btn, -.network-nav__collapse-btn { - flex-shrink: 0; - width: 28px; - height: 28px; - padding: 0; - border: 0; - border-radius: 6px; - background: transparent; - color: #64748b; - cursor: pointer; - line-height: 1; - box-shadow: none; -} - -.app-main .network-nav__collapse-btn:hover, -.network-nav__collapse-btn:hover { - background: #eef2f7; - color: #0f172a; -} - -.network-nav__rail { - display: flex; - flex-direction: column; - align-items: center; - gap: 6px; - padding: 10px 4px; - overflow: auto; -} - -.network-nav__rail-link { - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - border-radius: 8px; - text-decoration: none; - color: #475569; - font-size: 12px; - font-weight: 700; - background: transparent; -} - -.network-nav__rail-link:hover { - background: #eef2f7; - color: #0f172a; -} - -.network-nav__rail-link.is-active { - background: #e3effc; - color: #1565c0; } .network-nav__scroll { flex: 1; min-height: 0; overflow: auto; - padding: 6px 0 16px; + padding: 10px 0 16px; } .network-nav__group { - margin: 0; + margin: 0 0 4px; } .app-main .network-nav__group-toggle, @@ -3625,13 +3541,14 @@ pre { align-items: center; gap: 6px; width: 100%; - padding: 10px 12px; + padding: 9px 14px; border: 0; border-radius: 0; background: transparent; - color: #0f172a; - font-size: 13px; - font-weight: 600; + color: #334155; + font-size: 12px; + font-weight: 650; + letter-spacing: 0.02em; text-align: left; cursor: pointer; } @@ -3639,6 +3556,7 @@ pre { .app-main .network-nav__group-toggle:hover, .network-nav__group-toggle:hover { background: #eef2f7; + color: #0f172a; } .network-nav__group.is-active-group .network-nav__group-toggle { @@ -3648,8 +3566,8 @@ pre { .network-nav__chevron { display: inline-block; width: 1em; - color: #64748b; - font-size: 12px; + color: #94a3b8; + font-size: 11px; line-height: 1; } @@ -3661,16 +3579,18 @@ pre { .network-nav__list { list-style: none; margin: 0; - padding: 0 0 6px; + padding: 0 8px 6px 10px; } .network-nav__link { display: block; - padding: 7px 12px 7px 30px; - color: #334155; + padding: 7px 12px 7px 18px; + border-radius: 6px; + color: #475569; text-decoration: none; font-size: 13px; - border-left: 3px solid transparent; + line-height: 1.35; + border-left: 2px solid transparent; } .network-nav__link:hover { @@ -3679,10 +3599,10 @@ pre { } .network-nav__link.is-active { - background: #e3effc; + background: #e8f1fc; color: #1565c0; - border-left-color: #1565c0; font-weight: 600; + border-left-color: #1565c0; } .network-main { @@ -3690,10 +3610,130 @@ pre { min-width: 0; min-height: 0; overflow: auto; - padding: 16px 20px 24px; + padding: 16px 18px 24px; background: #eef2f7; } +.network-main > .panel { + padding: 16px 18px 18px; +} + +.network-main .panel__toolbar { + margin-bottom: 14px; + padding-bottom: 12px; + border-bottom: 1px solid #e8edf3; +} + +.network-main .panel__toolbar h2 { + font-size: 17px; + font-weight: 650; + color: #0f172a; + letter-spacing: -0.01em; +} + +.network-main .filter-inline { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + margin-bottom: 0; + padding: 10px 12px; + border: 1px solid #e2e8f0; + border-radius: 8px; + background: #f8fafc; + grid-template-columns: none; +} + +.network-main .filter-inline input, +.network-main .filter-inline select { + width: auto; + min-width: 140px; + flex: 1 1 160px; + max-width: 280px; + height: 32px; + margin: 0; + border: 1px solid #cbd5e1; + border-radius: 6px; + background: #fff; + padding: 0 10px; + font-size: 13px; +} + +.network-main .filter-inline button { + height: 32px; + flex: 0 0 auto; +} + +.network-main .pt-list { + gap: 12px; +} + +.network-main .pager.pt-list-pager, +.network-main .pt-list-pager { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-top: 2px; +} + +.pt-list-status--ok, +.pt-list-status--up, +.pt-list-status--connected { + background: #dcfce7; + color: #166534; +} + +.pt-list-status--down, +.pt-list-status--failed, +.pt-list-status--critical { + background: #fee2e2; + color: #991b1b; +} + +.pt-list-status--major { + background: #ffedd5; + color: #9a3412; +} + +.pt-list-status--minor, +.pt-list-status--warning { + background: #fef9c3; + color: #854d0e; +} + +.pt-list-status--info { + background: #e0f2fe; + color: #075985; +} + +.pt-list-status--unknown { + background: #f1f5f9; + color: #475569; +} + +.network-main .nm-config-view { + margin-top: 14px; + padding-top: 14px; + border-top: 1px solid #e2e8f0; +} + +.network-main .nm-config-view textarea { + width: 100%; + min-height: 420px; + margin: 0; + padding: 12px 14px; + border: 1px solid #e2e8f0; + border-radius: 8px; + background: #0b1220; + color: #e2e8f0; + font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 12px; + line-height: 1.45; + resize: vertical; +} + .network-main:has(.topo-page), .network-main:has(.webcrt-shell), .network-main:has(.pt-wall-shell) { diff --git a/web/src/pages/CollectPage.tsx b/web/src/pages/CollectPage.tsx index 011a5cb..2dbcdf6 100644 --- a/web/src/pages/CollectPage.tsx +++ b/web/src/pages/CollectPage.tsx @@ -445,46 +445,62 @@ export function CollectPage() { {jobsQuery.isFetching ? t("common.refreshing") : t("common.refresh")} - - - - - - - - - - - - - {(jobsQuery.data?.items ?? []).map((job) => ( - setExpandedJobId(expandedJobId === job.id ? "" : job.id)} - onPause={() => pauseMutation.mutate(job.id)} - onStart={() => startJobMutation.mutate(job.id)} - onRetryFailed={() => retryFailedMutation.mutate(job.id)} - onDelete={() => { - if (window.confirm(t("collect.confirmDelete"))) deleteMutation.mutate(job.id); - }} - actionPending={actionPending} - startPending={startJobMutation.isPending} - /> - ))} - -
{t("collect.jobs.col.title")}{t("collect.jobs.col.status")}{t("collect.jobs.col.progress")}{t("collect.jobs.col.created")}{t("collect.jobs.col.lastRun")}{t("collect.jobs.col.actions")}
-
-
{t("common.pagerMeta", { total: jobTotal, page: jobPage, pages: jobPages })}
-
- - +
+ {!(jobsQuery.data?.items ?? []).length && !jobsQuery.isLoading ? ( +
+

{t("common.empty")}

+
+ ) : ( +
+ + + + + + + + + + + + + {(jobsQuery.data?.items ?? []).map((job) => ( + setExpandedJobId(expandedJobId === job.id ? "" : job.id)} + onPause={() => pauseMutation.mutate(job.id)} + onStart={() => startJobMutation.mutate(job.id)} + onRetryFailed={() => retryFailedMutation.mutate(job.id)} + onDelete={() => { + if (window.confirm(t("collect.confirmDelete"))) deleteMutation.mutate(job.id); + }} + actionPending={actionPending} + startPending={startJobMutation.isPending} + /> + ))} + +
{t("collect.jobs.col.title")}{t("collect.jobs.col.status")}{t("collect.jobs.col.progress")}{t("collect.jobs.col.created")}{t("collect.jobs.col.lastRun")}{t("collect.jobs.col.actions")}
+
+ )} +
+
+ {t("common.pagerMeta", { + total: String(jobTotal), + page: String(jobPage), + pages: String(jobPages), + })} +
+
+ + +
diff --git a/web/src/pages/ConfigSyncPage.tsx b/web/src/pages/ConfigSyncPage.tsx index abbdeb2..92f61ff 100644 --- a/web/src/pages/ConfigSyncPage.tsx +++ b/web/src/pages/ConfigSyncPage.tsx @@ -229,30 +229,34 @@ export function ConfigSyncPage() {
-
-
-
{t("configSync.kpi.snapshots")}
- {dash?.snapshot_count ?? "-"} -
-
-
{t("configSync.kpi.running")}
- - {running - ? `${running.status} · ${running.success_count}/${running.planned_count}` - : t("configSync.kpi.idle")} - -
-
-
{t("configSync.kpi.last")}
- - {last - ? `${last.status} · ok ${last.success_count} / fail ${last.fail_count}` - : t("common.empty")} - -
-
-
{t("configSync.kpi.nextDue")}
- {dash?.next_due_at ? formatSystemTime(dash.next_due_at) : t("common.empty")} +
+
+
+
{t("configSync.kpi.snapshots")}
+
{dash?.snapshot_count ?? "—"}
+
+
+
{t("configSync.kpi.running")}
+
+ {running + ? `${running.status} · ${running.success_count}/${running.planned_count}` + : t("configSync.kpi.idle")} +
+
+
+
{t("configSync.kpi.last")}
+
+ {last + ? `${last.status} · ok ${last.success_count} / fail ${last.fail_count}` + : t("common.empty")} +
+
+
+
{t("configSync.kpi.nextDue")}
+
+ {dash?.next_due_at ? formatSystemTime(dash.next_due_at) : t("common.empty")} +
+
@@ -382,7 +386,8 @@ export function ConfigSyncPage() {

{t("configSync.cyclesTitle")}

- +
+
+ - + @@ -453,16 +474,23 @@ export function ConfigSyncPage() { ) : null}
@@ -409,9 +414,25 @@ export function ConfigSyncPage() { {expandedCycleId === c.id ? t("configSync.collapse") : t("configSync.expand")} - {c.id.slice(0, 8)}{c.id.slice(0, 8)} {c.trigger_mode}{c.status} + + {c.status} + + {c.success_count}/{c.planned_count} · fail {c.fail_count}
-
+
+
+ + {t("common.pagerMeta", { + total: String(cycleTotal), + page: String(cyclePage), + pages: String(cyclePages), + })} + +
- - {t("common.pagerMeta", { total: String(cycleTotal), page: String(cyclePage), pages: String(cyclePages) })} - +
{expandedCycleId ? ( diff --git a/web/src/pages/network/NetworkAlarmsPage.tsx b/web/src/pages/network/NetworkAlarmsPage.tsx index 07d0159..8a6ac6f 100644 --- a/web/src/pages/network/NetworkAlarmsPage.tsx +++ b/web/src/pages/network/NetworkAlarmsPage.tsx @@ -6,6 +6,16 @@ import { useI18n } from "../../i18n"; import { pageCount } from "../../utils/display"; import { formatSystemTime } from "../../utils/time"; +function severityClass(sev: string | null | undefined): string { + const s = String(sev || "").trim().toLowerCase(); + if (s.includes("critical")) return "pt-list-status--critical"; + if (s.includes("major")) return "pt-list-status--major"; + if (s.includes("minor")) return "pt-list-status--minor"; + if (s.includes("warning") || s.includes("warn")) return "pt-list-status--warning"; + if (s.includes("info") || s.includes("indeterminate")) return "pt-list-status--info"; + return "pt-list-status--unknown"; +} + /** Current UME alarms query view for Network Management. */ export function NetworkAlarmsPage() { const { t } = useI18n(); @@ -17,7 +27,14 @@ export function NetworkAlarmsPage() { const [curPageSize, setCurPageSize] = useState(50); const currentQuery = useQuery({ - queryKey: queryKeys.umeCurrentAlarms(curSeverity, curCleared, curHostName, curKeyword, curPage, curPageSize), + queryKey: queryKeys.umeCurrentAlarms( + curSeverity, + curCleared, + curHostName, + curKeyword, + curPage, + curPageSize, + ), queryFn: () => fetchUmeCurrentAlarms({ severity: curSeverity, @@ -30,6 +47,7 @@ export function NetworkAlarmsPage() { staleTime: 5000, }); + const items = currentQuery.data?.items || []; const curTotal = Number(currentQuery.data?.total || 0); const curPages = pageCount(curTotal, curPageSize); const perPage = (n: number) => t("common.perPage", { n: String(n) }); @@ -40,130 +58,164 @@ export function NetworkAlarmsPage() {

{t("ume.alarms.title")}

-
- { - setCurKeyword(e.target.value); - setCurPage(1); - }} - /> - { - setCurHostName(e.target.value); - setCurPage(1); - }} - /> - - - -
- {currentQuery.isLoading ?

{t("common.refreshing")}

: null} - {currentQuery.isError ?

{t("common.opFailed")}

: null} - - - - - - - - - - - - - {(currentQuery.data?.items || []).map((x) => ( - - - - - - - - - ))} - -
time_createdseverityne_idhost_namene_typecause
{formatSystemTime(x.time_created)}{x.perceived_severity}{x.ne_id} - {(x.host_name || "").trim() ? ( - - ) : ( - - {t("common.empty")} - - )} - {x.ne_type ?? ""}{x.native_probable_cause}
-
-
{t("common.pagerMeta", { total: curTotal, page: curPage, pages: curPages })}
-
- - - { - setCurPageSize(Number(e.target.value) || 50); + setCurKeyword(e.target.value); + setCurPage(1); + }} + /> + { + setCurHostName(e.target.value); + setCurPage(1); + }} + /> + + + +
+ + {currentQuery.isLoading ?

{t("common.refreshing")}

: null} + {currentQuery.isError ?

{t("common.opFailed")}

: null} + + {!items.length && !currentQuery.isLoading ? ( +
+

{t("common.empty")}

+
+ ) : ( +
+ + + + + + + + + + + + + {items.map((x) => ( + + + + + + + + + ))} + +
time_createdseverityne_idhost_namene_typecause
{formatSystemTime(x.time_created)} + + {x.perceived_severity || "—"} + + {x.ne_id} + {(x.host_name || "").trim() ? ( + + ) : ( + + {t("common.empty")} + + )} + {x.ne_type ?? ""}{x.native_probable_cause}
+
+ )} + +
+
+ {t("common.pagerMeta", { + total: String(curTotal), + page: String(curPage), + pages: String(curPages), + })} +
+
+ + + +
diff --git a/web/src/pages/network/NetworkConfigsPage.tsx b/web/src/pages/network/NetworkConfigsPage.tsx index 0faac0c..7371254 100644 --- a/web/src/pages/network/NetworkConfigsPage.tsx +++ b/web/src/pages/network/NetworkConfigsPage.tsx @@ -49,6 +49,7 @@ export function NetworkConfigsPage() { const pages = pageCount(total, 20); const detail = detailQuery.data; const showAlt = Boolean(detail?.has_alt); + const hasFilters = Boolean(keyword || source || vendor); const exportConfig = async ( src: string, @@ -73,200 +74,216 @@ export function NetworkConfigsPage() {

{t("networkConfigs.title")}

-
- { - setKeyword(e.target.value); - setPage(1); - }} - /> - - { - setVendor(e.target.value); - setPage(1); - }} - /> - -
+
+
+ { + setKeyword(e.target.value); + setPage(1); + }} + /> + + { + setVendor(e.target.value); + setPage(1); + }} + /> + +
- {listQuery.isLoading ?

{t("common.refreshing")}

: null} + {listQuery.isLoading ?

{t("common.refreshing")}

: null} - - - - - - - - - - - - - {items.map((row) => { - const exportKey = `${row.source}:${row.target_id}:list`; - return ( - - - - - - - - - - ); - })} - {!items.length && !listQuery.isLoading ? ( - - - - ) : null} - -
{t("networkConfigs.col.name")}IP{t("networkConfigs.col.vendor")}{t("networkConfigs.col.source")}{t("networkConfigs.col.size")}{t("networkConfigs.col.collected")} -
{row.ne_name || row.target_id}{row.ne_ip}{row.vendor || "-"}{row.source}{fmtBytes(row.plain_size)}{row.collected_at ? formatSystemTime(row.collected_at) : "-"} - - - -
- {t("networkConfigs.empty")} -
+ {!items.length && !listQuery.isLoading ? ( +
+

{t("networkConfigs.empty")}

+
+ ) : ( +
+ + + + + + + + + + + + + + {items.map((row) => { + const exportKey = `${row.source}:${row.target_id}:list`; + return ( + + + + + + + + + + ); + })} + +
{t("networkConfigs.col.name")}IP{t("networkConfigs.col.vendor")}{t("networkConfigs.col.source")}{t("networkConfigs.col.size")}{t("networkConfigs.col.collected")}{t("portTraffic.col.actions")}
{row.ne_name || row.target_id}{row.ne_ip}{row.vendor || "—"}{row.source}{fmtBytes(row.plain_size)} + {row.collected_at ? formatSystemTime(row.collected_at) : "—"} + +
+ + + +
+
+
+ )} -
- - - {t("common.pagerMeta", { total: String(total), page: String(page), pages: String(pages) })} - - -
+
+ + {t("common.pagerMeta", { + total: String(total), + page: String(page), + pages: String(pages), + })} + +
+ + +
+
- {selected ? ( -
-
-

- {detail?.ne_name || selected.id}{" "} - - ({selected.source} / {detail?.ne_ip || "-"}) - -

-
- - {showAlt ? ( + {selected ? ( +
+
+

+ {detail?.ne_name || selected.id}{" "} + + ({selected.source} / {detail?.ne_ip || "—"}) + +

+
- ) : null} - + {showAlt ? ( + + ) : null} + +
+ {detailQuery.isLoading ?

{t("common.refreshing")}

: null} + {showAlt ? ( +
+ + +
+ ) : null} +