import React, { useState, useEffect } from 'react'; import { Sidebar } from './components/Sidebar.js'; import { Topbar } from './components/Topbar.js'; import { RebuildModal } from './components/RebuildModal.js'; import { OverviewView } from './views/OverviewView.js'; import { StorageView } from './views/StorageView.js'; import { SharesView } from './views/SharesView.js'; import { AppsView } from './views/AppsView.js'; import { GitOpsView } from './views/GitOpsView.js'; import { LogsView } from './views/LogsView.js'; import { WizardView } from './views/WizardView.js'; import { api } from './api/client.js'; import type { SystemStatus, ZfsPool, ZfsDataset, ZfsSnapshot, UnimportedPool, SmbShare, NfsExport, AppCatalogItem, InstalledApp, GitOpsStatus, GitCommit, LogEntry, AppRuntime, } from './types/index.js'; export const App: React.FC = () => { const getInitialTab = () => { if (typeof window !== 'undefined') { const hash = window.location.hash.replace('#', ''); if (hash) return hash; const params = new URLSearchParams(window.location.search); const tab = params.get('tab'); if (tab) return tab; } return 'overview'; }; const [currentTab, setCurrentTab] = useState(getInitialTab); const [showRebuildModal, setShowRebuildModal] = useState(false); useEffect(() => { const onHashChange = () => { const hash = window.location.hash.replace('#', ''); if (hash) setCurrentTab(hash); }; window.addEventListener('hashchange', onHashChange); return () => window.removeEventListener('hashchange', onHashChange); }, []); const selectTab = (tab: string) => { setCurrentTab(tab); if (typeof window !== 'undefined') { window.location.hash = tab; } }; // Application state const [status, setStatus] = useState(null); const [pools, setPools] = useState([]); const [datasets, setDatasets] = useState([]); const [snapshots, setSnapshots] = useState([]); const [unimportedPools, setUnimportedPools] = useState([]); const [smbShares, setSmbShares] = useState>({}); const [nfsExports, setNfsExports] = useState([]); const [catalog, setCatalog] = useState([]); const [installedApps, setInstalledApps] = useState([]); const [gitops, setGitops] = useState(null); const [commits, setCommits] = useState([]); const [logs, setLogs] = useState([]); const [selectedLogUnit, setSelectedLogUnit] = useState('all'); const loadData = async () => { try { const [ statusRes, poolsRes, datasetsRes, snapshotsRes, unimportedRes, sharesRes, exportsRes, catalogRes, appsRes, gitopsRes, commitsRes, logsRes, ] = await Promise.all([ api.getStatus(), api.getPools(), api.getDatasets(), api.getSnapshots(), api.getUnimportedPools(), api.getSmbShares(), api.getNfsExports(), api.getAppCatalog(), api.getInstalledApps(), api.getGitOpsStatus(), api.getGitCommits(), api.getLogs(), ]); if (statusRes) setStatus(statusRes.status); if (poolsRes) setPools(poolsRes.pools); if (datasetsRes) setDatasets(datasetsRes.datasets); if (snapshotsRes) setSnapshots(snapshotsRes.snapshots); if (unimportedRes) setUnimportedPools(unimportedRes.pools); if (sharesRes) setSmbShares(sharesRes.shares); if (exportsRes) setNfsExports(exportsRes.exports); if (catalogRes) setCatalog(catalogRes.catalog); if (appsRes) setInstalledApps(appsRes.apps); if (gitopsRes) setGitops(gitopsRes.status); if (commitsRes) setCommits(commitsRes.commits); if (logsRes) setLogs(logsRes.logs); } catch (err) { console.error('Error loading NaxOS state:', err); } }; useEffect(() => { loadData(); const interval = setInterval(loadData, 8000); return () => clearInterval(interval); }, []); // Handlers const handleImportPool = async (poolName: string) => { await api.importPool({ poolName, force: true, noMount: true }); await loadData(); }; const handleScrubPool = async (poolName: string) => { await api.scrubPool(poolName, 'start'); await loadData(); }; const handleCreateSnapshot = async (dataset: string, tag: string) => { await api.createSnapshot(dataset, tag); await loadData(); }; const handleRollbackSnapshot = async (snapshotName: string) => { await api.rollbackSnapshot(snapshotName); await loadData(); }; const handleSaveSmbShare = async (share: SmbShare) => { await api.saveSmbShare(share); await loadData(); }; const handleDeleteSmbShare = async (name: string) => { await api.deleteSmbShare(name); await loadData(); }; const handleInstallApp = async (appId: string, runtime: AppRuntime) => { await api.installApp({ appId, runtime }); await loadData(); }; const handleSetAppStatus = async (appId: string, appStatus: 'running' | 'stopped') => { await api.setAppStatus(appId, appStatus); await loadData(); }; const handleUninstallApp = async (appId: string) => { await api.uninstallApp(appId); await loadData(); }; const handleRollbackGitOps = async (commitSha: string) => { await api.rollbackCommit(commitSha); await loadData(); }; const handleReboot = async () => { if (confirm('Are you sure you want to reboot the NaxOS storage appliance?')) { await api.reboot(); alert('Reboot signal sent. The appliance will restart momentarily.'); } }; return (
setShowRebuildModal(true)} onReboot={handleReboot} />
{(currentTab === 'overview' || currentTab === 'analytics') && ( )} {currentTab === 'storage' && ( )} {currentTab === 'shares' && ( )} {currentTab === 'apps' && ( )} {currentTab === 'gitops' && ( setShowRebuildModal(true)} /> )} {currentTab === 'logs' && ( )} {currentTab === 'wizard' && }
setShowRebuildModal(false)} onComplete={loadData} />
); };