feat: TR-069 Exchange Viewer - structured MongoDB storage with SSE streaming and sequence diagram UI
- MongoDB storage: cwmp_exchanges collection with indexes (deviceId+timestamp, deviceId+sequenceNumber unique, TTL 24h) - ExchangeStoreService: CWMP method extraction, per-device queue for ordering, retry logic, message cap (5000/device) - Exchange API: GET/DELETE/SSE stream endpoints with Last-Event-ID reconnection - CwmpLoggerMiddleware: dual-write (MongoDB + legacy files) with fire-and-forget - Frontend: SequenceViewer (CPE↔ACS lifelines, arrows, auto-scroll, virtual scroll) + InspectorPanel (formatted XML, copy, syntax highlighting) - Tr069Trace page: integrated new viewer with col-6/col-6 layout - DeviceDetail page: collapsible exchange section when device is traced - 14 property-based tests, 40+ unit tests, 5 integration tests - Health check includes index verification - Config: feature flags, SSE timeouts, retry policy
This commit is contained in:
1 parent
3fefbf6c40
commit
0c83a4ccb2
50 files changed
+15731
-43
No files matched your search
@@ -0,0 +1,180 @@
|
||||
---
|
||||
name: Precision Protocol System
|
||||
colors:
|
||||
surface: '#f8f9ff'
|
||||
surface-dim: '#cbdbf5'
|
||||
surface-bright: '#f8f9ff'
|
||||
surface-container-lowest: '#ffffff'
|
||||
surface-container-low: '#eff4ff'
|
||||
surface-container: '#e5eeff'
|
||||
surface-container-high: '#dce9ff'
|
||||
surface-container-highest: '#d3e4fe'
|
||||
on-surface: '#0b1c30'
|
||||
on-surface-variant: '#45464d'
|
||||
inverse-surface: '#213145'
|
||||
inverse-on-surface: '#eaf1ff'
|
||||
outline: '#76777d'
|
||||
outline-variant: '#c6c6cd'
|
||||
surface-tint: '#565e74'
|
||||
primary: '#000000'
|
||||
on-primary: '#ffffff'
|
||||
primary-container: '#131b2e'
|
||||
on-primary-container: '#7c839b'
|
||||
inverse-primary: '#bec6e0'
|
||||
secondary: '#006a61'
|
||||
on-secondary: '#ffffff'
|
||||
secondary-container: '#86f2e4'
|
||||
on-secondary-container: '#006f66'
|
||||
tertiary: '#000000'
|
||||
on-tertiary: '#ffffff'
|
||||
tertiary-container: '#0f0069'
|
||||
on-tertiary-container: '#7671ff'
|
||||
error: '#ba1a1a'
|
||||
on-error: '#ffffff'
|
||||
error-container: '#ffdad6'
|
||||
on-error-container: '#93000a'
|
||||
primary-fixed: '#dae2fd'
|
||||
primary-fixed-dim: '#bec6e0'
|
||||
on-primary-fixed: '#131b2e'
|
||||
on-primary-fixed-variant: '#3f465c'
|
||||
secondary-fixed: '#89f5e7'
|
||||
secondary-fixed-dim: '#6bd8cb'
|
||||
on-secondary-fixed: '#00201d'
|
||||
on-secondary-fixed-variant: '#005049'
|
||||
tertiary-fixed: '#e2dfff'
|
||||
tertiary-fixed-dim: '#c3c0ff'
|
||||
on-tertiary-fixed: '#0f0069'
|
||||
on-tertiary-fixed-variant: '#3323cc'
|
||||
background: '#f8f9ff'
|
||||
on-background: '#0b1c30'
|
||||
surface-variant: '#d3e4fe'
|
||||
typography:
|
||||
headline-lg:
|
||||
fontFamily: Inter
|
||||
fontSize: 30px
|
||||
fontWeight: '600'
|
||||
lineHeight: 38px
|
||||
letterSpacing: -0.02em
|
||||
headline-md:
|
||||
fontFamily: Inter
|
||||
fontSize: 24px
|
||||
fontWeight: '600'
|
||||
lineHeight: 32px
|
||||
letterSpacing: -0.01em
|
||||
headline-sm:
|
||||
fontFamily: Inter
|
||||
fontSize: 18px
|
||||
fontWeight: '600'
|
||||
lineHeight: 26px
|
||||
body-lg:
|
||||
fontFamily: Inter
|
||||
fontSize: 16px
|
||||
fontWeight: '400'
|
||||
lineHeight: 24px
|
||||
body-md:
|
||||
fontFamily: Inter
|
||||
fontSize: 14px
|
||||
fontWeight: '400'
|
||||
lineHeight: 20px
|
||||
body-sm:
|
||||
fontFamily: Inter
|
||||
fontSize: 13px
|
||||
fontWeight: '400'
|
||||
lineHeight: 18px
|
||||
label-md:
|
||||
fontFamily: Inter
|
||||
fontSize: 12px
|
||||
fontWeight: '500'
|
||||
lineHeight: 16px
|
||||
letterSpacing: 0.05em
|
||||
mono-md:
|
||||
fontFamily: JetBrains Mono
|
||||
fontSize: 13px
|
||||
fontWeight: '400'
|
||||
lineHeight: 20px
|
||||
mono-sm:
|
||||
fontFamily: JetBrains Mono
|
||||
fontSize: 11px
|
||||
fontWeight: '400'
|
||||
lineHeight: 16px
|
||||
rounded:
|
||||
sm: 0.125rem
|
||||
DEFAULT: 0.25rem
|
||||
md: 0.375rem
|
||||
lg: 0.5rem
|
||||
xl: 0.75rem
|
||||
full: 9999px
|
||||
spacing:
|
||||
unit: 4px
|
||||
container-padding: 24px
|
||||
gutter: 16px
|
||||
sidebar-width: 260px
|
||||
stack-sm: 8px
|
||||
stack-md: 16px
|
||||
stack-lg: 32px
|
||||
---
|
||||
|
||||
## Brand & Style
|
||||
|
||||
This design system is engineered for high-performance network analysis and protocol monitoring. The brand personality is rooted in **precision, neutrality, and clarity**. It avoids decorative flourishes to prioritize the rapid interpretation of complex data structures.
|
||||
|
||||
The visual style is a blend of **Minimalism** and **Modern Corporate** aesthetics. It utilizes a spacious layout to reduce the cognitive load inherent in dense data environments. The interface conveys reliability through a systematic application of subtle borders and high-contrast typography, ensuring that critical network events are immediately visible.
|
||||
|
||||
## Colors
|
||||
|
||||
The palette is anchored in **Slate and Zinc** neutrals to provide a professional, calm environment for long-term monitoring.
|
||||
|
||||
* **Primary (Slate 950):** Used for core navigation, high-level headers, and text to ensure maximum contrast.
|
||||
* **Neutral (Slate/Zinc):** Used for structural elements, secondary text, and inactive states.
|
||||
* **Flow Accents:** Semantic colors are reserved for directional data. **Teal (ACS -> CPE)** and **Indigo (CPE -> ACS)** allow engineers to visually parse the direction of TR-069 traffic at a glance without reading text labels.
|
||||
* **Success/Error:** Traditional Green and Red are used sparingly for status indicators and connection states.
|
||||
|
||||
## Typography
|
||||
|
||||
This design system utilizes a dual-font strategy. **Inter** is the workhorse for the UI, chosen for its exceptional legibility in small sizes and high-density layouts. **JetBrains Mono** is utilized exclusively for technical data, including XML, JSON payloads, and packet hex dumps.
|
||||
|
||||
Headlines use tighter letter spacing for a more authoritative look, while labels utilize uppercase styling and increased tracking for clear categorization in sidebars and table headers. Monospaced text is rendered with a slightly smaller font size than body text to accommodate long lines of code without excessive wrapping.
|
||||
|
||||
## Layout & Spacing
|
||||
|
||||
The layout follows a **Hybrid Grid** model. Navigation and property inspectors use fixed-width sidebars, while the main content area—typically containing packet logs or traffic graphs—uses a **Fluid Grid** to maximize horizontal real estate.
|
||||
|
||||
### Breakpoints
|
||||
* **Desktop (1280px+):** 12-column fluid grid, 24px margins.
|
||||
* **Tablet (768px - 1279px):** 8-column fluid grid, 16px margins. Sidebar collapses to icons.
|
||||
* **Mobile (<767px):** 4-column fluid grid, 12px margins. Horizontal scrolling enabled for data tables.
|
||||
|
||||
Spacing is based on a **4px base unit**. Component internals use 8px or 12px increments to maintain a compact, "instrumental" feel without appearing cluttered.
|
||||
|
||||
## Elevation & Depth
|
||||
|
||||
To maintain a technical and minimalist aesthetic, this design system avoids heavy shadows. Instead, it relies on **Tonal Layers** and **Low-Contrast Outlines**.
|
||||
|
||||
* **Background (Level 0):** Slate-50. The base canvas.
|
||||
* **Surface (Level 1):** White. Primary cards and panels.
|
||||
* **Active/Overlay (Level 2):** White with a subtle 1px border in Slate-200 and a very soft, high-diffusion shadow (4px blur, 2% opacity) to indicate temporary elements like tooltips or dropdowns.
|
||||
|
||||
Visual separation between adjacent panels is achieved via 1px solid borders in Slate-200 rather than elevation, ensuring the UI remains crisp and "flat."
|
||||
|
||||
## Shapes
|
||||
|
||||
The shape language is **Soft (Level 1)**. A 4px radius is applied to buttons, input fields, and panels. This subtle rounding provides a modern touch without sacrificing the professional, rigid structure required for a technical tool.
|
||||
|
||||
Status indicators (pills) use a slightly larger 12px radius to distinguish them from interactive buttons, while data visualization bars use sharp 2px corners for maximum precision.
|
||||
|
||||
## Components
|
||||
|
||||
### Buttons
|
||||
Primary buttons use the Slate-950 background with white text. Ghost buttons (transparent background with Slate-200 borders) are preferred for secondary actions to keep the focus on the data.
|
||||
|
||||
### Packet Log Entries
|
||||
Logs must use a zebra-stripe pattern (White / Slate-50) for horizontal tracking. The "Flow Direction" is indicated by a 4px vertical accent bar on the far left of the row (Teal or Indigo).
|
||||
|
||||
### Input Fields
|
||||
Fields use a 1px Slate-200 border. On focus, the border transitions to Slate-900. Labels are always positioned above the input in the `label-md` style for clarity.
|
||||
|
||||
### Data Displays (Code Blocks)
|
||||
XML/JSON payloads are wrapped in containers with a Slate-50 background and a 1px border. They use `mono-md` typography with syntax highlighting that mirrors the flow colors (Teal for tags, Indigo for values).
|
||||
|
||||
### Chips & Badges
|
||||
Used for protocol methods (e.g., `Inform`, `GetParameterValues`). These are small, low-contrast components (Slate-100 background) to avoid competing with the flow direction accents.
|
||||
@@ -0,0 +1,367 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="light" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>TR-069 Protocol Analyzer</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
vertical-align: middle;
|
||||
}
|
||||
/* Custom scrollbar for technical look */
|
||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: #e2e8f0; border-radius: 10px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
||||
|
||||
.lifeline-gradient {
|
||||
background: linear-gradient(to bottom, #000 0%, #000 5%, transparent 5%, transparent 100%);
|
||||
background-size: 1px 20px;
|
||||
}
|
||||
</style>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
"colors": {
|
||||
"tertiary-fixed-dim": "#c3c0ff",
|
||||
"surface-container-high": "#dce9ff",
|
||||
"surface-container-lowest": "#ffffff",
|
||||
"on-background": "#0b1c30",
|
||||
"on-tertiary": "#ffffff",
|
||||
"error": "#ba1a1a",
|
||||
"on-primary-fixed": "#131b2e",
|
||||
"surface-tint": "#565e74",
|
||||
"secondary-fixed-dim": "#6bd8cb",
|
||||
"secondary-container": "#86f2e4",
|
||||
"surface-container": "#e5eeff",
|
||||
"on-primary-fixed-variant": "#3f465c",
|
||||
"outline-variant": "#c6c6cd",
|
||||
"on-secondary-fixed": "#00201d",
|
||||
"on-surface-variant": "#45464d",
|
||||
"surface-bright": "#f8f9ff",
|
||||
"on-primary": "#ffffff",
|
||||
"tertiary": "#000000",
|
||||
"primary-fixed-dim": "#bec6e0",
|
||||
"on-tertiary-container": "#7671ff",
|
||||
"surface-container-low": "#eff4ff",
|
||||
"surface-container-highest": "#d3e4fe",
|
||||
"surface-variant": "#d3e4fe",
|
||||
"inverse-on-surface": "#eaf1ff",
|
||||
"secondary": "#006a61",
|
||||
"primary-fixed": "#dae2fd",
|
||||
"primary": "#000000",
|
||||
"tertiary-container": "#0f0069",
|
||||
"on-error-container": "#93000a",
|
||||
"on-tertiary-fixed-variant": "#3323cc",
|
||||
"on-surface": "#0b1c30",
|
||||
"on-error": "#ffffff",
|
||||
"on-secondary": "#ffffff",
|
||||
"surface-dim": "#cbdbf5",
|
||||
"error-container": "#ffdad6",
|
||||
"tertiary-fixed": "#e2dfff",
|
||||
"secondary-fixed": "#89f5e7",
|
||||
"background": "#f8f9ff",
|
||||
"primary-container": "#131b2e",
|
||||
"on-secondary-fixed-variant": "#005049",
|
||||
"on-tertiary-fixed": "#0f0069",
|
||||
"on-secondary-container": "#006f66",
|
||||
"surface": "#f8f9ff",
|
||||
"inverse-surface": "#213145",
|
||||
"inverse-primary": "#bec6e0",
|
||||
"outline": "#76777d"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "0.125rem",
|
||||
"lg": "0.25rem",
|
||||
"xl": "0.5rem",
|
||||
"full": "0.75rem"
|
||||
},
|
||||
"spacing": {
|
||||
"gutter": "16px",
|
||||
"stack-md": "16px",
|
||||
"stack-sm": "8px",
|
||||
"unit": "4px",
|
||||
"stack-lg": "32px",
|
||||
"sidebar-width": "260px",
|
||||
"container-padding": "24px"
|
||||
},
|
||||
"fontFamily": {
|
||||
"mono-sm": ["JetBrains Mono"],
|
||||
"headline-md": ["Inter"],
|
||||
"mono-md": ["JetBrains Mono"],
|
||||
"headline-lg": ["Inter"],
|
||||
"body-md": ["Inter"],
|
||||
"body-sm": ["Inter"],
|
||||
"body-lg": ["Inter"],
|
||||
"label-md": ["Inter"],
|
||||
"headline-sm": ["Inter"]
|
||||
},
|
||||
"fontSize": {
|
||||
"mono-sm": ["11px", { "lineHeight": "16px", "fontWeight": "400" }],
|
||||
"headline-md": ["24px", { "lineHeight": "32px", "letterSpacing": "-0.01em", "fontWeight": "600" }],
|
||||
"mono-md": ["13px", { "lineHeight": "20px", "fontWeight": "400" }],
|
||||
"headline-lg": ["30px", { "lineHeight": "38px", "letterSpacing": "-0.02em", "fontWeight": "600" }],
|
||||
"body-md": ["14px", { "lineHeight": "20px", "fontWeight": "400" }],
|
||||
"body-sm": ["13px", { "lineHeight": "18px", "fontWeight": "400" }],
|
||||
"body-lg": ["16px", { "lineHeight": "24px", "fontWeight": "400" }],
|
||||
"label-md": ["12px", { "lineHeight": "16px", "letterSpacing": "0.05em", "fontWeight": "500" }],
|
||||
"headline-sm": ["18px", { "lineHeight": "26px", "fontWeight": "600" }]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-background text-on-background font-body-md overflow-hidden">
|
||||
<!-- Top Navigation Shell -->
|
||||
<header class="bg-surface text-on-surface font-headline-sm text-headline-sm docked full-width top-0 border-b border-outline-variant flex justify-between items-center px-gutter w-full h-16 z-50">
|
||||
<div class="flex items-center gap-stack-lg">
|
||||
<span class="font-headline-sm text-headline-sm font-bold text-on-surface">TR-069 Protocol Analyzer</span>
|
||||
<nav class="hidden md:flex items-center h-16 gap-6">
|
||||
<a class="text-on-surface-variant font-label-md text-label-md h-full flex items-center px-2 hover:bg-surface-container transition-colors duration-200" href="#">Dashboard</a>
|
||||
<a class="text-on-surface-variant font-label-md text-label-md h-full flex items-center px-2 hover:bg-surface-container transition-colors duration-200" href="#">Live Stream</a>
|
||||
<a class="text-primary font-bold border-b-2 border-primary h-full flex items-center px-2" href="#">Analysis</a>
|
||||
<a class="text-on-surface-variant font-label-md text-label-md h-full flex items-center px-2 hover:bg-surface-container transition-colors duration-200" href="#">Config</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="relative group">
|
||||
<span class="material-symbols-outlined cursor-pointer p-2 hover:bg-surface-container-high rounded-full transition-colors duration-150" data-icon="filter_list">filter_list</span>
|
||||
</div>
|
||||
<span class="material-symbols-outlined cursor-pointer p-2 hover:bg-surface-container-high rounded-full transition-colors duration-150" data-icon="settings">settings</span>
|
||||
<span class="material-symbols-outlined cursor-pointer p-2 hover:bg-surface-container-high rounded-full transition-colors duration-150" data-icon="help">help</span>
|
||||
</div>
|
||||
</header>
|
||||
<div class="flex h-[calc(100vh-64px)] overflow-hidden">
|
||||
<!-- Side Navigation Shell -->
|
||||
<aside class="bg-surface-container-low text-primary font-label-md text-label-md docked left-0 h-full w-sidebar-width border-r border-outline-variant flex flex-col sticky top-0">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center gap-3 mb-8">
|
||||
<div class="w-10 h-10 rounded-full bg-primary-container flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-surface" data-icon="router">router</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-headline-sm text-[14px] font-bold text-primary">Network Monitor</div>
|
||||
<div class="text-[11px] text-on-surface-variant">Active Nodes: 1,240</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="w-full bg-primary text-on-primary py-2.5 rounded-lg font-label-md text-label-md mb-8 hover:opacity-90 transition-opacity">
|
||||
New Capture
|
||||
</button>
|
||||
<nav class="space-y-1">
|
||||
<a class="flex items-center gap-3 px-4 py-3 bg-secondary-container text-on-secondary-container font-semibold rounded-lg transition-all duration-150" href="#">
|
||||
<span class="material-symbols-outlined" data-icon="history">history</span>
|
||||
<span>Sessions</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-4 py-3 text-on-surface-variant hover:bg-surface-container-high rounded-lg transition-all duration-150" href="#">
|
||||
<span class="material-symbols-outlined" data-icon="router">router</span>
|
||||
<span>Devices</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-4 py-3 text-on-surface-variant hover:bg-surface-container-high rounded-lg transition-all duration-150" href="#">
|
||||
<span class="material-symbols-outlined" data-icon="analytics">analytics</span>
|
||||
<span>Diagnostics</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-4 py-3 text-on-surface-variant hover:bg-surface-container-high rounded-lg transition-all duration-150" href="#">
|
||||
<span class="material-symbols-outlined" data-icon="list_alt">list_alt</span>
|
||||
<span>Logs</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="mt-auto p-6 border-t border-outline-variant">
|
||||
<a class="flex items-center gap-3 px-4 py-3 text-on-surface-variant hover:bg-surface-container-high rounded-lg transition-all duration-150" href="#">
|
||||
<span class="material-symbols-outlined" data-icon="logout">logout</span>
|
||||
<span>Logout</span>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
<!-- Main Content Area -->
|
||||
<main class="flex-1 flex flex-col overflow-hidden bg-white">
|
||||
<!-- Session Status Header -->
|
||||
<div class="flex items-center justify-between px-8 py-4 bg-surface-container-low border-b border-outline-variant">
|
||||
<div class="flex gap-8">
|
||||
<div>
|
||||
<span class="text-label-md font-label-md text-on-surface-variant block uppercase tracking-wider mb-1">Device Serial</span>
|
||||
<span class="text-body-md font-mono-md font-medium text-on-surface">TR069-NODE-550291-B</span>
|
||||
</div>
|
||||
<div class="h-10 w-px bg-outline-variant"></div>
|
||||
<div>
|
||||
<span class="text-label-md font-label-md text-on-surface-variant block uppercase tracking-wider mb-1">IP Address</span>
|
||||
<span class="text-body-md font-mono-md font-medium text-on-surface">192.168.100.42</span>
|
||||
</div>
|
||||
<div class="h-10 w-px bg-outline-variant"></div>
|
||||
<div>
|
||||
<span class="text-label-md font-label-md text-on-surface-variant block uppercase tracking-wider mb-1">Connection Status</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full bg-secondary"></span>
|
||||
<span class="text-body-md font-medium text-on-surface">CWMP Session Established</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-3 py-1 bg-surface-container text-on-surface-variant rounded-full text-label-md font-label-md border border-outline-variant">Session ID: 88241</span>
|
||||
<button class="material-symbols-outlined text-on-surface-variant p-1" data-icon="more_vert">more_vert</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sequence Diagram Content -->
|
||||
<div class="flex-1 flex overflow-hidden">
|
||||
<div class="flex-1 overflow-y-auto relative bg-slate-50 p-12">
|
||||
<!-- Lifeline Headers -->
|
||||
<div class="sticky top-0 z-10 flex justify-around mb-12 pointer-events-none">
|
||||
<div class="px-10 py-3 bg-primary text-on-primary rounded font-bold shadow-sm border border-primary min-w-[120px] text-center">CPE</div>
|
||||
<div class="px-10 py-3 bg-primary text-on-primary rounded font-bold shadow-sm border border-primary min-w-[120px] text-center">ACS</div>
|
||||
</div>
|
||||
<!-- Vertical Lifelines -->
|
||||
<div class="absolute top-0 bottom-0 left-1/4 w-px bg-outline pointer-events-none opacity-20"></div>
|
||||
<div class="absolute top-0 bottom-0 right-1/4 w-px bg-outline pointer-events-none opacity-20"></div>
|
||||
<!-- Messages -->
|
||||
<div class="relative space-y-10 pb-20">
|
||||
<!-- Row: Inform -->
|
||||
<div class="relative group cursor-pointer" onclick="selectMessage('inform')">
|
||||
<div class="absolute left-[25%] right-[25%] flex items-center">
|
||||
<div class="flex-1 h-px bg-secondary relative">
|
||||
<div class="absolute right-0 -top-[5px] border-y-[6px] border-y-transparent border-l-[10px] border-l-secondary"></div>
|
||||
</div>
|
||||
<div class="absolute left-1/2 -translate-x-1/2 -top-6 text-center">
|
||||
<span class="px-2 py-0.5 bg-secondary-container text-on-secondary-container text-label-md font-label-md rounded border border-secondary/20">Inform</span>
|
||||
<span class="block text-[10px] font-mono-sm text-on-surface-variant mt-1">10:05:22.450</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-8 invisible">spacer</div>
|
||||
</div>
|
||||
<!-- Row: InformResponse -->
|
||||
<div class="relative group cursor-pointer" onclick="selectMessage('inform-response')">
|
||||
<div class="absolute left-[25%] right-[25%] flex items-center">
|
||||
<div class="flex-1 h-px bg-tertiary-container relative">
|
||||
<div class="absolute left-0 -top-[5px] border-y-[6px] border-y-transparent border-r-[10px] border-r-tertiary-container"></div>
|
||||
</div>
|
||||
<div class="absolute left-1/2 -translate-x-1/2 -top-6 text-center">
|
||||
<span class="px-2 py-0.5 bg-surface-container-high text-on-tertiary-fixed-variant text-label-md font-label-md rounded border border-outline">InformResponse</span>
|
||||
<span class="block text-[10px] font-mono-sm text-on-surface-variant mt-1">10:05:22.612</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-8 invisible">spacer</div>
|
||||
</div>
|
||||
<!-- Selected Row: GetParameterValues -->
|
||||
<div class="relative group cursor-pointer" onclick="selectMessage('get-params')">
|
||||
<!-- Selection Highlight -->
|
||||
<div class="absolute -left-12 -right-12 h-16 bg-surface-container border-y border-outline-variant -mt-4 opacity-50 z-0"></div>
|
||||
<div class="absolute left-[25%] right-[25%] flex items-center z-10">
|
||||
<div class="flex-1 h-[2px] bg-primary relative">
|
||||
<div class="absolute left-0 -top-[6px] border-y-[7px] border-y-transparent border-r-[12px] border-r-primary"></div>
|
||||
</div>
|
||||
<div class="absolute left-1/2 -translate-x-1/2 -top-6 text-center">
|
||||
<span class="px-2 py-0.5 bg-primary text-on-primary text-label-md font-label-md rounded shadow-sm">GetParameterValues</span>
|
||||
<span class="block text-[10px] font-mono-sm font-bold text-primary mt-1">10:05:23.002</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-8 invisible">spacer</div>
|
||||
</div>
|
||||
<!-- Row: GetParameterValuesResponse -->
|
||||
<div class="relative group cursor-pointer" onclick="selectMessage('get-params-response')">
|
||||
<div class="absolute left-[25%] right-[25%] flex items-center">
|
||||
<div class="flex-1 h-px bg-secondary relative">
|
||||
<div class="absolute right-0 -top-[5px] border-y-[6px] border-y-transparent border-l-[10px] border-l-secondary"></div>
|
||||
</div>
|
||||
<div class="absolute left-1/2 -translate-x-1/2 -top-6 text-center">
|
||||
<span class="px-2 py-0.5 bg-secondary-container text-on-secondary-container text-label-md font-label-md rounded border border-secondary/20">GetParameterValuesResponse</span>
|
||||
<span class="block text-[10px] font-mono-sm text-on-surface-variant mt-1">10:05:23.189</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-8 invisible">spacer</div>
|
||||
</div>
|
||||
<!-- Row: SetParameterValues -->
|
||||
<div class="relative group cursor-pointer">
|
||||
<div class="absolute left-[25%] right-[25%] flex items-center">
|
||||
<div class="flex-1 h-px bg-tertiary-container relative">
|
||||
<div class="absolute left-0 -top-[5px] border-y-[6px] border-y-transparent border-r-[10px] border-r-tertiary-container"></div>
|
||||
</div>
|
||||
<div class="absolute left-1/2 -translate-x-1/2 -top-6 text-center">
|
||||
<span class="px-2 py-0.5 bg-surface-container-high text-on-tertiary-fixed-variant text-label-md font-label-md rounded border border-outline">SetParameterValues</span>
|
||||
<span class="block text-[10px] font-mono-sm text-on-surface-variant mt-1">10:05:23.441</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-8 invisible">spacer</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Inspector Panel (Right) -->
|
||||
<div class="w-[450px] bg-white border-l border-outline-variant flex flex-col shadow-xl z-20">
|
||||
<div class="p-4 border-b border-outline-variant bg-surface flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-on-surface-variant" data-icon="description">description</span>
|
||||
<span class="font-headline-sm text-[16px] font-bold text-on-surface">Message Detail</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="material-symbols-outlined text-on-surface-variant hover:text-primary transition-colors" data-icon="content_copy">content_copy</button>
|
||||
<button class="material-symbols-outlined text-on-surface-variant hover:text-primary transition-colors" data-icon="close">close</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 overflow-auto bg-surface-container-low p-4">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span class="text-label-md font-label-md bg-primary-container text-on-primary-container px-2 py-1 rounded">CWMP:GetParameterValues</span>
|
||||
<span class="text-mono-sm text-on-surface-variant">ID: 1024_A</span>
|
||||
</div>
|
||||
<!-- Code Block Display -->
|
||||
<div class="rounded-lg border border-outline-variant bg-white p-4 font-mono-md text-mono-md leading-relaxed shadow-inner overflow-hidden">
|
||||
<pre class="whitespace-pre-wrap"><span class="text-secondary"><soap:Envelope</span> <span class="text-tertiary-fixed-variant">xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"</span><span class="text-secondary">></span>
|
||||
<span class="text-secondary"><soap:Header></span>
|
||||
<span class="text-secondary"><cwmp:ID</span> <span class="text-tertiary-fixed-variant">soap:mustUnderstand="1"</span><span class="text-secondary">></span>1024_A<span class="text-secondary"></cwmp:ID></span>
|
||||
<span class="text-secondary"></soap:Header></span>
|
||||
<span class="text-secondary"><soap:Body></span>
|
||||
<span class="text-secondary"><cwmp:GetParameterValues></span>
|
||||
<span class="text-secondary"><ParameterNames</span> <span class="text-tertiary-fixed-variant">soap:arrayType="xsd:string[3]"</span><span class="text-secondary">></span>
|
||||
<span class="text-secondary"><string></span>InternetGatewayDevice.DeviceInfo.HardwareVersion<span class="text-secondary"></string></span>
|
||||
<span class="text-secondary"><string></span>InternetGatewayDevice.DeviceInfo.SoftwareVersion<span class="text-secondary"></string></span>
|
||||
<span class="text-secondary"><string></span>InternetGatewayDevice.ManagementServer.ConnectionRequestURL<span class="text-secondary"></string></span>
|
||||
<span class="text-secondary"></ParameterNames></span>
|
||||
<span class="text-secondary"></cwmp:GetParameterValues></span>
|
||||
<span class="text-secondary"></soap:Body></span>
|
||||
<span class="text-secondary"></soap:Envelope></span></pre>
|
||||
</div>
|
||||
<div class="mt-6 space-y-4">
|
||||
<h4 class="text-label-md font-label-md text-on-surface-variant uppercase tracking-wider">Analysis Summary</h4>
|
||||
<div class="p-3 bg-secondary-container/20 border-l-4 border-secondary rounded-r">
|
||||
<p class="text-body-sm text-on-surface">ACS is requesting device hardware and software versioning information to determine compatible firmware upgrade paths.</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="bg-white p-3 border border-outline-variant rounded">
|
||||
<span class="text-label-md font-label-md text-on-surface-variant block mb-1">MTU Size</span>
|
||||
<span class="text-body-md font-medium">1500 Bytes</span>
|
||||
</div>
|
||||
<div class="bg-white p-3 border border-outline-variant rounded">
|
||||
<span class="text-label-md font-label-md text-on-surface-variant block mb-1">Latency</span>
|
||||
<span class="text-body-md font-medium text-secondary">42ms</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<!-- Micro-interaction script -->
|
||||
<script>
|
||||
function selectMessage(id) {
|
||||
// Placeholder logic for visual feedback in a prototype
|
||||
console.log('Selected message context:', id);
|
||||
}
|
||||
|
||||
// Add subtle hover tracking for interactive feel
|
||||
document.querySelectorAll('.group').forEach(item => {
|
||||
item.addEventListener('mouseenter', () => {
|
||||
item.style.transform = 'translateX(2px)';
|
||||
item.style.transition = 'transform 0.1s ease-out';
|
||||
});
|
||||
item.addEventListener('mouseleave', () => {
|
||||
item.style.transform = 'translateX(0)';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body></html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 255 KiB |
Reference in new issue
Block a user