Skip to main content

Container Session UI Requirements Specification

Task: B.4.5.1 API Reference: ADR-055 Licensed Docker Container Schema Design Target: Frontend Dashboard for Multi-Tenant Container Session Management


Executive Summary

This specification defines the complete frontend requirements for the Container Session Management Dashboard, enabling administrators, managers, and users to monitor and manage containerized CODITECT instances across Docker, Cloud Workstations, and Kubernetes deployments.

Key Capabilities

  1. Multi-Tenant Visibility - Hierarchical access control (System Admin → Tenant Admin → Team Manager → User)
  2. Real-time Monitoring - Live session status, heartbeat tracking, user counts
  3. Container Lifecycle Management - View active/released/expired sessions with lifecycle events
  4. User Session Tracking - Per-container user sessions with join/leave events
  5. License Utilization - Visual representation of seat consumption across containers

Technology Stack

  • Framework: React 18 with TypeScript
  • State Management: React Context + Zustand for global state
  • UI Components: Chakra UI (consistent with existing CODITECT design system)
  • Data Fetching: React Query for caching and real-time updates
  • Charts: Recharts for utilization visualization
  • Real-time: Polling (5-second intervals) with optional WebSocket upgrade path

Table of Contents

  1. User Roles & Access Levels
  2. Component Hierarchy
  3. Data Flow Architecture
  4. Component Specifications
  5. State Management Design
  6. API Integration Patterns
  7. Real-time Update Strategy
  8. Error Handling Matrix
  9. Acceptance Criteria
  10. Implementation Roadmap

1. User Roles & Access Levels

Role Hierarchy

System Admin (AZ1.AI)
├── Platform-wide visibility
├── All tenants, all containers
└── Advanced diagnostics and metrics

Tenant Admin (Customer Organization)
├── Tenant-level visibility
├── All containers within tenant
└── License utilization reports

Team Manager (Customer Team)
├── Team-level visibility
├── Team's containers only
└── Team seat allocation

User (Individual Developer)
├── User-level visibility
├── Their own sessions across containers
└── Personal usage history

User Stories

System Admin (AZ1.AI)

US-SA-01: As a system admin, I want to see all active container sessions across all tenants so I can monitor platform health.

US-SA-02: As a system admin, I want to filter by tenant, container type, or status so I can diagnose issues quickly.

US-SA-03: As a system admin, I want to terminate zombie sessions so I can reclaim licenses.

US-SA-04: As a system admin, I want to see historical session trends so I can plan infrastructure scaling.

Tenant Admin

US-TA-01: As a tenant admin, I want to see all containers licensed to my organization so I can track resource usage.

US-TA-02: As a tenant admin, I want to see license utilization (active containers vs total seats) so I can optimize spending.

US-TA-03: As a tenant admin, I want to see which teams are using containers so I can allocate licenses fairly.

US-TA-04: As a tenant admin, I want to receive alerts when container limits are reached so I can request more licenses.

Team Manager

US-TM-01: As a team manager, I want to see my team's active containers so I can monitor team usage.

US-TM-02: As a team manager, I want to see which users are in each container so I can coordinate work.

US-TM-03: As a team manager, I want to know when containers are idle (no users) so I can reclaim seats.

User

US-U-01: As a user, I want to see all my active container sessions so I know where I'm logged in.

US-U-02: As a user, I want to leave a container session remotely so I can free up a seat if I forgot to log out.

US-U-03: As a user, I want to see my session history so I can track my usage patterns.


2. Component Hierarchy

Component Tree (TypeScript)

// Root Dashboard
ContainerSessionDashboard
├── DashboardHeader
│ ├── BreadcrumbNav
│ ├── RoleSelector (System Admin only)
│ └── RefreshControl

├── SessionFilters
│ ├── TenantFilter (System Admin, Tenant Admin)
│ ├── ContainerTypeFilter
│ ├── StatusFilter
│ └── SearchBar

├── SessionMetrics
│ ├── ActiveSessionsCard
│ ├── LicenseUtilizationCard
│ └── HeartbeatHealthCard

├── ContainerSessionList
│ └── ContainerSessionCard[] (grid/list view)
│ ├── SessionStatusBadge
│ ├── ContainerTypeIcon
│ ├── UserCountIndicator
│ ├── HeartbeatTimer
│ └── ActionMenu

