feat(ui): implement modern NaxOS Web Dashboard with Perses analytics and ZFS migration wizard
CI & Build NaxOS Web Dashboard / build (push) Failing after 1m29s
CI & Build NaxOS Web Dashboard / build (push) Failing after 1m29s
This commit is contained in:
+246
@@ -0,0 +1,246 @@
|
||||
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 { AnalyticsView } from './views/AnalyticsView.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 [currentTab, setCurrentTab] = useState('overview');
|
||||
const [showRebuildModal, setShowRebuildModal] = useState(false);
|
||||
|
||||
// Application state
|
||||
const [status, setStatus] = useState<SystemStatus | null>(null);
|
||||
const [pools, setPools] = useState<ZfsPool[]>([]);
|
||||
const [datasets, setDatasets] = useState<ZfsDataset[]>([]);
|
||||
const [snapshots, setSnapshots] = useState<ZfsSnapshot[]>([]);
|
||||
const [unimportedPools, setUnimportedPools] = useState<UnimportedPool[]>([]);
|
||||
const [smbShares, setSmbShares] = useState<Record<string, SmbShare>>({});
|
||||
const [nfsExports, setNfsExports] = useState<NfsExport[]>([]);
|
||||
const [catalog, setCatalog] = useState<AppCatalogItem[]>([]);
|
||||
const [installedApps, setInstalledApps] = useState<InstalledApp[]>([]);
|
||||
const [gitops, setGitops] = useState<GitOpsStatus | null>(null);
|
||||
const [commits, setCommits] = useState<GitCommit[]>([]);
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
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 (
|
||||
<div className="flex h-screen w-screen overflow-hidden bg-slate-950 text-slate-100 font-sans">
|
||||
<Sidebar currentTab={currentTab} onSelectTab={setCurrentTab} />
|
||||
|
||||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
<Topbar
|
||||
status={status}
|
||||
gitops={gitops}
|
||||
onTriggerRebuild={() => setShowRebuildModal(true)}
|
||||
onReboot={handleReboot}
|
||||
/>
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-8">
|
||||
{currentTab === 'overview' && (
|
||||
<OverviewView
|
||||
status={status}
|
||||
pools={pools}
|
||||
apps={installedApps}
|
||||
onNavigate={setCurrentTab}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentTab === 'storage' && (
|
||||
<StorageView
|
||||
pools={pools}
|
||||
datasets={datasets}
|
||||
snapshots={snapshots}
|
||||
unimportedPools={unimportedPools}
|
||||
onRefresh={loadData}
|
||||
onImportPool={handleImportPool}
|
||||
onScrubPool={handleScrubPool}
|
||||
onCreateSnapshot={handleCreateSnapshot}
|
||||
onRollbackSnapshot={handleRollbackSnapshot}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentTab === 'shares' && (
|
||||
<SharesView
|
||||
smbShares={smbShares}
|
||||
nfsExports={nfsExports}
|
||||
onSaveSmbShare={handleSaveSmbShare}
|
||||
onDeleteSmbShare={handleDeleteSmbShare}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentTab === 'apps' && (
|
||||
<AppsView
|
||||
catalog={catalog}
|
||||
installedApps={installedApps}
|
||||
onInstallApp={handleInstallApp}
|
||||
onSetStatus={handleSetAppStatus}
|
||||
onUninstall={handleUninstallApp}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentTab === 'analytics' && <AnalyticsView />}
|
||||
|
||||
{currentTab === 'gitops' && (
|
||||
<GitOpsView
|
||||
status={gitops}
|
||||
commits={commits}
|
||||
onRollback={handleRollbackGitOps}
|
||||
onTriggerRebuild={() => setShowRebuildModal(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentTab === 'logs' && (
|
||||
<LogsView
|
||||
logs={logs}
|
||||
selectedUnit={selectedLogUnit}
|
||||
onSelectUnit={setSelectedLogUnit}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentTab === 'wizard' && <WizardView />}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<RebuildModal
|
||||
isOpen={showRebuildModal}
|
||||
onClose={() => setShowRebuildModal(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user