New Service.is_pilot flag - a pilot is a Service (type RDP) that:
- is excluded from /api/public/services-by-category and
/api/request-more-access (admin-granted only, no self-service)
- still shows as a locked card on the dashboard for users without
access (Доступно по запросу section), but with a По приглашению
badge instead of the self-request button/flow
- gets its own non-clickable teaser row on the public /login page
(logos only, informational)
RdpSlot.assigned_user_id (nullable) - pilot slots are bound to one
specific user instead of being drawn from the shared pool; regular
RDP services are unaffected (field stays NULL, same pool behaviour
as before). The /go/ allocator branches on service.is_pilot to pick
the caller's assigned slot instead of any free one. Slots release
automatically (cleanup_loop) when the owning grant is revoked or
expires, and immediately on manual ACL revoke.
UserServiceAccess.expires_at (nullable) - per-grant access window,
used by pilots so their access can be shorter than the account's own
expires_at; NULL (unchanged default) means "follow the account".
has_access() and the dashboard's granted/locked split both honour it.
Admin UI: "Это пилот" checkbox on the RDP service form, an
assign-user dropdown on a pilot's slot table (replaces the
occupied-by column), and a per-pilot expiry date field in the user
ACL grid.
Schema is applied via the existing ensure_schema_compatibility()
idempotent ALTER TABLE pattern (no alembic in this project) - no
manual migration step needed, it runs at container startup.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- app/main.py: raise products_raw[:20] cap to [:200] in /api/request-access
and /api/request-more-access - was silently truncating access requests
with more than 20 selected products
- accumulated bundles/product-page WIP (config, models, runtime, templates)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- New RdpSlot model (rdp_slots table): service_id, rdp_username,
rdp_password, container_name
- Each slot gets a dedicated portal-rdpslot-<slug>-<id> container with
Traefik route /rdp/<slot_id>/ and restart_policy=unless-stopped
- go_service: RDP services with slots use pool allocation — finds first
free slot (not occupied by active session), returns 503 if all busy
- session_status + session_view: handle RDPSLOT: container_id prefix
- terminate_session_record: restarts slot container in background on close
- session_redirect_url: RDPSLOT sessions redirect to /s/<id>/view
- startup_event: starts containers for all configured slots on boot
- Admin: POST /api/admin/services/{id}/rdp-slots, DELETE /api/admin/rdp-slots/{id}
- admin.html: slot management UI (list, add, delete); removed ACL exclusivity
- set_acl: removed RDP 1-user exclusivity — RDP services now assignable to many
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>