feat: remove state preservation
This commit is contained in:
10
README.md
10
README.md
@@ -33,13 +33,13 @@ spore-ui/
|
|||||||
├── public/ # Frontend files
|
├── public/ # Frontend files
|
||||||
│ ├── index.html # Main HTML page
|
│ ├── index.html # Main HTML page
|
||||||
│ ├── styles.css # All CSS styles
|
│ ├── styles.css # All CSS styles
|
||||||
│ ├── framework.js # Enhanced component framework with state preservation
|
│ ├── framework.js # Enhanced component framework
|
||||||
│ ├── components.js # UI components with partial update support
|
│ ├── components.js # UI components with partial update support
|
||||||
│ ├── view-models.js # Data models with UI state management
|
│ ├── view-models.js # Data models with UI state management
|
||||||
│ ├── app.js # Main application logic
|
│ ├── app.js # Main application logic
|
||||||
│ └── test-state-preservation.html # Test interface for state preservation
|
│ └── test-interface.html # Test interface
|
||||||
├── docs/
|
├── docs/
|
||||||
│ └── STATE_PRESERVATION.md # Detailed documentation of state preservation system
|
│ └── FRAMEWORK_README.md # Framework documentation
|
||||||
└── README.md # This file
|
└── README.md # This file
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ spore-ui/
|
|||||||
1. **Install dependencies**: `npm install`
|
1. **Install dependencies**: `npm install`
|
||||||
2. **Start the server**: `npm start`
|
2. **Start the server**: `npm start`
|
||||||
3. **Open in browser**: `http://localhost:3001`
|
3. **Open in browser**: `http://localhost:3001`
|
||||||
4. **Test state preservation**: `http://localhost:3001/test-state-preservation.html`
|
4. **Test interface**: `http://localhost:3001/test-interface.html`
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ spore-ui/
|
|||||||
|
|
||||||
- **Backend**: Express.js, Node.js
|
- **Backend**: Express.js, Node.js
|
||||||
- **Frontend**: Vanilla JavaScript, CSS3, HTML5
|
- **Frontend**: Vanilla JavaScript, CSS3, HTML5
|
||||||
- **Framework**: Custom component-based architecture with state preservation
|
- **Framework**: Custom component-based architecture
|
||||||
- **API**: SPORE Embedded System API
|
- **API**: SPORE Embedded System API
|
||||||
- **Design**: Glassmorphism, CSS Grid, Flexbox
|
- **Design**: Glassmorphism, CSS Grid, Flexbox
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Set up periodic updates with state preservation
|
// Set up periodic updates
|
||||||
function setupPeriodicUpdates() {
|
function setupPeriodicUpdates() {
|
||||||
// Auto-refresh cluster members every 30 seconds using smart update
|
// Auto-refresh cluster members every 30 seconds using smart update
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
@@ -99,7 +99,7 @@ function setupPeriodicUpdates() {
|
|||||||
|
|
||||||
// Use smart update if available, otherwise fall back to regular update
|
// Use smart update if available, otherwise fall back to regular update
|
||||||
if (viewModel.smartUpdate && typeof viewModel.smartUpdate === 'function') {
|
if (viewModel.smartUpdate && typeof viewModel.smartUpdate === 'function') {
|
||||||
logger.debug('App: Performing smart update to preserve UI state...');
|
logger.debug('App: Performing smart update...');
|
||||||
viewModel.smartUpdate();
|
viewModel.smartUpdate();
|
||||||
} else if (viewModel.updateClusterMembers && typeof viewModel.updateClusterMembers === 'function') {
|
} else if (viewModel.updateClusterMembers && typeof viewModel.updateClusterMembers === 'function') {
|
||||||
logger.debug('App: Performing regular update...');
|
logger.debug('App: Performing regular update...');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Cluster Members Component with enhanced state preservation
|
// Cluster Members Component
|
||||||
class ClusterMembersComponent extends Component {
|
class ClusterMembersComponent extends Component {
|
||||||
constructor(container, viewModel, eventBus) {
|
constructor(container, viewModel, eventBus) {
|
||||||
super(container, viewModel, eventBus);
|
super(container, viewModel, eventBus);
|
||||||
@@ -142,7 +142,7 @@ class ClusterMembersComponent extends Component {
|
|||||||
logger.debug('ClusterMembersComponent: View model listeners set up');
|
logger.debug('ClusterMembersComponent: View model listeners set up');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle members update with state preservation
|
// Handle members update
|
||||||
handleMembersUpdate(newMembers, previousMembers) {
|
handleMembersUpdate(newMembers, previousMembers) {
|
||||||
logger.debug('ClusterMembersComponent: Members updated:', { newMembers, previousMembers });
|
logger.debug('ClusterMembersComponent: Members updated:', { newMembers, previousMembers });
|
||||||
|
|
||||||
@@ -166,9 +166,9 @@ class ClusterMembersComponent extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.shouldPreserveState(newMembers, previousMembers)) {
|
if (this.shouldSkipFullRender(newMembers, previousMembers)) {
|
||||||
// Perform partial update to preserve UI state
|
// Perform partial update
|
||||||
logger.debug('ClusterMembersComponent: Preserving state, performing partial update');
|
logger.debug('ClusterMembersComponent: Skipping full render, performing partial update');
|
||||||
this.updateMembersPartially(newMembers, previousMembers);
|
this.updateMembersPartially(newMembers, previousMembers);
|
||||||
} else {
|
} else {
|
||||||
// Full re-render if structure changed significantly
|
// Full re-render if structure changed significantly
|
||||||
@@ -242,8 +242,8 @@ class ClusterMembersComponent extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we should preserve UI state during update
|
// Check if we should skip full re-render during update
|
||||||
shouldPreserveState(newMembers, previousMembers) {
|
shouldSkipFullRender(newMembers, previousMembers) {
|
||||||
if (!previousMembers || !Array.isArray(previousMembers)) return false;
|
if (!previousMembers || !Array.isArray(previousMembers)) return false;
|
||||||
if (!Array.isArray(newMembers)) return false;
|
if (!Array.isArray(newMembers)) return false;
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ class ClusterMembersComponent extends Component {
|
|||||||
const newIps = new Set(newMembers.map(m => m.ip));
|
const newIps = new Set(newMembers.map(m => m.ip));
|
||||||
const prevIps = new Set(previousMembers.map(m => m.ip));
|
const prevIps = new Set(previousMembers.map(m => m.ip));
|
||||||
|
|
||||||
// If IPs are the same, we can preserve state
|
// If IPs are the same, we can skip full re-render
|
||||||
return newIps.size === prevIps.size &&
|
return newIps.size === prevIps.size &&
|
||||||
[...newIps].every(ip => prevIps.has(ip));
|
[...newIps].every(ip => prevIps.has(ip));
|
||||||
}
|
}
|
||||||
@@ -269,9 +269,9 @@ class ClusterMembersComponent extends Component {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update members partially to preserve UI state
|
// Update members partially
|
||||||
updateMembersPartially(newMembers, previousMembers) {
|
updateMembersPartially(newMembers, previousMembers) {
|
||||||
logger.debug('ClusterMembersComponent: Performing partial update to preserve UI state');
|
logger.debug('ClusterMembersComponent: Performing partial update');
|
||||||
|
|
||||||
// Build previous map by IP for stable diffs
|
// Build previous map by IP for stable diffs
|
||||||
const prevByIp = new Map((previousMembers || []).map(m => [m.ip, m]));
|
const prevByIp = new Map((previousMembers || []).map(m => [m.ip, m]));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Node Details Component with enhanced state preservation
|
// Node Details Component
|
||||||
class NodeDetailsComponent extends Component {
|
class NodeDetailsComponent extends Component {
|
||||||
constructor(container, viewModel, eventBus) {
|
constructor(container, viewModel, eventBus) {
|
||||||
super(container, viewModel, eventBus);
|
super(container, viewModel, eventBus);
|
||||||
@@ -15,14 +15,14 @@ class NodeDetailsComponent extends Component {
|
|||||||
this.subscribeToProperty('monitoringResources', this.handleMonitoringResourcesUpdate.bind(this));
|
this.subscribeToProperty('monitoringResources', this.handleMonitoringResourcesUpdate.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle node status update with state preservation
|
// Handle node status update
|
||||||
handleNodeStatusUpdate(newStatus, previousStatus) {
|
handleNodeStatusUpdate(newStatus, previousStatus) {
|
||||||
if (newStatus && !this.viewModel.get('isLoading')) {
|
if (newStatus && !this.viewModel.get('isLoading')) {
|
||||||
this.renderNodeDetails(newStatus, this.viewModel.get('tasks'), this.viewModel.get('endpoints'), this.viewModel.get('monitoringResources'));
|
this.renderNodeDetails(newStatus, this.viewModel.get('tasks'), this.viewModel.get('endpoints'), this.viewModel.get('monitoringResources'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle tasks update with state preservation
|
// Handle tasks update
|
||||||
handleTasksUpdate(newTasks, previousTasks) {
|
handleTasksUpdate(newTasks, previousTasks) {
|
||||||
const nodeStatus = this.viewModel.get('nodeStatus');
|
const nodeStatus = this.viewModel.get('nodeStatus');
|
||||||
if (nodeStatus && !this.viewModel.get('isLoading')) {
|
if (nodeStatus && !this.viewModel.get('isLoading')) {
|
||||||
@@ -51,7 +51,7 @@ class NodeDetailsComponent extends Component {
|
|||||||
this.updateActiveTab(newTab, previousTab);
|
this.updateActiveTab(newTab, previousTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle endpoints update with state preservation
|
// Handle endpoints update
|
||||||
handleEndpointsUpdate(newEndpoints, previousEndpoints) {
|
handleEndpointsUpdate(newEndpoints, previousEndpoints) {
|
||||||
const nodeStatus = this.viewModel.get('nodeStatus');
|
const nodeStatus = this.viewModel.get('nodeStatus');
|
||||||
const tasks = this.viewModel.get('tasks');
|
const tasks = this.viewModel.get('tasks');
|
||||||
@@ -60,7 +60,7 @@ class NodeDetailsComponent extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle monitoring resources update with state preservation
|
// Handle monitoring resources update
|
||||||
handleMonitoringResourcesUpdate(newResources, previousResources) {
|
handleMonitoringResourcesUpdate(newResources, previousResources) {
|
||||||
const nodeStatus = this.viewModel.get('nodeStatus');
|
const nodeStatus = this.viewModel.get('nodeStatus');
|
||||||
const tasks = this.viewModel.get('tasks');
|
const tasks = this.viewModel.get('tasks');
|
||||||
|
|||||||
@@ -225,10 +225,7 @@ class ViewModel {
|
|||||||
|
|
||||||
// Batch update with change detection
|
// Batch update with change detection
|
||||||
batchUpdate(updates, options = {}) {
|
batchUpdate(updates, options = {}) {
|
||||||
const { preserveUIState = true, notifyChanges = true } = options;
|
const { notifyChanges = true } = options;
|
||||||
|
|
||||||
// Optionally preserve UI state snapshot
|
|
||||||
const currentUIState = preserveUIState ? new Map(this._uiState) : null;
|
|
||||||
|
|
||||||
// Track which keys actually change and what the previous values were
|
// Track which keys actually change and what the previous values were
|
||||||
const changedKeys = [];
|
const changedKeys = [];
|
||||||
@@ -245,11 +242,6 @@ class ViewModel {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Restore UI state if requested
|
|
||||||
if (preserveUIState && currentUIState) {
|
|
||||||
this._uiState = currentUIState;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify listeners for changed keys
|
// Notify listeners for changed keys
|
||||||
if (notifyChanges) {
|
if (notifyChanges) {
|
||||||
changedKeys.forEach(key => {
|
changedKeys.forEach(key => {
|
||||||
@@ -259,7 +251,7 @@ class ViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base Component class with enhanced state preservation
|
// Base Component class
|
||||||
class Component {
|
class Component {
|
||||||
constructor(container, viewModel, eventBus) {
|
constructor(container, viewModel, eventBus) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// View Models for SPORE UI Components
|
// View Models for SPORE UI Components
|
||||||
|
|
||||||
// Cluster View Model with enhanced state preservation
|
// Cluster View Model
|
||||||
class ClusterViewModel extends ViewModel {
|
class ClusterViewModel extends ViewModel {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -23,7 +23,7 @@ class ClusterViewModel extends ViewModel {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cluster members with state preservation
|
// Update cluster members
|
||||||
async updateClusterMembers() {
|
async updateClusterMembers() {
|
||||||
try {
|
try {
|
||||||
logger.debug('ClusterViewModel: updateClusterMembers called');
|
logger.debug('ClusterViewModel: updateClusterMembers called');
|
||||||
@@ -45,12 +45,12 @@ class ClusterViewModel extends ViewModel {
|
|||||||
? members.filter(m => m && m.status && m.status.toUpperCase() === 'ACTIVE').length
|
? members.filter(m => m && m.status && m.status.toUpperCase() === 'ACTIVE').length
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
// Use batch update to preserve UI state
|
// Use batch update
|
||||||
this.batchUpdate({
|
this.batchUpdate({
|
||||||
members: members,
|
members: members,
|
||||||
lastUpdateTime: new Date().toISOString(),
|
lastUpdateTime: new Date().toISOString(),
|
||||||
onlineNodes: onlineNodes
|
onlineNodes: onlineNodes
|
||||||
}, { preserveUIState: true });
|
});
|
||||||
|
|
||||||
// Restore expanded cards and active tabs
|
// Restore expanded cards and active tabs
|
||||||
this.set('expandedCards', currentExpandedCards);
|
this.set('expandedCards', currentExpandedCards);
|
||||||
@@ -69,12 +69,12 @@ class ClusterViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update primary node display with state preservation
|
// Update primary node display
|
||||||
async updatePrimaryNodeDisplay() {
|
async updatePrimaryNodeDisplay() {
|
||||||
try {
|
try {
|
||||||
const discoveryInfo = await window.apiClient.getDiscoveryInfo();
|
const discoveryInfo = await window.apiClient.getDiscoveryInfo();
|
||||||
|
|
||||||
// Use batch update to preserve UI state
|
// Use batch update
|
||||||
const updates = {};
|
const updates = {};
|
||||||
|
|
||||||
if (discoveryInfo.primaryNode) {
|
if (discoveryInfo.primaryNode) {
|
||||||
@@ -91,7 +91,7 @@ class ClusterViewModel extends ViewModel {
|
|||||||
updates.totalNodes = 0;
|
updates.totalNodes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.batchUpdate(updates, { preserveUIState: true });
|
this.batchUpdate(updates);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch discovery info:', error);
|
console.error('Failed to fetch discovery info:', error);
|
||||||
@@ -208,7 +208,7 @@ class ClusterViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node Details View Model with enhanced state preservation
|
// Node Details View Model
|
||||||
class NodeDetailsViewModel extends ViewModel {
|
class NodeDetailsViewModel extends ViewModel {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -225,7 +225,7 @@ class NodeDetailsViewModel extends ViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load node details with state preservation
|
// Load node details
|
||||||
async loadNodeDetails(ip) {
|
async loadNodeDetails(ip) {
|
||||||
try {
|
try {
|
||||||
// Store current UI state
|
// Store current UI state
|
||||||
@@ -237,10 +237,10 @@ class NodeDetailsViewModel extends ViewModel {
|
|||||||
|
|
||||||
const nodeStatus = await window.apiClient.getNodeStatus(ip);
|
const nodeStatus = await window.apiClient.getNodeStatus(ip);
|
||||||
|
|
||||||
// Use batch update to preserve UI state
|
// Use batch update
|
||||||
this.batchUpdate({
|
this.batchUpdate({
|
||||||
nodeStatus: nodeStatus
|
nodeStatus: nodeStatus
|
||||||
}, { preserveUIState: true });
|
});
|
||||||
|
|
||||||
// Restore active tab
|
// Restore active tab
|
||||||
this.set('activeTab', currentActiveTab);
|
this.set('activeTab', currentActiveTab);
|
||||||
@@ -262,7 +262,7 @@ class NodeDetailsViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load tasks data with state preservation
|
// Load tasks data
|
||||||
async loadTasksData() {
|
async loadTasksData() {
|
||||||
try {
|
try {
|
||||||
const ip = this.get('nodeIp');
|
const ip = this.get('nodeIp');
|
||||||
@@ -276,7 +276,7 @@ class NodeDetailsViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load endpoints data with state preservation
|
// Load endpoints data
|
||||||
async loadEndpointsData() {
|
async loadEndpointsData() {
|
||||||
try {
|
try {
|
||||||
const ip = this.get('nodeIp');
|
const ip = this.get('nodeIp');
|
||||||
@@ -290,7 +290,7 @@ class NodeDetailsViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load monitoring resources data with state preservation
|
// Load monitoring resources data
|
||||||
async loadMonitoringResources() {
|
async loadMonitoringResources() {
|
||||||
try {
|
try {
|
||||||
const ip = this.get('nodeIp');
|
const ip = this.get('nodeIp');
|
||||||
|
|||||||
Reference in New Issue
Block a user