Web interface (Angular)¶
The Angular 22 application (signals, zoneless, Bootstrap 5) is in web/. Its build is written
into src/security_review_mcp/webui/, from where the controller serves it on /ui/ and the
wheel embeds it.
cd web
npm ci
npm run build # production build (to redo before uv build)
npm start # dev server on http://localhost:4200/ui/
npm run format # Prettier
npm start forwards /api to http://127.0.0.1:8080 (web/proxy.conf.json): start the
controller in parallel with security-review-controller serve --port 8080, or use the VS Code
configuration Web UI + API (Angular dev server, port 4200), which starts both
(Controller + web UI (Angular, port 4200) if you also want to debug the Python): every save
of a file of web/src/ reloads the page (styles and templates without a full reload), and the
breakpoints set in the .ts files are honored. In the devcontainer, Chrome is opened on the
local workstation and port 4200 is forwarded automatically.
Layout of web/src/app/¶
| Folder | Content |
|---|---|
core/ |
Services (API calls, session, triage), guards, interceptors, models of the API (models/api.models.ts), translations (i18n/) |
features/ |
One folder per screen (reports, rules, tokens, access…), loaded by app.routes.ts |
shared/ |
Reusable components and utilities without business logic |
layout/ |
Shell of the application (menu, header) |
Code conventions¶
- Components: template and styles in separate files. A feature with several screens has one
folder per component (
features/access/role-list/); its shared files (styles, base class, error messages) stay at the root of the feature. - Forms: the input forms use the Signal Forms (
form(),[formField], validators with a translated message); the list filters and the dynamic lists of checkboxes stay plain event handlers. - HTTP calls: through
rxResourcefor the data of a screen, orawait firstValueFrom(...)for an action; no.subscribe()in the components. - State:
signal()andcomputed(); a value derived from another one but still editable (a form filled with the loaded settings) is alinkedSignal().effect()is kept for real side effects (document attributes, local storage, timers), never to copy a signal into another. A service exposes its signals withasReadonly()and changes them through named methods. - Folders:
shared/holds reusable components without business logic: they get their data through inputs and report through outputs, never through a business service ofcore/(the finding card emits the decision,TriageServicesaves it). The shell of the application islayout/. - Templates: host bindings in
host: {}(no@HostListener); a message that explains why a field is invalid is linked to it witharia-describedby. - Tables:
app-table-controlsholds the search and the filters; the sort is chosen on the column headers (<th [appSortHeader]="sort" sortKey="name">), with aTableSortnamed after the table (shared/utils/table-sort.ts) that the browser remembers undersecreview.sort.<name>. - Colors: Bootstrap variables (
var(--bs-…)) or the design tokens ofstyles.css(--app-…), never a color written in a component, so that the light and dark themes work.
Interface translations¶
The interface is translated into English, French and Spanish by an in-house service
(web/src/app/core/i18n/), without any extra dependency. Each screen has its catalog in
core/i18n/messages/<screen>.ts, declared in messages/index.ts, with its messages in the three
languages:
export const tokens = defineMessages({
en: { 'tokens.created': 'Token “{label}” created.' },
fr: { 'tokens.created': 'Jeton « {label} » créé.' },
es: { 'tokens.created': 'Token «{label}» creado.' },
});
- In a template:
{{ 'tokens.title' | t }}, or with parameters{{ 'tokens.created' | t: { label: name } }}. - In the code:
I18nService.translate(...)(confirmations, messages of the Signal Forms validators throughmessage: () => ...). A message shown later (success, error) is kept asmsg(key, parameters)and translated in the template, so that it follows a change of language. - The title of the routes is a key, translated by
I18nTitleStrategy.
The keys are typed: an unknown key, or a key missing in one of the three languages, fails
npm run build.
Checks¶
npm --prefix web run build # also checks the i18n keys
npm --prefix web exec --no -- prettier --check <changed files>
A change of the audit catalog (core/i18n/messages/audit.ts) is also checked by
uv run pytest tests/test_audit.py: every kind of audit event needs its label in the three
languages. The REST routes called by the interface are described in
Controller (FastAPI).