Module System¶
Overview¶
The Module System provides a modular architecture for organizing OpenStack service functionality in Substation. It consists of two main components:
- ModuleOrchestrator: Manages the module system lifecycle, including registration, initialization, and health checking
- ModuleNavigationProvider: Protocol that modules implement to provide navigation-related functionality
Location: Sources/Substation/Framework/
Architecture¶
graph TB
subgraph Orchestration["Module Orchestration"]
MO[ModuleOrchestrator]
MR[ModuleRegistry]
FF[FeatureFlags]
end
subgraph Modules["OpenStack Modules"]
SM[ServersModule]
NM[NetworksModule]
VM[VolumesModule]
OM[Other Modules...]
end
subgraph Views["View System"]
VR[ViewRegistry]
CV[CoreViews]
end
MO --> MR
MO --> FF
MO --> VR
MR --> SM
MR --> NM
MR --> VM
MR --> OM
SM -.-> |implements| MNP[ModuleNavigationProvider]
NM -.-> |implements| MNP
VM -.-> |implements| MNP
ModuleOrchestrator¶
The ModuleOrchestrator manages the entire module system lifecycle.
Class Definition¶
@MainActor
final class ModuleOrchestrator {
/// The module registry (nil if module system failed to initialize)
private(set) var moduleRegistry: ModuleRegistry?
/// Whether the module system is enabled and loaded
var isModuleSystemActive: Bool
/// Get module count
var moduleCount: Int
}
Initialization¶
/// Initialize the module system with a TUI reference
/// - Parameter tui: The TUI instance to initialize with
/// - Throws: ModuleError if initialization fails
func initialize(with tui: TUI) async throws
The initialization process:
sequenceDiagram
participant TUI
participant MO as ModuleOrchestrator
participant CV as CoreViews
participant VR as ViewRegistry
participant FF as FeatureFlags
participant MR as ModuleRegistry
TUI->>MO: initialize(with: tui)
MO->>CV: registerViewsEnhanced(tui)
CV-->>MO: [ViewMetadata]
MO->>VR: register(metadataList)
MO->>FF: useModuleSystem?
alt Module System Enabled
MO->>MR: initialize(with: tui)
MR-->>MO: Success
MO->>VR: logRegistrationStatus()
else Module System Disabled
MO->>VR: logRegistrationStatus()
Note over MO: Continue with core views only
end
Module Access Methods¶
/// Get all loaded modules
func allModules() -> [any OpenStackModule]
/// Get a specific module by identifier
func module(identifier: String) -> (any OpenStackModule)?
Module Operations¶
/// Reload all modules
func reloadModules(with tui: TUI) async throws
/// Check module health for all loaded modules
func checkModuleHealth() async -> [String: ModuleHealthStatus]
/// Unload a specific module
func unloadModule(identifier: String) async
/// Clear all modules from the registry
func clearAllModules()
Health Check Flow¶
sequenceDiagram
participant TUI
participant MO as ModuleOrchestrator
participant MR as ModuleRegistry
participant M1 as Module 1
participant M2 as Module 2
TUI->>MO: checkModuleHealth()
MO->>MR: allModules()
MR-->>MO: [Module 1, Module 2, ...]
par Health Checks
MO->>M1: healthCheck()
M1-->>MO: ModuleHealthStatus
and
MO->>M2: healthCheck()
M2-->>MO: ModuleHealthStatus
end
MO-->>TUI: [String: ModuleHealthStatus]
ModuleNavigationProvider¶
Protocol that modules implement to provide navigation-related functionality to the TUI system.
Protocol Definition¶
@MainActor
protocol ModuleNavigationProvider {
/// Number of items in the current view
var itemCount: Int { get }
/// Maximum selection index for bounds checking
var maxSelectionIndex: Int { get }
/// Refresh data for this module
func refresh() async throws
/// Get contextual command suggestions for the current view
func getContextualSuggestions() -> [String]
/// Open detail view for the currently selected resource
func openDetailView(tui: TUI) -> Bool
/// Ensure data is loaded for the current view
func ensureDataLoaded(tui: TUI) async
}
Protocol Purpose¶
graph LR
subgraph TUI["TUI System"]
Nav[Navigation]
Scroll[Scroll Bounds]
Refresh[Refresh Logic]
Commands[Command Mode]
end
subgraph Module["Module"]
MNP[ModuleNavigationProvider]
end
Nav --> |openDetailView| MNP
Scroll --> |maxSelectionIndex| MNP
Refresh --> |refresh| MNP
Commands --> |getContextualSuggestions| MNP
Default Implementations¶
The protocol provides default implementations for optional functionality:
extension ModuleNavigationProvider {
/// Default: itemCount - 1
var maxSelectionIndex: Int {
return max(0, itemCount - 1)
}
/// Default: empty array (no suggestions)
func getContextualSuggestions() -> [String] {
return []
}
/// Default: false (not handled by module)
func openDetailView(tui: TUI) -> Bool {
return false
}
/// Default: no-op (no lazy loading)
func ensureDataLoaded(tui: TUI) async {
// Default: no lazy loading needed
}
}
Method Details¶
itemCount¶
Returns the total count of items currently displayed by the module. Used for scroll calculations and empty state detection.
maxSelectionIndex¶
Returns the maximum valid selection index for the current view. Typically itemCount - 1, but may differ for views with headers or non-selectable elements.
refresh()¶
Clears cached data and fetches fresh data from the server. Called when: - User triggers manual refresh - Automatic refresh interval expires
getContextualSuggestions()¶
Returns an array of command strings relevant to the current module state. Displayed in command mode to help users discover navigation options.
openDetailView(tui:)¶
Handles navigation to the detail view for the currently selected item. Returns true if the module handled the navigation, false to fall back to TUI's built-in navigation.
ensureDataLoaded(tui:)¶
Called when entering a view to perform lazy loading. Used by modules that only load data on first access (e.g., Barbican secrets, Swift objects).
Module Lifecycle¶
stateDiagram-v2
[*] --> Uninitialized
Uninitialized --> Initializing: initialize()
Initializing --> Active: Success
Initializing --> Failed: Error
Active --> Reloading: reloadModules()
Reloading --> Active: Success
Reloading --> Failed: Error
Active --> Unloading: unloadModule()
Unloading --> Uninitialized
Active --> Cleared: clearAllModules()
Cleared --> [*]
Usage Examples¶
Initializing the Module System¶
let orchestrator = ModuleOrchestrator()
try await orchestrator.initialize(with: tui)
if orchestrator.isModuleSystemActive {
print("Module system active with \(orchestrator.moduleCount) modules")
}
Accessing Modules¶
// Get all modules
let modules = orchestrator.allModules()
// Get specific module
if let serversModule = orchestrator.module(identifier: "servers") {
// Use the module
}
Health Checking¶
let healthStatus = await orchestrator.checkModuleHealth()
for (moduleId, status) in healthStatus {
print("\(moduleId): \(status)")
}
Implementing ModuleNavigationProvider¶
extension ServersModule: ModuleNavigationProvider {
var itemCount: Int {
return servers.count
}
func refresh() async throws {
servers = try await client.listServers()
}
func getContextualSuggestions() -> [String] {
return ["servers", "server-create", "server-console"]
}
func openDetailView(tui: TUI) -> Bool {
guard let selected = selectedServer else { return false }
tui.viewCoordinator.selectedResource = selected
tui.viewCoordinator.changeView(to: .serverDetail)
return true
}
}
Feature Flags¶
The module system is controlled by FeatureFlags.useModuleSystem. When disabled:
- Core system views are still registered
- Module-specific views are not available
- isModuleSystemActive returns false
Error Handling¶
Module initialization errors are logged but do not prevent the application from running. The system continues with core views only when module initialization fails.