Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,56 @@ Maintained in the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) forma

## [Unreleased]

### Review fixes: Cesium scenes, basemap chain and navigation (2026-10-19)

- **Military symbols**: unknown and hostile SIDCs were written as `UFA-…`/`HFA-…` (affiliation in the coding-scheme position); milsymbol rejected them and drew them as **friendly** blue frames, so the Situation page's hostile and unknown tracks contradicted their red/yellow labels and trails. Sea-surface function codes (`MS`) were invalid too. The table now uses valid codes (`symbols.test.ts` asserts validity and affiliation for every entry), and `militarySymbol` returns the real `width`/`height` plus an anchor `offset` — the labelled station symbol (95×41) used to be stretched into a square
- **Direction**: targets vanished after 20 minutes of the 12 h clock window (positions were sampled only for 0–1200 s with no extrapolation). Positions are now evaluated analytically per frame (`CallbackPositionProperty`, the same function as the margin panel). The polar chart follows the site theme instead of a hard-coded `theme="dark"`
- **GeoHash**: clicking a cell flies in again — the handler read the `viewer` state captured by `onReady` (always `null` on first mount, a destroyed instance after a basemap retry)
- **TimeDeck**: re-reads the clock range when a scene configures it after mount (Trajectory's slider was stuck on Cesium's default 24 h window) and updates at most per second / 10 Hz instead of re-rendering on every Cesium tick, paused or not
- **Situation**: the event feed recovers after the 12 h loop wraps or the user scrubs backwards (it used to stay empty while the alert KPI restarted)
- **Heatmap**: the click probe uses the current transmitter powers and re-computes when a slider moves; a worker that fails to load (chunk 404, no module-worker support) now falls back to the main-thread sampler instead of leaving the layer blank
- **Scene HUD**: the top-right action bar moved left of Cesium's toolbar, which it used to cover (Home and the 2D/3D picker kept on purpose); the toolbar sits above the glass panels so the picker can expand
- **Basemap failure detection**: the AMap fallback host is now observed (`TILE_HOSTS`); HTTP-level tile errors (429/403/5xx, invisible to the XHR `error` event) come from the current base layer's `imageryProvider.errorEvent`, re-attached on every layer swap (the old listener sat on `ImageryLayer.errorEvent`, which only reports provider-creation failures, and only incremented a counter); network failures count only for the current provider's host, so in-flight requests of a removed layer no longer push the new one over the threshold; the AMap probe no longer raises the banner once the carto upgrade has taken over (OSM and AMap blocked + carto reachable used to flag every Cesium page), and only one AMap probe runs at a time
- **Navigation**: clicking a group title goes to the first visible page the role can access (`firstAccessibleLeaf` in `src/utils/routeNav.ts`) — the static `/system` → `/system/users` redirect sent operator/analyst/viewer, who still see the group because `/system/access` has no access key, to a 403. The command palette excludes `hideInMenu` and wildcard routes (sign-in and the `*` 404 were listed) and ignores Enter/arrow keys while an IME is composing
- **CI**: the e2e basemap failure test also blocks `*.autonavi.com` (the AMap fallback answered 200 with `access-control-allow-origin: *`, so the banner never appeared); the e2e console whitelist includes `basemaps.cartocdn.com`; `size-budget.json` raises `publicMB` from 4 to 4.5 for the 586 KB glTF (public measured 3.82 MB, over the old 3.8 MB headroom line)
- **Tests**: new `symbols.test.ts` and `routeNav.test.ts`; palette tests cover hidden and wildcard routes; lifecycle tests cover the host filter and HTTP-level provider errors
- **Low-severity follow-ups**: Trajectory replays on the new viewer after a basemap retry and swaps in the glTF aircraft when its HEAD check resolves after the viewer is ready (unless the user started a custom route); the coverage corridor actually grows (it was re-sliced from its own prefix and stuck at two samples) and no longer runs ahead of the aircraft when speed ≠ 1. Situation's KPI/event heartbeat is throttled to ≤4 Hz by wall clock (it re-rendered every frame at ×120/×600). `useCesiumViewer` tears down the viewer, tile observer and rejection listener when a page's `onReady` throws, logs it and shows the error state. BoxSelect tools tolerate a null viewer; `polarPattern` returns `azimuthDeg`; the unused `meta.fl`, `landOn`, `followEntity` and `tileObserverInstalled` are gone; en-US labels fixed (Sample quality, Trails, East China Sea); the group-nav e2e matches "Audit log". New `fieldModel.test.ts`; situation and station-fleet tests cover enter/exit pairing, determinism, weight proportionality and tiny counts
- **Last four low-severity items**: the heatmap's propagation distance scales longitude by cos(latitude) (`planarDistanceKm`, within 0.5% of the haversine probe; east–west distances used to be ~15% too long, squashing the isolines against the coverage circles); GroupNav and the command palette share `canAccessRoute` with plugin-access semantics, so an access key missing from `access.ts` hides the page there too instead of showing a link into a 403; the palette input is a proper `combobox` (`aria-controls`, `aria-activedescendant`, empty state moved out of the listbox as a `status`); the Cesium scene bar scrolls horizontally on narrow screens and keeps the selected scene in view instead of truncating every label