└── SessionDetailDrawer (drawer/modal)
├── SessionOverview
├── UserSessionList
│ └── UserSessionCard[]
│ ├── UserAvatar
│ ├── ActivityStatus
│ └── JoinLeaveTimestamps
├── HeartbeatTimeline
├── SessionMetadata
└── ActionButtons

3. Data Flow Architecture

Data Flow Layers

  1. Presentation Layer - React components render UI based on state
  2. State Management Layer - React Context (auth/tenant) + Zustand (UI state)
  3. Data Fetching Layer - React Query (caching, refetching, mutations)
  4. API Client Layer - Axios with interceptors for auth, error handling
  5. Backend API Layer - Django REST Framework with tenant isolation

4. Component Specifications

4.1 ContainerSessionDashboard (Root)

Purpose: Root component orchestrating the entire dashboard.

TypeScript Interface:

interface ContainerSessionDashboardProps {
// No props - reads from AuthContext
}

interface DashboardState {
view: 'grid' | 'list';
selectedSession: ContainerSession | null;
isDetailDrawerOpen: boolean;
}

Responsibilities:

  • Initialize React Query H.P.005-HOOKS
  • Manage global dashboard state (view mode, selected session)
  • Handle role-based conditional rendering
  • Coordinate real-time updates

Hooks Used:

const { user, role, tenant } = useAuth();
const { data: sessions, isLoading, error } = useContainerSessions(filters);
const [view, setView] = useState<'grid' | 'list'>('grid');
const [selectedSession, setSelectedSession] = useState<ContainerSession | null>(null);

Layout:

<Box>
<DashboardHeader role={role} onRefresh={refetch} />
<SessionFilters role={role} onFilterChange={setFilters} />
<SessionMetrics sessions={sessions} />
<ContainerSessionList
sessions={sessions}
view={view}
onSelectSession={setSelectedSession}
/>
<SessionDetailDrawer
session={selectedSession}
isOpen={!!selectedSession}
onClose={() => setSelectedSession(null)}
/>
</Box>

4.2 SessionFilters

Purpose: Filter container sessions by tenant, type, status, or search.

TypeScript Interface:

interface SessionFiltersProps {
role: UserRole;
initialFilters?: SessionFilters;
onFilterChange: (filters: SessionFilters) => void;
}

interface SessionFilters {
tenantId?: string; // System Admin only
containerType?: ContainerType[];
status?: SessionStatus[];
search?: string; // Search by container_id, container_name, hostname
dateRange?: { start: Date; end: Date };
}

enum ContainerType {
DOCKER = 'docker',
WORKSTATION = 'workstation',
KUBERNETES = 'kubernetes',
}

enum SessionStatus {
ACTIVE = 'active',
RELEASED = 'released',
EXPIRED = 'expired',
TERMINATED = 'terminated',
}

UI Elements:

  • TenantFilter (Dropdown, System Admin only) - Select tenant to view
  • ContainerTypeFilter (Checkbox group) - Filter by Docker, Workstation, K8s
  • StatusFilter (Checkbox group) - Active, Released, Expired, Terminated
  • SearchBar (Text input with debounce) - Search container ID, name, hostname
  • DateRangePicker (Optional) - Filter by started_at date range

Behavior:

  • Filters apply immediately (no "Apply" button)
  • Debounce search input (300ms)
  • Persist filters to localStorage
  • Show active filter count badge
  • "Clear All" button to reset

4.3 SessionMetrics

Purpose: Display high-level KPIs for container sessions.

TypeScript Interface:

interface SessionMetricsProps {
sessions: ContainerSession[];
license?: License; // For utilization calculation
}

interface MetricCardData {
title: string;
value: number | string;
trend?: 'up' | 'down' | 'neutral';
trendValue?: string;
status?: 'success' | 'warning' | 'error';
icon?: React.ReactNode;
}

Metric Cards:

  1. Active Sessions Card

    • Value: Count of sessions with status='active'
    • Subtitle: "Currently Running"
    • Trend: +/- change from 1 hour ago
    • Icon: CheckCircle (green)
  2. License Utilization Card

    • Value: ${activeContainers} / ${license.seats_total} (e.g., "8 / 10")
    • Percentage: Visual progress bar
    • Status:
      • Green: <75% utilization
      • Yellow: 75-90% utilization
      • Red: >90% utilization
    • Icon: Shield (color-coded)
  3. Heartbeat Health Card

    • Value: Count of sessions with healthy heartbeat (< 5.5 min since last)
    • Subtitle: "Healthy / Total Active"
    • Warning Indicator: Sessions approaching expiry (>5.5 min, <6 min)
    • Icon: Activity (pulse animation)

