Commit Graph

231 Commits

Author SHA1 Message Date
161182b392 feat(api): support netN add/remove in VM and LXC config updates
- update_vm_config: whitelist netN/scsiN/virtioN/ideN/sataN/ipconfigN by
  regex instead of hardcoded 0-3; accept validated delete='netN' key;
  raise with real Proxmox error text instead of returning False
- update_container_config: refactor to dict signature with whitelist +
  netN + delete (fixes LXC config PUT endpoint that passed the whole
  dict as hostname)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 02:32:21 +05:00
7d52297f76 fix(deploy): apply VM config reliably after clone
- wait for clone lock release before config.put (cross-node clones kept
  lock past wait_for_task, dropping ALL requested settings silently)
- configure_vm applies params in independent groups (compute/network/
  cloudinit/disk) with per-group errors and lock retry
- set sockets=1 with cores so total vCPU matches request
- keep template net0 model/MAC/options, replace only bridge
- disk resize: pick boot disk via regex over all buses, report failures
- surface config errors as deploy warnings in UI toast instead of
  silent success

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 02:31:21 +05:00
e366646257 fix(deploy): cacheable build in update watchdog + auto-prune
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 8s
Watchdog rebuilt images with --no-cache on every push, writing fresh
build-cache layers that were never reused. Over ~2 weeks this grew the
Docker build cache to 57GB and filled the production disk.

- Switch `build --no-cache` to `build --pull` (cacheable). Dockerfiles
  already isolate heavy layers (apt/pip/npm) from source COPY, so app/
  frontend changes are still rebuilt — without re-running apt/pip/npm.
- Add `docker image prune` + `docker builder prune until=24h` after a
  successful deploy so old images and stale cache don't accumulate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:09:17 +05:00
3622747371 fix(dashboard): make donut tooltip readable
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 7s
Theme tokens are oklch(); wrapping them in hsl(var(--card)) produced an
invalid, transparent tooltip background so the label blended into the
card. Use the oklch vars directly and force readable text colour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:48:58 +05:00
daa75e1502 feat(dashboard): show top disk stats as a donut chart
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 8s
Replace the disk I/O area chart with a donut where each slice is a node
sized by its disk usage % (disk_pct). More meaningful than I/O, which is
near-zero most of the time. Use real node names (name|node) in the legend
instead of node-0/node-1, and drop the now-unused RRD fetch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:41:54 +05:00
381e7ae821 i18n(instances): localize bulk operation toolbar buttons
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 7s
Translate Start/Stop/Delete/Clear and the selected-count label in the bulk
actions toolbar (add instances.selected_count key). Also includes node label
display in the node filter/table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:02:11 +05:00
1f9a51b0be fix(dashboard): render disk chart; show server name as node label
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 7s
- Dashboard "ТОП статистика по диску" was stuck on "Загрузка...": the
  node name in the dashboard_metrics WS payload is `name`, not `node`, so
  the RRD query stayed disabled. Use name fallback and unwrap the
  {data:[...]} response shape.
- Instances list + detail page: show the panel-side server name (from the
  add-server form) in the "Узел" column/label instead of the Proxmox node
  hostname. Real node is still used for all API calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 18:56:27 +05:00
fd47241394 feat(instances): bulk restart action and live bulk-operation status tracking
Add a Restart button to the bulk-selection toolbar (backend already supports
bulk_restart) and track bulk operation progress like instance creation:
per-row status overlay on affected instances plus a progress bar driven by
task_update WebSocket events.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 18:56:20 +05:00
1d1bd33e03 fix(instances): show RAM in correct units (MiB to bytes) in config
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 7s
VM config `memory` is in MiB but formatBytes treated it as bytes,
rendering "2 KB RAM" instead of "2 GB RAM". Convert before formatting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 18:47:47 +05:00
9b1000ba76 docs: document per-user quotas and bump version to v1.7.0
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 8s
Update README/README.ru, CHANGELOG/CHANGELOG.ru, WIKI and VERSION to
cover the per-user resource quotas feature (commit 35dcbfd): instance/
vCPU/RAM/disk limits, HTTP 429 enforcement, quota:view/quota:manage
permissions, admin + self-service endpoints and UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:59:08 +05:00
56d944a4c8 Удалить CODE_REVIEW.md
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 7s
2026-06-26 23:53:23 +05:00
35dcbfd18f feat(quotas): per-user resource limits for VM/LXC deployment
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 54s
Deploy to Production / Trigger production update (push) Successful in 8s
Add per-user quotas (max instances, vCPU, RAM, disk) enforced at deploy
time and surfaced to users self-service.

