Back to flin
flin

The Admin Console From Scratch

Building a complete admin console from scratch in the final sessions.

Thales & Claude | March 25, 2026 8 min flin
flinadmin-consoledashboardentity-browsersessions

Every database needs an admin interface. MySQL has phpMyAdmin. PostgreSQL has pgAdmin. MongoDB has Compass. Django ships with its own admin panel. Rails has ActiveAdmin. These tools exist because developers need to inspect their data, debug their queries, and manage their schemas without writing code.

FLIN's admin console lives at /_flin. Navigate to it in any FLIN application running in dev mode, and you get a corporate-grade dashboard with route inspection, entity browsing, schema management, real-time stats, theme toggling, and internationalization support. It is embedded in the FLIN binary -- no separate installation, no configuration, no additional dependencies. It exists because the binary includes it via include_str!().

The console was built across Sessions 259-263 and 300-301, spanning late January through February 11, 2026. This is the story of building a complete admin tool from zero lines of code to a production-ready interface.

Session 259: The Foundation

Session 259 established the console architecture in approximately two hours. The core decision was to build the admin interface using pure HTML, CSS, and JavaScript -- not FLIN's own component system. This was deliberate:

1. The console has no build step. It loads instantly without parsing or compilation. 2. It ships embedded in the binary. Every FLIN installation includes it via Rust's include_str!() macro. 3. It follows the pattern already established by /_flin/grants/* for download grant management. 4. It works even if the FLIN compiler has bugs -- the admin console is independent of the application being developed.

The backend consisted of two new Rust files:

// src/server/console/mod.rs -- Route dispatcher
const INDEX_HTML: &str = include_str!("../../../resources/embedded/console/index.html");
const CONSOLE_CSS: &str = include_str!("../../../resources/embedded/console/console.css");
const CONSOLE_JS: &str = include_str!("../../../resources/embedded/console/console.js");

pub async fn handle_console_request( request: &Request, stream: &mut TcpStream, registry: &Registry, ) -> Result<(), ServerError> { let path = request.path(); match path { "/_flin" | "/_flin/" => serve_html(INDEX_HTML, stream), "/_flin/console.css" => serve_css(CONSOLE_CSS, stream), "/_flin/console.js" => serve_js(CONSOLE_JS, stream), "/_flin/api/stats" => serve_stats(registry, stream), "/_flin/api/routes" => serve_routes(registry, stream), "/_flin/api/entities" => serve_entities(registry, stream), _ => serve_404(stream), } } ```

The route interception was clean -- a single check in the HTTP handler:

// src/server/http.rs (line ~1191)
if path.starts_with("/_flin") && !path.starts_with("/_flin/grants/") {
    return super::console::handle_console_request(&request, stream, &registry).await;
}

The frontend was a single-page application with a professional design system inspired by modern admin dashboards: a dark slate sidebar (#0f172a), an indigo primary color (#6366f1), rounded cards with subtle shadows, and responsive breakpoints.

Session 259 delivered: - A collapsible sidebar with navigation items - A sticky header with logo, search trigger, dev mode badge, theme toggle, and language selector - A stats dashboard with entity count, route count, record count, and mode indicator - Quick action cards linking to entities, routes, documentation, and query editor - Route listing with colored method badges (GET = green, POST = blue, PUT = amber, DELETE = red) - Theme persistence in localStorage - Language persistence in localStorage (English, French, Spanish) - Mobile-responsive layout with hamburger menu

Sessions 260-263: Entity Browser and CRUD

Session 260 fixed sidebar collapse behavior -- a visual bug where tooltips did not appear correctly in the collapsed state. Sessions 261-263 built the entity browser, which transformed the console from a read-only dashboard into a full data management tool.

Session 261 added the MVP login flow (for production mode), entity listing with schema display, and basic query execution. Session 262 built the entity browser with full CRUD operations:

Entity Browser Features (Session 262):
- Entity selector dropdown with all registered entities
- Schema display showing field names, types, and constraints
- Records table with pagination
- Create record modal with type-aware form fields
- Edit record modal pre-filled with current values
- Delete confirmation dialog
- Real-time record count

Session 263 added enhancements: search within entity records, bulk operations, export functionality, and improved error handling.

The entity browser follows a pattern familiar to anyone who has used phpMyAdmin: select a table (entity), see its structure, browse its records, create/edit/delete rows. The difference is that FLIN's entity browser is aware of the entity's type system. A bool field renders as a toggle switch. A time field renders as a date picker. A file field shows a file upload widget.

Sessions 300-301: The Final Polish

After a gap of several weeks (during which other features were being built), Sessions 300-301 returned to the admin console for final polish.

Session 300 enhanced the console UI/UX with improved navigation, better error states, loading indicators, and keyboard shortcuts. Session 301 -- the final session of the entire FLIN development -- tackled three bugs and added a major feature.

The three bugs were practical pain points discovered during real usage:

/* Bug 1: Horizontal scroll for wide tables (Session 301) */
#records-container {
    overflow-x: auto;
}

/ Bug 2: Sticky Actions column / .records-table th:last-child, .records-table td:last-child { position: sticky; right: 0; background: var(--color-bg-primary); z-index: 2; box-shadow: -2px 0 4px rgba(0,0,0,0.05); } ```

