SysOps: deploy-all — 2026-06-09 10:59 UTC

This commit is contained in:
sysops
2026-06-09 10:59:45 +00:00
parent 21ea3a2c81
commit 7f973b7203
9 changed files with 669 additions and 94 deletions
+105 -25
View File
@@ -2,7 +2,7 @@
{% block extra_head %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<link rel="stylesheet" href="/static/css/hermes.css" />
<link rel="stylesheet" href="/static/css/herman-dashboard.css?v=13" />
<link rel="stylesheet" href="/static/css/herman-dashboard.css?v=14" />
{% endblock %}
{% block content %}
<div class="herman-shell hm-neo">
@@ -35,7 +35,7 @@
<div class="briefing-meta" id="briefing-meta"></div>
</div>
<div class="herman-briefing-actions">
<button type="button" class="btn btn-sm" @click="editLayout = !editLayout" x-text="editLayout ? 'Klaar ordenen' : '⋮⋮ Ordenen'"></button>
<button type="button" class="btn btn-sm" :class="editLayout ? 'btn-pulse' : ''" @click="toggleEditLayout()" x-text="editLayout ? 'Klaar' : '⚙ Dashboard instellen'"></button>
<button type="button" class="btn btn-pulse" @click="refreshLive()" :disabled="loading">↻ Live data</button>
<button type="button" class="btn btn-pulse-green" @click="generate()" :disabled="loading">✨ Genereer dagrapport</button>
<a href="/clients" class="btn btn-pulse-green">CRM Clients</a>
@@ -45,17 +45,18 @@
</header>
<div id="viz-toolbar" class="viz-toolbar"></div>
<div id="dashboard-customize" class="hm-customize-wrap" x-show="editLayout" x-cloak></div>
<div id="briefing-dashboard" class="hm-dash-layout">
<div id="briefing-dashboard" class="hm-dash-layout" :class="editLayout ? 'hm-dash-editing' : ''">
<section class="hm-dash-section hm-dash-full hm-widget-draggable" data-widget="kpis">
<div class="hm-widget-handle" x-show="editLayout">Sleep widget · CEO KPI's</div>
<h3 class="hm-section-title"><span class="hm-live-dot"></span> CEO KPI's</h3>
<section class="hm-dash-section hm-dash-full" data-widget="kpis">
<div class="hm-widget-handle" x-show="editLayout">⋮⋮ Sleep · CEO KPI's</div>
<h3 class="hm-section-title"><span class="hm-live-dot"></span> CEO KPI's <small class="hm-widget-hint" x-show="editLayout">— kies KPI's hieronder</small></h3>
<div id="briefing-kpis" class="hm-neo-kpi-row"></div>
</section>
<section class="hm-dash-section hm-dash-full hm-widget-draggable" data-widget="executive">
<div class="hm-widget-handle" x-show="editLayout">Sleep widget · Executive</div>
<section class="hm-dash-section hm-dash-full" data-widget="executive">
<div class="hm-widget-handle" x-show="editLayout">⋮⋮ Sleep · Executive</div>
<h3 class="hm-section-title"><span class="hm-live-dot"></span> Alles op een rij</h3>
<div class="hm-chart-panel">
<div class="hm-chart-head"><h4>Executive samenvatting</h4><small>live database</small></div>
@@ -64,8 +65,8 @@
<div id="briefing-clients-mini" class="beurs-mini-row" style="margin-top:0.75rem"></div>
</section>
<section class="hm-dash-section hm-dash-full hm-widget-draggable" data-widget="briefing">
<div class="hm-widget-handle" x-show="editLayout">Sleep widget · Briefing</div>
<section class="hm-dash-section hm-dash-full" data-widget="briefing">
<div class="hm-widget-handle" x-show="editLayout">⋮⋮ Sleep · Briefing</div>
<h3 class="hm-section-title">Herman briefing</h3>
<div class="briefing-summary-grid">
<div class="briefing-card summary">
@@ -89,8 +90,16 @@
</div>
</section>
<section class="hm-dash-section hm-widget-draggable" data-widget="retail">
<div class="hm-widget-handle" x-show="editLayout">Sleep widget · Retail</div>
<section class="hm-dash-section hm-dash-full" data-widget="rss">
<div class="hm-widget-handle" x-show="editLayout">⋮⋮ Sleep · RSS feed</div>
<h3 class="hm-section-title"><span class="hm-live-dot"></span> RSS live feed</h3>
<div class="hm-chart-panel">
<div id="briefing-rss-feed" class="hm-rss-panel"></div>
</div>
</section>
<section class="hm-dash-section" data-widget="retail">
<div class="hm-widget-handle" x-show="editLayout">⋮⋮ Sleep · Retail</div>
<h3 class="hm-section-title">Retail operatie</h3>
<div class="hm-dash-two-col">
<div class="hm-chart-panel">
@@ -109,8 +118,8 @@
</div>
</section>
<section class="hm-dash-section hm-dash-full hm-dash-analytics hm-widget-draggable" data-widget="analytics">
<div class="hm-widget-handle" x-show="editLayout">Sleep widget · Analytics</div>
<section class="hm-dash-section hm-dash-full hm-dash-analytics" data-widget="analytics">
<div class="hm-widget-handle" x-show="editLayout">⋮⋮ Sleep · Analytics</div>
<h3 class="hm-section-title"><span class="hm-live-dot"></span> Data & analytics</h3>
<div class="hm-neo-charts">
<div class="hm-chart-panel hm-analytics-panel">
@@ -149,8 +158,8 @@
</div>
</section>
<div class="hub-grid hm-dash-feed hm-widget-draggable" data-widget="feed">
<div class="hm-widget-handle" x-show="editLayout">Sleep widget · Feed</div>
<div class="hub-grid hm-dash-feed" data-widget="feed">
<div class="hm-widget-handle" x-show="editLayout">⋮⋮ Sleep · Feed</div>
<section class="panel">
<h2>Agent feed <span class="live-badge" id="agent-feed-badge">Live</span></h2>
<div id="agent-feed-live">
@@ -175,8 +184,8 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/viz-engine.js?v=3"></script>
<script src="/static/js/dashboard-layout.js?v=1"></script>
<script src="/static/js/briefing-charts.js?v=11"></script>
<script src="/static/js/dashboard-layout.js?v=3"></script>
<script src="/static/js/briefing-charts.js?v=12"></script>
<script>
const INITIAL_BRIEFING = {{ briefing_payload | tojson }};
function dashboardBriefing() {
@@ -184,25 +193,74 @@ function dashboardBriefing() {
loading: false, loadingMsg: 'Herman werkt…', editLayout: false,
content: INITIAL_BRIEFING.content || '', createdAt: INITIAL_BRIEFING.created_at || '',
_pollStop: null, _wsStop: null, _vizMode: 'neo-bars', _stats: null,
_prefs: null, _setDragMode: null, _saveTimer: null,
applyDashboardPrefs(prefs) {
this._prefs = prefs;
window._dashboardKpis = prefs.dashboard_kpis || DashboardLayout.DEFAULT_KPIS;
const dash = document.getElementById('briefing-dashboard');
const order = DashboardLayout.visibleLayout(prefs);
DashboardLayout.applyOrder(dash, order);
DashboardLayout.applyVisibility(dash, prefs.dashboard_widgets);
if (this._setDragMode) this._setDragMode(this.editLayout);
if (this._stats) BriefingCharts.renderLive(dash, this._stats, this._vizMode, window._dashboardKpis);
},
scheduleSave(patch) {
if (!this._prefs) return;
if (patch.widgets) this._prefs.dashboard_widgets = patch.widgets;
if (patch.kpis) this._prefs.dashboard_kpis = patch.kpis;
if (patch.layout) this._prefs.dashboard_layout = patch.layout;
clearTimeout(this._saveTimer);
this._saveTimer = setTimeout(async () => {
await DashboardLayout.saveDashboardConfig(
this._prefs.dashboard_layout,
this._prefs.dashboard_widgets,
this._prefs.dashboard_kpis
);
this.applyDashboardPrefs(this._prefs);
}, 400);
},
toggleEditLayout() {
this.editLayout = !this.editLayout;
if (this._setDragMode) this._setDragMode(this.editLayout);
},
async init() {
window._dashboardVizMode = this._vizMode;
const prefs = await DashboardLayout.loadPrefs();
this._prefs = prefs;
this._vizMode = prefs.global_viz_mode || 'neo-bars';
window._dashboardVizMode = this._vizMode;
DashboardLayout.applyOrder(document.getElementById('briefing-dashboard'), prefs.dashboard_layout || []);
DashboardLayout.initDrag(document.getElementById('briefing-dashboard'), (order) => DashboardLayout.saveLayout(order));
window._dashboardKpis = prefs.dashboard_kpis || DashboardLayout.DEFAULT_KPIS;
const dash = document.getElementById('briefing-dashboard');
this.applyDashboardPrefs(prefs);
this._setDragMode = DashboardLayout.initDrag(dash, (order) => {
const widgets = this._prefs.dashboard_widgets || {};
const hidden = (this._prefs.dashboard_layout || []).filter(id => widgets[id] === false);
const full = order.concat(hidden.filter(id => order.indexOf(id) < 0));
this._prefs.dashboard_layout = full;
this.scheduleSave({ layout: full });
}, false);
DashboardLayout.buildCustomizePanel(document.getElementById('dashboard-customize'), prefs, (patch) => {
this.scheduleSave(patch);
if (patch.widgets) this.applyDashboardPrefs(this._prefs);
if (patch.kpis && this._stats) BriefingCharts.renderKpis(document.getElementById('briefing-kpis'), this._stats, patch.kpis);
});
DashboardLayout.buildVizSelector(document.getElementById('viz-toolbar'), prefs, async (mode) => {
this._vizMode = mode;
window._dashboardVizMode = mode;
await DashboardLayout.saveViz(mode);
document.getElementById('viz-label-sentiment').textContent = mode;
document.getElementById('viz-label-agents').textContent = mode;
if (this._stats) BriefingCharts.renderLive(document.getElementById('briefing-dashboard'), this._stats, mode);
if (this._stats) BriefingCharts.renderLive(dash, this._stats, mode, window._dashboardKpis);
});
window._refreshRssDash = () => this.refreshRss(true);
await this.refreshLive(false);
if (!sessionStorage.getItem('rss_auto_refreshed')) {
const cnt = (this._stats && (this._stats.rss_items || (this._stats.rss_live || []).length)) || 0;
if (cnt < 3) { sessionStorage.setItem('rss_auto_refreshed', '1'); await this.refreshRss(false); }
}
await this.loadApprovals();
await this.loadProjectsMini();
if (this.content) BriefingCharts.renderText(document.getElementById('briefing-dashboard'), this.content, this.createdAt);
if (this.content) BriefingCharts.renderText(dash, this.content, this.createdAt);
window._refreshBriefingLive = (t) => this.refreshLive(t);
this._pollStop = CockpitLive.startPolling(() => { this.refreshLive(false); this.loadApprovals(); }, 30000);
this._wsStop = CockpitLive.connectFeed((data) => {
@@ -210,6 +268,21 @@ function dashboardBriefing() {
CockpitLive.updateLiveBadge(document.getElementById('agent-feed-badge'), new Date().toLocaleTimeString('nl-NL', {hour:'2-digit',minute:'2-digit', timeZone:'Europe/Amsterdam'}));
});
},
async refreshRss(toast) {
if (toast) Cockpit.toast('RSS feeds ophalen…', 'info');
try {
await fetch('/api/retail/rss/refresh', { method: 'POST' });
const live = await fetch('/api/retail/rss/live?limit=25').then(x => x.json());
if (this._stats && live.items) {
this._stats.rss_live = live.items;
this._stats.rss_items = live.count || live.items.length;
BriefingCharts.renderRssFeed(document.getElementById('briefing-rss-feed'), this._stats);
BriefingCharts.renderRetail(document.getElementById('briefing-retail'), this._stats);
BriefingCharts.renderKpis(document.getElementById('briefing-kpis'), this._stats, window._dashboardKpis);
}
if (toast) Cockpit.toast('RSS bijgewerkt (' + (live.count || 0) + ' items)', 'success');
} catch (e) { if (toast) Cockpit.toast(e.message || 'RSS fout', 'error'); }
},
async loadApprovals() {
try {
const r = await fetch('/api/agents/approvals?status=pending&limit=10').then(x => x.json());
@@ -271,9 +344,16 @@ function dashboardBriefing() {
const r = await fetch('/api/herman/briefing/stats').then(x => x.json());
if (r.stats) {
this._stats = r.stats;
BriefingCharts.renderLive(document.getElementById('briefing-dashboard'), r.stats, this._vizMode);
this.renderClientsMini(r.stats);
this.renderActivityLog(r.stats.activity_log || []);
try {
const live = await fetch('/api/retail/rss/live?limit=25').then(x => x.json());
if (live.items && live.items.length) {
this._stats.rss_live = live.items;
this._stats.rss_items = live.count || live.items.length;
}
} catch (e) {}
BriefingCharts.renderLive(document.getElementById('briefing-dashboard'), this._stats, this._vizMode, window._dashboardKpis);
this.renderClientsMini(this._stats);
this.renderActivityLog(this._stats.activity_log || []);
document.getElementById('briefing-meta').textContent = 'Live sync: ' + (r.at || '').substring(0, 19).replace('T', ' ') + ' UTC';
}
if (toast !== false) Cockpit.toast('Live data bijgewerkt', 'success');