feat: v1.1.8 — user-server assignment, VM/LXC ownership, smart login redirect, RBAC migrations

- Migration 20: user_servers association table (User ↔ ProxmoxServer)
- API: GET/PUT /api/users/{id}/servers — assign servers to users with workspace-conflict validation
- API: GET /api/users/{id}/server-assignments — enriched server list (assigned + compatible flags)
- Non-admin server filter: intersection of assigned_servers and workspace servers
- Migration 21: dashboard:view = false for user role (new key format)
- Migration 22: proxmox.cluster.manage → cluster:manage for admin role
- API: GET/PUT /api/{server_id}/vm/{vmid}/owner — VM/LXC owner assignment (admin-only)
- Login: smart redirect based on user permissions after authentication
- Login: returnUrl param support for post-login redirect
- Roles API: user_count field per role in GET /api/roles
- node_uptime added to server detail API response
- Remove redundant PermissionChecker from GET /users HTML route
This commit is contained in:
markmorado
2026-03-16 08:27:36 +01:00
parent c09c0c5b63
commit 57575d58ce
22 changed files with 1436 additions and 314 deletions

View File

@@ -11,7 +11,8 @@
</div>
{% endblock %}
{% block header_status %}
{% block sidebar_status %}
<div class="sidebar-status-section">
<div class="status-chip {% if stats.total_alerts == 0 %}status-ok{% elif stats.critical_alerts > 0 %}status-error{% else %}status-warning{% endif %}" id="dashStatusChip"{% if stats.total_alerts > 0 %} onclick="openAlertsModal()" style="cursor:pointer;" title="{{ t('view_alerts') if t('view_alerts') else 'Посмотреть предупреждения' }}"{% endif %}>
<span class="status-chip-dot"></span>
<span id="dashStatusText">
@@ -24,7 +25,7 @@
{%- endif -%}
</span>
</div>
<div class="header-datetime" id="dashDatetime"></div>
</div>
{% endblock %}
{% block content %}
@@ -201,24 +202,6 @@
<script>
// ======== Dashboard JS ========
// Live clock
(function startClock() {
const el = document.getElementById('dashDatetime');
if (!el) return;
function tick() {
const now = new Date();
el.textContent = now.toLocaleDateString(
'{{ "ru-RU" if lang == "ru" else "en-US" }}',
{ month: 'short', day: 'numeric' }
) + ', ' + now.toLocaleTimeString(
'{{ "ru-RU" if lang == "ru" else "en-US" }}',
{ hour: '2-digit', minute: '2-digit' }
);
}
tick();
setInterval(tick, 30000);
})();
// Colour the % badge based on value
function setPctBadgeColor(el, pct) {
el.classList.remove('pct-ok', 'pct-warn', 'pct-high');