Extend VendorTenantSummary with agentCount, environmentCount, and
agentLimit fields. Fetch counts in parallel using CompletableFuture
per tenant, only calling server API for ACTIVE tenants with RUNNING
servers. Agent limit extracted from license limits JSONB.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The dashboard was showing hardcoded zeroes for agent and environment usage.
Now fetches real counts via M2M API from the tenant's server.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All private key writes now use writeAtomicRestricted which sets POSIX
owner-read/write permissions after writing. Gracefully skips on
non-POSIX filesystems (Windows dev).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Encrypted PKCS#8 private keys are decrypted during staging using the
provided password. The decrypted key is stored for Traefik (which needs
cleartext PEM). Unencrypted keys continue to work without a password.
- CertificateManager.stage() accepts optional keyPassword
- DockerCertificateManager handles EncryptedPrivateKeyInfo decryption
- UI: password field in upload form (vendor CertificatesPage)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tenants can upload multiple CA certificates for enterprise SSO providers
that use private certificate authorities.
- New tenant_ca_certs table (V013) with PEM storage in DB
- Stage/activate/delete lifecycle per CA cert
- Aggregated ca.pem rebuild on activate/delete (atomic .wip swap)
- REST API: GET/POST/DELETE on /api/tenant/ca
- UI: CA Certificates section on SSO page with upload, activate, remove
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RequireScope and LandingRedirect now wait for scopesReady flag before
evaluating, preventing the race where org-scoped tokens load before
global tokens and the vendor gets incorrectly redirected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Truncate fingerprint with hover tooltip
- Remove duplicate warning icon in stale banner
- Style file inputs to match design system
- Bump grid min-width for better card spacing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Certificate management (provider interface, lifecycle, bootstrap, UI)
- Async tenant provisioning with polling UX
- Server restart capability (vendor + tenant)
- Audit log actor name resolution from Logto
- SSO connector management, vendor audit page
- Updated API reference with all current endpoints
- Fixed architecture table (per-tenant containers are dynamic)
- Updated migration list through V012
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vendor persona doesn't need "Open Server Dashboard" in sidebar footer.
Removed inline Fingerprint icon from Identity (Logto) menu item.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AuditService now looks up username/name/email from Logto Management API
when actorEmail is null, with an in-memory cache to avoid repeated calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests now mock tenantRepository.findById() since provisionAsync re-loads
the tenant entity, and assert on the entity directly rather than the
return value of createAndProvision().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vendor: POST /api/vendor/tenants/{id}/restart (platform:admin scope)
Tenant: POST /api/tenant/server/restart (tenant:manage scope)
Both call TenantProvisioner.stop() then start() on the server + UI
containers. Restart button on vendor TenantDetailPage (Actions card)
and tenant TenantDashboardPage (Server card). Allowed in any status
including PROVISIONING.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: extract Docker provisioning into @Async method so the API
returns immediately with status=PROVISIONING. The tenant record, Logto
org, admin user, and license are created synchronously; container
provisioning, health check, license push, and OIDC config happen in a
background thread.
Frontend: navigate to tenant detail page immediately after creation.
Detail page polls every 3s while status=PROVISIONING and shows a
spinner indicator. Toast notification when provisioning completes.
Fixes#52.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update to @cameleer/design-system@0.1.40 where TopBar no longer depends
on GlobalFilterProvider or CommandPaletteProvider. Remove these
unnecessary provider wrappers from main.tsx. Fixes#53.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add LogtoManagementClient methods for SSO connector CRUD + org JIT
- Add TenantSsoService with tenant isolation (validates connector-org link)
- Add TenantSsoController at /api/tenant/sso with test endpoint
- Create SsoPage with provider selection, dynamic config form, test button
- Remove old OIDC config endpoints from tenant portal (server OIDC is
now platform-managed, set during provisioning)
- Sidebar: OIDC -> SSO with Shield icon
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server now hardcodes Logto org scopes in the auth flow, so the
provisioner no longer needs to push them via OIDC config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add /platform/ to SPA postLogoutRedirectUris in bootstrap (fixes#54)
- Use amber color + bold for active vendor sidebar items
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace JPQL @Query with dynamic SQL via JdbcTemplate to avoid
Hibernate null parameter type issues (bytea vs text). Conditionally
appends WHERE clauses only for non-null filters, matching the proven
pattern from cameleer3-server's PostgresAuditRepository.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hibernate binds null String params as bytea, causing PostgreSQL
lower(bytea) error. Convert null search to empty string in service
layer, use empty-string check in JPQL instead of IS NULL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hibernate passes null search param as bytea type, causing PostgreSQL
to fail on LOWER(bytea). COALESCE converts null to empty string.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without this, hard refresh on SPA routes returns 401 because Spring
Security intercepts before SpaController can forward to index.html.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vendor sees all audit events with tenant filter at /vendor/audit.
Tenant admin sees only their own events at /tenant/audit.
Both support pagination, action/result filters, and text search.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Documents traefik.docker.network label requirement, JAR volume mount,
CAMELEER_API_URL env var, additionalScopes for org roles, and the
OIDC role fallback priority (claim mapping > token roles > defaults).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The server needs a shared Docker volume at /data/jars to store
uploaded JARs that deployed app containers can access. Without this
mount, deployed containers fail with "Unable to access jarfile".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without urn:logto:scope:organizations and
urn:logto:scope:organization_roles in the additionalScopes, Logto
doesn't include organization role data in the Custom JWT context.
This caused the roles claim to be empty, so all OIDC users got
defaultRoles (VIEWER) instead of their org role (e.g. owner →
server:admin).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When VENDOR_SEED_ENABLED=true, the vendor user is now also created
in the Logto admin tenant with user + default:admin roles, giving
them access to the Logto admin console at port 3002.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Identity (Logto) link in the vendor sidebar pointed to /console
which doesn't exist. The Logto admin console is served on port 3002
via a dedicated Traefik entrypoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Traefik's Docker provider resolves container IPs using the configured
default network ('cameleer'). For dynamically-created containers not
managed by compose, this network name doesn't match. Adding the
traefik.docker.network label explicitly tells Traefik to use the
cameleer-traefik network for routing, fixing 504 Gateway Timeouts
on /t/{slug}/api/* paths.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The nginx template in cameleer3-server-ui uses ${CAMELEER_API_URL} for
the upstream proxy target, not API_URL. The wrong env var name caused
the baked-in default (http://cameleer3-server:8081) to be used, which
doesn't resolve in per-tenant networks where the server is named
cameleer-server-{slug}.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bootstrap was stuck waiting for cameleer3-server which no longer exists
in docker-compose. Removed server wait loop and SERVER_ENDPOINT config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After provisioning a server, pushes Logto Traditional Web App
credentials (client ID + secret) via the server's OIDC admin API.
This enables SSO: users authenticated via Logto can access the
server dashboard without a separate login.
Reads tradAppSecret from bootstrap JSON via LogtoConfig.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Removed cameleer3-server and cameleer3-server-ui from docker-compose
(tenants provision their own server instances via the vendor console)
- Removed viewer/camel user from bootstrap (tenant users created during
provisioning)
- Removed Phase 7 server OIDC configuration (provisioned servers get
OIDC config from env vars, claim mappings via Logto Custom JWT)
- Removed server-related env vars from bootstrap (SERVER_ENDPOINT, etc.)
- Removed jardata volume from dev overlay
Clean slate: docker compose up gives you Traefik + PostgreSQL +
ClickHouse + Logto + SaaS platform + vendor seed. Everything else
(servers, tenants, users) created through the vendor console.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Sidebar: Tenants moved into expandable "Vendor" section with
sub-items for Tenants and Identity (Logto console link)
- Bootstrap: removed example organization creation (Phase 6 org)
— tenants are now created exclusively via the vendor console
- Removed BootstrapDataSeeder (no auto-seeded tenant/license)
- Bootstrap log updated to reflect clean-slate approach
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each tenant gets an isolated Docker bridge network (cameleer-tenant-{slug}).
Server + UI containers use the tenant network as primary, with additional
connections to the shared services network (postgres/clickhouse/logto) and
Traefik network (routing). Tenant networks are internal (no internet) and
isolated from each other. Apps deployed by the tenant server also join
the tenant network. Network is removed on tenant delete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
During tenant provisioning, adds /t/{slug}/oidc/callback to the Logto
Traditional Web App's registered redirect URIs. This enables the
server's OIDC login flow to work when accessed via Traefik routing.
Also reads tradAppId from bootstrap JSON via LogtoConfig.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto usernames must match alphanumeric regex. The form now strips
invalid characters on input and shows a hint about the constraint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When creating a tenant, the vendor can specify adminUsername +
adminPassword. The backend creates the user in Logto and assigns them
the owner org role. The vendor user is also auto-added to every new
org for support access.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>