feat(dashboard): auto-refresh after sync, configurable polling, extra filters
- Detect missed sync completions via last_run.run_id comparison - Load polling interval from settings (dashboard_poll_seconds, default 5s) - Add 1min/3min scheduler interval options - Add 1zi/2zile period filter options - New Dashboard settings card for polling interval Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ class AppSettingsUpdate(BaseModel):
|
|||||||
gomag_api_shop: str = ""
|
gomag_api_shop: str = ""
|
||||||
gomag_order_days_back: str = "7"
|
gomag_order_days_back: str = "7"
|
||||||
gomag_limit: str = "100"
|
gomag_limit: str = "100"
|
||||||
|
dashboard_poll_seconds: str = "5"
|
||||||
|
|
||||||
|
|
||||||
# API endpoints
|
# API endpoints
|
||||||
@@ -600,6 +601,7 @@ async def get_app_settings():
|
|||||||
"gomag_api_shop": s.get("gomag_api_shop", "") or config_settings.GOMAG_API_SHOP,
|
"gomag_api_shop": s.get("gomag_api_shop", "") or config_settings.GOMAG_API_SHOP,
|
||||||
"gomag_order_days_back": s.get("gomag_order_days_back", "") or str(config_settings.GOMAG_ORDER_DAYS_BACK),
|
"gomag_order_days_back": s.get("gomag_order_days_back", "") or str(config_settings.GOMAG_ORDER_DAYS_BACK),
|
||||||
"gomag_limit": s.get("gomag_limit", "") or str(config_settings.GOMAG_LIMIT),
|
"gomag_limit": s.get("gomag_limit", "") or str(config_settings.GOMAG_LIMIT),
|
||||||
|
"dashboard_poll_seconds": s.get("dashboard_poll_seconds", "5"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -618,6 +620,7 @@ async def update_app_settings(config: AppSettingsUpdate):
|
|||||||
await sqlite_service.set_app_setting("gomag_api_shop", config.gomag_api_shop)
|
await sqlite_service.set_app_setting("gomag_api_shop", config.gomag_api_shop)
|
||||||
await sqlite_service.set_app_setting("gomag_order_days_back", config.gomag_order_days_back)
|
await sqlite_service.set_app_setting("gomag_order_days_back", config.gomag_order_days_back)
|
||||||
await sqlite_service.set_app_setting("gomag_limit", config.gomag_limit)
|
await sqlite_service.set_app_setting("gomag_limit", config.gomag_limit)
|
||||||
|
await sqlite_service.set_app_setting("dashboard_poll_seconds", config.dashboard_poll_seconds)
|
||||||
return {"success": True}
|
return {"success": True}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,21 +13,32 @@ let _pollInterval = null;
|
|||||||
let _lastSyncStatus = null;
|
let _lastSyncStatus = null;
|
||||||
let _lastRunId = null;
|
let _lastRunId = null;
|
||||||
let _currentRunId = null;
|
let _currentRunId = null;
|
||||||
|
let _pollIntervalMs = 5000; // default, overridden from settings
|
||||||
|
let _knownLastRunId = null; // track last_run.run_id to detect missed syncs
|
||||||
|
|
||||||
// ── Init ──────────────────────────────────────────
|
// ── Init ──────────────────────────────────────────
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
await initPollInterval();
|
||||||
loadSchedulerStatus();
|
loadSchedulerStatus();
|
||||||
loadDashOrders();
|
loadDashOrders();
|
||||||
startSyncPolling();
|
startSyncPolling();
|
||||||
wireFilterBar();
|
wireFilterBar();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function initPollInterval() {
|
||||||
|
try {
|
||||||
|
const data = await fetchJSON('/api/settings');
|
||||||
|
const sec = parseInt(data.dashboard_poll_seconds) || 5;
|
||||||
|
_pollIntervalMs = sec * 1000;
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Smart Sync Polling ────────────────────────────
|
// ── Smart Sync Polling ────────────────────────────
|
||||||
|
|
||||||
function startSyncPolling() {
|
function startSyncPolling() {
|
||||||
if (_pollInterval) clearInterval(_pollInterval);
|
if (_pollInterval) clearInterval(_pollInterval);
|
||||||
_pollInterval = setInterval(pollSyncStatus, 30000);
|
_pollInterval = setInterval(pollSyncStatus, _pollIntervalMs);
|
||||||
pollSyncStatus(); // immediate first call
|
pollSyncStatus(); // immediate first call
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +48,12 @@ async function pollSyncStatus() {
|
|||||||
updateSyncPanel(data);
|
updateSyncPanel(data);
|
||||||
const isRunning = data.status === 'running';
|
const isRunning = data.status === 'running';
|
||||||
const wasRunning = _lastSyncStatus === 'running';
|
const wasRunning = _lastSyncStatus === 'running';
|
||||||
|
|
||||||
|
// Detect missed sync completions via last_run.run_id change
|
||||||
|
const newLastRunId = data.last_run?.run_id || null;
|
||||||
|
const missedSync = !isRunning && !wasRunning && _knownLastRunId && newLastRunId && newLastRunId !== _knownLastRunId;
|
||||||
|
_knownLastRunId = newLastRunId;
|
||||||
|
|
||||||
if (isRunning && !wasRunning) {
|
if (isRunning && !wasRunning) {
|
||||||
// Switched to running — speed up polling
|
// Switched to running — speed up polling
|
||||||
clearInterval(_pollInterval);
|
clearInterval(_pollInterval);
|
||||||
@@ -44,7 +61,10 @@ async function pollSyncStatus() {
|
|||||||
} else if (!isRunning && wasRunning) {
|
} else if (!isRunning && wasRunning) {
|
||||||
// Sync just completed — slow down and refresh orders
|
// Sync just completed — slow down and refresh orders
|
||||||
clearInterval(_pollInterval);
|
clearInterval(_pollInterval);
|
||||||
_pollInterval = setInterval(pollSyncStatus, 30000);
|
_pollInterval = setInterval(pollSyncStatus, _pollIntervalMs);
|
||||||
|
loadDashOrders();
|
||||||
|
} else if (missedSync) {
|
||||||
|
// Sync completed while we weren't watching (e.g. auto-sync) — refresh orders
|
||||||
loadDashOrders();
|
loadDashOrders();
|
||||||
}
|
}
|
||||||
_lastSyncStatus = data.status;
|
_lastSyncStatus = data.status;
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ async function loadSettings() {
|
|||||||
if (el('settGomagApiShop')) el('settGomagApiShop').value = data.gomag_api_shop || '';
|
if (el('settGomagApiShop')) el('settGomagApiShop').value = data.gomag_api_shop || '';
|
||||||
if (el('settGomagDaysBack')) el('settGomagDaysBack').value = data.gomag_order_days_back || '7';
|
if (el('settGomagDaysBack')) el('settGomagDaysBack').value = data.gomag_order_days_back || '7';
|
||||||
if (el('settGomagLimit')) el('settGomagLimit').value = data.gomag_limit || '100';
|
if (el('settGomagLimit')) el('settGomagLimit').value = data.gomag_limit || '100';
|
||||||
|
if (el('settDashPollSeconds')) el('settDashPollSeconds').value = data.dashboard_poll_seconds || '5';
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('loadSettings error:', err);
|
console.error('loadSettings error:', err);
|
||||||
}
|
}
|
||||||
@@ -89,6 +90,7 @@ async function saveSettings() {
|
|||||||
gomag_api_shop: el('settGomagApiShop')?.value?.trim() || '',
|
gomag_api_shop: el('settGomagApiShop')?.value?.trim() || '',
|
||||||
gomag_order_days_back: el('settGomagDaysBack')?.value?.trim() || '7',
|
gomag_order_days_back: el('settGomagDaysBack')?.value?.trim() || '7',
|
||||||
gomag_limit: el('settGomagLimit')?.value?.trim() || '100',
|
gomag_limit: el('settGomagLimit')?.value?.trim() || '100',
|
||||||
|
dashboard_poll_seconds: el('settDashPollSeconds')?.value?.trim() || '5',
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/settings', {
|
const res = await fetch('/api/settings', {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
<input type="checkbox" id="schedulerToggle" class="cursor-pointer" onchange="toggleScheduler()">
|
<input type="checkbox" id="schedulerToggle" class="cursor-pointer" onchange="toggleScheduler()">
|
||||||
</label>
|
</label>
|
||||||
<select id="schedulerInterval" class="select-compact" onchange="updateSchedulerInterval()">
|
<select id="schedulerInterval" class="select-compact" onchange="updateSchedulerInterval()">
|
||||||
|
<option value="1">1 min</option>
|
||||||
|
<option value="3">3 min</option>
|
||||||
<option value="5">5 min</option>
|
<option value="5">5 min</option>
|
||||||
<option value="10" selected>10 min</option>
|
<option value="10" selected>10 min</option>
|
||||||
<option value="30">30 min</option>
|
<option value="30">30 min</option>
|
||||||
@@ -49,6 +51,8 @@
|
|||||||
<div class="filter-bar" id="ordersFilterBar">
|
<div class="filter-bar" id="ordersFilterBar">
|
||||||
<!-- Period dropdown -->
|
<!-- Period dropdown -->
|
||||||
<select id="periodSelect" class="select-compact">
|
<select id="periodSelect" class="select-compact">
|
||||||
|
<option value="1">1 zi</option>
|
||||||
|
<option value="2">2 zile</option>
|
||||||
<option value="3">3 zile</option>
|
<option value="3">3 zile</option>
|
||||||
<option value="7" selected>7 zile</option>
|
<option value="7" selected>7 zile</option>
|
||||||
<option value="30">30 zile</option>
|
<option value="30">30 zile</option>
|
||||||
@@ -200,5 +204,5 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=14"></script>
|
<script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=15"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -123,6 +123,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header py-2 px-3 fw-semibold">Dashboard</div>
|
||||||
|
<div class="card-body py-2 px-3">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label mb-0 small">Interval polling (secunde)</label>
|
||||||
|
<input type="number" class="form-control form-control-sm" id="settDashPollSeconds" value="5" min="1" max="300">
|
||||||
|
<div class="form-text" style="font-size:0.75rem">Cât de des verifică dashboard-ul starea sync-ului (implicit 5s)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<button class="btn btn-primary btn-sm" onclick="saveSettings()">Salvează Setările</button>
|
<button class="btn btn-primary btn-sm" onclick="saveSettings()">Salvează Setările</button>
|
||||||
<span id="settSaveResult" class="ms-2 small"></span>
|
<span id="settSaveResult" class="ms-2 small"></span>
|
||||||
@@ -131,5 +146,5 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ request.scope.get('root_path', '') }}/static/js/settings.js?v=2"></script>
|
<script src="{{ request.scope.get('root_path', '') }}/static/js/settings.js?v=3"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user