Layout:

<SimpleGrid columns={{ base: 1, md: 3 }} spacing={4}>
<MetricCard {...activeSessionsData} />
<MetricCard {...licenseUtilizationData} />
<MetricCard {...heartbeatHealthData} />
</SimpleGrid>

4.4 ContainerSessionCard

Purpose: Display a single container session in grid or list view.

TypeScript Interface:

interface ContainerSessionCardProps {
session: ContainerSession;
view: 'grid' | 'list';
onSelect: (session: ContainerSession) => void;
actions?: SessionAction[];
}

interface ContainerSession {
id: string;
container_id: string;
container_type: 'docker' | 'workstation' | 'kubernetes';
container_name: string;
status: 'active' | 'released' | 'expired' | 'terminated';
max_users: number;
current_user_count: number;
last_heartbeat_at: string; // ISO 8601
started_at: string;
released_at?: string;
expired_at?: string;
hostname?: string;
client_version?: string;
ip_address?: string;
}

interface SessionAction {
label: string;
icon: React.ReactNode;
onClick: (session: ContainerSession) => void;
isDisabled?: boolean;
variant?: 'primary' | 'danger';
}

Visual Elements:

  1. SessionStatusBadge

    • Active: Green badge with pulse animation
    • Released: Gray badge (graceful shutdown)
    • Expired: Orange badge (missed heartbeats)
    • Terminated: Red badge (forced termination)
  2. ContainerTypeIcon

    • Docker: Docker whale icon (blue)
    • Workstation: Cloud icon (purple)
    • Kubernetes: K8s hexagon icon (blue)
  3. UserCountIndicator

    • Format: "👤 2 / 5" (current / max)
    • Color:
      • Green: <75% capacity
      • Yellow: 75-100% capacity
      • Red: At max capacity
  4. HeartbeatTimer

    • Format: "Last heartbeat: 2m 34s ago"
    • Color:
      • Green: <4 min
      • Yellow: 4-5.5 min
      • Red: >5.5 min (approaching expiry)
    • Updates: Every second via local timer
  5. ActionMenu

    • View Details (all roles)
    • Terminate Session (Admin/Manager only, active sessions only)
    • View Users (active sessions with users)

Grid View Layout:

<Card>
<CardHeader>
<Flex justify="space-between">
<HStack>
<ContainerTypeIcon type={session.container_type} />
<Text fontWeight="bold">{session.container_name || session.container_id.slice(0, 12)}</Text>
</HStack>
<SessionStatusBadge status={session.status} />
</Flex>
</CardHeader>
<CardBody>
<VStack align="start" spacing={2}>
<UserCountIndicator current={session.current_user_count} max={session.max_users} />
<HeartbeatTimer lastHeartbeat={session.last_heartbeat_at} />
<Text fontSize="sm" color="gray.500">Started: {formatRelativeTime(session.started_at)}</Text>
</VStack>
</CardBody>
<CardFooter>
<ActionMenu session={session} />
</CardFooter>
</Card>

List View Layout:

<Table>
<Tr onClick={() => onSelect(session)} cursor="pointer">
<Td><ContainerTypeIcon type={session.container_type} /></Td>
<Td>{session.container_name || session.container_id.slice(0, 12)}</Td>
<Td><SessionStatusBadge status={session.status} /></Td>
<Td><UserCountIndicator current={session.current_user_count} max={session.max_users} /></Td>
<Td><HeartbeatTimer lastHeartbeat={session.last_heartbeat_at} /></Td>
<Td>{formatRelativeTime(session.started_at)}</Td>
<Td><ActionMenu session={session} /></Td>
</Tr>
</Table>

4.5 SessionDetailDrawer

Purpose: Show comprehensive details for a selected container session.

TypeScript Interface:

interface SessionDetailDrawerProps {
session: ContainerSession | null;
isOpen: boolean;
onClose: () => void;
}

interface SessionDetail extends ContainerSession {
user_sessions: UserSession[];
heartbeat_history: HeartbeatEvent[];
metadata: Record<string, any>;
}

interface UserSession {
id: string;
user_email?: string;
external_user_id?: string;
is_active: boolean;
started_at: string;
ended_at?: string;
last_activity_at: string;
ip_address?: string;
user_agent?: string;
}

interface HeartbeatEvent {
timestamp: string;
heartbeat_count: number;
metrics?: {
cpu_usage?: number;
memory_usage?: number;
user_count?: number;
};
}

