Showing 1 changed files with 53 additions and 14 deletions
+53 -14
scripts/host_manager.pl
@@ -2460,9 +2460,14 @@ sub app_html {
2460 2460
     .work-order-checkitem { display: flex; align-items: flex-start; gap: 8px; min-width: 0; color: var(--ink); font-size: 13px; font-weight: 400; }
2461 2461
     .work-order-checkitem input[type="checkbox"] { width: auto; flex: 0 0 auto; margin: 2px 0 0; }
2462 2462
     .work-order-checkitem span { min-width: 0; overflow-wrap: anywhere; }
2463
-    .debug-controls { display: grid; grid-template-columns: minmax(220px, 320px) auto 1fr; gap: 8px; align-items: center; width: 100%; }
2464
-    .debug-controls select { min-width: 0; }
2463
+    .debug-controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; width: 100%; }
2465 2464
     .debug-meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
2465
+    .debug-table-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 8px; padding: 10px; border-top: 1px solid var(--line); }
2466
+    .debug-table-card { display: grid; align-content: center; justify-items: start; gap: 5px; min-height: 58px; padding: 9px 10px; text-align: left; background: #fff; }
2467
+    .debug-table-card:hover { border-color: #9fb7e9; background: #f8fbff; }
2468
+    .debug-table-card.active { border-color: var(--accent); background: #e8f0fe; box-shadow: inset 0 0 0 1px var(--accent); }
2469
+    .debug-table-card-name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); font-weight: 700; }
2470
+    .debug-table-card-rows { color: var(--muted); font-size: 12px; }
2466 2471
     .debug-section { display: grid; gap: 16px; }
2467 2472
     .host-tools { display: flex; align-items: center; justify-content: flex-end; gap: 8px; min-width: 0; }
2468 2473
     .host-tools input { max-width: 240px; }
@@ -2511,7 +2516,7 @@ sub app_html {
2511 2516
       .panel-head { align-items: stretch; flex-direction: column; }
2512 2517
       .host-tools { justify-content: flex-start; flex-wrap: wrap; }
2513 2518
       .host-tools input { max-width: none; }
2514
-      .debug-controls { grid-template-columns: 1fr; }
2519
+      .debug-controls { align-items: stretch; }
2515 2520
       .modal-backdrop { padding-top: 16px; }
2516 2521
       .modal { max-height: calc(100dvh - 32px); }
2517 2522
       .grid { grid-template-columns: 1fr; }
@@ -2673,11 +2678,11 @@ sub app_html {
2673 2678
           </div>
2674 2679
           <div class="toolbar">
2675 2680
             <div class="debug-controls">
2676
-              <select id="debug-db-table" aria-label="Database table"></select>
2677 2681
               <button type="button" id="debug-db-refresh">Refresh</button>
2678 2682
               <div class="debug-meta muted mono" id="debug-db-meta"></div>
2679 2683
             </div>
2680 2684
           </div>
2685
+          <div class="debug-table-cards" id="debug-db-tables"></div>
2681 2686
         </section>
2682 2687
         <section class="debug-section">
2683 2688
           <section class="panel">
@@ -2741,7 +2746,7 @@ sub app_html {
2741 2746
   </div>
2742 2747
 
2743 2748
   <script>
2744
-    let state = { hosts: [], problems: [], workOrders: [], authenticated: false };
2749
+    let state = { hosts: [], problems: [], workOrders: [], authenticated: false, debugTable: '' };
2745 2750
     let hostFormSnapshot = '';
2746 2751
 
2747 2752
     const $ = (id) => document.getElementById(id);
@@ -2999,18 +3004,55 @@ sub app_html {
2999 3004
     async function renderDebugDatabase() {
3000 3005
       if (!state.authenticated) return;
3001 3006
       const data = await api('/api/debug/database/tables');
3002
-      const tableSelect = $('debug-db-table');
3003
-      const current = tableSelect.value;
3004 3007
       const tables = data.tables || [];
3005
-      tableSelect.innerHTML = tables.map(table => `<option value="${escapeHtml(table.name)}">${escapeHtml(table.name)} (${escapeHtml(String(table.rows))})</option>`).join('');
3006
-      const selected = tables.some(table => table.name === current) ? current : (tables[0] ? tables[0].name : '');
3007
-      tableSelect.value = selected;
3008
+      const selected = tables.some(table => table.name === state.debugTable) ? state.debugTable : (tables[0] ? tables[0].name : '');
3009
+      state.debugTable = selected;
3008 3010
       $('debug-db-stats').innerHTML = [
3009 3011
         ['tables', data.counts ? data.counts.tables : tables.length],
3010 3012
         ['rows', data.counts ? data.counts.rows : tables.reduce((total, table) => total + Number(table.rows || 0), 0)],
3011 3013
       ].map(([k, v]) => `<span class="stat">${escapeHtml(k)}: ${escapeHtml(String(v))}</span>`).join('');
3012 3014
       $('debug-db-meta').textContent = data.database || '';
3013
-      if (selected) await renderDebugTable(selected);
3015
+      renderDebugTableCards(tables, selected);
3016
+      if (selected) {
3017
+        await renderDebugTable(selected);
3018
+      } else {
3019
+        clearDebugTable();
3020
+      }
3021
+    }
3022
+
3023
+    function renderDebugTableCards(tables, selected) {
3024
+      $('debug-db-tables').innerHTML = tables.length
3025
+        ? tables.map(table => {
3026
+            const active = table.name === selected;
3027
+            return `<button type="button" class="debug-table-card ${active ? 'active' : ''}" data-debug-table="${escapeHtml(table.name)}" aria-pressed="${active ? 'true' : 'false'}">
3028
+              <span class="debug-table-card-name mono">${escapeHtml(table.name)}</span>
3029
+              <span class="debug-table-card-rows">${escapeHtml(String(table.rows || 0))} rows</span>
3030
+            </button>`;
3031
+          }).join('')
3032
+        : '<div class="ca-empty muted">No database tables found.</div>';
3033
+      document.querySelectorAll('[data-debug-table]').forEach(button => {
3034
+        button.addEventListener('click', () => selectDebugTable(button.dataset.debugTable).catch(e => {
3035
+          if (!isAuthLost(e)) msg(e.message);
3036
+        }));
3037
+      });
3038
+    }
3039
+
3040
+    async function selectDebugTable(tableName) {
3041
+      state.debugTable = tableName || '';
3042
+      document.querySelectorAll('[data-debug-table]').forEach(button => {
3043
+        const active = button.dataset.debugTable === state.debugTable;
3044
+        button.classList.toggle('active', active);
3045
+        button.setAttribute('aria-pressed', active ? 'true' : 'false');
3046
+      });
3047
+      if (state.debugTable) await renderDebugTable(state.debugTable);
3048
+    }
3049
+
3050
+    function clearDebugTable() {
3051
+      $('debug-table-stats').innerHTML = '';
3052
+      $('debug-table-rows').innerHTML = '<div class="ca-empty muted">No table selected.</div>';
3053
+      $('debug-table-columns').innerHTML = '<div class="ca-empty muted">No table selected.</div>';
3054
+      $('debug-table-indexes').innerHTML = '<div class="ca-empty muted">No table selected.</div>';
3055
+      $('debug-table-foreign-keys').innerHTML = '<div class="ca-empty muted">No table selected.</div>';
3014 3056
     }
3015 3057
 
3016 3058
     async function renderDebugTable(tableName) {
@@ -3372,9 +3414,6 @@ sub app_html {
3372 3414
     $('debug-db-refresh').addEventListener('click', () => renderDebugDatabase().catch(e => {
3373 3415
       if (!isAuthLost(e)) msg(e.message);
3374 3416
     }));
3375
-    $('debug-db-table').addEventListener('change', () => renderDebugTable($('debug-db-table').value).catch(e => {
3376
-      if (!isAuthLost(e)) msg(e.message);
3377
-    }));
3378 3417
     $('close-host-modal').addEventListener('click', requestCloseHostModal);
3379 3418
     $('host-modal').addEventListener('click', (event) => {
3380 3419
       if (event.target === $('host-modal') && !$('save-host').disabled) closeHostModal();