Backend:
- UserQuota model (one-to-one, NULL = unlimited) + migration #28
- quota_service: usage from vm_instances cache, check_quota raises 429
- enforce in VM (templates.py) and LXC (lxc.py) deploy paths
- RBAC quota:view / quota:manage, granted to admin
- admin API GET/PUT /admin/api/users/{id}/quota
- self-service GET /settings/api/quota
- tighten VM list isolation via can_view_all_instances

Frontend:
- admin quota dialog in UsersPage with usage bars
- quota card on Settings page (own limits)
- remaining-quota hint in CreateInstanceWizard config step
- useUserQuota/useSetUserQuota, useMyQuota hooks; RU/EN locales

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 18:15:02 +05:00
d59d787bed feat(dashboard): redesign in VMmanager style with multi-node disk chart
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 7s
- Three-panel top row: cluster overview, tasks-today, node gauges
- Cluster overview: IPv4 available in stats row alongside nodes/VM count,
  RAM/disk progress bars, RAM overselling ratio
- Tasks panel: completed/running/queued/errors/total from useAllTasks
- Node panel: SVG circular CPU & RAM gauges
- Bottom: nodes table with top-10 tabs (by CPU or disk%) + Grafana-style
  disk I/O chart with parallel useQueries for top-5 nodes, CartesianGrid,
  per-node colored lines and legend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 03:26:41 +05:00
28fa6eb5b4 fix(instances): reset stale server/node filter on workspace switch
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 10s
Persisted instances-server-filter/instances-node-filter kept an old id
after switching workspace, emptying the list and showing the raw id in
the Select. Drop the filter to 'all' when the server/node is no longer
present in the active workspace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 03:25:44 +05:00
7117c8adc3 feat(images): collapsible sections with persisted state
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 7s
Make 'Cloud-образы ВМ' and 'LXC-шаблоны' section headers clickable to
collapse/expand. State is saved to localStorage per section so it
survives reloads and navigation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 03:24:55 +05:00
845c8024c4 feat(auth): forgot/reset password pages and wiring
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 1m16s
Deploy to Production / Trigger production update (push) Successful in 8s
- ForgotPasswordPage and ResetPasswordPage in the split-screen auth layout.
- "Forgot password" link on the login page now routes to /forgot-password.
- Public routes /forgot-password and /reset-password; reset page validates
  the token on mount and gates the form, with show/hide password and
  confirmation matching.
- i18n keys (reset.*) for ru/en.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 03:24:19 +05:00
8b93161434 feat(auth): self-service password reset (forgot/reset endpoints)
Add an email-based "forgot password" flow:
- PasswordResetToken model (stores only a SHA-256 hash, single-use, TTL)
  and migration 27 for the password_reset_tokens table.
- POST /api/auth/forgot-password — generic response (no account
  enumeration), 60s throttle, emails a one-time reset link via the
  existing EmailChannel as a background task.
- GET /api/auth/reset-password/validate — token validity check for the UI.
- POST /api/auth/reset-password — validates the token, enforces the
  password policy, sets the new password, clears lock state, invalidates
  outstanding tokens and terminates all sessions.
