secubox-openwrt/package/secubox/luci-app-secubox-admin/htdocs/luci-static/resources/secubox-admin/state-utils.js
CyberMind-FR e258d86eea feat: Admin Control Center with State Management (v0.9.0)
Major feature release implementing comprehensive state management, component registry,
and admin control center with full UI integration.

## Backend Features (secubox-core v0.9.0-1)

State Management System:
-  State database (state-db.json) with 15 states across 4 categories
-  State machine with transition matrix validation
-  secubox-state CLI (8 commands: get, set, history, list, validate, sync, freeze, clear-error)
-  state-machine.sh with atomic transitions using flock
-  State history tracking with timestamps and reasons
-  Error state handling with detailed error info
-  Frozen state support for system-critical components

Component Registry System:
-  Component registry database (component-registry.json)
-  secubox-component CLI (7 commands: list, get, register, unregister, tree, affected, set-setting)
-  Component types: app, module, widget, service, composite
-  Dependency tracking (required/optional)
-  Recursive dependency tree resolution
-  Reverse dependency tracking
-  Component settings management
-  Profile tagging and filtering

Auto-Sync System:
-  secubox-sync-registry CLI for catalog synchronization
-  Auto-populate from catalog.json
-  Plugin catalog directory scanning
-  Installed package detection
-  Automatic state initialization

RPC Backend (luci.secubox):
-  6 state management RPC methods
-  5 component registry RPC methods
-  Bulk operations support
-  State validation endpoints

## Frontend Features (luci-app-secubox-admin v1.0.0-16)

UI Components:
-  state-utils.js: 20+ utility functions, state config, transition validation
-  StateIndicator.js: 5 rendering modes (badge, compact, pill, dot, statistics)
-  StateTimeline.js: 4 visualization modes (vertical, horizontal, compact, transition diagram)
-  state-management.css: 600+ lines with animations, responsive design, accessibility

Admin Control Center Dashboard:
-  System overview panel with health metrics
-  Component state summary with statistics
-  Recent state transitions timeline
-  Alerts panel for warnings and errors
-  Quick actions panel
-  Real-time updates (5-second polling)
-  Metric cards with hover effects
-  State distribution by category

API Integration (api.js):
-  11 RPC method declarations
-  Enhanced methods: getComponentWithState(), getAllComponentsWithStates()
-  Bulk operations: bulkSetComponentState()
-  State statistics: getStateStatistics()
-  Retry logic with exponential backoff
-  Promise-based async operations

## Documentation

Comprehensive Documentation:
-  API-REFERENCE.md (1,200+ lines): Complete API docs for RPC, CLI, JS
-  EXAMPLES.md (800+ lines): 30+ usage examples, shell scripts, integration patterns
-  State definitions table (15 states)
-  State transition matrix
-  Component metadata schemas
-  Error codes reference
-  Testing examples

## State Definitions

15 States Across 4 Categories:
- Persistent: available, installed, active, disabled, frozen
- Transient: installing, configuring, activating, starting, stopping, uninstalling
- Runtime: running, stopped
- Error: error (with subtypes)

State Transition Flow:
available → installing → installed → configuring → configured →
activating → active → starting → running → stopping → stopped

## Technical Details

Files Created (10 backend + 8 frontend):
Backend:
- /usr/sbin/secubox-state (12KB, 8 commands)
- /usr/sbin/secubox-component (12KB, 7 commands)
- /usr/sbin/secubox-sync-registry (8.4KB)
- /usr/share/secubox/state-machine.sh (5.2KB)
- /var/lib/secubox/state-db.json (schema)
- /var/lib/secubox/component-registry.json (schema)

Frontend:
- resources/secubox-admin/state-utils.js (~400 lines)
- resources/secubox-admin/components/StateIndicator.js (~350 lines)
- resources/secubox-admin/components/StateTimeline.js (~450 lines)
- resources/secubox-admin/state-management.css (~600 lines)
- resources/view/secubox-admin/control-center.js (~550 lines)
- resources/secubox-admin/api.js (+145 lines)

