refactor: update App and StatusPage components for improved styling and functionality
This commit is contained in:
parent
9eeffde903
commit
5e329a7f39
|
|
@ -33,28 +33,30 @@ export default function App() {
|
||||||
return (
|
return (
|
||||||
<div className="size-full flex flex-col bg-neutral-50">
|
<div className="size-full flex flex-col bg-neutral-50">
|
||||||
<Toaster position="top-center" />
|
<Toaster position="top-center" />
|
||||||
<header className="bg-white border-b border-neutral-200 px-4 py-3 flex-shrink-0">
|
<header className="bg-[#4d4d4d] px-0 py-0 flex-shrink-0">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-stretch justify-between min-h-[56px]">
|
||||||
<img src={logoSvg} alt="Meatloaf" className="h-8" />
|
<div className="flex items-center h-full">
|
||||||
|
<img src={logoSvg} alt="Meatloaf" className="h-full max-h-[56px] w-auto object-contain" />
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowSearch(true)}
|
onClick={() => setShowSearch(true)}
|
||||||
className="p-2 hover:bg-neutral-100 rounded-lg"
|
className="p-2 hover:bg-[#5e5e5e] rounded-lg"
|
||||||
>
|
>
|
||||||
<Search className="w-5 h-5" />
|
<Search className="w-5 h-5 text-white" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentPage('tools')}
|
onClick={() => setCurrentPage('tools')}
|
||||||
className="p-2 hover:bg-neutral-100 rounded-lg"
|
className="p-2 hover:bg-[#5e5e5e] rounded-lg"
|
||||||
>
|
>
|
||||||
<Wrench className="w-5 h-5" />
|
<Wrench className="w-5 h-5 text-white" />
|
||||||
</button>
|
</button>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowProfileMenu(!showProfileMenu)}
|
onClick={() => setShowProfileMenu(!showProfileMenu)}
|
||||||
className="p-2 hover:bg-neutral-100 rounded-lg"
|
className="p-2 hover:bg-[#5e5e5e] rounded-lg"
|
||||||
>
|
>
|
||||||
<User className="w-5 h-5" />
|
<User className="w-5 h-5 text-white" />
|
||||||
</button>
|
</button>
|
||||||
{showProfileMenu && (
|
{showProfileMenu && (
|
||||||
<div className="absolute right-0 top-12 bg-white rounded-lg shadow-lg border border-neutral-200 py-2 min-w-[200px] z-20">
|
<div className="absolute right-0 top-12 bg-white rounded-lg shadow-lg border border-neutral-200 py-2 min-w-[200px] z-20">
|
||||||
|
|
@ -65,21 +67,21 @@ export default function App() {
|
||||||
}}
|
}}
|
||||||
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2"
|
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Settings className="w-4 h-4" />
|
<Settings className="w-4 h-4 text-[#4d4d4d]" />
|
||||||
Settings
|
Settings
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowProfileMenu(false)}
|
onClick={() => setShowProfileMenu(false)}
|
||||||
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2"
|
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Bell className="w-4 h-4" />
|
<Bell className="w-4 h-4 text-[#4d4d4d]" />
|
||||||
Notifications
|
Notifications
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowProfileMenu(false)}
|
onClick={() => setShowProfileMenu(false)}
|
||||||
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2"
|
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<FileText className="w-4 h-4" />
|
<FileText className="w-4 h-4 text-[#4d4d4d]" />
|
||||||
Documentation
|
Documentation
|
||||||
</button>
|
</button>
|
||||||
<div className="border-t border-neutral-200 my-2" />
|
<div className="border-t border-neutral-200 my-2" />
|
||||||
|
|
@ -87,7 +89,7 @@ export default function App() {
|
||||||
onClick={() => setShowProfileMenu(false)}
|
onClick={() => setShowProfileMenu(false)}
|
||||||
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2 text-red-600"
|
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2 text-red-600"
|
||||||
>
|
>
|
||||||
<LogOut className="w-4 h-4" />
|
<LogOut className="w-4 h-4 text-[#4d4d4d]" />
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -101,61 +103,49 @@ export default function App() {
|
||||||
{pages[currentPage]}
|
{pages[currentPage]}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<nav className="bg-white border-t border-neutral-200 flex-shrink-0">
|
<nav className="bg-[#4d4d4d] flex-shrink-0">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentPage('status')}
|
onClick={() => setCurrentPage('status')}
|
||||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||||
currentPage === 'status' ? 'text-blue-600' : 'text-neutral-600'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<Activity className="w-5 h-5" />
|
<Activity className="w-5 h-5 text-white" />
|
||||||
<span className="text-xs">Status</span>
|
<span className="text-xs text-white">Status</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentPage('devices')}
|
onClick={() => setCurrentPage('devices')}
|
||||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||||
currentPage === 'devices' ? 'text-blue-600' : 'text-neutral-600'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<HardDrive className="w-5 h-5" />
|
<HardDrive className="w-5 h-5 text-white" />
|
||||||
<span className="text-xs">Devices</span>
|
<span className="text-xs text-white">Devices</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentPage('iec')}
|
onClick={() => setCurrentPage('iec')}
|
||||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||||
currentPage === 'iec' ? 'text-blue-600' : 'text-neutral-600'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<Cpu className="w-5 h-5" />
|
<Cpu className="w-5 h-5 text-white" />
|
||||||
<span className="text-xs">IEC</span>
|
<span className="text-xs text-white">IEC</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentPage('network')}
|
onClick={() => setCurrentPage('network')}
|
||||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||||
currentPage === 'network' ? 'text-blue-600' : 'text-neutral-600'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<Network className="w-5 h-5" />
|
<Network className="w-5 h-5 text-white" />
|
||||||
<span className="text-xs">Network</span>
|
<span className="text-xs text-white">Network</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentPage('other')}
|
onClick={() => setCurrentPage('other')}
|
||||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||||
currentPage === 'other' ? 'text-blue-600' : 'text-neutral-600'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<MoreHorizontal className="w-5 h-5" />
|
<MoreHorizontal className="w-5 h-5 text-white" />
|
||||||
<span className="text-xs">More</span>
|
<span className="text-xs text-white">More</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentPage('general')}
|
onClick={() => setCurrentPage('general')}
|
||||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||||
currentPage === 'general' ? 'text-blue-600' : 'text-neutral-600'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<Settings className="w-5 h-5" />
|
<Settings className="w-5 h-5 text-white" />
|
||||||
<span className="text-xs">General</span>
|
<span className="text-xs text-white">General</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,21 @@
|
||||||
|
import { useState } from 'react';
|
||||||
import { HardDrive, Activity, Wifi, Signal, Clock } from 'lucide-react';
|
import { HardDrive, Activity, Wifi, Signal, Clock } from 'lucide-react';
|
||||||
|
import DeviceDetailOverlay from './DeviceDetailOverlay';
|
||||||
|
|
||||||
interface StatusPageProps {
|
interface StatusPageProps {
|
||||||
config: any;
|
config: any;
|
||||||
setConfig: (config: any) => void;
|
setConfig: (config: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StatusPage({ config }: StatusPageProps) {
|
export default function StatusPage({ config, setConfig }: StatusPageProps) {
|
||||||
|
// Mock memory stats
|
||||||
|
const memory = {
|
||||||
|
heap: { total: 4096, free: 1024 }, // in KB
|
||||||
|
psram: { total: 8192, free: 4096 },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Overlay state for active device
|
||||||
|
const [showDeviceOverlay, setShowDeviceOverlay] = useState(false);
|
||||||
// Find the first enabled device as the active device
|
// Find the first enabled device as the active device
|
||||||
const findActiveDevice = () => {
|
const findActiveDevice = () => {
|
||||||
if (config.iec?.devices?.drive) {
|
if (config.iec?.devices?.drive) {
|
||||||
|
|
@ -42,16 +52,39 @@ export default function StatusPage({ config }: StatusPageProps) {
|
||||||
<h2 className="text-sm text-neutral-500">System Status</h2>
|
<h2 className="text-sm text-neutral-500">System Status</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg p-4">
|
<div className="bg-white border border-neutral-200 rounded-lg p-4">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="mb-4">
|
||||||
<div className="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
|
<div className="text-xs text-neutral-500 mb-1">Memory Utilization</div>
|
||||||
<Signal className="w-5 h-5 text-green-600" />
|
<div className="flex flex-col gap-2">
|
||||||
</div>
|
{/* Heap Graph */}
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">Online</div>
|
<div className="flex justify-between text-xs mb-0.5">
|
||||||
<div className="text-sm text-neutral-500">All systems operational</div>
|
<span>Heap</span>
|
||||||
|
<span>{memory.heap.free} KB free / {memory.heap.total} KB</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-3 bg-neutral-200 rounded overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-3 bg-blue-500"
|
||||||
|
style={{ width: `${((memory.heap.total - memory.heap.free) / memory.heap.total) * 100}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* PSRAM Graph */}
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between text-xs mb-0.5">
|
||||||
|
<span>PSRAM</span>
|
||||||
|
<span>{memory.psram.free} KB free / {memory.psram.total} KB</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-3 bg-neutral-200 rounded overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-3 bg-green-500"
|
||||||
|
style={{ width: `${((memory.psram.total - memory.psram.free) / memory.psram.total) * 100}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-neutral-500">WiFi</div>
|
<div className="text-xs text-neutral-500">WiFi</div>
|
||||||
|
|
@ -59,6 +92,10 @@ export default function StatusPage({ config }: StatusPageProps) {
|
||||||
<Wifi className="w-3 h-3 text-green-600" />
|
<Wifi className="w-3 h-3 text-green-600" />
|
||||||
<span>Connected</span>
|
<span>Connected</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-xs text-neutral-500 mt-1">IP Address</div>
|
||||||
|
<div className="text-sm text-neutral-700">192.168.1.100</div>
|
||||||
|
<div className="text-xs text-neutral-500 mt-1">MAC Address</div>
|
||||||
|
<div className="text-sm text-neutral-700">AA:BB:CC:DD:EE:FF</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-neutral-500">Uptime</div>
|
<div className="text-xs text-neutral-500">Uptime</div>
|
||||||
|
|
@ -74,7 +111,10 @@ export default function StatusPage({ config }: StatusPageProps) {
|
||||||
<>
|
<>
|
||||||
<h2 className="text-sm text-neutral-500 pt-2">Active Device</h2>
|
<h2 className="text-sm text-neutral-500 pt-2">Active Device</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg p-4">
|
<div
|
||||||
|
className="bg-white border border-neutral-200 rounded-lg p-4 cursor-pointer hover:bg-neutral-50 transition"
|
||||||
|
onClick={() => setShowDeviceOverlay(true)}
|
||||||
|
>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
||||||
|
|
@ -106,8 +146,66 @@ export default function StatusPage({ config }: StatusPageProps) {
|
||||||
<div className="text-lg font-medium text-green-600">{stats.errors}</div>
|
<div className="text-lg font-medium text-green-600">{stats.errors}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Media switch buttons if media set is detected */}
|
||||||
|
{(() => {
|
||||||
|
// Media set detection logic (copied from DeviceDetailOverlay)
|
||||||
|
const url = activeDevice.url;
|
||||||
|
if (!url) return null;
|
||||||
|
const match = url.match(/^(.+?)(\d+)(\.[^.]+)$/);
|
||||||
|
if (!match) return null;
|
||||||
|
const [, prefix, num, ext] = match;
|
||||||
|
const currentNum = parseInt(num);
|
||||||
|
const mediaSet = [];
|
||||||
|
for (let i = 1; i <= 10; i++) {
|
||||||
|
mediaSet.push(`${prefix}${i}${ext}`);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-2 mt-2">
|
||||||
|
{mediaSet.map((file, idx) => (
|
||||||
|
<button
|
||||||
|
key={file}
|
||||||
|
className={`px-2 py-1 rounded text-xs border ${url === file ? 'bg-blue-600 text-white border-blue-600' : 'bg-neutral-100 text-neutral-700 border-neutral-300'}`}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (setConfig) {
|
||||||
|
const newConfig = JSON.parse(JSON.stringify(config));
|
||||||
|
let current = newConfig;
|
||||||
|
if (current.iec && current.iec.devices && current.iec.devices.drive && current.iec.devices.drive[num]) {
|
||||||
|
current.iec.devices.drive[num].url = file;
|
||||||
|
setConfig(newConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{idx + 1}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showDeviceOverlay && (
|
||||||
|
<DeviceDetailOverlay
|
||||||
|
device={{
|
||||||
|
id: `drive-${activeDevice.number}`,
|
||||||
|
number: activeDevice.number,
|
||||||
|
type: 'drive',
|
||||||
|
name: activeDevice.name,
|
||||||
|
enabled: activeDevice.enabled,
|
||||||
|
url: activeDevice.url,
|
||||||
|
mode: activeDevice.mode
|
||||||
|
}}
|
||||||
|
config={config}
|
||||||
|
setConfig={setConfig}
|
||||||
|
onClose={() => setShowDeviceOverlay(false)}
|
||||||
|
onNavigate={() => {}}
|
||||||
|
hasPrev={false}
|
||||||
|
hasNext={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<h2 className="text-sm text-neutral-500 pt-2 flex items-center gap-2">
|
<h2 className="text-sm text-neutral-500 pt-2 flex items-center gap-2">
|
||||||
<Activity className="w-4 h-4" />
|
<Activity className="w-4 h-4" />
|
||||||
Activity Log
|
Activity Log
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user