File storage-v2.js of Package PersistenceOS
/**
* PersistenceOS Storage Management Module v2
* Storage pool and filesystem management for PersistenceOS
* Extracted for Phase 4 modular architecture
*/
/**
* Storage Configuration
*/
const STORAGE_CONFIG = {
REFRESH_INTERVAL: 20000, // 20 seconds
API_ENDPOINTS: {
POOLS: '/api/storage/pools',
CREATE_POOL: '/api/storage/pools/create',
DELETE_POOL: '/api/storage/pools/{id}/delete',
DATASETS: '/api/storage/info',
SNAPSHOTS: '/api/snapshots'
},
DEBUG: true
};
/**
* Storage Management System
* Handles all storage operations and state management
*/
class PersistenceStorage {
constructor() {
this.pools = [];
this.datasets = [];
this.isRefreshing = false;
this.refreshInterval = null;
this.init();
}
init() {
if (STORAGE_CONFIG.DEBUG) {
console.log('๐พ PersistenceOS Storage Management Module v2 initialized');
}
// Start auto-refresh
this.startAutoRefresh();
}
/**
* Get all storage pools
*/
getPools() {
return [...this.pools];
}
/**
* Get pool by ID
*/
getPool(id) {
return this.pools.find(pool => pool.id === id);
}
/**
* Get all datasets
*/
getDatasets() {
return [...this.datasets];
}
/**
* Get datasets for a specific pool
*/
getPoolDatasets(poolName) {
return this.datasets.filter(ds => ds.pool === poolName);
}
/**
* Get storage statistics
*/
getStorageStats() {
const healthy = this.pools.filter(pool => pool.status === 'healthy').length;
const degraded = this.pools.filter(pool => pool.status === 'degraded').length;
const total = this.pools.length;
return { healthy, degraded, total };
}
/**
* Create a new storage pool - Hybrid Dynamic Popup Approach
* Opens a separate popup window with comprehensive pool creation interface
*/
async createPool() {
console.log('๐ Opening Create Storage Pool - Hybrid Dynamic Popup');
console.log('๐ Debug: createPool method called successfully');
try {
// Remove any existing popup first
const existingPopup = document.getElementById('createPoolPopup');
if (existingPopup) {
existingPopup.remove();
}
// Get available devices
let availableDevices = [];
try {
const devicesResponse = await fetch('/api/storage/devices');
if (devicesResponse.ok) {
const devicesData = await devicesResponse.json();
availableDevices = devicesData.devices || [];
}
console.log('๐ Available devices:', availableDevices);
} catch (error) {
console.error('โ Failed to load available devices:', error);
availableDevices = []; // Continue with manual entry
}
// Create and show the popup
this.createDynamicPoolPopup(availableDevices);
} catch (error) {
console.error('โ Failed to open create pool popup:', error);
this.showNotification('โ Failed to open create pool dialog: ' + error.message, 'error');
}
}
/**
* Create Dynamic Pool Creation Popup - Hybrid Approach
* @param {Array} availableDevices - List of available storage devices
*/
createDynamicPoolPopup(availableDevices) {
console.log('๐ฏ Creating dynamic pool creation popup');
// Create popup overlay
const popup = document.createElement('div');
popup.id = 'createPoolPopup';
popup.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
overflow: auto;
padding: 20px;
box-sizing: border-box;
`;
// Create popup window
const window = document.createElement('div');
window.style.cssText = `
background: #2c3e50;
border-radius: 12px;
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
animation: slideIn 0.3s ease-out;
`;
// Add animation styles if not exists
if (!document.getElementById('popupStyles')) {
const style = document.createElement('style');
style.id = 'popupStyles';
style.textContent = `
@keyframes slideIn {
from { opacity: 0; transform: scale(0.9) translateY(-20px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
`;
document.head.appendChild(style);
}
// Create comprehensive popup content with all PersistenceOS storage features
window.innerHTML = `
<div style="
background: linear-gradient(135deg, #3498db, #2980b9);
padding: 1.5rem 2rem;
border-radius: 12px 12px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
">
<h3 style="margin: 0; color: white; font-size: 1.3rem;">
<i class="fas fa-plus"></i> Create Advanced Storage Pool - VM Hypervisor
</h3>
<button onclick="closeCreatePoolPopup()" style="
background: rgba(255,255,255,0.2);
border: none;
color: white;
padding: 0.5rem;
border-radius: 50%;
cursor: pointer;
font-size: 1.2rem;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s ease;
" onmouseover="this.style.background='rgba(255,255,255,0.3)'"
onmouseout="this.style.background='rgba(255,255,255,0.2)'">
<i class="fas fa-times"></i>
</button>
</div>
<div style="padding: 2rem;">
<form id="createPoolForm">
<!-- Basic Configuration -->
<div style="margin-bottom: 2rem; padding: 1.5rem; background: rgba(52, 73, 94, 0.3); border-radius: 8px; border-left: 4px solid #3498db;">
<h4 style="color: #3498db; margin-bottom: 1rem;">
<i class="fas fa-cog"></i> Basic Configuration
</h4>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Pool Name: <span style="color: #e74c3c;">*</span>
</label>
<input type="text" id="poolName" required
placeholder="e.g., vm-storage, data-pool"
style="width: 100%; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem;">
<small style="color: #95a5a6; font-size: 0.875rem; margin-top: 0.25rem; display: block;">
Pool name for VM storage (alphanumeric, hyphens, underscores only)
</small>
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Filesystem Type: <span style="color: #e74c3c;">*</span>
</label>
<select id="poolType" required onchange="updateFilesystemOptions()" style="width: 100%; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem;">
<option value="btrfs">BTRFS (Snapshots, Compression, RAID)</option>
<option value="xfs">XFS (High Performance, Quotas)</option>
<option value="ext4">EXT4 (Compatible, Stable)</option>
<option value="lvm">LVM (Logical Volumes, Thin Provisioning)</option>
</select>
<div id="filesystemInfo" style="margin-top: 0.5rem; padding: 0.75rem; background: rgba(46, 204, 113, 0.1); border-radius: 4px; border-left: 3px solid #2ecc71;">
<div id="btrfsInfo">
<strong style="color: #2ecc71;">BTRFS Features (btrfsprogs):</strong>
<ul style="margin: 0.5rem 0; padding-left: 1.5rem; color: #95a5a6; font-size: 0.875rem;">
<li>โ
Native snapshots for VM backups (snapper integration)</li>
<li>โ
Copy-on-write for efficient VM cloning</li>
<li>โ
Built-in compression (zstd, lzo, zlib)</li>
<li>โ
Software RAID (RAID0, RAID1, RAID10)</li>
<li>โ
Self-healing with checksums</li>
<li>โ
Dynamic resizing and subvolumes</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Device Selection -->
<div style="margin-bottom: 2rem; padding: 1.5rem; background: rgba(52, 73, 94, 0.3); border-radius: 8px; border-left: 4px solid #3498db;">
<h4 style="color: #3498db; margin-bottom: 1rem;">
<i class="fas fa-hdd"></i> Storage Device
</h4>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Device Path: <span style="color: #e74c3c;">*</span>
</label>
<div style="display: flex; gap: 0.5rem;">
<input type="text" id="devicePath" required
placeholder="/dev/sdb, /dev/nvme0n1, /dev/sdc1"
style="flex: 1; padding: 0.75rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; font-size: 1rem;">
<button type="button" onclick="detectAvailableDevices()" style="
background: #3498db;
color: white;
border: none;
padding: 0.75rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.875rem;
white-space: nowrap;
transition: background 0.2s ease;
" onmouseover="this.style.background='#2980b9'" onmouseout="this.style.background='#3498db'">
<i class="fas fa-search"></i> Detect
</button>
</div>
<small style="color: #95a5a6; font-size: 0.875rem; margin-top: 0.25rem; display: block;">
Enter device path or click "Detect" to scan for available storage devices
</small>
<div id="deviceList" style="margin-top: 0.5rem; display: none;"></div>
</div>
</div>
<!-- Advanced Storage Options -->
<div id="advancedOptions" style="margin-bottom: 2rem; padding: 1.5rem; background: rgba(142, 68, 173, 0.2); border-radius: 8px; border-left: 4px solid #8e44ad;">
<h4 style="color: #8e44ad; margin-bottom: 1rem;">
<i class="fas fa-sliders-h"></i> Advanced Storage Options
</h4>
<!-- BTRFS Specific Options -->
<div id="btrfsOptions" style="display: block;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem;">
<div>
<label style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
<input type="checkbox" id="enableCompression" checked>
<span style="font-weight: bold;">Enable Compression</span>
</label>
<select id="compressionType" style="width: 100%; padding: 0.5rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white;">
<option value="zstd">ZSTD (Recommended)</option>
<option value="lzo">LZO (Fast)</option>
<option value="zlib">ZLIB (High Compression)</option>
</select>
</div>
<div>
<label style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
<input type="checkbox" id="enableRAID">
<span style="font-weight: bold;">Enable RAID</span>
</label>
<select id="raidLevel" disabled style="width: 100%; padding: 0.5rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white;">
<option value="single">Single (No RAID)</option>
<option value="raid0">RAID0 (Striping)</option>
<option value="raid1">RAID1 (Mirroring)</option>
<option value="raid10">RAID10 (Stripe+Mirror)</option>
</select>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
<label style="display: flex; align-items: center; gap: 0.5rem;">
<input type="checkbox" id="enableSnapshots" checked>
<span style="font-weight: bold;">Enable Snapper Integration</span>
</label>
<label style="display: flex; align-items: center; gap: 0.5rem;">
<input type="checkbox" id="enableQuotas">
<span style="font-weight: bold;">Enable Quotas</span>
</label>
</div>
</div>
<!-- LVM Specific Options -->
<div id="lvmOptions" style="display: none;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem;">
<div>
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">Volume Group Name:</label>
<input type="text" id="vgName" placeholder="vg-vm-storage" style="width: 100%; padding: 0.5rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white;">
</div>
<div>
<label style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
<input type="checkbox" id="enableThinProvisioning">
<span style="font-weight: bold;">Thin Provisioning</span>
</label>
<input type="text" id="thinPoolSize" placeholder="80%" disabled style="width: 100%; padding: 0.5rem; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white;">
</div>
</div>
</div>
<!-- VM Hypervisor Options -->
<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid rgba(255,255,255,0.1);">
<h5 style="color: #3498db; margin-bottom: 0.5rem;">VM Hypervisor Integration:</h5>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
<label style="display: flex; align-items: center; gap: 0.5rem;">
<input type="checkbox" id="createVMDirs" checked>
<span>Create VM directory structure (/vms, /iso, /templates)</span>
</label>
<label style="display: flex; align-items: center; gap: 0.5rem;">
<input type="checkbox" id="enableLibvirtPool" checked>
<span>Register with libvirt storage pool</span>
</label>
</div>
</div>
</div>
<!-- Action Buttons -->
<div style="display: flex; gap: 1rem; justify-content: flex-end; padding-top: 1rem; border-top: 1px solid rgba(255,255,255,0.1);">
<button type="button" onclick="closeCreatePoolPopup()" style="
background: #95a5a6;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s ease;
" onmouseover="this.style.background='#7f8c8d'" onmouseout="this.style.background='#95a5a6'">
<i class="fas fa-times"></i> Cancel
</button>
<button type="button" onclick="submitCreatePool()" id="createPoolBtn" style="
background: #27ae60;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s ease;
" onmouseover="this.style.background='#229954'" onmouseout="this.style.background='#27ae60'">
<i class="fas fa-plus"></i> Create Pool
</button>
</div>
</form>
</div>
`;
// Add to popup and show
popup.appendChild(window);
document.body.appendChild(popup);
// Setup event handlers
this.setupPopupEventHandlers();
// Focus on pool name input
setTimeout(() => {
const poolNameInput = document.getElementById('poolName');
if (poolNameInput) poolNameInput.focus();
}, 100);
console.log('โ
Dynamic pool creation popup opened');
}
/**
* Setup event handlers for the popup
*/
setupPopupEventHandlers() {
// Global functions for popup
window.closeCreatePoolPopup = () => {
const popup = document.getElementById('createPoolPopup');
if (popup) {
popup.remove();
}
// Cleanup global functions
delete window.closeCreatePoolPopup;
delete window.submitCreatePool;
delete window.updateFilesystemOptions;
delete window.detectAvailableDevices;
delete window.selectDevice;
};
// Dynamic filesystem options updater
window.updateFilesystemOptions = () => {
const poolType = document.getElementById('poolType').value;
const filesystemInfo = document.getElementById('filesystemInfo');
const btrfsOptions = document.getElementById('btrfsOptions');
const lvmOptions = document.getElementById('lvmOptions');
// Update filesystem information display
let infoHtml = '';
switch (poolType) {
case 'btrfs':
infoHtml = `
<div>
<strong style="color: #2ecc71;">BTRFS Features (btrfsprogs):</strong>
<ul style="margin: 0.5rem 0; padding-left: 1.5rem; color: #95a5a6; font-size: 0.875rem;">
<li>โ
Native snapshots for VM backups (snapper integration)</li>
<li>โ
Copy-on-write for efficient VM cloning</li>
<li>โ
Built-in compression (zstd, lzo, zlib)</li>
<li>โ
Software RAID (RAID0, RAID1, RAID10)</li>
<li>โ
Self-healing with checksums</li>
<li>โ
Dynamic resizing and subvolumes</li>
</ul>
</div>
`;
btrfsOptions.style.display = 'block';
lvmOptions.style.display = 'none';
break;
case 'xfs':
infoHtml = `
<div>
<strong style="color: #f39c12;">XFS Features (xfsprogs):</strong>
<ul style="margin: 0.5rem 0; padding-left: 1.5rem; color: #95a5a6; font-size: 0.875rem;">
<li>โ
High performance for large files and VM images</li>
<li>โ
Excellent for sequential I/O workloads</li>
<li>โ
Online resizing (grow only)</li>
<li>โ
Project quotas for VM disk limits</li>
<li>โ No built-in snapshots (use LVM snapshots)</li>
<li>โ No compression</li>
</ul>
</div>
`;
btrfsOptions.style.display = 'none';
lvmOptions.style.display = 'none';
break;
case 'ext4':
infoHtml = `
<div>
<strong style="color: #3498db;">EXT4 Features (e2fsprogs):</strong>
<ul style="margin: 0.5rem 0; padding-left: 1.5rem; color: #95a5a6; font-size: 0.875rem;">
<li>โ
Mature and stable filesystem</li>
<li>โ
Wide compatibility across Linux distributions</li>
<li>โ
Online resizing (grow and shrink)</li>
<li>โ
Journaling for crash recovery</li>
<li>โ No built-in snapshots (use LVM snapshots)</li>
<li>โ No compression</li>
</ul>
</div>
`;
btrfsOptions.style.display = 'none';
lvmOptions.style.display = 'none';
break;
case 'lvm':
infoHtml = `
<div>
<strong style="color: #8e44ad;">LVM Features (lvm2):</strong>
<ul style="margin: 0.5rem 0; padding-left: 1.5rem; color: #95a5a6; font-size: 0.875rem;">
<li>โ
Logical volume management and flexibility</li>
<li>โ
Thin provisioning for efficient space usage</li>
<li>โ
LVM snapshots for backup and cloning</li>
<li>โ
Dynamic volume resizing</li>
<li>โ
Multiple physical volumes in one group</li>
<li>โ ๏ธ Requires filesystem on top (ext4, xfs, etc.)</li>
</ul>
</div>
`;
btrfsOptions.style.display = 'none';
lvmOptions.style.display = 'block';
break;
}
if (filesystemInfo) {
filesystemInfo.innerHTML = infoHtml;
}
};
// Setup checkbox event handlers
setTimeout(() => {
// RAID checkbox handler
const enableRAID = document.getElementById('enableRAID');
const raidLevel = document.getElementById('raidLevel');
if (enableRAID && raidLevel) {
enableRAID.addEventListener('change', function() {
raidLevel.disabled = !this.checked;
if (!this.checked) {
raidLevel.value = 'single';
}
});
}
// Thin provisioning checkbox handler
const enableThinProvisioning = document.getElementById('enableThinProvisioning');
const thinPoolSize = document.getElementById('thinPoolSize');
if (enableThinProvisioning && thinPoolSize) {
enableThinProvisioning.addEventListener('change', function() {
thinPoolSize.disabled = !this.checked;
if (this.checked) {
thinPoolSize.placeholder = '80%';
} else {
thinPoolSize.value = '';
}
});
}
// Compression checkbox handler
const enableCompression = document.getElementById('enableCompression');
const compressionType = document.getElementById('compressionType');
if (enableCompression && compressionType) {
enableCompression.addEventListener('change', function() {
compressionType.disabled = !this.checked;
});
}
}, 100);
// Device detection function
window.detectAvailableDevices = async () => {
const deviceList = document.getElementById('deviceList');
const detectBtn = event.target;
try {
// Show loading state
detectBtn.disabled = true;
detectBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Scanning...';
// Fetch available devices from API
const response = await fetch('/api/storage/devices');
const data = await response.json();
if (response.ok && data.devices && data.devices.length > 0) {
// Display device list
let deviceHtml = `
<div style="margin-top: 0.5rem; padding: 0.75rem; background: rgba(52, 152, 219, 0.1); border-radius: 4px; border-left: 3px solid #3498db;">
<strong style="color: #3498db;">Available Storage Devices:</strong>
<div style="margin-top: 0.5rem; max-height: 150px; overflow-y: auto;">
`;
data.devices.forEach(device => {
const sizeGB = device.size ? Math.round(device.size / (1024*1024*1024)) : 'Unknown';
const deviceType = device.type || 'disk';
const model = device.model || 'Unknown Model';
deviceHtml += `
<div style="
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
margin: 0.25rem 0;
background: rgba(255,255,255,0.05);
border-radius: 4px;
cursor: pointer;
transition: background 0.2s ease;
" onclick="selectDevice('${device.path}')"
onmouseover="this.style.background='rgba(255,255,255,0.1)'"
onmouseout="this.style.background='rgba(255,255,255,0.05)'">
<div>
<strong style="color: white;">${device.path}</strong>
<div style="font-size: 0.8rem; color: #95a5a6;">
${model} โข ${deviceType.toUpperCase()} โข ${sizeGB}GB
</div>
</div>
<i class="fas fa-mouse-pointer" style="color: #3498db;"></i>
</div>
`;
});
deviceHtml += `
</div>
<small style="color: #95a5a6; font-size: 0.8rem;">Click on a device to select it</small>
</div>
`;
deviceList.innerHTML = deviceHtml;
deviceList.style.display = 'block';
} else {
deviceList.innerHTML = `
<div style="margin-top: 0.5rem; padding: 0.75rem; background: rgba(231, 76, 60, 0.1); border-radius: 4px; border-left: 3px solid #e74c3c;">
<strong style="color: #e74c3c;">No available devices found</strong>
<div style="font-size: 0.875rem; color: #95a5a6; margin-top: 0.25rem;">
Please ensure storage devices are connected and not in use.
</div>
</div>
`;
deviceList.style.display = 'block';
}
} catch (error) {
console.error('Device detection failed:', error);
deviceList.innerHTML = `
<div style="margin-top: 0.5rem; padding: 0.75rem; background: rgba(231, 76, 60, 0.1); border-radius: 4px; border-left: 3px solid #e74c3c;">
<strong style="color: #e74c3c;">Device detection failed</strong>
<div style="font-size: 0.875rem; color: #95a5a6; margin-top: 0.25rem;">
${error.message || 'Unable to scan for storage devices'}
</div>
</div>
`;
deviceList.style.display = 'block';
} finally {
// Restore button state
detectBtn.disabled = false;
detectBtn.innerHTML = '<i class="fas fa-search"></i> Detect';
}
};
// Device selection helper
window.selectDevice = (devicePath) => {
const deviceInput = document.getElementById('devicePath');
if (deviceInput) {
deviceInput.value = devicePath;
deviceInput.focus();
// Hide device list after selection
const deviceList = document.getElementById('deviceList');
if (deviceList) {
setTimeout(() => {
deviceList.style.display = 'none';
}, 1000);
}
}
};
window.submitCreatePool = async () => {
const createBtn = document.getElementById('createPoolBtn');
const originalText = createBtn.innerHTML;
try {
// Disable button and show loading
createBtn.disabled = true;
createBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Creating...';
// Get form data
const poolName = document.getElementById('poolName').value.trim();
const poolType = document.getElementById('poolType').value;
const devicePath = document.getElementById('devicePath').value.trim();
// Get advanced options
const enableCompression = document.getElementById('enableCompression')?.checked || false;
const compressionType = document.getElementById('compressionType')?.value || 'zstd';
const enableRAID = document.getElementById('enableRAID')?.checked || false;
const raidLevel = document.getElementById('raidLevel')?.value || 'single';
const enableSnapshots = document.getElementById('enableSnapshots')?.checked || false;
const enableQuotas = document.getElementById('enableQuotas')?.checked || false;
const vgName = document.getElementById('vgName')?.value?.trim() || `vg-${poolName}`;
const enableThinProvisioning = document.getElementById('enableThinProvisioning')?.checked || false;
const thinPoolSize = document.getElementById('thinPoolSize')?.value?.trim() || '80%';
const createVMDirs = document.getElementById('createVMDirs')?.checked || false;
const enableLibvirtPool = document.getElementById('enableLibvirtPool')?.checked || false;
// Basic validation
if (!poolName || !devicePath) {
throw new Error('Pool name and device path are required');
}
if (!/^[a-zA-Z0-9_-]+$/.test(poolName)) {
throw new Error('Pool name can only contain letters, numbers, hyphens, and underscores');
}
if (!devicePath.startsWith('/dev/')) {
throw new Error('Device path must start with /dev/');
}
// Show progress
this.showNotification(`๐ Creating advanced storage pool "${poolName}" with ${poolType.toUpperCase()}...`, 'info');
// Build comprehensive options object
const poolOptions = {
// VM Hypervisor Integration
create_vm_directories: createVMDirs,
enable_libvirt_pool: enableLibvirtPool,
// BTRFS specific options
enable_compression: enableCompression && poolType === 'btrfs',
compression_type: compressionType,
enable_raid: enableRAID && poolType === 'btrfs',
raid_level: raidLevel,
enable_snapshots: enableSnapshots && poolType === 'btrfs',
enable_quotas: enableQuotas,
// LVM specific options
volume_group_name: vgName,
enable_thin_provisioning: enableThinProvisioning && poolType === 'lvm',
thin_pool_size: thinPoolSize,
// General options
mount_point: `/mnt/${poolName}`,
filesystem_type: poolType
};
// Submit to API
const response = await fetch('/api/storage/pools/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: poolName,
filesystem_type: poolType,
device_path: devicePath,
mount_point: `/mnt/${poolName}`,
options: poolOptions
})
});
if (response.ok) {
const result = await response.json();
if (result.success) {
this.showNotification(`โ
Storage pool "${poolName}" created successfully!`, 'success');
window.closeCreatePoolPopup();
await this.refreshStorage();
} else {
throw new Error(result.message || 'Pool creation failed');
}
} else {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `HTTP ${response.status}: ${response.statusText}`);
}
} catch (error) {
console.error('โ Failed to create storage pool:', error);
this.showNotification(`โ Failed to create pool: ${error.message}`, 'error');
} finally {
// Re-enable button
createBtn.disabled = false;
createBtn.innerHTML = originalText;
}
};
// Close popup when clicking outside
const popup = document.getElementById('createPoolPopup');
if (popup) {
popup.addEventListener('click', (e) => {
if (e.target === popup) {
window.closeCreatePoolPopup();
}
});
}
}
/**
* Delete a storage pool
*/
async deletePool(id) {
if (STORAGE_CONFIG.DEBUG) {
console.log(`๐๏ธ Deleting storage pool: ${id}`);
}
const poolIndex = this.pools.findIndex(pool => pool.id === id);
if (poolIndex === -1) {
throw new Error(`Pool ${id} not found`);
}
const pool = this.pools[poolIndex];
// Check if pool has datasets
const poolDatasets = this.getPoolDatasets(pool.name);
if (poolDatasets.length > 0) {
throw new Error(`Cannot delete pool ${pool.name}: contains ${poolDatasets.length} datasets`);
}
// Simulate API call
await this.simulateAPICall();
this.pools.splice(poolIndex, 1);
this.notifyStorageUpdate();
if (STORAGE_CONFIG.DEBUG) {
console.log(`โ
Storage pool ${pool.name} deleted successfully`);
}
}
/**
* Refresh storage information
*/
async refreshStorage() {
if (this.isRefreshing) return;
this.isRefreshing = true;
if (STORAGE_CONFIG.DEBUG) {
console.log('๐ Refreshing storage information...');
}
try {
// Simulate API call with random updates
await this.simulateStorageRefresh();
if (STORAGE_CONFIG.DEBUG) {
console.log('โ
Storage information refreshed successfully');
}
this.notifyStorageUpdate();
} catch (error) {
if (STORAGE_CONFIG.DEBUG) {
console.error('โ Storage refresh failed:', error);
}
throw error;
} finally {
this.isRefreshing = false;
}
}
/**
* Simulate API call delay
*/
async simulateAPICall() {
return new Promise((resolve) => {
setTimeout(resolve, 1500 + Math.random() * 1000);
});
}
/**
* Simulate storage refresh with random updates
*/
async simulateStorageRefresh() {
return new Promise((resolve) => {
setTimeout(() => {
// Update pool usage with slight variations
this.pools.forEach(pool => {
const variation = (Math.random() - 0.5) * 2; // -1 to +1
pool.usage = Math.max(0, Math.min(100, pool.usage + variation));
// Update used/available based on usage
const totalGB = parseInt(pool.total);
const usedGB = Math.floor(totalGB * pool.usage / 100);
const availableGB = totalGB - usedGB;
pool.used = `${usedGB} GB`;
pool.available = `${availableGB} GB`;
});
resolve();
}, 1000);
});
}
/**
* Start auto-refresh timer
*/
startAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
this.refreshInterval = setInterval(() => {
this.refreshStorage().catch(error => {
if (STORAGE_CONFIG.DEBUG) {
console.warn('โ ๏ธ Storage auto-refresh failed:', error);
}
});
}, STORAGE_CONFIG.REFRESH_INTERVAL);
if (STORAGE_CONFIG.DEBUG) {
console.log('โฐ Storage auto-refresh started (20s interval)');
}
}
/**
* Stop auto-refresh timer
*/
stopAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = null;
if (STORAGE_CONFIG.DEBUG) {
console.log('โน๏ธ Storage auto-refresh stopped');
}
}
}
/**
* Notify storage update listeners
*/
notifyStorageUpdate() {
// Dispatch custom event for Vue reactivity
window.dispatchEvent(new CustomEvent('storageDataUpdate', {
detail: {
pools: this.getPools(),
datasets: this.getDatasets(),
stats: this.getStorageStats()
}
}));
}
/**
* Cleanup resources
*/
destroy() {
this.stopAutoRefresh();
if (STORAGE_CONFIG.DEBUG) {
console.log('๐๏ธ Storage Management module destroyed');
}
}
}
// Create global storage management instance
window.PersistenceStorage = new PersistenceStorage();
// Backward compatibility
window.Storage = window.PersistenceStorage;
// Export enhanced functions globally for Vue.js integration
window.createPool = () => {
console.log('๐ Global createPool function called');
console.log('๐ PersistenceStorage instance:', window.PersistenceStorage);
console.log('๐ createPool method exists:', typeof window.PersistenceStorage.createPool);
return window.PersistenceStorage.createPool();
};
window.refreshStorage = () => window.PersistenceStorage.refreshStorage();
window.importPool = (poolData) => window.PersistenceStorage.importPool(poolData);
window.managePool = (poolId) => window.PersistenceStorage.managePool(poolId);
window.snapshotPool = (poolId) => window.PersistenceStorage.snapshotPool(poolId);
// Add a simple test function
window.testStorageModule = () => {
console.log('๐งช Testing Storage Module...');
console.log('โ
PersistenceStorage instance:', window.PersistenceStorage);
console.log('โ
createPool method type:', typeof window.PersistenceStorage.createPool);
console.log('โ
Available methods:', Object.getOwnPropertyNames(Object.getPrototypeOf(window.PersistenceStorage)));
return 'Storage module test complete - check console for details';
};
// Add direct popup test function
window.showCreatePoolPopup = () => {
console.log('๐งช Direct popup test called');
try {
return window.PersistenceStorage.createPool();
} catch (error) {
console.error('โ Direct popup test failed:', error);
return error;
}
};
// Export for module systems
if (typeof module !== 'undefined' && module.exports) {
module.exports = { PersistenceStorage, STORAGE_CONFIG };
}
console.log('โ
PersistenceOS Storage Management Module v2 loaded successfully');
console.log('โ
Enhanced storage functions exported globally for Vue.js integration');