The first bug: entities with many fields (like a Person entity with 16 fields) pushed the table off-screen, hiding the Edit and Delete buttons. The fix was overflow-x: auto. The second bug: even with horizontal scroll, the Actions column scrolled away. The fix was position: sticky on the last column. The third bug: boolean checkboxes in edit modals were using value="true" instead of the checked attribute, so all booleans appeared unchecked regardless of their actual value.

The major feature was entity definition management -- full CRUD for entity schemas, not just records:

// Entity definitions can now be created from the console GUI
// The console generates valid .flin files:

entity Product { title: text @required price: float = 0.0 inStock: bool = true } ```

Three new API endpoints were added:

POST   /_flin/api/entities          Create entity definition
PUT    /_flin/api/entities/:name    Update entity schema
DELETE /_flin/api/entities/:name    Delete entity definition

Each endpoint includes thorough validation: entity names must be PascalCase, field names must be valid identifiers, field types must be valid FLIN types, reserved names (id, created_at, updated_at, deleted_at, version) are rejected, and duplicate field names are prevented.

The entity definition workflow is intuitive: click "New Entity," enter a name, add fields with types and constraints, click "Create Entity." The console writes a .flin file to the entities directory, and the file watcher automatically detects the change and reloads the entity registry. The developer never touches a terminal.

Architecture: Embedded Assets

The console's architecture demonstrates a pattern that is central to FLIN's philosophy: everything ships in the binary.

// Every console file is compiled into the FLIN binary
const INDEX_HTML: &str = include_str!("../../../resources/embedded/console/index.html");
const CONSOLE_CSS: &str = include_str!("../../../resources/embedded/console/console.css");
const CONSOLE_JS: &str = include_str!("../../../resources/embedded/console/console.js");
const TRANSLATIONS_JS: &str = include_str!("../../../resources/embedded/console/translations.js");
const ENTITIES_HTML: &str = include_str!("../../../resources/embedded/console/content/entities.html");
const SCHEMA_HTML: &str = include_str!("../../../resources/embedded/console/content/schema.html");

Rust's include_str!() macro reads a file at compile time and embeds its contents as a string literal in the binary. The console HTML, CSS, and JavaScript are compiled into the FLIN executable. There are no external files to serve, no CDN to configure, no asset pipeline to maintain. When you download the FLIN binary, the admin console is already inside it.

This approach has trade-offs. The console cannot be customized without recompiling FLIN. Updates to the console require a new FLIN release. But for a developer tool that ships with a programming language, these trade-offs are acceptable. The console is not a user-facing application -- it is a development tool. Consistency and reliability matter more than customizability.

The Design System

The console's visual design follows a professional corporate aesthetic:

/* CSS variables for theming (Session 259) */
:root {
    --color-bg: #ffffff;
    --color-bg-secondary: #f8fafc;
    --sidebar-bg: #0f172a;
    --color-primary: #6366f1;
    --color-primary-hover: #4f46e5;
    --color-text: #0f172a;
    --color-text-muted: #64748b;
    --color-border: #e2e8f0;
    --radius-md: 0.5rem;
    --radius-lg: 0.75rem;
    --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
    --shadow-md: 0 4px 6px rgba(0,0,0,0.07);
}

[data-theme="dark"] { --color-bg: #0f172a; --color-bg-secondary: #1e293b; --color-text: #f1f5f9; --color-text-muted: #94a3b8; --color-border: #334155; } ```

Dark mode, light mode, and three languages (English, French, Spanish) -- all toggleable from the header. The language switching is handled by a translations file that maps keys to strings in each language, enabling the console to serve FLIN's multilingual audience.

What It Means to Ship a Built-In Admin Console

Most web frameworks treat admin interfaces as optional add-ons. You install a package, configure it, register your models, and customize the templates. The admin is an afterthought -- a tool you set up after the "real" work is done.

FLIN treats the admin console as infrastructure. It exists because FLIN applications have entities, and developers need to see their entities. It requires no setup because there is nothing to configure. It works in development mode automatically. In production mode, it requires authentication.

The admin console is the last feature built during FLIN's 42-day development. It is fitting that the final session of the entire project was spent fixing bugs in the entity browser and adding entity definition CRUD. The console is where developers interact with their data. Making it work correctly was the right way to close the book on FLIN's initial development.

Session 301. February 11, 2026. Entity management bug fixes. The work is done.

---

This is Part 202 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO built a programming language from scratch.

Series Navigation: - [201] The File Storage Marathon: 30 Sessions - [202] The Admin Console From Scratch (you are here) - [203] 9 Agents Running in Parallel: The i18n Sprint

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles