avoiding triple reload in last job list
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -1,2 +1,2 @@
|
||||
APP_VERSION = "0.5.5.30"
|
||||
APP_VERSION = "0.5.5.31"
|
||||
__version__ = APP_VERSION
|
||||
|
||||
Reference in New Issue
Block a user