feat: live-updating status panel with per-task poll timings
- Each service fetch is individually timed (SABnzbd, Sonarr, Radarr, qBit) - Status panel shows timing bar chart with ms per task and total - Shows 'Last Poll' age that updates live - Shows client refresh rate (1s/5s/10s/Off) - Status panel auto-refreshes in sync with dashboard refresh cycle - Changing refresh rate restarts the status panel refresh too - TTL counters update live on each refresh
This commit is contained in:
@@ -51,6 +51,14 @@ function handleRefreshRateChange(e) {
|
|||||||
const rate = parseInt(e.target.value);
|
const rate = parseInt(e.target.value);
|
||||||
currentRefreshRate = rate;
|
currentRefreshRate = rate;
|
||||||
startAutoRefresh();
|
startAutoRefresh();
|
||||||
|
// Restart status panel refresh if it's open
|
||||||
|
const statusPanel = document.getElementById('status-panel');
|
||||||
|
if (statusPanel && statusPanel.style.display !== 'none') {
|
||||||
|
if (statusRefreshHandle) { clearInterval(statusRefreshHandle); statusRefreshHandle = null; }
|
||||||
|
if (currentRefreshRate > 0) {
|
||||||
|
statusRefreshHandle = setInterval(refreshStatusPanel, currentRefreshRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleShowAllToggle(e) {
|
function handleShowAllToggle(e) {
|
||||||
@@ -569,21 +577,42 @@ function escapeHtml(str) {
|
|||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let statusRefreshHandle = null;
|
||||||
|
|
||||||
async function toggleStatusPanel() {
|
async function toggleStatusPanel() {
|
||||||
const panel = document.getElementById('status-panel');
|
const panel = document.getElementById('status-panel');
|
||||||
if (panel.style.display !== 'none') {
|
if (panel.style.display !== 'none') {
|
||||||
panel.style.display = 'none';
|
panel.style.display = 'none';
|
||||||
|
if (statusRefreshHandle) { clearInterval(statusRefreshHandle); statusRefreshHandle = null; }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
panel.style.display = 'block';
|
panel.style.display = 'block';
|
||||||
panel.innerHTML = '<p class="status-loading">Loading status…</p>';
|
await refreshStatusPanel();
|
||||||
|
// Auto-refresh in sync with dashboard refresh rate
|
||||||
|
if (statusRefreshHandle) clearInterval(statusRefreshHandle);
|
||||||
|
if (currentRefreshRate > 0) {
|
||||||
|
statusRefreshHandle = setInterval(refreshStatusPanel, currentRefreshRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeStatusPanel() {
|
||||||
|
document.getElementById('status-panel').style.display = 'none';
|
||||||
|
if (statusRefreshHandle) { clearInterval(statusRefreshHandle); statusRefreshHandle = null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshStatusPanel() {
|
||||||
|
const panel = document.getElementById('status-panel');
|
||||||
|
if (!panel || panel.style.display === 'none') return;
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/dashboard/status');
|
const res = await fetch('/api/dashboard/status');
|
||||||
if (!res.ok) throw new Error('Failed to fetch status');
|
if (!res.ok) throw new Error('Failed to fetch status');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
renderStatusPanel(data, panel);
|
renderStatusPanel(data, panel);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
panel.innerHTML = '<p class="status-error">Failed to load status.</p>';
|
// Don't overwrite panel on transient error during auto-refresh
|
||||||
|
if (!panel.innerHTML || panel.innerHTML.includes('status-loading')) {
|
||||||
|
panel.innerHTML = '<p class="status-error">Failed to load status.</p>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,11 +624,12 @@ function renderStatusPanel(data, panel) {
|
|||||||
const uptime = `${hrs}h ${mins}m ${secs}s`;
|
const uptime = `${hrs}h ${mins}m ${secs}s`;
|
||||||
|
|
||||||
const totalKB = (data.cache.totalSizeBytes / 1024).toFixed(1);
|
const totalKB = (data.cache.totalSizeBytes / 1024).toFixed(1);
|
||||||
|
const refreshLabel = currentRefreshRate > 0 ? (currentRefreshRate / 1000) + 's' : 'Off';
|
||||||
|
|
||||||
let html = `
|
let html = `
|
||||||
<div class="status-header">
|
<div class="status-header">
|
||||||
<h3>Server Status</h3>
|
<h3>Server Status</h3>
|
||||||
<button class="status-close" onclick="document.getElementById('status-panel').style.display='none'">×</button>
|
<button class="status-close" onclick="closeStatusPanel()">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-grid">
|
<div class="status-grid">
|
||||||
<div class="status-card">
|
<div class="status-card">
|
||||||
@@ -613,7 +643,31 @@ function renderStatusPanel(data, panel) {
|
|||||||
<div class="status-card-title">Polling</div>
|
<div class="status-card-title">Polling</div>
|
||||||
<div class="status-row"><span>Mode</span><span>${data.polling.enabled ? 'Background' : 'On-demand'}</span></div>
|
<div class="status-row"><span>Mode</span><span>${data.polling.enabled ? 'Background' : 'On-demand'}</span></div>
|
||||||
${data.polling.enabled ? `<div class="status-row"><span>Interval</span><span>${data.polling.intervalMs / 1000}s</span></div>` : ''}
|
${data.polling.enabled ? `<div class="status-row"><span>Interval</span><span>${data.polling.intervalMs / 1000}s</span></div>` : ''}
|
||||||
</div>
|
<div class="status-row"><span>Client refresh</span><span>${refreshLabel}</span></div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
// Poll timings card
|
||||||
|
const lp = data.polling.lastPoll;
|
||||||
|
if (lp) {
|
||||||
|
const pollAge = Math.round((Date.now() - new Date(lp.timestamp).getTime()) / 1000);
|
||||||
|
html += `
|
||||||
|
<div class="status-card status-card-wide">
|
||||||
|
<div class="status-card-title">Last Poll (${lp.totalMs}ms total, ${pollAge}s ago)</div>
|
||||||
|
<div class="status-timings">`;
|
||||||
|
for (const t of lp.tasks) {
|
||||||
|
const barWidth = lp.totalMs > 0 ? Math.max(2, (t.ms / lp.totalMs) * 100) : 0;
|
||||||
|
html += `
|
||||||
|
<div class="timing-row">
|
||||||
|
<span class="timing-label">${escapeHtml(t.label)}</span>
|
||||||
|
<div class="timing-bar-bg"><div class="timing-bar" style="width:${barWidth.toFixed(1)}%"></div></div>
|
||||||
|
<span class="timing-value">${t.ms}ms</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
html += `</div></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache table
|
||||||
|
html += `
|
||||||
<div class="status-card status-card-wide">
|
<div class="status-card status-card-wide">
|
||||||
<div class="status-card-title">Cache (${data.cache.entryCount} entries, ${totalKB} KB)</div>
|
<div class="status-card-title">Cache (${data.cache.entryCount} entries, ${totalKB} KB)</div>
|
||||||
<table class="status-table">
|
<table class="status-table">
|
||||||
|
|||||||
@@ -862,6 +862,51 @@ body {
|
|||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-timings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timing-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timing-label {
|
||||||
|
width: 110px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timing-bar-bg {
|
||||||
|
flex: 1;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timing-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--accent);
|
||||||
|
border-radius: 4px;
|
||||||
|
min-width: 2px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timing-value {
|
||||||
|
width: 50px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.status-loading, .status-error {
|
.status-loading, .status-error {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const router = express.Router();
|
|||||||
|
|
||||||
const { mapTorrentToDownload } = require('../utils/qbittorrent');
|
const { mapTorrentToDownload } = require('../utils/qbittorrent');
|
||||||
const cache = require('../utils/cache');
|
const cache = require('../utils/cache');
|
||||||
const { pollAllServices, POLLING_ENABLED } = require('../utils/poller');
|
const { pollAllServices, getLastPollTimings, POLLING_ENABLED } = require('../utils/poller');
|
||||||
|
|
||||||
const EMBY_URL = process.env.EMBY_URL;
|
const EMBY_URL = process.env.EMBY_URL;
|
||||||
const EMBY_API_KEY = process.env.EMBY_API_KEY;
|
const EMBY_API_KEY = process.env.EMBY_API_KEY;
|
||||||
@@ -625,7 +625,8 @@ router.get('/status', (req, res) => {
|
|||||||
},
|
},
|
||||||
polling: {
|
polling: {
|
||||||
enabled: POLLING_ENABLED,
|
enabled: POLLING_ENABLED,
|
||||||
intervalMs: POLLING_ENABLED ? require('../utils/poller').POLL_INTERVAL : 0
|
intervalMs: POLLING_ENABLED ? require('../utils/poller').POLL_INTERVAL : 0,
|
||||||
|
lastPoll: getLastPollTimings()
|
||||||
},
|
},
|
||||||
cache: cacheStats
|
cache: cacheStats
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ const POLL_INTERVAL = (rawPollInterval === 'off' || rawPollInterval === 'false'
|
|||||||
const POLLING_ENABLED = POLL_INTERVAL > 0;
|
const POLLING_ENABLED = POLL_INTERVAL > 0;
|
||||||
|
|
||||||
let polling = false;
|
let polling = false;
|
||||||
|
let lastPollTimings = null;
|
||||||
|
|
||||||
|
// Timed fetch helper: runs a fetch and records how long it took
|
||||||
|
async function timed(label, fn) {
|
||||||
|
const t0 = Date.now();
|
||||||
|
const result = await fn();
|
||||||
|
return { label, result, ms: Date.now() - t0 };
|
||||||
|
}
|
||||||
|
|
||||||
async function pollAllServices() {
|
async function pollAllServices() {
|
||||||
if (polling) {
|
if (polling) {
|
||||||
@@ -28,40 +36,33 @@ async function pollAllServices() {
|
|||||||
const sonarrInstances = getSonarrInstances();
|
const sonarrInstances = getSonarrInstances();
|
||||||
const radarrInstances = getRadarrInstances();
|
const radarrInstances = getRadarrInstances();
|
||||||
|
|
||||||
// All fetches in parallel
|
// All fetches in parallel, each individually timed
|
||||||
const [
|
const results = await Promise.all([
|
||||||
sabQueues, sabHistories,
|
timed('SABnzbd Queue', () => Promise.all(sabInstances.map(inst =>
|
||||||
sonarrTagsResults, sonarrQueues, sonarrHistories, sonarrSeriesResults,
|
|
||||||
radarrQueues, radarrHistories, radarrMoviesResults, radarrTagsResults,
|
|
||||||
qbittorrentTorrents
|
|
||||||
] = await Promise.all([
|
|
||||||
// SABnzbd
|
|
||||||
Promise.all(sabInstances.map(inst =>
|
|
||||||
axios.get(`${inst.url}/api`, {
|
axios.get(`${inst.url}/api`, {
|
||||||
params: { mode: 'queue', apikey: inst.apiKey, output: 'json' }
|
params: { mode: 'queue', apikey: inst.apiKey, output: 'json' }
|
||||||
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
||||||
console.error(`[Poller] SABnzbd ${inst.id} queue error:`, err.message);
|
console.error(`[Poller] SABnzbd ${inst.id} queue error:`, err.message);
|
||||||
return { instance: inst.id, data: { queue: { slots: [] } } };
|
return { instance: inst.id, data: { queue: { slots: [] } } };
|
||||||
})
|
})
|
||||||
)),
|
))),
|
||||||
Promise.all(sabInstances.map(inst =>
|
timed('SABnzbd History', () => Promise.all(sabInstances.map(inst =>
|
||||||
axios.get(`${inst.url}/api`, {
|
axios.get(`${inst.url}/api`, {
|
||||||
params: { mode: 'history', apikey: inst.apiKey, output: 'json', limit: 20 }
|
params: { mode: 'history', apikey: inst.apiKey, output: 'json', limit: 20 }
|
||||||
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
||||||
console.error(`[Poller] SABnzbd ${inst.id} history error:`, err.message);
|
console.error(`[Poller] SABnzbd ${inst.id} history error:`, err.message);
|
||||||
return { instance: inst.id, data: { history: { slots: [] } } };
|
return { instance: inst.id, data: { history: { slots: [] } } };
|
||||||
})
|
})
|
||||||
)),
|
))),
|
||||||
// Sonarr
|
timed('Sonarr Tags', () => Promise.all(sonarrInstances.map(inst =>
|
||||||
Promise.all(sonarrInstances.map(inst =>
|
|
||||||
axios.get(`${inst.url}/api/v3/tag`, {
|
axios.get(`${inst.url}/api/v3/tag`, {
|
||||||
headers: { 'X-Api-Key': inst.apiKey }
|
headers: { 'X-Api-Key': inst.apiKey }
|
||||||
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
||||||
console.error(`[Poller] Sonarr ${inst.id} tags error:`, err.message);
|
console.error(`[Poller] Sonarr ${inst.id} tags error:`, err.message);
|
||||||
return { instance: inst.id, data: [] };
|
return { instance: inst.id, data: [] };
|
||||||
})
|
})
|
||||||
)),
|
))),
|
||||||
Promise.all(sonarrInstances.map(inst =>
|
timed('Sonarr Queue', () => Promise.all(sonarrInstances.map(inst =>
|
||||||
axios.get(`${inst.url}/api/v3/queue`, {
|
axios.get(`${inst.url}/api/v3/queue`, {
|
||||||
headers: { 'X-Api-Key': inst.apiKey },
|
headers: { 'X-Api-Key': inst.apiKey },
|
||||||
params: { includeSeries: true, includeEpisode: true }
|
params: { includeSeries: true, includeEpisode: true }
|
||||||
@@ -69,8 +70,8 @@ async function pollAllServices() {
|
|||||||
console.error(`[Poller] Sonarr ${inst.id} queue error:`, err.message);
|
console.error(`[Poller] Sonarr ${inst.id} queue error:`, err.message);
|
||||||
return { instance: inst.id, data: { records: [] } };
|
return { instance: inst.id, data: { records: [] } };
|
||||||
})
|
})
|
||||||
)),
|
))),
|
||||||
Promise.all(sonarrInstances.map(inst =>
|
timed('Sonarr History', () => Promise.all(sonarrInstances.map(inst =>
|
||||||
axios.get(`${inst.url}/api/v3/history`, {
|
axios.get(`${inst.url}/api/v3/history`, {
|
||||||
headers: { 'X-Api-Key': inst.apiKey },
|
headers: { 'X-Api-Key': inst.apiKey },
|
||||||
params: { pageSize: 20, includeSeries: true, includeEpisode: true }
|
params: { pageSize: 20, includeSeries: true, includeEpisode: true }
|
||||||
@@ -78,17 +79,16 @@ async function pollAllServices() {
|
|||||||
console.error(`[Poller] Sonarr ${inst.id} history error:`, err.message);
|
console.error(`[Poller] Sonarr ${inst.id} history error:`, err.message);
|
||||||
return { instance: inst.id, data: { records: [] } };
|
return { instance: inst.id, data: { records: [] } };
|
||||||
})
|
})
|
||||||
)),
|
))),
|
||||||
Promise.all(sonarrInstances.map(inst =>
|
timed('Sonarr Series', () => Promise.all(sonarrInstances.map(inst =>
|
||||||
axios.get(`${inst.url}/api/v3/series`, {
|
axios.get(`${inst.url}/api/v3/series`, {
|
||||||
headers: { 'X-Api-Key': inst.apiKey }
|
headers: { 'X-Api-Key': inst.apiKey }
|
||||||
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
||||||
console.error(`[Poller] Sonarr ${inst.id} series error:`, err.message);
|
console.error(`[Poller] Sonarr ${inst.id} series error:`, err.message);
|
||||||
return { instance: inst.id, data: [] };
|
return { instance: inst.id, data: [] };
|
||||||
})
|
})
|
||||||
)),
|
))),
|
||||||
// Radarr
|
timed('Radarr Queue', () => Promise.all(radarrInstances.map(inst =>
|
||||||
Promise.all(radarrInstances.map(inst =>
|
|
||||||
axios.get(`${inst.url}/api/v3/queue`, {
|
axios.get(`${inst.url}/api/v3/queue`, {
|
||||||
headers: { 'X-Api-Key': inst.apiKey },
|
headers: { 'X-Api-Key': inst.apiKey },
|
||||||
params: { includeMovie: true }
|
params: { includeMovie: true }
|
||||||
@@ -96,8 +96,8 @@ async function pollAllServices() {
|
|||||||
console.error(`[Poller] Radarr ${inst.id} queue error:`, err.message);
|
console.error(`[Poller] Radarr ${inst.id} queue error:`, err.message);
|
||||||
return { instance: inst.id, data: { records: [] } };
|
return { instance: inst.id, data: { records: [] } };
|
||||||
})
|
})
|
||||||
)),
|
))),
|
||||||
Promise.all(radarrInstances.map(inst =>
|
timed('Radarr History', () => Promise.all(radarrInstances.map(inst =>
|
||||||
axios.get(`${inst.url}/api/v3/history`, {
|
axios.get(`${inst.url}/api/v3/history`, {
|
||||||
headers: { 'X-Api-Key': inst.apiKey },
|
headers: { 'X-Api-Key': inst.apiKey },
|
||||||
params: { pageSize: 20, includeMovie: true }
|
params: { pageSize: 20, includeMovie: true }
|
||||||
@@ -105,30 +105,46 @@ async function pollAllServices() {
|
|||||||
console.error(`[Poller] Radarr ${inst.id} history error:`, err.message);
|
console.error(`[Poller] Radarr ${inst.id} history error:`, err.message);
|
||||||
return { instance: inst.id, data: { records: [] } };
|
return { instance: inst.id, data: { records: [] } };
|
||||||
})
|
})
|
||||||
)),
|
))),
|
||||||
Promise.all(radarrInstances.map(inst =>
|
timed('Radarr Movies', () => Promise.all(radarrInstances.map(inst =>
|
||||||
axios.get(`${inst.url}/api/v3/movie`, {
|
axios.get(`${inst.url}/api/v3/movie`, {
|
||||||
headers: { 'X-Api-Key': inst.apiKey }
|
headers: { 'X-Api-Key': inst.apiKey }
|
||||||
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
||||||
console.error(`[Poller] Radarr ${inst.id} movies error:`, err.message);
|
console.error(`[Poller] Radarr ${inst.id} movies error:`, err.message);
|
||||||
return { instance: inst.id, data: [] };
|
return { instance: inst.id, data: [] };
|
||||||
})
|
})
|
||||||
)),
|
))),
|
||||||
Promise.all(radarrInstances.map(inst =>
|
timed('Radarr Tags', () => Promise.all(radarrInstances.map(inst =>
|
||||||
axios.get(`${inst.url}/api/v3/tag`, {
|
axios.get(`${inst.url}/api/v3/tag`, {
|
||||||
headers: { 'X-Api-Key': inst.apiKey }
|
headers: { 'X-Api-Key': inst.apiKey }
|
||||||
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
||||||
console.error(`[Poller] Radarr ${inst.id} tags error:`, err.message);
|
console.error(`[Poller] Radarr ${inst.id} tags error:`, err.message);
|
||||||
return { instance: inst.id, data: [] };
|
return { instance: inst.id, data: [] };
|
||||||
})
|
})
|
||||||
)),
|
))),
|
||||||
// qBittorrent
|
timed('qBittorrent', () => getTorrents().catch(err => {
|
||||||
getTorrents().catch(err => {
|
|
||||||
console.error(`[Poller] qBittorrent error:`, err.message);
|
console.error(`[Poller] qBittorrent error:`, err.message);
|
||||||
return [];
|
return [];
|
||||||
})
|
}))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const [
|
||||||
|
{ result: sabQueues }, { result: sabHistories },
|
||||||
|
{ result: sonarrTagsResults }, { result: sonarrQueues },
|
||||||
|
{ result: sonarrHistories }, { result: sonarrSeriesResults },
|
||||||
|
{ result: radarrQueues }, { result: radarrHistories },
|
||||||
|
{ result: radarrMoviesResults }, { result: radarrTagsResults },
|
||||||
|
{ result: qbittorrentTorrents }
|
||||||
|
] = results;
|
||||||
|
|
||||||
|
// Store per-task timings
|
||||||
|
const totalMs = Date.now() - start;
|
||||||
|
lastPollTimings = {
|
||||||
|
totalMs,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
tasks: results.map(r => ({ label: r.label, ms: r.ms }))
|
||||||
|
};
|
||||||
|
|
||||||
// When polling is active, TTL is 3x interval to avoid gaps between polls
|
// When polling is active, TTL is 3x interval to avoid gaps between polls
|
||||||
// When polling is disabled (on-demand), use 30s so data refreshes on next request after expiry
|
// When polling is disabled (on-demand), use 30s so data refreshes on next request after expiry
|
||||||
const cacheTTL = POLLING_ENABLED ? POLL_INTERVAL * 3 : 30000;
|
const cacheTTL = POLLING_ENABLED ? POLL_INTERVAL * 3 : 30000;
|
||||||
@@ -205,4 +221,8 @@ function stopPoller() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { startPoller, stopPoller, pollAllServices, POLL_INTERVAL, POLLING_ENABLED };
|
function getLastPollTimings() {
|
||||||
|
return lastPollTimings;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { startPoller, stopPoller, pollAllServices, getLastPollTimings, POLL_INTERVAL, POLLING_ENABLED };
|
||||||
|
|||||||
Reference in New Issue
Block a user