### Cesium situational scenes (2026-10-18)

- **Situation**: rebuilt on a deterministic maritime/air model (`situationModel.ts`: core/warning/watch zones, six formation templates, 61 tracks with AIS/flight metadata, zone enter/exit events computed offline and streamed into a live feed during replay), MIL-STD-2525C symbols drawn with milsymbol (`components/CesiumViewer/symbols.ts`), a shared affiliation/accent palette (`palette.ts`) and a keyframe camera tour (`CameraDirector.ts`)
- **One clock for six scenes**: `TimeDeck` binds play/pause, speed and scrubbing to `viewer.clock`, replacing per-page `performance.now()` heartbeats
- **GeoHash**: a synthetic 8000-station fleet (`stationFleet.ts`, 34 provincial clusters, fixed seed) alongside the live 240 devices; extruded density cells grow in on precision/source changes
- **Heatmap**: the field is computed in a Web Worker (`heatWorker.ts`) and rendered through an ImageData colour LUT with d3-contour isolines (`heatRender.ts`); `fieldModel.ts` rewritten
- **Trajectory**: a glTF patrol aircraft (`public/assets/models/aircraft.glb`, the Apache-2.0 Cesium_Air sample) with a canvas billboard fallback when the model is unreachable
- **Basemap chain without an ion token**: a colour-graded ("night") OSM layer by default; a pixel-checked probe upgrades to CartoDB Dark Matter; when OSM is unreachable the AMap raster tiles are the last fallback (GCJ-02, basemap only — not used for measurements); the failure banner shows only when all of them fail. `https://*.basemaps.cartocdn.com` is allowed in img-src/connect-src in all four CSP copies
- **HUD for all six scenes**: compass and distance legend (cesium-navigation-es6), graticule toggle, MGRS cursor readout
- **Dependencies**: `milsymbol` 3.0.4, `mgrs` 2.2.0

### Two navigation fixes (2026-10-18)

- **Clicking a top-level group now enters its default page** (`/system` → users; since 2026-10-19 the first page the role can access, see below). Implemented with `subMenuItemRender` wrapping submenu titles in a click handler: `postMenuData`/`onTitleClick` injection does not work here because pro-components 3.1.14's internal build mangles BaseMenu's prop to `n`, while `subMenuItemRender` is a plain-name prop passed verbatim through the ProLayout → Header → TopNavHeader → BaseMenu `{...props}` chain. Nested group titles in the dropdown (Cesium) navigate the same way; on phones (≤768px, where the header collapses to a drawer) the click handler defers to the drawer's native expand
- **Fixed the tab row jumping on page entry**. ProLayout's content `main` carries a default `padding: 32px 40px` and only drops it (`has-page-container` class) once a PageContainer mounts — on a route's first visit the lazy-chunk frame renders GroupNav inside that padding at (88, 40), then it snaps to (56, 0). A `:has([data-tour="groupnav"])` rule zeroes the content padding whenever the tab row is present (the steady state already is zero), and GroupNav now stays mounted with an empty selection through group-root redirect frames (`/system` → `/system/users`) instead of unmounting for one frame

### Layout polish: hierarchy, dropdowns and the command palette (2026-10-18)

- **Visual hierarchy**: the group tab row is now the single emphasis — 40px tall, `colorSplit` bottom hairline, left edge aligned to the same 40px reference line as page content (was 24px/misaligned). Group pages no longer repeat the page name in a large PageContainer title: DemoPage collapses to a compact row (14px secondary description + page actions), cutting the page header from ~80px to ~36px; standalone pages (dashboard) keep the full header. The group name rides the right end of the tab row as a small uppercase tertiary tag — scroll context without breaking the left reference line
- **Header dropdowns restored**: top-level groups carry their children again (hover/click popup, access-filtered), so any page is one click from anywhere instead of top-group + tab; on phones the hamburger drawer gets the full two-level tree back
- **Command palette (⌘K / Ctrl+K)**: `src/components/CommandPalette` searches every accessible leaf page by localized name or path (menu.* keys, same cumulative rules as titles), keyboard-first (↑↓ / Enter / Esc), with a header search button as the discoverable entry. Traverses umi's unnamed layout wrappers in clientRoutes — a plain name-based walk yields nothing
- **Cesium scene bar**: sticky second row (top: 96px) visually identical to the group tabs, each scene with a semantic icon (grid / aim / heatmap / route / select / picture) so the scene switcher reads as navigation while the in-page `Segmented` controls stay parameters
- **Interaction details**: switching tabs or palette entries resets scroll to top (long tables used to land at the old offset); the tab row scrolls horizontally instead of antd's «···» overflow (a collapsed selected tab loses the current position) and auto-scrolls the selection into view; route changes fade content in over 150ms, disabled under `prefers-reduced-motion`
- **Tests**: palette flatten/filter unit tests (including the unnamed-wrapper case), GroupNav tests updated for the no-selection guard

