Browse Source

Changed app defaults

maziggy 2 weeks ago
parent
commit
ba3dc613d1

+ 2 - 2
backend/app/schemas/settings.py

@@ -122,9 +122,9 @@ class AppSettings(BaseModel):
     )
 
     # Dark mode theme settings
-    dark_style: str = Field(default="classic", description="Dark mode style: classic, glow, vibrant")
+    dark_style: str = Field(default="vibrant", description="Dark mode style: classic, glow, vibrant")
     dark_background: str = Field(
-        default="neutral", description="Dark mode background: neutral, warm, cool, oled, slate, forest"
+        default="cool", description="Dark mode background: neutral, warm, cool, oled, slate, forest"
     )
     dark_accent: str = Field(default="green", description="Dark mode accent: green, teal, blue, orange, purple, red")
 

+ 131 - 0
frontend/src/__tests__/mocks/handlers.ts

@@ -421,4 +421,135 @@ export const handlers = [
       total_folders: 0,
     });
   }),
+
+  // ========================================================================
+  // Read-on-mount fallbacks
+  // ------------------------------------------------------------------------
+  // Components fire background fetches when they mount (status badges,
+  // notification-template loaders, oidc/ldap/2fa probes, etc.). Without
+  // handlers these fall through to the real network and the rejected promise
+  // surfaces as an ECONNREFUSED stack trace in test stderr. These minimal
+  // disabled-state stubs silence the trace. Per-test handlers added via
+  // server.use(...) still win.
+  // ========================================================================
+
+  // Lists → empty arrays
+  http.get('/api/v1/archives/', () => HttpResponse.json([])),
+  http.get('/api/v1/auth/oidc/providers', () => HttpResponse.json([])),
+  http.get('/api/v1/auth/oidc/providers/all', () => HttpResponse.json([])),
+  http.get('/api/v1/auth/tokens', () => HttpResponse.json([])),
+  http.get('/api/v1/auth/tokens/all', () => HttpResponse.json([])),
+  http.get('/api/v1/external-links/', () => HttpResponse.json([])),
+  http.get('/api/v1/inventory/assignments', () => HttpResponse.json([])),
+  http.get('/api/v1/inventory/catalog', () => HttpResponse.json([])),
+  http.get('/api/v1/inventory/colors', () => HttpResponse.json([])),
+  http.get('/api/v1/inventory/spools', () => HttpResponse.json([])),
+  http.get('/api/v1/library/folders', () => HttpResponse.json([])),
+  http.get('/api/v1/library/folders/by-archive/:id', () => HttpResponse.json([])),
+  http.get('/api/v1/maintenance/overview', () => HttpResponse.json([])),
+  http.get('/api/v1/makerworld/recent-imports', () => HttpResponse.json([])),
+  http.get('/api/v1/notification-templates', () => HttpResponse.json([])),
+  http.get('/api/v1/pending-uploads/', () => HttpResponse.json([])),
+  http.get('/api/v1/printers/:id/ams-labels', () => HttpResponse.json([])),
+  http.get('/api/v1/printers/:id/slot-presets', () => HttpResponse.json([])),
+  http.get('/api/v1/smart-plugs/by-printer/:id', () => HttpResponse.json([])),
+  http.get('/api/v1/smart-plugs/by-printer/:id/scripts', () => HttpResponse.json([])),
+  http.get('/api/v1/spoolbuddy/devices', () => HttpResponse.json([])),
+  http.get('/api/v1/spoolman/inventory/filaments', () => HttpResponse.json([])),
+  http.get('/api/v1/spoolman/spools/linked', () => HttpResponse.json([])),
+  http.get('/api/v1/spoolman/spools/unlinked', () => HttpResponse.json([])),
+  http.get('/api/v1/users/', () => HttpResponse.json([])),
+
+  // Status / object endpoints → minimal disabled-state responses
+  http.get('/api/v1/archives/purge/settings', () =>
+    HttpResponse.json({ enabled: false, retention_days: 0 })
+  ),
+  http.get('/api/v1/auth/2fa/status', () =>
+    HttpResponse.json({ totp_enabled: false, email_otp_enabled: false, backup_codes_remaining: 0 })
+  ),
+  http.get('/api/v1/auth/advanced-auth/status', () =>
+    HttpResponse.json({ advanced_auth_enabled: false, smtp_configured: false })
+  ),
+  http.get('/api/v1/auth/ldap/status', () =>
+    HttpResponse.json({ ldap_enabled: false, ldap_configured: false })
+  ),
+  http.get('/api/v1/cloud/status', () =>
+    HttpResponse.json({ is_authenticated: false, email: null, region: null })
+  ),
+  http.get('/api/v1/firmware/updates/:id', () => HttpResponse.json(null)),
+  http.get('/api/v1/github-backup/status', () =>
+    HttpResponse.json({
+      enabled: false,
+      configured: false,
+      last_backup_at: null,
+      last_error: null,
+      schedule_enabled: false,
+    })
+  ),
+  http.get('/api/v1/library/trash', () => HttpResponse.json({ items: [], total: 0 })),
+  http.get('/api/v1/library/trash/settings', () =>
+    HttpResponse.json({ enabled: false, retention_days: 0 })
+  ),
+  http.get('/api/v1/obico/status', () =>
+    HttpResponse.json({
+      is_running: false,
+      last_error: null,
+      per_printer: {},
+      thresholds: { low: 0, high: 0 },
+      history: [],
+      enabled: false,
+      ml_url: '',
+      sensitivity: 'medium',
+      action: 'notify',
+      poll_interval: 30,
+      external_url_configured: false,
+    })
+  ),
+  http.get('/api/v1/printers/:id/current-print-user', () => HttpResponse.json(null)),
+  http.get('/api/v1/settings/check-ffmpeg', () =>
+    HttpResponse.json({ available: false, version: null })
+  ),
+  http.get('/api/v1/settings/mqtt/status', () =>
+    HttpResponse.json({ enabled: false, connected: false, broker: '', port: 1883, topic_prefix: '' })
+  ),
+  // NOTE: /api/v1/settings/spoolman intentionally NOT stubbed globally —
+  // src/__tests__/api/client.test.ts uses it as an auth-header canary with
+  // its own setupServer instance, and a global default would race with that
+  // file's runtime handlers. Tests that need this endpoint stub it locally.
+  http.get('/api/v1/settings/virtual-printer', () => HttpResponse.json({})),
+  http.get('/api/v1/spoolman/status', () =>
+    HttpResponse.json({ enabled: false, connected: false, url: null })
+  ),
+  http.get('/api/v1/system/storage-usage', () =>
+    HttpResponse.json({
+      roots: [],
+      total_bytes: 0,
+      total_formatted: '0 B',
+      categories: [],
+      other_breakdown: [],
+      scan_errors: 0,
+      generated_at: '2024-01-01T00:00:00Z',
+      cache: { hit: false, age_seconds: 0, max_age_seconds: 0 },
+    })
+  ),
+  http.get('/api/v1/updates/check', () =>
+    HttpResponse.json({ update_available: false, latest_version: null })
+  ),
+  http.get('/api/v1/updates/status', () =>
+    HttpResponse.json({ update_available: false, latest_version: null })
+  ),
+  http.get('/api/v1/updates/version', () =>
+    HttpResponse.json({ version: '0.1.5', commit: 'test' })
+  ),
+  http.get('/openapi.json', () =>
+    HttpResponse.json({ openapi: '3.0.0', info: { title: 'Bambuddy', version: '0.1.5' }, paths: {} })
+  ),
+  http.post('/api/v1/cloud/filament-info', () => HttpResponse.json({ filaments: [] })),
+  http.post('/api/v1/printers/camera/stream-token', () =>
+    HttpResponse.json({ token: 'test-token', expires_at: '2099-01-01T00:00:00Z' })
+  ),
+  http.put('/api/v1/settings/', async ({ request }) => {
+    const body = (await request.json()) as Record<string, unknown>;
+    return HttpResponse.json(body);
+  }),
 ];

