Task 8 ยท Frontend

Frontend Dashboard

EdgeGuard โ€” a React single-page application with 7 pages, dark/light mode, interactive benchmark charts, and live cluster monitoring. Deployed as 2 highly-available k3s replicas.

๐Ÿ‘ค Lead: Muhammad Saleem, Muhammad Furqan Shafique โœ“ Complete React ยท nginx ยท JWT ยท k3s ยท REST API

Overview

The EdgeGuard frontend is a React Single Page Application (SPA) โ€” a web app that loads once and handles all page navigation client-side without full page reloads. It communicates exclusively with the Flask backend REST API and never touches the database or MinIO directly.

The application has 7 pages covering every aspect of the EdgeGuard system โ€” live camera feed, security alerts, cluster health, auto-scaling, pod distribution, benchmark results, and user management. All pages support both dark and light mode with a toggle persisted in localStorage.

The built React app is served by nginx running inside a Docker container, deployed as 2 k3s replicas with pod anti-affinity โ€” same high-availability approach as the backend. Users access the app via a k3s NodePort service.

โ„น๏ธ Why React and not plain HTML/JavaScript?

A 7-page application with live data, interactive charts, pagination, and real-time updates would be extremely complex to maintain in plain HTML/JS. React's component model lets each page be built independently and reused โ€” the node health card component, for example, is written once and used in both the Monitoring page and the Dashboard overview. State management and automatic re-rendering when data changes are built in.

Tech Stack

UI Framework

React
Component-based JavaScript framework. Each page and UI element is a reusable React component. State management handles live data updates.

Web Server

nginx
Serves the compiled React build (static HTML/JS/CSS). Also handles SPA routing โ€” all URL paths return index.html so React Router handles navigation.

API Communication

Fetch / Axios
All API calls go to the Flask backend. JWT token stored in localStorage is attached to every request as Authorization: Bearer header.

Charts

SVG Charts
Custom SVG-based charts for benchmark results. Interactive hover tooltips, color-coded efficiency bars, and ideal vs actual speedup lines.

Routing

React Router
Client-side routing between the 7 pages. No page reload on navigation โ€” the SPA loads once and React handles all URL changes.

Deployment

Docker + k3s
React build output copied into nginx Docker image. Deployed as 2 k3s replicas with anti-affinity โ€” same pattern as backend.

How the Frontend Talks to the Backend

Every page in EdgeGuard needs data from the backend. The pattern is always the same: React component mounts โ†’ calls backend API with JWT token โ†’ receives JSON โ†’ renders data. Live pages (Camera, Monitoring) poll the API every few seconds to keep data fresh.

javascriptStandard API call pattern used across all pages
// Standard API call pattern โ€” used on every page
const fetchData = async () => {
  const token = localStorage.getItem('token');
  
  const response = await fetch(`${API_BASE}/api/alerts?page=${page}`, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    }
  });

  if (response.status === 401) {
    // Token expired โ€” redirect to login
    localStorage.removeItem('token');
    navigate('/login');
    return;
  }

  const data = await response.json();
  setAlerts(data.alerts);
  setTotalPages(data.total_pages);
};

// Auto-refresh every 10 seconds for live pages
useEffect(() => {
  fetchData();
  const interval = setInterval(fetchData, 10000);
  return () => clearInterval(interval);  // cleanup on unmount
}, [page]);

The 7 Pages

Page 1
๐ŸŽฅ Live Camera

Displays a real-time video feed from the Raspberry Pi 4 AI Camera with AI detection overlays. Each detected person has a bounding box showing their classification (face_visible in green, face_covered in red) and the confidence score percentage.

How it works

The backend proxies the ZMQ video stream from Pi4 as an MJPEG HTTP stream at /api/stream. The React frontend renders this as a standard HTML <img> tag pointing to the stream endpoint โ€” the browser handles MJPEG rendering natively. Detection bounding boxes are drawn on a transparent canvas overlay positioned exactly on top of the video element. The frontend polls /api/latest-detections every 100ms to get current bounding box coordinates and labels.

API: /api/stream API: /api/latest-detections Polling: 100ms Canvas overlay
Page 2
๐Ÿ”” Alerts

Paginated history of all suspicious events detected by the AI. Each alert shows the event type, confidence score, timestamp, camera ID, and the snapshot image fetched from MinIO via a presigned URL. Alerts are numbered with a per-alert ID for easy reference during incident review.

How it works

