Closed
Conversation
…uildinfo formatting
- Introduced a new Routing component to visualize device connections using React Flow and Dagre for layout. - Added RoutingDeviceNode component to represent individual devices with input and output ports. - Implemented signal type filtering and device selection/deselection functionality. - Created corresponding SCSS modules for styling the Routing component and device nodes. - Updated package.json to include necessary dependencies: @xyflow/react and dagre. - Enhanced apiSlice to fetch routing devices and tie lines from the backend. - Updated TopNav to include a link to the new Routing feature.
…nce Routing with dark mode and unconnected ports functionality
…bileControl UI, and add TieLineEdge component for routing visualization
- Added LoginForm component for user authentication. - Introduced RequireAuth component to protect routes. - Created ApiPaths component to display available API paths. - Added ApiPathDetailDrawer for detailed view of selected API paths. - Updated routing to include login and API paths. - Integrated Redux for authentication state management. - Enhanced DebugConsole with device filtering and search capabilities. - Refactored TopNav to dynamically display available apps based on authentication. - Added selectors and slice for authentication and debug console state management.
There was a problem hiding this comment.
Pull request overview
Adds authenticated, app-scoped routing and new diagnostic tooling pages to the React UI (routing graph, API path explorer, initialization exception viewer), while improving Debug Console filtering and connection error visibility.
Changes:
- Introduces login/auth state, protected routes, and app discovery.
- Adds new pages/features: Routing graph (xyflow/dagre), API Paths, Initialization Exceptions, plus Mobile Control client management.
- Refactors Debug Console filters to Redux state and surfaces WebSocket connection failures (certificate hint).
Reviewed changes
Copilot reviewed 30 out of 32 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.tsbuildinfo | Updated TS incremental build metadata (shouldn’t be committed). |
| .gitignore | Adds duplicate ignore entry for tsbuildinfo. |
| package.json | Adds xyflow/dagre dependencies. |
| package-lock.json | Locks new dependency tree for xyflow/dagre. |
| src/App.tsx | Reworks routing: login + protected app routes; wraps in ErrorBoundary. |
| src/features/ErrorBoundary.tsx | Adds global error boundary wrapper component. |
| src/features/RequireAuth.tsx | Adds protected-route gate that redirects to login. |
| src/store/auth/authSlice.ts | Adds auth state (isAuthenticated, availableApps). |
| src/store/auth/authSelectors.ts | Adds selectors for auth state. |
| src/store/store.ts | Registers auth + debugConsole reducers in store. |
| src/features/LoginForm.tsx | Adds login form + app probing to populate available apps. |
| src/features/TopNav.tsx | Uses availableApps for app switcher; adds new nav links; feature-gates initialization exceptions. |
| src/shared/functions/meetsMinimumVersion.ts | Adds helper for version gating. |
| src/store/apiSlice.ts | Adds new RTK Query endpoints and DTOs (paths, routing, login, mobile control). |
| src/features/ApiPaths.tsx | Adds API path list view. |
| src/features/ApiPathDetailDrawer.tsx | Adds off-canvas drawer for API path details/links. |
| src/features/InitializationExceptions.tsx | Adds initialization exception list + stack trace expansion. |
| src/store/websocketSlice.ts | Tracks failed WebSocket URL and connection attempt state. |
| src/store/websocketMiddleware.ts | Dispatches connection attempt + failure actions on WebSocket errors. |
| src/features/DebugConsole/DebugConsole.tsx | Shows certificate warning link on WebSocket failure; updates button variants. |
| src/store/debugConsole/debugConsoleSlice.ts | Adds Redux state for debug-console filters. |
| src/store/debugConsole/debugConsoleSelectors.ts | Adds selectors for debug-console filter state. |
| src/features/DebugConsole/debugConsts.ts | Adds log-level ordering map for comparisons. |
| src/features/DebugConsole/hooks/useFilteredMessages.ts | Refactors message filtering to Redux-driven filters + per-device min levels. |
| src/features/DebugConsole/DeviceFilterDropdown.tsx | Adds device + per-device log level filter UI. |
| src/features/DebugConsole/DebugFilters.tsx | Replaces URL-search-param filters with Redux-based filter controls. |
| src/features/MobileControl.tsx | Adds create/delete client flows + action paths table and modals. |
| src/features/Routing.tsx | Adds routing graph visualization with dagre layout + filtering controls. |
| src/features/Routing.module.scss | Styles for routing filter bar + tie-line tooltip. |
| src/features/RoutingDeviceNode.tsx | Adds custom React Flow node for routing devices/ports. |
| src/features/RoutingDeviceNode.module.scss | Styles for routing device node and port tooltips. |
| src/features/TieLineEdge.tsx | Adds custom edge renderer + hover/selection tooltip. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+15
to
+16
| console.log("Initialization exceptions:", data?.Exceptions); | ||
|
|
Comment on lines
+36
to
+43
| /** Dispatched by the middleware when a connection attempt fails */ | ||
| connectionFailed(state, action: PayloadAction<string>) { | ||
| state.failedUrl = action.payload; | ||
| }, | ||
| /** Dispatched by the middleware when a new connection attempt starts */ | ||
| connectionAttemptStarted(state) { | ||
| state.failedUrl = null; | ||
| }, |
Comment on lines
19
to
30
| const items = useMemo(() => { | ||
| if (!devices) return [{ id: debugConsts.GLOBAL, label: "Global"}]; | ||
| if (!devices) return [{ id: debugConsts.GLOBAL, label: 'Global' }]; | ||
|
|
||
| let fullList: IdLabel[] = [ | ||
| { id: debugConsts.GLOBAL, label: "Global"} | ||
| ]; | ||
| const deviceItems: IdLabel[] = devices | ||
| .map((d) => ({ id: d.Key, label: d.Name || d.Key })) | ||
| .sort((a, b) => a.label.localeCompare(b.label)); | ||
|
|
||
| devices.forEach((d) => { | ||
| fullList.push({ id: d.Key, label: d.Name}); | ||
| }); | ||
|
|
||
| return fullList; | ||
| return [{ id: debugConsts.GLOBAL, label: 'Global' }, ...deviceItems]; | ||
| }, [devices]); | ||
|
|
||
| if (!devices) return null; | ||
| if (!devices) return null; | ||
|
|
Comment on lines
+1
to
+4
| // Compares dot-separated version strings numerically (e.g. "2.29" > "2.9"). | ||
| // Strips semver pre-release suffixes (e.g. "2.29.0-alpha.1" → "2.29.0"). | ||
| export function meetsMinVersion(version: string, minimum: string): boolean { | ||
| const vParts = version.split("-")[0].split(".").map((s) => parseInt(s, 10)); |
Comment on lines
+25
to
+28
| const failedUrl = useSelector((state: RootState) => state.websocket.failedUrl); | ||
| const certUrl = failedUrl | ||
| ? new URL(failedUrl).origin.replace(/^wss:/, 'https:').replace(/^ws:/, 'http:') | ||
| : null; |
| > | ||
| {opt.label} | ||
| </Dropdown.Item> | ||
| ))} </Dropdown.Menu> |
| ], | ||
| "version": "6.0.2" | ||
| } | ||
| {"root":["./src/app.test.tsx","./src/app.tsx","./src/index.tsx","./src/react-app-env.d.ts","./src/reportwebvitals.ts","./src/setuptests.ts","./src/features/apipathdetaildrawer.tsx","./src/features/apipaths.tsx","./src/features/configfile.tsx","./src/features/devicedetail.tsx","./src/features/devicelist.tsx","./src/features/errorboundary.tsx","./src/features/errorbox.tsx","./src/features/initializationexceptions.tsx","./src/features/loginform.tsx","./src/features/mainlayout.tsx","./src/features/mobilecontrol.tsx","./src/features/requireauth.tsx","./src/features/routing.tsx","./src/features/routingdevicenode.tsx","./src/features/tielineedge.tsx","./src/features/topnav.tsx","./src/features/types.tsx","./src/features/versions.tsx","./src/features/debugconsole/consolewindow.tsx","./src/features/debugconsole/debugconsole.tsx","./src/features/debugconsole/debugfilters.tsx","./src/features/debugconsole/devicefilterdropdown.tsx","./src/features/debugconsole/logmessagedetaildrawer.tsx","./src/features/debugconsole/minimumlogleveldropdown.tsx","./src/features/debugconsole/restartconfirmmodal.tsx","./src/features/debugconsole/debugconsts.ts","./src/features/debugconsole/hooks/usefilteredmessages.ts","./src/services/httpservice.ts","./src/shared/filterclearbutton.tsx","./src/shared/filterdropdownsearchparams.tsx","./src/shared/filtersearchtext.tsx","./src/shared/headerscrollerfooter.tsx","./src/shared/listfiltersheader.tsx","./src/shared/tablecellspacer.tsx","./src/shared/functions/meetsminimumversion.ts","./src/shared/hooks/useappparams.ts","./src/shared/icons/objecticons.ts","./src/shared/icons/othericons.ts","./src/shared/icons/index.tsx","./src/shared/types/idlabel.ts","./src/shared/types/logmessage.ts","./src/store/apislice.ts","./src/store/hooks.ts","./src/store/store.ts","./src/store/websocketmiddleware.ts","./src/store/websocketslice.ts","./src/store/auth/authselectors.ts","./src/store/auth/authslice.ts","./src/store/commonui/commonuihooks.ts","./src/store/commonui/commonuiselectors.ts","./src/store/commonui/commonuislice.ts","./src/store/commonui/commonuistate.ts","./src/store/debugconsole/debugconsoleselectors.ts","./src/store/debugconsole/debugconsoleslice.ts","./vite.config.ts"],"version":"6.0.2"} No newline at end of file |
Comment on lines
+84
to
+146
| // Devices that appear in at least one *visible* tie line endpoint | ||
| const connectedKeys = new Set<string>( | ||
| data.tieLines | ||
| .filter((tl) => !hiddenTypes.has(tl.signalType)) | ||
| .flatMap((tl) => [tl.sourceDeviceKey, tl.destinationDeviceKey]), | ||
| ); | ||
|
|
||
| // Tie lines that pass all active filters (used for port-level filtering) | ||
| const visibleTieLines = data.tieLines.filter( | ||
| (tl) => | ||
| !hiddenTypes.has(tl.signalType) && | ||
| !hiddenDevices.has(tl.sourceDeviceKey) && | ||
| !hiddenDevices.has(tl.destinationDeviceKey), | ||
| ); | ||
| const connectedPortKeys = hideUnconnectedPorts | ||
| ? new Set<string>( | ||
| visibleTieLines.flatMap((tl) => [ | ||
| `${tl.sourceDeviceKey}:${tl.sourcePortKey}`, | ||
| `${tl.destinationDeviceKey}:${tl.destinationPortKey}`, | ||
| ]), | ||
| ) | ||
| : null; | ||
|
|
||
| const devices = data.devices.filter((d) => { | ||
| if (hiddenDevices.has(d.key)) return false; | ||
| if (hideUnconnected && !connectedKeys.has(d.key)) return false; | ||
| return true; | ||
| }); | ||
|
|
||
| // Filter each device's ports down to only those with active tie lines | ||
| const effectiveDevices = devices.map((d) => | ||
| connectedPortKeys | ||
| ? { | ||
| ...d, | ||
| inputPorts: (d.inputPorts ?? []).filter((p) => | ||
| connectedPortKeys.has(`${d.key}:${p.key}`), | ||
| ), | ||
| outputPorts: (d.outputPorts ?? []).filter((p) => | ||
| connectedPortKeys.has(`${d.key}:${p.key}`), | ||
| ), | ||
| } | ||
| : d, | ||
| ); | ||
|
|
||
| // Collect one unique device-pair edge per source→destination (dagre only | ||
| // needs connectivity, not multiplicity, for rank assignment). | ||
| const uniquePairs = [ | ||
| ...new Set( | ||
| data.tieLines.map( | ||
| (tl) => `${tl.sourceDeviceKey}|${tl.destinationDeviceKey}`, | ||
| ), | ||
| ), | ||
| ]; | ||
|
|
||
| // ── Pass 1: layout with all edges to detect cross-level (backwards) edges ── | ||
| const g1 = makeGraph(); | ||
| for (const device of effectiveDevices) { | ||
| g1.setNode(device.key, { width: NODE_WIDTH, height: nodeHeight(device) }); | ||
| } | ||
| for (const pair of uniquePairs) { | ||
| const [src, dst] = pair.split("|"); | ||
| g1.setEdge(src, dst); | ||
| } |
Comment on lines
+50
to
+85
| return ( | ||
| <> | ||
| <tr key={`ex-${idx}`}> | ||
| <td className="text-muted">{idx + 1}</td> | ||
| <td> | ||
| <span className="text-danger fw-semibold"> | ||
| {ex.Message} | ||
| </span> | ||
| </td> | ||
| <td> | ||
| {ex.StackTrace && ( | ||
| <button | ||
| className="btn btn-sm btn-outline-secondary" | ||
| onClick={() => | ||
| setExpandedIndex(isExpanded ? null : idx) | ||
| } | ||
| > | ||
| {isExpanded ? "Hide" : "Show"} | ||
| </button> | ||
| )} | ||
| </td> | ||
| </tr> | ||
| {isExpanded && ex.StackTrace && ( | ||
| <tr key={`ex-${idx}-trace`}> | ||
| <td colSpan={3} className="p-0"> | ||
| <pre | ||
| className="m-0 p-3 bg-light text-muted" | ||
| style={{ fontSize: "0.75rem", whiteSpace: "pre-wrap" }} | ||
| > | ||
| {ex.StackTrace} | ||
| </pre> | ||
| </td> | ||
| </tr> | ||
| )} | ||
| </> | ||
| ); |
| const probeAppId = isValidAppId ? appId : ALL_APP_IDS[0]; | ||
|
|
||
| if (isAuthenticated) { | ||
| return <Navigate to={from ?? `/${appId}/versions`} replace />; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.