Skip to content

Conversation

@thegranduke
Copy link
Contributor

@thegranduke thegranduke commented Nov 24, 2025

  • add the throughput, user consumption, and device stats cards to /dashboard
  • style the charts to match the device overview visuals and auto-scale units (Kbps ↔ Gbps, KB ↔ TB)
  • introduce a shared time-range selector plus headline metrics (total transferred, peak, top user)
  • compute trend percentages by comparing the selected period with the previous period, and render arrows + percentages
  • improve resilience: graceful empty states, sequential device fallback for stats, and better tooltips/logging

Summary by CodeRabbit

  • New Features

    • Network Overview dashboard with selectable time ranges (5min, 1hour, 24hour, 7days)
    • Network throughput chart showing total data transferred, peak, and trend vs. prior period
    • User activity chart showing per-user consumption with top-user summary
    • Device statistics card that auto-discovers monitored devices and refreshes periodically
  • Improvements

    • More resilient data handling, graceful empty/loading states, and improved error handling across dashboard charts

✏️ Tip: You can customize this high-level summary in your review settings.

…shed adaptive metrics**

- add NetworkThroughputChart, UserActivityChart, and DashboardDeviceStats to the dashboard
- introduce a global timeframe selector (5 min, 1 hr, 24 hr, 7 days) that drives all charts
- fetch aggregated port stats for all switches and reuse DeviceStatsChart in a simplified wrapper
- implement user-consumption sorting, top-user highlighting, and summary cards
- add empty states, logging, and refresh intervals for clearer monitoring feedback
- restyle charts for consistency with device overview (gradient fills, unified tooltips, improved axes)
- auto-convert throughput (K/M/Gbps) and consumption (K/M/G/TB) for readability
- surface headline metrics: total data transferred, peak throughput, and trend vs previous period
- fetch previous-period data to compute trend and render only once both datasets resolve (no flicker)

**Overview:**

Dashboard now offers live throughput, consumption, and device stats with unified time-range control, adaptive metric formatting, and consistent UI. Critical metrics (total, peak, trends, top consumers) are surfaced up front, and charts remain readable across all traffic scales.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 24, 2025

Walkthrough

Adds client-side time-range state and a Network Overview to the dashboard; introduces three new chart components (NetworkThroughputChart, UserActivityChart, DashboardDeviceStats) that fetch and render aggregated metrics; makes AggregateTimeSeriesPoint fields optional and relaxes null-checks in port utilization chart.

Changes