The frontend calls /api/alerts?page=<n>&limit=10 on load and on every page change. The backend returns a paginated JSON response including presigned MinIO image URLs. The frontend renders each alert as a card with the snapshot image, confidence badge, and timestamp. Navigation uses First / Previous / Next / Last buttons with the current page and total shown. Individual alerts can be deleted โ€” this calls DELETE /api/alerts/<id> which removes both the database record and the MinIO snapshot.

API: /api/alerts Pagination: First/Last MinIO presigned URLs Delete support
Page 3
๐Ÿ“Š Cluster Monitoring

Live health grid showing all 10 cluster nodes with real-time CPU usage, RAM usage, uptime, and online/offline status. Service health panel shows whether the Detection Service, Backend API, Prometheus, and Grafana are running. Direct link to the Grafana dashboard for deeper time-series inspection.

How it works

Polls /api/system/status every 10 seconds. The backend queries Prometheus for each node's current CPU and RAM, checks service pod status via the Kubernetes API, and returns a unified JSON response. Each node is rendered as a card with a color indicator โ€” green (healthy), yellow (high load), red (offline). CPU and RAM are shown as percentage bars. If a node does not respond to Prometheus scrapes, it shows as offline immediately.

API: /api/system/status Polling: 10s Prometheus data 10 node cards
Page 4
โšก Auto-Scaler

Shows the current cluster CPU average and the auto-scaling status. When cluster CPU exceeds a configurable threshold, the Pi4 sensor-node automatically joins the k3s cluster as an emergency compute node, adding 4 more cores. When CPU drops below the threshold, it gracefully leaves.

How it works

The backend reads cluster-wide CPU average from Prometheus every 10 seconds. When the threshold is exceeded, it executes a kubectl command to add pi4 as a k3s worker node. The frontend polls /api/autoscaler/status and shows the current CPU gauge, threshold setting (adjustable from the UI), and the Pi4 join/leave history log. The threshold can be changed live from the frontend โ€” the new value is sent to the backend via a POST request and takes effect on the next polling cycle.

API: /api/autoscaler/status Configurable threshold Pi4 join/leave log Prometheus CPU average
Page 5
๐Ÿ—‚๏ธ Pod Distribution

Visual overview of how Kubernetes pods are distributed across all cluster nodes. Shows which pods are running on which Pi3 nodes โ€” useful for verifying that anti-affinity rules are working correctly (e.g. confirming backend Replica 1 and Replica 2 are on different nodes).

How it works

Calls /api/pods which queries the Kubernetes API server for all running pods and their node assignments. The frontend renders a grid โ€” each column is a node, each row is a pod. Pods are color-coded by namespace or application type. This page makes it visually obvious when a node is overloaded with pods or when the anti-affinity rules have placed replicas correctly on separate nodes.

API: /api/pods Kubernetes API Node ร— Pod grid Color-coded namespaces
Page 6
๐Ÿ“ˆ Benchmark Results

Interactive benchmark results for all 4 tasks โ€” Monte Carlo Pi, Matrix Multiplication, HPL, and Task Distributor. Features speedup charts, runtime bars, hover tooltips with exact values, and an MPI Based / Non-MPI toggle to filter between MPI benchmarks (Tasks 2 and 3) and the Task Distributor (Task 4).

How it works

Calls /api/benchmarks on load to fetch all benchmark data from PostgreSQL. Data is processed client-side to compute speedup (time at 1 core / time at N cores), efficiency, and min/max/mean. Charts are built with custom SVG โ€” bar charts for runtime, line charts for speedup vs ideal. Each bar has an interactive tooltip on hover showing exact values. The MPI/Non-MPI toggle filters which benchmark cards are shown โ€” Monte Carlo Pi and Matrix Multiply appear under MPI, Task Distributor appears under Non-MPI. HPL (GFLOPS) is shown separately as it measures a different metric.

Chart features