Sections:

  1. SessionOverview

    • Container ID (full, with copy button)
    • Container Name
    • Type (Docker/Workstation/K8s)
    • Status Badge
    • License Key (truncated, Admin only)
    • Started At / Ended At
    • Duration (calculated)
    • Hostname
    • IP Address
    • Client Version
  2. UserSessionList

    • Table of users in this container
    • Columns: User Email, Status, Joined At, Last Activity, Actions
    • Actions:
      • View User Profile (Admin/Manager)
      • Kick User (Admin/Manager, active users only)
    • Empty State: "No users in this container"
  3. HeartbeatTimeline

    • Timeline chart showing heartbeat events
    • X-axis: Time
    • Y-axis: Heartbeat count
    • Annotations: User join/leave events
    • Color: Green (healthy), Yellow (warning), Red (missed)
  4. SessionMetadata

    • Expandable JSON viewer
    • Show metadata from session.metadata field
    • Format: Syntax-highlighted JSON
  5. ActionButtons

    • Terminate Session (Admin/Manager, active only)
      • Confirmation modal
      • Reason field (optional)
      • "Are you sure? This will disconnect all users."
    • Refresh Data (all roles)
    • Export Details (JSON download)

Layout:

<Drawer isOpen={isOpen} onClose={onClose} size="xl">
<DrawerOverlay />
<DrawerContent>
<DrawerHeader>
<HStack>
<ContainerTypeIcon type={session.container_type} />
<Text>Container Session Details</Text>
</HStack>
</DrawerHeader>
<DrawerBody>
<Tabs>
<TabList>
<Tab>Overview</Tab>
<Tab>Users ({session.current_user_count})</Tab>
<Tab>Heartbeats</Tab>
<Tab>Metadata</Tab>
</TabList>
<TabPanels>
<TabPanel><SessionOverview session={session} /></TabPanel>
<TabPanel><UserSessionList users={session.user_sessions} /></TabPanel>
<TabPanel><HeartbeatTimeline events={session.heartbeat_history} /></TabPanel>
<TabPanel><SessionMetadata metadata={session.metadata} /></TabPanel>
</TabPanels>
</Tabs>
</DrawerBody>
<DrawerFooter>
<ActionButtons session={session} onClose={onClose} />
</DrawerFooter>
</DrawerContent>
</Drawer>

5. State Management Design

5.1 React Context (Authentication & Tenant)

AuthContext:

interface AuthContextValue {
user: User | null;
role: UserRole;
tenant: Tenant | null;
isLoading: boolean;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}

enum UserRole {
SYSTEM_ADMIN = 'system_admin',
TENANT_ADMIN = 'tenant_admin',
TEAM_MANAGER = 'team_manager',
USER = 'user',
}

interface User {
id: string;
email: string;
name: string;
role: UserRole;
tenant_id?: string;
team_id?: string;
}

interface Tenant {
id: string;
name: string;
slug: string;
license_seats_total: number;
}

Usage:

const { user, role, tenant } = useAuth();

// Role-based rendering
{role === UserRole.SYSTEM_ADMIN && <TenantFilter />}

// Tenant-scoped queries
const { data: sessions } = useContainerSessions({ tenant_id: tenant?.id });

5.2 Zustand (UI State)

Dashboard Store:

interface DashboardState {
// View preferences
view: 'grid' | 'list';
setView: (view: 'grid' | 'list') => void;

// Filters
filters: SessionFilters;
setFilters: (filters: Partial<SessionFilters>) => void;
clearFilters: () => void;

// Selected session
selectedSessionId: string | null;
setSelectedSession: (id: string | null) => void;

// UI state
isDetailDrawerOpen: boolean;
openDetailDrawer: (sessionId: string) => void;
closeDetailDrawer: () => void;

// Pagination
page: number;
pageSize: number;
setPage: (page: number) => void;

// Sort
sortBy: 'started_at' | 'last_heartbeat_at' | 'current_user_count';
sortOrder: 'asc' | 'desc';
setSorting: (by: string, order: 'asc' | 'desc') => void;
}