+ 4 - 0
frontend/src/__tests__/pages/InventoryPageCopyButton.test.tsx

@@ -149,8 +149,12 @@ function setupHandlers(spools: unknown[] = [MOCK_SPOOL]) {
     http.get('/api/v1/cloud/local-presets', () =>
       HttpResponse.json({ filament: [], printer: [], process: [] })
     ),
+    http.get('/api/v1/local-presets/', () =>
+      HttpResponse.json({ filament: [], printer: [], process: [] })
+    ),
     http.get('/api/v1/cloud/builtin-filaments', () => HttpResponse.json([])),
     http.get('/api/v1/inventory/color-catalog', () => HttpResponse.json([])),
+    http.get('/api/v1/inventory/colors', () => HttpResponse.json([])),
     http.get('/api/v1/inventory/spool-catalog', () => HttpResponse.json([])),
     http.get('/api/v1/printers/', () => HttpResponse.json([])),
   );

+ 14 - 4
frontend/src/__tests__/pages/InventoryPageDeepLink.test.tsx

@@ -120,6 +120,7 @@ function setupCommonHandlers(spoolList: object[]) {
       HttpResponse.json({ spoolman_enabled: 'false', spoolman_url: '', spoolman_sync_mode: 'auto', spoolman_disable_weight_sync: 'false', spoolman_report_partial_usage: 'true' })
     ),
     http.get('/api/v1/inventory/spools', () => HttpResponse.json(spoolList)),
