Pilots: separate admin tab, collapsible dashboard section

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>
This commit is contained in:
2026-10-02 06:51:18 +00:00
parent 46e5b5a41e
commit ae630f9115
3 changed files with 282 additions and 44 deletions
+10 -4
View File
@@ -1047,7 +1047,10 @@ def index(request: Request, user: Optional[User] = Depends(get_current_user), db
)
).all()
)
services = [svc for svc in all_services if svc.id in granted_ids]
# Pilots the user has access to get their own collapsible section above
# the regular catalog instead of sitting in the "Ваши сервисы" grid.
pilot_services = [svc for svc in all_services if svc.id in granted_ids and svc.is_pilot]
services = [svc for svc in all_services if svc.id in granted_ids and not svc.is_pilot]
locked_services = [svc for svc in all_services if svc.id not in granted_ids]
pilot_ids = {svc.id for svc in all_services if svc.is_pilot}
@@ -1107,6 +1110,7 @@ def index(request: Request, user: Optional[User] = Depends(get_current_user), db
"user": user,
"services": services,
"locked_services": locked_services,
"pilot_services": pilot_services,
"pilot_ids": pilot_ids,
"categories": categories,
"category_counts": category_counts,
@@ -1148,8 +1152,9 @@ def admin_page(request: Request, admin: User = Depends(require_admin), db: Sessi
categories = db.scalars(select(Category).order_by(Category.name)).all()
services = db.scalars(select(Service).where(Service.type.in_([ServiceType.WEB, ServiceType.RDP])).order_by(Service.id)).all()
web_services = [s for s in services if s.type == ServiceType.WEB]
rdp_services = [s for s in services if s.type == ServiceType.RDP]
pilot_service_ids = {s.id for s in services if s.is_pilot}
rdp_services = [s for s in services if s.type == ServiceType.RDP and not s.is_pilot]
pilot_services = [s for s in services if s.is_pilot]
pilot_service_ids = {s.id for s in pilot_services}
service_category_map = {s.id: [] for s in services}
if services:
service_rows = db.execute(
@@ -1231,7 +1236,7 @@ def admin_page(request: Request, admin: User = Depends(require_admin), db: Sessi
{"cutoff": cutoff},
).mappings().all()
rdp_slots: dict[int, list] = {}
for svc in rdp_services:
for svc in rdp_services + pilot_services:
slots = db.scalars(select(RdpSlot).where(RdpSlot.service_id == svc.id).order_by(RdpSlot.id)).all()
slot_list = []
for slot in slots:
@@ -1278,6 +1283,7 @@ def admin_page(request: Request, admin: User = Depends(require_admin), db: Sessi
],
"web_services": web_services,
"rdp_services": rdp_services,
"pilot_services": pilot_services,
"pilot_service_ids": pilot_service_ids,
"services": services,
"categories": categories,