### Layout refactor: top nav + in-content group tabs (2026-10-18)

- **Navigation**: the `mix` layout (top-level groups in the header + a second menu column on the left) is replaced by a two-level scheme with a single navigation shell — `layout: 'top'` carries the eight first-level groups in the header (their dropdowns were briefly dropped here and restored the same day, see "Layout polish" above; no route sets `hideChildrenInMenu`), and the second level moved into `GroupNav`, a sticky horizontal menu at the top of the content area (`src/components/GroupNav`). The content region reclaims the former 208px sider width; page URLs, access keys and legacy redirects are untouched
- **GroupNav** filters items with the same access semantics as the ProLayout menu, reuses the `menu.<group>.<page>` i18n keys, highlights the current page by longest-prefix match (`/map/cesium/geohash` → the Cesium tab), keeps antd Menu's keyboard navigation but scrolls horizontally (`disabledOverflow`) instead of collapsing into «···», and exposes a `<nav aria-label>` landmark. Single pages (dashboard) and groups whose items are all filtered out render no bar
- **Cesium third level dissolved**: `/map/cesium` gains a scene shell (`src/pages/Map/Cesium/Layout.tsx`) — a `Segmented` control over the six 3D scenes with the active child rendered in an `<Outlet />`. The only third-level menu in the app becomes in-page switching; the six scene routes, their paths and permissions are unchanged
- **DemoPage**: page titles are now computed from the matched route chain (`useNamedChain` + `menuKeyOf`, the same source as `document.title`) instead of the ProLayout menu context, which no longer contains second-level items; breadcrumbs are dropped (the group tab row already states the context)
- **Guide tour**: new "pages within the group" step pointing at the tab row (only shown when it exists, i.e. not on the dashboard)
- **Tests**: new unit tests cover GroupNav access filtering, selection and the null cases; `e2e/flows.spec.ts` gains a smoke test that switches pages via the tab row. Demo docs no longer reference the left menu

### PDF page on browsers without ES2026 built-ins (2026-10-16)

