806f2bdb1b
devops-tools/wrapped/wrapped-build/pipeline/head There was a failure building this commit
Добавлены Share/QR PNG, zip all, size meter, PWA-manifest, системная тема и haptic на copy; улучшены success/unwrap и статистика админки (EN/RU).
237 lines
8.4 KiB
HTML
237 lines
8.4 KiB
HTML
{% extends "admin_base.html" %}
|
|
{% block admin_content %}
|
|
<section class="stats-page">
|
|
<p class="stats-updated hint">
|
|
<span data-i18n="admin.stats.updated">Updated</span>
|
|
<time id="stats-generated" data-ts="{{ stats.generated_at.isoformat() }}">{{ stats.generated_at }}</time>
|
|
</p>
|
|
|
|
<div class="stats-grid">
|
|
<article class="stat-card accent">
|
|
<div class="stat-card-head">
|
|
<i class="fa-solid fa-database" aria-hidden="true"></i>
|
|
<h2 data-i18n="admin.stats.minioTitle">MinIO (pending ciphertext)</h2>
|
|
</div>
|
|
<p class="stat-value">{{ stats.minio_human }}</p>
|
|
<p class="stat-sub">
|
|
<span class="stat-num">{{ stats.minio_objects }}</span>
|
|
<span data-i18n="admin.stats.objects">object(s)</span>
|
|
·
|
|
<span data-i18n="admin.stats.dbPending">DB pending</span>
|
|
{{ stats.size_pending_human }}
|
|
</p>
|
|
{% if stats.minio_error %}
|
|
<p class="error stat-error">{{ stats.minio_error }}</p>
|
|
{% endif %}
|
|
</article>
|
|
|
|
<article class="stat-card">
|
|
<div class="stat-card-head">
|
|
<i class="fa-solid fa-lock-open" aria-hidden="true"></i>
|
|
<h2 data-i18n="admin.stats.unwrappedTitle">Successful unwraps</h2>
|
|
</div>
|
|
<p class="stat-value">{{ stats.unwraps_success }}</p>
|
|
<p class="stat-sub">
|
|
<span data-i18n="admin.stats.dbConsumed">DB consumed</span>
|
|
<span class="stat-num">{{ stats.wraps_consumed }}</span>
|
|
·
|
|
<span data-i18n="admin.stats.last24h">24h</span>
|
|
<span class="stat-num">{{ stats.consumed_24h }}</span>
|
|
</p>
|
|
</article>
|
|
|
|
<article class="stat-card">
|
|
<div class="stat-card-head">
|
|
<i class="fa-solid fa-shield-halved" aria-hidden="true"></i>
|
|
<h2 data-i18n="admin.stats.passwordBurnsTitle">Burned by password</h2>
|
|
</div>
|
|
<p class="stat-value">{{ stats.password_burns }}</p>
|
|
<p class="stat-sub" data-i18n="admin.stats.passwordBurnsHint">
|
|
Destroyed after too many wrong passwords
|
|
</p>
|
|
</article>
|
|
|
|
<article class="stat-card">
|
|
<div class="stat-card-head">
|
|
<i class="fa-solid fa-hourglass-half" aria-hidden="true"></i>
|
|
<h2 data-i18n="admin.stats.pendingTitle">Not unwrapped</h2>
|
|
</div>
|
|
<p class="stat-value">{{ stats.wraps_pending }}</p>
|
|
<p class="stat-sub">
|
|
{{ stats.size_pending_human }}
|
|
·
|
|
<span class="stat-num">{{ stats.items_pending }}</span>
|
|
<span data-i18n="admin.stats.items">item(s)</span>
|
|
</p>
|
|
</article>
|
|
|
|
<article class="stat-card">
|
|
<div class="stat-card-head">
|
|
<i class="fa-solid fa-cloud-arrow-up" aria-hidden="true"></i>
|
|
<h2 data-i18n="admin.stats.uploadedTitle">Uploaded (all time)</h2>
|
|
</div>
|
|
<p class="stat-value">{{ stats.wraps_total }}</p>
|
|
<p class="stat-sub">
|
|
<span class="stat-num">{{ stats.items_total }}</span>
|
|
<span data-i18n="admin.stats.items">item(s)</span>
|
|
·
|
|
{{ stats.size_total_human }}
|
|
</p>
|
|
</article>
|
|
</div>
|
|
|
|
<div class="stats-sections">
|
|
<section class="stats-section">
|
|
<h3 data-i18n="admin.stats.wrapsSection">Wraps by status</h3>
|
|
<div class="stats-table-wrap">
|
|
<table class="stats-table">
|
|
<thead>
|
|
<tr>
|
|
<th data-i18n="admin.stats.col.status">Status</th>
|
|
<th data-i18n="admin.stats.col.count">Count</th>
|
|
<th data-i18n="admin.stats.col.size">Size</th>
|
|
<th data-i18n="admin.stats.col.items">Items</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td data-i18n="admin.stats.status.pending">pending</td>
|
|
<td class="mono">{{ stats.wraps_pending }}</td>
|
|
<td class="mono">{{ stats.size_pending_human }}</td>
|
|
<td class="mono">{{ stats.items_pending }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td data-i18n="admin.stats.status.consumed">consumed</td>
|
|
<td class="mono">{{ stats.wraps_consumed }}</td>
|
|
<td class="mono">{{ stats.size_consumed_human }}</td>
|
|
<td class="mono">{{ stats.items_consumed }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td data-i18n="admin.stats.status.expired">expired</td>
|
|
<td class="mono">{{ stats.wraps_expired }}</td>
|
|
<td class="mono">{{ stats.size_expired_human }}</td>
|
|
<td class="mono">{{ stats.items_expired }}</td>
|
|
</tr>
|
|
<tr class="stats-total-row">
|
|
<td data-i18n="admin.stats.total">Total</td>
|
|
<td class="mono">{{ stats.wraps_total }}</td>
|
|
<td class="mono">{{ stats.size_total_human }}</td>
|
|
<td class="mono">{{ stats.items_total }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="stats-section">
|
|
<h3 data-i18n="admin.stats.extraSection">More</h3>
|
|
<ul class="stats-list">
|
|
<li>
|
|
<span data-i18n="admin.stats.withPassword">With password</span>
|
|
<strong class="mono">{{ stats.wraps_with_password }}</strong>
|
|
</li>
|
|
<li>
|
|
<span data-i18n="admin.stats.created24h">Created (24h)</span>
|
|
<strong class="mono">{{ stats.created_24h }}</strong>
|
|
</li>
|
|
<li>
|
|
<span data-i18n="admin.stats.created7d">Created (7d)</span>
|
|
<strong class="mono">{{ stats.created_7d }}</strong>
|
|
</li>
|
|
<li>
|
|
<span data-i18n="admin.stats.createsOk">Creates ok (audit)</span>
|
|
<strong class="mono">{{ stats.audit_creates_ok }}</strong>
|
|
</li>
|
|
<li>
|
|
<span data-i18n="admin.stats.createsFail">Creates fail (audit)</span>
|
|
<strong class="mono">{{ stats.audit_creates_fail }}</strong>
|
|
</li>
|
|
<li>
|
|
<span data-i18n="admin.stats.unwrapsFail">Unwrap fails (audit)</span>
|
|
<strong class="mono">{{ stats.audit_unwraps_fail }}</strong>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="stats-sections">
|
|
<section class="stats-section">
|
|
<h3 data-i18n="admin.stats.failReasonsSection">Unwrap fail reasons (all time)</h3>
|
|
<div class="stats-table-wrap">
|
|
<table class="stats-table">
|
|
<thead>
|
|
<tr>
|
|
<th data-i18n="admin.stats.col.reason">Reason</th>
|
|
<th data-i18n="admin.stats.col.count">Count</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% if stats.unwrap_fail_reasons %}
|
|
{% for row in stats.unwrap_fail_reasons %}
|
|
<tr>
|
|
<td class="mono">{{ row.reason }}</td>
|
|
<td class="mono">{{ row.count }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="2" class="hint" data-i18n="admin.stats.noFailReasons">No failed unwraps yet.</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="stats-section">
|
|
<h3 data-i18n="admin.stats.failReasons24hSection">Unwrap fail reasons (24h)</h3>
|
|
<div class="stats-table-wrap">
|
|
<table class="stats-table">
|
|
<thead>
|
|
<tr>
|
|
<th data-i18n="admin.stats.col.reason">Reason</th>
|
|
<th data-i18n="admin.stats.col.count">Count</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% if stats.unwrap_fail_reasons_24h %}
|
|
{% for row in stats.unwrap_fail_reasons_24h %}
|
|
<tr>
|
|
<td class="mono">{{ row.reason }}</td>
|
|
<td class="mono">{{ row.count }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="2" class="hint" data-i18n="admin.stats.noFailReasons">No failed unwraps yet.</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</section>
|
|
{% endblock %}
|
|
{% block admin_scripts %}
|
|
<script>
|
|
(() => {
|
|
const el = document.getElementById("stats-generated");
|
|
if (!el) return;
|
|
const raw = el.getAttribute("data-ts");
|
|
if (!raw) return;
|
|
const d = new Date(raw);
|
|
if (Number.isNaN(d.getTime())) return;
|
|
const locale = window.WrappedI18n?.locale?.() || undefined;
|
|
el.textContent = new Intl.DateTimeFormat(locale, {
|
|
day: "2-digit",
|
|
month: "short",
|
|
year: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
second: "2-digit",
|
|
}).format(d);
|
|
})();
|
|
</script>
|
|
{% endblock %}
|