const useDashboardStore = create<DashboardState>((set) => ({
view: 'grid',
setView: (view) => set({ view }),

filters: {},
setFilters: (filters) => set((state) => ({
filters: { ...state.filters, ...filters }
})),
clearFilters: () => set({ filters: {} }),

selectedSessionId: null,
setSelectedSession: (id) => set({ selectedSessionId: id }),

isDetailDrawerOpen: false,
openDetailDrawer: (sessionId) => set({
selectedSessionId: sessionId,
isDetailDrawerOpen: true
}),
closeDetailDrawer: () => set({
isDetailDrawerOpen: false,
selectedSessionId: null
}),

page: 1,
pageSize: 20,
setPage: (page) => set({ page }),

sortBy: 'started_at',
sortOrder: 'desc',
setSorting: (sortBy, sortOrder) => set({ sortBy, sortOrder }),
}));

Persistence:

// Persist view preference to localStorage
const useDashboardStore = create<DashboardState>(
persist(
(set) => ({
// ... state
}),
{
name: 'dashboard-store',
partialize: (state) => ({
view: state.view,
filters: state.filters,
sortBy: state.sortBy,
sortOrder: state.sortOrder,
}),
}
)
);

6. API Integration Patterns

6.1 React Query Hooks

useContainerSessions (List):

interface UseContainerSessionsParams {
tenant_id?: string;
container_type?: ContainerType[];
status?: SessionStatus[];
search?: string;
page?: number;
page_size?: number;
sort_by?: string;
sort_order?: 'asc' | 'desc';
}

function useContainerSessions(params: UseContainerSessionsParams) {
return useQuery({
queryKey: ['containerSessions', params],
queryFn: () => apiClient.get('/api/v1/sessions/', { params }),
refetchInterval: 5000, // Poll every 5 seconds
staleTime: 3000, // Consider data stale after 3 seconds
keepPreviousData: true, // For pagination
});
}

useContainerSessionDetail:

function useContainerSessionDetail(sessionId: string | null) {
return useQuery({
queryKey: ['containerSession', sessionId],
queryFn: () => apiClient.get(`/api/v1/sessions/${sessionId}/`),
enabled: !!sessionId, // Only fetch if sessionId exists
refetchInterval: 5000,
});
}

useTerminateSession (Mutation):

function useTerminateSession() {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (sessionId: string) =>
apiClient.post(`/api/v1/sessions/${sessionId}/terminate/`),
onSuccess: () => {
queryClient.invalidateQueries(['containerSessions']);
toast.success('Container session terminated');
},
onError: (error) => {
toast.error(`Failed to terminate: ${error.message}`);
},
});
}

useKickUser (Mutation):

interface KickUserParams {
sessionId: string;
userSessionId: string;
}

function useKickUser() {
const queryClient = useQueryClient();

return useMutation({
mutationFn: ({ sessionId, userSessionId }: KickUserParams) =>
apiClient.post(`/api/v1/sessions/${sessionId}/users/${userSessionId}/kick/`),
onSuccess: (_, variables) => {
queryClient.invalidateQueries(['containerSession', variables.sessionId]);
toast.success('User removed from container');
},
});
}

6.2 API Client Configuration

// src/api/client.ts
import axios from 'axios';
import { getAuthToken } from '@/utils/auth';

const apiClient = axios.create({
baseURL: process.env.REACT_APP_API_URL || 'https://api.coditect.ai',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});

// Request interceptor: Add auth token
apiClient.interceptors.request.use(
(H.P.009-CONFIG) => {
const token = getAuthToken();
if (token) {
H.P.009-CONFIG.headers.Authorization = `Bearer ${token}`;
}
return H.P.009-CONFIG;
},
(error) => Promise.reject(error)
);

// Response interceptor: Handle errors
apiClient.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response?.status === 401) {
// Redirect to login
window.location.href = '/login';
}
return Promise.reject(error);
}
);

export default apiClient;

7. Real-time Update Strategy

7.1 Polling (Initial Implementation)

Strategy: React Query automatic refetching every 5 seconds.

Pros:

  • Simple implementation
  • No additional infrastructure
  • Works with existing REST API
  • Automatic retry on failure

Cons:

  • Higher latency (up to 5 seconds)
  • Unnecessary requests if no changes
  • Scales poorly with many clients

Configuration:

useQuery({
queryKey: ['containerSessions'],
queryFn: fetchSessions,
refetchInterval: 5000,
refetchIntervalInBackground: true, // Continue polling when tab inactive
});

7.2 WebSocket Upgrade Path (Future)

Strategy: Django Channels + WebSocket for real-time push updates.

WebSocket Events:

