Files
foodlinkk-command-center/cockpit/templates/hermes.html
T

269 lines
14 KiB
HTML
Raw Normal View History

{% extends "base.html" %}
{% block title %}Hermes · KPI's{% endblock %}
{% block extra_head %}
<link rel="stylesheet" href="/static/css/hermes.css?v=11" />
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
{% endblock %}
{% block content %}
<div x-data="hermesKpiDashboard()" x-init="init()" class="hermes-shell hm-neo">
<header class="hm-neo-header page-header">
<div>
<h1>Hermes · Technische KPI's</h1>
<p class="hm-sub">Live processen van Hermes Agent + Herman — klik een blok voor detail ·
<a href="http://10.4.7.10:9119/" target="_blank" rel="noopener">Hermes UI</a>
<span x-show="kpis.synced_at" class="text-muted" x-text="' · sync ' + formatTime(kpis.synced_at)"></span>
</p>
</div>
<div class="retail-actions">
<span class="status-badge" :class="kpis.online ? 'on' : 'off'" x-text="kpis.online ? 'Online' : 'Offline'"></span>
<button type="button" class="btn btn-sm" @click="refresh()" :disabled="loading">↻ Vernieuwen</button>
</div>
</header>
<!-- Zone A -->
<div class="hm-neo-kpi-row">
<div class="hm-neo-kpi" :class="{ highlight: kpis.online }">
<div class="hm-neo-kpi-top">
<div class="hm-neo-ring" :style="'--ring-color:' + (kpis.online ? '#b8ff3c' : '#f87171') + ';--ring-pct:80%'"><div class="hm-neo-ring-inner" x-text="kpis.online ? '●' : '○'"></div></div>
<div><div class="hm-neo-kpi-label">Hermes</div><div class="hm-neo-kpi-sub">Agent status</div></div>
</div>
<div class="hm-neo-kpi-value" style="font-size:1.25rem" x-text="kpis.online ? 'Online' : 'Offline'"></div>
</div>
<div class="hm-neo-kpi">
<div class="hm-neo-kpi-top">
<div class="hm-neo-ring" style="--ring-color:#00e5ff;--ring-pct:70%"><div class="hm-neo-ring-inner">✈️</div></div>
<div><div class="hm-neo-kpi-label">Telegram</div><div class="hm-neo-kpi-sub">actieve chats</div></div>
</div>
<div class="hm-neo-kpi-value" x-text="ov.telegram_sessions_active || 0"></div>
</div>
<div class="hm-neo-kpi">
<div class="hm-neo-kpi-top">
<div class="hm-neo-ring" style="--ring-color:#ff9f43;--ring-pct:75%"><div class="hm-neo-ring-inner">🔧</div></div>
<div><div class="hm-neo-kpi-label">Tool-calls</div><div class="hm-neo-kpi-sub">totaal werk</div></div>
</div>
<div class="hm-neo-kpi-value" x-text="fmt(ov.tool_calls_total)"></div>
</div>
<div class="hm-neo-kpi">
<div class="hm-neo-kpi-top">
<div class="hm-neo-ring" style="--ring-color:#a855f7;--ring-pct:65%"><div class="hm-neo-ring-inner">💬</div></div>
<div><div class="hm-neo-kpi-label">Tekst in / uit</div><div class="hm-neo-kpi-sub">user · agent</div></div>
</div>
<div class="hm-neo-kpi-value" style="font-size:1.15rem" x-text="(ov.text_in||0) + ' / ' + (ov.text_out||0)"></div>
</div>
<div class="hm-neo-kpi">
<div class="hm-neo-kpi-top">
<div class="hm-neo-ring" style="--ring-color:#38bdf8;--ring-pct:55%"><div class="hm-neo-ring-inner">🧩</div></div>
<div><div class="hm-neo-kpi-label">Skills</div><div class="hm-neo-kpi-sub">geïnstalleerd</div></div>
</div>
<div class="hm-neo-kpi-value" x-text="ov.skills_installed || 0"></div>
</div>
<div class="hm-neo-kpi">
<div class="hm-neo-kpi-top">
<div class="hm-neo-ring" style="--ring-color:#94a3b8;--ring-pct:50%"><div class="hm-neo-ring-inner">⏱</div></div>
<div><div class="hm-neo-kpi-label">Sync</div><div class="hm-neo-kpi-sub">laatste snapshot</div></div>
</div>
<div class="hm-neo-kpi-value" style="font-size:0.95rem" x-text="formatTime(kpis.synced_at) || '—'"></div>
</div>
</div>
<!-- Zone B -->
<h2 class="hm-section-title">Wat doet hij? <span class="hm-section-hint">klik een blok</span></h2>
<div class="hm-bucket-grid">
<template x-for="b in (kpis.buckets || [])" :key="b.key">
<button type="button" class="hm-bucket-card hm-bucket-click" @click="openBucket(b)">
<div class="hm-bucket-label" x-text="b.label"></div>
<div class="hm-bucket-count" x-text="fmt(b.count)"></div>
<div class="hm-bucket-parts" x-text="partsLine(b.parts)"></div>
<div class="hm-bucket-open">Open →</div>
</button>
</template>
</div>
<!-- Zone C -->
<div class="hm-neo-charts" style="margin-top:1.25rem">
<div class="hm-chart-panel">
<div class="hm-chart-head"><h3>Top tools</h3><small>meest gebruikt</small></div>
<div class="hm-chart-wrap"><canvas id="hm-chart-tools"></canvas></div>
<ul class="hm-tool-list">
<template x-for="t in (kpis.tools_top || []).slice(0,8)" :key="t.name">
<li><span x-text="t.name"></span><strong x-text="fmt(t.count)"></strong></li>
</template>
</ul>
</div>
<div class="hm-chart-panel">
<div class="hm-chart-head"><h3>Bronnen</h3><small>via welk kanaal</small></div>
<div class="hm-chart-wrap"><canvas id="hm-chart-sources"></canvas></div>
<ul class="hm-tool-list">
<template x-for="s in (kpis.sources || [])" :key="s.source">
<li>
<span x-text="sourceLabel(s.source)"></span>
<strong x-text="fmt(s.tools) + ' tools · ' + (s.sessions||0) + ' sessies'"></strong>
</li>
</template>
</ul>
</div>
<div class="hm-chart-panel">
<div class="hm-chart-head"><h3>Herman (cockpit)</h3><small>agent_events</small></div>
<ul class="hm-tool-list hm-tool-list-tall">
<template x-for="e in (kpis.herman_cockpit || [])" :key="e.event_type">
<li><span x-text="e.event_type"></span><strong x-text="fmt(e.count)"></strong></li>
</template>
<li x-show="!(kpis.herman_cockpit || []).length" class="text-muted">Geen events</li>
</ul>
<p class="page-tip" style="margin-top:0.75rem;font-size:0.72rem">
Delegaties: <strong x-text="ov.async_delegations || 0"></strong> ·
Delivery: <strong x-text="ov.delivery_obligations || 0"></strong>
</p>
</div>
</div>
<!-- Explorer / detail modal -->
<div class="hm-modal-backdrop" x-show="modal.open" x-cloak @keydown.escape.window="closeModal()" @click.self="closeModal()">
<div class="hm-modal" role="dialog" aria-modal="true" @click.stop>
<header class="hm-modal-head">
<div>
<h2 x-text="modal.title"></h2>
<p class="hm-modal-sub">
<span x-text="fmt(modal.detail.count || 0) + ' calls'"></span>
<template x-for="(n, name) in (modal.detail.parts || {})" :key="name">
<button type="button" class="hm-chip" :class="{ active: modal.toolFilter === name }"
@click="setToolFilter(name)" x-text="n + ' ' + name.replace(/_/g,' ')"></button>
</template>
<button type="button" class="hm-chip" :class="{ active: !modal.toolFilter }" @click="setToolFilter(null)">alles</button>
</p>
</div>
<button type="button" class="hm-modal-close" @click="closeModal()" aria-label="Sluiten">×</button>
</header>
<div class="hm-modal-tabs" x-show="modal.key === 'files'">
<button type="button" :class="{ active: modal.tab === 'activity' }" @click="modal.tab = 'activity'">Activiteit</button>
<button type="button" :class="{ active: modal.tab === 'touched' }" @click="modal.tab = 'touched'">Aangeraakte files</button>
<button type="button" :class="{ active: modal.tab === 'explorer' }" @click="openExplorerTab()">Verkenner</button>
</div>
<div class="hm-modal-body" :class="{ 'hm-modal-split': true }">
<!-- Left pane -->
<aside class="hm-modal-side">
<div class="hm-side-loading" x-show="modal.loading">Laden…</div>
<!-- Activity list -->
<template x-if="modal.tab === 'activity' || modal.key !== 'files'">
<ul class="hm-event-list">
<template x-for="ev in (modal.detail.events || [])" :key="ev.id">
<li>
<button type="button" class="hm-event-item" :class="{ active: modal.selectedEvent && modal.selectedEvent.id === ev.id }"
@click="selectEvent(ev)">
<div class="hm-event-top">
<strong x-text="ev.title"></strong>
<time x-text="formatTime(ev.created_at)"></time>
</div>
<div class="hm-event-tool" x-text="ev.tool"></div>
<div class="hm-event-preview" x-text="ev.preview"></div>
</button>
</li>
</template>
<li x-show="!(modal.detail.events || []).length && !modal.loading" class="text-muted hm-empty">Geen events</li>
</ul>
</template>
<!-- Touched files -->
<template x-if="modal.key === 'files' && modal.tab === 'touched'">
<ul class="hm-file-list">
<template x-for="f in (modal.detail.files || [])" :key="f.path">
<li>
<button type="button" class="hm-file-item" :class="{ active: modal.preview && modal.preview.path === f.path }"
@click="openFile(f.path)">
<div class="hm-file-name">
<span x-text="f.exists ? '📄' : '◌'"></span>
<strong x-text="f.name"></strong>
</div>
<div class="hm-file-path" x-text="f.path"></div>
<div class="hm-file-meta">
<span x-text="Object.keys(f.ops || {}).map(k => (f.ops[k] + ' ' + k.replace(/_/g,' '))).join(' · ')"></span>
<time x-text="formatTime(f.last_at)"></time>
</div>
</button>
</li>
</template>
<li x-show="!(modal.detail.files || []).length && !modal.loading" class="text-muted hm-empty">Geen bestanden gevonden</li>
</ul>
</template>
<!-- Explorer -->
<template x-if="modal.key === 'files' && modal.tab === 'explorer'">
<div class="hm-explorer">
<div class="hm-explorer-bar">
<button type="button" class="btn btn-sm" @click="explorerUp()" :disabled="!modal.explorer.parent && !modal.explorer.path">↑ Omhoog</button>
<button type="button" class="btn btn-sm" @click="loadExplorer('')">Roots</button>
<code class="hm-explorer-path" x-text="modal.explorer.path || '(roots)'"></code>
</div>
<ul class="hm-file-list">
<template x-for="e in (modal.explorer.entries || [])" :key="e.path">
<li>
<button type="button" class="hm-file-item" @click="e.type === 'dir' ? loadExplorer(e.path) : openFile(e.path)">
<div class="hm-file-name">
<span x-text="e.type === 'dir' ? '📁' : '📄'"></span>
<strong x-text="e.name"></strong>
</div>
<div class="hm-file-meta">
<span x-show="e.size != null" x-text="fmtBytes(e.size)"></span>
<time x-text="formatTime(e.mtime)"></time>
</div>
</button>
</li>
</template>
</ul>
</div>
</template>
</aside>
<!-- Right pane preview -->
<section class="hm-modal-preview">
<div x-show="modal.previewLoading" class="hm-side-loading">Bestand laden…</div>
<template x-if="modal.preview && !modal.previewLoading">
<div>
<div class="hm-preview-head">
<div>
<h3 x-text="modal.preview.name || 'Preview'"></h3>
<code x-text="modal.preview.path || ''"></code>
</div>
<div class="hm-preview-meta" x-show="modal.preview.size != null">
<span x-text="fmtBytes(modal.preview.size)"></span>
<span x-text="formatTime(modal.preview.mtime)"></span>
</div>
</div>
<pre class="hm-preview-code" x-text="modal.preview.content || modal.preview.preview || modal.preview.error || '—'"></pre>
<p class="hm-preview-note" x-show="modal.preview.truncated">Preview afgekapt (bestand te groot).</p>
</div>
</template>
<template x-if="!modal.preview && modal.selectedEvent && !modal.previewLoading">
<div>
<div class="hm-preview-head">
<div>
<h3 x-text="modal.selectedEvent.title"></h3>
<p class="hm-modal-sub" x-text="modal.selectedEvent.tool + ' · ' + formatTime(modal.selectedEvent.created_at)"></p>
</div>
</div>
<pre class="hm-preview-code" x-text="modal.selectedEvent.preview || '—'"></pre>
<div class="hm-preview-paths" x-show="(modal.selectedEvent.paths || []).length">
<template x-for="p in (modal.selectedEvent.paths || [])" :key="p">
<button type="button" class="hm-chip" @click="openFile(p); if(modal.key==='files') modal.tab='touched'" x-text="p"></button>
</template>
</div>
</div>
</template>
<div x-show="!modal.preview && !modal.selectedEvent && !modal.previewLoading" class="hm-empty-pane">
Selecteer een event of bestand links om details te zien.
</div>
</section>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/hermes-ui.js?v=11"></script>
{% endblock %}