โ€” Color coding: green (>85% efficiency), yellow (50โ€“85%), red (<50% / parallelization limit reached)
โ€” Ideal speedup line overlaid on actual speedup bars
โ€” Problem size selector tabs (0.1M, 1M, 10M, 100M, 1B, 10B for Monte Carlo)
โ€” N size selector for Matrix Multiply (N=1000 to N=4000)
โ€” Min/Max error bars showing run variance
โ€” Values displayed on each bar (Prof. Baun's format)

API: /api/benchmarks MPI / Non-MPI toggle Custom SVG charts Hover tooltips Color-coded efficiency
Page 7
๐Ÿ‘ค User Administration

Admin page for managing EdgeGuard user accounts. Lists all users with their username, role (admin/viewer), and creation date. Admins can create new users with a password and role, and delete existing users. Regular viewers cannot access this page โ€” it is protected by role-based access control.

How it works

Calls /api/users to list users. Create form sends a POST with username, password, and role. Delete button sends DELETE to /api/users/<id>. The backend validates that the requesting user has admin role before allowing any user management operation. Non-admin users who navigate to this URL are shown a "Not authorized" message โ€” the backend also enforces this server-side regardless of what the frontend shows.

API: /api/users Role-based access Create / Delete users Admin only

Dark / Light Mode

All 7 pages support both dark and light mode. The toggle button is in the navigation bar โ€” clicking it switches all colors instantly without a page reload. The chosen theme is saved in localStorage so it persists across browser sessions.

javascriptTheme implementation โ€” CSS variables + React state
// Theme toggle โ€” stored in localStorage
const [isDark, setIsDark] = useState(
  localStorage.getItem('theme') === 'dark'
);

const toggleTheme = () => {
  const newTheme = !isDark;
  setIsDark(newTheme);
  localStorage.setItem('theme', newTheme ? 'dark' : 'light');
  // Apply to root element โ€” all CSS variables update instantly
  document.documentElement.setAttribute('data-theme', newTheme ? 'dark' : 'light');
};
cssCSS variables for theming
/* Light mode (default) */
:root {
  --bg: #f0f4f8;
  --surface: #ffffff;
  --text: #0f172a;
  --border: #e2e8f0;
}

/* Dark mode โ€” overrides same variables */
[data-theme="dark"] {
  --bg: #0b0f1a;
  --surface: #111827;
  --text: #e2e8f0;
  --border: #1e2d45;
}

/* All components use variables โ€” theme switch is instant */
.card {
  background: var(--surface);   /* automatically changes with theme */
  color: var(--text);
  border: 1px solid var(--border);
}

nginx Configuration for React SPA

React Router handles client-side navigation โ€” when a user goes to /alerts or /monitoring, the browser requests that URL from the server. Without special nginx config, the server would return 404 because those files don't exist on disk. The fix is to make nginx always return index.html for any URL โ€” React Router then reads the URL and shows the correct page.

nginxnginx.conf โ€” SPA routing fix
server {
    listen 80;
    root /usr/share/nginx/html;
    index index.html;

    # Serve static files (JS, CSS, images) directly
    location ~* \.(js|css|png|jpg|svg|ico)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # All other URLs โ†’ return index.html so React Router handles them
    location / {
        try_files $uri $uri/ /index.html;
    }
}

k3s Deployment

The frontend is built with npm run build which produces a static dist/ folder of HTML, CSS, and JavaScript. This is copied into an nginx Docker image and deployed as a k3s Deployment with 2 replicas and pod anti-affinity โ€” same pattern as the backend.

dockerfileDockerfile โ€” build and serve React app
# Stage 1: Build React app
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Stage 2: Serve with nginx
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
bashCheck frontend is running in k3s
# Both frontend pods should show Running on different nodes
kubectl get pods -A -o wide | grep frontend

# Check service and NodePort
kubectl get svc -A | grep frontend

# View frontend logs
kubectl logs -l app=edgeguard-frontend --all-containers

โœ… Zero-downtime deployment

When updating the frontend (new build), k3s performs a rolling update โ€” it starts a new pod with the updated image, waits for it to become healthy, then terminates the old pod. With 2 replicas, one is always serving traffic during the update. Users never see a downtime or blank page during deployments.

Key Findings

โœ… 7 fully functional pages โ€” all connected to live backend data

Every page fetches real data from the Flask API with JWT authentication. No mock data or hardcoded values โ€” what you see in the demo is the actual live system state.

โœ… Benchmark Results page replicates Prof. Baun's chart format

Runtime bars (top), speedup bars (bottom), values on bars, color-coded by efficiency, ideal line overlay โ€” exactly matching the format from lecture slides. The MPI/Non-MPI toggle and problem size selector make it easy to navigate all 4 benchmarks interactively during the presentation.

โš ๏ธ Backend URL is hardcoded to cluster IP

The React app has the backend URL set to http://10.10.10.1:30800. This only works when the browser is on the same network as the cluster. Outside the cluster LAN (e.g. from a phone on mobile data), the app loads but API calls fail. For the presentation, the laptop must be connected to the cluster hotspot.

๐Ÿ” SPA vs Multi-Page Application

A traditional web app would reload the full page on every navigation โ€” fetching HTML from the server each time. A React SPA loads once and handles all navigation client-side. This gives instant page transitions (no network round-trip), persistent state across pages (JWT token, theme choice), and a native app-like feel. The tradeoff is initial load time โ€” the entire React bundle downloads on first visit.