File snapshots-v2.js of Package PersistenceOS
/**
* PersistenceOS Snapshots Management Module v2
* System and storage snapshot management for PersistenceOS
* Extracted for Phase 4 modular architecture
*/
/**
* Snapshots Configuration
*/
const SNAPSHOTS_CONFIG = {
REFRESH_INTERVAL: 25000, // 25 seconds
API_BASE_URL: window.location.hostname === 'localhost' ? 'http://localhost:8081' : `http://${window.location.hostname}:8080`, // Dynamic API backend
API_ENDPOINTS: {
LIST: '/api/snapshots',
CREATE: '/api/snapshots/create',
DELETE: '/api/snapshots/{id}/delete',
RESTORE: '/api/snapshots/{id}/restore',
SCHEDULE: '/api/snapshots/schedule',
SCHEDULES: '/api/snapshots/schedules',
CONFIGS: '/api/snapshots/configs',
STATISTICS: '/api/snapshots/statistics'
},
DEBUG: true
};
/**
* Snapshots Management System
* Handles all snapshot operations and state management
*/
class PersistenceSnapshots {
constructor() {
this.snapshots = [];
this.schedules = [];
this.isRefreshing = false;
this.refreshInterval = null;
this.init();
}
init() {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log('đ¸ PersistenceOS Snapshots Management Module v2 initialized');
}
// Start auto-refresh
this.startAutoRefresh();
}
/**
* Get all snapshots
*/
getSnapshots() {
return [...this.snapshots];
}
/**
* Get snapshot by ID
*/
getSnapshot(id) {
return this.snapshots.find(snap => snap.id === id);
}
/**
* Get snapshots by type
*/
getSnapshotsByType(type) {
return this.snapshots.filter(snap => snap.type === type);
}
/**
* Get all schedules
*/
getSchedules() {
return [...this.schedules];
}
/**
* Get schedule by ID
*/
getSchedule(id) {
return this.schedules.find(sched => sched.id === id);
}
/**
* Get snapshot statistics
*/
getSnapshotStats() {
const system = this.snapshots.filter(snap => snap.type === 'system').length;
const vm = this.snapshots.filter(snap => snap.type === 'vm').length;
const manual = this.snapshots.filter(snap => snap.type === 'manual').length;
const total = this.snapshots.length;
const totalSize = this.snapshots.reduce((sum, snap) => {
const sizeGB = parseFloat(snap.size.replace(' GB', ''));
return sum + sizeGB;
}, 0);
return { system, vm, manual, total, totalSize: totalSize.toFixed(1) + ' GB' };
}
/**
* Create a new snapshot with modal interface (hybrid pattern)
*/
async createSnapshot(snapshotNameOrData) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log('đ¸ Creating snapshot:', snapshotNameOrData);
}
// Handle both string (legacy) and object (new) parameters
let snapshotData;
if (typeof snapshotNameOrData === 'string') {
// Legacy call - show modal to get full data
snapshotData = await this.showSnapshotCreationModal();
if (!snapshotData) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log('đ¸ Snapshot creation cancelled by user');
}
return null;
}
} else if (typeof snapshotNameOrData === 'object') {
snapshotData = snapshotNameOrData;
} else {
// Show modal for interactive creation
snapshotData = await this.showSnapshotCreationModal();
if (!snapshotData) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log('đ¸ Snapshot creation cancelled by user');
}
return null;
}
}
try {
// Call real backend API
const response = await fetch(`${SNAPSHOTS_CONFIG.API_BASE_URL}/api/snapshots/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
description: snapshotData.description,
config: snapshotData.config || 'root'
})
});
if (response.ok) {
const result = await response.json();
if (result.success) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`â
Snapshot #${result.snapshot_number} created successfully`);
}
// Refresh snapshots list
await this.refreshSnapshots();
return {
success: true,
snapshot_number: result.snapshot_number,
message: result.message
};
} else {
throw new Error(result.error);
}
} else {
const error = await response.json();
throw new Error(error.detail || 'Failed to create snapshot');
}
} catch (error) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.error('â Error creating snapshot:', error);
}
throw error;
}
}
/**
* Show snapshot creation modal (similar to VM/Storage creation)
*/
async showSnapshotCreationModal() {
return new Promise((resolve) => {
// Create modal HTML
const modalHtml = `
<div id="snapshotCreationModal" class="modal-overlay" style="
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
">
<div class="modal-content" style="
background: #2c3e50;
border-radius: 8px;
padding: 2rem;
width: 90%;
max-width: 500px;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
">
<div class="modal-header" style="margin-bottom: 1.5rem;">
<h3 style="margin: 0; color: #3498db; display: flex; align-items: center; gap: 0.5rem;">
<i class="fas fa-camera"></i> Create System Snapshot
</h3>
</div>
<div class="modal-body">
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Description: <span style="color: #e74c3c;">*</span>
</label>
<input type="text" id="snapshotDescription" required
placeholder="Enter snapshot description..."
value="Manual snapshot created on ${new Date().toLocaleString()}"
style="width: 100%; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem;">
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Configuration:
</label>
<select id="snapshotConfig" style="width: 100%; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem;">
<option value="root">root (System snapshots)</option>
</select>
</div>
<div style="background: #34495e; padding: 1rem; border-radius: 4px; margin-bottom: 1rem;">
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
<i class="fas fa-info-circle" style="color: #3498db;"></i>
<strong>Snapshot Information</strong>
</div>
<div style="font-size: 0.9rem; color: #bdc3c7; line-height: 1.4;">
This will create a system snapshot using snapper, which is compatible with MicroOS 6.1's transactional update system.
Snapshots allow you to restore the system to a previous state if needed.
</div>
</div>
</div>
<div class="modal-footer" style="display: flex; gap: 1rem; justify-content: flex-end;">
<button type="button" onclick="closeSnapshotCreationModal(null)" style="
background: #7f8c8d;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s ease;
">
<i class="fas fa-times"></i> Cancel
</button>
<button type="button" onclick="submitSnapshotCreation()" style="
background: #3498db;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s ease;
">
<i class="fas fa-camera"></i> Create Snapshot
</button>
</div>
</div>
</div>
`;
// Add modal to DOM
document.body.insertAdjacentHTML('beforeend', modalHtml);
document.body.classList.add('modal-open');
// Focus on description field
setTimeout(() => {
document.getElementById('snapshotDescription').focus();
document.getElementById('snapshotDescription').select();
}, 100);
// Add modal functions
window.closeSnapshotCreationModal = (result) => {
const modal = document.getElementById('snapshotCreationModal');
if (modal) {
modal.remove();
document.body.classList.remove('modal-open');
}
delete window.closeSnapshotCreationModal;
delete window.submitSnapshotCreation;
resolve(result);
};
window.submitSnapshotCreation = () => {
const description = document.getElementById('snapshotDescription').value.trim();
const config = document.getElementById('snapshotConfig').value;
if (!description) {
alert('Please enter a description for the snapshot.');
return;
}
window.closeSnapshotCreationModal({ description, config });
};
});
}
/**
* Show snapshot scheduling modal
*/
async showSnapshotScheduleModal() {
return new Promise((resolve) => {
const modalHtml = `
<div id="snapshotScheduleModal" class="modal-overlay" style="
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
">
<div class="modal-content" style="
background: #2c3e50;
border-radius: 8px;
padding: 2rem;
width: 90%;
max-width: 600px;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
max-height: 80vh;
overflow-y: auto;
">
<div class="modal-header" style="margin-bottom: 1.5rem;">
<h3 style="margin: 0; color: #3498db; display: flex; align-items: center; gap: 0.5rem;">
<i class="fas fa-clock"></i> Configure Snapshot Schedule
</h3>
</div>
<div class="modal-body">
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Schedule Type: <span style="color: #e74c3c;">*</span>
</label>
<select id="scheduleType" style="width: 100%; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem;">
<option value="timeline">Timeline Snapshots (Hourly)</option>
<option value="custom">Custom Schedule</option>
</select>
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Configuration: <span style="color: #e74c3c;">*</span>
</label>
<select id="scheduleConfig" style="width: 100%; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem;">
<option value="root">root (System snapshots)</option>
</select>
</div>
<div id="customScheduleOptions" style="display: none;">
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Schedule Name:
</label>
<input type="text" id="scheduleName" placeholder="e.g., daily-backup"
style="width: 100%; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem;">
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Frequency:
</label>
<select id="scheduleFrequency" style="width: 100%; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem;">
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
</select>
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Time:
</label>
<input type="time" id="scheduleTime" value="02:00"
style="width: 100%; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem;">
</div>
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Retention (snapshots to keep):
</label>
<input type="number" id="scheduleRetention" value="7" min="1" max="100"
style="width: 100%; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem;">
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Description:
</label>
<textarea id="scheduleDescription" rows="3" placeholder="Optional description for this schedule..."
style="width: 100%; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem; resize: vertical;"></textarea>
</div>
<div style="background: #34495e; padding: 1rem; border-radius: 4px; margin-bottom: 1rem;">
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
<i class="fas fa-info-circle" style="color: #3498db;"></i>
<strong>Schedule Information</strong>
</div>
<div id="scheduleInfo" style="font-size: 0.9rem; color: #bdc3c7; line-height: 1.4;">
Timeline snapshots use snapper's built-in timeline feature for automatic hourly snapshots with cleanup.
</div>
</div>
</div>
<div class="modal-footer" style="display: flex; gap: 1rem; justify-content: flex-end;">
<button type="button" onclick="closeSnapshotScheduleModal(null)" style="
background: #7f8c8d;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s ease;
">
<i class="fas fa-times"></i> Cancel
</button>
<button type="button" onclick="submitSnapshotSchedule()" style="
background: #3498db;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s ease;
">
<i class="fas fa-clock"></i> Create Schedule
</button>
</div>
</div>
</div>
`;
// Add modal to DOM
document.body.insertAdjacentHTML('beforeend', modalHtml);
document.body.classList.add('modal-open');
// Add event handlers for schedule type change
const scheduleTypeSelect = document.getElementById('scheduleType');
const customOptions = document.getElementById('customScheduleOptions');
const scheduleInfo = document.getElementById('scheduleInfo');
scheduleTypeSelect.addEventListener('change', () => {
if (scheduleTypeSelect.value === 'custom') {
customOptions.style.display = 'block';
scheduleInfo.textContent = 'Custom schedules use systemd timers for flexible scheduling options.';
} else {
customOptions.style.display = 'none';
scheduleInfo.textContent = 'Timeline snapshots use snapper\'s built-in timeline feature for automatic hourly snapshots with cleanup.';
}
});
// Add modal functions
window.closeSnapshotScheduleModal = (result) => {
const modal = document.getElementById('snapshotScheduleModal');
if (modal) {
modal.remove();
document.body.classList.remove('modal-open');
}
delete window.closeSnapshotScheduleModal;
delete window.submitSnapshotSchedule;
resolve(result);
};
window.submitSnapshotSchedule = () => {
const type = document.getElementById('scheduleType').value;
const config = document.getElementById('scheduleConfig').value;
const retention = parseInt(document.getElementById('scheduleRetention').value);
const description = document.getElementById('scheduleDescription').value.trim();
let scheduleData = { type, config, retention, description };
if (type === 'custom') {
const name = document.getElementById('scheduleName').value.trim();
const frequency = document.getElementById('scheduleFrequency').value;
const time = document.getElementById('scheduleTime').value;
if (!name) {
alert('Please enter a name for the custom schedule.');
return;
}
scheduleData = { ...scheduleData, name, frequency, time };
}
window.closeSnapshotScheduleModal(scheduleData);
};
});
}
/**
* Show schedule configuration interface
*/
async showScheduleConfiguration() {
try {
const scheduleData = await this.showSnapshotScheduleModal();
if (!scheduleData) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log('đ
Schedule configuration cancelled by user');
}
return null;
}
// Create the schedule
const result = await this.createSchedule(scheduleData);
if (result.success) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`â
Schedule created successfully: ${result.schedule_id}`);
}
return result;
} else {
throw new Error(result.error || 'Failed to create schedule');
}
} catch (error) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.error('â Error in schedule configuration:', error);
}
throw error;
}
}
/**
* Delete a snapshot
*/
async deleteSnapshot(id) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`đī¸ Deleting snapshot: ${id}`);
}
const snapIndex = this.snapshots.findIndex(snap => snap.id === id);
if (snapIndex === -1) {
throw new Error(`Snapshot ${id} not found`);
}
const snapshot = this.snapshots[snapIndex];
// Simulate API call
await this.simulateAPICall();
this.snapshots.splice(snapIndex, 1);
this.notifySnapshotUpdate();
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`â
Snapshot ${snapshot.name} deleted successfully`);
}
}
/**
* Restore from snapshot
*/
async restoreSnapshot(id) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`đ Restoring from snapshot: ${id}`);
}
const snapshot = this.getSnapshot(id);
if (!snapshot) {
throw new Error(`Snapshot ${id} not found`);
}
// Simulate API call (longer for restore)
await this.simulateAPICall(3000);
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`â
System restored from snapshot ${snapshot.name} successfully`);
}
}
/**
* Create or update snapshot schedule
*/
async updateSchedule(scheduleData) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log('â° Updating snapshot schedule:', scheduleData);
}
// Validate schedule data
if (!scheduleData.name || !scheduleData.frequency) {
throw new Error('Invalid schedule data: name and frequency are required');
}
// Simulate API call
await this.simulateAPICall();
if (scheduleData.id) {
// Update existing schedule
const schedIndex = this.schedules.findIndex(sched => sched.id === scheduleData.id);
if (schedIndex !== -1) {
Object.assign(this.schedules[schedIndex], scheduleData);
}
} else {
// Create new schedule
const newSchedule = {
id: `sched-${Date.now()}`,
...scheduleData,
lastRun: null,
nextRun: this.calculateNextRun(scheduleData.frequency, scheduleData.time)
};
this.schedules.push(newSchedule);
}
this.notifySnapshotUpdate();
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log('â
Snapshot schedule updated successfully');
}
}
/**
* Calculate next run time for schedule
*/
calculateNextRun(frequency, time) {
const now = new Date();
const [hours, minutes] = time.split(':').map(Number);
let nextRun = new Date();
nextRun.setHours(hours, minutes, 0, 0);
switch (frequency) {
case 'daily':
if (nextRun <= now) {
nextRun.setDate(nextRun.getDate() + 1);
}
break;
case 'weekly':
nextRun.setDate(nextRun.getDate() + (7 - nextRun.getDay()));
break;
case 'monthly':
nextRun.setMonth(nextRun.getMonth() + 1, 1);
break;
}
return nextRun.toISOString();
}
/**
* Refresh snapshots information using real API
*/
async refreshSnapshots() {
if (this.isRefreshing) return;
this.isRefreshing = true;
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log('đ Refreshing snapshots information...');
}
try {
// Call real backend API
const response = await fetch(`${SNAPSHOTS_CONFIG.API_BASE_URL}/api/snapshots`);
if (response.ok) {
const data = await response.json();
// Update snapshots list with real data
this.snapshots = data.snapshots.map(snap => ({
id: `snap-${snap.number}`,
name: `snapshot-${snap.number}`,
number: snap.number,
date: snap.date,
description: snap.description,
type: 'system',
status: 'completed',
size: 'Unknown' // snapper doesn't provide size info easily
})) || [];
this.snapperAvailable = data.snapper_available;
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`â
Snapshots information refreshed successfully (${this.snapshots.length} snapshots found)`);
}
} else {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.warn('â ī¸ Failed to fetch snapshots from API');
}
// Keep existing snapshots on API failure
}
// Also load schedules during refresh
await this.loadSchedules();
this.notifySnapshotUpdate();
} catch (error) {
// Handle connection errors gracefully
if (error.message.includes('Failed to fetch') || error.message.includes('ERR_CONNECTION_REFUSED')) {
// Silently handle connection errors - this is normal when API is not available
if (SNAPSHOTS_CONFIG.DEBUG) {
console.warn('â ī¸ Snapshots API not available:', error.message);
}
} else {
// Log other types of errors
console.warn('â ī¸ Snapshots refresh failed:', error.message);
}
// Don't throw error to prevent breaking the UI
// Keep existing snapshots on error
} finally {
this.isRefreshing = false;
}
}
/**
* Simulate API call delay
*/
async simulateAPICall(delay = 1200) {
return new Promise((resolve) => {
setTimeout(resolve, delay + Math.random() * 800);
});
}
/**
* Start auto-refresh timer
*/
startAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
this.refreshInterval = setInterval(() => {
this.refreshSnapshots().catch(error => {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.warn('â ī¸ Snapshots auto-refresh failed:', error);
}
});
}, SNAPSHOTS_CONFIG.REFRESH_INTERVAL);
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log('â° Snapshots auto-refresh started (25s interval)');
}
}
/**
* Stop auto-refresh timer
*/
stopAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = null;
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log('âšī¸ Snapshots auto-refresh stopped');
}
}
}
/**
* Notify snapshot update listeners
*/
notifySnapshotUpdate() {
// Dispatch custom event for Vue reactivity
window.dispatchEvent(new CustomEvent('snapshotDataUpdate', {
detail: {
snapshots: this.getSnapshots(),
schedules: this.getSchedules(),
stats: this.getSnapshotStats()
}
}));
}
/**
* Load snapshot schedules from backend
*/
async loadSchedules() {
try {
const response = await fetch(`${SNAPSHOTS_CONFIG.API_BASE_URL}/api/snapshots/schedules`);
if (response.ok) {
const data = await response.json();
this.schedules = data.schedules || [];
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`â
Loaded ${this.schedules.length} snapshot schedules`);
}
} else {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.warn('â ī¸ Failed to load schedules from API');
}
this.schedules = [];
}
} catch (error) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.error('â Error loading schedules:', error);
}
this.schedules = [];
}
}
/**
* Create a new snapshot schedule
*/
async createSchedule(scheduleData) {
try {
const response = await fetch(`${SNAPSHOTS_CONFIG.API_BASE_URL}/api/snapshots/schedules`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(scheduleData)
});
if (response.ok) {
const result = await response.json();
if (result.success) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`â
Schedule created: ${result.schedule_id}`);
}
// Refresh schedules list
await this.loadSchedules();
return result;
} else {
throw new Error(result.error);
}
} else {
const error = await response.json();
throw new Error(error.detail || 'Failed to create schedule');
}
} catch (error) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.error('â Error creating schedule:', error);
}
throw error;
}
}
/**
* Delete a snapshot schedule
*/
async deleteSchedule(scheduleId) {
try {
const response = await fetch(`${SNAPSHOTS_CONFIG.API_BASE_URL}/api/snapshots/schedules/${scheduleId}`, {
method: 'DELETE'
});
if (response.ok) {
const result = await response.json();
if (result.success) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`â
Schedule deleted: ${scheduleId}`);
}
// Refresh schedules list
await this.loadSchedules();
return result;
} else {
throw new Error(result.error);
}
} else {
const error = await response.json();
throw new Error(error.detail || 'Failed to delete schedule');
}
} catch (error) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.error('â Error deleting schedule:', error);
}
throw error;
}
}
/**
* Delete a snapshot
*/
async deleteSnapshot(snapshotNumber, config = 'root') {
try {
const response = await fetch(`${SNAPSHOTS_CONFIG.API_BASE_URL}/api/snapshots/${snapshotNumber}?config=${config}`, {
method: 'DELETE'
});
if (response.ok) {
const result = await response.json();
if (result.success) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`â
Snapshot #${snapshotNumber} deleted`);
}
// Refresh snapshots list
await this.refreshSnapshots();
return result;
} else {
throw new Error(result.error);
}
} else {
const error = await response.json();
throw new Error(error.detail || 'Failed to delete snapshot');
}
} catch (error) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.error('â Error deleting snapshot:', error);
}
throw error;
}
}
/**
* Restore from a snapshot
*/
async restoreSnapshot(snapshotNumber, config = 'root') {
try {
const response = await fetch(`${SNAPSHOTS_CONFIG.API_BASE_URL}/api/snapshots/${snapshotNumber}/restore?config=${config}`, {
method: 'POST'
});
if (response.ok) {
const result = await response.json();
if (result.success) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log(`â
Snapshot #${snapshotNumber} restore initiated`);
}
return result;
} else {
throw new Error(result.error);
}
} else {
const error = await response.json();
throw new Error(error.detail || 'Failed to restore snapshot');
}
} catch (error) {
if (SNAPSHOTS_CONFIG.DEBUG) {
console.error('â Error restoring snapshot:', error);
}
throw error;
}
}
/**
* Cleanup resources
*/
destroy() {
this.stopAutoRefresh();
if (SNAPSHOTS_CONFIG.DEBUG) {
console.log('đī¸ Snapshots Management module destroyed');
}
}
}
// Create global snapshots management instance
window.PersistenceSnapshots = new PersistenceSnapshots();
// Backward compatibility
window.Snapshots = window.PersistenceSnapshots;
// Export for module systems
if (typeof module !== 'undefined' && module.exports) {
module.exports = { PersistenceSnapshots, SNAPSHOTS_CONFIG };
}
console.log('â
PersistenceOS Snapshots Management Module v2 loaded successfully');