enum WebSocketEvent {
SESSION_CREATED = 'session.created',
SESSION_UPDATED = 'session.updated',
SESSION_RELEASED = 'session.released',
SESSION_EXPIRED = 'session.expired',
USER_JOINED = 'user.joined',
USER_LEFT = 'user.left',
HEARTBEAT_RECEIVED = 'heartbeat.received',
}

interface WebSocketMessage {
event: WebSocketEvent;
data: ContainerSession | UserSession;
timestamp: string;
}

React Hook (useWebSocket):

function useWebSocket(tenant_id?: string) {
const queryClient = useQueryClient();
const [socket, setSocket] = useState<WebSocket | null>(null);

useEffect(() => {
const ws = new WebSocket(`wss://api.coditect.ai/ws/sessions/${tenant_id}/`);

ws.onmessage = (event) => {
const message: WebSocketMessage = JSON.parse(event.data);

// Update React Query cache based on event
switch (message.event) {
case 'session.created':
case 'session.updated':
queryClient.setQueryData(['containerSessions'], (old: any) => {
// Merge new session data
return updateSessions(old, message.data);
});
break;
case 'session.released':
case 'session.expired':
queryClient.invalidateQueries(['containerSessions']);
break;
case 'heartbeat.received':
// Update heartbeat timer without full refetch
queryClient.setQueryData(['containerSession', message.data.id], message.data);
break;
}
};

setSocket(ws);
return () => ws.close();
}, [tenant_id]);

return socket;
}

Fallback Strategy:

  • If WebSocket connection fails, fall back to polling
  • Show connection status indicator in dashboard header
  • Retry WebSocket connection with exponential backoff

8. Error Handling Matrix

8.1 API Error Codes

HTTP CodeError CodeUI ResponseUser Action
400invalid_requestToast error with validation detailsFix input and retry
401invalid_tokenRedirect to loginRe-authenticate
402container_limit_exceededModal: "License limit reached. Contact admin."Contact admin or upgrade license
402user_limit_exceededToast: "Container at max capacity"Wait or join different container
403license_inactiveModal: "License inactive. Contact support."Contact support
403license_expiredModal: "License expired. Renew license."Renew license
403license_suspendedModal: "License suspended. Contact billing."Resolve billing issue
404session_not_foundToast: "Session not found (may have expired)"Refresh list
410session_expiredToast: "Session has expired"Refresh list
500internal_errorToast: "Server error. Try again later."Retry or contact support

8.2 Error Handling Components

ErrorBoundary:

class SessionErrorBoundary extends React.Component<Props, State> {
state = { hasError: false, error: null };

static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}

componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Session dashboard error:', error, errorInfo);
// Send to error tracking (Sentry)
}

render() {
if (this.state.hasError) {
return (
<Alert status="error">
<AlertIcon />
<AlertTitle>Dashboard Error</AlertTitle>
<AlertDescription>
Failed to load container sessions. Please refresh the page.
</AlertDescription>
</Alert>
);
}
return this.props.children;
}
}

Network Error Handler:

function NetworkErrorAlert({ error, onRetry }: Props) {
if (error?.code === 'NETWORK_ERROR') {
return (
<Alert status="warning">
<AlertIcon />
<AlertTitle>Connection Lost</AlertTitle>
<AlertDescription>
Unable to reach server. Check your internet connection.
<Button ml={4} onClick={onRetry}>Retry</Button>
</AlertDescription>
</Alert>
);
}
return null;
}

Empty State:

function EmptyState({ filters }: Props) {
const hasFilters = Object.keys(filters).length > 0;

return (
<Center h="400px">
<VStack spacing={4}>
<Icon as={SearchIcon} boxSize={12} color="gray.400" />
<Text fontSize="lg" color="gray.500">
{hasFilters
? 'No container sessions match your filters'
: 'No container sessions found'
}
</Text>
{hasFilters && (
<Button onClick={clearFilters}>Clear Filters</Button>
)}
</VStack>
</Center>
);
}

9. Acceptance Criteria

9.1 Functional Requirements

FR-01: System Admin can view all container sessions across all tenants.

  • Test: Login as System Admin → See sessions from multiple tenants
  • Pass: Sessions from 3+ tenants visible

FR-02: Tenant Admin can only see their tenant's container sessions.

  • Test: Login as Tenant A admin → Verify Tenant B sessions not visible
  • Pass: Only Tenant A sessions shown

FR-03: Real-time updates reflect session changes within 10 seconds.

  • Test: Start container → Observe dashboard
  • Pass: New session appears within 10s (polling) or 2s (WebSocket)

