Backend:
- GET /api/sync/health returns {last_sync_at, last_sync_status,
last_halt_reason, recent_phase_failures, escalation_phase, is_healthy}.
healthy when last run was completed (or none yet), no phase has
tripped the 3-in-a-row escalation, and recent failures <= 1.
- Dashboard + run-level endpoints include `malformed` count so the
Defecte pill can render.
Frontend:
- Health pill in .sync-card-controls with three states — healthy
(success green, check icon), warning (amber, triangle), escalated
(error red, x-octagon + glow). Tooltip exposes the halt reason and
the top phases with recent failures.
- Status-dot + badge add MALFORMED treatment via --compare orange,
distinct from ERROR red. DESIGN.md notes the diagnostic rationale
(ERROR = runtime, MALFORMED = payload source issue).
- Defecte filter pill on dashboard + logs pages. Mobile segmented
control includes Defecte count. Counts wired to the malformed key.
- startSync() shows a native confirm modal when state is
halted_escalation — operator override still possible, not silenced.
- ORDER_STATUS.MALFORMED mirror added to shared.js.
- Cache-bust: style.css v46, shared.js v47, dashboard.js v52,
logs.js v16.
5 endpoint tests cover empty state, completed, failed, escalated,
single-failure warning. Full CI: 257 unit + 33 e2e green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
115 lines
6.7 KiB
HTML
115 lines
6.7 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Jurnale Import - GoMag Import{% endblock %}
|
|
{% block nav_logs %}active{% endblock %}
|
|
{% block bnav_logs %}active{% endblock %}
|
|
|
|
{% block content %}
|
|
<h4 class="mb-4">Jurnale Import</h4>
|
|
|
|
<!-- Sync Run Selector + Status + Controls (single card) -->
|
|
<div class="card mb-3">
|
|
<div class="card-body py-2">
|
|
<!-- Desktop layout -->
|
|
<div class="d-none d-md-flex align-items-center gap-3 flex-wrap">
|
|
<label class="form-label mb-0 fw-bold text-nowrap">Sync Run:</label>
|
|
<select class="form-select form-select-sm" id="runsDropdown" onchange="selectRun(this.value)" style="max-width:400px">
|
|
<option value="">Se incarca...</option>
|
|
</select>
|
|
<button class="btn btn-sm btn-outline-secondary text-nowrap" onclick="loadRuns()" title="Reincarca lista"><i class="bi bi-arrow-clockwise"></i></button>
|
|
<span id="logStatusBadge" style="font-weight:600">-</span>
|
|
<div class="form-check form-switch mb-0">
|
|
<input class="form-check-input" type="checkbox" id="autoRefreshToggle" checked>
|
|
<label class="form-check-label small" for="autoRefreshToggle">Auto-refresh</label>
|
|
</div>
|
|
<button class="btn btn-sm btn-outline-secondary" id="btnShowTextLog" onclick="toggleTextLog()">
|
|
<i class="bi bi-file-text"></i> Log text brut
|
|
</button>
|
|
</div>
|
|
<!-- Mobile compact layout -->
|
|
<div class="d-flex d-md-none align-items-center gap-2">
|
|
<span id="mobileRunDot" class="sync-status-dot idle" style="width:8px;height:8px"></span>
|
|
<select class="form-select form-select-sm flex-grow-1" id="runsDropdownMobile" onchange="selectRun(this.value)" style="font-size:0.8rem">
|
|
<option value="">Se incarca...</option>
|
|
</select>
|
|
<button class="btn btn-sm btn-outline-secondary" onclick="loadRuns()" title="Reincarca"><i class="bi bi-arrow-clockwise"></i></button>
|
|
<div class="dropdown">
|
|
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown"><i class="bi bi-three-dots-vertical"></i></button>
|
|
<ul class="dropdown-menu dropdown-menu-end">
|
|
<li>
|
|
<label class="dropdown-item d-flex align-items-center gap-2">
|
|
<input class="form-check-input" type="checkbox" id="autoRefreshToggleMobile" checked> Auto-refresh
|
|
</label>
|
|
</li>
|
|
<li><a class="dropdown-item" href="#" onclick="toggleTextLog();return false"><i class="bi bi-file-text me-1"></i> Log text brut</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty state (shown when no run selected) -->
|
|
<div id="logEmptyState" class="text-center py-5" style="color:var(--text-muted)">
|
|
<i class="bi bi-journal-text" style="font-size:2.5rem;opacity:0.4"></i>
|
|
<p class="mt-3 mb-1" style="font-size:0.9375rem">Selecteaza un sync run din lista de mai sus</p>
|
|
<p style="font-size:0.8125rem">Jurnalele arata detalii pentru fiecare sincronizare: comenzi importate, omise, erori.</p>
|
|
</div>
|
|
|
|
<!-- Detail Viewer (shown when run selected) -->
|
|
<div id="logViewerSection" style="display:none;">
|
|
<!-- Filter pills -->
|
|
<div class="filter-bar mb-3" id="orderFilterPills">
|
|
<button class="filter-pill active d-none d-md-inline-flex" data-log-status="all">Toate <span class="filter-count fc-neutral" id="countAll">0</span></button>
|
|
<button class="filter-pill d-none d-md-inline-flex" data-log-status="{{ OrderStatus.IMPORTED.value }}">Importate <span class="filter-count fc-green" id="countImported">0</span></button>
|
|
<button class="filter-pill d-none d-md-inline-flex" data-log-status="{{ OrderStatus.ALREADY_IMPORTED.value }}">Deja imp. <span class="filter-count fc-blue" id="countAlreadyImported">0</span></button>
|
|
<button class="filter-pill d-none d-md-inline-flex" data-log-status="{{ OrderStatus.SKIPPED.value }}">Omise <span class="filter-count fc-yellow" id="countSkipped">0</span></button>
|
|
<button class="filter-pill d-none d-md-inline-flex" data-log-status="{{ OrderStatus.ERROR.value }}">Erori <span class="filter-count fc-red" id="countError">0</span></button>
|
|
<button class="filter-pill d-none d-md-inline-flex" data-log-status="{{ OrderStatus.MALFORMED.value }}">Defecte <span class="filter-count fc-orange" id="countMalformed">0</span></button>
|
|
</div>
|
|
<div class="d-md-none mb-2" id="logsMobileSeg" style="overflow-x:auto"></div>
|
|
|
|
<!-- Orders table -->
|
|
<div class="card mb-3">
|
|
<div id="ordersPaginationTop" class="pag-strip"></div>
|
|
<div class="card-body p-0">
|
|
<div id="logsMobileList" class="mobile-list"></div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:24px"></th>
|
|
<th>#</th>
|
|
<th class="sortable" onclick="sortOrdersBy('order_date')">Data comanda <span class="sort-icon" data-col="order_date"></span></th>
|
|
<th class="sortable" onclick="sortOrdersBy('order_number')">Nr. comanda <span class="sort-icon" data-col="order_number"></span></th>
|
|
<th class="sortable" onclick="sortOrdersBy('customer_name')">Client <span class="sort-icon" data-col="customer_name"></span></th>
|
|
<th class="sortable" onclick="sortOrdersBy('items_count')">Articole <span class="sort-icon" data-col="items_count"></span></th>
|
|
<th class="text-end">Transport</th>
|
|
<th class="text-end">Discount</th>
|
|
<th class="text-end">Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="runOrdersBody">
|
|
<tr><td colspan="9" class="text-center text-muted py-3">Selecteaza un sync run</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div id="ordersPagination" class="pag-strip pag-strip-bottom"></div>
|
|
</div>
|
|
|
|
<!-- Collapsible text log -->
|
|
<div id="textLogSection" style="display:none;">
|
|
<div class="card">
|
|
<div class="card-header">Log text brut</div>
|
|
<pre class="log-viewer" id="logContent">Se incarca...</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hidden field for pre-selected run from URL/server -->
|
|
<input type="hidden" id="preselectedRun" value="{{ selected_run }}">
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script src="{{ request.scope.get('root_path', '') }}/static/js/logs.js?v=16"></script>
|
|
{% endblock %}
|