Added auto app update; Added maintenance module with notifications

This commit is contained in:
Martin Ziegler
2025-12-01 08:39:07 +01:00
parent 8571236bc0
commit f126b0a075
26 changed files with 2278 additions and 511 deletions
@@ -4,6 +4,7 @@ import { X, Save, Loader2, Send, CheckCircle, XCircle } from 'lucide-react';
import { api } from '../api/client';
import type { NotificationProvider, NotificationProviderCreate, NotificationProviderUpdate, ProviderType } from '../api/client';
import { Button } from './Button';
import { Toggle } from './Toggle';
interface AddNotificationModalProps {
provider?: NotificationProvider | null;
@@ -350,15 +351,10 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-sm text-white">Quiet Hours (Do Not Disturb)</label>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={quietHoursEnabled}
onChange={(e) => setQuietHoursEnabled(e.target.checked)}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
</label>
<Toggle
checked={quietHoursEnabled}
onChange={setQuietHoursEnabled}
/>
</div>
{quietHoursEnabled && (
<div className="grid grid-cols-2 gap-3">
+2 -1
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import { NavLink, Outlet, useNavigate, useLocation } from 'react-router-dom';
import { Printer, Archive, Calendar, BarChart3, Cloud, Settings, Sun, Moon, ChevronLeft, ChevronRight, Keyboard, Github, GripVertical, ArrowUpCircle, type LucideIcon } from 'lucide-react';
import { Printer, Archive, Calendar, BarChart3, Cloud, Settings, Sun, Moon, ChevronLeft, ChevronRight, Keyboard, Github, GripVertical, ArrowUpCircle, Wrench, type LucideIcon } from 'lucide-react';
import { useTheme } from '../contexts/ThemeContext';
import { KeyboardShortcutsModal } from './KeyboardShortcutsModal';
import { useQuery } from '@tanstack/react-query';
@@ -19,6 +19,7 @@ export const defaultNavItems: NavItem[] = [
{ id: 'queue', to: '/queue', icon: Calendar, label: 'Queue' },
{ id: 'stats', to: '/stats', icon: BarChart3, label: 'Statistics' },
{ id: 'profiles', to: '/profiles', icon: Cloud, label: 'Profiles' },
{ id: 'maintenance', to: '/maintenance', icon: Wrench, label: 'Maintenance' },
{ id: 'settings', to: '/settings', icon: Settings, label: 'Settings' },
];
@@ -6,6 +6,7 @@ import type { NotificationProvider, NotificationProviderUpdate } from '../api/cl
import { Card, CardContent } from './Card';
import { Button } from './Button';
import { ConfirmModal } from './ConfirmModal';
import { Toggle } from './Toggle';
interface NotificationProviderCardProps {
provider: NotificationProvider;
@@ -134,6 +135,9 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
{provider.on_filament_low && (
<span className="px-2 py-0.5 bg-amber-500/20 text-amber-400 text-xs rounded">Low Filament</span>
)}
{provider.on_maintenance_due && (
<span className="px-2 py-0.5 bg-purple-500/20 text-purple-400 text-xs rounded">Maintenance</span>
)}
{provider.quiet_hours_enabled && (
<span className="px-2 py-0.5 bg-purple-500/20 text-purple-400 text-xs rounded flex items-center gap-1">
<Moon className="w-3 h-3" />
@@ -204,15 +208,10 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
<p className="text-sm text-white">Enabled</p>
<p className="text-xs text-bambu-gray">Send notifications from this provider</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={provider.enabled}
onChange={(e) => updateMutation.mutate({ enabled: e.target.checked })}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
</label>
<Toggle
checked={provider.enabled}
onChange={(checked) => updateMutation.mutate({ enabled: checked })}
/>
</div>
{/* Print Lifecycle Events */}
@@ -221,54 +220,34 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
<div className="flex items-center justify-between">
<p className="text-sm text-white">Print Started</p>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={provider.on_print_start}
onChange={(e) => updateMutation.mutate({ on_print_start: e.target.checked })}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
</label>
<Toggle
checked={provider.on_print_start}
onChange={(checked) => updateMutation.mutate({ on_print_start: checked })}
/>
</div>
<div className="flex items-center justify-between">
<p className="text-sm text-white">Print Completed</p>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={provider.on_print_complete}
onChange={(e) => updateMutation.mutate({ on_print_complete: e.target.checked })}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
</label>
<Toggle
checked={provider.on_print_complete}
onChange={(checked) => updateMutation.mutate({ on_print_complete: checked })}
/>
</div>
<div className="flex items-center justify-between">
<p className="text-sm text-white">Print Failed</p>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={provider.on_print_failed}
onChange={(e) => updateMutation.mutate({ on_print_failed: e.target.checked })}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
</label>
<Toggle
checked={provider.on_print_failed}
onChange={(checked) => updateMutation.mutate({ on_print_failed: checked })}
/>
</div>
<div className="flex items-center justify-between">
<p className="text-sm text-white">Print Stopped</p>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={provider.on_print_stopped}
onChange={(e) => updateMutation.mutate({ on_print_stopped: e.target.checked })}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
</label>
<Toggle
checked={provider.on_print_stopped}
onChange={(checked) => updateMutation.mutate({ on_print_stopped: checked })}
/>
</div>
<div className="flex items-center justify-between">
@@ -276,15 +255,10 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
<p className="text-sm text-white">Progress Milestones</p>
<p className="text-xs text-bambu-gray">Notify at 25%, 50%, 75%</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={provider.on_print_progress}
onChange={(e) => updateMutation.mutate({ on_print_progress: e.target.checked })}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
</label>
<Toggle
checked={provider.on_print_progress}
onChange={(checked) => updateMutation.mutate({ on_print_progress: checked })}
/>
</div>
</div>
@@ -294,41 +268,37 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
<div className="flex items-center justify-between">
<p className="text-sm text-white">Printer Offline</p>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={provider.on_printer_offline}
onChange={(e) => updateMutation.mutate({ on_printer_offline: e.target.checked })}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
</label>
<Toggle
checked={provider.on_printer_offline}
onChange={(checked) => updateMutation.mutate({ on_printer_offline: checked })}
/>
</div>
<div className="flex items-center justify-between">
<p className="text-sm text-white">Printer Error</p>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={provider.on_printer_error}
onChange={(e) => updateMutation.mutate({ on_printer_error: e.target.checked })}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
</label>
<Toggle
checked={provider.on_printer_error}
onChange={(checked) => updateMutation.mutate({ on_printer_error: checked })}
/>
</div>
<div className="flex items-center justify-between">
<p className="text-sm text-white">Low Filament</p>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={provider.on_filament_low}
onChange={(e) => updateMutation.mutate({ on_filament_low: e.target.checked })}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
</label>
<Toggle
checked={provider.on_filament_low}
onChange={(checked) => updateMutation.mutate({ on_filament_low: checked })}
/>
</div>
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-white">Maintenance Due</p>
<p className="text-xs text-bambu-gray">Notify when maintenance is needed</p>
</div>
<Toggle
checked={provider.on_maintenance_due ?? false}
onChange={(checked) => updateMutation.mutate({ on_maintenance_due: checked })}
/>
</div>
</div>
@@ -339,15 +309,10 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
<Moon className="w-4 h-4 text-purple-400" />
<p className="text-sm text-white">Quiet Hours</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={provider.quiet_hours_enabled}
onChange={(e) => updateMutation.mutate({ quiet_hours_enabled: e.target.checked })}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
</label>
<Toggle
checked={provider.quiet_hours_enabled}
onChange={(checked) => updateMutation.mutate({ quiet_hours_enabled: checked })}
/>
</div>
{provider.quiet_hours_enabled && (
+38
View File
@@ -0,0 +1,38 @@
interface ToggleProps {
checked: boolean;
onChange: (checked: boolean) => void;
disabled?: boolean;
}
export function Toggle({ checked, onChange, disabled }: ToggleProps) {
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (!disabled) {
onChange(!checked);
}
};
return (
<button
type="button"
role="switch"
aria-checked={checked}
disabled={disabled}
onClick={handleClick}
className={`relative inline-flex w-9 h-5 rounded-full transition-colors flex-shrink-0 focus:outline-none focus:ring-2 focus:ring-bambu-green focus:ring-offset-2 focus:ring-offset-bambu-dark ${
disabled
? 'bg-bambu-dark-tertiary/50 cursor-not-allowed opacity-50'
: checked
? 'bg-bambu-green cursor-pointer'
: 'bg-bambu-dark-tertiary cursor-pointer hover:bg-bambu-dark-tertiary/80'
}`}
>
<span
className={`pointer-events-none absolute top-[2px] left-[2px] w-4 h-4 bg-white rounded-full shadow transition-transform duration-200 ease-in-out ${
checked ? 'translate-x-4' : 'translate-x-0'
}`}
/>
</button>
);
}