- **Fix**: on Chromium 141 the PDF page crashed into the error boundary with `getOrInsertComputed is not a function`. pdfjs-dist 6's modern build calls `Map`/`WeakMap.prototype.getOrInsert(Computed)` (native from Chrome/Edge 145, Firefox 144, Safari 26.2) and, while converting embedded fonts in the worker, `Math.sumPrecise` (Chrome/Edge 147, Firefox 137, Safari 26.2). Without `Math.sumPrecise`, embedded fonts silently fell back to system fonts. `src/pages/Document/Pdf/pdfjsPolyfills.mjs` now defines these only when missing and never replaces a native implementation. It loads on the main thread before react-pdf, and in the worker through a blob entry module (`pdfWorkerSrc.ts`) that imports it before pdf.js's own worker, so pdf.js still creates and terminates one worker per document
Expand Down
11 changes: 8 additions & 3 deletions config/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
* 升级加载器必须重新推导主机,scripts/csp-amap-pinning.test.ts 把包版本、注入地址与来源清单绑在一起。
* 页面只有配置 AMAP_KEY 才调用 load(),加载器内置的共享 key(es/config.js)不会被使用。
* - style 'unsafe-inline':antd cssinjs 运行时注入 + tailwind 任意值
* - img/connect 保留 *.amap.com / *.autonavi.com:高德瓦片与数据服务走图片/XHR,不是脚本来源
* openstreetmap:瓦片(OpenLayers 走 <img>,Cesium 无 token 回落走 XHR,故 img/connect 均列出);cesium.com:ion 影像
* - virtualearth.net:显式配置 ion token 的 Cesium 影像可能引用 Bing;无 token 时仅使用 HTTPS OSM
* - img/connect 保留 *.amap.com / *.autonavi.com:高德瓦片与数据服务走图片/XHR,不是脚本来源;
* Cesium 无 token 链路在 OSM 不可达时也用高德瓦片兜底
* openstreetmap:瓦片(OpenLayers 走 <img>,Cesium 无 token 默认底图走 XHR,故 img/connect 均列出);cesium.com:ion 影像
* basemaps.cartocdn.com:Cesium 无 token 时探针通过后升级的暗色底图(CartoDB Dark Matter,OSM 数据的 CDN 分发)
* - virtualearth.net:显式配置 ion token 的 Cesium 影像可能引用 Bing;无 token 时不涉及
* (无 token 链路:「夜航滤镜」OSM → carto 探针升级 → OSM 不可达时高德兜底)
* - media blob::wavesurfer 7 把音频解码后以 blob URL 交给 <audio> 播放(缺了它音频可视页无法播放)
* - huoshanstatic:xgplayer 演示视频
*/
Expand All @@ -34,6 +37,7 @@ export const CSP_DIRECTIVES: [string, string[]][] = [
'https://*.amap.com',
'https://*.autonavi.com',
'https://tile.openstreetmap.org',
'https://*.basemaps.cartocdn.com',
'https://ion.cesium.com',
'https://api.cesium.com',
'https://assets.ion.cesium.com',
Expand All @@ -48,6 +52,7 @@ export const CSP_DIRECTIVES: [string, string[]][] = [
'https://*.amap.com',
'https://*.autonavi.com',
'https://tile.openstreetmap.org',
'https://*.basemaps.cartocdn.com',
'https://ion.cesium.com',
'https://api.cesium.com',
'https://assets.ion.cesium.com',
Expand Down
2 changes: 2 additions & 0 deletions config/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ export const routes: RouteItem[] = [
name: 'cesium',
path: '/map/cesium',
icon: 'GlobalOutlined',
// 场景壳:页内 Segmented 切换六个三维场景,子路由渲染在 Outlet(三级菜单从此不存在)
component: './Map/Cesium/Layout',
routes: [
{ path: '/map/cesium', redirect: '/map/cesium/geohash' },
{ name: 'geohash', path: '/map/cesium/geohash', component: './Map/Cesium/GeoHash' },
Expand Down
4 changes: 2 additions & 2 deletions docs/demo/demo-script.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
- **语义对象 ID**:无(看的是权限行为;告警仍以 `ALM-SCN-*` 为例)。
- **预期可见结果**:
- 工作台指标与刚才 admin 看到的一致(含 O+1 后的在线数)——同一份数据,只是权限不同;
- `/system/users` 直接访问得到 403 结果页(「对不起,您没有权限访问此页面」+ 返回首页按钮);左侧菜单没有「系统管理」分组;
- `/system/users` 直接访问得到 403 结果页(「对不起,您没有权限访问此页面」+ 返回首页按钮);顶部菜单没有「系统管理」分组;
- 告警中心没有「处置」下拉(按钮级隐藏);演示后端对 `POST /api/v1/alarms/:id/handle` 返回 403「当前账号没有该操作的权限」(接口级才是真检查,隐藏按钮只是 UX,见 [role-matrix.md](role-matrix.md))。
- **下一步**:退回登录换 admin,E10。

Expand Down Expand Up @@ -169,7 +169,7 @@ E02 抄基线,E04–E08 做操作,按下表核对(依据 [src/demo/availab
- **两轮之间**的差异只有「演示时间」标签的锚点前进;所有 `SCN` 对象的 ID、状态、时间关系保持设计约束(创建 ≤ 响应 ≤ 完成 ≤ 验收)。
- **彻底退出受控模式**:头像菜单「退出受控场景」→ 回实时时钟 + 清空补丁(自然分布数据随真实时间推进,长开页面跨过 60 秒阈值会按当前时间重建基础数据、补丁保留,见 [src/demo/clock.ts](../../src/demo/clock.ts) 与 [src/demo/store.ts](../../src/demo/store.ts))。
- **多标签页**:演示时钟与补丁经 storage 事件互通;另一标签页进入 / 退出场景或重置后,本标签页在下次请求或刷新时同步。
- 排查:若「进入固定演示场景」菜单不存在,确认演示模式开启且当前账号是 admin;若地图瓦片空白,检查网络(OSM)或 CSP 部署副本。
- 排查:若「进入固定演示场景」菜单不存在,确认演示模式开启且当前账号是 admin;若 Cesium 底图空白或出现「底图瓦片加载失败」:无 token 时链路是「夜航滤镜」OSM →(探针通过)carto 暗色 →(OSM 不可达)高德兜底,三者都不可达才告警,检查网络或 CSP 部署副本(需放行 tile.openstreetmap.org、_.basemaps.cartocdn.com、_.autonavi.com);OpenLayers 页只用 OSM。

## 附录:外部条件依赖与未验证边界(如实声明)

Expand Down
Loading
Loading