DEV Community

linou518
linou518

Posted on

Building a Minimal Admin Dashboard with SPA Flask — Refactoring 7,000 Lines Down to 500

Building a Minimal Admin Dashboard with SPA × Flask — Refactoring 7,000 Lines Down to 500

A while back, I had the opportunity to create a "bundled with miniPC" version from our existing in-house dashboard. The requirements were simple — just project task management and local node status monitoring. Timelines, family calendars, agent management... none of that was needed.

In the end, the backend went from 7,000+ lines and frontend from 3,800+ lines down to roughly 1,000 lines backend and 2,000 lines frontend. Here's what I learned through this process.


Why "Cutting" is Hard

Adding code is easy. "This feature would be handy" or "let's handle that edge case" — it just keeps accumulating. Cutting is hard. You don't know how far you can cut, and you're afraid of breaking things.

This time, the constraints were clear: "task management and node management only". That constraint let me cut without hesitation.

What I removed:

  • Timeline (calendar integration, time-axis UI)
  • Family anniversaries and birthdays feature
  • Claude API usage dashboard
  • Multi-node management (remote node list, bot control per node)
  • Agent task scheduled execution view

What I kept:

  • Project and task CRUD
  • OpenClaw status check and bot management for this machine only

"This machine only" was the key. The original dashboard had a multi-node design monitoring multiple remote nodes, but the miniPC bundled version only manages that specific miniPC. When the design premise changes, code complexity drops dramatically.


Flask × Single-File SPA Architecture

The tech stack didn't change. Flask + vanilla JS SPA (single index.html file).

03_dashboard-lite/
├── server.py       # Flask backend (~1,000 lines)
├── index.html      # Frontend SPA (~2,000 lines)
├── install.sh      # systemd one-shot setup
└── requirements.txt  # flask only
Enter fullscreen mode Exit fullscreen mode

The only dependency is Flask. No Node.js required, no build step. It runs with pip3 install flask && python3 server.py. This was an important choice for a shipping product.

server.py Design Philosophy

# Auto-detect OC_PATH from environment variables
OC_PATH = os.environ.get('OC_PATH') or _detect_oc_path()

def _detect_oc_path():
    candidates = [
        Path.home() / '.openclaw',
        Path('/etc/openclaw'),
    ]
    for p in candidates:
        if (p / 'config.json').exists():
            return str(p)
    return str(Path.home() / '.openclaw')
Enter fullscreen mode Exit fullscreen mode

Auto-detecting config paths so it works even if the installation directory structure differs. Can also be overridden with environment variables. This kind of "designed to run on someone else's machine" thinking is something you rarely consider when building internal tools.

Simplifying the Node Management API

The multi-node version's API received "which node" as a parameter and fetched data via SSH internally.

The Lite version is "this machine only" so that disappears:

@app.route('/api/node/status')
def node_status():
    # Just read local files
    config_path = Path(OC_PATH) / 'config.json'
    ...
Enter fullscreen mode Exit fullscreen mode

The SSH session management, timeout handling, and error handling layers simply vanish. Not only does code decrease, but failure points decrease too.


Frontend Overhaul: Removing Brand Identity

The original dashboard used our in-house brand colors (green-based). Since the miniPC bundled version ships as a "generic product," we needed to strip the TechsFree identity.

New theme: dark UI + purple accent. The reason is simple — "a code editor aesthetic feels right for a tech product."

Managing all colors with CSS variables:

:root {
    --bg-primary: #0f0f1a;
    --bg-secondary: #1a1a2e;
    --bg-card: #16213e;
    --accent: #7c3aed;
    --accent-hover: #6d28d9;
    --text-primary: #e2e8f0;
    --text-secondary: #94a3b8;
}
Enter fullscreen mode Exit fullscreen mode

This alone changes the entire UI color scheme. Adding theme support as an afterthought becomes a nightmare, but if you design with CSS variables from the start, swapping it out later is easy.


install.sh: Zero to Running

The "install experience" is critical for a shipping product.

#!/bin/bash
PORT=${1:-8099}

# Install Python dependencies
pip3 install -r requirements.txt -q

# Generate systemd unit file
cat > ~/.config/systemd/user/openclaw-dashboard.service << EOF
[Unit]
Description=OpenClaw Dashboard Lite
[Service]
ExecStart=$(which python3) $(pwd)/server.py
Environment=LITE_PORT=${PORT}
Restart=always
[Install]
WantedBy=default.target
EOF

# Enable and start
systemctl --user enable openclaw-dashboard.service
systemctl --user start openclaw-dashboard.service
echo "Dashboard Lite started on port ${PORT}"
Enter fullscreen mode Exit fullscreen mode

bash install.sh completes everything including startup as a systemd service. The port number can be changed via argument, making conflicts easy to avoid.


What I Learned

Constraints are your friend. When you have a "this is all we need" constraint, code naturally gets cut. Without constraints, trying to build a "general-purpose tool" leads to endless bloat.

Single-File is surprisingly viable. Fitting an SPA into a single index.html without a bundler looks unmaintainable, but at ~2,000 lines it's perfectly manageable. The absence of import statements actually makes behavior easier to trace.

Write it to run on someone else's machine. Hardcoded paths, environment-specific assumptions — internal tools tend to be full of these. Building a shipping product exposes all those shortcuts.


Summary

  • Before: Backend 7,000 lines + Frontend 3,800 lines (multi-feature, multi-node)
  • After: Backend 1,000 lines + Frontend 2,000 lines (tasks + local node only)
  • Stack: Flask + vanilla JS SPA (dependency: flask only)
  • Install: Single command bash install.sh [port]

I was reminded once again: cutting code is engineering in itself.


Tags: webdev, flask, spa, refactoring, python, tools, frontend, minipc

Top comments (0)