Files
gomag-vending/api/app/templates/dashboard.html
Marius Mutu 650e98539e feat(sync): add SSE live feed, unified logs page, fix Oracle connection
- Add SSE event bus in sync_service (subscribe/unsubscribe/_emit)
- Add GET /api/sync/stream SSE endpoint for real-time sync progress
- Rewrite logs.html: unified runs table + live feed + summary + filters
- Rewrite logs.js: SSE EventSource client, run selection, pagination
- Dashboard: clickable runs navigate to /logs?run=, sync started banner
- Remove "Import Comenzi" nav item, delete sync_detail.html
- Add error_message column to sync_runs table with migration
- Fix: export TNS_ADMIN as OS env var so oracledb finds tnsnames.ora
- Fix: use get_oracle_connection() instead of direct pool.acquire()
- Fix: CRM_POLITICI_PRET_ART INSERT to match actual table schema

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 18:08:09 +02:00

205 lines
8.1 KiB
HTML

{% extends "base.html" %}
{% block title %}Dashboard - GoMag Import{% endblock %}
{% block nav_dashboard %}active{% endblock %}
{% block content %}
<h4 class="mb-4">Dashboard</h4>
<!-- Stat cards - Row 1: Comenzi -->
<div class="row g-3 mb-2" id="statsRow">
<div class="col">
<div class="card stat-card">
<div class="stat-value text-info" id="stat-new">-</div>
<div class="stat-label">Comenzi Noi</div>
</div>
</div>
<div class="col">
<div class="card stat-card">
<div class="stat-value text-primary" id="stat-ready">-</div>
<div class="stat-label">Ready</div>
</div>
</div>
<div class="col">
<div class="card stat-card">
<div class="stat-value text-success" id="stat-imported">-</div>
<div class="stat-label">Importate</div>
</div>
</div>
<div class="col">
<div class="card stat-card">
<div class="stat-value text-warning" id="stat-skipped">-</div>
<div class="stat-label">Fără Mapare</div>
</div>
</div>
<div class="col">
<div class="card stat-card">
<div class="stat-value text-danger" id="stat-errors">-</div>
<div class="stat-label">Erori Import</div>
</div>
</div>
</div>
<!-- Stat cards - Row 2: Articole -->
<div class="row g-3 mb-4" id="statsRowArticles">
<div class="col">
<div class="card stat-card">
<div class="stat-value text-secondary" id="stat-total-skus">-</div>
<div class="stat-label">Total SKU Scanate</div>
</div>
</div>
<div class="col">
<div class="card stat-card">
<div class="stat-value text-success" id="stat-mapped-skus">-</div>
<div class="stat-label">Cu Mapare</div>
</div>
</div>
<div class="col">
<div class="card stat-card">
<div class="stat-value text-warning" id="stat-missing-skus">-</div>
<div class="stat-label">Fără Mapare</div>
</div>
</div>
<div class="col d-none d-md-block"></div>
<div class="col d-none d-md-block"></div>
</div>
<!-- Sync Control -->
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<span>Sync Control</span>
<div class="d-flex align-items-center gap-2">
<a href="/logs" class="btn btn-sm btn-outline-info">
<i class="bi bi-journal-text"></i> Jurnale Import
</a>
<span class="badge bg-secondary" id="syncStatusBadge">idle</span>
</div>
</div>
<div class="card-body">
<div class="row align-items-center">
<div class="col-auto">
<button class="btn btn-success btn-sm" id="btnStartSync" onclick="startSync()">
<i class="bi bi-play-fill"></i> Start Sync
</button>
<button class="btn btn-outline-secondary btn-sm" id="btnScan" onclick="scanOrders()">
<i class="bi bi-search"></i> Scan
</button>
<button class="btn btn-danger btn-sm d-none" id="btnStopSync" onclick="stopSync()">
<i class="bi bi-stop-fill"></i> Stop
</button>
</div>
<div class="col-auto">
<div class="form-check form-switch d-inline-block me-2">
<input class="form-check-input" type="checkbox" id="schedulerToggle" onchange="toggleScheduler()">
<label class="form-check-label" for="schedulerToggle">Scheduler</label>
</div>
<select class="form-select form-select-sm d-inline-block" style="width:auto" id="schedulerInterval" onchange="updateSchedulerInterval()">
<option value="1">1 min</option>
<option value="5" selected>5 min</option>
<option value="10">10 min</option>
<option value="15">15 min</option>
<option value="30">30 min</option>
<option value="60">60 min</option>
</select>
</div>
<div class="col">
<small class="text-muted" id="syncProgressText"></small>
</div>
</div>
<div class="mt-2 d-none" id="syncStartedBanner">
<div class="alert alert-info alert-sm py-1 px-2 mb-0 d-inline-block">
<small><i class="bi bi-broadcast"></i> Sync pornit — <a href="#" id="syncRunLink">vezi progresul live</a></small>
</div>
</div>
</div>
</div>
<!-- Recent Sync Runs -->
<div class="card mb-4">
<div class="card-header">Ultimele Sync Runs</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Data</th>
<th>Status</th>
<th>Total</th>
<th>OK</th>
<th>Fără mapare</th>
<th>Erori</th>
<th>Durata</th>
</tr>
</thead>
<tbody id="syncRunsBody">
<tr><td colspan="7" class="text-center text-muted py-3">Se incarca...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Missing SKUs (quick resolve) -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span>SKU-uri Lipsa</span>
<a href="/missing-skus" class="btn btn-sm btn-outline-primary">Vezi toate</a>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>SKU</th>
<th>Produs</th>
<th>Nr. Comenzi</th>
<th>Primul Client</th>
<th colspan="2">Acțiune</th>
</tr>
</thead>
<tbody id="missingSkusBody">
<tr><td colspan="5" class="text-center text-muted py-3">Se incarca...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Map SKU Modal (copied from missing_skus.html) -->
<div class="modal fade" id="mapModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Mapeaza SKU: <code id="mapSku"></code></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3 position-relative">
<label class="form-label">CODMAT (Articol ROA)</label>
<input type="text" class="form-control" id="mapCodmat" placeholder="Cauta codmat sau denumire..." autocomplete="off">
<div class="autocomplete-dropdown d-none" id="mapAutocomplete"></div>
<small class="text-muted" id="mapSelectedArticle"></small>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label">Cantitate ROA</label>
<input type="number" class="form-control" id="mapCantitate" value="1" step="0.001" min="0.001">
</div>
<div class="col-6 mb-3">
<label class="form-label">Procent Pret (%)</label>
<input type="number" class="form-control" id="mapProcent" value="100" step="0.01" min="0" max="100">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Anuleaza</button>
<button type="button" class="btn btn-primary" onclick="saveQuickMap()">Salveaza</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/dashboard.js"></script>
{% endblock %}