Add pilots: invite-only products with per-user RDP slots
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>
This commit is contained in:
@@ -115,6 +115,10 @@
|
||||
.lg2-wall-tile img{width:100%;height:100%;object-fit:contain;filter:grayscale(1) opacity(.75)}
|
||||
.lg2-wall-tile:hover img{filter:none}
|
||||
.lg2-wall-tile:hover{border-color:var(--lg2-accent)}
|
||||
.lg2-pilots-sub{font:400 12.5px/1.5 "Ubuntu",sans-serif;color:var(--lg2-fg-faint);margin:-6px 0 14px;max-width:62ch}
|
||||
.lg2-wall-tile-pilot{cursor:default}
|
||||
.lg2-wall-tile-pilot:hover{border-color:var(--lg2-line)}
|
||||
.lg2-wall-tile-pilot:hover img{filter:grayscale(1) opacity(.75)}
|
||||
|
||||
.lg2-pitch-footer{margin-top:auto;padding-top:36px;font:400 11px/1 "Ubuntu Mono",monospace;color:var(--lg2-fg-faint)}
|
||||
.lg2-pitch-footer a{color:inherit;text-decoration:none}
|
||||
@@ -273,6 +277,18 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if public_pilots %}
|
||||
<div class="lg2-wall-head"><h2>Пилотные проекты</h2><span class="lg2-wall-count">по приглашению</span></div>
|
||||
<p class="lg2-pilots-sub">Отдельные среды для пилотных внедрений — доступ выдаёт ваш менеджер MONT, здесь их нельзя запросить самостоятельно.</p>
|
||||
<div class="lg2-logo-wall lg2-logo-wall-pilots">
|
||||
{% for service in public_pilots %}
|
||||
<div class="lg2-wall-tile lg2-wall-tile-pilot" title="{{ service.name }}">
|
||||
<img src="{{ service.icon_path or '/static/service-placeholder.svg' }}" alt="{{ service.name }}" />
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="lg2-pitch-footer">MONT PROVING GROUND</div>
|
||||
</aside>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user