FR-04: Users can terminate container sessions (if authorized).

  • Test: Admin clicks "Terminate" → Container receives termination signal
  • Pass: Session status changes to "terminated", all users disconnected

FR-05: User session list shows all users in a container.

  • Test: 3 users join container → Open session detail drawer
  • Pass: All 3 users listed with join timestamps

FR-06: Heartbeat timer updates in real-time and shows warning before expiry.

  • Test: Observe heartbeat timer for 5 minutes
  • Pass: Timer increments every second, turns yellow at 4min, red at 5.5min

FR-07: License utilization displays accurate active/total ratio.

  • Test: License with 10 seats, 7 active containers
  • Pass: Shows "7 / 10" with 70% progress bar (yellow)

FR-08: Filters persist across page refreshes.

  • Test: Apply filters → Refresh page
  • Pass: Filters still applied after refresh

FR-09: Search filters sessions by container ID, name, or hostname.

  • Test: Search "prod-workstation" → See only matching containers
  • Pass: Results match search term

FR-10: Session detail drawer shows comprehensive metadata.

  • Test: Open session detail → Check all sections
  • Pass: Overview, Users, Heartbeats, Metadata all populated

9.2 Non-Functional Requirements

NFR-01: Dashboard loads in <2 seconds on 100 sessions.

  • Test: Load dashboard with 100 sessions → Measure load time
  • Pass: Initial render <2s

NFR-02: UI remains responsive during real-time updates.

  • Test: 10 concurrent session updates → Interact with UI
  • Pass: No lag, animations smooth

NFR-03: Mobile responsive (tablets and phones).

  • Test: Open dashboard on iPad (768px) and iPhone (375px)
  • Pass: Layout adapts, all functions accessible

NFR-04: Accessible (WCAG 2.1 AA).

  • Test: Use screen reader, keyboard navigation, color contrast checker
  • Pass: All elements accessible, contrast >4.5:1

NFR-05: Error messages are user-friendly and actionable.

  • Test: Trigger 5 different error types → Read error messages
  • Pass: Messages explain what happened and what to do

10. Implementation Roadmap

Phase 1: Core Dashboard (Sprint 1 - 2 weeks)

Week 1:

  • Setup React project structure
  • Implement AuthContext with role-based access
  • Create API client with interceptors
  • Implement useContainerSessions hook
  • Build ContainerSessionCard (grid view)
  • Build SessionFilters (basic)
  • Build SessionMetrics (3 cards)

Week 2:

  • Build ContainerSessionList (grid + list view)
  • Implement SessionDetailDrawer (overview tab)
  • Add HeartbeatTimer with real-time countdown
  • Add SessionStatusBadge with color coding
  • Implement basic error handling (toast notifications)
  • Add unit tests for components

Deliverable: MVP dashboard with session viewing, filtering, and detail view.


Phase 2: User Management & Actions (Sprint 2 - 1.5 weeks)

Week 3:

  • Implement UserSessionList in detail drawer
  • Add useTerminateSession mutation
  • Build termination confirmation modal
  • Add useKickUser mutation (remove user from container)
  • Implement role-based action visibility
  • Add HeartbeatTimeline chart

Week 4 (first half):

  • Add session export (JSON download)
  • Implement date range filter
  • Add sort by column (started_at, heartbeat, users)
  • Add pagination controls
  • Integration tests for mutations

Deliverable: Full session management with user tracking and admin actions.


Phase 3: Real-time & Polish (Sprint 3 - 1.5 weeks)

Week 4 (second half):

  • Implement React Query polling (5s intervals)
  • Add connection status indicator
  • Optimize re-renders (React.memo, useMemo)
  • Add loading skeletons for better UX
  • Implement filter persistence (localStorage)

Week 5:

  • Add mobile responsive layouts
  • Accessibility audit and fixes (WCAG 2.1 AA)
  • Performance testing (100+ sessions)
  • E2E tests with Cypress
  • Documentation (component docs, user guide)

Deliverable: Production-ready dashboard with real-time updates and polish.


Phase 4: Advanced Features (Future Sprints)

Future Enhancements:

  • WebSocket integration for instant updates
  • Advanced analytics (session duration trends, peak usage times)
  • CSV export for reporting
  • Session alerts (email when limit reached)
  • Container metrics visualization (CPU, memory from heartbeat)
  • Multi-language support (i18n)

Appendix A: TypeScript Type Definitions

// src/types/containerSessions.ts

