From 8ae38a42fba5e3862fcdfda0efcbd6236c5568e1 Mon Sep 17 00:00:00 2001 From: Rizqi Date: Sun, 21 Jun 2026 04:02:24 +0700 Subject: [PATCH] feat: implement Flask-based web UI for vSphere VM management, job scheduling, and NFS status monitoring --- .../__pycache__/backup_core.cpython-310.pyc | Bin 11698 -> 12705 bytes .../__pycache__/gui_app.cpython-310.pyc | Bin 16791 -> 17606 bytes vsphere_backup/backup_core.py | 64 ++++++++-- vsphere_backup/gui_app.py | 37 +++++- vsphere_backup/templates/create_job.html | 114 +++++++++++++++++- vsphere_backup/templates/job_detail.html | 12 ++ 6 files changed, 209 insertions(+), 18 deletions(-) diff --git a/vsphere_backup/__pycache__/backup_core.cpython-310.pyc b/vsphere_backup/__pycache__/backup_core.cpython-310.pyc index 5efcc76b409ef1fe6894013d7870358b32ebb623..d18e10db93860e14392ab73c576648077ed66f61 100644 GIT binary patch delta 5206 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 MF*o+w+5$KJ4Q-=NXaE2J diff --git a/vsphere_backup/__pycache__/gui_app.cpython-310.pyc b/vsphere_backup/__pycache__/gui_app.cpython-310.pyc index 1dcbf16a5127279d3c127de6ff8e5750fabcc152..2867b216eb5517cb66bd13eb0c442b0b76cbf01c 100644 GIT binary patch delta 4063 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 delta 3207 zcma)8eQ;FO6@T}=MN|}POBh$)AwWw4DsR->dOsmo|^&hZ&RMF9%^Aduc=~#B> z{`TB+&pG$rv%hoRdwnb0J;2=EE|*=xPtE6b(atqH-1+RWqZNaUb-;_Pibdfvt5J%1 zC*;URNpumP&+GV%Hkr?i%i4VYZN6Z$G{6Q}=pn6qXc1pH%;1Q;Vi_8)L^C#K_Ap;H zX7(_et>VqYjIZY3C0(f5a$GaaFkFZ~GG@10E5{gX@5F%bC{t!){B@RDg!3`8kXFXE zPI)^Mzek5hcq?z?F5Vtjn8f9k)(Ccuyscc$=$G-1E*wob^ezNbGTD&C8JCA;OHz2! zn3T-bVcB4oHf0#EXGykk%T#V3vviE>oZL00bC2shcu((M$4V)f4$E_9z*9N>%|S;( z?%-k&Paw7uJdO}@=the$G#+kUZ-_XlpCtGx!FGZj1UnIy24zt~lb;|2<%AN&Xc`16 zGT@oq%=#SE%8H%Q*Ji5Q9Gp7s`9OTZT2Vzvj)`YPF#o%#3oTWJ`-^a3%N{OIb5RK3;c3HR!n zNVqJqr$yHv?G`-f68U6RKoBJO?;v+$h#^`XMhMxg5S`klgsrQ)E!@*$w2Plh61y=>o(5>=4OS=_9OW#aR65$PJ}z3EEK4s zETVnM;B3L90;i#xPM;KWnXYIGPsXnrmzmTd@f6bu8HJ)h<~H4i-E`JVW(v>klyz=6 zblq@9U549qwlZ|diz_jY=^642OH4AINN*+PMQdNDEOwfr5kBRx|ue>5Yq=3pBR^8-VN30=||5OEYJRLa!s{d?*<6qLA)Di>mm&Bs*z!2 z@?s2b?>Wv(up7vHQk>z@yy6-!)mkhm@hsd9 zEc70swW9gE$%zz0bhNc;LIAv56k_|KqIenx3m4Z_ z(`kcQ_dw^dbHT}CziM@pU2v^9+xsGIK6*rwUV!Y9X$~Zeq*F&JNr6=*4S90^hxIBRU3f)qN6h4tF!*a}H*vIC6nhzjf4Ts_6fn1~(FS4+h5Is_O z$uMy`X{|!#6w5Hv#RW4>zkqv{n&}poCk*t{mM$}mm+|t=5)WdPr5SFdcbe%z(pSWj z@HsGLsW?Hmwusn467{{l)XJ)LEs@UN9(}a$k9WYk-D~vku0-pEowsy!Z3-J5>$Se* z>Z!AV)G_P3s(66feW>s@ncnEuVU2pB}>e>_#ui<*aj+=#fx@O>U53)ouPCIx=-nbri zR6oJaLP5t0eXqe|?B#WQxWi*RX1j`~+r;PdM98MCk< z)y|ykI8PQD!E+FuSvK)HMWSjPjpW2=N!SV|u>7wwWpNdb%-prWiuySDJVCIB0No^E zk$pExLB(pX!fMO3jtY8aj=t5I%*X_8Q*DwyY6BhS}wWGYpYxCQ=(S?o0kHTamtT7ZUytO_nR1lgyM zeE)|fqh@H^XJ55peRnUClKaKK$(fGX=S3}izah(VU&5N8dv1Vz20P|PTq|f>9GGTt z8(1UOgq6ME+C)k?%)2Auuvml32}R$ei{C)FF&~LyHm+qM_)Ftc2#7@DD#0~^PvF_6tgM?P z-Xi#v;4^~TaH^?-y$Ls)+9##SW%e&@OYF>Rvoo6yhfPMV*c^mjw/disks') +@login_required +def api_vm_disks(vm_name): + """Return disk list for a specific VM (from cache).""" + vm_list, error, _ = get_cached_vms( + session['host'], session['user'], session['password'], + no_verify_ssl=session.get('no_verify_ssl', False) + ) + for vm in vm_list: + if vm['name'] == vm_name: + return jsonify(vm.get('disks', [])) + return jsonify({'error': f'VM "{vm_name}" not found'}), 404 + + # ── Create Job ──────────────────────────────────────────────────────────────── @app.route('/jobs/create', methods=['GET', 'POST']) @@ -435,6 +456,14 @@ def create_job(): else: sched_time = '' + # disk_filter: None = all disks; list = selected disks only + disk_selection_shown = 'disk_selection_shown' in request.form + if disk_selection_shown: + raw_filter = request.form.getlist('disk_filter') + disk_filter = raw_filter if raw_filter else None + else: + disk_filter = None # disks not shown yet = backup all + jid = create_and_start_job( vm_name=vm_name, dest=dest, @@ -448,8 +477,10 @@ def create_job(): weekly_day=weekly_day, interval_hours=interval_hrs, label=label, + disk_filter=disk_filter, ) - flash(f'Job created successfully!', 'success') + n_disks = len(disk_filter) if disk_filter is not None else 'all' + flash(f'Job created — {n_disks} disk(s) selected.', 'success') return redirect(url_for('job_detail', jobid=jid)) # GET: load VM list for the dropdown diff --git a/vsphere_backup/templates/create_job.html b/vsphere_backup/templates/create_job.html index 7da9dec..8dc9f8f 100644 --- a/vsphere_backup/templates/create_job.html +++ b/vsphere_backup/templates/create_job.html @@ -135,7 +135,8 @@
- {% for vm in vms %}
+ + + +
📁 Destination
@@ -338,10 +360,98 @@ selectSchedule('daily'); {% endif %} - // Pre-fill dest from ?dest= query param (e.g. from NFS manager "Use as Target") + // Pre-fill dest from ?dest= query param const urlDest = new URLSearchParams(window.location.search).get('dest'); if (urlDest) document.getElementById('dest').value = urlDest; + // ── Disk Selection ────────────────────────────────────────────────────────── + function escHtml(s) { + return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); + } + + function onVmChange(vmName) { + const diskCard = document.getElementById('diskCard'); + if (!vmName) { + diskCard.style.display = 'none'; + document.getElementById('disk_selection_shown').value = ''; + return; + } + diskCard.style.display = ''; + document.getElementById('disk_selection_shown').value = '1'; + document.getElementById('diskLoader').style.display = 'block'; + document.getElementById('diskList').innerHTML = ''; + document.getElementById('diskTip').style.display = 'none'; + document.getElementById('diskCardBadge').textContent = ''; + + fetch('/api/vm/' + encodeURIComponent(vmName) + '/disks') + .then(r => r.json()) + .then(disks => { + document.getElementById('diskLoader').style.display = 'none'; + if (!Array.isArray(disks) || !disks.length) { + document.getElementById('diskList').innerHTML = + '
No virtual disks found on this VM.
'; + return; + } + + // Sort smallest first (OS disk is usually smallest) + disks.sort((a, b) => a.size_gb - b.size_gb); + + let html = ''; + disks.forEach((disk, i) => { + const sizeLabel = disk.size_gb >= 1000 + ? (disk.size_gb/1024).toFixed(1) + ' TB' + : disk.size_gb + ' GB'; + const sizeColor = disk.size_gb > 100 ? 'var(--warning)' : 'var(--success)'; + const hint = i === 0 + ? ' OS' + : ''; + html += `
+ + + ${sizeLabel} +
`; + }); + html += `
+ + + +
`; + + document.getElementById('diskList').innerHTML = html; + document.getElementById('diskTip').style.display = ''; + document.getElementById('diskCardBadge').textContent = + disks.length + ' disk' + (disks.length > 1 ? 's' : '') + ' found'; + }) + .catch(() => { + document.getElementById('diskLoader').style.display = 'none'; + document.getElementById('diskList').innerHTML = + '
⚠ Failed to load disk list
'; + }); + } + + function selectAllDisks(checked) { + document.querySelectorAll('input[name="disk_filter"]').forEach(cb => cb.checked = checked); + } + function selectOsOnly() { + // Smallest disk is first (sorted above) + const cbs = document.querySelectorAll('input[name="disk_filter"]'); + cbs.forEach((cb, i) => { cb.checked = (i === 0); }); + } + + // Auto-trigger disk load if VM pre-selected (from ?vm=) + const initVm = document.getElementById('vm_name').value; + if (initVm) onVmChange(initVm); + // Load NFS mounts for quick-select fetch('/api/nfs') .then(r => r.json()) diff --git a/vsphere_backup/templates/job_detail.html b/vsphere_backup/templates/job_detail.html index 37e9ff8..46c0cba 100644 --- a/vsphere_backup/templates/job_detail.html +++ b/vsphere_backup/templates/job_detail.html @@ -184,6 +184,18 @@ {% if job.sftp_host %} · 📤 SFTP: {{ job.sftp_host }}{% endif %}
+
+
Disks
+
+ {% if job.disks_count is none %} + All disks + {% elif job.disks_count == 0 %} + VMX only (0 disks) + {% else %} + {{ job.disks_count }} disk{{ 's' if job.disks_count != 1 else '' }} selected + {% endif %} +
+
{% if job.schedule_id %}