Presence, AD/SCCM/DNS reasons, monitors, snapshots, new-device priority
Worker: pings every tracked device on a presence sweep (hourly in business hours plus midnight) and records last online; inventory reports whether a device was found in AD and SCCM; monitors from WmiMonitorID; snapshots of the last reachable state replace "Cached"; newly tracked devices are swept and published first; totals count source devices only; per-type timing in Worker-State. Front end: details dialog shows presence, reasons instead of N/A, "Waiting for worker...", monitors and snapshot times, and refreshes in place; Custom View gains Days Offline, Last Online, Found In and monitor columns; manual pair changes show immediately; sync dialog shows timing by type. Custom View gets a large scrollbar; "Exactly ticked" filter.
This commit is contained in:
1 parent
50c2a18a68
commit
7868948bfc
10 files changed
+1230
-356
No files matched your search
+87
-14
@@ -81,8 +81,12 @@
|
||||
|
||||
// Custom View can be wider than the page. A sideways-scrolling box would stop its header
|
||||
// from sticking, so the box clips instead (overflow-x: clip) and the table is shifted
|
||||
// sideways by a scrollbar pinned to the bottom of the window. Wheel, shift+wheel and
|
||||
// touch swipes over the table drive the same scrollbar.
|
||||
// sideways by a scrollbar of our own, pinned to the bottom of the window: a wide track
|
||||
// with a draggable thumb (click the track to page), arrow buttons that repeat while held,
|
||||
// and a position readout. Wheel, shift+wheel, trackpad and touch swipes over the table
|
||||
// drive the same position. customHScroll.by(dx) is also used by the column drag.
|
||||
const customHScroll = { left: 0, max: 0, bar: null, by(dx) { this.set(this.left + dx); }, set() {} };
|
||||
window.customHScroll = customHScroll;
|
||||
function setupCustomHScroll() {
|
||||
const box = document.getElementById('scroll-custom');
|
||||
const host = document.getElementById('custom-table-host');
|
||||
@@ -93,27 +97,96 @@
|
||||
bar = document.createElement('div');
|
||||
bar.id = 'custom-hscroll';
|
||||
bar.className = 'custom-hscroll no-copy';
|
||||
bar.appendChild(document.createElement('div'));
|
||||
bar.innerHTML =
|
||||
'<button type="button" class="chs-btn chs-left" aria-label="Scroll columns left">◀</button>' +
|
||||
'<div class="chs-track" role="scrollbar" aria-orientation="horizontal" aria-controls="table-custom" tabindex="0"><div class="chs-thumb"><span></span></div></div>' +
|
||||
'<button type="button" class="chs-btn chs-right" aria-label="Scroll columns right">▶</button>' +
|
||||
'<span class="chs-info"></span>';
|
||||
box.insertAdjacentElement('afterend', bar);
|
||||
const applyX = () => { host.style.transform = bar.scrollLeft ? `translateX(${-bar.scrollLeft}px)` : ''; };
|
||||
bar.addEventListener('scroll', applyX, { passive: true });
|
||||
customHScroll.bar = bar;
|
||||
const track = bar.querySelector('.chs-track'), thumb = bar.querySelector('.chs-thumb');
|
||||
const info = bar.querySelector('.chs-info');
|
||||
const geom = () => {
|
||||
const tw = track.clientWidth, view = box.clientWidth, full = view + customHScroll.max;
|
||||
const w = Math.max(48, Math.round(tw * view / Math.max(full, 1)));
|
||||
return { tw, w, room: Math.max(tw - w, 1) };
|
||||
};
|
||||
customHScroll.draw = () => {
|
||||
const g = geom();
|
||||
thumb.style.width = g.w + 'px';
|
||||
thumb.style.transform = `translateX(${Math.round(g.room * (customHScroll.max ? customHScroll.left / customHScroll.max : 0))}px)`;
|
||||
const pct = customHScroll.max ? Math.round(100 * customHScroll.left / customHScroll.max) : 0;
|
||||
info.textContent = pct + '%';
|
||||
track.setAttribute('aria-valuenow', pct);
|
||||
bar.querySelector('.chs-left').disabled = customHScroll.left <= 0;
|
||||
bar.querySelector('.chs-right').disabled = customHScroll.left >= customHScroll.max;
|
||||
};
|
||||
customHScroll.set = x => {
|
||||
const v = Math.max(0, Math.min(customHScroll.max, Math.round(x)));
|
||||
customHScroll.left = v;
|
||||
host.style.transform = v ? `translateX(${-v}px)` : '';
|
||||
customHScroll.draw();
|
||||
};
|
||||
// Thumb drag
|
||||
thumb.addEventListener('pointerdown', e => {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
thumb.setPointerCapture(e.pointerId);
|
||||
bar.classList.add('dragging');
|
||||
const x0 = e.clientX, l0 = customHScroll.left, g = geom();
|
||||
const move = ev => customHScroll.set(l0 + (ev.clientX - x0) * customHScroll.max / g.room);
|
||||
const up = () => { bar.classList.remove('dragging'); thumb.removeEventListener('pointermove', move); thumb.removeEventListener('pointerup', up); thumb.removeEventListener('pointercancel', up); };
|
||||
thumb.addEventListener('pointermove', move);
|
||||
thumb.addEventListener('pointerup', up);
|
||||
thumb.addEventListener('pointercancel', up);
|
||||
});
|
||||
// Click on the track: jump so the thumb centres on the pointer
|
||||
track.addEventListener('pointerdown', e => {
|
||||
if (e.target !== track) return;
|
||||
const g = geom(), r = track.getBoundingClientRect();
|
||||
customHScroll.set(((e.clientX - r.left - g.w / 2) / g.room) * customHScroll.max);
|
||||
});
|
||||
track.addEventListener('keydown', e => {
|
||||
const page = box.clientWidth * 0.8;
|
||||
const k = { ArrowLeft: -60, ArrowRight: 60, PageUp: -page, PageDown: page }[e.key];
|
||||
if (k) { e.preventDefault(); customHScroll.by(k); }
|
||||
else if (e.key === 'Home') { e.preventDefault(); customHScroll.set(0); }
|
||||
else if (e.key === 'End') { e.preventDefault(); customHScroll.set(customHScroll.max); }
|
||||
});
|
||||
// Arrow buttons: one step per click, keep going while held
|
||||
bar.querySelectorAll('.chs-btn').forEach(btn => {
|
||||
const dir = btn.classList.contains('chs-left') ? -1 : 1;
|
||||
let timer = null;
|
||||
const stop = () => { clearInterval(timer); clearTimeout(timer); timer = null; };
|
||||
btn.addEventListener('pointerdown', e => {
|
||||
e.preventDefault();
|
||||
customHScroll.by(dir * 160);
|
||||
timer = setTimeout(() => { timer = setInterval(() => customHScroll.by(dir * 40), 30); }, 300);
|
||||
});
|
||||
['pointerup', 'pointerleave', 'pointercancel'].forEach(t => btn.addEventListener(t, stop));
|
||||
});
|
||||
box.addEventListener('wheel', e => {
|
||||
if (bar.hidden) return;
|
||||
const dx = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : (e.shiftKey ? e.deltaY : 0);
|
||||
if (!dx) return;
|
||||
e.preventDefault();
|
||||
bar.scrollLeft += dx;
|
||||
customHScroll.by(dx);
|
||||
}, { passive: false });
|
||||
bar.addEventListener('wheel', e => {
|
||||
const d = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
|
||||
if (!d) return;
|
||||
e.preventDefault();
|
||||
customHScroll.by(d);
|
||||
}, { passive: false });
|
||||
let start = null;
|
||||
box.addEventListener('touchstart', e => {
|
||||
if (bar.hidden || e.touches.length !== 1) { start = null; return; }
|
||||
start = { x: e.touches[0].clientX, y: e.touches[0].clientY, left: bar.scrollLeft, lock: null };
|
||||
start = { x: e.touches[0].clientX, y: e.touches[0].clientY, left: customHScroll.left, lock: null };
|
||||
}, { passive: true });
|
||||
box.addEventListener('touchmove', e => {
|
||||
if (!start) return;
|
||||
const dx = e.touches[0].clientX - start.x, dy = e.touches[0].clientY - start.y;
|
||||
if (!start.lock && Math.hypot(dx, dy) > 8) start.lock = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y';
|
||||
if (start.lock === 'x') { e.preventDefault(); bar.scrollLeft = start.left - dx; }
|
||||
if (start.lock === 'x') { e.preventDefault(); customHScroll.set(start.left - dx); }
|
||||
}, { passive: false });
|
||||
box.addEventListener('touchend', () => { start = null; }, { passive: true });
|
||||
}
|
||||
@@ -124,11 +197,11 @@
|
||||
}
|
||||
const full = Math.ceil(table.getBoundingClientRect().width);
|
||||
const view = box.clientWidth;
|
||||
const inner = bar.firstChild;
|
||||
if (inner.style.width !== full + 'px') inner.style.width = full + 'px';
|
||||
const hide = full <= view + 1;
|
||||
const max = Math.max(0, full - view);
|
||||
const hide = max <= 1;
|
||||
if (bar.hidden !== hide) bar.hidden = hide;
|
||||
if (hide && bar.scrollLeft) { bar.scrollLeft = 0; host.style.transform = ''; }
|
||||
customHScroll.max = hide ? 0 : max;
|
||||
customHScroll.set(customHScroll.left);
|
||||
}
|
||||
|
||||
// Auto-sizing text boxes (Workbench stacks) only grow while typing in the original;
|
||||
@@ -206,8 +279,8 @@
|
||||
const box = document.getElementById('scroll-custom');
|
||||
if (bar && !bar.hidden && box) {
|
||||
const r = box.getBoundingClientRect();
|
||||
if (e.clientX < r.left + 40) bar.scrollLeft -= 18;
|
||||
else if (e.clientX > r.right - 40) bar.scrollLeft += 18;
|
||||
if (e.clientX < r.left + 40) customHScroll.by(-18);
|
||||
else if (e.clientX > r.right - 40) customHScroll.by(18);
|
||||
}
|
||||
|
||||
// Target slot = number of other columns whose middle is left of the pointer
|
||||
|
||||
Reference in new issue
Block a user