- Configurable link lifetime via PASSWORD_RESET_TOKEN_EXPIRE_MINUTES (30m).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 03:24:19 +05:00
65e14585c5 chore(frontend): make vite dev runnable outside Docker
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 8s
Allow overriding the dep-optimizer cache dir via VITE_CACHE_DIR (the
default node_modules/.vite is root-owned when built in Docker) and honor
a PORT injected by the harness. Both default to prior behavior when the
env vars are unset, so Docker builds are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 03:05:18 +05:00
89962a7f20 feat(auth): split-screen login with geometric pattern
Redesign the login page into a two-column layout: a decorative
geometric line pattern on the left (hidden on mobile) and the form on
the right with the PVEmanager logo on top and a footer with a language
switcher + copyright. Add a password show/hide toggle and a "forgot
password" link. Login logic (username/password) is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 03:05:18 +05:00
0510c1739d docs(images): document cloud-image catalog; bump to v1.6.0
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 8s
Add v1.6.0 CHANGELOG entries (EN/RU) for the Images module: cloud-image
catalog, auto-convert to VM template, custom mirrors, arch-aware filtering.
Add feature bullet to README (EN/RU) and a dedicated WIKI section + TOC entry.
Bump VERSION 1.5.2-1 -> 1.6.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 01:38:22 +05:00
f880c9e177 fix(images): idempotent download (skip if file exists) + real failure reason
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 52s
Deploy to Production / Trigger production update (push) Successful in 8s
Retrying a template build failed at download because the qcow2 from a prior
attempt already existed and download-url refuses to overwrite. Now skip the
download when the volume already exists (volume_exists), and surface the PVE
task-log tail on download failure instead of a generic message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:38:50 +05:00
cdb49abf0f fix(images): sanitize VM name for import; honest create-error message
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 55s
Deploy to Production / Trigger production update (push) Successful in 9s
create_vm_with_import got the display name ('Ubuntu 22.04 LTS (Jammy) Cloud')
which PVE rejects (Parameter verification failed on 'name'). Sanitize to a
DNS-safe name (e.g. ubuntu-22-04-lts-jammy-cloud). Also stop attributing every
create failure to 'import unsupported' — only say that when the error mentions
import; otherwise surface the real error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:31:34 +05:00
1e06be10ad fix(images): normalize import filename extension (.img -> .qcow2)
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 52s
Deploy to Production / Trigger production update (push) Successful in 8s
Proxmox content=import rejects Ubuntu cloud '*.img' with 'invalid filename
or wrong extension' though the format is qcow2. Rename non-import-extension
files to .qcow2 before download-url; import-from auto-detects the format.
Applied to both download and template workers (download filename + import volid).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:52:56 +05:00
03b574777c fix(images): default arch to node platform; fix node selector placeholder
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Successful in 55s
Deploy to Production / Trigger production update (push) Failing after 10s
- arch filter defaults to amd64 and auto-syncs to the selected node's arch
  (useNodeArch), so amd64 nodes no longer show arm64 images by default
- node Select no longer renders raw '__none__'; shows 'Нода' placeholder
  and a disabled hint when no nodes are reachable

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:28:04 +05:00
2dff9f8c5c feat(images): node-arch endpoint for arch-aware filtering
GET /proxmox/api/{server_id}/images/node-arch derives amd64/arm64 from
node status current-kernel.machine; returns null when node unreachable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:28:04 +05:00
d2f57e13db feat(images): Phase 3 UI — custom mirror manager (admin)
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Successful in 51s
Deploy to Production / Trigger production update (push) Failing after 10s
Admin-only MirrorsManager on ImagesPage: list/add/edit/delete custom
mirrors with kind/arch/url/template/checksum fields; mirror CRUD hooks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:17:00 +05:00
ed84e00e7b feat(images): Phase 3 backend — custom mirror CRUD
GET/POST/PUT/DELETE /proxmox/api/images/mirrors (perm template:manage).
Enabled mirrors merge into the catalog feed automatically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:17:00 +05:00
ec8f4904a1 feat(images): Phase 2 UI — make-template toggle in download dialog
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Successful in 51s
Deploy to Production / Trigger production update (push) Failing after 11s
qcow2: 'make VM template' toggle (default on) with disk-storage picker,
cores/memory/bridge and cloud-init user/password fields; routes to
image_template task kind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:11:20 +05:00
c7edf72d19 feat(images): Phase 2 — auto-convert qcow2 to VM template
- client.create_vm_with_import: API-only disk import via scsi0 import-from (PVE 8.2+)
- _do_image_template_sync worker: download(import) -> create+import -> cloud-init
  -> convert_to_template -> register OSTemplate (Cloud Images group)