Cohort / File(s) Summary
Dashboard Layout & Time-Range
ui/ui/src/app/dashboard/page.tsx
Adds client-side timeRange state ("5min" | "1hour" | "24hour" | "7days"), a Network Overview header and selector, and renders NetworkThroughputChart, UserActivityChart, and DashboardDeviceStats in a responsive grid.
New Dashboard Components
ui/ui/src/components/dashboard/DashboardDeviceStats.tsx, ui/ui/src/components/dashboard/NetworkThroughputChart.tsx, ui/ui/src/components/dashboard/UserActivityChart.tsx
Adds three default-export React components: DashboardDeviceStats (device discovery, per-device stats polling, refresh/cleanup, loading/empty states), NetworkThroughputChart (aggregates port throughput, metrics, trend, periodic refresh), and UserActivityChart (per-user consumption, dynamic units, bar chart, periodic refresh).
Port Utilization Chart
ui/ui/src/components/graphs/port-utilization/PortUtilizationChart.tsx
Replaces strict null checks (!== null) with loose null checks (!= null) for avg_utilization and max_utilization, treating undefined as missing as well.
Types
ui/ui/src/lib/types.ts
Makes fields on AggregateTimeSeriesPoint optional (ip_address, port_name, avg_utilization, max_utilization, avg_throughput, max_throughput) and adds optional total_throughput.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DashboardPage
    participant TimeRangeSelect
    participant NetworkThroughputChart
    participant UserActivityChart
    participant DashboardDeviceStats
    participant API

    User->>DashboardPage: Open dashboard
    DashboardPage->>DashboardPage: init timeRange ("1hour")

    rect rgba(200,220,255,0.25)
      User->>TimeRangeSelect: change range
      TimeRangeSelect->>DashboardPage: update timeRange
      DashboardPage->>NetworkThroughputChart: pass timeRange
      DashboardPage->>UserActivityChart: pass timeRange
      DashboardPage->>DashboardDeviceStats: mount
    end

    rect rgba(220,200,255,0.12)
      NetworkThroughputChart->>API: fetchPortStatsAggregate (current & previous)
      API-->>NetworkThroughputChart: aggregated throughput
      NetworkThroughputChart->>NetworkThroughputChart: compute peak/total & render
    end

    rect rgba(255,220,200,0.12)
      UserActivityChart->>API: fetchAggregateFlowsByUser(timeRange)
      API-->>UserActivityChart: per-user aggregates
      UserActivityChart->>UserActivityChart: convert/sort & render
    end

    rect rgba(220,255,200,0.12)
      DashboardDeviceStats->>API: fetch devices
      API-->>DashboardDeviceStats: device list
      loop per device with lan_ip_address
        DashboardDeviceStats->>API: fetchDeviceStatsAggregate(device, timeRange)
        API-->>DashboardDeviceStats: device stats or empty
      end
      DashboardDeviceStats->>DashboardDeviceStats: pick first device with data & render
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay special attention to DashboardDeviceStats.tsx: polling, AbortController cleanup, per-device retry/fallback logic.
  • Verify correctness of aggregation, unit formatting, and previous-period comparison in NetworkThroughputChart.tsx.
  • Confirm UserActivityChart dynamic unit selection and label formatting.
  • Ensure consumers handle newly optional fields in AggregateTimeSeriesPoint and that relaxed null-checks don't hide unexpected undefineds.

Possibly related PRs

  • PR #32 — introduces device-stats API and fetchDeviceStatsAggregate utility used by DashboardDeviceStats.
  • PR #26 — modifies port-utilization UI and types; overlaps with types and chart changes here.
  • PR #25 — earlier AggregateTimeSeriesPoint changes related to port-utilization types that align with this PR.

Poem

🐰 I nibble bytes and hop through charts,
Time ranges twirl like tiny arts,
Devices hum and metrics gleam,
Charts bloom bright — a rabbit's dream,
Hop on, fetch fast, the dashboard sings!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: adding time-range control and metrics to dashboard charts.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (10)
ui/ui/src/lib/types.ts (1)

1157-1165: AggregateTimeSeriesPoint optional fields and total_throughput look consistent

Making utilization/throughput fields optional and adding total_throughput matches the new dashboard usage and relaxed null checks. Just ensure any consumers that build keys from ip_address/port_name either only receive per-port records or explicitly skip records where those are absent.

ui/ui/src/components/graphs/port-utilization/PortUtilizationChart.tsx (1)

70-94: Using != null for utilization guards correctly against undefined

Switching to point.avg_utilization != null / point.max_utilization != null is the right adjustment now that these fields can be undefined; it prevents NaNs from leaking into the chart when the backend omits values.

If you ever want to distinguish “unknown” from real 0% in the tooltip, consider rendering an "N/A" label when the stored value is null rather than treating it as 0.

ui/ui/src/components/dashboard/NetworkThroughputChart.tsx (3)

45-97: Metrics and trend calculation are sound; consider reusing aggregated series

The aggregation to timeMap for peak and total transferred, plus the trend percentage against previousPeriodTotal, is logically consistent given Mbps input and fixed intervalSeconds. This same “timestamp → summed throughput” reduction is repeated in several places, though; extracting a useMemo that returns the aggregated series once (and reusing it for metrics, trend, and chart data) would reduce duplication and make it easier to evolve the logic (e.g., to switch to total_throughput if the backend starts populating it).

Also applies to: 115-126


184-272: Data fetching and previous-period comparison are reasonable; consider cancellation

Fetching current and 2×-window data in parallel, then splitting the wider window at the midpoint to approximate the “previous period” total, is a pragmatic approach and looks correct given a regular bucket cadence. The polling and cleanup with setInterval / clearInterval are also handled properly. For extra robustness, you might later introduce an AbortController and pass its signal into fetchPortStatsAggregate so that in-flight requests from an old timeRange don’t race with newer ones.


