diff --git a/CHANGELOG.md b/CHANGELOG.md index 222017f8..00f13276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). --- +## v26.06.106 (2026-06-16) + +### Fixed + +- **Admin dashboard: the Overview "Thread Count" gauge no longer renders the + thread count as a percentage.** The gauge widget (`createGaugeChart`) was a + fixed 0–100 percentage meter — it clamped its value to 100 and appended a + hard-coded `%` to the centre readout — but the Overview page feeds it the + absolute active-thread count. A process with 8 threads therefore showed + `8%`, and any count above 100 clamped to `100%`. The gauge now accepts a + `max` (the value that fills the arc) and a `unit` (the readout suffix); the + thread gauge passes `unit: ''` and `max: 100`, so it shows the raw count + (`8`, `150`, …) while still acting as a thread-leak indicator (amber past 60, + red past 80). The defaults (`max: 100`, `unit: '%'`) keep every percentage + gauge rendering exactly as before. + +--- + ## v26.06.105 (2026-06-15) ### Fixed diff --git a/pyproject.toml b/pyproject.toml index 63a18c40..45873a81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.6.105" +version = "26.6.106" description = "The official Python implementation of the Firefly Framework — DI, CQRS, EDA, hexagonal architecture, and more." readme = "README.md" license = "Apache-2.0" diff --git a/src/pyfly/__init__.py b/src/pyfly/__init__.py index adf0812b..12202efa 100644 --- a/src/pyfly/__init__.py +++ b/src/pyfly/__init__.py @@ -13,4 +13,4 @@ # limitations under the License. """PyFly — Enterprise Python Framework.""" -__version__ = "26.06.105" +__version__ = "26.06.106" diff --git a/src/pyfly/admin/static/js/charts.js b/src/pyfly/admin/static/js/charts.js index 8158e3f1..85f2f6a6 100644 --- a/src/pyfly/admin/static/js/charts.js +++ b/src/pyfly/admin/static/js/charts.js @@ -254,11 +254,24 @@ export function createDonutChart(canvas, options = {}) { */ export function createGaugeChart(canvas, options = {}) { const thresholds = options.thresholds || { warning: 60, danger: 80 }; - let currentValue = Math.max(0, Math.min(100, options.value || 0)); + // ``max`` is the value that fills the arc completely; ``unit`` is the suffix + // on the centre readout. The defaults (100 / '%') keep the gauge a + // percentage meter. Pass ``{ max, unit: '' }`` to show an absolute count + // (e.g. thread count, connection count) as a plain number rather than a %. + const max = options.max != null && options.max > 0 ? options.max : 100; + const unit = options.unit != null ? options.unit : '%'; + // The displayed value is the raw number (not clamped to 100); only the arc + // fill is bounded to 0-100% of ``max``. + let currentValue = Math.max(0, options.value || 0); + + function fill(val) { + return Math.max(0, Math.min(100, (val / max) * 100)); + } function getColor(val) { - if (val >= thresholds.danger) return cssVar('--admin-danger') || '#f43f5e'; - if (val >= thresholds.warning) return cssVar('--admin-warning') || '#f59e0b'; + const pct = fill(val); + if (pct >= thresholds.danger) return cssVar('--admin-danger') || '#f43f5e'; + if (pct >= thresholds.warning) return cssVar('--admin-warning') || '#f59e0b'; return cssVar('--admin-success') || '#10b981'; } @@ -266,7 +279,7 @@ export function createGaugeChart(canvas, options = {}) { type: 'doughnut', data: { datasets: [{ - data: [currentValue, 100 - currentValue], + data: [fill(currentValue), 100 - fill(currentValue)], backgroundColor: [getColor(currentValue), cssVar('--admin-border-subtle') || '#162032'], borderWidth: 0, }], @@ -293,7 +306,7 @@ export function createGaugeChart(canvas, options = {}) { ctx.font = `700 ${Math.round(size * 0.2)}px ${cssVar('--admin-font-mono') || 'monospace'}`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.fillText(`${Math.round(currentValue)}%`, width / 2, height / 2 - 4); + ctx.fillText(`${Math.round(currentValue)}${unit}`, width / 2, height / 2 - 4); // Label if (options.label) { ctx.fillStyle = cssVar('--admin-text-muted') || '#64748b'; @@ -307,8 +320,8 @@ export function createGaugeChart(canvas, options = {}) { return { update(value) { - currentValue = Math.max(0, Math.min(100, value)); - chart.data.datasets[0].data = [currentValue, 100 - currentValue]; + currentValue = Math.max(0, value); + chart.data.datasets[0].data = [fill(currentValue), 100 - fill(currentValue)]; chart.data.datasets[0].backgroundColor = [getColor(currentValue), cssVar('--admin-border-subtle') || '#162032']; chart.update(); }, diff --git a/src/pyfly/admin/static/js/views/overview.js b/src/pyfly/admin/static/js/views/overview.js index d800e7a6..85e28523 100644 --- a/src/pyfly/admin/static/js/views/overview.js +++ b/src/pyfly/admin/static/js/views/overview.js @@ -454,6 +454,12 @@ export async function render(container, api) { threadGauge = createGaugeChart(threadCanvas, { value: 0, label: 'Threads', + // Thread count is an absolute number, not a percentage: show the + // raw count (unit: '') and scale the arc against a generous ceiling + // so it doubles as a thread-leak indicator (amber past 60, red past + // 80) without ever rendering a misleading ``%``. + unit: '', + max: 100, thresholds: { warning: 60, danger: 80 }, }); diff --git a/uv.lock b/uv.lock index 4fb91583..1ab8d502 100644 --- a/uv.lock +++ b/uv.lock @@ -2160,7 +2160,7 @@ wheels = [ [[package]] name = "pyfly" -version = "26.6.105" +version = "26.6.106" source = { editable = "." } dependencies = [ { name = "pydantic" },