← Back to 2026-02-06
pythonfastapiadmin-dashboardweb-componentsalpine-jsragchatbot

Building a Complete Admin Dashboard and Embeddable Chat Widget: A Full-Stack Development Journey

Oli·

Building a Complete Admin Dashboard and Embeddable Chat Widget: A Full-Stack Development Journey

Sometimes you have one of those development sessions where everything just clicks. Today was one of those days. I set out to build a complete admin dashboard and embeddable chat widget for MiniRAG (my lightweight RAG chatbot system), and by the end of the session, I had added 19 files, written 2,661 lines of code, and somehow kept all 31 existing tests passing.

Here's the story of how it all came together.

The Mission: From API to User Interface

MiniRAG started as a pure API—clean, functional, but not exactly user-friendly for non-technical users. The goal was ambitious: build a complete admin interface for managing chatbots and an embeddable widget that anyone could drop into their website with a simple script tag.

Phase 1: Expanding the API Foundation

Before building any UI, I needed to expand the API to support administrative operations. This meant adding several new endpoint groups:

Authentication & User Management

# New endpoints added:
POST /v1/auth/login     # Email + password → JWT
GET  /v1/auth/me        # Get current user info
GET  /v1/users          # List all users (admin only)
POST /v1/users          # Create new user
PATCH /v1/users/{id}    # Update user
DELETE /v1/users/{id}   # Deactivate user

The user management endpoints use a _require_elevated() helper that ensures only owners and admins can perform these operations—a simple but effective authorization layer.

System Monitoring & Analytics

GET /v1/stats/overview  # Summary counts (users, bots, conversations)
GET /v1/stats/usage     # Daily usage by model
GET /v1/system/health   # Check postgres/qdrant/redis connectivity

The stats endpoint was particularly interesting to implement. I needed daily usage aggregation that would work with both PostgreSQL (production) and SQLite (development):

# This SQL works on both databases
cast(UsageEvent.created_at, Date)

Sometimes the simple solutions are the most elegant.

Phase 2: The Admin Dashboard

For the frontend, I made a deliberate choice to keep things simple and dependency-free. No build tools, no complex framework setup—just Alpine.js for reactivity, Tailwind CSS for styling, and vanilla JavaScript for API calls.

The SPA Architecture

The dashboard is a true single-page application with 8 different views:

  • Login & user management
  • Bot profile management
  • Knowledge source management
  • Real-time chat interface
  • Analytics dashboard
  • System health monitoring
  • API documentation
  • Widget configuration

All routing happens client-side using Alpine.js's x-show directive—surprisingly effective for this scale of application.

API Integration Layer

I built a clean API wrapper that handles authentication automatically:

// api.js - Clean abstraction over fetch()
class API {
  constructor() {
    this.baseUrl = '/v1';
    this.token = localStorage.getItem('token');
  }
  
  async request(endpoint, options = {}) {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      ...options,
      headers: {
        'Authorization': `Bearer ${this.token}`,
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
    
    if (response.status === 401) {
      // Auto-redirect on auth failure
      window.location.href = '/dashboard#login';
      return;
    }
    
    return response.json();
  }
}

This approach eliminated repetitive auth handling throughout the application.

Phase 3: The Embeddable Widget

The embeddable widget was the most technically interesting piece. The requirement was simple: users should be able to add a chatbot to any website with just:

<script src="https://your-domain.com/dashboard/widget/minirag-widget.js" 
        data-bot-id="your-bot-id"
        data-title="Ask me anything!">
</script>

Shadow DOM for Isolation

I used Shadow DOM to ensure the widget's styles wouldn't conflict with the host page:

class MiniRAGWidget extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>${widgetStyles}</style>
      <div class="minirag-widget">
        <!-- Widget HTML -->
      </div>
    `;
  }
}

customElements.define('minirag-widget', MiniRAGWidget);

The widget automatically inserts itself into the page, reads configuration from the script tag's data attributes, and handles the entire chat experience independently.

User Experience Details

I added several UX enhancements that make the widget feel polished:

  • Typing indicators during AI response generation
  • Source citations that expand to show context
  • Smooth animations for message appearance
  • Responsive design that works on mobile
  • CSS custom properties for easy theming

Phase 4: Documentation and Developer Experience

No feature is complete without documentation. I created three comprehensive guides:

  1. Installation Guide - Getting MiniRAG running locally or in production
  2. Admin Guide - Managing users, bots, and content through the dashboard
  3. Widget Integration Guide - Embedding the chat widget with customization options

The README got a major update too, expanding from a simple API reference to a complete project overview with screenshots and usage examples.

Lessons Learned

The Power of Simplicity

Using Alpine.js instead of React or Vue meant zero build time and immediate browser refresh during development. For an admin dashboard that doesn't need complex state management, this approach was incredibly productive.

CSS-in-JS vs. Shadow DOM

The widget's Shadow DOM approach for style isolation worked flawlessly, but required careful planning of the CSS architecture. Using CSS custom properties for theming gave users flexibility without exposing internal styling complexity.

Testing Strategy

One of my biggest concerns was breaking existing functionality. The fact that all 31 existing tests passed immediately after this massive addition validated the modular architecture decisions made early in the project.

The Results

By the end of the session, MiniRAG had transformed from a developer-only API into a complete platform:

  • 29 API endpoints covering everything from authentication to analytics
  • Complete admin dashboard for non-technical users
  • Embeddable widget that works on any website
  • Comprehensive documentation for developers and users
  • Zero regressions in existing functionality

The entire codebase went from a backend API to a full-stack application in a single development session—a testament to the power of thoughtful architecture and keeping things simple.

What's Next?

The immediate roadmap focuses on polish and production readiness:

  • Comprehensive test coverage for all new endpoints
  • End-to-end testing of the complete user journey
  • Production security hardening (especially CORS configuration)
  • Performance optimization for the analytics dashboard

Sometimes the best development sessions are the ones where you build exactly what you envisioned, everything works on the first try, and you end up with something that feels much larger than the sum of its parts.


Want to see the code? MiniRAG is open source and available on GitHub. The complete implementation from this session is available in commit d44f65b.