150-180: Y-axis auto-scaling is good; align chart label with chosen unit

The getYAxisUnit logic correctly picks Mbps/Gbps/Kbps based on the max aggregated throughput. Right now, ChartContainer’s config label is hardcoded to "Mbps", so the legend/unit hint can diverge from the actual Y-axis unit when the data is very small or very large. Consider wiring the label to yAxisConfig.unit so everything stays in sync.

Also applies to: 328-419

ui/ui/src/app/dashboard/page.tsx (1)

18-24: Time range selector and wiring look correct; avoid duplicating TimeRange type

State management and the Select wiring for timeRange are straightforward and correctly thread the value into both NetworkThroughputChart and UserActivityChart. Since the same "5min" | "1hour" | "24hour" | "7days" union is now defined in three files, consider extracting a shared TimeRange type (e.g., in a small dashboard/types module) to keep them from drifting if you add new ranges later.

Also applies to: 30-35, 66-89

ui/ui/src/components/dashboard/UserActivityChart.tsx (1)

15-19: Data loading and TimeRange handling work; consider shared type and basic error handling

Mapping timeRange → period string and polling cadence is clear, and the effect correctly refreshes on token or timeRange changes with proper interval cleanup. It might be worth (a) reusing a shared TimeRange type with the dashboard/throughput chart to avoid drift, and (b) wrapping fetchAggregateFlowsByUser in a small try/catch so a transient network/API error doesn’t surface as an unhandled exception but instead shows the empty-state message.

Also applies to: 103-130, 132-156

ui/ui/src/components/dashboard/DashboardDeviceStats.tsx (3)

37-39: Improve type safety for response normalization.

The type assertion bypasses TypeScript's type checking. Consider using a type guard function instead to ensure runtime safety.

Add a type guard before the normalization logic:

// At the top of the file or in a utils file
function isResultsResponse(value: unknown): value is { results: NetworkDeviceDetails[] } {
  return (
    typeof value === 'object' && 
    value !== null && 
    'results' in value &&
    Array.isArray((value as { results: unknown }).results)
  );
}

Then use it:

         // handle paginated or array response
         let devices: NetworkDeviceDetails[] = [];
         if (Array.isArray(switches)) {
           devices = switches;
-        } else if (switches && typeof switches === 'object' && 'results' in switches) {
-          const results = (switches as { results: NetworkDeviceDetails[] }).results;
-          devices = Array.isArray(results) ? results : [];
+        } else if (isResultsResponse(switches)) {
+          devices = switches.results;
         }

107-108: Consider preventing overlapping refresh requests.

If load() takes longer than 60 seconds (e.g., due to slow network or multiple device retries), the interval will trigger another request before the previous one completes, potentially causing overlapping requests and unnecessary load.

Consider using setTimeout recursively instead of setInterval:

     };
     load();
-    // Refresh every minute like the other charts
-    const interval = setInterval(load, 60000);
+    // Refresh every minute like the other charts, but wait for previous load to complete
+    let timeoutId: NodeJS.Timeout;
+    const scheduleNext = () => {
+      timeoutId = setTimeout(async () => {
+        await load();
+        if (isMounted) scheduleNext();
+      }, 60000);
+    };
+    scheduleNext();
     return () => {
       isMounted = false;
       abortController.abort();
-      clearInterval(interval);
+      clearTimeout(timeoutId);
     };

111-128: LGTM! Consider adding ARIA labels for accessibility.

The loading and empty states are well-implemented with clear user feedback. As an optional enhancement, consider adding role and aria-label attributes to improve screen reader support.

Optional: Add accessibility attributes:

       <Card>
         <CardHeader>
           <CardTitle>Device Statistics</CardTitle>
         </CardHeader>
         <CardContent>
-          <div className="flex items-center justify-center min-h-[300px] text-muted-foreground">
+          <div 
+            className="flex items-center justify-center min-h-[300px] text-muted-foreground"
+            role="status"
+            aria-label="No devices available"
+          >
             No monitored devices found.
           </div>
         </CardContent>
       </Card>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2db6b51 and 309b7e9.

📒 Files selected for processing (6)
  • ui/ui/src/app/dashboard/page.tsx (3 hunks)
  • ui/ui/src/components/dashboard/DashboardDeviceStats.tsx (1 hunks)
  • ui/ui/src/components/dashboard/NetworkThroughputChart.tsx (1 hunks)
  • ui/ui/src/components/dashboard/UserActivityChart.tsx (1 hunks)
  • ui/ui/src/components/graphs/port-utilization/PortUtilizationChart.tsx (1 hunks)
  • ui/ui/src/lib/types.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
ui/ui/src/app/dashboard/page.tsx (2)
ui/ui/src/components/dashboard/NetworkThroughputChart.tsx (1)
  • NetworkThroughputChart (19-425)
ui/ui/src/components/dashboard/UserActivityChart.tsx (1)
  • UserActivityChart (21-205)
ui/ui/src/components/dashboard/DashboardDeviceStats.tsx (6)
ui/ui/src/lib/types.ts (2)
  • DeviceStatsTimeSeriesPoint (1177-1185)
  • NetworkDeviceDetails (72-83)
backend/control_center/general/views.py (1)
  • switches (570-574)
ui/ui/src/lib/devices.ts (1)
  • fetchSwitches (97-104)
ui/ui/src/lib/deviceStats.ts (1)
  • fetchDeviceStatsAggregate (34-54)
ui/ui/src/components/graphs/device-stats/DeviceStatsSkeleton.tsx (1)
  • DeviceStatsSkeleton (26-44)
ui/ui/src/components/graphs/device-stats/DeviceStatsChart.tsx (1)
  • DeviceStatsChart (40-193)
ui/ui/src/components/dashboard/UserActivityChart.tsx (1)
ui/ui/src/lib/networkData.ts (1)
  • fetchAggregateFlowsByUser (43-53)
ui/ui/src/components/dashboard/NetworkThroughputChart.tsx (2)
ui/ui/src/lib/types.ts (1)
  • AggregateTimeSeriesPoint (1157-1166)
