The dashboard is the front door of the admin console. It is the first thing a developer sees after logging in. If it shows stale data or placeholder values, trust in the entire console evaporates. If it shows real, live metrics about the running application, the developer immediately understands what the console is and what it can do.
Session 259 built the FLIN console dashboard in roughly two hours. Stats cards showing entity counts, route counts, and total records. System health gauges for database size and memory usage. A real-time activity feed showing the last 20 database operations. Quick action links to jump to the most-used console pages. All powered by three API endpoints returning live data from the FLIN runtime.
The Stats Cards
The dashboard opens with a grid of four stats cards. Each card shows a single metric pulled from the /_flin/api/stats endpoint:
// What the stats API conceptually returns
route GET "/_flin/api/stats" {
guard admin_sessionstats = { app_name: flin.config.name, mode: flin.config.mode, // "dev" or "prod" version: "v1.0.0-alpha.2", entities_count: flin.entities().len, routes_count: flin.routes().len, records_count: flin.total_records(), database_size: flin.db_size() }
respond json(stats) } ```
The Rust implementation reads real data from the runtime. Entity count comes from the EntityRegistry. Route count comes from the compiled route table. Record count calls total_record_count() on ZeroCore, which sums across all collections. Database size scans the .flindb/ directory on disk.
pub fn get_stats(db: &ZeroCore, registry: &EntityRegistry) -> Response {
let entities_count = db.collection_names().len();
let routes_count = get_route_count();
let records_count = db.total_record_count();
let db_size = calculate_db_size();let stats = json!({ "app_name": get_app_name(), "mode": if cfg!(debug_assertions) { "dev" } else { "prod" }, "version": env!("CARGO_PKG_VERSION"), "entities_count": entities_count, "routes_count": routes_count, "records_count": records_count, "database_size": format_bytes(db_size), });
json_response(200, &stats) } ```
The first card (Entities) gets a gradient background -- indigo to purple -- making it visually prominent. This is deliberate. Entities are the core abstraction in FLIN, and the entity count is the metric most developers check first: "Did the runtime discover all my entity definitions?"
System Health Gauges
Below the stats cards sit two health gauges: database health and memory usage. These are rendered as animated SVG arcs that fill from 0% to 100%, with color transitions from green (healthy) to amber (warning) to red (critical).
The data comes from the /_flin/api/health endpoint:
pub fn get_health(db: &ZeroCore) -> Response {
let memory = get_memory_stats();
let db_size = calculate_db_size();
let uptime = SERVER_START_TIME.elapsed();let health = json!({ "memory": { "used_bytes": memory.used, "total_bytes": memory.total, "percentage": (memory.used as f64 / memory.total as f64 * 100.0) as u32, }, "database": { "size_bytes": db_size, "size_formatted": format_bytes(db_size), }, "uptime_seconds": uptime.as_secs(), "status": if memory.used as f64 / memory.total as f64 > 0.9 { "warning" } else { "healthy" }, });
json_response(200, &health) } ```
The get_memory_stats() function uses platform-specific system calls. On Linux, it reads from /proc/meminfo. On macOS, it uses sysctl. The result is a real percentage reflecting the process's memory footprint relative to available system memory.
The gauges are not decorative. A developer running a FLIN app on a 512 MB VPS can glance at the dashboard and see "Memory: 73%" -- immediately understanding whether there is headroom for more traffic or whether it is time to scale up.
The Activity Feed
The right side of the dashboard displays a live activity feed: the last 20 database operations performed by the application. Every entity save, update, and delete is captured and displayed with a timestamp, operation type, entity name, and record ID.
// What the activity API conceptually returns
route GET "/_flin/api/activity" {
guard admin_sessionrecent = flin.recent_operations(20)
activities = recent.map(op => { type: op.type, // "create", "update", "delete" entity: op.entity_type, // "User", "Product", "Order" record_id: op.id, timestamp: op.timestamp, fields_changed: op.changed_fields })
respond json(activities) } ```
The activity feed serves a specific debugging purpose. When a developer creates a record through their application's UI and wants to verify it reached the database, the dashboard shows the operation in real time. No need to open a database client. No need to write a query. The activity feed is a live confirmation that the system is working.
Design Decisions
Why Pure HTML/CSS/JS?
Session 259 made the deliberate choice to build the console frontend in vanilla HTML, CSS, and JavaScript rather than compiling FLIN templates. Three reasons drove this decision:
1. No circular dependency. If the console were written in FLIN, a bug in the FLIN compiler could make the console inaccessible -- exactly when you need it most.
2. No build step. The console works the moment the FLIN binary compiles. There is no npm install, no vite build, no asset pipeline.
3. Precedent. The approach follows the existing /_flin/grants/* implementation, which already served static HTML from the binary.
The 0fee.dev Design System
The console adopted the design language from 0fee.dev, another ZeroSuite product. Slate-900 sidebar (#0f172a), indigo primary color (#6366f1), rounded-xl cards with subtle borders, system fonts with monospace for code. This was not accidental -- it meant the design system was already battle-tested on a production application, saving hours of design iteration.
// The design tokens used throughout the console
theme = {
sidebar_bg: "#0f172a", // Slate-900
primary: "#6366f1", // Indigo-500
card_radius: "0.75rem", // Rounded-xl
card_border: "1px solid rgba(0,0,0,0.08)",
font_mono: "ui-monospace, 'Cascadia Code', monospace",
method_colors: {
GET: "#22c55e", // Green-500
POST: "#3b82f6", // Blue-500
PUT: "#f59e0b", // Amber-500
DELETE: "#ef4444" // Red-500
}
}Responsive Design
The console is fully responsive. On mobile (below 768px), the sidebar collapses behind a hamburger menu with an overlay. The stats grid shifts from four columns to two, then to one. The activity feed stacks below the health gauges instead of sitting beside them.
This matters because developers in Abidjan, Dakar, and Nairobi sometimes check their applications from their phones. A console that only works on desktop is a console that fails at the worst possible time -- when you are away from your workstation and something breaks.
The Sidebar: Collapsible, Persistent, Contextual
The sidebar deserved its own engineering effort. It supports three states: expanded (full labels), collapsed (icons only with tooltips), and hidden (mobile overlay). The collapsed/expanded state persists in localStorage, so it survives page refreshes.
// Sidebar sections with collapsible groups
sidebar = {
sections: [
{
label: "Main",
items: [
{ icon: "home", label: "Dashboard", path: "/_flin" },
{ icon: "database", label: "Entities", path: "/_flin/entities" },
{ icon: "diagram", label: "Schema", path: "/_flin/schema" },
{ icon: "terminal", label: "Query Editor", path: "/_flin/query" }
]
},
{
label: "Monitoring",
collapsible: true,
items: [
{ icon: "scroll", label: "Logs", path: "/_flin/logs" },
{ icon: "gauge", label: "Metrics", path: "/_flin/metrics" },
{ icon: "chart", label: "Analytics", path: "/_flin/analytics" }
]
}
]
}The sidebar also includes a theme toggle (dark/light), a language selector (English, French, Spanish), and a version badge in the footer. All preferences persist across sessions via localStorage.
Dark Mode: Not an Afterthought
The entire console supports dark mode, toggled from the header. Every color, every border, every shadow has a dark-mode variant. This was not retrofitted -- the CSS was written with CSS custom properties from the start, making the theme switch a single property change on the root element.
Developers who work late -- which in the Abidjan timezone means collaborating with European clients in the afternoon -- appreciate not being blinded by a white dashboard at midnight.
What the Dashboard Replaced
Before the console existed, debugging a FLIN application meant:
1. Reading the terminal output for route discovery messages.
2. Using curl to test API endpoints.
3. Writing throwaway .flin scripts to query the database.
4. Checking the .flindb/ directory manually for file sizes.
After the console: open /_flin, and everything is visible in one place. The dashboard alone -- just the first page -- replaced four separate debugging workflows.
Session 260 would immediately reveal that this first version had a problem: the sidebar was inconsistent across subpages. But that story deserves its own article.
---
This is Part 137 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO built a real-time admin dashboard into a programming language.
Series Navigation: - [136] Building phpMyAdmin for FLIN - [137] The Admin Console Dashboard (you are here) - [138] Entity Browser and CRUD Operations