Admin Pilots tab:
- list and create/edit form merged into one - "+ Добавить пилота" at
the top of the list clears the form into create mode, clicking a
pilot switches it into edit mode (title, Delete button and icon
box toggle accordingly) instead of keeping two separate stacked
forms
- each list row gets an inline on/off toggle (⏻/▶) to flip active
without opening the form
- categories and the login/password hint field removed from the
pilot form per earlier feedback - not applicable to pilots
Access model: assigning a pilot's RDP slot to a user is now the
single action that grants them the pilot - it creates/updates their
UserServiceAccess row (with the optional expiry typed right next to
the assign dropdown) in the same call, and unassigning revokes it.
Previously slot assignment and the ACL grant were two separate steps
an admin could forget to pair up, leaving a user "granted" with no
working slot or a slot with no visible access.
Consequently: pilots are no longer listed in the Users tab's ACL
grid at all - the Pilots tab is now the only place pilot access is
managed. set_acl() was fixed to never touch pilot grants regardless
of what's posted (it used to free a user's pilot slot whenever it
saw a pilot id missing from service_ids - which happens on every
save now that the grid never includes pilots, so saving any other
product's ACL would have silently stripped every pilot grant).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pilots are invite-only and never appear in category-browsable
self-service catalogs, so the category checklist served no purpose
there. Dropped the login/password hint field too - not requested
for pilots.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Admin: moved pilot management out of the RDP tab into its own
"Pilots" tab - dedicated list, create/edit form and slot table with
a per-slot user-assign dropdown (always on, no more is_pilot
checkbox toggling the RDP tab's slot table between two modes). The
RDP tab goes back to exactly its pre-pilots shape.
Dashboard: a granted pilot no longer sits inside the "Ваши сервисы"
grid - it gets its own <details> section above it, open by default
whenever the user has at least one pilot (and simply absent when
they have none, rather than showing empty and collapsed).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>