Nahrazeni sablony kompletnim webem a klientskym portalem

Web a portal Automia v jednom containeru. Express obsluhuje API
i zbuildovanou React aplikaci z dist/public.

Obsah:
- verejny web: homepage, sluzby, o nas, kontakt, 404
- prihlaseni pres JWT, demo ucty
- portal: prehled s grafem, tickety, incidenty, automatizace, konektory
- builder automatizaci: strom akci, vetveni podminkou
- katalog 25 konektoru v 8 kategoriich
- webhook s registrovanou adresou, token generuje server
- zivy dashboard pres SSE vcetne simulace provozu
- Swagger UI na /docs a OpenAPI na /openapi.json

Soulad s AGENTS.md:
- ROOT_PATH z prostredi, prefix proxy nikde nehardcodovan
- mount na koren i na prefix, funguje s handle_path i bez nej
- base tag a window.__BASE_PATH__ vkladane do index.html za behu
- OpenAPI servers obsahuje prefix, Try it out vola spravnou adresu
- povinne /health a /docs, port 3000, naslouchani na 0.0.0.0
- secrets jen z environment variables, nikdy v logu

Dokumentace ve slozce documentation/.
This commit is contained in:
JiriUhlir
2026-07-31 17:00:37 +02:00
parent 46f2f0b07e
commit 7b045a9f20
100 changed files with 15409 additions and 35 deletions
+45
View File
@@ -0,0 +1,45 @@
# 01 - Prehled a stav
## Co aplikace je
Web a klientsky portal IT firmy. Verejna cast prodava sluzbu, cast za prihlasenim
ukazuje klientovi stav jeho automatizaci, ticketu a incidentu.
Vse je jedna aplikace v jednom containeru. Express obsluhuje API i zbuildovanou
React aplikaci ze slozky `dist/public`.
## Stav
| Oblast | Stav | Poznamka |
| --------------------------------- | ------ | ----------------------------------------------------- |
| Verejny web | hotovo | homepage, sluzby, o nas, kontakt, 404 |
| Prihlaseni | hotovo | JWT, demo ucty |
| Dashboard | hotovo | prehled, tickety, incidenty, automatizace, nastaveni |
| Zivy dashboard pres SSE | hotovo | zmeny se projevi bez obnoveni stranky |
| Simulace provozu | hotovo | tlacitko v postrannim menu portalu |
| Katalog konektoru | hotovo | 25 sluzeb, 8 kategorii |
| Builder automatizaci | hotovo | strom akci, vetveni podminkou |
| Webhook s registrovanou adresou | hotovo | token generuje server, verejny endpoint validuje data |
| Nastaveni poli akci | chybi | akce zatim neumi cerpat z parametru spoustece |
| Beh automatizaci | chybi | ulozeny strom se nevykonava, neni runtime |
| Databaze | chybi | data jsou v pameti, restart je vrati na vychozi stav |
| Odesilani e-mailu z formulare | chybi | poptavka se zatim jen loguje |
## Znama omezeni
Data jsou v pameti procesu. Restart containeru vrati tickety, incidenty
i automatizace do vychoziho stavu. Nove vytvorene zaznamy se ztrati.
Obsah verejneho webu je ukazkovy. Nazev firmy, reference, tym i cisla jsou
vymyslene a pred ostrym pouzitim se musi nahradit. Firemni udaje jsou na jednom
miste v `web/src/config/brand.ts`.
Zivy stream drzi seznam posluchacu v pameti jedne instance. Pri vice instancich
by ho musel nahradit sdileny kanal, napriklad Redis pub/sub.
## Dalsi krok
Nejuzitecnejsi pristavek je nastaveni poli akci a s nim predavani dat mezi kroky,
aby slo rict "do e-mailu dej parametr customer ze spoustece". Je to zasah do
datoveho modelu, vyplati se navrhnout drive nez se builder rozsiri dal.
Podrobnosti v [05-dashboard-a-builder.md](05-dashboard-a-builder.md).
+103
View File
@@ -0,0 +1,103 @@
# 02 - AppFactory a reverse proxy
Pravidla jsou v [AGENTS.md](../AGENTS.md). Tenhle soubor popisuje, jak je
aplikace plni.
## Kde aplikace bezi
```
prohlizec Caddy container
https://services.csbot.cz/apps/<app-id>/dashboard
handle_path odstrani prefix
GET /dashboard
```
Prohlizec tedy vidi prefix, aplikace uvnitr uz ne. Z toho plyne vsechno ostatni.
## Prefix se nikdy nehardcoduje
Prichazi z promenne `ROOT_PATH`. Zpracovava ho `src/config.ts`, ktery ho
normalizuje (doplni uvodni lomitko, odstrani koncove).
Aplikace se mountuje na koren **i** na prefix:
```ts
app.use(api);
if (config.rootPath) app.use(config.rootPath, api);
```
Diky tomu funguje at uz Caddy prefix odstrani, nebo ne, a taky lokalne bez proxy.
## Jak se resi statika a routovani SPA
Tohle je nejcastejsi misto, kde aplikace za proxy spadne.
Vite build ma `base: './'`, tedy relativni odkazy na soubory. Server pri odeslani
`index.html` vklada do hlavicky:
```html
<base href="/apps/<app-id>/">
<script>window.__BASE_PATH__ = "/apps/<app-id>";</script>
```
- `<base>` zajisti, ze se relativni odkazy na CSS a JS slozi spravne i na vnorene
ceste jako `/apps/<app-id>/dashboard/tickety`.
- `window.__BASE_PATH__` cte frontend. Pouziva ho `web/src/lib/api.ts` pro
skladani adres API a `web/src/main.tsx` jako `basename` pro React Router.
Bez `<base>` by prohlizec hledal soubory v `/apps/<app-id>/dashboard/assets/...`
a dostal by HTML aplikace misto skriptu.
## Povinne endpointy
| Verejna cesta | Vraci |
| ------------------------------ | -------------------------------------- |
| `/apps/<app-id>/health` | `{"status":"ok","uptimeSec":N}` |
| `/apps/<app-id>/docs` | presmeruje na `/docs/` |
| `/apps/<app-id>/docs/` | Swagger UI |
| `/apps/<app-id>/openapi.json` | OpenAPI definice |
Presmerovani z `/docs` na `/docs/` je nutne. Bez koncoveho lomitka by se
relativni odkazy Swagger UI na CSS a JS skladaly o uroven vys a nenacetly by se.
Router je proto vytvoreny s `strict: true`. Bez toho by se cesta `/docs`
shodovala i s `/docs/` a presmerovani by se zacyklilo.
## Swagger Try it out
`servers` v OpenAPI obsahuje prefix z `ROOT_PATH`:
```json
{ "servers": [{ "url": "/apps/csbot-prototype" }] }
```
Diky tomu tlacitko Try it out vola endpointy pres prefix, ne na koreni domeny.
Definici sestavuje `src/openapi.ts`.
Pozn.: `/api/dashboard/stream` je Server-Sent Events. Swagger UI streamovanou
odpoved rozumne nezobrazi, testuje se prohlizecem nebo curlem.
## Overeni po zmene
```bash
curl -i https://services.csbot.cz/apps/<app-id>/health
curl -i https://services.csbot.cz/apps/<app-id>/docs/
curl -s https://services.csbot.cz/apps/<app-id>/openapi.json | head -40
```
Ve Swagger UI zkontrolovat, ze Try it out vola adresu s `/apps/<app-id>`.
Lokalne se da proxy simulovat:
```bash
ROOT_PATH=/apps/csbot-prototype npm start
curl -i http://localhost:3000/apps/csbot-prototype/health
curl -i http://localhost:3000/health
```
Obe varianty musi vratit 200.
## Co se v tomhle repozitari nemeni
Deploy mechanismus, konfigurace Caddy, Gitea webhooky, registry, secrets storage
ani AppFactory sluzby. Port 3000 se nemeni bez upravy metadat aplikace.
@@ -0,0 +1,85 @@
# 03 - Architektura a mapa kodu
## Technologie
| Vrstva | Technologie |
| ------- | ---------------------------------------------------- |
| Server | Node.js 20, Express 4, TypeScript, ESM |
| Web | React 18, Vite 6, TypeScript, Tailwind 4, React Router 6 |
| Auth | JWT (jsonwebtoken), hesla bcrypt |
| Validace| zod |
| Docs | swagger-ui-express nad rucne psanou OpenAPI definici |
Jeden `package.json`. Runtime zavislosti jsou v `dependencies`, nastroje pro build
webu v `devDependencies` - runtime image je pak instaluje pres `--omit=dev`.
## Build
```
tsc src/**.ts -> dist/*.js
vite web/ -> dist/public/
```
Server obsluhuje `dist/public` jako statiku. Dockerfile kopiruje do vysledneho
image jen `dist`, takze staci jedna slozka.
## Mapa kodu - server
| Cesta | K cemu je |
| --------------------------- | -------------------------------------------------------- |
| `src/index.ts` | vstupni bod: middleware, mount routeru, statika, SPA, Swagger |
| `src/config.ts` | cteni environment variables, normalizace `ROOT_PATH` |
| `src/openapi.ts` | OpenAPI definice vcetne `servers` s prefixem proxy |
| `src/types.ts` | typy uzivatele a JWT payloadu |
| `src/middleware/auth.ts` | `requireAuth`, `requireRole` |
| `src/events/bus.ts` | sbernice udalosti, ze ktere cerpa SSE stream |
| `src/routes/auth.ts` | prihlaseni, odhlaseni, kdo jsem |
| `src/routes/dashboard.ts` | data portalu, katalog konektoru, CRUD automatizaci |
| `src/routes/stream.ts` | SSE stream zmen |
| `src/routes/simulate.ts` | vyvolani provoznich udalosti |
| `src/routes/webhook.ts` | verejny prijem dat do automatizace |
| `src/routes/contact.ts` | poptavkovy formular z webu |
| `src/data/ticketStore.ts` | tickety vcetne zmen a udalosti |
| `src/data/incidentStore.ts` | incidenty vcetne zmen a udalosti |
| `src/data/automationStore.ts` | automatizace, strom akci, tokeny webhooku |
| `src/data/connectors.ts` | katalog konektoru, jejich spousteču a akci |
| `src/data/conditions.ts` | typy parametru a operatory podminek |
| `src/data/users.ts` | demo uzivatele |
| `src/data/mock.ts` | souhrn pro prehled a casova rada grafu |
## Mapa kodu - web
| Cesta | K cemu je |
| ---------------------------------- | -------------------------------------------------- |
| `web/src/main.tsx` | vstupni bod, `basename` routeru podle prefixu proxy |
| `web/src/App.tsx` | routovani, portal se nacita lazy |
| `web/src/index.css` | design tokeny a vlastni utility Tailwindu |
| `web/src/config/brand.ts` | vsechny firemni udaje na jednom miste |
| `web/src/lib/api.ts` | fetch wrapper, sprava tokenu, skladani adres |
| `web/src/lib/eventStream.ts` | cteni SSE streamu pres fetch |
| `web/src/lib/useApiQuery.ts` | nacitani dat vcetne obnoveni pri udalosti |
| `web/src/lib/flow.ts` | ciste funkce nad stromem automatizace |
| `web/src/components/dashboard/` | shell portalu, dlazdice, graf, stream, simulace |
| `web/src/components/dashboard/flow/` | strom akci a vyber kroku |
| `web/src/components/home/` | sekce homepage |
| `web/src/pages/` | jedna stranka je jeden soubor |
## Klicova rozhodnuti
**Jeden container misto dvou.** AppFactory nasazuje jednu aplikaci, proto Express
obsluhuje i statiku. Odpada CORS i druha deploy jednotka.
**SSE misto WebSocketu.** Tok dat je jednosmerny, server ke klientovi. Klient posila
zmeny beznym REST volanim. SSE prochazi reverse proxy bez zvlastni konfigurace.
**Stream pres fetch, ne pres EventSource.** EventSource neumi poslat hlavicku
`Authorization` a token by musel byt v adrese, odkud se dostane do access logu.
Cenou je rucni parsovani a rucni znovupripojeni v `web/src/lib/eventStream.ts`.
**Ceske cesty v URL.** `/sluzby`, `/o-nas`, `/prihlaseni`, `/dashboard/tickety`.
Kod zustava anglicky.
**Data v pameti.** Vedome zjednoduseni prototypu. Uloziste jsou oddelena od rout,
takze napojeni na databazi znamena prepsat soubory v `src/data/`, ne endpointy.
**Zadna ticha selhani.** Kazdy `catch` loguje a uzivatel se o chybe dozvi.
+124
View File
@@ -0,0 +1,124 @@
# 04 - API
Interaktivni dokumentace je na `/apps/<app-id>/docs`. Tenhle soubor popisuje to,
co ze Swaggeru neni videt.
## Endpointy
Verejne:
| Metoda | Cesta | Popis |
| ------ | ------------------- | --------------------------------------- |
| GET | `/health` | health check |
| GET | `/docs` | Swagger UI |
| GET | `/openapi.json` | OpenAPI definice |
| POST | `/api/auth/login` | prihlaseni, vraci JWT |
| POST | `/api/contact` | poptavka z webu |
| POST | `/webhook/:token` | prijem dat do automatizace |
Vyzaduji `Authorization: Bearer <token>`:
| Metoda | Cesta |
| ------ | --------------------------------------------------- |
| GET | `/api/auth/me` |
| POST | `/api/auth/logout` |
| GET | `/api/dashboard/summary` |
| GET | `/api/dashboard/tickets` |
| GET | `/api/dashboard/incidents` |
| GET | `/api/dashboard/connectors` |
| GET | `/api/dashboard/stream` |
| GET | `/api/dashboard/automations` |
| POST | `/api/dashboard/automations` |
| GET | `/api/dashboard/automations/:id` |
| PUT | `/api/dashboard/automations/:id` |
| DELETE | `/api/dashboard/automations/:id` |
| POST | `/api/dashboard/automations/:id/webhook/regenerate` |
| POST | `/api/simulate` |
## Format chyb
Jednotny pro cele API:
```json
{ "error": "validation_error", "message": "Zadejte platny e-mail." }
```
| HTTP | `error` | Kdy |
| ---- | --------------------- | --------------------------------------- |
| 400 | `validation_error` | vstup neprosel schematem |
| 401 | `unauthorized` | chybi nebo neplatny token |
| 401 | `invalid_credentials` | spatny e-mail nebo heslo |
| 403 | `forbidden` | nedostatecna role |
| 404 | `not_found` | zaznam nebo endpoint neexistuje |
| 409 | ruzne | operace nedava v danem stavu smysl |
| 500 | `internal_error` | neodchycena chyba, detail jen mimo produkci |
`message` je vzdy cesky a je urcena k zobrazeni uzivateli.
## Autentizace
Hesla se hashuji bcryptem, plaintext se nikde neuklada. Login vraci JWT
podepsany `JWT_SECRET` s platnosti `JWT_EXPIRES_IN`.
Spatne heslo i neexistujici e-mail vraci stejnou odpoved, aby se neprozradilo,
ktere ucty existuji. Pokus se loguje bez hesla.
Token si drzi klient v `localStorage`. Pro produkci je cilovy stav `httpOnly`
cookie se `Secure` a `SameSite` plus CSRF token.
## Zivy stream
`GET /api/dashboard/stream` je Server-Sent Events. Po pripojeni posle potvrzeni
a poslednich par udalosti, pak uz jen nove. Kazdych 25 sekund jde komentarovy
radek, aby spojeni neuspalo proxy.
Typy udalosti: `ticket.created`, `ticket.updated`, `ticket.resolved`,
`incident.started`, `incident.updated`, `incident.resolved`,
`automation.created`, `automation.updated`, `automation.deleted`,
`automation.run`, `webhook.received`.
Klient se pripojuje pres fetch s hlavickou `Authorization`, ne pres EventSource.
Duvod je v [03-architektura-a-mapa-kodu.md](03-architektura-a-mapa-kodu.md).
## Webhook
Verejny endpoint bez prihlaseni. Autorizuje neuhodnutelny token v adrese,
32 znaku z `randomBytes(24)` v base64url.
Token generuje **vyhradne server**. Hodnota `webhookToken` poslana klientem se
ignoruje, jinak by si sel nastavit predvidatelnou adresu.
| Situace | Odpoved |
| ----------------------------------- | ------- |
| vse v poradku | 202 |
| neznamy token | 404 |
| automatizace je pozastavena | 409 |
| chybi povinny parametr, spatny typ | 400 |
Parametry navic se neodmitaji, jen loguji. Odesilatele bezne posilaji i vlastni
data a odmitat je by rozbijelo integrace.
```bash
curl -X POST https://services.csbot.cz/apps/<app-id>/webhook/<token> \
-H "Content-Type: application/json" \
-d '{"customer":"Nordis","score":18}'
```
Prototyp pozadavek prijme, zvaliduje a zapocita do metrik, ale strom akci
nevykona - runtime neexistuje.
## Simulace
`POST /api/simulate` vyvola provozni udalost pro nahled ziveho dashboardu.
Zamerne meni skutecna data, ne jen posila falesnou notifikaci.
Akce: `ticket.created`, `ticket.resolved`, `incident.started`,
`incident.resolved`, `automation.run`.
Nevyplnena pole server doplni ukazkovou hodnotou. U akci s "resolved" se bez
zadaneho id pouzije prvni nevyrizeny zaznam.
## Pri pridani endpointu
Soucasne aktualizovat `src/openapi.ts` a tenhle soubor. Swagger musi odpovidat
skutecnemu chovani aplikace, jinak je horsi nez zadny.
+119
View File
@@ -0,0 +1,119 @@
# 05 - Dashboard, builder automatizaci a simulace
## Stranky portalu
```
/dashboard prehled: dlazdice, graf za 14 dni, posledni tickety a incidenty
/dashboard/automatizace seznam a zalozeni nove
/dashboard/automatizace/:id builder: strom akci
/dashboard/konektory katalog sluzeb, jejich spousteču a akci
/dashboard/tickety tabulka ticketu
/dashboard/incidenty prehled incidentu
/dashboard/nastaveni udaje o uctu
```
V postrannim menu je pod Nastavenim tlacitko **Simulace**.
## Zivy dashboard
Portal drzi jedno SSE spojeni pro celou aplikaci. Zajistuje ho
`EventStreamProvider` v `web/src/components/dashboard/`.
- Stav spojeni ukazuje `LiveIndicator` v horni liste. Uzivatel musi poznat,
ze data nejsou ziva.
- Prichozi udalosti ukazuje `EventToasts` jako bubliny vpravo dole.
- Data se obnovuji sama. `useApiQuery` ma volitelny `refetchOn` se seznamem typu
udalosti, po kterych se ma dotaz zopakovat. Vice udalosti tesne po sobe se
slouci do jednoho nacteni.
Pri vypadku se stream znovu pripojuje s exponencialne rostoucim odstupem
az do 15 sekund, aby pri vypadku serveru neubijel provoz.
## Simulace provozu
Modalni okno se otevre tlacitkem Simulace. Umoznuje:
- zalozit ticket s vlastnim predmetem, zadavatelem a prioritou,
- vyvolat incident s vlastnim popisem, sluzbou a zavaznosti,
- vyresit prvni nevyrizeny ticket nebo bezici incident,
- spustit automatizaci uspesne nebo s chybou.
Kazda akce opravdu meni data na serveru, takze se projevi i v seznamech
a v souhrnu, ne jen v bublinach.
## Strom akci
Automatizace se sklada z **spoustece** a **kroku**. Krok je bud akce nad
konektorem, nebo podminka se dvema vetvemi - proto je to strom, ne seznam.
```ts
interface AutomationFlow {
trigger: {
connectorId: string;
operationId: string;
fields: TriggerField[]; // vstupni parametry
webhookToken?: string; // generuje vyhradne server
} | null;
steps: FlowStep[];
}
type FlowStep =
| { id: string; kind: 'action'; connectorId: string; operationId: string }
| { id: string; kind: 'condition'; fieldId: string; operator: string;
value?: string; yes: FlowStep[]; no: FlowStep[] };
```
Podminka odkazuje na `field.id`, ne na nazev. Prejmenovani parametru proto
existujici podminky nerozbije.
Misto vlozeni urcuje `FlowPath` v `web/src/lib/flow.ts`: prazdne pole je hlavni
sekvence, `[{ stepId, branch }]` je vetev konkretni podminky.
## Webhook a vstupni parametry
U spoustece typu webhook vygeneruje server pri ulozeni adresu
`POST <verejna-adresa>/webhook/<token>`. U spoustece se deklaruji vstupni
parametry: nazev, typ (text, cislo, ano-ne, datum) a povinnost.
Podminky pak porovnavaji hodnotu parametru, napriklad `score >= 15`. Nabidka
operatoru se ridi typem, na cislo nejde pustit "obsahuje". Tabulka operatoru je
na obou stranach - `src/data/conditions.ts` a `web/src/lib/flow.ts`. Server je
autorita, kopie na klientovi existuje jen proto, aby UI nenabidlo nesmysl.
**Pri zmene upravit obe.**
Dokud spoustec nema zadny parametr, nejde pridat podminka - nebylo by podle ceho
se rozhodovat. Dialog to vysvetli.
## Validace
Rozlisuji se dve veci:
**Chyby** vraci 400 a neulozi se: neexistujici konektor nebo operace, operace
spatneho druhu, podminka na neexistujici parametr, operator nesedici na typ,
duplicitni nebo nevalidni nazev parametru.
**Nedodelky** se ulozi, jen brani zapnuti: chybi spoustec, zadny krok, webhook
bez adresy, podminka bez hodnoty. Vraci se v poli `issues` a builder je vypise.
Rozdelana prace se nikdy nezahazuje.
## Pridani konektoru
1. Pridat zaznam do `connectors` v `src/data/connectors.ts` vcetne `triggers`
a `actions`.
2. Pokud pouziva novou ikonu, doplnit klic do `web/src/lib/connectorIcons.ts`.
Musi existovat v `lucide-react`.
3. Pokud patri do nove kategorie, doplnit ji do `connectorCategories` a do typu
`ConnectorCategory` na obou stranach.
Builder i katalog ji vezmou automaticky.
## Co chybi
| Chybi | Poznamka |
| --------------------------- | -------------------------------------------------------- |
| Nastaveni poli akci | `fields` u akci se zobrazuji jen jako napoveda |
| Predavani dat do akci | chybi syntaxe odkazu, navrh je `{{trigger.customer}}` |
| Kombinovane podminky | jedna podminka je jedno porovnani, AND a OR jen vnorenim |
| Beh automatizaci | ulozeny strom se nevykonava |
| Historie behu a logy | prazdne, chybi runtime |
| Drag and drop | presouvani je zatim tlacitky nahoru a dolu |
+59
View File
@@ -0,0 +1,59 @@
# 99 - Zaznam zmen
Nejnovejsi nahore.
## 2026-07-31
Prvni nasazeni aplikace do repozitare csbot-prototype.
Puvodni sablona byla holy Express s endpointy `/` a `/health`. Nahradil ji
kompletni web a klientsky portal.
### Pridano
- Verejny web: homepage se sekcemi, sluzby, o nas, kontakt s formularem, 404.
- Prihlaseni pres JWT s demo ucty.
- Klientsky portal: prehled s grafem, tickety, incidenty, automatizace,
konektory, nastaveni.
- Builder automatizaci: strom akci, spoustec, vetveni podminkou.
- Katalog 25 konektoru v 8 kategoriich.
- Webhook s registrovanou adresou, token generuje server.
- Zivy dashboard pres SSE, vcetne indikatoru spojeni a bublin s udalostmi.
- Simulace provozu pod tlacitkem v postrannim menu portalu.
- Swagger UI na `/docs` a OpenAPI definice na `/openapi.json`.
- Dokumentace ve slozce `documentation/`.
- `.gitignore`, ktery drzi `node_modules` a `dist` mimo repozitar.
### Zmeneno oproti sablone
- Aplikace prepnuta na ESM (`"type": "module"`) a `module: NodeNext`.
- Jeden container obsluhuje API i zbuildovanou React aplikaci z `dist/public`.
- Dockerfile buildu je server i web, vysledny image dostava jen `dist`.
- Port zustava 3000, naslouchani na `0.0.0.0` beze zmeny.
### Reseni reverse proxy
- `ROOT_PATH` se cte z prostredi, nikde neni hardcoded.
- Aplikace se mountuje na koren i na prefix, funguje tedy at Caddy prefix
odstrani nebo ne.
- Server vklada do `index.html` znacku `<base>` a `window.__BASE_PATH__`,
aby SPA nasla soubory i na vnorenych cestach.
- OpenAPI `servers` obsahuje prefix, takze Swagger Try it out vola spravnou adresu.
- Router ma `strict: true`, jinak by se presmerovani `/docs` na `/docs/` zacyklilo.
### Overeno lokalne
S `ROOT_PATH=/apps/csbot-prototype`:
- `/apps/csbot-prototype/health` i `/health` vraci 200,
- `/apps/csbot-prototype/docs` presmeruje na `/docs/`, ta vraci Swagger UI,
- `swagger-ui.css` se nacte pres prefix,
- OpenAPI `servers` obsahuje `/apps/csbot-prototype`,
- `index.html` na vnorene ceste obsahuje spravny `<base>`,
- prihlaseni pres prefix vraci token,
- neexistujici cesta pod `/api` vraci JSON, ne HTML aplikace.
### Znama omezeni
Data jsou v pameti, restart je vrati do vychoziho stavu. Obsah webu je ukazkovy.
Ulozeny strom automatizace se nevykonava.