|
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
|
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
|
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
|
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
|
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
|
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();
|