- images download endpoint: to_template branch + cloud-init/disk_storage params
- clear fallback error when storage lacks import support (file kept)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:11:20 +05:00
32e06fe922 feat(images): frontend Images page, catalog hooks, download dialog
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Successful in 55s
Deploy to Production / Trigger production update (push) Failing after 11s
Phase 1 UI for VM image mirror feature:
- use-image-catalog hook (catalog, lxc repo templates, target storages, download)
- ImagesPage: server/node context, arch/search filters, cloud + LXC sections
- DownloadImageDialog: storage picker, background download via DeployTask
- CatalogImage/ImageMirror types, image_download/image_template task kinds
- sidebar nav 'Images' + /images route

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:57:16 +05:00
7aed5dd97e feat(images): backend catalog + mirror model + download API/worker
Phase 1 of VM image mirror feature:
- StorageMixin.download_url (PVE download-url/aria2) + get_download_target_storages
- builtin curated cloud-image catalog (Ubuntu/Debian/Rocky/Alma, amd64+arm64)
- ImageMirror model for admin custom mirrors
- /proxmox/api/images: catalog, lxc-templates proxy, storages, download
- _do_image_download_sync worker with log-tailing progress (DeployTask)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:51:21 +05:00
4d8cdc0143 Merge branch 'main' of https://github.com/markmorado/pvemanager
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 8s
2026-06-22 03:10:27 +05:00
6e67ad7e77 fix(disk-resize): keep growpart script ASCII-only
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Successful in 51s
Deploy to Production / Trigger production update (push) Has been cancelled
Proxmox's guest-agent exec (PVE/QemuServer/Agent.pm) dies with
"Wide character in subroutine entry" when input-data contains non-ASCII
bytes. The growpart script had Cyrillic comments, so the whole auto-grow
call returned 500 and the FS was never resized. Replace those comments
with ASCII so the script reaches the guest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 03:09:43 +05:00
Dilshod Rasulov
359b85310f Create SECURITY.md 2026-06-22 02:58:35 +05:00
f67ee24155 Merge branch 'feature/metrics-history'
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 50s
Deploy to Production / Trigger production update (push) Successful in 7s
2026-06-22 02:47:45 +05:00
9e9cac15e1 fix(disk-resize): pass node, real disk picker, shrink guard, grow output
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 49s
Deploy to Production / Trigger production update (push) Successful in 8s
Settings tab disk-resize UX:
- useResizeDisk/useUpdateConfig now send the required node query param,
  fixing the 422 "Field required" error.
- disk device is chosen from the VM's actual disks (defaults to the boot
  disk) instead of a hardcoded scsi0.
- show the current disk size, prefill it, and block shrinking before
  hitting Proxmox (which rejects it).
- render the growpart output and a grown/not-grown status after resize.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 02:43:12 +05:00
38d485be4b fix(disk-resize): honest task status, auto-grow FS, CRLF-safe guest exec
Backend fixes for VM/LXC disk resize:
- resize_vm_disk/resize_container_disk now honor wait_for_task and raise
  the real Proxmox exitstatus (e.g. "disk 'scsi0' does not exist") instead
  of always returning success.
- add grow_vm_filesystem: after a VM resize, rescan the disk (retry loop,
  needed for SATA/SCSI), growpart the partition and resize the FS
  (ext*/xfs/btrfs) via the guest agent; surface its output to the API.
- execute_script normalizes CRLF/CR to LF so pasted scripts and growpart
  don't break on trailing \r ("partition-number must be a number").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 02:43:03 +05:00
1a17ceed82 fix(sync): store VM memory/disk in MB/GB, not raw Proxmox bytes
The monitoring worker wrote Proxmox maxmem/maxdisk (bytes) straight into
the memory/disk_size columns, which the rest of the app stores as MB/GB.
Every sync cycle clobbered correct values with bytes, so the saved-config
card showed huge byte numbers under MB/GB labels. Convert via new
_bytes_to_mb/_bytes_to_gb helpers; existing rows self-heal on next sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 02:42:49 +05:00
ddd37060fe fix(metrics): move disk-fill chart to the very end after IOPS
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 01:23:32 +05:00
7a5485c63c fix(metrics): reorder graph cards, per-chart hover cursor, readable dark tooltip
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 01:21:04 +05:00
0f7c5a44df fix(metrics): move disk-fill chart up next to CPU and memory
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 21:57:52 +05:00
e90ad2e0d2 fix(metrics): keep previous series on NIC/range switch to avoid full-grid reload
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 21:57:52 +05:00
af10009375 chore: ignore local superpowers specs/plans and SDD scratch
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 21:35:15 +05:00
074983a1f6 fix(metrics): correct cpu/disk% chart scaling, evict stale collector state, lighten nic query, drop polling & dead RRD code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 21:35:02 +05:00
4f224d90cf feat(metrics): graphs tab reads stored history with date range, NIC selector, IOPS & disk% 2026-06-21 21:35:02 +05:00
0aed32f998 feat(metrics): add useVMMetrics hook 2026-06-21 21:35:02 +05:00
bbec17abd1 feat(metrics): add collector + retention loops and wiring 2026-06-21 21:35:02 +05:00
df2c59eb3f chore: pre-existing WIP (overview/realtime/format/types/use-instances)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 21:35:02 +05:00
6769e5b218 feat(metrics): add /metrics read endpoints for vm and container 2026-06-21 21:35:02 +05:00
e380ec1ab2 feat(metrics): add query_instance_metrics read aggregation 2026-06-21 21:35:02 +05:00