Add admin page overhaul and compliance schema drift check specs, compliance upload improvements, drift checker helper
This commit is contained in:
423
.kiro/specs/admin-page-overhaul/design.md
Normal file
423
.kiro/specs/admin-page-overhaul/design.md
Normal file
@@ -0,0 +1,423 @@
|
||||
# Design Document: Admin Page Overhaul
|
||||
|
||||
## Overview
|
||||
|
||||
The Admin Page Overhaul replaces the current inline `UserManagement` modal rendering on the admin page with a full-page, themed admin panel. The new `AdminPage` component follows the same layout conventions as `CompliancePage`, `ExportsPage`, and `KnowledgeBasePage` — a top-level page component rendered in the main content area of `App.js` when `currentPage === 'admin'`.
|
||||
|
||||
The page consolidates three admin functions into a single tabbed interface:
|
||||
|
||||
1. **User Management** — themed table with inline add/edit forms, group badges, and active status toggles
|
||||
2. **Audit Log** — paginated, filterable log table with action-type badges and date range filters
|
||||
3. **System Info** — stat cards showing user counts, audit log totals, and recent activity
|
||||
|
||||
All sections use the dark tactical intelligence theme defined in `DESIGN_SYSTEM.md` and `App.css` — `intel-card` containers, `intel-button` controls, `intel-input` form fields, `status-badge` action labels, and `stat-card` stat displays.
|
||||
|
||||
### Design Decisions
|
||||
|
||||
- **New component, not a wrapper.** The existing `UserManagement.js` and `AuditLog.js` are white-background modals with Tailwind utility classes. Wrapping them would create visual inconsistency. The `AdminPage` component builds themed versions of both panels from scratch, reusing the same backend API endpoints.
|
||||
- **Existing modals preserved.** The `UserMenu` quick-access links ("Manage Users", "Audit Log") continue to open the existing modal components. This keeps the quick-access workflow intact while the admin page provides the full-featured experience.
|
||||
- **No new backend endpoints.** All data comes from existing routes: `GET /api/users`, `POST/PATCH/DELETE /api/users/:id`, `GET /api/audit-logs`, `GET /api/audit-logs/actions`.
|
||||
- **Inline styles + App.css classes.** Follows the project convention of defining style constants in the component file and referencing `App.css` classes (`intel-card`, `intel-button`, `data-row`, etc.) where available.
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[App.js] -->|currentPage === 'admin' && isAdmin| B[AdminPage]
|
||||
B --> C[Tab Navigation]
|
||||
C -->|"User Management"| D[UserManagementPanel]
|
||||
C -->|"Audit Log"| E[AuditLogPanel]
|
||||
C -->|"System Info"| F[SystemInfoPanel]
|
||||
|
||||
D -->|GET /api/users| G[Backend: users.js]
|
||||
D -->|POST /api/users| G
|
||||
D -->|PATCH /api/users/:id| G
|
||||
D -->|DELETE /api/users/:id| G
|
||||
|
||||
E -->|GET /api/audit-logs| H[Backend: auditLog.js]
|
||||
E -->|GET /api/audit-logs/actions| H
|
||||
|
||||
F -->|GET /api/users| G
|
||||
F -->|GET /api/audit-logs?limit=10| H
|
||||
```
|
||||
|
||||
### Component Hierarchy
|
||||
|
||||
```
|
||||
AdminPage
|
||||
├── PageHeader (title + accent glow)
|
||||
├── TabNavigation (User Management | Audit Log | System Info)
|
||||
├── UserManagementPanel
|
||||
│ ├── AddUserButton / InlineForm
|
||||
│ ├── UserTable
|
||||
│ │ └── UserRow (group badge, status toggle, edit/delete actions)
|
||||
│ ├── ErrorBanner
|
||||
│ └── SuccessToast
|
||||
├── AuditLogPanel
|
||||
│ ├── FilterBar (username, action, entity type, start date, end date)
|
||||
│ ├── LogTable
|
||||
│ │ └── LogRow (timestamp, user, action badge, entity, details, IP)
|
||||
│ ├── Pagination
|
||||
│ ├── EmptyState
|
||||
│ └── ErrorBanner
|
||||
└── SystemInfoPanel
|
||||
├── StatCards (total users, active users, audit entries, recent logins)
|
||||
└── RecentActivityList (10 most recent audit entries)
|
||||
```
|
||||
|
||||
### Data Flow
|
||||
|
||||
1. `App.js` renders `<AdminPage />` when `currentPage === 'admin'` and `isAdmin()` returns true. Non-admin users are redirected to home.
|
||||
2. `AdminPage` manages the active tab in local state (default: `'users'`).
|
||||
3. Each panel fetches its own data on mount using `fetch()` with `credentials: 'include'`.
|
||||
4. Mutations (create, update, delete user) trigger a re-fetch of the user list. Success/error feedback is shown inline.
|
||||
5. Audit log panel manages its own pagination and filter state, re-fetching on filter apply or page change.
|
||||
6. System info panel fetches user list and recent audit logs on mount, computing derived stats client-side.
|
||||
|
||||
## Components and Interfaces
|
||||
|
||||
### AdminPage (main component)
|
||||
|
||||
```javascript
|
||||
// frontend/src/components/pages/AdminPage.js
|
||||
export default function AdminPage() {
|
||||
// Props: none (reads auth context internally)
|
||||
// State:
|
||||
// activeTab: 'users' | 'audit' | 'system'
|
||||
// Renders: PageHeader, TabNavigation, conditional panel
|
||||
}
|
||||
```
|
||||
|
||||
### TabNavigation
|
||||
|
||||
```javascript
|
||||
// Internal to AdminPage
|
||||
// Props:
|
||||
// activeTab: string
|
||||
// onTabChange: (tab: string) => void
|
||||
// Tabs: [
|
||||
// { id: 'users', label: 'User Management', icon: Shield },
|
||||
// { id: 'audit', label: 'Audit Log', icon: Clock },
|
||||
// { id: 'system', label: 'System Info', icon: Activity },
|
||||
// ]
|
||||
```
|
||||
|
||||
Styling: monospace uppercase text, `--intel-accent` border and background on active tab, transparent with muted text on inactive tabs. Matches the tab pattern used in `CompliancePage` (team tabs).
|
||||
|
||||
### UserManagementPanel
|
||||
|
||||
```javascript
|
||||
// Internal to AdminPage
|
||||
// State:
|
||||
// users: Array<User>
|
||||
// loading: boolean
|
||||
// error: string | null
|
||||
// showForm: boolean
|
||||
// editingUser: User | null
|
||||
// formData: { username, email, password, group }
|
||||
// formError: string
|
||||
// successMessage: string
|
||||
//
|
||||
// API calls:
|
||||
// GET /api/users → fetch all users
|
||||
// POST /api/users → create user
|
||||
// PATCH /api/users/:id → update user (fields, group, is_active)
|
||||
// DELETE /api/users/:id → delete user
|
||||
//
|
||||
// Group badge colors (themed):
|
||||
// Admin: --intel-danger (#EF4444)
|
||||
// Standard_User: --intel-accent (#0EA5E9)
|
||||
// Leadership: --intel-warning (#F59E0B)
|
||||
// Read_Only: --text-muted (#94A3B8)
|
||||
```
|
||||
|
||||
### AuditLogPanel
|
||||
|
||||
```javascript
|
||||
// Internal to AdminPage
|
||||
// State:
|
||||
// logs: Array<AuditLogEntry>
|
||||
// loading: boolean
|
||||
// error: string | null
|
||||
// pagination: { page, limit, total, totalPages }
|
||||
// filters: { user, action, entityType, startDate, endDate }
|
||||
// actions: string[] (populated from /api/audit-logs/actions)
|
||||
//
|
||||
// API calls:
|
||||
// GET /api/audit-logs?page=&limit=25&user=&action=&entityType=&startDate=&endDate=
|
||||
// GET /api/audit-logs/actions
|
||||
//
|
||||
// Action badge colors (themed):
|
||||
// login/logout: --intel-success (#10B981)
|
||||
// *_create: --intel-accent (#0EA5E9)
|
||||
// *_update/*_edit: --intel-warning (#F59E0B)
|
||||
// *_delete: --intel-danger (#EF4444)
|
||||
// default: --text-muted (#94A3B8)
|
||||
```
|
||||
|
||||
### SystemInfoPanel
|
||||
|
||||
```javascript
|
||||
// Internal to AdminPage
|
||||
// State:
|
||||
// users: Array<User>
|
||||
// recentLogs: Array<AuditLogEntry>
|
||||
// loading: boolean
|
||||
// errors: { users: string | null, logs: string | null }
|
||||
//
|
||||
// Derived stats:
|
||||
// totalUsers: users.length
|
||||
// activeUsers: users.filter(u => u.is_active).length
|
||||
// recentLogins: users.filter(u => u.last_login && withinLast7Days(u.last_login)).length
|
||||
// totalAuditEntries: fetched from audit-logs pagination.total
|
||||
//
|
||||
// API calls:
|
||||
// GET /api/users
|
||||
// GET /api/audit-logs?limit=10&page=1
|
||||
```
|
||||
|
||||
### Integration with App.js
|
||||
|
||||
```javascript
|
||||
// In App.js, replace:
|
||||
// {currentPage === 'admin' && isAdmin() && (
|
||||
// <div className="space-y-6">
|
||||
// <UserManagement onClose={() => setCurrentPage('home')} />
|
||||
// </div>
|
||||
// )}
|
||||
//
|
||||
// With:
|
||||
// {currentPage === 'admin' && isAdmin() && <AdminPage />}
|
||||
// {currentPage === 'admin' && !isAdmin() && /* redirect to home */}
|
||||
//
|
||||
// Keep existing modal triggers:
|
||||
// {showUserManagement && <UserManagement onClose={...} />}
|
||||
// {showAuditLog && <AuditLog onClose={...} />}
|
||||
```
|
||||
|
||||
## Data Models
|
||||
|
||||
### User (from GET /api/users)
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: number,
|
||||
username: string,
|
||||
email: string,
|
||||
group: 'Admin' | 'Standard_User' | 'Leadership' | 'Read_Only',
|
||||
is_active: 0 | 1,
|
||||
created_at: string, // ISO datetime
|
||||
last_login: string | null // ISO datetime
|
||||
}
|
||||
```
|
||||
|
||||
### AuditLogEntry (from GET /api/audit-logs)
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: number,
|
||||
user_id: number,
|
||||
username: string,
|
||||
action: string, // e.g. 'login', 'user_create', 'cve_delete'
|
||||
entity_type: string, // e.g. 'auth', 'user', 'cve', 'document'
|
||||
entity_id: string | null,
|
||||
details: string | null, // JSON string
|
||||
ip_address: string | null,
|
||||
created_at: string // ISO datetime
|
||||
}
|
||||
```
|
||||
|
||||
### AuditLogPagination (from GET /api/audit-logs response)
|
||||
|
||||
```javascript
|
||||
{
|
||||
logs: AuditLogEntry[],
|
||||
pagination: {
|
||||
page: number,
|
||||
limit: number,
|
||||
total: number,
|
||||
totalPages: number
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Tab Configuration
|
||||
|
||||
```javascript
|
||||
const TABS = [
|
||||
{ id: 'users', label: 'User Management', icon: Shield },
|
||||
{ id: 'audit', label: 'Audit Log', icon: Clock },
|
||||
{ id: 'system', label: 'System Info', icon: Activity },
|
||||
];
|
||||
```
|
||||
|
||||
### Group Badge Theme Map
|
||||
|
||||
```javascript
|
||||
const GROUP_BADGE_THEMED = {
|
||||
Admin: { bg: 'rgba(239,68,68,0.15)', border: '#EF4444', text: '#FCA5A5' },
|
||||
Standard_User: { bg: 'rgba(14,165,233,0.15)', border: '#0EA5E9', text: '#7DD3FC' },
|
||||
Leadership: { bg: 'rgba(245,158,11,0.15)', border: '#F59E0B', text: '#FCD34D' },
|
||||
Read_Only: { bg: 'rgba(148,163,184,0.15)', border: '#94A3B8', text: '#CBD5E1' },
|
||||
};
|
||||
```
|
||||
|
||||
### Action Badge Theme Map
|
||||
|
||||
```javascript
|
||||
const ACTION_BADGE_THEMED = {
|
||||
login: { bg: 'rgba(16,185,129,0.15)', border: '#10B981', text: '#6EE7B7' },
|
||||
logout: { bg: 'rgba(148,163,184,0.15)', border: '#94A3B8', text: '#CBD5E1' },
|
||||
login_failed: { bg: 'rgba(239,68,68,0.15)', border: '#EF4444', text: '#FCA5A5' },
|
||||
user_create: { bg: 'rgba(14,165,233,0.15)', border: '#0EA5E9', text: '#7DD3FC' },
|
||||
user_update: { bg: 'rgba(245,158,11,0.15)', border: '#F59E0B', text: '#FCD34D' },
|
||||
user_delete: { bg: 'rgba(239,68,68,0.15)', border: '#EF4444', text: '#FCA5A5' },
|
||||
cve_create: { bg: 'rgba(14,165,233,0.15)', border: '#0EA5E9', text: '#7DD3FC' },
|
||||
cve_edit: { bg: 'rgba(245,158,11,0.15)', border: '#F59E0B', text: '#FCD34D' },
|
||||
cve_delete: { bg: 'rgba(239,68,68,0.15)', border: '#EF4444', text: '#FCA5A5' },
|
||||
document_upload: { bg: 'rgba(139,92,246,0.15)', border: '#8B5CF6', text: '#C4B5FD' },
|
||||
document_delete: { bg: 'rgba(239,68,68,0.15)', border: '#EF4444', text: '#FCA5A5' },
|
||||
};
|
||||
```
|
||||
|
||||
## Correctness Properties
|
||||
|
||||
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
||||
|
||||
### Property 1: Group badge color mapping is total and correct
|
||||
|
||||
*For any* valid user group string (`Admin`, `Standard_User`, `Leadership`, `Read_Only`), the group badge styling function SHALL return a non-null object with `bg`, `border`, and `text` fields matching the themed color for that group. *For any* string that is not one of the four valid groups, the function SHALL return the default muted styling.
|
||||
|
||||
**Validates: Requirements 3.3**
|
||||
|
||||
### Property 2: Edit form population preserves user data
|
||||
|
||||
*For any* user object with arbitrary `username`, `email`, and `group` values, populating the edit form from that user SHALL result in `formData.username === user.username`, `formData.email === user.email`, and `formData.group === user.group`, with `formData.password` set to an empty string.
|
||||
|
||||
**Validates: Requirements 3.5**
|
||||
|
||||
### Property 3: Self-modification prevention
|
||||
|
||||
*For any* user list that contains the currently authenticated admin user, the admin user's own row SHALL have the group dropdown disabled and the active status toggle disabled. *For any* other user in the list, those controls SHALL be enabled.
|
||||
|
||||
**Validates: Requirements 3.8**
|
||||
|
||||
### Property 4: Action badge color mapping is total and correct
|
||||
|
||||
*For any* known audit log action string (from the set of defined actions: `login`, `logout`, `login_failed`, `user_create`, `user_update`, `user_delete`, `cve_create`, `cve_edit`, `cve_delete`, `document_upload`, `document_delete`), the action badge styling function SHALL return the correct themed color object. *For any* unknown action string, the function SHALL return the default muted styling.
|
||||
|
||||
**Validates: Requirements 4.4**
|
||||
|
||||
### Property 5: Applying filters resets pagination to page 1
|
||||
|
||||
*For any* combination of filter values (username text, action type, entity type, start date, end date) and *for any* current page number, applying the filters SHALL result in a fetch call with `page=1`.
|
||||
|
||||
**Validates: Requirements 4.7**
|
||||
|
||||
### Property 6: Recent login count computation
|
||||
|
||||
*For any* list of user objects with random `last_login` timestamps (including null values), the computed "recent logins" count SHALL equal the number of users whose `last_login` is non-null and falls within the last 7 days from the current time.
|
||||
|
||||
**Validates: Requirements 5.1**
|
||||
|
||||
### Property 7: Admin-only access control
|
||||
|
||||
*For any* user object, the admin page content SHALL be rendered if and only if `user.group === 'Admin'`. When `user.group` is any value other than `'Admin'`, the system SHALL redirect to the home page.
|
||||
|
||||
**Validates: Requirements 6.1, 6.2**
|
||||
|
||||
## Error Handling
|
||||
|
||||
### User Management Panel
|
||||
|
||||
| Error Scenario | Handling |
|
||||
|---|---|
|
||||
| `GET /api/users` fails | Display error banner with `--intel-danger` styling. User table is hidden. Retry on next tab switch or manual refresh. |
|
||||
| `POST /api/users` fails (validation) | Display `formError` message below the form in danger color. Form remains open for correction. |
|
||||
| `POST /api/users` fails (409 conflict) | Display "Username or email already exists" in `formError`. |
|
||||
| `PATCH /api/users/:id` fails | Display inline error. Revert optimistic UI changes if any. |
|
||||
| `DELETE /api/users/:id` fails | Display alert with error message. User list unchanged. |
|
||||
| Self-demotion attempt | Group dropdown disabled for current user. Backend returns 400 if bypassed. |
|
||||
| Self-deactivation attempt | Toggle disabled for current user. Backend returns 400 if bypassed. |
|
||||
|
||||
### Audit Log Panel
|
||||
|
||||
| Error Scenario | Handling |
|
||||
|---|---|
|
||||
| `GET /api/audit-logs` fails | Display error banner with `--intel-danger` styling. Table hidden. |
|
||||
| `GET /api/audit-logs/actions` fails | Action filter dropdown shows no options. Non-critical — silently ignored. |
|
||||
| Invalid date range (start > end) | Client-side: no validation needed — backend handles gracefully by returning empty results. |
|
||||
| Empty result set | Display "No audit log entries found" message in `--text-muted` color. |
|
||||
|
||||
### System Info Panel
|
||||
|
||||
| Error Scenario | Handling |
|
||||
|---|---|
|
||||
| `GET /api/users` fails | Affected stat cards (total users, active users, recent logins) show "Unable to load" fallback text. |
|
||||
| `GET /api/audit-logs` fails | Audit entries stat card and recent activity list show "Unable to load" fallback. |
|
||||
| Partial failure (one endpoint fails) | Only the affected cards show fallback. Successfully loaded cards display normally. |
|
||||
|
||||
### Success Feedback
|
||||
|
||||
- Create user: green success toast "User created successfully" auto-dismisses after 2 seconds.
|
||||
- Update user: green success toast "User updated successfully" auto-dismisses after 2 seconds.
|
||||
- Delete user: green success toast "User deleted" auto-dismisses after 2 seconds.
|
||||
- Toggle active status: immediate UI update, no toast (inline visual feedback is sufficient).
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests (Example-Based)
|
||||
|
||||
Unit tests cover specific rendering, interaction, and integration scenarios:
|
||||
|
||||
**AdminPage structure:**
|
||||
- Renders page header with "Admin Panel" title
|
||||
- Defaults to User Management tab on mount
|
||||
- Switches panels when tabs are clicked
|
||||
- Only renders when user is admin (access control)
|
||||
|
||||
**UserManagementPanel:**
|
||||
- Renders user table with all required columns
|
||||
- Displays themed group badges for each group type
|
||||
- Shows inline form when "Add User" is clicked
|
||||
- Populates form with user data when edit is clicked
|
||||
- Shows confirmation dialog on delete
|
||||
- Disables self-modification controls for current user
|
||||
- Displays error banner on API failure
|
||||
- Displays success toast on successful operations
|
||||
|
||||
**AuditLogPanel:**
|
||||
- Renders log table with all required columns
|
||||
- Displays themed action badges
|
||||
- Renders filter controls (username, action, entity type, dates)
|
||||
- Fetches page 1 when filters are applied
|
||||
- Navigates pages with pagination controls
|
||||
- Shows empty state when no results
|
||||
- Shows error banner on API failure
|
||||
|
||||
**SystemInfoPanel:**
|
||||
- Renders four stat cards with correct labels
|
||||
- Computes derived stats correctly from mock data
|
||||
- Shows recent activity list with up to 10 entries
|
||||
- Shows fallback message when an API call fails
|
||||
|
||||
### Property-Based Tests
|
||||
|
||||
Property-based tests use [fast-check](https://github.com/dubzzz/fast-check) to verify universal properties across generated inputs. Each test runs a minimum of 100 iterations.
|
||||
|
||||
| Property | Test Description | Tag |
|
||||
|---|---|---|
|
||||
| Property 1 | Generate random group strings (valid + invalid), verify badge function returns correct colors | Feature: admin-page-overhaul, Property 1: Group badge color mapping is total and correct |
|
||||
| Property 2 | Generate random user objects, verify edit form population matches user fields exactly | Feature: admin-page-overhaul, Property 2: Edit form population preserves user data |
|
||||
| Property 3 | Generate random user lists containing the current admin, verify self-edit controls are disabled | Feature: admin-page-overhaul, Property 3: Self-modification prevention |
|
||||
| Property 4 | Generate random action strings (known + unknown), verify badge function returns correct colors | Feature: admin-page-overhaul, Property 4: Action badge color mapping is total and correct |
|
||||
| Property 5 | Generate random filter states and current page numbers, verify fetch is called with page=1 | Feature: admin-page-overhaul, Property 5: Applying filters resets pagination to page 1 |
|
||||
| Property 6 | Generate random user lists with random last_login timestamps, verify recent login count matches manual computation | Feature: admin-page-overhaul, Property 6: Recent login count computation |
|
||||
| Property 7 | Generate random user objects with random groups, verify admin page renders iff group === 'Admin' | Feature: admin-page-overhaul, Property 7: Admin-only access control |
|
||||
|
||||
### Test Configuration
|
||||
|
||||
- **Library:** fast-check (JavaScript property-based testing)
|
||||
- **Runner:** Jest (via react-scripts test)
|
||||
- **Iterations:** Minimum 100 per property test (`fc.assert(property, { numRuns: 100 })`)
|
||||
- **Tag format:** Comment at top of each property test referencing the design property
|
||||
Reference in New Issue
Block a user