agnes-the-ai-analyst/app/web/templates/base.html
ZdenekSrotyr b091cf7003 feat(ui): version badge in footer + /api/version endpoint
UI now shows a small footer badge with:
- release channel + CalVer version (e.g. 'stable-2026.04.47')
- floating image tag (e.g. 'stable')
- time since last container restart (proxy for 'last deployed')

Backend:
- app/api/health.py: /api/health returns image_tag, commit_sha, deployed_at
- app/api/health.py: new /api/version endpoint (lightweight, no DB hit, for
  footer badge polling)

Infra:
- startup-script.sh.tpl: resolves image digest from ghcr pull, derives
  channel + version from the tag name, and writes AGNES_VERSION /
  RELEASE_CHANNEL / AGNES_COMMIT_SHA into .env so the app can surface them
  to the UI.

UI:
- app/web/templates/base.html: footer loads /api/version asynchronously and
  renders '<channel>-<version> · <tag> · deployed <relative> (<UTC>)'.
  Tooltip shows full detail (commit sha, schema version).
2026-04-21 20:19:40 +02:00

76 lines
3 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Data Analyst Portal{% endblock %}</title>
<link rel="stylesheet" href="{{ static_url('style.css') }}">
<link rel="stylesheet" href="{{ static_url('style-custom.css') }}">
{% include '_theme.html' %}
</head>
<body>
<div class="container">
<header>
<div class="logo">
<h1>Data Analyst Portal</h1>
<p class="subtitle">{{ config.INSTANCE_SUBTITLE }}</p>
</div>
{% if session.user %}
<nav>
<span class="user-info">
{% if session.user.picture %}
<img src="{{ session.user.picture }}" alt="Profile" class="avatar">
{% endif %}
{{ session.user.email }}
</span>
<a href="{{ url_for('auth.logout') }}" class="btn btn-secondary btn-sm">Logout</a>
</nav>
{% endif %}
</header>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-messages">
{% for category, message in messages %}
<div class="flash flash-{{ category }}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>&copy; {{ now().year if now is defined else 2024 }} {{ config.INSTANCE_COPYRIGHT or 'AI Data Analyst' }}</p>
<p class="version-badge" style="font-size: 0.75rem; color: var(--muted, #888); margin-top: 0.5rem;">
<span id="agnes-version-badge">Loading version…</span>
</p>
</footer>
</div>
<script>
// Version badge — fetched from /api/version, no auth needed.
(function() {
fetch('/api/version').then(r => r.ok ? r.json() : null).then(v => {
if (!v) return;
const el = document.getElementById('agnes-version-badge');
if (!el) return;
const deployed = new Date(v.deployed_at);
const relative = (() => {
const s = Math.floor((Date.now() - deployed.getTime()) / 1000);
if (s < 60) return s + 's ago';
if (s < 3600) return Math.floor(s/60) + 'm ago';
if (s < 86400) return Math.floor(s/3600) + 'h ago';
return Math.floor(s/86400) + 'd ago';
})();
const tag = v.image_tag && v.image_tag !== 'unknown' ? ' · ' + v.image_tag : '';
el.textContent = `${v.channel}-${v.version}${tag} · deployed ${relative} (${deployed.toISOString().slice(0,19).replace('T',' ')}Z)`;
el.title = `version ${v.version}\nchannel ${v.channel}\nimage tag ${v.image_tag}\ncommit ${v.commit_sha}\nschema v${v.schema_version}\ndeployed at ${v.deployed_at}`;
}).catch(() => {});
})();
</script>
</body>
</html>