The sidebar nav section had no overflow containment, so scroll events
propagated to the page body causing the whole layout to scroll up
into blank space. Add overflow-y-auto for internal scrolling and
overscroll-contain to prevent propagation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The single Settings tab was too long. Split into coherent groups:
- Announcements: alerts, bulletin board, MQTT diagnostics
- Billing: rate, billing day/notes, billing password, cost centers
- Notifications: SMTP status, email alert configuration
- Content: support resources, applications, downtime apps, agencies
- System: thresholds, work hours, downtime reminders, lifecycle
rules (verification, unsponsored deletion, audit, auto-archive),
security (session timeout, upload limit, PKCS12)
Settings form state is lifted into a shared useSettingsForm hook so
edits on any tab are saved together. Save button only appears when
there are unsaved changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The countdown used floor division (timedelta.days / differenceInDays)
which truncates partial days. A user unsponsored at 2pm with a 7-day
window would immediately show 6 days remaining instead of 7. Switch
to ceiling division (math.ceil / Math.ceil) so any remaining time in
a day counts as a full day.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows an info icon with tooltip explaining billing rules: users with
permissions longer than a day must be paid for, and the last project
to sponsor a user before the billing date pays. Only appears when the
billing count differs from the sponsored count.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The billing count line in the At a Glance card is redundant when it
equals the sponsored number. Only show it when they differ.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds two stat cards above the list cards on the user dashboard showing
project count and teammate count. Counts are derived from the
already-scoped useProjects and useMyTeammates hooks (row-level
filtered by KC permissions), not the admin stats endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a channel field (alert/bulletin) to announcements so the system
supports two distinct surfaces: short alert messages in the top bar
and longer-form bulletin posts on the user dashboard.
- Model: channel column (alert/bulletin, default alert) with migration
- Routes: channel filter on list/delete-all, channel field on create
- MQTT: publishes to sub-topics (mgmt/announcements/alert, .../bulletin)
- Frontend: useLiveAlerts (top bar), useLiveBulletins (dashboard),
channel-aware MQTT subscriptions
- Admin: separate Alerts and Bulletin Board management sections
- Dashboard: AnnouncementsSection wired up with live bulletin feed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the static banner_messages setting with a full announcements
system backed by an MQTT broker (Mosquitto). Announcements are persisted
to a dedicated DB table for history/audit and pushed in real time via
MQTT-over-WebSocket to all connected browser clients.
- Mosquitto container in dev.sh and docker-compose.mqtt.yml overlay
- Backend: Announcement model, paho-mqtt client singleton, CRUD API
with dual-write (DB + MQTT publish), mqtt-status health check,
mqtt-config endpoint for frontend broker discovery
- Frontend: mqtt.js WebSocket client, useLiveAnnouncements hook that
hydrates from REST then merges real-time MQTT events
- Admin UI: AnnouncementsSection with create/delete + expiry field,
MqttDiagnosticsSection mirroring the Keycloak connectivity pipeline
- Helm chart: mqtt values, configmap, and secret entries for prod
- Data migration from banner_messages to announcements table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Dashboard toggle: VELA users see tabs (My Dashboard / Admin View),
non-VELA users see only the personal dashboard
- My Projects card: scrollable list of user's projects with linked keys
- My Teammates card: deduplicated teammates across shared projects with
email copy buttons and linked project keys/names
- Applications section: configurable app cards with icons, descriptions,
and live status pills from proxied health check URLs
- Admin settings: Applications manager with image upload, drag-to-reorder,
and audit logging
- Backend: GET /api/dashboard/my-teammates, GET/PUT /api/settings/applications,
GET /api/dashboard/app-status with SSRF protection and input validation
- Seed data for Artifactory, Bitbucket, OpenShift, SRM, Black Duck,
Coverity, and SwaggerHub
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Backend: add billing_notes setting with 5000 char limit
- Admin page: add textarea field under Project & User Defaults
- Project detail: display billing notes instead of placeholder text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add billing_day_of_month setting (default: 1st) with admin UI field
- Add GET /api/projects/<key>/billing-count endpoint that computes
currently sponsored + unsponsored users this project is liable for
- Display "Paying for X this month" below At a Glance cards on project detail
- Include sponsorship audit entries (sponsored/released) in user history query
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Project detail: show cost center as "code - name" matching billing page format
- Projects list: add red "Unverified" pill that filters to overdue projects
- Users list: add red "Unsponsored" and amber "Delete Countdown" pills
- User detail: make project key column a clickable link to project detail
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Connector lines between steps were green for skipped checks, making it
look like the chain succeeded even with no connection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The global axios instance defaults to application/json, which prevents
FormData from being sent correctly. Other file uploads already set the
header explicitly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New CostCenter model with CSV upload and manual add support
- Pipe-delimited CSV parsing extracts Cost Center and Code Name columns
- Upload preview shows which projects will lose their cost center
- Manual entries preserved across CSV re-uploads
- Fuzzy search combobox replaces text inputs on project form and billing page
- Cost center manager added to admin settings under Billing section
- Billing page: filter out archived projects, gate funding doc behind password
- Widen cost_center column from VARCHAR(10) to VARCHAR(50)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend now returns an empty billing array when password is not verified,
preventing data exposure via developer tools. Frontend no longer renders
the table at all when locked — only the password card is shown.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Exports a CSV of all licenses with name, vendor, part number, cost,
purchase date, and expiration date. Generated client-side from the
already-fetched license data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New GET /api/projects/<key>/members/export endpoint returns a CSV with
name, email, sponsor_status, sponsor_project, last_login_at,
unsponsored_since, and days_until_deletion. Export button added to the
Users Associated with Project table toolbar on the project detail page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Billing page now requires a shared password (separate from KC auth) to view any
data. Financial columns (Total, Spent, Available) populated from pipe-delimited
CSV uploads, parsed and stored encrypted (Fernet) in a new billing_data table.
Unmatched funding docs stored for auto-populate when projects get assigned matching
numbers. Admins set/reset the billing password from the Settings page with audit
logging and a gov-only access warning.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Funding document is now a text field (funding_doc_number) on the Project model
instead of a separate file-upload model. Both cost_center and funding_doc_number
are inline-editable on the billing page via PATCH /api/billing/<id>. Removed
FundingDocument model and all file upload/download/delete endpoints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New billing domain: backend blueprint with Swagger-documented CRUD endpoints for
funding documents (one per project), FundingDocument model, billing_required
decorator checking VELA-mgmt-billing group membership. Frontend billing page with
DataTable (key, name, cost center, funding document upload/download/delete),
BillingRoute guard, hasBillingAccess in auth context. KC seed creates
VELA-mgmt-billing child group and adds Bill Billings (bill@bills.com) as a
billing-only test user.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Link to user detail page as an alternative to releasing sponsorship,
so admins can transfer sponsorship to another project instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add vela_or_admin_required decorator that gates access to hardcoded
admin or users with any VELA group membership. Applied to all cert,
license, and downtime API routes. Frontend sidebar hides these sections
and routes redirect non-VELA users to the dashboard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove hardcoded .limit(3) on expiring licenses/certs query so all
expiring items are returned
- Increase scroll container to max-h-[220px] to show ~3 items with
partial 4th visible as scroll hint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add PKCS#7 (P7B/P7C) parsing support for cert import
- Allow adding/updating private keys on existing certs via PUT endpoint
- Add drag-and-drop file reading on private key textarea in cert form
- Truncate long issuer names in certs table column (max 200px)
- Fix certs table not refreshing after create/import/update/delete
- Add .p7b/.p7c to import dialog accepted file extensions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
User detail page shows expandable accordion rows with permission
pills (e.g. mgmt-write, bitbucket-read) per project, scoped to
viewer access. "Manage" link directs to project groups tab.
Group member add/remove routes now write UserPermission rows
immediately (DB-first pattern), matching the rest of the codebase.
KC sync does full delete+rebuild per project to eliminate orphaned
and duplicate rows. Startup dedup migration cleans existing data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
License form shows seat/device count when type is Per Seat or Per
Device. Release sponsorship button now shows a confirmation dialog
warning that the user will be disabled in Keycloak and their account
deleted after the configurable unsponsored deletion period.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Daily email reminders for unresolved downtime entries with configurable
time and max days via admin settings (background worker thread)
- Downtime form: enclave, scope, and planned/unplanned now required
- Audit log resolves entity names from details when entities are deleted
instead of showing "Deleted (ID: X)"
- Projects list: agency and service columns with faceted filters
- All data tables: "All" option in rows-per-page dropdown
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Count badges next to Users, Licenses, and Certificates titles now
reflect the filtered row count from TanStack Table, updating live
with search and faceted filters. Added onFilteredCountChange callback
to the shared DataTable component.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Users list gets a disable/enable button per row (admin-only).
Licenses and certs lists get archive/unarchive buttons per row
(VELA write/admin only). All use confirmation dialogs and existing
backend endpoints with proper permission gating.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Projects card shows red "Unverified" pill linking to filtered project
list with new Verified column and faceted filter. License and cert
cards show amber "Expiring" pills alongside existing red "Expired"
pills. Added amber badge color to StatCard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces hardcoded 30-day verification window with a configurable
setting (default 90 days). Changes to any settings are now audit
logged with from/to values.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Full audit log page (VELA-only) with search, filters, and pagination
- Sidebar nav item gated on global_role (VELA users)
- User detail "Add to Project" now supports multi-select with checkboxes
- Status banner system: admin-configurable messages in topbar with
info/warning/critical types, cycling, dismiss, and expand for long text
- Banner management UI in admin settings page
- Fix table overflow in audit log with min-w-0 on app shell
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- GET /api/projects/<key>/groups/export — CSV download of all group memberships
- POST /api/projects/<key>/groups/import-preview — dry-run with diff preview
- POST /api/projects/<key>/groups/import — apply import via KC queue
- Flexible CSV parsing: supports email,group / name,group / full format
- User matching by email first, name fallback, ambiguous match detection
Frontend:
- Export/Import CSV buttons in group management header
- Import dialog with drag-and-drop upload, preview with color-coded results
- Export requires read access, import requires write/admin
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Keycloak SSO button is now the prominent primary action on the login page
- Local username/password form hidden behind collapsible "Admin access" toggle
- Falls back to username/password form when Keycloak is not configured
- Rename container to mgmt-keycloak, backup filename to mgmt-backup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused displayName variable in keycloak-discrepancy-section
- Add missing san property to Cert interface
- Fix null assignability in downtime-form Select handlers
- Fix DialogTrigger asChild -> render prop for base-ui compatibility
- Exclude test files from tsconfig.app.json to avoid vitest type leakage
- Fix type assertion in users-columns test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- After push_to_keycloak membership resolve, update any UserPermission
rows with stale keycloak_user_id to match the user's current KC ID.
This was the root cause: resolve added user to KC by their current ID,
but the discrepancy scan still saw the old ID in UserPermission.
- Remove removeQueries/invalidateQueries from resolve hooks — wiping the
cache caused the UI to unmount to "Not yet scanned" and re-mount,
jumping to the top. The component's refetch() already handles refresh.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- Fix scanned_at date format (was double timezone: +00:00Z)
- Fix membership discrepancies: walk child groups (mirrors sync logic),
deduplicate with set arithmetic, fix per-child exception handling
- Filter group comparison to top-level KC groups only (skip child groups
that some KC versions return in flat list)
- Resolve user names from KC user list instead of showing raw UUIDs
- Add Cache-Control: no-store on all API responses to prevent browser
from serving stale data after mutations
- Add max=1000 pagination param to get_group_members
Frontend:
- Replace jargon badges: Local Only → Missing in KC, KC Only →
Missing locally, Mismatch → Out of sync
- Rewrite membership rows: show user name/email instead of UUIDs,
plain-English descriptions instead of two-column diff layout
- Remove staleTime from discrepancy query, use removeQueries instead
of invalidateQueries to ensure fresh data after resolve
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>