Author SHA1 Message Date
kyle f0b4d84c0b Merging 2026-10-07 15:01:02 -06:00
kyle 067b714678 Upload files to "/" 2026-10-07 20:10:11 +00:00
kyle 3355a7d4bc Update collectors/veeam/Send-VeeamBackupStatus.ps1 2026-10-07 20:09:47 +00:00
kyle e916afaae1 Update collectors/veeam/MockVeeam.ps1 2026-10-07 20:09:36 +00:00
kyle 1f96d11a27 Update collectors/veeam/Install-VeeamCollector.ps1 2026-10-07 20:09:26 +00:00
kyle 41c4fa2700 Delete collectors/veeam/Send-VeeamBackupStatus.ps1 2026-10-07 20:09:13 +00:00
kyle a84a35463b Delete collectors/veeam/MockVeeam.ps1 2026-10-07 20:09:09 +00:00
kyle 96b729bec8 Delete collectors/veeam/Install-VeeamCollector.ps1 2026-10-07 20:09:04 +00:00
kyle ae574fa2b6 Delete README.md 2026-10-07 20:08:58 +00:00
kyle e50711b93b Delete Get-VeeamCollectorDiagnostics.ps1 2026-10-07 20:07:30 +00:00
kyle 8fd17e3817 Upload files to "/" 2026-10-07 20:05:28 +00:00
kyle 986cf3be88 Upload files to "collectors/veeam" 2026-10-07 19:42:01 +00:00
kyle 17ee303670 Update scheduled task time to one hour from 15 minutes 2026-10-07 15:57:49 +00:00
kyle effafc5fa0 Delete directory 'test' 2026-10-07 15:56:47 +00:00
kyle 2318579baa Update test/testfolder 2026-10-07 15:56:26 +00:00
kyle 1f02c1c84b Add testfolder 2026-10-07 15:55:31 +00:00
kyle 3b1aee7016 Update scheduled task to use Powershell 7
Todo: add logic to check for and install Powershell 7
2026-10-07 15:48:27 +00:00
kyle d3ab05416d Update manage.py 2026-10-07 01:36:10 +00:00
kyle b8452e1fbb Upload files to "/" 2026-10-06 00:55:03 +00:00
kyle d12e00f69e Update collectors/veeam/README.md 2026-10-06 00:54:51 +00:00
kyle 33c1613ef9 Update collectors/vveam/README.md 2026-10-06 00:54:38 +00:00
kyle 8e29b9cbc4 Update collectors/veeam/Install-VeeamCollector.ps1 2026-10-06 00:54:26 +00:00
kyle 49bf9ef094 Update collectors/vveam/Install-VeeamCollector.ps1 2026-10-06 00:54:13 +00:00
kyle 4d183da7bd Update collectors/veeam/Send-VeeamBackupStatus.ps1 2026-10-06 00:53:47 +00:00
kyle 25f7717ace Update collectors/veeam/MockVeeam.ps1 2026-10-06 00:53:34 +00:00
kyle be23e03c5f Upload files to "/" 2026-10-06 00:53:21 +00:00
kyle 65c587da0d Delete format.js 2026-10-05 18:05:01 +00:00
kyle 29f961f80e Delete config.js 2026-10-05 18:04:49 +00:00
kyle 02789b1910 Update deploy/nginx.conf 2026-10-05 18:03:49 +00:00
kyle 54a6c0b3d6 Update deploy/backup-dashboard-api.service 2026-10-05 18:02:38 +00:00
kyle f11345bb0f Upload files to "backend" 2026-10-05 04:03:26 +00:00
kyle 7217b3e9a8 Upload files to "backend/app" 2026-10-05 04:03:05 +00:00
kyle 47ec7dc9a7 Upload files to "backend/app" 2026-10-05 04:02:57 +00:00
kyle 15ea9e0fec Update backend/app/__init__.py 2026-10-05 04:02:44 +00:00
kyle fe4bd6827c Upload files to "backend" 2026-10-05 04:02:31 +00:00
kyle 4e57eb1a46 Update backend/tests/test_api.py 2026-10-05 04:02:13 +00:00
kyle 670994b937 Update backend/tests/__init__.py 2026-10-05 04:02:03 +00:00
kyle a7c3169606 Upload files to "backend" 2026-10-05 04:01:49 +00:00
kyle 938a0a2af0 Upload files to "backend" 2026-10-05 04:01:38 +00:00
kyle f7e029db8a Upload files to "public/js" 2026-10-05 04:01:12 +00:00
kyle 2df1a75fd5 Update public/css/styles.css 2026-10-05 04:00:56 +00:00
kyle 4d8ee209a5 Update public/index.html 2026-10-05 04:00:38 +00:00
kyle 6c486fbaf3 Update public/js/api.js 2026-10-05 04:00:29 +00:00
kyle fdc7deff16 Upload files to "/" 2026-10-05 04:00:12 +00:00
kyle 0391807027 Upload files to "/" 2026-10-05 03:59:41 +00:00
kyle b0c560377b Update backend/requirements.txt 2026-10-05 03:58:42 +00:00
kyle 17346cc058 Update backend/requirements-dev.txt 2026-10-05 03:58:28 +00:00
kyle 65eb3c88f3 Update backend/manage.py 2026-10-05 03:58:08 +00:00
kyle 996fe33a40 initial upload 2026-10-05 03:57:48 +00:00
8 changed files with 126 additions and 0 deletions
+1
Submodule Backup-Dashboard added at ba798284df
+8
View File
@@ -0,0 +1,8 @@
__pycache__/
*.pyc
.venv/
venv/
.pytest_cache/
*.db
*.db-wal
*.db-shm
Binary file not shown.
+11
View File
@@ -5,9 +5,12 @@ from pathlib import Path
# The modal shows this many days of run history; retention can never be shorter.
HISTORY_DAYS = 30
<<<<<<< HEAD
=======
# Shared by the systemd service (EnvironmentFile=) and manage.py, so both always use the same DB.
DEFAULT_ENV_FILE = "/etc/backup-dashboard/backup-dashboard.env"
>>>>>>> add-backend-api
@dataclass(frozen=True)
class Settings:
@@ -17,6 +20,13 @@ class Settings:
serve_static: bool = False
<<<<<<< HEAD
def load_settings() -> Settings:
return Settings(
db_path=Path(os.environ.get("BACKUP_DASH_DB", "backup-dashboard.db")),
retention_days=max(HISTORY_DAYS, int(os.environ.get("BACKUP_DASH_RETENTION_DAYS", "90"))),
serve_static=os.environ.get("BACKUP_DASH_SERVE_STATIC") == "1",
=======
def read_env_file(path: Path) -> dict[str, str]:
"""Parse simple KEY=VALUE lines (the subset systemd's EnvironmentFile accepts)."""
values = {}
@@ -40,4 +50,5 @@ def load_settings() -> Settings:
db_path=Path(env.get("BACKUP_DASH_DB", "backup-dashboard.db")),
retention_days=max(HISTORY_DAYS, int(env.get("BACKUP_DASH_RETENTION_DAYS", "90"))),
serve_static=env.get("BACKUP_DASH_SERVE_STATIC") == "1",
>>>>>>> add-backend-api
)
Binary file not shown.
+83
View File
@@ -0,0 +1,83 @@
# Stand-ins for the Veeam cmdlets, shaped like the real objects' properties the collector reads.
# Dot-source this before running the collector to try it without a VBR server:
#
# . ./tests/MockVeeam.ps1
# ./Send-VeeamBackupStatus.ps1 -ConfigPath ./tests/config.json -DryRun
# Fixed to the top of the hour, and IDs derived from names/times, so repeated runs see the same
# jobs and sessions, like a real server
$now = Get-Date -Minute 0 -Second 0 -Millisecond 0
function New-StableGuid([string]$Seed) {
[guid]::new([Security.Cryptography.MD5]::Create().ComputeHash([Text.Encoding]::UTF8.GetBytes($Seed)))
}
$notSet = Get-Date '1900-01-01'
function New-MockJob($Name, $Type, $NextRun, [bool]$Enabled = $true) {
[pscustomobject]@{
Id = New-StableGuid $Name; Name = $Name; JobType = $Type
IsScheduleEnabled = $Enabled
ScheduleOptions = [pscustomobject]@{ NextRun = $NextRun }
}
}
function New-MockSession($Job, $Start, $End, $State, $Result, $Reason = '', $Tasks = @(), $LogRecords = @(), $Size = 0) {
$s = [pscustomobject]@{
Id = New-StableGuid "$($Job.Name)|$($Start.Ticks)"; JobId = $Job.Id; JobName = $Job.Name
CreationTime = $Start; EndTime = $End; State = $State; Result = $Result
IsRetryMode = $false
Info = [pscustomobject]@{ Reason = $Reason }
BackupStats = [pscustomobject]@{ BackupSize = $Size; DataSize = $Size * 3 }
Logger = [pscustomobject]@{ Records = $LogRecords }
Tasks = $Tasks
}
$s.Logger | Add-Member ScriptMethod GetLog { [pscustomobject]@{ UpdatedRecords = $this.Records } }
$s | Add-Member ScriptMethod GetTaskSessions { $this.Tasks }
$s
}
function New-MockTask($Name, $Status, $Reason = '') {
[pscustomobject]@{ Name = $Name; Status = $Status; Info = [pscustomobject]@{ Reason = $Reason } }
}
$global:MockJobs = @(
(New-MockJob 'File Server – Nightly' 'Backup' $now.Date.AddDays(1).AddHours(22)),
# Older VBR versions return NextRun as a culture-formatted string
(New-MockJob 'SQL01 – Logs' 'Backup' $now.AddMinutes(40).ToString()),
(New-MockJob 'Offsite Copy' 'BackupSync' $null),
(New-MockJob 'Old Exchange' 'Backup' $now.AddDays(1) $false),
# VBR 12+ backup copy job (immediate mode, no schedule): the "policy" job Get-VBRJob returns
(New-MockJob 'Cloud Copy' 'SimpleBackupCopyPolicy' $null)
)
$fs, $sql, $copy, $old, $cloud = $global:MockJobs
# Its hidden worker job, one per source job. Get-VBRJob doesn't return it, and Get-VBRBackupSession
# returns only the worker's sessions. Shape taken from a real VBR 13 server.
$cloudWorker = [pscustomobject]@{
Id = New-StableGuid 'Cloud Copy\File Server – Nightly'; Name = 'Cloud Copy\File Server – Nightly'
JobType = 'SimpleBackupCopyWorker'; PolicyJobId = $cloud.Id
}
function New-MockWorkerSession($Start, $End, $Result) {
$s = New-MockSession $cloudWorker $Start $End 'Stopped' $Result -Size 40GB
$s | Add-Member ScriptMethod GetJob { $cloudWorker }
$s
}
$global:MockSessions = @(
(New-MockSession $fs $now.AddDays(-1).AddHours(-2) $now.AddDays(-1).AddHours(-1) 'Stopped' 'Success' -Size 128GB -Tasks @(
(New-MockTask 'FS01' 'Success'), (New-MockTask 'FS02' 'Success'))),
(New-MockSession $fs $now.AddMinutes(-20) $notSet 'Working' 'None' -Tasks @((New-MockTask 'FS01' 'InProgress'))),
(New-MockSession $sql $now.AddHours(-1) $now.AddMinutes(-55) 'Stopped' 'Warning' -Size 2GB `
-Tasks @((New-MockTask 'SQL01' 'Warning' 'Failed to truncate transaction logs for database "ERP_PROD"'))),
(New-MockSession $copy $now.AddHours(-5) $now.AddHours(-4) 'Stopped' 'Failed' 'Error: Unable to connect to repository NAS02' `
-LogRecords @(
[pscustomobject]@{ Title = 'Preparing backup chain'; Status = 'ESucceeded' },
[pscustomobject]@{ Title = 'Network path not found (0x80070035)'; Status = 'EFailed' })),
(New-MockSession $old $now.AddDays(-10) $now.AddDays(-10).AddHours(1) 'Stopped' 'Success'), # outside 48h lookback
(New-MockWorkerSession $now.AddDays(-1).AddMinutes(-55) $now.AddDays(-1).AddMinutes(-30) 'Success'),
(New-MockWorkerSession $now.AddMinutes(-55) $now.AddMinutes(-35) 'Warning'),
# A session from a job the collector doesn't know (e.g. deleted): must be ignored, not crash
(New-MockSession ([pscustomobject]@{ Id = New-StableGuid 'gone'; Name = 'Deleted job' }) $now.AddHours(-2) $now.AddHours(-1) 'Stopped' 'Success')
)
function Get-VBRJob { [CmdletBinding()] param() $global:MockJobs }
function Get-VBRBackupSession { [CmdletBinding()] param() $global:MockSessions }
+18
View File
@@ -5,25 +5,37 @@ It covers jobs returned by `Get-VBRJob`: VM backup, replication and backup copy
## Install (on the VBR server, as an administrator)
<<<<<<< HEAD
=======
Requires PowerShell 7 installed machine-wide (`winget install Microsoft.PowerShell` or the MSI),
which VBR 13's Veeam module expects. The Microsoft Store version won't work, because the SYSTEM
account can't run it. The installer checks for this before changing anything.
>>>>>>> add-backend-api
1. On the dashboard host, create a key for this server:
```bash
python manage.py create-key veeam-acme
```
<<<<<<< HEAD
2. Copy this folder to the VBR server and run:
=======
2. Copy this folder to the VBR server and run, from PowerShell 7 (`pwsh`):
>>>>>>> add-backend-api
```powershell
.\Install-VeeamCollector.ps1 -ApiUrl https://backups.example.com -Tenant "Acme Corp"
```
It prompts for the key, then:
- writes `C:\ProgramData\BackupDashboard\veeam-collector.json` (readable only by SYSTEM and Administrators)
- copies the script to `C:\Program Files\BackupDashboard\`
<<<<<<< HEAD
- registers the scheduled task **Backup Dashboard - Veeam collector** (SYSTEM, every 15 minutes)
3. Preview the payloads, then backfill 30 days so the history view is populated straight away:
=======
- registers the scheduled task **Backup Dashboard - Veeam collector** (SYSTEM, every 15 minutes,
run by `C:\Program Files\PowerShell\7\pwsh.exe`; use `-PwshPath` if it's installed elsewhere)
3. Preview the payloads, then backfill 30 days so the history view is populated straight away
(from `pwsh`):
>>>>>>> add-backend-api
```powershell
& 'C:\Program Files\BackupDashboard\Send-VeeamBackupStatus.ps1' -DryRun
& 'C:\Program Files\BackupDashboard\Send-VeeamBackupStatus.ps1' -LookbackHours 720
@@ -44,10 +56,13 @@ delete the scheduled task and both `BackupDashboard` folders.
| Message | Session reason, each VM or object that didn't succeed, and warning/error lines from the session log |
| Size | `BackupStats.BackupSize` |
<<<<<<< HEAD
=======
**Backup copy jobs (VBR 12+):** Veeam records their sessions under hidden per-source "worker" jobs.
The collector files each worker session under its copy job (via the worker's `PolicyJobId`), so a
copy job gets one run per source job it copies, with the source in `copied_from`.
>>>>>>> add-backend-api
Each run covers the last 48 hours (`LookbackHours`), so a few missed runs (server rebooting, API
down) fill in automatically. Sessions already delivered are recorded in
`veeam-collector.state.json`. Running sessions are re-sent until they finish.
@@ -58,9 +73,12 @@ down) fill in automatically. Sessions already delivered are recorded in
- Exit codes: `0` OK, `1` some jobs failed to send (they're retried next run), `2` setup failed
(config, Veeam module, or Veeam connection).
- `API key rejected`: the key was revoked or mistyped. Re-run the installer with a new key.
<<<<<<< HEAD
=======
- A job shows **Unknown** on the dashboard: the collector sent the job but matched no sessions to it.
Run `.\Get-VeeamCollectorDiagnostics.ps1` (read-only). It lists sessions whose job ID matches no
job, plus the type and link properties of the jobs left without sessions.
>>>>>>> add-backend-api
- `Get-VBRBackupSession` loads every session in the VBR database. On a server with years of history,
this can make each run take a minute or more. The task's 30-minute limit and "don't start if
already running" setting keep that safe.
+5
View File
@@ -6,8 +6,13 @@ After=network.target
User=backupdash
Group=backupdash
WorkingDirectory=/opt/backup-dashboard/backend
<<<<<<< HEAD
Environment=BACKUP_DASH_DB=/var/lib/backup-dashboard/backup-dashboard.db
Environment=BACKUP_DASH_RETENTION_DAYS=90
=======
# Settings live in this file so manage.py reads exactly the same ones (see deploy/backup-dashboard.env)
EnvironmentFile=/etc/backup-dashboard/backup-dashboard.env
>>>>>>> add-backend-api
# Bound to localhost: only nginx can reach it, so nginx's auth can't be bypassed from outside.
# One worker is plenty at this scale and keeps SQLite writes simple.
ExecStart=/opt/backup-dashboard/venv/bin/uvicorn --factory app.main:create_app --host 127.0.0.1 --port 8000 --proxy-headers