feat: expand bgp observability surfaces
This commit is contained in:
@@ -59,6 +59,10 @@ import {
|
||||
getSatellitePositions,
|
||||
showPredictedOrbit,
|
||||
hidePredictedOrbit,
|
||||
highlightRelatedSatellites,
|
||||
clearRelatedSatelliteHighlights,
|
||||
getRelatedSatelliteIndicesForRegions,
|
||||
updateRelatedSatelliteHighlights,
|
||||
updateBreathingPhase,
|
||||
isSatelliteFrontFacing,
|
||||
setSatelliteCamera,
|
||||
@@ -88,10 +92,15 @@ import {
|
||||
formatBGPLocation,
|
||||
formatBGPObservedTime,
|
||||
formatBGPObservedBy,
|
||||
formatBGPRelatedCables,
|
||||
formatBGPRouteChange,
|
||||
formatBGPTopEventTypes,
|
||||
formatBGPScope,
|
||||
formatBGPCollectorCoverageHalo,
|
||||
formatBGPSeverityLabel,
|
||||
formatBGPStatusLabel,
|
||||
showBGPEventOverlay,
|
||||
showBGPCollectorCoverageOverlay,
|
||||
} from "./bgp.js";
|
||||
import {
|
||||
setupControls,
|
||||
@@ -165,6 +174,18 @@ const DRAG_ROTATION_FACTOR = 0.005;
|
||||
const DRAG_SMOOTHING_FACTOR = 0.18;
|
||||
const INERTIA_DAMPING = 0.92;
|
||||
const INERTIA_MIN_VELOCITY = 0.00008;
|
||||
const HUD_INTERACTIVE_SELECTORS = [
|
||||
"#info-panel",
|
||||
"#info-panel *",
|
||||
"#right-toolbar-group",
|
||||
"#right-toolbar-group *",
|
||||
"#coordinates-display",
|
||||
"#coordinates-display *",
|
||||
"#legend",
|
||||
"#legend *",
|
||||
"#earth-stats",
|
||||
"#earth-stats *",
|
||||
];
|
||||
|
||||
function bindListener(target, eventName, handler, options) {
|
||||
if (!target) return;
|
||||
@@ -174,6 +195,12 @@ function bindListener(target, eventName, handler, options) {
|
||||
);
|
||||
}
|
||||
|
||||
function isEventOnHud(event) {
|
||||
const target = event?.target;
|
||||
if (!(target instanceof Element)) return false;
|
||||
return HUD_INTERACTIVE_SELECTORS.some((selector) => target.closest(selector));
|
||||
}
|
||||
|
||||
function disposeMaterial(material) {
|
||||
if (!material) return;
|
||||
if (Array.isArray(material)) {
|
||||
@@ -234,11 +261,18 @@ export function clearLockedObject() {
|
||||
clearAllCableStates();
|
||||
clearCableSelection();
|
||||
clearBGPSelection();
|
||||
clearRelatedSatelliteHighlights();
|
||||
setSatelliteRingState(null, "none", null);
|
||||
clearRuntimeSelection();
|
||||
setLegendItems("satellites", getSatelliteLegendItems());
|
||||
}
|
||||
|
||||
export function clearLockedObjectAndInfo() {
|
||||
clearLockedObject();
|
||||
hideInfoCard();
|
||||
hideTooltip();
|
||||
}
|
||||
|
||||
function isSameCable(cable1, cable2) {
|
||||
if (!cable1 || !cable2) return false;
|
||||
const id1 = cable1.userData?.cableId;
|
||||
@@ -281,6 +315,22 @@ function resetTransientBGPStates() {
|
||||
});
|
||||
}
|
||||
|
||||
function clearTransientHoverState() {
|
||||
resetTransientBGPStates();
|
||||
hoveredBGP = null;
|
||||
|
||||
if (hoveredCable && !isSameCable(hoveredCable, lockedObject)) {
|
||||
setCableState(hoveredCable.userData.cableId, CABLE_STATE.NORMAL);
|
||||
}
|
||||
hoveredCable = null;
|
||||
|
||||
if (hoveredSatelliteIndex !== null && hoveredSatelliteIndex !== lockedSatelliteIndex) {
|
||||
setSatelliteRingState(hoveredSatelliteIndex, "none", null);
|
||||
}
|
||||
hoveredSatellite = null;
|
||||
hoveredSatelliteIndex = null;
|
||||
}
|
||||
|
||||
function applyBGPHoverState(marker) {
|
||||
resetTransientBGPStates();
|
||||
if (!marker) {
|
||||
@@ -419,6 +469,11 @@ function showBGPInfo(marker) {
|
||||
marker.userData.observed_by ||
|
||||
formatBGPObservedBy(marker.userData.collectors),
|
||||
impacted_scope: formatBGPImpactedScope(impactedRegions),
|
||||
related_cables: formatBGPRelatedCables(marker.userData.related_cables),
|
||||
related_satellites:
|
||||
marker.userData.related_satellite_count > 0
|
||||
? `${marker.userData.related_satellite_count}颗附近卫星`
|
||||
: "-",
|
||||
location:
|
||||
marker.userData.location ||
|
||||
formatBGPLocation(marker.userData.city, marker.userData.country),
|
||||
@@ -433,10 +488,65 @@ function showBGPCollectorInfo(marker) {
|
||||
collector: marker.userData.collector,
|
||||
location: formatBGPLocation(marker.userData.city, marker.userData.country),
|
||||
anomaly_count: marker.userData.anomaly_count ?? 0,
|
||||
observation_count: marker.userData.observation_count ?? 0,
|
||||
recent_24h_observation_count: marker.userData.recent_24h_observation_count ?? 0,
|
||||
recent_7d_observation_count: marker.userData.recent_7d_observation_count ?? 0,
|
||||
prefix_count: marker.userData.prefix_count ?? 0,
|
||||
origin_asn_count: marker.userData.origin_asn_count ?? 0,
|
||||
top_event_types: formatBGPTopEventTypes(marker.userData.top_event_types),
|
||||
coverage_halo: formatBGPCollectorCoverageHalo(marker.userData),
|
||||
related_satellites:
|
||||
marker.userData.related_satellite_count > 0
|
||||
? `${marker.userData.related_satellite_count}颗附近卫星`
|
||||
: "-",
|
||||
latest_event_type: marker.userData.latest_event_type || "-",
|
||||
latest_observed_at: formatBGPObservedTime(marker.userData.latest_observed_at),
|
||||
baseline_scope: formatBGPScope(marker.userData.baseline_scope),
|
||||
status: formatBGPCollectorStatus(marker.userData.status || "online"),
|
||||
});
|
||||
}
|
||||
|
||||
function getBGPRelatedCableNames(marker) {
|
||||
const items = Array.isArray(marker?.userData?.related_cables)
|
||||
? marker.userData.related_cables
|
||||
: [];
|
||||
|
||||
const names = [];
|
||||
items.forEach((item) => {
|
||||
const cableNames = Array.isArray(item?.cable_names) ? item.cable_names : [];
|
||||
cableNames.forEach((name) => {
|
||||
if (name && !names.includes(name)) {
|
||||
names.push(name);
|
||||
}
|
||||
});
|
||||
});
|
||||
return names;
|
||||
}
|
||||
|
||||
function getBGPRelatedRegions(marker) {
|
||||
if (Array.isArray(marker?.userData?.impacted_regions) && marker.userData.impacted_regions.length > 0) {
|
||||
return marker.userData.impacted_regions;
|
||||
}
|
||||
|
||||
if (
|
||||
marker?.userData?.type === "bgp_collector" &&
|
||||
typeof marker.userData.latitude === "number" &&
|
||||
typeof marker.userData.longitude === "number"
|
||||
) {
|
||||
return [
|
||||
{
|
||||
collector: marker.userData.collector,
|
||||
city: marker.userData.city,
|
||||
country: marker.userData.country,
|
||||
latitude: marker.userData.latitude,
|
||||
longitude: marker.userData.longitude,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function applyCableVisualState() {
|
||||
const allCables = getCableLines();
|
||||
const pulse = (Math.sin(Date.now() * CABLE_CONFIG.pulseSpeed) + 1) * 0.5;
|
||||
@@ -462,7 +572,8 @@ function applyCableVisualState() {
|
||||
if (
|
||||
(lockedObjectType === "cable" && lockedObject) ||
|
||||
(lockedObjectType === "satellite" && lockedSatellite) ||
|
||||
(lockedObjectType === "bgp" && lockedObject)
|
||||
(lockedObjectType === "bgp" && lockedObject) ||
|
||||
(lockedObjectType === "bgp_collector" && lockedObject)
|
||||
) {
|
||||
cable.material.opacity = CABLE_CONFIG.otherOpacity;
|
||||
const origColor = cable.userData.originalColor;
|
||||
@@ -972,6 +1083,26 @@ function onMouseMove(event) {
|
||||
const earth = getEarth();
|
||||
if (!earth) return;
|
||||
|
||||
if (isEventOnHud(event)) {
|
||||
clearTransientHoverState();
|
||||
|
||||
if (lockedObjectType === "bgp" && lockedObject) {
|
||||
applyBGPHoverState(lockedObject);
|
||||
showBGPInfo(lockedObject);
|
||||
} else if (lockedObjectType === "bgp_collector" && lockedObject) {
|
||||
applyBGPHoverState(lockedObject);
|
||||
showBGPCollectorInfo(lockedObject);
|
||||
} else if (lockedObjectType === "cable" && lockedObject) {
|
||||
showCableInfo(lockedObject);
|
||||
} else if (lockedObjectType === "satellite" && lockedSatellite) {
|
||||
showSatelliteInfo(lockedSatellite.properties);
|
||||
} else {
|
||||
hideInfoCard();
|
||||
}
|
||||
hideTooltip();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDragging) {
|
||||
if (Date.now() - dragStartTime > 500) {
|
||||
isLongDrag = true;
|
||||
@@ -1029,12 +1160,8 @@ function onMouseMove(event) {
|
||||
bgpCollectorIntersects,
|
||||
);
|
||||
|
||||
if (
|
||||
hoveredBGP &&
|
||||
!isSameBGPMarker(hoveredBGP, hoveredBGPMarker)
|
||||
) {
|
||||
resetTransientBGPStates();
|
||||
hoveredBGP = null;
|
||||
if (hoveredBGP && !isSameBGPMarker(hoveredBGP, hoveredBGPMarker)) {
|
||||
clearTransientHoverState();
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -1042,20 +1169,14 @@ function onMouseMove(event) {
|
||||
(!cableIntersects.length ||
|
||||
!isSameCable(cableIntersects[0]?.object, hoveredCable))
|
||||
) {
|
||||
if (!isSameCable(hoveredCable, lockedObject)) {
|
||||
setCableState(hoveredCable.userData.cableId, CABLE_STATE.NORMAL);
|
||||
}
|
||||
hoveredCable = null;
|
||||
clearTransientHoverState();
|
||||
}
|
||||
|
||||
if (
|
||||
hoveredSatelliteIndex !== null &&
|
||||
hoveredSatelliteIndex !== hoveredSatIndexFromIntersect
|
||||
) {
|
||||
if (hoveredSatelliteIndex !== lockedSatelliteIndex) {
|
||||
setSatelliteRingState(hoveredSatelliteIndex, "none", null);
|
||||
}
|
||||
hoveredSatelliteIndex = null;
|
||||
clearTransientHoverState();
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -1142,6 +1263,10 @@ function onMouseMove(event) {
|
||||
}
|
||||
|
||||
function onMouseDown(event) {
|
||||
if (isEventOnHud(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const earth = getEarth();
|
||||
isDragging = true;
|
||||
dragStartTime = Date.now();
|
||||
@@ -1170,6 +1295,7 @@ function onMouseLeave() {
|
||||
function onClick(event) {
|
||||
const earth = getEarth();
|
||||
if (!earth) return;
|
||||
if (isEventOnHud(event)) return;
|
||||
|
||||
updatePointerFromEvent(event);
|
||||
|
||||
@@ -1210,6 +1336,14 @@ function onClick(event) {
|
||||
lastBGPClickPos = { x: event.clientX, y: event.clientY };
|
||||
setAutoRotate(false);
|
||||
showBGPEventOverlay(clickedMarker, earth);
|
||||
{
|
||||
const relatedSatelliteIndices = getRelatedSatelliteIndicesForRegions(
|
||||
getBGPRelatedRegions(clickedMarker),
|
||||
{ limit: 6, maxAngleDeg: 20 },
|
||||
);
|
||||
clickedMarker.userData.related_satellite_count = relatedSatelliteIndices.length;
|
||||
highlightRelatedSatellites(relatedSatelliteIndices, "#7dd3fc");
|
||||
}
|
||||
showBGPInfo(clickedMarker);
|
||||
showStatusMessage(
|
||||
`已选择BGP事件: ${clickedMarker.userData.collector}`,
|
||||
@@ -1231,6 +1365,15 @@ function onClick(event) {
|
||||
lastBGPClickType = "bgp_collector";
|
||||
lastBGPClickPos = { x: event.clientX, y: event.clientY };
|
||||
setAutoRotate(false);
|
||||
showBGPCollectorCoverageOverlay(clickedMarker, earth);
|
||||
{
|
||||
const relatedSatelliteIndices = getRelatedSatelliteIndicesForRegions(
|
||||
getBGPRelatedRegions(clickedMarker),
|
||||
{ limit: 4, maxAngleDeg: 18 },
|
||||
);
|
||||
clickedMarker.userData.related_satellite_count = relatedSatelliteIndices.length;
|
||||
highlightRelatedSatellites(relatedSatelliteIndices, "#93c5fd");
|
||||
}
|
||||
showBGPCollectorInfo(clickedMarker);
|
||||
showStatusMessage(
|
||||
`已选择观测站: ${clickedMarker.userData.collector}`,
|
||||
@@ -1374,6 +1517,21 @@ function animate() {
|
||||
) {
|
||||
applyLandingPointVisualState(null, true);
|
||||
} else if (lockedObjectType === "bgp" && lockedObject) {
|
||||
const relatedCableNames = getBGPRelatedCableNames(lockedObject);
|
||||
clearAllCableStates();
|
||||
relatedCableNames.forEach((name) => {
|
||||
getCableLines().forEach((cable) => {
|
||||
if (cable.userData?.name === name) {
|
||||
setCableState(cable.userData.cableId, CABLE_STATE.HOVERED);
|
||||
}
|
||||
});
|
||||
});
|
||||
applyLandingPointVisualState(
|
||||
relatedCableNames.length > 0 ? relatedCableNames : null,
|
||||
relatedCableNames.length === 0,
|
||||
);
|
||||
} else if (lockedObjectType === "bgp_collector" && lockedObject) {
|
||||
clearAllCableStates();
|
||||
applyLandingPointVisualState(null, true);
|
||||
} else {
|
||||
resetLandingPointVisualState();
|
||||
@@ -1381,6 +1539,7 @@ function animate() {
|
||||
|
||||
updateSatellitePositions(deltaTime);
|
||||
updateBreathingPhase(deltaTime);
|
||||
updateRelatedSatelliteHighlights();
|
||||
|
||||
const satPositions = getSatellitePositions();
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user