Krok padał na runnerze bez żadnego komunikatu. Powód był podwójny:
- asercja zakładała, że bez backendu nginx odpowie 502, a to zależy od
tego, jak szybko zawiedzie DNS. Na runnerze zapytanie wisiało 30 sekund,
aż do domyślnego resolver_timeout.
- grep bez dopasowania kończy się kodem 1, a Gitea uruchamia kroki w
bash -e -o pipefail, więc podstawienie zmiennej przerywało cały krok,
zanim zdążył wypisać powód. Lokalnie tego nie było widać, bo zsh nie
ustawia tych flag.
Zamiast opierać się na tym, że proxy zawiedzie, job stawia teraz atrapę
backendu pod nazwą discord-bot - dokładnie tą, którą wskazuje nginx.conf
i compose.yaml - i sprawdza, że przez proxy wraca treść z atrapy. Test
przechodzi więc wtedy, gdy przekazywanie faktycznie działa, a nie wtedy,
gdy zawodzi w oczekiwany sposób. Sprawdzane jest też /ws.
Pozostałe usztywnienia: limit czasu na każde zapytanie, || true tam, gdzie
brak dopasowania jest poprawnym wynikiem, jawne komunikaty błędów z logiem
kontenera oraz sprzątanie kontenerów i sieci przez trap.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Workflow .gitea/workflows/frontend.yaml z dwoma jobami:
- verify npm ci, ESLint, kontrola typów, build produkcyjny i test dymny
- image walidacja compose.yaml, build obrazu panelu i sprawdzenie
działającego kontenera; job czeka na verify
Test dymny działa na zbudowanym artefakcie, a nie na źródłach, więc
sprawdza dokładnie to, co trafia do obrazu.
Job image weryfikuje trzy rzeczy w uruchomionym kontenerze: czy nginx
oddaje panel, czy nieznana ścieżka wraca jako index.html (fallback SPA)
oraz czy /api jest przekazywane do backendu. Backendu w tym kroku nie ma,
więc oczekiwaną odpowiedzią jest błąd bramy - 502 dowodzi, że reguła proxy
istnieje i celuje w usługę discord-bot, podczas gdy 404 oznaczałoby, że
nginx potraktował /api jak ścieżkę aplikacji.
Filtry paths ograniczają uruchomienia do zmian w katalogu frontend,
compose.yaml i samym workflow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nginx nasłuchuje wyłącznie na IPv4, a w obrazie alpine localhost rozwiązuje
się najpierw na ::1. Healthcheck kończył się więc odmową połączenia i
kontener nigdy nie osiągał stanu healthy - wyszło to przy pisaniu CI,
gdzie ten sam sposób odpytania nie działał.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Konfiguracja we flat configu: zalecane zestawy js, typescript-eslint oraz
eslint-plugin-vue. Bloki <script setup lang="ts"> obsługuje vue-eslint-parser
przekazujący zawartość do parsera TypeScriptu.
Odstępstwa od domyślnych reguł:
- vue/multi-word-component-names wyłączone - kafelki panelu mają nazwy
własne i nie każda jest dwuczłonowa
- no-unused-vars przepuszcza nazwy z prefiksem podkreślenia
- no-console wyłączone dla testu dymnego, który raportuje wynik na stdout
Szablony przepuszczone przez eslint --fix, co ujednoliciło łamanie
atrybutów i domykanie znaczników. Po zmianach kontrola typów, build
i test dymny nadal przechodzą.
Dochodzą skrypty npm: lint (z --max-warnings 0) oraz lint:fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
README opisuje pracę lokalną (npm run dev z proxy na backend), budowanie,
uruchomienie kontenera oraz strukturę katalogu src z rolą każdego pliku.
Spisane też zasady, które utrzymują panel spójnym: stan płynie z serwera,
zero innerHTML, ikony jako komponenty zamiast skryptu z CDN.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ładuje artefakt z dist/ w jsdom, podstawiając atrapy WebSocketu i fetcha,
a następnie sprawdza dwie rzeczy naraz:
- czy dane z serwera faktycznie renderują się w interfejsie (aktualny utwór,
kolejka, historia, baza plików, głośność, tryb zapętlania, odmiana
liczebników, formatowanie czasu)
- czy kliknięcia trafiają we właściwe endpointy - każdy przycisk sterujący
porównywany jest z oczekiwaną parą metoda + ścieżka
Razem 31 sprawdzeń, uruchamiane przez npm run smoke po npm run build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rozdzielenie na dwie niezależne usługi:
- frontend/Dockerfile buduje panel w etapie node:22-alpine, a artefakt
serwuje nginx:1.27-alpine. Obraz nie zawiera Node'a ani źródeł.
- frontend/nginx.conf serwuje aplikację jednostronicową (nieznane ścieżki
wracają jako index.html), cache'uje /assets z sumą kontrolną w nazwie,
a /api i /ws przekazuje do usługi discord-bot. Przeglądarka widzi jedno
źródło, więc backend nie potrzebuje CORS-u.
Adres backendu siedzi w zmiennej nginxa, więc nazwa jest rozwiązywana
przy żądaniu i panel wstaje niezależnie od kolejności startu kontenerów.
- Proxy nie limituje rozmiaru ciała żądania i nie buforuje go, żeby
wysyłka dużych plików wideo/audio przechodziła bez zmian w backendzie.
Timeouty podniesione, bo panel trzyma WebSocket otwarty na stałe.
- Root Dockerfile wraca do samego Pythona i wystawia port 8000.
- compose.yaml uruchamia obie usługi we wspólnej sieci: panel na :80,
API na :8000 (wystawione do diagnostyki).
- Katalog static/ znika z repozytorium - nie ma już czego serwować.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Panel działa w osobnym kontenerze, więc serwis Pythona przestaje serwować
pliki statyczne:
- usunięty mount StaticFiles na "/" wraz z importem
- tytuł aplikacji zmieniony na "ŻyrarBOT API"
- GET / zwraca opis usługi zamiast panelu, żeby wejście pod adres backendu
nie kończyło się nic niemówiącym 404
- GET /api/health informuje, czy bot zdążył połączyć się z Discordem;
przydaje się jako healthcheck kontenera i przy diagnozie proxy
Pozostałe endpointy oraz WebSocket pozostają bez zmian.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
static/app.js (1196 linii imperatywnego JS operującego na getElementById)
oraz static/index.html zastąpione w całości aplikacją z frontend/.
Arkusz stylów został wcześniej przeniesiony, nie usuwamy go ponownie.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Widok rozbity na komponenty odpowiadające kafelkom pulpitu:
- AppHeader, VoiceChannelPanel, PlayerPanel, AddTrackPanel
- UploadsPanel wraz z UploadedFileRow współdzielonym z oknem modalnym
- QueuePanel i QueueItem - przeciąganie, zmiana kolejności, limit odtworzeń
- HistoryPanel, ModalShell, SettingsModal, UploadsLibraryModal, ToastHost
Arkusz stylów przeniesiony ze static/style.css bez zmian w wyglądzie.
Style, które app.js wstrzykiwał inline przy budowaniu HTML-a, przeniesione
do klas współdzielonych i bloków scoped w komponentach.
Poprawki bezpieczeństwa wynikające z przepisania:
- Tytuły utworów i nazwy plików nie trafiają już do innerHTML. Poprzednia
wersja składała wiersze kolejki, historii i listy plików ze stringów,
więc tytuł zawierający znaczniki HTML był wstawiany do DOM bez escapowania.
- Nazwy plików nie są już wklejane do atrybutów onclick z ręcznym
escapowaniem apostrofu, które i tak nie obejmowało cudzysłowu. Zdarzenia
podpina Vue, bez generowania kodu w stringach.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Logika, która w app.js żyła w zmiennych globalnych, trafia do czterech
composables o wyraźnym zakresie odpowiedzialności:
- useBotStatus jedno połączenie WebSocket na aplikację, z licznikiem
subskrybentów i ponawianiem po zerwaniu. Flaga paused
wstrzymuje aktualizacje na czas przeciągania utworów.
- useToasts kolejka powiadomień wraz z helperem run(), który
opakowuje wywołanie API w parę komunikatów sukces/błąd.
- useTrackProgress płynny pasek postępu - między pakietami z serwera czas
dopisujemy lokalnie, a każdy pakiet resynchronizuje licznik.
- useUploads serwerowa baza plików, współdzielona przez kafelek na
pulpicie i okno z pełną listą.
Dochodzi też utils/plural.ts z polską odmianą liczebników, zamiast
powtarzanego w kilku miejscach warunku count < 5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- types/status.ts spisuje kształt odpowiedzi /api/status oraz pakietów
rozgłaszanych po WebSockecie. Dotąd struktura tych danych istniała
wyłącznie w głowie autora i w rozsianych po kodzie odwołaniach.
- api/client.ts zbiera wszystkie adresy endpointów w jednym miejscu i
ujednolica obsługę błędów: pole "detail" z odpowiedzi FastAPI trafia do
wyjątku ApiError, więc warstwa widoku dostaje gotowy komunikat.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Panel przestaje być zestawem plików statycznych serwowanych przez backend
i staje się osobnym projektem w katalogu frontend/.
- Vue 3 z Composition API jako framework - szablony komponentów są bliskie
dotychczasowemu HTML-owi, więc migracja nie wymaga przepisywania widoków
na inny model mentalny.
- TypeScript w trybie strict; kontrakt danych z API będzie opisany typami.
- Vite jako bundler, z aliasem @ na src/ i budowaniem do dist/.
- Serwer deweloperski przekierowuje /api i /ws na backend; adres można
nadpisać zmienną VITE_BACKEND_URL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>