diff --git a/webui/src/App.jsx b/webui/src/App.jsx
index bc08b5e..bb56281 100644
--- a/webui/src/App.jsx
+++ b/webui/src/App.jsx
@@ -6,7 +6,6 @@ import Root from "@/common/layouts/Root.jsx";
import UserManagement from "@/pages/UserManagement";
import SystemSettings from "@/pages/SystemSettings";
import Machines from "@/pages/Machines";
-import MachineDetails from "@/pages/MachineDetails";
import "@fontsource/plus-jakarta-sans/300.css";
import "@fontsource/plus-jakarta-sans/400.css";
import "@fontsource/plus-jakarta-sans/600.css";
@@ -25,7 +24,6 @@ const App = () => {
{path: "/", element: },
{path: "/dashboard", element: },
{path: "/machines", element: },
- {path: "/machines/:id", element: },
{path: "/servers", element: },
{path: "/settings", element: },
{path: "/admin/users", element: },
diff --git a/webui/src/pages/MachineDetails/MachineDetails.jsx b/webui/src/pages/MachineDetails/MachineDetails.jsx
deleted file mode 100644
index 7a7b79c..0000000
--- a/webui/src/pages/MachineDetails/MachineDetails.jsx
+++ /dev/null
@@ -1,416 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { useParams, useNavigate } from 'react-router-dom';
-import { getRequest } from '@/common/utils/RequestUtil.js';
-import { useToast } from '@/common/contexts/ToastContext.jsx';
-import Card, { CardHeader, CardBody } from '@/common/components/Card';
-import Grid from '@/common/components/Grid';
-import LoadingSpinner from '@/common/components/LoadingSpinner';
-import EmptyState from '@/common/components/EmptyState';
-import PageHeader from '@/common/components/PageHeader';
-import DetailItem, { DetailList } from '@/common/components/DetailItem';
-import Badge from '@/common/components/Badge';
-import Button from '@/common/components/Button';
-import {
- ArrowLeft,
- Camera,
- HardDrive,
- Folder,
- Calendar,
- Hash,
- Database,
- Devices,
- Eye,
- ArrowCircleLeft
-} from '@phosphor-icons/react';
-import './styles.sass';
-
-export const MachineDetails = () => {
- const { id } = useParams();
- const navigate = useNavigate();
- const toast = useToast();
- const [machine, setMachine] = useState(null);
- const [snapshots, setSnapshots] = useState([]);
- const [loading, setLoading] = useState(true);
- const [selectedSnapshot, setSelectedSnapshot] = useState(null);
- const [snapshotDetails, setSnapshotDetails] = useState(null);
- const [loadingDetails, setLoadingDetails] = useState(false);
-
- useEffect(() => {
- if (id) {
- fetchMachineData();
- }
- }, [id]);
-
- const fetchMachineData = async () => {
- try {
- setLoading(true);
-
- // Fetch machine info and snapshots in parallel
- const [machineResponse, snapshotsResponse] = await Promise.all([
- getRequest(`machines/${id}`),
- getRequest(`machines/${id}/snapshots`)
- ]);
-
- setMachine(machineResponse);
- setSnapshots(snapshotsResponse);
- } catch (error) {
- console.error('Failed to fetch machine data:', error);
- toast.error('Failed to load machine details');
- } finally {
- setLoading(false);
- }
- };
-
- const fetchSnapshotDetails = async (snapshotId) => {
- try {
- setLoadingDetails(true);
- const details = await getRequest(`machines/${id}/snapshots/${snapshotId}`);
- setSnapshotDetails(details);
- setSelectedSnapshot(snapshotId);
- } catch (error) {
- console.error('Failed to fetch snapshot details:', error);
- toast.error('Failed to load snapshot details');
- } finally {
- setLoadingDetails(false);
- }
- };
-
- const backToSnapshots = () => {
- setSelectedSnapshot(null);
- setSnapshotDetails(null);
- };
-
- const formatBytes = (bytes) => {
- if (!bytes) return '0 B';
- const k = 1024;
- const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
- };
-
- const formatDate = (dateString) => {
- if (!dateString || dateString === 'Unknown') return 'Unknown';
- try {
- // Handle both "2025-09-09 20:19:48" and "2025-09-09 20:19:48 UTC" formats
- const cleanDate = dateString.replace(' UTC', '');
- const date = new Date(cleanDate);
- if (isNaN(date.getTime())) {
- return dateString; // Return original if parsing fails
- }
- return date.toLocaleString('en-US', {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit'
- });
- } catch {
- return dateString;
- }
- };
-
- const formatLBA = (lba) => {
- if (!lba && lba !== 0) return '0';
- return lba.toLocaleString();
- };
-
- const getFsTypeColor = (fsType) => {
- switch (fsType?.toLowerCase()) {
- case 'ext':
- case 'ext4':
- case 'ext3':
- case 'ext2':
- return 'success';
- case 'ntfs':
- return 'info';
- case 'fat32':
- case 'fat':
- return 'warning';
- case 'xfs':
- return 'info';
- case 'btrfs':
- return 'success';
- default:
- return 'secondary';
- }
- };
-
- const truncateHash = (hash, length = 16) => {
- if (!hash) return 'Unknown';
- return hash.length > length ? `${hash.substring(0, length)}...` : hash;
- };
-
- if (loading) {
- return (
-
-
navigate('/machines')}>
-
- Back to Machines
-
- }
- />
-
-
- );
- }
-
- if (!machine) {
- return (
-
-
navigate('/machines')}>
-
- Back to Machines
-
- }
- />
- }
- title="Machine Not Found"
- subtitle="This machine may have been deleted or you don't have access to it."
- />
-
- );
- }
-
- return (
-
-
-
- Back to Snapshots
-
- ) : (
- navigate('/machines')}>
-
- Back to Machines
-
- )
- }
- />
-
-
- {/* Machine Information - Only show when not viewing snapshot details */}
- {!selectedSnapshot && (
-
-
- Machine Information
-
-
-
-
-
-
- Active
- } />
-
-
-
- )}
-
- {/* Snapshots List or Details */}
- {!selectedSnapshot ? (
- /* Snapshots List */
-
-
- Snapshots ({snapshots.length})
-
-
- {snapshots.length === 0 ? (
- }
- title="No Snapshots"
- subtitle="This machine hasn't created any snapshots yet."
- />
- ) : (
-
- {snapshots.map((snapshot) => (
-
-
-
-
-
-
-
Snapshot
-
-
-
-
- {formatDate(snapshot.created_at)}
-
- }
- />
-
-
- {truncateHash(snapshot.id, 24)}
-
- }
- />
-
-
- {truncateHash(snapshot.snapshot_hash, 24)}
-
- }
- />
-
-
-
- fetchSnapshotDetails(snapshot.id)}
- >
-
- View Details
-
-
-
-
-
- ))}
-
- )}
-
-
- ) : (
- /* Snapshot Details */
-
-
- Snapshot {selectedSnapshot} Details
-
-
- {loadingDetails ? (
-
- ) : snapshotDetails ? (
-
- {/* Snapshot Metadata */}
-
-
- Metadata
-
-
-
-
-
- {formatDate(snapshotDetails.created_at)}
-
- }
- />
-
-
- {snapshotDetails.snapshot_hash}
-
- }
- />
-
-
-
-
-
- {/* Disks */}
-
-
Disks ({snapshotDetails.disks.length})
-
- {snapshotDetails.disks.map((disk, diskIndex) => (
-
-
- Disk {diskIndex + 1}
-
-
-
-
-
-
-
-
- {/* Partitions */}
- {disk.partitions.length > 0 && (
-
-
Partitions
-
- {disk.partitions.map((partition, partIndex) => (
-
-
-
- Partition {partIndex + 1}
-
- {partition.fs_type.toUpperCase()}
-
-
-
-
-
-
-
-
-
-
-
-
- ))}
-
-
- )}
-
-
- ))}
-
-
-
- ) : (
- }
- title="No Details Available"
- subtitle="Unable to load snapshot details."
- />
- )}
-
-
- )}
-
-
- );
-};
-
-export default MachineDetails;
diff --git a/webui/src/pages/MachineDetails/index.js b/webui/src/pages/MachineDetails/index.js
deleted file mode 100644
index e110807..0000000
--- a/webui/src/pages/MachineDetails/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from './MachineDetails.jsx';
-export { MachineDetails } from './MachineDetails.jsx';
diff --git a/webui/src/pages/MachineDetails/styles.sass b/webui/src/pages/MachineDetails/styles.sass
deleted file mode 100644
index 0bb1b3a..0000000
--- a/webui/src/pages/MachineDetails/styles.sass
+++ /dev/null
@@ -1,232 +0,0 @@
-// Machine Details Page Styles
-.machine-details
- // Snapshot Summary Cards (list view)
- .snapshot-summary-card
- transition: all 0.2s ease
- cursor: pointer
-
- &:hover
- border-color: var(--border-strong)
- box-shadow: 0 4px 12px rgba(31, 36, 41, 0.1)
- transform: translateY(-1px)
-
- .snapshot-summary
- display: flex
- justify-content: space-between
- align-items: flex-start
- gap: 1.5rem
-
- .snapshot-info
- flex: 1
-
- .snapshot-title
- display: flex
- align-items: center
- gap: 0.75rem
- margin-bottom: 1rem
-
- h4
- font-size: 1.125rem
- font-weight: 600
- color: var(--text)
- margin: 0
-
- .snapshot-date
- display: flex
- align-items: center
- gap: 0.5rem
- font-size: 0.875rem
- color: var(--text-dim)
-
- .snapshot-hash
- display: flex
- align-items: center
- gap: 0.5rem
- font-size: 0.875rem
-
- code
- background: var(--bg-elev)
- padding: 0.25rem 0.5rem
- border-radius: var(--radius-sm)
- font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace
- color: var(--text-dim)
- font-size: 0.8rem
-
- .snapshot-actions
- display: flex
- flex-direction: column
- gap: 0.5rem
-
- // Snapshot Detail View
- .snapshot-details
- .snapshot-metadata
- margin-bottom: 2rem
- background: linear-gradient(135deg, var(--bg-alt) 0%, var(--bg-elev) 100%)
- border: 1px solid var(--border)
-
- .disks-section
- h4
- font-size: 1.25rem
- font-weight: 600
- color: var(--text)
- margin-bottom: 1.5rem
- display: flex
- align-items: center
- gap: 0.75rem
- padding-bottom: 0.5rem
- border-bottom: 2px solid var(--border)
-
- .disk-card
- border: 1px solid var(--border)
- background: linear-gradient(135deg, var(--bg-alt) 0%, var(--bg-elev) 100%)
- transition: all 0.2s ease
- position: relative
- overflow: hidden
-
- &::before
- content: ''
- position: absolute
- top: 0
- left: 0
- right: 0
- height: 3px
- background: linear-gradient(90deg, var(--accent) 0%, var(--success) 100%)
- opacity: 0
- transition: opacity 0.2s ease
-
- &:hover
- border-color: var(--border-strong)
- box-shadow: 0 6px 20px rgba(31, 36, 41, 0.15)
- transform: translateY(-2px)
-
- &::before
- opacity: 1
-
- .partitions-section
- margin-top: 2rem
-
- h6
- font-size: 1rem
- font-weight: 600
- color: var(--text)
- margin-bottom: 1rem
- display: flex
- align-items: center
- gap: 0.5rem
- padding: 0.5rem 0
- border-bottom: 1px solid var(--border)
-
- .partition-card
- border: 1px solid var(--border)
- background: var(--bg-elev)
- transition: all 0.2s ease
- position: relative
-
- &:hover
- border-color: var(--border-strong)
- box-shadow: 0 3px 10px rgba(31, 36, 41, 0.1)
- transform: translateY(-1px)
-
- .partition-header
- display: flex
- justify-content: space-between
- align-items: center
-
- span
- font-size: 0.875rem
- font-weight: 600
- color: var(--text)
-
- // Enhanced visual feedback
- .snapshot-date, .snapshot-hash
- transition: color 0.2s ease
-
- &:hover
- color: var(--text)
-
- // Better spacing for detail items
- .detail-list
- .detail-item
- padding: 0.75rem 0
- border-bottom: 1px solid var(--border)
-
- &:last-child
- border-bottom: none
-
- .detail-label
- font-weight: 500
- color: var(--text-dim)
- font-size: 0.875rem
- text-transform: uppercase
- letter-spacing: 0.05em
-
- .detail-value
- font-weight: 500
- color: var(--text)
-
- code
- background: var(--bg-elev)
- padding: 0.25rem 0.5rem
- border-radius: var(--radius-sm)
- font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace
- font-size: 0.8rem
- border: 1px solid var(--border)
-
- // Loading and error states
- .loading-section
- text-align: center
- padding: 3rem
-
- .spinner
- border: 3px solid var(--border)
- border-top: 3px solid var(--accent)
- border-radius: 50%
- width: 40px
- height: 40px
- animation: spin 1s linear infinite
- margin: 0 auto 1rem
-
- @keyframes spin
- 0%
- transform: rotate(0deg)
- 100%
- transform: rotate(360deg)
-
- // Responsive design
- @media (max-width: 768px)
- .snapshot-summary
- flex-direction: column
- gap: 1rem
-
- .snapshot-actions
- flex-direction: row
- align-self: stretch
-
- .disk-card .partitions-section h6
- font-size: 0.875rem
-
- .disks-section h4
- font-size: 1.125rem
-
- // Visual hierarchy improvements
- .card
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1)
-
- &:hover
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15)
-
- .badge
- font-weight: 600
- letter-spacing: 0.025em
-
- &.variant-success
- background: linear-gradient(135deg, var(--success) 0%, #22c55e 100%)
-
- &.variant-info
- background: linear-gradient(135deg, var(--info) 0%, #3b82f6 100%)
-
- &.variant-warning
- background: linear-gradient(135deg, var(--warning) 0%, #f59e0b 100%)
-
- &.variant-secondary
- background: linear-gradient(135deg, var(--text-dim) 0%, #6b7280 100%)
diff --git a/webui/src/pages/Machines/Machines.jsx b/webui/src/pages/Machines/Machines.jsx
index 6e0fe1e..c05366a 100644
--- a/webui/src/pages/Machines/Machines.jsx
+++ b/webui/src/pages/Machines/Machines.jsx
@@ -1,5 +1,4 @@
import React, {useState, useEffect, useContext} from 'react';
-import {useNavigate} from 'react-router-dom';
import {UserContext} from '@/common/contexts/UserContext.jsx';
import {useToast} from '@/common/contexts/ToastContext.jsx';
import {getRequest, postRequest, deleteRequest} from '@/common/utils/RequestUtil.js';
@@ -29,7 +28,6 @@ import './styles.sass';
export const Machines = () => {
const {user: currentUser} = useContext(UserContext);
const toast = useToast();
- const navigate = useNavigate();
const [machines, setMachines] = useState([]);
const [loading, setLoading] = useState(true);
const [showCreateModal, setShowCreateModal] = useState(false);
@@ -181,14 +179,6 @@ export const Machines = () => {
}
};
- const handleMachineClick = (machineId) => {
- navigate(`/machines/${machineId}`);
- };
-
- const handleActionClick = (e) => {
- e.stopPropagation(); // Prevent navigation when clicking action buttons
- };
-
const handleInputChange = (e) => {
const {name, value} = e.target;
setFormData(prev => ({
@@ -230,13 +220,7 @@ export const Machines = () => {
{machines.map(machine => (
- handleMachineClick(machine.id)}
- style={{ cursor: 'pointer' }}
- >
+
@@ -249,7 +233,7 @@ export const Machines = () => {
{formatUuid(machine.uuid)}
-