feat: Proxmox management suite — hardware, firewall, pools, disks, storage, access, node admin
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 54s
Deploy to Production / Trigger production update (push) Successful in 7s

- VM/LXC hardware editor with PCI/USB passthrough (BIOS/OVMF + efidisk,
  machine type, VGA, SCSI controller, CPU sockets); passthrough locked while running
- Firewall at datacenter/node/VM levels: rules, security groups, IP sets,
  aliases, options, log — with rule dialog referencing security groups
- Resource pools (CRUD + membership) and per-instance tags editor
- Node disks: physical disk list, SMART viewer, ZFS pool create/destroy, wipe
- Storage: add LVM/LVM-thin/ZFS/RBD/CephFS types + enable/disable toggle
- PVE Access: API tokens (create with one-time secret, delete) and realms (LDAP/AD/OpenID)
- Node admin: systemd services (start/stop/restart), APT updates & repositories
- Shared: SelectTrigger defaults to full width (fixes narrow dropdowns)

Also bundles related in-progress work: auth screen refresh, node shell console
page, server-updates badge, and node:*/access:* RBAC permissions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
markmorado
2026-07-04 05:40:52 +05:00
parent 5c8baefb3f
commit 00e36e1cc6
31 changed files with 2576 additions and 357 deletions

View File

@@ -26,6 +26,7 @@ NEW_DEFAULT_ROLES = [
"server:update": True,
"server:delete": True,
"server:manage": True,
"cluster:manage": True,
# VMs
"vm:view": True,
"vm:create": True,
@@ -57,9 +58,26 @@ NEW_DEFAULT_ROLES = [
# Storage
"storage:view": True,
"storage:manage": True,
# Firewall
"firewall:view": True,
"firewall:manage": True,
# Networking (interfaces + SDN)
"network:view": True,
"network:manage": True,
# Node administration (services, APT, upgrade)
"node:view": True,
"node:manage": True,
"node:upgrade": True,
# Resource pools
"pool:view": True,
"pool:manage": True,
# PVE access (realms & API tokens)
"access:view": True,
"access:manage": True,
# Backups
"backup:view": True,
"backup:create": True,
"backup:restore": True,
"backup:delete": True,
"backup:manage": True,
# IPAM
@@ -115,6 +133,10 @@ NEW_DEFAULT_ROLES = [
"lxc:console": True,
"template:view": True,
"storage:view": True,
"firewall:view": True,
"network:view": True,
"node:view": True,
"pool:view": True,
"backup:view": True,
"backup:create": True,
"ipam:view": True,
@@ -146,6 +168,10 @@ NEW_DEFAULT_ROLES = [
"lxc:console": True,
"template:view": True,
"storage:view": True,
"firewall:view": True,
"network:view": True,
"node:view": True,
"pool:view": True,
"ipam:view": True,
"setting:view": True,
"notification:view": True,
@@ -164,6 +190,10 @@ NEW_DEFAULT_ROLES = [
"lxc:view": True,
"template:view": True,
"storage:view": True,
"firewall:view": True,
"network:view": True,
"node:view": True,
"pool:view": True,
"ipam:view": True,
}
},
@@ -446,3 +476,67 @@ def ensure_default_roles_new_format(conn) -> None:
}
)
logger.info(f"Created role '{role_data['name']}' with new permission format")
# Granular permissions introduced after RBAC v2. For existing deployments the
# corresponding features used to be gated behind umbrella permissions
# (server:view / server:manage / vm:view / user:manage). Backfill the new
# granular keys for any role that already holds the umbrella permission so that
# custom roles do not silently lose access after the split.
GRANULAR_BACKFILL = [
# (umbrella permission, [granular permissions implied by it])
("server:view", ["firewall:view", "network:view", "node:view", "storage:view"]),
("vm:view", ["pool:view", "firewall:view"]),
("server:manage", [
"firewall:view", "firewall:manage",
"network:view", "network:manage",
"node:view", "node:manage", "node:upgrade",
"storage:view", "storage:manage",
"pool:view", "pool:manage",
]),
("user:manage", ["access:view", "access:manage"]),
]
def backfill_granular_permissions(conn) -> int:
"""
Add granular permission keys to roles that already have the matching
umbrella permission. Only adds missing keys — never overrides an explicit
grant/deny — so it is idempotent and safe to run on every startup.
Returns the number of roles updated.
"""
import json
result = conn.execute(text("SELECT id, name, permissions FROM roles"))
roles = result.fetchall()
updated = 0
for role_id, role_name, permissions in roles:
if isinstance(permissions, str):
try:
permissions = json.loads(permissions)
except Exception:
continue
if not isinstance(permissions, dict) or not permissions:
continue
additions = {}
for umbrella, implied in GRANULAR_BACKFILL:
if permissions.get(umbrella) is True:
for perm in implied:
if perm not in permissions:
additions[perm] = True
if additions:
merged = {**permissions, **additions}
conn.execute(
text("UPDATE roles SET permissions = :perms WHERE id = :id"),
{"perms": json.dumps(merged), "id": role_id},
)
logger.info(
f"Backfilled {len(additions)} granular permission(s) for role '{role_name}'"
)
updated += 1
return updated

View File

@@ -638,7 +638,89 @@ def _create_permissions() -> PermissionRegistry:
category="Notifications",
requires=["notification:view"]
))
# Firewall permissions (datacenter / node / VM firewall)
registry.register(Permission(
resource="firewall", action="view",
display_name="View Firewall",
description="View firewall rules, groups and options",
category="Firewall"
))
registry.register(Permission(
resource="firewall", action="manage",
display_name="Manage Firewall",
description="Manage firewall rules, security groups, IP sets and options",
category="Firewall",
requires=["firewall:view"]
))
# Networking permissions (node interfaces + SDN)
registry.register(Permission(
resource="network", action="view",
display_name="View Networking",
description="View node network interfaces and SDN configuration",
category="Networking"
))
registry.register(Permission(
resource="network", action="manage",
display_name="Manage Networking",
description="Manage node network interfaces, bridges and SDN (zones, vnets, subnets)",
category="Networking",
requires=["network:view"]
))
# Node administration permissions (services, APT, upgrade)
registry.register(Permission(
resource="node", action="view",
display_name="View Node Administration",
description="View node services, APT updates and repositories",
category="Node Administration"
))
registry.register(Permission(
resource="node", action="manage",
display_name="Manage Node Administration",
description="Control systemd services and manage APT repositories/refresh",
category="Node Administration",
requires=["node:view"]
))
registry.register(Permission(
resource="node", action="upgrade",
display_name="Upgrade Node Packages",
description="Run apt dist-upgrade via the node console (root shell)",
category="Node Administration",
requires=["node:view"]
))
# Resource pool permissions
registry.register(Permission(
resource="pool", action="view",
display_name="View Pools",
description="View resource pools and their members",
category="Resource Pools"
))
registry.register(Permission(
resource="pool", action="manage",
display_name="Manage Pools",
description="Create/delete pools and manage pool membership",
category="Resource Pools",
requires=["pool:view"]
))
# PVE access permissions (authentication realms & API tokens)
registry.register(Permission(
resource="access", action="view",
display_name="View PVE Access",
description="View Proxmox authentication realms and API tokens",
category="PVE Access"
))
registry.register(Permission(
resource="access", action="manage",
display_name="Manage PVE Access",
description="Manage Proxmox authentication realms and API tokens",
category="PVE Access",
requires=["access:view"]
))
return registry