Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,37 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

---

## v26.05.09 (2026-05-31)

### Admin dashboard — live time-series metrics

- **Live trend on the Metrics view.** Selecting a numeric metric now opens a
rolling **time-series chart** (Chart.js, themed) that polls the metric on the
configured refresh interval and keeps a 60-point window — replacing the old
static value snapshot.
- **Value / Rate toggle.** Switch between the absolute value and a per-second
delta (Δ/s); a downward step is shown honestly (real gauge decrease or counter
reset) rather than smoothed away.
- **Pause/Resume** the live feed, a **Current / Min / Max / Avg** summary strip,
and a **measurement selector** for multi-series (tagged) Prometheus metrics —
switching reseeds the series. The measurements table refreshes live too.
- Non-numeric metrics (e.g. `python.version`) show a snapshot with a clear note
instead of an empty chart. Rapid metric switching and navigation are race-safe
(a load-generation token drops superseded fetches and poll ticks) and tear down
the timer + chart on exit — no dangling intervals.
- **Responsive.** The list/detail split stacks on mobile and the chart canvas
resizes; verified zero horizontal overflow at 390px and side-by-side at 1440px,
in both dark and light themes.

### Admin dashboard — cache correctness

- The SPA shell (`index.html`) is now served with `Cache-Control: no-cache`, so
the version-stamped (`?v=…`) asset URLs are actually revalidated after a
framework upgrade. Previously a heuristically cached shell could keep pointing
at the prior version's CSS/JS.

---

## v26.05.08 (2026-05-31)

### Admin dashboard — ⌘K command palette
Expand Down
2 changes: 1 addition & 1 deletion docs/modules/admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ stream for live updates.

| View | Sidebar ID | Description |
|------|-----------|-------------|
| **Metrics** | `metrics` | Built-in process metrics (CPU, memory, threads, GC, uptime) always available without external dependencies. Optional Prometheus metrics included when `prometheus_client` is installed. Drill-down to individual values with descriptions and units. Real-time SSE updates. |
| **Metrics** | `metrics` | Built-in process metrics (CPU, memory, threads, GC, uptime) always available without external dependencies. Optional Prometheus metrics included when `prometheus_client` is installed. Selecting a numeric metric opens a **live time-series trend** — a rolling chart polled at the configured refresh interval with a Value / Rate (Δ/s) toggle, pause/resume, Current/Min/Max/Avg summary, a measurement selector for multi-series (tagged) metrics, and a live-refreshing measurements table. Non-numeric metrics show a snapshot instead. |
| **Scheduled Tasks** | `scheduled` | All `@scheduled` tasks with cron expressions, fixed-rate/delay configuration, and execution status. |
| **HTTP Traces** | `traces` | Recent HTTP request/response traces captured by `TraceCollectorFilter`. Shows method, path, status code, duration, query string, client host, content type, user agent, and response content-length. Click-to-detail panel. Status code filter pills (All, 2xx, 3xx, 4xx, 5xx). Real-time SSE for new traces. Ring buffer of 500 entries. |

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "pyfly"
# CalVer YY.MM.PATCH — package metadata uses PEP 440 normalized form (26.5.4);
# git tag, GitHub release and human-readable display use leading-zero form
# (v26.05.04) to match the Java/.NET/Go siblings.
version = "26.5.8"
version = "26.5.9"
description = "The official Python implementation of the Firefly Framework — DI, CQRS, EDA, hexagonal architecture, and more."
readme = "README.md"
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/pyfly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
# limitations under the License.
"""PyFly — Enterprise Python Framework."""

__version__ = "26.05.08"
__version__ = "26.05.09"
9 changes: 8 additions & 1 deletion src/pyfly/admin/adapters/starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,4 +410,11 @@ async def _handle_spa(self, request: Request) -> Response:
rf'\1="\2?v={__version__}"',
content,
)
return Response(content, media_type="text/html")
# The SPA shell must revalidate so version-stamped asset URLs (?v=...)
# are picked up after a framework upgrade. Without this, a heuristically
# cached index.html keeps pointing at the previous version's assets.
return Response(
content,
media_type="text/html",
headers={"Cache-Control": "no-cache"},
)
131 changes: 131 additions & 0 deletions src/pyfly/admin/static/css/admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -1879,3 +1879,134 @@ body.wallboard-mode .admin-content {
display: none;
}
}

/* ── Metrics: split layout + live trend ─────────────────────────── */
.metrics-split {
display: flex;
gap: 16px;
align-items: flex-start;
}

.metrics-list-panel {
width: 300px;
min-width: 300px;
flex-shrink: 0;
}

.metrics-detail-panel {
flex: 1;
min-width: 0;
}

@media (max-width: 768px) {
.metrics-split {
flex-direction: column;
align-items: stretch;
}

.metrics-list-panel,
.metrics-detail-panel {
width: 100%;
min-width: 0;
}
}

.trend-toolbar {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}

.trend-live {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--admin-text-muted);
}

.trend-live-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--admin-success);
animation: trend-pulse 2s infinite;
}

.trend-live.paused .trend-live-dot {
background: var(--admin-text-muted);
animation: none;
}

@keyframes trend-pulse {
0% { box-shadow: 0 0 0 0 var(--admin-success-dim); }
70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); }
100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
}

.seg-toggle {
display: inline-flex;
border: 1px solid var(--admin-border);
border-radius: var(--admin-radius-sm);
overflow: hidden;
}

.seg-btn {
padding: 4px 12px;
font-size: 0.72rem;
background: transparent;
color: var(--admin-text-muted);
border: none;
cursor: pointer;
font-family: var(--admin-font-sans);
transition: all var(--admin-transition);
}

.seg-btn.active {
background: var(--admin-primary-dim);
color: var(--admin-primary);
}

.seg-btn:not(.active):hover {
background: var(--admin-surface-hover);
color: var(--admin-text);
}

.trend-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin-top: 14px;
}

.trend-stat {
background: var(--admin-bg-subtle);
border: 1px solid var(--admin-border-subtle);
border-radius: var(--admin-radius-sm);
padding: 8px 10px;
}

.trend-stat-label {
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--admin-text-muted);
}

.trend-stat-value {
font-size: 0.95rem;
font-weight: 600;
font-family: var(--admin-font-mono);
color: var(--admin-text);
margin-top: 2px;
word-break: break-all;
}

@media (max-width: 520px) {
.trend-stats {
grid-template-columns: repeat(2, 1fr);
}
}
Loading
Loading