The admin.ldap message block referenced throughout the LDAP admin
page (t('title'), t('connectionTitle'), t('serverUrl'), field-mapping
and sync labels, etc.) didn't exist in de.json/en.json at all, so the
whole page rendered raw translation keys instead of text -- a
pre-existing gap surfaced while testing the new AD-prefill/group-filter
feature on this same page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New-config form now defaults to the CTL Active Directory connection
values (balios.ctl.local:3268, dc=ctl,dc=local, AD person filter,
down-level bind-DN hint) with the password left blank; editing an
existing config still shows its real saved values.
Adds a group/OU import filter section: discover AD groups/OUs via
GET /ldap/groups, toggle selection or add DNs manually, persist via
PATCH /ldap/config. Empty selection keeps today's "import everyone
under base DN" behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
applyAccentColor() forced --sidebar-accent-foreground to the raw accent
color regardless of theme. Works in dark mode (bright text on dark-tinted
bg) but in light mode the tinted bg is near-white, so full-saturation
yellow text on pale-yellow bg was nearly invisible. Now only overrides
the foreground in dark mode; light mode keeps the theme's default
high-contrast foreground token.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Note widget toggle button rendered raw key "widgets.note.editMode"
instead of translated label — keys were never added to either locale.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- dashboard-grid.test: add noCompactor to react-grid-layout mock
- note-widget.test: enable edit mode before typing (onChange is undefined
when isEditing=false), replace native dispatchEvent with fireEvent.change
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cert-manager was seeded and activated in the DB but missing from the
frontend whitelist, causing the dynamic route to always show "module
not found".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- actions.ts: mergeCertsAction(files, outputFormat, password?) builds FormData with
multiple file fields; delegates to postForm('merge', ...) (T-09-02/T-09-04)
- MergeTab.tsx: multi-file state (local), file input (multiple), output selector
(pem|pfx), Zusammenfuehren button disabled when < 2 files (data-testid for tests),
onOutputFormatChange callback to page.tsx for shared PasswordField visibility
- ConvertTab.tsx: gains pfx option + onTargetFormatChange callback (same pattern)
- page.tsx: lifts mergeOutputFormat + convertOutputFormat state; showPassword now
also true when active tab's output format is 'pfx'; passes callbacks to tabs
- cert-manager.test.tsx: 5 new tests — MergeTab disabled/enabled by file count,
shared PasswordField appears on pfx output, downloadBase64 called on success;
ConvertTab pfx option present; all 19/19 web tests green
- All production cert-manager files type-clean (pre-existing test type issues unchanged)
- Add FileResponse interface to actions.ts
- Add convertCertAction(input, targetFormat): builds FormData with
file/pemText/password + targetFormat, calls postForm convert endpoint
- Implement ConvertTab: native select for pem/der/p7b targetFormat,
Konvertieren button with loading swap, error classification, empty state
- On success: calls downloadBase64(filename, content, mimeType)
- 3 new ConvertTab tests: format selector options, downloadBase64 invoked
on success, text-destructive error on format rejection
- All 14 web cert-manager tests green
- Added certManager namespace to de.json with full key set (tabs, dropZone, paste, password, or, actions, emptyState, error)
- Added certManager namespace to en.json with matching key structure
- German copy matches UI-SPEC Copywriting Contract exactly
- Both files share identical key paths under certManager
- SSRF check skipped for Exchange type (internal EWS servers are common)
- testConnectionFromConfig catches SSRF/validation errors, returns {success:false,error} instead of throwing 403
- updateSource reads existing.type to determine effective type for SSRF check
- Panel shows saveError/editSaveError on failed add/update
- Edit form initialValues now includes domain field
- i18n: calendar.saveError key added (de+en)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
header.tsx: accentColor was missing from setUser call on mount —
applyAccentColor(undefined) fired on every reload, removing --primary.
auth-store: also set --sidebar-accent (15% opacity) and
--sidebar-accent-foreground so active sidebar items match accent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Prisma: domain String? added to CalendarSource model (db push applied)
- DTOs: domain in CreateCalendarSourceDto, UpdateCalendarSourceDto, new TestCalendarSourceConfigDto
- Service: domain in SOURCE_SAFE_SELECT, addSource, updateSource; new testConnectionFromConfig method
- Controller: POST /calendar/sources/test-config (before :id routes to avoid collision)
- ExchangeProvider: domain in all source interfaces; passed as 3rd arg to EWS WebCredentials
- Frontend: domain in CalendarSource/CreateSourcePayload/UpdateSourcePayload; testSourceConfig API fn
- Form: domain field (Exchange-only), "Test connection" button with idle/loading/success/error states
- i18n: de+en keys for formFieldDomain, formFieldDomainHint, formTestConnection, formTesting, formTestSuccess, formTestFailed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
validateUrl() calls setUrlError() — calling it during render triggers
React error #301 (cannot update component while rendering). Remove it
from the isFormValid computation; onChange/onBlur already keep urlError
in sync so !urlError is sufficient.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add marketplace.accessDenied to de.json and en.json
- Add 12 calendar form field/action keys to widgets.calendar in both locales
- Replace all hardcoded English strings in calendar-source-form.tsx with t() calls
- Remove locale-detection hack on Cancel button (was comparing t() result to English string)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CR-01: fix SSRF bypass — isPrivateIpv6 now delegates ::ffff:<ipv4> to
isPrivateIpv4, covering 172.16-31.x and 169.254.x ranges
- CR-02: add ParseUUIDPipe to GET /favorites widgetId param + service guard
so missing widgetId returns 400 instead of leaking all user favorites
- WR-01: link-widget — replace raw 'link.error' key with t('link.error') (4 sites)
- WR-02: favorites-widget — fix load-path error to use t('favorites.error')
- WR-03: widget-catalog-modal — move aria-hidden from outer wrapper to backdrop
- WR-04: calculator — remove duplicate M button (MR clone); MC/MR/M+/M−/MS remain
- WR-05: schema — add FavoriteLink→WidgetInstance FK with onDelete:Cascade
- IN-01: create-widget.dto.ts — update comment from four to eight supported types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fetchFavorites called with instanceId on mount
- Link renders as anchor with target="_blank" rel="noreferrer" (T-08-12)
- Empty + edit mode shows add form; createFavorite called on submit
- Single-link enforcement: add form hidden when link exists (D-06)
- Edit mode: updateFavorite called with id and new title
- View toggle: list default, tile container on gridView click
- Letter fallback: iconUrl null shows first uppercase letter
- favorites-api.ts: FavoriteLink type + fetchFavorites/createFavorite/updateFavorite/deleteFavorite
all use credentials: include and API_URL/favorites
- favorites-widget.tsx: list/grid view, inline add/edit/delete in edit mode,
icon + letter fallback, rel=noreferrer + target=_blank, no dangerouslySetInnerHTML (T-08-07)
- useEffect deps fixed to [instanceId] only — excludes t() to prevent re-fetch on each render
- page.tsx: wireFavoritesWidget(FavoritesWidget) wired
- Full test suite: 81/81 pass (17 test files)
- Web TypeScript: clean
- fetchFavorites called with instanceId (widgetId scope, Pitfall 3)
- covers add/edit/delete, empty state, list/grid toggle, letter fallback
- tests fail: favorites-widget.tsx and favorites-api.ts do not exist yet
- stopwatch-widget.tsx: start/stop/reset/lap controls, setInterval tick (100ms)
- Reload reconstruction: Date.now() - startedAt + elapsed (Pitfall 2 fix)
- State persisted via updateWidgetConfig(instanceId, {...}) on each action
- Single interval cleared on unmount and when not running (T-08-04 mitigated)
- Lap times stored newest-first per RESEARCH recommendation
- No CSS modules — Tailwind only (grep -c module.css = 0)
- page.tsx: added wireStopwatchWidget(StopwatchWidget) import + call
- All 7 stopwatch tests pass (GREEN)
- Tests for start/stop/reset/lap controls
- Reload reconstruction test verifies elapsed from startedAt + stored elapsed
- Tests fail because stopwatch-widget.tsx does not yet exist (expected RED state)
- Password form errors (wrong pw, mismatch) now clear on first keystroke
in any password field instead of persisting until next submit.
- Avatar upload now bumps avatarVersion in auth store and sets hasAvatar=true,
so the header avatar switches to the uploaded image immediately without reload.
- Header img src uses ?v={avatarVersion} as cache-buster to force browser to
fetch the new avatar when version increments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SMTP configuration is an admin concern, not a per-user setting. Removed
it from the settings sidebar and relocated to /admin/smtp with a link in
the admin sidebar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AuthUser store: add optional hasAvatar? field
- Header: populate hasAvatar from fetchCurrentUser() result
- Header avatar button: shows <img src='/api-proxy/users/me/avatar'> when hasAvatar=true with onError fallback to initial span
- Plain <img> tag used (same-origin /api-proxy rewrite, no next/image remote config needed)
- Fix orphaned DkvModuleConfig: tenantId pointed to deleted tenant, updated to Default tenant
- DKV controller: return 404 instead of HTTP 200 null when no config exists
- IMAP provider: also detect PDFs sent as application/octet-stream (check filename extension)
- IMAP provider: add seen:false filter so already-processed emails are skipped on re-poll
- IMAP provider: mark email as \Seen after successful PDF download to prevent reprocessing
- Frontend dkv-api: handle 404 from fetchConfig as "not yet configured" (returns null)
- InboxConfigForm: show warning banner when config not yet saved in DB
- InboxConfigForm: add "Jetzt prüfen" button to manually trigger POST /dkv/check-now
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
redirect() throws NEXT_REDIRECT internally — inside catch it was swallowed
and returned networkError. Extract cookie data in try/catch, then set
cookie and redirect() after the block so the throw propagates correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
client-side router.push races with Set-Cookie processing. redirect() in the
server action sends cookie + redirect in one response — browser applies the
new JWT before navigating, so middleware sees mustChangePassword=false.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After changePassword the API issues a new JWT with mustChangePassword=false.
The server action now reads Set-Cookie from the API response and sets it
in the browser so the middleware sees the updated flag and allows /dashboard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Client-side fetch to NEXT_PUBLIC_API_URL was unreachable in production.
Replace with a server action that uses API_INTERNAL_URL (http://api:3001)
server-to-server — no browser connectivity required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NEXT_PUBLIC_API_URL was undefined at build time, causing client bundles to
fall back to http://localhost:3001 — unreachable from the browser in prod.
- Add /api-proxy rewrite in next.config.ts (forwards to API_INTERNAL_URL at runtime)
- Bake NEXT_PUBLIC_API_URL=/api-proxy at build time in Dockerfile
- Fix api.ts to prefer API_INTERNAL_URL for server-side calls
- Fix docker-compose.prod.yml: set NEXT_PUBLIC_API_URL=http://api:3001 for runtime server-side code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sidebar.test: expand category before asserting on module names
(categories are collapsed by default since UI-Umbau)
- ci.yml: replace build-deploy with publish job that pushes images
to git.vicolab.de container registry
- docker-compose.prod.yml: pull-only compose for server deployments
using registry images
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CSV import dialog shows format line + example before mode selection
- Exchange connection test: on 401, inline hint lists common causes
(wrong credentials, domain format, username prefix, O365 not supported)
- Both de/en translations updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- testConnection now returns { success, message? } instead of boolean so
admins see the actual EWS/IMAP error in the UI rather than "Unbekannter Fehler"
- Exchange provider: resolveFolder() maps folder string to WellKnownFolderName
(Inbox, SentItems, DeletedItems, Drafts, JunkEmail + German aliases)
- InboxConfigForm: folder field now shown for both IMAP and Exchange protocols
with Exchange-specific help text listing valid well-known names
- Controller returns testConnection result directly (no more redundant wrapping)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>