Documentation:
- docs/admin-control-center/API-REFERENCE.md (1,200+ lines)
- docs/admin-control-center/EXAMPLES.md (800+ lines)

Files Modified (3):
- package/secubox/secubox-core/Makefile (v0.8.0 → v0.9.0-1)
- package/secubox/luci-app-secubox-admin/Makefile (release 15 → 16)
- package/secubox/secubox-core/root/usr/libexec/rpcd/luci.secubox (+157 lines)

## Installation & Migration

Makefile Updates:
- Added 3 new CLI tools to install section
- Added state-machine.sh to scripts
- Updated package description
- Enhanced postinst to initialize databases
- Auto-sync registry on first install

Postinst Features:
- Automatic state-db.json initialization
- Automatic component-registry.json initialization
- Catalog sync on install
- Version announcement with new features

## Performance & Security

Performance:
- File locking (flock) for atomic state transitions
- State history limited to 100 entries per component
- RPC retry logic with exponential backoff
- Bulk operations use Promise.all for parallel execution
- Component list caching (30 seconds)

Security:
- Frozen state prevents unauthorized modifications
- All state changes logged with timestamp and reason
- System-critical components have additional safeguards
- Proper authentication required for state transitions

## Testing & Validation

Features:
- State transition validation
- Component dependency resolution
- Circular dependency detection
- State consistency checker
- Integration test scripts included in docs

## Breaking Changes

None - Backward Compatible:
- Existing RPC methods remain functional
- State-aware methods are additive
- Components without state default to 'available'
- Migration is automatic on install

## Statistics

Total Implementation:
- Lines of Code: ~4,000
  - Backend: ~1,800 (Bash + JSON)
  - Frontend: ~2,200 (JavaScript + CSS)
  - Documentation: ~2,000 (Markdown)
- Functions/Commands: 40+
- RPC Methods: 11
- CLI Commands: 22
- UI Components: 5
- Documentation Pages: 2

## Next Phase

Remaining from Plan:
- Phase 4: System Hub integration
- Phase 5: Migration script (secubox-migrate-state)
- Phase 6: Additional documentation (ARCHITECTURE.md, STATE-MANAGEMENT.md, etc.)
- Phase 7: Additional UI views (components.js, state-manager.js, debug-panel.js)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-05 10:05:32 +01:00

458 lines
10 KiB
JavaScript

