avoiding triple reload in last job list

This commit is contained in:
2026-08-03 21:34:06 +00:00
parent 9b080bc860
commit bfc6128353
10 changed files with 77 additions and 11 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# AssetManager 0.5.5.30 # AssetManager 0.5.5.31
AssetManager is a self-hosted web application for managing IT equipment and other organizational assets. The project is released under the **Apache License 2.0** and may be used, modified, and redistributed for private and commercial purposes. AssetManager is a self-hosted web application for managing IT equipment and other organizational assets. The project is released under the **Apache License 2.0** and may be used, modified, and redistributed for private and commercial purposes.
+1 -1
View File
@@ -1 +1 @@
0.5.5.30 0.5.5.31
+4
View File
@@ -799,6 +799,10 @@ BASE_TRANSLATIONS.update({
"software.version_count": ("Number of versions", "Anzahl Versionen"), "software.version_count": ("Number of versions", "Anzahl Versionen"),
"software.asset_count": ("Assets", "Assets"), "software.asset_count": ("Assets", "Assets"),
"software.entry_count": ("Entries", "Einträge"), "software.entry_count": ("Entries", "Einträge"),
"software.unique_asset_count": ("Unique assets", "Eindeutige Assets"),
"software.unique_asset_count_help": ("Number of different assets on which this grouped software was found. An asset is counted only once, even if multiple versions or matching inventory records exist.", "Anzahl unterschiedlicher Assets, auf denen diese gruppierte Software gefunden wurde. Ein Asset wird nur einmal gezählt, auch wenn mehrere Versionen oder passende Inventareinträge vorhanden sind."),
"software.inventory_entry_count": ("Inventory entries", "Inventareinträge"),
"software.inventory_entry_count_help": ("Number of matching software inventory records. This value can be higher than the number of unique assets when multiple versions or records exist on the same asset.", "Anzahl der passenden Software-Inventardatensätze. Dieser Wert kann höher als die Anzahl eindeutiger Assets sein, wenn auf einem Asset mehrere Versionen oder Einträge vorhanden sind."),
"software.no_global_inventory": ("No central software inventory is available yet.", "Noch keine zentrale Softwareinventur vorhanden."), "software.no_global_inventory": ("No central software inventory is available yet.", "Noch keine zentrale Softwareinventur vorhanden."),
"software.last_job": ("Last job", "Letzter Job"), "software.last_job": ("Last job", "Letzter Job"),
"software.last_inventory": ("Last inventory", "Letzte Inventur") "software.last_inventory": ("Last inventory", "Letzte Inventur")
+3
View File
@@ -2823,3 +2823,6 @@ html[data-theme="dark"] .chart-total{
/* Administrator editor for the protected MeshCentral node ID. */ /* Administrator editor for the protected MeshCentral node ID. */
.mesh-node-id-editor{display:grid;gap:.55rem}.mesh-node-id-input{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.mesh-node-id-actions{display:flex;flex-wrap:wrap;gap:.55rem;align-items:center}.mesh-node-id-actions .button{width:auto;margin:0}.mesh-node-id-editor small{font-weight:400;margin:0} .mesh-node-id-editor{display:grid;gap:.55rem}.mesh-node-id-input{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.mesh-node-id-actions{display:flex;flex-wrap:wrap;gap:.55rem;align-items:center}.mesh-node-id-actions .button{width:auto;margin:0}.mesh-node-id-editor small{font-weight:400;margin:0}
/* Keep server-filter controls visible while only the table body is refreshed. */
table.server-filter-loading tbody{opacity:.55;transition:opacity .12s ease}
+3
View File
@@ -63,6 +63,9 @@
if (!event.detail?.table || event.detail.table === table) update(); if (!event.detail?.table || event.detail.table === table) update();
}); });
window.addEventListener('software-job-status-changed', update); window.addEventListener('software-job-status-changed', update);
window.addEventListener('asset-table-content-reloaded', event => {
if (event.detail?.table === table) update();
});
update(); update();
} }
+10
View File
@@ -69,10 +69,13 @@
} }
let timer = null; let timer = null;
let requestInProgress = false;
async function refresh() { async function refresh() {
if (requestInProgress) return;
const active = activeRows(); const active = activeRows();
if (!active.length) return; if (!active.length) return;
const ids = active.map(row => Number(row.dataset.jobId)).filter(Number.isInteger); const ids = active.map(row => Number(row.dataset.jobId)).filter(Number.isInteger);
requestInProgress = true;
try { try {
const response = await fetch('/api/software-jobs/statuses', { const response = await fetch('/api/software-jobs/statuses', {
method: 'POST', method: 'POST',
@@ -86,10 +89,17 @@
if (activeRows().length) timer = window.setTimeout(refresh, 3000); if (activeRows().length) timer = window.setTimeout(refresh, 3000);
} catch (_) { } catch (_) {
timer = window.setTimeout(refresh, 5000); timer = window.setTimeout(refresh, 5000);
} finally {
requestInProgress = false;
} }
} }
if (activeRows().length) timer = window.setTimeout(refresh, 1200); if (activeRows().length) timer = window.setTimeout(refresh, 1200);
window.addEventListener('asset-table-content-reloaded', event => {
if (event.detail?.table !== table || !activeRows().length) return;
if (timer) window.clearTimeout(timer);
timer = window.setTimeout(refresh, 250);
});
window.addEventListener('beforeunload', () => { window.addEventListener('beforeunload', () => {
if (timer) window.clearTimeout(timer); if (timer) window.clearTimeout(timer);
}, {once: true}); }, {once: true});
+42 -5
View File
@@ -142,6 +142,8 @@
const isAssetTable = table.id === 'asset-table'; const isAssetTable = table.id === 'asset-table';
const usesServerFilters = table.dataset.serverFilters === '1'; const usesServerFilters = table.dataset.serverFilters === '1';
let serverFilterTimer = null; let serverFilterTimer = null;
let serverFilterController = null;
let serverFilterRequestId = 0;
// Normal tables get stable synthetic keys. The asset table already has // Normal tables get stable synthetic keys. The asset table already has
// real data-field keys on its movable columns; selection and action // real data-field keys on its movable columns; selection and action
@@ -316,7 +318,7 @@
if (!usesServerFilters) return; if (!usesServerFilters) return;
if (serverFilterTimer) window.clearTimeout(serverFilterTimer); if (serverFilterTimer) window.clearTimeout(serverFilterTimer);
const navigate = () => { const navigate = async () => {
const target = new URL( const target = new URL(
table.dataset.serverFilterUrl || window.location.pathname, table.dataset.serverFilterUrl || window.location.pathname,
window.location.origin window.location.origin
@@ -331,13 +333,48 @@
if (value) target.searchParams.set(`job_filter_${definition.serverName}`, value); if (value) target.searchParams.set(`job_filter_${definition.serverName}`, value);
}); });
const requestId = ++serverFilterRequestId;
serverFilterController?.abort();
serverFilterController = new AbortController();
table.classList.add('server-filter-loading');
table.setAttribute('aria-busy', 'true');
try { try {
window.sessionStorage.setItem( const response = await fetch(target.toString(), {
'assetmanager:software-jobs:last-server-filter', cache: 'no-store',
document.activeElement?.name || '' headers: {'X-Requested-With': 'AssetManagerTableFilter'},
signal: serverFilterController.signal,
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const html = await response.text();
if (requestId !== serverFilterRequestId) return;
const documentCopy = new DOMParser().parseFromString(html, 'text/html');
const replacement = documentCopy.querySelector(
`table[data-storage-key="${CSS.escape(table.dataset.storageKey || '')}"]`
); );
} catch (_) {} const replacementBody = replacement?.tBodies?.[0];
if (!replacement || !replacementBody) throw new Error('Filtered table not found');
tbody.replaceChildren(...[...replacementBody.rows].map(row => row.cloneNode(true)));
table.dataset.serverFilterTotal = replacement.dataset.serverFilterTotal || String(tbody.rows.length);
window.history.replaceState({}, '', target.toString());
window.AssetManagerTime?.scan(tbody);
applyFilters();
window.dispatchEvent(new CustomEvent('asset-table-content-reloaded', {
detail: {table}
}));
} catch (error) {
if (error?.name !== 'AbortError') {
// Fall back to a normal navigation only when the partial reload fails.
window.location.assign(target.toString()); window.location.assign(target.toString());
}
} finally {
if (requestId === serverFilterRequestId) {
table.classList.remove('server-filter-loading');
table.removeAttribute('aria-busy');
}
}
}; };
if (immediate) navigate(); if (immediate) navigate();
+2 -2
View File
@@ -35,8 +35,8 @@
<th>{{ t('software.platform') }}</th> <th>{{ t('software.platform') }}</th>
<th>{{ t('software.versions') }}</th> <th>{{ t('software.versions') }}</th>
<th data-numeric-filter="1" data-filter-placeholder="{{ t('software.numeric_filter_placeholder') }}">{{ t('software.version_count') }}</th> <th data-numeric-filter="1" data-filter-placeholder="{{ t('software.numeric_filter_placeholder') }}">{{ t('software.version_count') }}</th>
<th data-numeric-filter="1" data-filter-placeholder="{{ t('software.numeric_filter_placeholder') }}">{{ t('software.asset_count') }}</th> <th data-numeric-filter="1" data-filter-placeholder="{{ t('software.numeric_filter_placeholder') }}"><span title="{{ t('software.unique_asset_count_help')|e }}">{{ t('software.unique_asset_count') }} ⓘ</span></th>
<th data-numeric-filter="1" data-filter-placeholder="{{ t('software.numeric_filter_placeholder') }}">{{ t('software.entry_count') }}</th> <th data-numeric-filter="1" data-filter-placeholder="{{ t('software.numeric_filter_placeholder') }}"><span title="{{ t('software.inventory_entry_count_help')|e }}">{{ t('software.inventory_entry_count') }} ⓘ</span></th>
<th>{{ t('software.last_seen') }}</th> <th>{{ t('software.last_seen') }}</th>
</tr></thead> </tr></thead>
<tbody> <tbody>
+1 -1
View File
@@ -1,2 +1,2 @@
APP_VERSION = "0.5.5.30" APP_VERSION = "0.5.5.31"
__version__ = APP_VERSION __version__ = APP_VERSION
+9
View File
@@ -0,0 +1,9 @@
# Version 0.5.5.31
## Changes
- Renamed the aggregated software columns to **Unique assets** and **Inventory entries**.
- Added explanatory tooltips describing the difference between distinct assets and matching inventory records.
- Reworked server-side filters in the recent jobs table to update only the table body instead of reloading the complete page.
- Kept filter controls and the active-filter indicator visible during database queries.
- Preserved live job status updates and bulk restart controls after filtered rows are refreshed.