+    http.get('/api/v1/inventory/spools/:id/usage', () => HttpResponse.json([])),
     http.get('/api/v1/inventory/assignments', () => HttpResponse.json([])),
     http.get('/api/v1/inventory/catalog', () => HttpResponse.json([])),
     // Deep-link flows open SpoolFormModal, which fires off these fetches the
@@ -133,8 +134,12 @@ function setupCommonHandlers(spoolList: object[]) {
     http.get('/api/v1/cloud/local-presets', () =>
       HttpResponse.json({ filament: [], printer: [], process: [] })
     ),
+    http.get('/api/v1/local-presets/', () =>
+      HttpResponse.json({ filament: [], printer: [], process: [] })
+    ),
     http.get('/api/v1/cloud/builtin-filaments', () => HttpResponse.json([])),
     http.get('/api/v1/inventory/color-catalog', () => HttpResponse.json([])),
+    http.get('/api/v1/inventory/colors', () => HttpResponse.json([])),
     http.get('/api/v1/inventory/spool-catalog', () => HttpResponse.json([])),
     http.get('/api/v1/printers/', () => HttpResponse.json([])),
   );
@@ -249,7 +254,11 @@ describe('InventoryPage - deep-link ?spool= flow', () => {
   describe('scenario 5 (T-Gap 8): deep-link works in Spoolman mode', () => {
     beforeEach(() => {
       window.history.pushState({}, '', '/?spool=42');
-      // Override settings with Spoolman enabled
+      // Inherit common modal stubs (cloud/status, colors, presets, etc.) — then
+      // override the bits that flip into Spoolman mode. Runtime handlers added
+      // last win, so the spoolman_enabled: true settings response shadows the
+      // common one.
+      setupCommonHandlers([BASE_SPOOL]);
       server.use(
         http.get('/api/v1/settings/', () =>
           HttpResponse.json({ ...MOCK_SETTINGS, spoolman_enabled: true, spoolman_url: 'http://spoolman.local:7912' })
@@ -257,10 +266,11 @@ describe('InventoryPage - deep-link ?spool= flow', () => {
         http.get('/api/v1/settings/spoolman', () =>
           HttpResponse.json({ spoolman_enabled: 'true', spoolman_url: 'http://spoolman.local:7912', spoolman_sync_mode: 'auto', spoolman_disable_weight_sync: 'false', spoolman_report_partial_usage: 'true' })
         ),
-        // In Spoolman mode the component fetches from /api/v1/spoolman/inventory/spools
+        // Spoolman-mode list + per-spool fetches the modal makes when editing a Spoolman spool
         http.get('/api/v1/spoolman/inventory/spools', () => HttpResponse.json([BASE_SPOOL])),
-        http.get('/api/v1/inventory/assignments', () => HttpResponse.json([])),
-        http.get('/api/v1/inventory/catalog', () => HttpResponse.json([])),
+        http.get('/api/v1/spoolman/inventory/spools/:id', () => HttpResponse.json(BASE_SPOOL)),
+        http.get('/api/v1/spoolman/inventory/slot-assignments/all', () => HttpResponse.json([])),
+        http.get('/api/v1/spoolman/inventory/filaments', () => HttpResponse.json([])),
       );
     });
 

+ 4 - 5
frontend/src/components/Layout.tsx

@@ -26,17 +26,16 @@ interface NavItem {
 }
 
 export const defaultNavItems: NavItem[] = [
-  // Primary workflow items
   { id: 'printers', to: '/', icon: Printer, labelKey: 'nav.printers' },
+  { id: 'inventory', to: '/inventory', icon: Disc3, labelKey: 'nav.inventory' },
   { id: 'archives', to: '/archives', icon: Archive, labelKey: 'nav.archives' },
   { id: 'queue', to: '/queue', icon: Calendar, labelKey: 'nav.queue' },
-  { id: 'stats', to: '/stats', icon: BarChart3, labelKey: 'nav.stats' },
-  { id: 'profiles', to: '/profiles', icon: Cloud, labelKey: 'nav.profiles' },
-  { id: 'maintenance', to: '/maintenance', icon: Wrench, labelKey: 'nav.maintenance' },
   { id: 'projects', to: '/projects', icon: FolderKanban, labelKey: 'nav.projects' },
-  { id: 'inventory', to: '/inventory', icon: Disc3, labelKey: 'nav.inventory' },
   { id: 'files', to: '/files', icon: FolderOpen, labelKey: 'nav.files' },
   { id: 'makerworld', to: '/makerworld', icon: Globe, labelKey: 'nav.makerworld' },
+  { id: 'profiles', to: '/profiles', icon: Cloud, labelKey: 'nav.profiles' },
+  { id: 'maintenance', to: '/maintenance', icon: Wrench, labelKey: 'nav.maintenance' },
+  { id: 'stats', to: '/stats', icon: BarChart3, labelKey: 'nav.stats' },
   // User-account features: kept adjacent to Settings intentionally
   { id: 'notifications', to: '/notifications', icon: Bell, labelKey: 'nav.notifications' },
   { id: 'settings', to: '/settings', icon: Settings, labelKey: 'nav.settings' },

+ 2 - 2
frontend/src/contexts/ThemeContext.tsx

@@ -40,10 +40,10 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
 
   // Dark mode settings
   const [darkStyle, setDarkStyleState] = useState<ThemeStyle>(() => {
-    return (localStorage.getItem('dark-style') as ThemeStyle) || 'classic';
+    return (localStorage.getItem('dark-style') as ThemeStyle) || 'vibrant';
   });
   const [darkBackground, setDarkBackgroundState] = useState<DarkBackground>(() => {
-    return (localStorage.getItem('dark-background') as DarkBackground) || 'neutral';
+    return (localStorage.getItem('dark-background') as DarkBackground) || 'cool';
   });
   const [darkAccent, setDarkAccentState] = useState<ThemeAccent>(() => {
     return (localStorage.getItem('dark-accent') as ThemeAccent) || 'green';

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-CPo_tbez.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-CBFhMIk6.js"></script>
+    <script type="module" crossorigin src="/assets/index-CPo_tbez.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BSBzgKvT.css">
   </head>
   <body>

Some files were not shown because too many files changed in this diff