From 33b4a3fdd6d4ac8e496e4c32f275ff4aa2b2c388 Mon Sep 17 00:00:00 2001
From: JiriUhlir <149317995+JiriUhlir@users.noreply.github.com>
Date: Wed, 26 Aug 2026 10:10:34 +0200
Subject: [PATCH] Na aplikaci je videt, ktery build to je
Nasazena aplikace o dva commity pozadu funguje a vypada skoro stejne jako
nova. Bez oznaceni buildu se to nepozna a hleda se chyba tam, kde zadna
neni - prave to nas dnes stalo pul hodiny hadani, proc je na produkci
porad stary vzhled.
- oznaceni buildu v pate postranniho menu, pod tlacitkem Odhlasit se:
verze, cas buildu a cislo commitu. Text jde oznacit jednim kliknutim.
- totez jako v hlavicce stranky. V JS balicku uz to
je, ale ten se musi stahnout a rozbalit; meta znacka je videt na jeden
curl bez prihlaseni, a presne to clovek potrebuje pri zjistovani, co bezi
na produkci.
- hodnoty dosazuje Vite pri buildu (define a plugin build-stamp), takze
neplati pro repozitar, ale pro ten konkretni balicek.
- .dockerignore uz nevylucuje .git/. Build z nej cte cislo commitu; bez nej
by se poznal jen cas buildu, ne co v nem je. Do vysledneho image se .git
nedostane, ten se sklada z ciste zakladni image a kopiruje se do nej jen
dist, scripts a migrace.
Ve vyvoji se misto casu pise "vyvoj" - cas buildu by tam byl pokazde jiny
a nikomu by nic nerekl.
Co-Authored-By: Claude Opus 5 (1M context)
---
.dockerignore | 6 +-
documentation/02-appfactory-proxy.md | 35 ++++++++++
documentation/99-zmeny.md | 24 +++++++
vite.config.ts | 68 ++++++++++++++++++-
.../components/dashboard/DashboardLayout.tsx | 13 ++++
web/src/config/version.ts | 46 +++++++++++++
web/src/vite-env.d.ts | 6 ++
7 files changed, 195 insertions(+), 3 deletions(-)
create mode 100644 web/src/config/version.ts
diff --git a/.dockerignore b/.dockerignore
index f925df8..3dd638e 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,6 +1,5 @@
node_modules/
dist/
-.git/
documentation/
*.log
.env
@@ -9,3 +8,8 @@ documentation/
# Lomitko na zacatku je nutne: bez nej by se vzorec shodl s KAZDOU
# slozkou 'data', tedy i se src/data - a zdrojove soubory by tise chybely.
/data/
+
+# `.git/` se zamerne NEvylucuje. Build z nej cte cislo commitu do oznaceni
+# verze - bez nej se na nasazene aplikaci pozna jen cas buildu, ne co v ni je.
+# Do vysledneho image se nedostane: ten vznika z ciste zakladni image
+# a kopiruje se do nej jen `dist`, `scripts` a migrace.
diff --git a/documentation/02-appfactory-proxy.md b/documentation/02-appfactory-proxy.md
index 04fa62d..480884d 100644
--- a/documentation/02-appfactory-proxy.md
+++ b/documentation/02-appfactory-proxy.md
@@ -104,6 +104,41 @@ Definici sestavuje `src/openapi.ts`.
Pozn.: `/api/dashboard/stream` je Server-Sent Events. Swagger UI streamovanou
odpoved rozumne nezobrazi, testuje se prohlizecem nebo curlem.
+## Jak se pozna, ktera verze bezi
+
+Nasazena aplikace, ktera je o dva commity pozadu, funguje a vypada skoro
+stejne jako nova. Bez oznaceni buildu se to nepozna a hleda se chyba tam, kde
+zadna neni - presne to nas jednou stalo pul hodiny.
+
+Kazdy build proto nese svoje oznaceni. Sklada ho Vite pri buildu
+(`vite.config.ts`, sekce `buildInfo`) a je videt na dvou mistech:
+
+| Kde | Jak se k tomu dostat |
+| --------------------- | ------------------------------------------- |
+| Pata postranniho menu | pod tlacitkem Odhlasit se, staci prihlaseni |
+| Hlavicka stranky | `curl` na adresu aplikace, bez prihlaseni |
+
+```bash
+curl -s https://services.csbot.cz/apps/csbot-prototype/ | grep app-build
+#
+```
+
+Tri udaje a kazdy rika neco jineho:
+
+| Udaj | K cemu |
+| ---------- | ------------------------------------------------------ |
+| Verze | z `package.json`, meni se zridka |
+| Cas buildu | **hlavni udaj.** Rika, jestli nasazeni vubec probehlo |
+| Commit | rika, **co** v tom je. Bez nej se pozna jen kdy, ne co |
+
+Commit se cte z `.git`, takze se `.git/` **zamerne nevylucuje**
+v `.dockerignore`. Do vysledneho image se nedostane: ten vznika z ciste
+zakladni image a kopiruje se do nej jen `dist`, `scripts` a migrace. Kdyby
+`.git` pri buildu chybelo, oznaceni se nerozbije, jen commit zustane prazdny.
+
+Ve vyvoji se misto casu pise "vyvoj". Cas buildu by tam byl pokazde jiny
+a nikomu by nic nerekl.
+
## Overeni po zmene
```bash
diff --git a/documentation/99-zmeny.md b/documentation/99-zmeny.md
index 947d2a4..e81cdcc 100644
--- a/documentation/99-zmeny.md
+++ b/documentation/99-zmeny.md
@@ -2,6 +2,30 @@
Nejnovejsi nahore.
+## 2026-08-26 - na aplikaci je videt, ktery build to je
+
+Nasazena aplikace o dva commity pozadu funguje a vypada skoro stejne jako
+nova. Bez oznaceni buildu se to nepozna a hleda se chyba tam, kde zadna neni.
+
+### Pridano
+
+- **Oznaceni buildu v pate postranniho menu**, pod tlacitkem Odhlasit se:
+ verze, cas buildu a cislo commitu. Text jde oznacit jednim kliknutim, aby
+ se dal poslat dal.
+- **Totez jako `` v hlavicce stranky.** V JS balicku uz
+ to je, ale ten se musi stahnout a rozbalit. Meta znacka je videt na jeden
+ `curl` bez prihlaseni - a prave to clovek potrebuje, kdyz zjistuje, co bezi
+ na produkci.
+- Hodnoty dosazuje Vite pri buildu (`define` a plugin `build-stamp`
+ ve `vite.config.ts`), takze neplati pro repozitar, ale pro ten konkretni
+ balicek.
+
+### Zmeneno
+
+- **`.dockerignore` uz nevylucuje `.git/`.** Build z nej cte cislo commitu;
+ bez nej by se poznal jen cas buildu, ne co v nem je. Do vysledneho image se
+ `.git` nedostane, ten se sklada z ciste zakladni image.
+
## 2026-08-26 - rebrand na WorkNuke
Prevzeti vizualniho smeru z predlohy. Popis, jak znacka funguje v kodu, je
diff --git a/vite.config.ts b/vite.config.ts
index c3c44a8..2acee19 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,11 +1,72 @@
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
+import { execSync } from 'node:child_process';
+import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
-import { defineConfig } from 'vite';
+import { defineConfig, type Plugin } from 'vite';
const here = path.dirname(fileURLToPath(import.meta.url));
+/**
+ * Oznaceni buildu.
+ *
+ * Bez nej se nasazena verze nepozna: aplikace bezi, jen je stara, a z venku
+ * to vypada stejne jako kdyz je nova. Prave to nas jednou stalo pul hodiny
+ * hadani, proc je na produkci porad stary vzhled.
+ *
+ * Cas buildu je hlavni udaj, protoze funguje vzdycky. Commit je bonus: do
+ * image se `.git` nekopiruje (`.dockerignore`), takze v produkcnim buildu
+ * chybi a zustane prazdny.
+ */
+const pkg: { version: string } = JSON.parse(
+ fs.readFileSync(path.resolve(here, 'package.json'), 'utf8'),
+);
+
+function gitCommit(): string {
+ try {
+ return execSync('git rev-parse --short HEAD', { stdio: ['ignore', 'pipe', 'ignore'] })
+ .toString()
+ .trim();
+ } catch {
+ // V containeru `.git` neni. Neni to chyba, jen se commit nedozvime.
+ return '';
+ }
+}
+
+const buildInfo = {
+ version: pkg.version,
+ builtAt: new Date().toISOString(),
+ commit: gitCommit(),
+};
+
+/**
+ * Vlozi oznaceni buildu i do hlavicky stranky.
+ *
+ * V JS balicku uz je, ale ten se musi stahnout a rozbalit. Meta znacka je
+ * videt na jeden `curl` bez prihlaseni - a presne to clovek potrebuje, kdyz
+ * zjistuje, co vlastne bezi na produkci.
+ */
+function buildStamp(): Plugin {
+ return {
+ name: 'build-stamp',
+ transformIndexHtml() {
+ return [
+ {
+ tag: 'meta',
+ attrs: {
+ name: 'app-build',
+ content: [buildInfo.version, buildInfo.builtAt, buildInfo.commit]
+ .filter(Boolean)
+ .join(' '),
+ },
+ injectTo: 'head',
+ },
+ ];
+ },
+ };
+}
+
export default defineConfig({
root: path.resolve(here, 'web'),
/**
@@ -14,7 +75,10 @@ export default defineConfig({
* podle ROOT_PATH a relativni odkazy se podle nej slozi spravne.
*/
base: './',
- plugins: [react(), tailwindcss()],
+ plugins: [react(), tailwindcss(), buildStamp()],
+ define: {
+ __BUILD_INFO__: JSON.stringify(buildInfo),
+ },
resolve: {
// Musi zustat v souladu s "paths" ve web/tsconfig.json
alias: {
diff --git a/web/src/components/dashboard/DashboardLayout.tsx b/web/src/components/dashboard/DashboardLayout.tsx
index 1040979..7769b7a 100644
--- a/web/src/components/dashboard/DashboardLayout.tsx
+++ b/web/src/components/dashboard/DashboardLayout.tsx
@@ -27,6 +27,7 @@ import { EventToasts } from '@/components/dashboard/EventToasts';
import { LiveIndicator } from '@/components/dashboard/LiveIndicator';
import { Logo } from '@/components/layout/Logo';
import { cn } from '@/lib/cn';
+import { buildInfo, versionLabel } from '@/config/version';
import { setActiveTenant, useActiveTenant } from '@/lib/tenant';
import { useApiQuery } from '@/lib/useApiQuery';
import type { Access } from '@/types/dashboard';
@@ -247,6 +248,18 @@ function DashboardShell() {
Odhlásit se
+
+ {/*
+ Ktera verze zrovna bezi. Bez toho se nasazena aplikace nepozna od
+ stare: obojí funguje a vypada skoro stejne. Cas je z buildu, ne
+ z prohlizece.
+ */}
+
+ {versionLabel()}
+
diff --git a/web/src/config/version.ts b/web/src/config/version.ts
new file mode 100644
index 0000000..c4b83d6
--- /dev/null
+++ b/web/src/config/version.ts
@@ -0,0 +1,46 @@
+/**
+ * Ktera verze zrovna bezi.
+ *
+ * Hodnoty dosazuje Vite pri buildu (`define` ve `vite.config.ts`), takze
+ * neplati pro repozitar, ale **pro tenhle konkretni balicek** - presne to,
+ * co clovek potrebuje vedet, kdyz se diva na nasazenou aplikaci.
+ *
+ * Cas buildu je hlavni udaj: rozliseni na sekundy staci, aby se poznalo, ze
+ * nasazeni probehlo nebo neprobehlo. Commit je nepovinny, do image se `.git`
+ * nekopiruje.
+ */
+
+const info: { version: string; builtAt: string; commit: string } =
+ typeof __BUILD_INFO__ === 'undefined'
+ ? { version: '0.0.0', builtAt: '', commit: '' }
+ : __BUILD_INFO__;
+
+export const buildInfo = info;
+
+/**
+ * Kratky popis verze pro patu postranniho menu.
+ *
+ * Ve vyvoji se cas buildu nepise: mel by tam kazdou minutu jiny a nikomu nic
+ * nerekne. Na nasazene aplikaci je naopak to hlavni.
+ */
+export function versionLabel(): string {
+ const parts = [`v${info.version}`];
+
+ if (import.meta.env.DEV) {
+ parts.push('vývoj');
+ } else if (info.builtAt) {
+ parts.push(
+ new Date(info.builtAt).toLocaleString('cs-CZ', {
+ day: 'numeric',
+ month: 'numeric',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ }),
+ );
+ }
+
+ if (info.commit) parts.push(info.commit);
+
+ return parts.join(' · ');
+}
diff --git a/web/src/vite-env.d.ts b/web/src/vite-env.d.ts
index 05dd671..591bd29 100644
--- a/web/src/vite-env.d.ts
+++ b/web/src/vite-env.d.ts
@@ -1,6 +1,12 @@
///
declare global {
+ /**
+ * Oznaceni buildu dosazene Vitem (`define` ve vite.config.ts).
+ * Ve vyvoji i v produkci existuje vzdy, jen s jinym obsahem.
+ */
+ const __BUILD_INFO__: { version: string; builtAt: string; commit: string };
+
interface Window {
/**
* Prefix reverse proxy vlozeny serverem do index.html podle ROOT_PATH.