feat: add project-aware agent workspace
Generic Container CI/CD / test-build-publish (push) Successful in 2m9s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 2m39s

This commit is contained in:
2026-10-22 18:52:18 +08:00
parent c6a0e74e72
commit 2d8506fea0
191 changed files with 12373 additions and 3556 deletions
@@ -9,7 +9,7 @@ import type {
StyleSpecification
} from "maplibre-gl";
import { useCallback, useEffect, useRef, useState, type RefObject } from "react";
import type { AccessTokenProvider } from "@/shared/auth/keycloak-auth";
import type { WorkbenchAccess } from "../api/request-context";
import { env } from "@/shared/config/env";
import { fetchScadaSnapshot, type ScadaDevice } from "../api/scada-client";
import type { DetailFeature } from "../types";
@@ -62,6 +62,7 @@ declare global {
}
type UseWorkbenchMapOptions = {
featureInteractionsEnabled?: boolean;
containerRef: RefObject<HTMLDivElement | null>;
impactVisible: boolean;
layerVisibility: Record<string, boolean>;
@@ -71,7 +72,8 @@ type UseWorkbenchMapOptions = {
simulationOverlay: FeatureCollection<Geometry> | null;
allowDemoSimulation: boolean;
networkView?: NetworkViewParams | null;
getAccessToken?: AccessTokenProvider;
getAccessToken?: WorkbenchAccess;
geoserverWorkspace?: string;
};
type UseWorkbenchMapResult = {
@@ -132,6 +134,7 @@ const EMPTY_SIMULATION_COLLECTION: FeatureCollection = {
};
export function useWorkbenchMap({
featureInteractionsEnabled = true,
containerRef,
impactVisible,
layerVisibility,
@@ -141,6 +144,7 @@ export function useWorkbenchMap({
simulationOverlay,
allowDemoSimulation,
networkView,
geoserverWorkspace,
getAccessToken
}: UseWorkbenchMapOptions): UseWorkbenchMapResult {
const mapRef = useRef<MapLibreMap | null>(null);
@@ -199,7 +203,7 @@ export function useWorkbenchMap({
);
geoServerLayerProbeInFlightRef.current = true;
void resolveGeoServerLayerAvailability(requestAbortController.signal)
void resolveGeoServerLayerAvailability(requestAbortController.signal, geoserverWorkspace)
.then((availability) => {
if (lifecycleAbortController.signal.aborted) return;
setAvailableGeoServerSourceIds((current) =>
@@ -237,7 +241,7 @@ export function useWorkbenchMap({
geoServerLayerProbeInFlightRef.current = false;
window.clearTimeout(probeTimeout);
};
}, [geoServerLayerRefreshRevision]);
}, [geoServerLayerRefreshRevision, geoserverWorkspace]);
useEffect(() => {
impactVisibleRef.current = impactVisible;
@@ -306,7 +310,7 @@ export function useWorkbenchMap({
(map.getSource(SCADA_SOURCE_ID) as GeoJSONSource | undefined)?.setData(
scadaCollectionRef.current
);
const sources = createWaterNetworkSources(REQUIRED_GEOSERVER_SOURCE_IDS);
const sources = createWaterNetworkSources(REQUIRED_GEOSERVER_SOURCE_IDS, geoserverWorkspace);
REQUIRED_GEOSERVER_SOURCE_IDS.forEach((sourceId) => {
const source = sources[sourceId];
if (source) map.addSource(sourceId, source);
@@ -315,8 +319,10 @@ export function useWorkbenchMap({
map.addSource(SIMULATION_SOURCE_IDS.annotations, simulationSources.annotations);
map.addSource(SIMULATION_SOURCE_IDS.backendAnalysis, simulationSources.backendAnalysis);
syncSimulationSourceData(map, simulationOverlayRef.current, allowDemoSimulationRef.current);
await registerSupplyAssetImages(map);
const scadaRegistration = await registerScadaImages(map);
await registerSupplyAssetImages(map, scadaAbortController.signal);
if (scadaAbortController.signal.aborted) return;
const scadaRegistration = await registerScadaImages(map, scadaAbortController.signal);
if (scadaAbortController.signal.aborted) return;
const activeScadaBusinessLayers =
scadaRegistration.status === "unavailable"
? scadaFallbackBusinessLayers
@@ -444,13 +450,13 @@ export function useWorkbenchMap({
mapRef.current = null;
delete window.__waterNetworkMap;
};
}, [containerRef, getAccessToken]);
}, [containerRef, getAccessToken, geoserverWorkspace]);
useEffect(() => {
const map = mapRef.current;
if (!mapReady || !map) return;
syncDataDependentGeoServerLayers(map, availableGeoServerSourceIds, layerVisibility);
}, [availableGeoServerSourceIds, layerVisibility, mapReady]);
syncDataDependentGeoServerLayers(map, availableGeoServerSourceIds, layerVisibility, geoserverWorkspace);
}, [availableGeoServerSourceIds, layerVisibility, mapReady, geoserverWorkspace]);
useEffect(() => {
const map = mapRef.current;
@@ -462,6 +468,7 @@ export function useWorkbenchMap({
}, [mapReady, networkView]);
useMapInteractions({
enabled: featureInteractionsEnabled,
availableSourceIds: availableGeoServerSourceIds,
mapRef,
mapReady,
@@ -557,7 +564,8 @@ function syncSimulationSourceData(
function syncDataDependentGeoServerLayers(
map: MapLibreMap,
availableSourceIds: readonly GeoServerWaterNetworkSourceId[],
layerVisibility: Record<string, boolean>
layerVisibility: Record<string, boolean>,
workspace?: string
) {
const availableSourceIdSet = new Set<GeoServerWaterNetworkSourceId>(availableSourceIds);
@@ -574,7 +582,7 @@ function syncDataDependentGeoServerLayers(
const requestedOptionalSourceIds = DATA_DEPENDENT_GEOSERVER_SOURCE_IDS.filter((sourceId) =>
availableSourceIdSet.has(sourceId)
);
const sources = createWaterNetworkSources(requestedOptionalSourceIds);
const sources = createWaterNetworkSources(requestedOptionalSourceIds, workspace);
requestedOptionalSourceIds.forEach((sourceId) => {
if (!map.getSource(sourceId) && sources[sourceId]) {
map.addSource(sourceId, sources[sourceId]);
@@ -620,7 +628,8 @@ function addMissingDataDependentLayers(
layers: StyleSpecification["layers"],
beforeLayerCandidates: readonly string[],
availableSourceIdSet: ReadonlySet<GeoServerWaterNetworkSourceId>,
layerVisibility: Record<string, boolean>
layerVisibility: Record<string, boolean>,
workspace?: string
) {
const beforeLayerId = beforeLayerCandidates.find((layerId) => map.getLayer(layerId));
layers.forEach((layer) => {