export interface ContainerSession {
id: string;
tenant_id: string;
organization_id?: string;
license_id: string;

// Container identification
container_id: string;
container_type: 'docker' | 'workstation' | 'kubernetes';
container_name: string;

// Session lifecycle
status: 'active' | 'released' | 'expired' | 'terminated';
started_at: string;
released_at?: string;
expired_at?: string;

// User capacity
max_users: number;
current_user_count: number;

// Heartbeat tracking
last_heartbeat_at: string;
heartbeat_count: number;
heartbeat_interval_seconds: number;

// Metadata
hostname?: string;
ip_address?: string;
client_version?: string;
metadata: Record<string, any>;

// Timestamps
created_at: string;
updated_at: string;
}

export interface UserSession {
id: string;
container_session_id: string;
user_id?: string;

// User identification
external_user_id?: string;
user_email?: string;

// Lifecycle
is_active: boolean;
started_at: string;
ended_at?: string;
last_activity_at: string;

// Metadata
ip_address?: string;
user_agent?: string;

// Timestamps
created_at: string;
updated_at: string;
}

export interface License {
id: string;
license_key: string;
tenant_id: string;

seats_total: number;
seats_used: number;

is_active: boolean;
is_suspended: boolean;
expires_at?: string;
}

export enum UserRole {
SYSTEM_ADMIN = 'system_admin',
TENANT_ADMIN = 'tenant_admin',
TEAM_MANAGER = 'team_manager',
USER = 'user',
}

export interface SessionFilters {
tenant_id?: string;
container_type?: ('docker' | 'workstation' | 'kubernetes')[];
status?: ('active' | 'released' | 'expired' | 'terminated')[];
search?: string;
date_range?: {
start: string;
end: string;
};
}

Appendix B: API Endpoint Reference

List Container Sessions

GET /api/v1/sessions/
Authorization: Bearer <jwt_token>

Query Parameters:
- tenant_id (string, optional): Filter by tenant (System Admin only)
- container_type (string[], optional): Filter by type (docker, workstation, kubernetes)
- status (string[], optional): Filter by status (active, released, expired, terminated)
- search (string, optional): Search container_id, container_name, hostname
- page (int, optional): Page number (default 1)
- page_size (int, optional): Items per page (default 20)
- sort_by (string, optional): Sort field (started_at, last_heartbeat_at, current_user_count)
- sort_order (string, optional): asc or desc

Response 200:
{
"count": 42,
"next": "/api/v1/sessions/?page=2",
"previous": null,
"results": [
{
"id": "uuid",
"container_id": "abc123...",
"container_type": "docker",
"status": "active",
"max_users": 1,
"current_user_count": 0,
"last_heartbeat_at": "2026-01-05T10:30:00Z",
"started_at": "2026-01-05T09:00:00Z"
}
]
}

Get Session Detail

GET /api/v1/sessions/{session_id}/
Authorization: Bearer <jwt_token>

Response 200:
{
"id": "uuid",
"container_id": "abc123...",
"container_type": "docker",
"container_name": "prod-workstation-01",
"status": "active",
"max_users": 5,
"current_user_count": 3,
"last_heartbeat_at": "2026-01-05T10:30:00Z",
"heartbeat_count": 42,
"started_at": "2026-01-05T09:00:00Z",
"hostname": "workstation-01.example.com",
"ip_address": "203.0.113.42",
"client_version": "1.2.0",
"metadata": {},
"user_sessions": [
{
"id": "uuid",
"user_email": "alice@example.com",
"is_active": true,
"started_at": "2026-01-05T09:15:00Z",
"last_activity_at": "2026-01-05T10:28:00Z"
}
]
}

Terminate Session

POST /api/v1/sessions/{session_id}/terminate/
Authorization: Bearer <jwt_token>
Content-Type: application/json

Body:
{
"reason": "License reallocation"
}

Response 200:
{
"session_id": "uuid",
"terminated_at": "2026-01-05T10:35:00Z",
"users_disconnected": 3
}

Kick User from Container

POST /api/v1/sessions/{session_id}/users/{user_session_id}/kick/
Authorization: Bearer <jwt_token>

Response 200:
{
"user_session_id": "uuid",
"kicked_at": "2026-01-05T10:40:00Z"
}

Appendix C: Mermaid User Flow Diagram


Revision History

VersionDateAuthorChanges
1.0.02026-01-05Claude (Task B.4.5.1)Initial specification

Status: Ready for Development Next Steps: Review with frontend team → Create implementation tasks → Begin Sprint 1