v2.0.1 — collab-attachments size cap + oumta overview tiles

- relay: raise /api/collab-attachments + /api/attachments/upload POST
  size limit from 256KB to 15MB, matching /api/attachments. Previous
  limit silently dropped any collab image >~100KB as 413 (envelope
  overhead pushes even modest JPEGs past the cap), leaving puller
  clients with broken <img src="uploads/..."> references.
- oumta-dashboard: centre overview tiles, bump value font to 56px,
  introduce .oumta-fail alarm state that paints the full tile red with
  white text. Toggled from JS for cluster state=down, workloads down
  >0, and open incidents >0. Fail colour uses --t-danger so it stays
  in sync with the "unavailable" workload labels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-17 18:38:17 +02:00
1 parent 1dfffa5d6d
commit 72a7c7c7cc
6 files changed
+43 -9

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
<?php
define('APP_NAME', 'hyprcrd');
define('APP_VERSION', '2.0.0');
define('APP_VERSION', '2.0.1');
// APP_DEBUG: gates verbose error responses (curl traces, upstream bodies).
// Off by default; enable via env for local troubleshooting only.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hyprcrd",
"version": "2.0.0",
"version": "2.0.1",
"description": "hyprcrd — self-hosted card manager",
"main": "main.js",
"scripts": {
@@ -46,6 +46,9 @@
border-radius: var(--ui-radius-card);
padding: 14px 16px;
display: flex; flex-direction: column; gap: 4px;
align-items: center;
text-align: center;
transition: background-color 0.2s, border-color 0.2s, color 0.2s;
}
.oumta-card-wide { grid-column: span 4; }
.oumta-card-label {
@@ -57,18 +60,36 @@
}
.oumta-card-value {
font-family: var(--font-content, monospace);
font-size: 28px;
font-size: 56px;
color: var(--t-bright);
font-weight: bold;
line-height: 1.05;
}
.oumta-card-value-sm { font-size: 14px; font-weight: normal; }
.oumta-card-value-sm { font-size: 18px; font-weight: normal; line-height: 1.3; }
.oumta-card-sub { font-size: 10px; color: var(--t-dim); }
.oumta-danger { color: var(--t-danger); }
/* Fail state: whole card goes red; label, value, sub all flip to white
* so the box reads as a single alarm tile rather than a coloured number
* on dark chrome. Triggered from JS via the oumta-fail class. Uses the
* theme's --t-danger to stay consistent with the "unavailable" state
* labels and workload state colours elsewhere in the dashboard. */
.oumta-card.oumta-fail {
background: var(--t-danger);
border-color: var(--t-danger);
color: #fff;
}
.oumta-card.oumta-fail .oumta-card-label,
.oumta-card.oumta-fail .oumta-card-value,
.oumta-card.oumta-fail .oumta-card-sub {
color: #fff;
}
.oumta-cluster-status[data-state="healthy"] .oumta-card-value { color: #4ade80; }
.oumta-cluster-status[data-state="degraded"] .oumta-card-value { color: #fb923c; }
.oumta-cluster-status[data-state="down"] .oumta-card-value { color: var(--t-danger); }
.oumta-cluster-status[data-state="unknown"] .oumta-card-value { color: var(--t-dim); }
/* data-state="down" is handled by .oumta-fail (toggled from JS) so the
* cluster tile goes fully red + white like the other alarms. */
/* ── Attention list ── */
.oumta-section { margin-top: 8px; }
@@ -46,7 +46,7 @@
</div>
<div class="oumta-card">
<div class="oumta-card-label">Workloads down</div>
<div class="oumta-card-value oumta-danger" id="oumta-workloads-down">—</div>
<div class="oumta-card-value" id="oumta-workloads-down">—</div>
</div>
<div class="oumta-card">
<div class="oumta-card-label">Open incidents</div>
@@ -269,19 +269,28 @@ function renderOverview() {
if (!o) return;
// Values that come directly from the Go overview endpoint [REAL].
$('oumta-cluster-card').dataset.state = o.clusterState || 'unknown';
$('oumta-cluster-state').textContent = o.clusterState || 'unknown';
const clusterCard = $('oumta-cluster-card');
const clusterState = o.clusterState || 'unknown';
clusterCard.dataset.state = clusterState;
clusterCard.classList.toggle('oumta-fail', clusterState === 'down');
$('oumta-cluster-state').textContent = clusterState;
$('oumta-cluster-updated').textContent = o.updatedAtUtc ? 'as of ' + formatTime(o.updatedAtUtc) : 'no data';
$('oumta-workloads-up').textContent = o.workloadsUp ?? '—';
$('oumta-workloads-down').textContent = o.workloadsDown ?? '—';
$('oumta-last-import').textContent = o.updatedAtUtc ? formatTime(o.updatedAtUtc) : '—';
// Any workload down → the "workloads down" tile becomes an alarm tile.
const downCard = $('oumta-workloads-down').closest('.oumta-card');
if (downCard) downCard.classList.toggle('oumta-fail', (o.workloadsDown ?? 0) > 0);
// Values derived client-side from the workloads list — the overview
// endpoint doesn't carry these today. If the backend later adds
// `incidents` / `attention` fields, prefer those over the derivation.
const attention = wl.filter(w => w.state !== 'healthy')
.sort((a, b) => rankState(a.state) - rankState(b.state));
$('oumta-incidents').textContent = attention.length || '—';
const incCard = $('oumta-incidents').closest('.oumta-card');
if (incCard) incCard.classList.toggle('oumta-fail', attention.length > 0);
const list = $('oumta-attention-list');
if (!attention.length) {
+5 -1
View File
@@ -66,7 +66,11 @@ if (str_starts_with($route, '/api/')) {
if ($method === 'OPTIONS') exit;
$contentLength = (int)($_SERVER['CONTENT_LENGTH'] ?? 0);
$sizeLimit = ($route === '/api/attachments') ? 15728640 : 262144;
// Attachment routes handle binary blobs; everything else is JSON metadata.
// A ~150KB image becomes a ~275KB envelope after base64 + GCM framing,
// so 256KB would silently drop any non-trivial image on collab pushes.
$isAttachmentRoute = in_array($route, ['/api/attachments', '/api/attachments/upload', '/api/collab-attachments'], true);
$sizeLimit = $isAttachmentRoute ? 15728640 : 262144;
if ($contentLength > $sizeLimit) jsonResponse(['error' => 'payload too large'], 413);
// Each route file contains its handlers as if-blocks that exit on