fix: rewrite dashboard_refresh.js with correct bricks API

- Replace non-existent bricks.app.find_widget_by_id() with bricks.getWidgetById(id, bricks.app)
- Replace widget.el.textContent with widget.set_text() method
- Fix getBaseUrl() to handle /modulename paths (no .ui suffix) returning /undefined
- Remove chart refresh from JS — use ChartBar's built-in refresh_period: 10
- Update index.ui ChartBar widget with refresh_period: 10
- Also fixed all find_widget_by_id references across skills:
  * bricks-framework SKILL.md (2 occurrences)
  * bricks-framework references: auto-refresh-dashboard.md, auto-refresh-pattern.md, bar.md, line.md, websocket.md
  * module-development-spec references: read-only-module-pattern.md
  * harnessed-module-development SKILL.md (2 occurrences)
  * harnessed-module-development references: reasoning-visualization.md, bricks-ui-pitfalls.md
  * rbac-permission-initialization-pattern references: websocket-debugging.md
This commit is contained in:
yumoqing 2026-05-24 16:05:56 +08:00
parent b83131146a
commit c7180bda77
2 changed files with 25 additions and 43 deletions

View File

@ -1,48 +1,42 @@
/** /**
* Dashboard auto-refresh script * Dashboard auto-refresh script
* Polls API every 10 seconds and updates stat cards + chart * Polls API every 10 seconds and updates stat cards
* Auto-loaded by Sage's header.tmpl from wwwroot/scripts/ * ChartBar handles its own refresh via refresh_period
* Auto-loaded by Sage's header.tmpl from wwwroot/
*/ */
(function() { (function() {
'use strict'; 'use strict';
// Derive base URL from current page (e.g. /dashboard_for_sage/index.ui -> /dashboard_for_sage)
function getBaseUrl() { function getBaseUrl() {
var path = window.location.pathname; var path = window.location.pathname;
// path is like /dashboard_for_sage/index.ui or /dashboard_for_sage // path is like /dashboard_for_sage/index.ui or /dashboard_for_sage/
var parts = path.split('/').filter(function(p) { return p; }); var parts = path.split('/').filter(function(p) { return p.length > 0; });
if (parts.length === 0) return '';
return '/' + parts[0]; return '/' + parts[0];
} }
function buildUrl(dspyFile) { function buildUrl(dspyFile) {
var base = getBaseUrl(); var base = getBaseUrl();
// Add _webbricks_=1 for non-initial requests
return base + '/' + dspyFile + '?_webbricks_=1'; return base + '/' + dspyFile + '?_webbricks_=1';
} }
function updateCard(id, value) { function setCardText(cardId, value) {
try { try {
var widget = bricks.app.find_widget_by_id(id); var widget = bricks.getWidgetById(cardId, bricks.app);
if (widget && widget.el) { if (widget && typeof widget.set_text === 'function') {
widget.el.textContent = value; widget.set_text(String(value));
}
} catch(e) {
// Widget may not be ready yet
} }
} catch(e) {}
} }
function formatNumber(n) { function formatNumber(n) {
if (typeof n === 'number') { if (typeof n === 'number') return n.toLocaleString();
return n.toLocaleString(); return String(n);
}
return n;
} }
function formatAmount(n) { function formatAmount(n) {
if (typeof n === 'number') { if (typeof n === 'number') return '\u00a5' + n.toFixed(2);
return '¥' + n.toFixed(2); return '\u00a50.00';
}
return '¥0.00';
} }
async function refreshStats() { async function refreshStats() {
@ -51,10 +45,10 @@
var resp = await fetch(url, { credentials: 'include' }); var resp = await fetch(url, { credentials: 'include' });
if (!resp.ok) return; if (!resp.ok) return;
var data = await resp.json(); var data = await resp.json();
updateCard('today_cnt_value', formatNumber(data.cnt)); setCardText('today_cnt_value', formatNumber(data.cnt));
updateCard('today_amount_value', formatAmount(data.total_amount)); setCardText('today_amount_value', formatAmount(data.total_amount));
} catch(e) { } catch(e) {
// Silently fail on refresh errors console.error('refreshStats error:', e);
} }
} }
@ -64,35 +58,22 @@
var resp = await fetch(url, { credentials: 'include' }); var resp = await fetch(url, { credentials: 'include' });
if (!resp.ok) return; if (!resp.ok) return;
var data = await resp.json(); var data = await resp.json();
updateCard('total_users_value', formatNumber(data.total_users)); setCardText('total_users_value', formatNumber(data.total_users));
updateCard('concurrent_users_value', formatNumber(data.concurrent_users)); setCardText('concurrent_users_value', formatNumber(data.concurrent_users));
} catch(e) { } catch(e) {
// Silently fail on refresh errors console.error('refreshUsers error:', e);
}
}
async function refreshChart() {
try {
var chart = bricks.app.find_widget_by_id('top_models_chart');
if (chart && chart.render_urldata) {
await chart.render_urldata({});
}
} catch(e) {
// Silently fail on refresh errors
} }
} }
async function refreshAll() { async function refreshAll() {
await refreshStats(); await refreshStats();
await refreshUsers(); await refreshUsers();
await refreshChart();
} }
// Start auto-refresh every 10 seconds // Wait for bricks framework to initialize, then start auto-refresh
// Delay initial load to allow bricks framework to initialize
setTimeout(function() { setTimeout(function() {
refreshAll(); refreshAll();
setInterval(refreshAll, 10000); setInterval(refreshAll, 10000);
}, 1000); }, 2000);
})(); })();

View File

@ -188,7 +188,8 @@
"width": "100%", "width": "100%",
"data_url": "{{entire_url('api/get_top_models.dspy')}}", "data_url": "{{entire_url('api/get_top_models.dspy')}}",
"nameField": "model_name", "nameField": "model_name",
"valueFields": ["cnt", "total_amount"] "valueFields": ["cnt", "total_amount"],
"refresh_period": 10
} }
} }
] ]