- 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>
existsBySlug found DELETED records, blocking slug reuse. Changed to
existsBySlugAndStatusNot(slug, DELETED) so deleted tenant slugs can
be reclaimed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The server's /actuator/health requires auth. The public health endpoint
is /api/v1/health (same as compose-managed server's Docker HEALTHCHECK).
Also increased health check retries/timeout and added startPeriod.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- OrgResolver merges global + org-scoped token scopes so vendor's
platform:admin (from global saas-vendor role) is always visible
- LandingRedirect waits for scopes to load before redirecting
(prevents premature redirect to server dashboard)
- Layout hides tenant portal sidebar items when vendor is on
/vendor/* routes; shows them when navigating to tenant context
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vendor's platform:admin scope comes from a global Logto role, which is
only present in the non-org-scoped token. OrgResolver now fetches both
the global token and the org-scoped token, merging their scopes. This
ensures vendor users see platform:admin and land on the vendor console.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Traefik strip-prefix middleware so /t/{slug}/api -> /api on server
- Add priority to routers (server API=10, UI=5) to prevent conflicts
- Mount Docker socket + group_add in server containers for app deployment
- Add JAR storage, Docker network, volume env vars for runtime orchestrator
- Use HashMap for labels (>10 entries exceeds Map.of limit)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Vendor (platform:admin): sees only TENANTS in sidebar
- Tenant admin (tenant:manage): sees Dashboard, License, OIDC, Team, Settings
- Regular user (operator/viewer): redirected to server dashboard directly
- LandingRedirect checks scopes in priority order: vendor > admin > server
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VendorTenantControllerTest and TenantPortalControllerTest use
@SpringBootTest + Testcontainers PostgreSQL, same as the existing
controller tests that are already excluded.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds CAMELEER_AUTH_TOKEN, CAMELEER_JWT_SECRET, CAMELEER_OIDC_AUDIENCE,
CLICKHOUSE_URL to provisioned server containers. Also passes PUBLIC_HOST
and PUBLIC_PROTOCOL to SaaS container in dev overlay so provisioner
resolves the correct hostname instead of defaulting to localhost.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LandingRedirect component checks scopes — platform:admin goes to
vendor console, others go to tenant dashboard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 12 in logto-bootstrap.sh creates saas-vendor global role + vendor
user when VENDOR_SEED_ENABLED=true. Enabled by default in dev overlay.
Also restores GlobalFilterProvider + CommandPaletteProvider (required by
DS TopBar internally).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Docker socket volume, group_add: ["0"], and provisioning env vars
(CAMELEER_SERVER_IMAGE, CAMELEER_SERVER_UI_IMAGE, CAMELEER_NETWORK,
CAMELEER_TRAEFIK_NETWORK) to the cameleer-saas service in docker-compose.dev.yml.
Splits the flat 3-page UI into /vendor/* (platform:admin) and /tenant/*
(all authenticated users) route trees, with stub pages, new API hooks,
updated Layout with persona-aware sidebar, and SpaController forwarding.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace stub with full Docker implementation using docker-java. Manages
per-tenant server and UI containers with Traefik labels, health checks,
image pull, network attachment, and full lifecycle (provision/start/stop/remove/status).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds server_endpoint and provision_error columns to tenants table (V011 migration),
updates TenantEntity and TenantResponse with new fields and a from() factory,
adds revokeLicense() to LicenseService, and updates TenantController to use the factory.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Redesign SaaS platform from read-only viewer into vendor management
plane with tenant provisioning, license management, and customer
self-service. Two personas (vendor/customer), pluggable provisioning
interface (Docker first, K8s later), per-tenant server instances.
User stories tracked as Gitea issues #40-#51. Closes#37.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>