'use strict';
'require baseclass';
/**
* SecuBox State Management Utilities
* Helper functions for state validation, formatting, and visualization
*/
// State configuration with colors, icons, and labels
var STATE_CONFIG = {
available: {
color: '#6b7280',
icon: '○',
label: 'Available',
category: 'persistent',
description: 'Component is available for installation'
},
installing: {
color: '#3b82f6',
icon: '⏳',
label: 'Installing',
category: 'transient',
description: 'Installation in progress'
},
installed: {
color: '#8b5cf6',
icon: '✓',
label: 'Installed',
category: 'persistent',
description: 'Component is installed but not active'
},
configuring: {
color: '#3b82f6',
icon: '⚙',
label: 'Configuring',
category: 'transient',
description: 'Configuration in progress'
},
configured: {
color: '#8b5cf6',
icon: '✓',
label: 'Configured',
category: 'transient',
description: 'Configuration completed'
},
activating: {
color: '#3b82f6',
icon: '↗',
label: 'Activating',
category: 'transient',
description: 'Activation in progress'
},
active: {
color: '#06b6d4',
icon: '●',
label: 'Active',
category: 'persistent',
description: 'Component is active but not running'
},
starting: {
color: '#3b82f6',
icon: '▶',
label: 'Starting',
category: 'transient',
description: 'Service is starting'
},
running: {
color: '#10b981',
icon: '▶',
label: 'Running',
category: 'runtime',
description: 'Service is running'
},
stopping: {
color: '#f59e0b',
icon: '⏸',
label: 'Stopping',
category: 'transient',
description: 'Service is stopping'
},
stopped: {
color: '#6b7280',
icon: '⏹',
label: 'Stopped',
category: 'runtime',
description: 'Service is stopped'
},
error: {
color: '#ef4444',
icon: '✗',
label: 'Error',
category: 'error',
description: 'Component encountered an error'
},
frozen: {
color: '#06b6d4',
icon: '❄',
label: 'Frozen',
category: 'persistent',
description: 'Component is frozen (locked)'
},
disabled: {
color: '#9ca3af',
icon: '⊘',
label: 'Disabled',
category: 'persistent',
description: 'Component is disabled'
},
uninstalling: {
color: '#f59e0b',
icon: '⏳',
label: 'Uninstalling',
category: 'transient',
description: 'Uninstallation in progress'
}
};
// State transition matrix
var STATE_TRANSITIONS = {
available: ['installing'],
installing: ['installed', 'error'],
installed: ['configuring', 'uninstalling'],
configuring: ['configured', 'error'],
configured: ['activating', 'disabled'],
activating: ['active', 'error'],
active: ['starting', 'disabled', 'frozen'],
starting: ['running', 'error'],
running: ['stopping', 'error', 'frozen'],
stopping: ['stopped', 'error'],
stopped: ['starting', 'disabled', 'uninstalling'],
error: ['available', 'installed', 'stopped'],
frozen: ['active'],
disabled: ['active', 'uninstalling'],
uninstalling: ['available', 'error']
};
return baseclass.extend({
/**
* Get state configuration
* @param {string} state - State name
* @returns {Object} State configuration
*/
getStateConfig: function(state) {
return STATE_CONFIG[state] || {
color: '#6b7280',
icon: '?',
label: state || 'Unknown',
category: 'unknown',
description: 'Unknown state'
};
},
/**
* Get state color
* @param {string} state - State name
* @returns {string} CSS color value
*/
getStateColor: function(state) {
var config = this.getStateConfig(state);
return config.color;
},
/**
* Get state icon
* @param {string} state - State name
* @returns {string} Icon character
*/
getStateIcon: function(state) {
var config = this.getStateConfig(state);
return config.icon;
},
/**
* Get state label
* @param {string} state - State name
* @returns {string} Human-readable label
*/
getStateLabel: function(state) {
var config = this.getStateConfig(state);
return config.label;
},
/**
* Get state category
* @param {string} state - State name
* @returns {string} Category (persistent, transient, runtime, error, unknown)
*/
getStateCategory: function(state) {
var config = this.getStateConfig(state);
return config.category;
},
/**
* Check if transition is valid
* @param {string} fromState - Current state
* @param {string} toState - Target state
* @returns {boolean} True if transition is allowed
*/
canTransition: function(fromState, toState) {
var allowedTransitions = STATE_TRANSITIONS[fromState];
if (!allowedTransitions) {
return false;
}
return allowedTransitions.indexOf(toState) !== -1;
},
/**
* Get allowed next states
* @param {string} currentState - Current state
* @returns {Array<string>} Array of allowed next states
*/
getNextStates: function(currentState) {
return STATE_TRANSITIONS[currentState] || [];
},
/**
* Get all available states
* @returns {Array<string>} Array of all state names
*/
getAllStates: function() {
return Object.keys(STATE_CONFIG);
},
/**
* Format state history entry
* @param {Object} historyEntry - History entry object
* @returns {string} Formatted history string
*/
formatHistoryEntry: function(historyEntry) {
if (!historyEntry) {
return '';
}
var state = historyEntry.state || 'unknown';
var timestamp = historyEntry.timestamp || '';
var reason = historyEntry.reason || 'unknown';
var date = timestamp ? new Date(timestamp) : null;
var timeStr = date ? date.toLocaleString() : timestamp;
return timeStr + ' - ' + this.getStateLabel(state) + ' (' + reason + ')';
},
/**
* Format timestamp
* @param {string} timestamp - ISO timestamp
* @returns {string} Formatted timestamp
*/
formatTimestamp: function(timestamp) {
if (!timestamp) {
return 'N/A';
}
try {
var date = new Date(timestamp);
return date.toLocaleString();
} catch (e) {
return timestamp;
}
},
/**
* Get time ago string
* @param {string} timestamp - ISO timestamp
* @returns {string} Relative time string (e.g., "5 minutes ago")
*/
getTimeAgo: function(timestamp) {
if (!timestamp) {
return 'never';
}
try {
var date = new Date(timestamp);
var now = new Date();
var seconds = Math.floor((now - date) / 1000);
if (seconds < 60) {
return seconds + ' second' + (seconds !== 1 ? 's' : '') + ' ago';
}
var minutes = Math.floor(seconds / 60);
if (minutes < 60) {
return minutes + ' minute' + (minutes !== 1 ? 's' : '') + ' ago';
}
var hours = Math.floor(minutes / 60);
if (hours < 24) {
return hours + ' hour' + (hours !== 1 ? 's' : '') + ' ago';
}
var days = Math.floor(hours / 24);
if (days < 30) {
return days + ' day' + (days !== 1 ? 's' : '') + ' ago';
}
var months = Math.floor(days / 30);
return months + ' month' + (months !== 1 ? 's' : '') + ' ago';
} catch (e) {
return 'unknown';
}
},
/**
* Check if state is transient
* @param {string} state - State name
* @returns {boolean} True if state is transient
*/
isTransient: function(state) {
return this.getStateCategory(state) === 'transient';
},
/**
* Check if state is error
* @param {string} state - State name
* @returns {boolean} True if state is error
*/
isError: function(state) {
return this.getStateCategory(state) === 'error';
},
/**
* Check if state is running
* @param {string} state - State name
* @returns {boolean} True if state is running
*/
isRunning: function(state) {
return state === 'running';
},
/**
* Check if state is frozen
* @param {string} state - State name
* @returns {boolean} True if state is frozen
*/
isFrozen: function(state) {
return state === 'frozen';
},
/**
* Get CSS class for state
* @param {string} state - State name
* @returns {string} CSS class name
*/
getStateClass: function(state) {
return 'state-' + (state || 'unknown');
},
/**
* Get badge CSS classes
* @param {string} state - State name
* @returns {string} Space-separated CSS classes
*/
getBadgeClasses: function(state) {
var classes = ['cyber-badge', 'state-badge', this.getStateClass(state)];
var category = this.getStateCategory(state);
if (category) {
classes.push('state-category-' + category);
}
return classes.join(' ');
},
/**
* Filter states by category
* @param {string} category - Category name
* @returns {Array<string>} Array of state names in category
*/
getStatesByCategory: function(category) {
var states = [];
var allStates = this.getAllStates();
for (var i = 0; i < allStates.length; i++) {
if (this.getStateCategory(allStates[i]) === category) {
states.push(allStates[i]);
}
}
return states;
},
/**
* Get state statistics from component list
* @param {Array<Object>} components - Array of components with state
* @returns {Object} State distribution statistics
*/
getStateStatistics: function(components) {
var stats = {
total: components ? components.length : 0,
by_state: {},
by_category: {
persistent: 0,
transient: 0,
runtime: 0,
error: 0,
unknown: 0
}
};
if (!components || !components.length) {
return stats;
}
for (var i = 0; i < components.length; i++) {
var state = components[i].current_state || components[i].state || 'unknown';
// Count by state
if (!stats.by_state[state]) {
stats.by_state[state] = 0;
}
stats.by_state[state]++;
// Count by category
var category = this.getStateCategory(state);
if (stats.by_category[category] !== undefined) {
stats.by_category[category]++;
}
}
return stats;
},
/**
* Sort components by state priority
* @param {Array<Object>} components - Array of components
* @returns {Array<Object>} Sorted components
*/
sortByStatePriority: function(components) {
if (!components || !components.length) {
return components;
}
var priorities = {
error: 1,
frozen: 2,
running: 3,
starting: 4,
stopping: 5,
active: 6,
stopped: 7,
installed: 8,
installing: 9,
disabled: 10,
available: 11
};
return components.slice().sort(function(a, b) {
var stateA = a.current_state || a.state || 'unknown';
var stateB = b.current_state || b.state || 'unknown';
var priorityA = priorities[stateA] || 99;
var priorityB = priorities[stateB] || 99;
return priorityA - priorityB;
});
}
});