ui/ui/src/lib/portUtilization.ts (1)
  • fetchPortStatsAggregate (64-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Docker Build Validation
🔇 Additional comments (4)
ui/ui/src/components/dashboard/UserActivityChart.tsx (1)

44-101: Unit conversion and chart rendering are coherent

formatBytes and getYAxisUnit correctly treat stored values as MB and convert to B/KB/MB/GB/TB as needed, and the Bar/LabelList usage produces intuitive per-user labels even with the numeric X-axis hidden. The overall chart behavior (sorted descending, top user highlighted, labels using formatBytes) looks consistent and easy to interpret.

Also applies to: 157-199

ui/ui/src/components/dashboard/DashboardDeviceStats.tsx (3)

1-17: LGTM!

Imports are well-organized and all necessary dependencies are included.


18-23: LGTM!

Component state initialization is properly typed and uses appropriate default values.


130-159: LGTM!

The main render logic properly handles both empty data and data-available states with clear, user-friendly messaging. The display of data point count and device IP provides helpful context to users.

Comment on lines 65 to 66
1, // 1 hour
"1 minute"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Major: Hardcoded time range contradicts PR objectives.

The PR summary states: "A shared time-range selector and headline metrics... are introduced." However, this component hardcodes a 1-hour window with 1-minute granularity instead of using a shared time-range control. This creates an inconsistent user experience where other dashboard charts may respond to time-range changes but device stats remain fixed at 1 hour.

Consider accepting time range parameters as props or from a shared context to align with the dashboard's time-range selector mentioned in the PR objectives.

🤖 Prompt for AI Agents
In ui/ui/src/components/dashboard/DashboardDeviceStats.tsx around lines 65-66,
the component currently hardcodes a 1-hour window and 1-minute granularity which
contradicts the PR's shared time-range objective; update the component to accept
the dashboard time-range (either via props or from the shared time-range
context/store) and use those values for the window and resolution instead of the
hardcoded "1 hour / 1 minute" so the device stats react to global time-range
changes and remain consistent with other dashboard charts.

- cancel in-flight stats requests when the component unmounts or the
  auth token changes by wiring fetchDeviceStatsAggregate to an
  AbortController signal
- guard all setState calls behind an isMounted flag to avoid React
  warnings and memory leaks
- remove noisy console.log/warn debug traces while keeping real error
  logging in place

Overview:
- Prevents pressure from uncancelled polling requests.
- Keeps the console clean and avoids state updates after unmount.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
ui/ui/src/components/dashboard/DashboardDeviceStats.tsx (1)

61-62: Hardcoded time range contradicts PR objectives.

The 1-hour window and 1-minute granularity are still hardcoded, which is inconsistent with the PR's stated goal of introducing "A shared time-range selector." This creates a dashboard where other charts respond to time-range changes but device stats remain fixed.

As noted in the previous review, consider accepting time range parameters via props or from a shared context to align with the dashboard's time-range selector.

🧹 Nitpick comments (2)
ui/ui/src/components/dashboard/DashboardDeviceStats.tsx (2)

31-31: Update misleading comment.

The comment states "Get first switch" but the code actually fetches all switches and iterates through them sequentially (lines 52-78) to find one with available stats data.

Apply this diff:

-        // 1. Get first switch
+        // Get all switches and try each until we find one with stats data
         const switches = await fetchSwitches(token);

86-93: Consider displaying error state to users.

When an error occurs, the component logs it but only shows a generic empty state to the user. Users cannot distinguish between "no data available" and "error fetching data," which could lead to confusion during troubleshooting.

Consider adding an error state:

   const [data, setData] = useState<DeviceStatsTimeSeriesPoint[]>([]);
   const [device, setDevice] = useState<NetworkDeviceDetails | null>(null);
   const [loading, setLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);

Then in the catch block:

       } catch (e) {
         if (abortController.signal.aborted) return;
         console.error("Failed to load device stats for dashboard", e);
         if (e instanceof Error) {
           console.error("Error details:", e.message, e.stack);
         }
         if (!isMounted) return;
+        setError("Failed to load device statistics. Please try again later.");
         setData([]);

And render it in the UI:

+  if (error) {
+    return (
+      <Card>
+        <CardHeader>
+          <CardTitle>Device Statistics</CardTitle>
+        </CardHeader>
+        <CardContent>
+          <div className="flex items-center justify-center min-h-[300px] text-destructive">
+            {error}
+          </div>
+        </CardContent>
+      </Card>
+    );
+  }
+
   if (!device) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 309b7e9 and d0dd437.

📒 Files selected for processing (1)
  • ui/ui/src/components/dashboard/DashboardDeviceStats.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ui/ui/src/components/dashboard/DashboardDeviceStats.tsx (5)
ui/ui/src/lib/types.ts (2)
  • DeviceStatsTimeSeriesPoint (1177-1185)
  • NetworkDeviceDetails (72-83)
ui/ui/src/lib/devices.ts (1)
  • fetchSwitches (97-104)
ui/ui/src/lib/deviceStats.ts (1)
  • fetchDeviceStatsAggregate (34-54)
ui/ui/src/components/graphs/device-stats/DeviceStatsSkeleton.tsx (1)
  • DeviceStatsSkeleton (26-44)
ui/ui/src/components/graphs/device-stats/DeviceStatsChart.tsx (1)
  • DeviceStatsChart (40-193)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Docker Build Validation
🔇 Additional comments (4)
ui/ui/src/components/dashboard/DashboardDeviceStats.tsx (4)

1-23: LGTM! Clean component setup.

The imports are well-organized and the state management is properly typed. The component structure follows React best practices.


24-108: Well done! Memory leak prevention properly implemented.

The component now correctly handles cleanup with AbortController, the isMounted flag, and proper state update guards. This addresses the critical memory leak issue from the previous review.


110-127: LGTM! Well-structured loading and empty states.

The skeleton loader provides good UX during data fetching, and the empty state clearly communicates when no devices are available for monitoring.


129-158: LGTM! Clear and informative rendering logic.

The component provides good user feedback with device identification in the card description, data point counts, and helpful empty states when no statistics are available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant