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
+4
View File
@@ -799,6 +799,10 @@ BASE_TRANSLATIONS.update({
"software.version_count": ("Number of versions", "Anzahl Versionen"),
"software.asset_count": ("Assets", "Assets"),
"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.last_job": ("Last job", "Letzter Job"),
"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. */
.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();
});
window.addEventListener('software-job-status-changed', update);
window.addEventListener('asset-table-content-reloaded', event => {
if (event.detail?.table === table) update();
});
update();
}
+10
View File
@@ -69,10 +69,13 @@
}
let timer = null;
let requestInProgress = false;
async function refresh() {
if (requestInProgress) return;
const active = activeRows();
if (!active.length) return;
const ids = active.map(row => Number(row.dataset.jobId)).filter(Number.isInteger);
requestInProgress = true;
try {
const response = await fetch('/api/software-jobs/statuses', {
method: 'POST',
@@ -86,10 +89,17 @@
if (activeRows().length) timer = window.setTimeout(refresh, 3000);
} catch (_) {
timer = window.setTimeout(refresh, 5000);
} finally {
requestInProgress = false;
}
}
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', () => {
if (timer) window.clearTimeout(timer);
}, {once: true});
+43 -6
View File
@@ -142,6 +142,8 @@
const isAssetTable = table.id === 'asset-table';
const usesServerFilters = table.dataset.serverFilters === '1';
let serverFilterTimer = null;
let serverFilterController = null;
let serverFilterRequestId = 0;
// Normal tables get stable synthetic keys. The asset table already has
// real data-field keys on its movable columns; selection and action
@@ -316,7 +318,7 @@
if (!usesServerFilters) return;
if (serverFilterTimer) window.clearTimeout(serverFilterTimer);
const navigate = () => {
const navigate = async () => {
const target = new URL(
table.dataset.serverFilterUrl || window.location.pathname,
window.location.origin
@@ -331,13 +333,48 @@
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 {
window.sessionStorage.setItem(
'assetmanager:software-jobs:last-server-filter',
document.activeElement?.name || ''
const response = await fetch(target.toString(), {
cache: 'no-store',
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 (_) {}
window.location.assign(target.toString());
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());
}
} finally {
if (requestId === serverFilterRequestId) {
table.classList.remove('server-filter-loading');
table.removeAttribute('aria-busy');
}
}
};
if (immediate) navigate();
+2 -2
View File
@@ -35,8 +35,8 @@
<th>{{ t('software.platform') }}</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.asset_count') }}</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.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') }}"><span title="{{ t('software.inventory_entry_count_help')|e }}">{{ t('software.inventory_entry_count') }} ⓘ</span></th>
<th>{{ t('software.last_seen') }}</th>
</tr></thead>
<tbody>
+1 -1
View File
@@ -1,2 +1,2 @@
APP_VERSION = "0.5.5.30"
APP_VERSION = "0.5.5.31"
__version__ = APP_VERSION