vSphere-Backup-Manager/vsphere_backup/templates/create_job.html

369 lines
15 KiB
HTML

{% extends "base.html" %}
{% set active_page = 'create_job' %}
{% block title %}Create Backup Job — vSphere Backup Manager{% endblock %}
{% block head %}
<style>
.wizard-wrap { max-width: 720px; }
.wizard-steps {
display: flex; align-items: center; gap: 0;
margin-bottom: 32px; counter-reset: step;
}
.step {
display: flex; align-items: center; gap: 12px;
flex: 1; position: relative;
}
.step:not(:last-child)::after {
content: '';
flex: 1; height: 2px;
background: var(--border);
margin: 0 16px;
border-radius: 2px;
}
.step.done:not(:last-child)::after { background: var(--accent); }
.step-num {
width: 32px; height: 32px; border-radius: 50%;
border: 2px solid var(--border);
display: flex; align-items: center; justify-content: center;
font-size: 13px; font-weight: 700; flex-shrink: 0;
color: var(--text-muted);
transition: all 0.25s ease;
}
.step.active .step-num { border-color: var(--accent); color: #ffffff; background: var(--accent); box-shadow: 0 0 12px var(--accent-glow); }
.step.done .step-num { border-color: var(--accent-2); background: var(--accent-2); color: #ffffff; box-shadow: 0 0 12px rgba(6, 182, 212, 0.25); }
.step-label { font-size: 13.5px; font-weight: 600; color: var(--text-muted); }
.step.active .step-label { color: var(--text-primary); }
.section-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
margin-bottom: 20px;
overflow: hidden;
box-shadow: var(--shadow);
backdrop-filter: blur(8px);
}
.section-card-header {
padding: 16px 24px;
border-bottom: 1px solid var(--border);
background: rgba(255,255,255,0.01);
display: flex; align-items: center; gap: 12px;
font-size: 14.5px; font-weight: 700;
letter-spacing: -0.01em;
}
.section-card-body { padding: 24px; }
.schedule-options {
display: grid; grid-template-columns: repeat(2, 1fr);
gap: 12px; margin-bottom: 20px;
}
.schedule-opt {
border: 1.5px solid var(--border);
border-radius: var(--radius-sm);
padding: 16px 20px;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
display: flex; align-items: flex-start; gap: 12px;
background: rgba(255, 255, 255, 0.01);
}
.schedule-opt:hover { border-color: var(--border-bright); background: var(--bg-card-hover); transform: translateY(-1px); }
.schedule-opt.selected { border-color: var(--accent); background: rgba(99, 102, 241, 0.08); box-shadow: 0 4px 12px rgba(99, 102, 241, 0.05); }
.schedule-opt input[type=radio] { display: none; }
.schedule-opt-icon { font-size: 22px; }
.schedule-opt-title { font-size: 14.5px; font-weight: 700; }
.schedule-opt-desc { font-size: 12px; color: var(--text-muted); margin-top: 3px; font-weight: 500; }
.schedule-detail { display: none; }
.schedule-detail.visible { display: block; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.sftp-toggle-btn {
background: none; border: none; cursor: pointer;
color: var(--accent-2); font-size: 13.5px; font-weight: 600;
display: inline-flex; align-items: center; gap: 8px;
padding: 4px 8px; margin-top: 8px;
transition: color 0.2s;
border-radius: var(--radius-sm);
}
.sftp-toggle-btn:hover {
color: var(--accent);
background: rgba(255,255,255,0.02);
}
.sftp-section { display: none; margin-top: 20px; }
.sftp-section.visible { display: block; }
.action-bar {
display: flex; gap: 14px; align-items: center;
padding: 24px 0 0;
}
</style>
{% endblock %}
{% block content %}
<div class="topbar">
<div>
<div class="topbar-title">Create Backup Job</div>
<div class="topbar-subtitle">Configure a new backup job for a virtual machine</div>
</div>
</div>
<div class="content">
<div class="wizard-wrap">
<!-- Progress steps -->
<div class="wizard-steps">
<div class="step done">
<div class="step-num"></div>
<div class="step-label">Connected</div>
</div>
<div class="step active">
<div class="step-num">2</div>
<div class="step-label">Configure Job</div>
</div>
<div class="step">
<div class="step-num">3</div>
<div class="step-label">Review & Run</div>
</div>
</div>
<form method="post" action="/jobs/create" id="jobForm">
<!-- VM Selection -->
<div class="section-card">
<div class="section-card-header">🖥 Virtual Machine</div>
<div class="section-card-body">
<div class="form-group" style="margin:0;">
<label class="form-label" for="vm_name">Select VM to back up</label>
<select id="vm_name" name="vm_name" class="form-control" required>
<option value="">— Choose a VM —</option>
{% for vm in vms %}
<option value="{{ vm.name }}"
{% if vm.name == selected_vm %}selected{% endif %}>
{{ vm.name }}
{% if vm.power_state == 'poweredOn' %}🟢{% elif vm.power_state == 'poweredOff' %}🔴{% else %}🟡{% endif %}
({{ vm.guest_os[:30] if vm.guest_os else 'Unknown' }})
</option>
{% endfor %}
</select>
</div>
</div>
</div>
<!-- Destination -->
<div class="section-card">
<div class="section-card-header">📁 Destination</div>
<div class="section-card-body">
<!-- NFS quick-select -->
<div id="nfsTargets" style="margin-bottom:14px; display:none;">
<div class="form-label" style="margin-bottom:6px;">📡 NFS Mounts (click to use as destination)</div>
<div id="nfsMountList" style="display:flex; gap:8px; flex-wrap:wrap;"></div>
</div>
<div class="form-group">
<label class="form-label" for="dest">Local backup path</label>
<input id="dest" class="form-control" type="text" name="dest"
value="./backups" placeholder="e.g. /mnt/nfs-backup or /data/vmbackups" required />
</div>
<div class="form-check">
<input type="checkbox" id="compress" name="compress" />
<label for="compress">Compress with zstd (smaller files, slower)</label>
</div>
<div class="form-check">
<input type="checkbox" id="no_verify_ssl" name="no_verify_ssl"
{% if session.get('no_verify_ssl') %}checked{% endif %} />
<label for="no_verify_ssl">Skip SSL certificate verification</label>
</div>
<!-- SFTP Toggle -->
<button type="button" class="sftp-toggle-btn" onclick="toggleSFTP()">
<span id="sftpToggleIcon"></span>
Upload to SFTP server (optional)
</button>
<div class="sftp-section" id="sftpSection">
<div class="form-row" style="margin-top:14px;">
<div class="form-group" style="margin:0;">
<label class="form-label" for="sftp_host">SFTP Host</label>
<input id="sftp_host" class="form-control" type="text" name="sftp_host" placeholder="sftp.example.com" />
</div>
<div class="form-group" style="margin:0;">
<label class="form-label" for="sftp_user">SFTP Username</label>
<input id="sftp_user" class="form-control" type="text" name="sftp_user" placeholder="backupuser" />
</div>
</div>
<div class="form-group" style="margin-top:12px;">
<label class="form-label" for="sftp_password">SFTP Password</label>
<input id="sftp_password" class="form-control" type="password" name="sftp_password" placeholder="••••••••" />
</div>
</div>
</div>
</div>
<!-- Schedule -->
<div class="section-card">
<div class="section-card-header">🕐 Schedule</div>
<div class="section-card-body">
<div class="schedule-options">
<label class="schedule-opt {% if not show_schedule %}selected{% endif %}" id="opt-now" onclick="selectSchedule('now')">
<input type="radio" name="schedule_type" value="now" {% if not show_schedule %}checked{% endif %} />
<div>
<div class="schedule-opt-icon"></div>
<div class="schedule-opt-title">Run Now</div>
<div class="schedule-opt-desc">Start the backup immediately</div>
</div>
</label>
<label class="schedule-opt {% if show_schedule %}selected{% endif %}" id="opt-daily" onclick="selectSchedule('daily')">
<input type="radio" name="schedule_type" value="daily" {% if show_schedule %}checked{% endif %}/>
<div>
<div class="schedule-opt-icon">📅</div>
<div class="schedule-opt-title">Daily</div>
<div class="schedule-opt-desc">Repeat every day at a set time</div>
</div>
</label>
<label class="schedule-opt" id="opt-weekly" onclick="selectSchedule('weekly')">
<input type="radio" name="schedule_type" value="weekly" />
<div>
<div class="schedule-opt-icon">🗓</div>
<div class="schedule-opt-title">Weekly</div>
<div class="schedule-opt-desc">Repeat every week on a specific day</div>
</div>
</label>
<label class="schedule-opt" id="opt-interval" onclick="selectSchedule('interval')">
<input type="radio" name="schedule_type" value="interval" />
<div>
<div class="schedule-opt-icon">🔁</div>
<div class="schedule-opt-title">Interval</div>
<div class="schedule-opt-desc">Repeat every N hours</div>
</div>
</label>
</div>
<!-- Daily detail -->
<div class="schedule-detail {% if show_schedule %}visible{% endif %}" id="detail-daily">
<div class="form-row">
<div class="form-group" style="margin:0;">
<label class="form-label" for="daily_time">Time (24h)</label>
<input id="daily_time" class="form-control" type="time" name="daily_time" value="02:00" />
</div>
</div>
</div>
<!-- Weekly detail -->
<div class="schedule-detail" id="detail-weekly">
<div class="form-row">
<div class="form-group" style="margin:0;">
<label class="form-label" for="weekly_day">Day of Week</label>
<select id="weekly_day" class="form-control" name="weekly_day">
<option value="0">Monday</option>
<option value="1">Tuesday</option>
<option value="2">Wednesday</option>
<option value="3">Thursday</option>
<option value="4">Friday</option>
<option value="5">Saturday</option>
<option value="6">Sunday</option>
</select>
</div>
<div class="form-group" style="margin:0;">
<label class="form-label" for="weekly_time">Time (24h)</label>
<input id="weekly_time" class="form-control" type="time" name="weekly_time" value="02:00" />
</div>
</div>
</div>
<!-- Interval detail -->
<div class="schedule-detail" id="detail-interval">
<div class="form-row">
<div class="form-group" style="margin:0;">
<label class="form-label" for="interval_hours">Every (hours)</label>
<input id="interval_hours" class="form-control" type="number"
name="interval_hours" value="24" min="1" max="8760" />
</div>
</div>
</div>
<!-- Job label -->
<div class="form-group" style="margin-top: 18px; margin-bottom:0">
<label class="form-label" for="job_label">Job label (optional)</label>
<input id="job_label" class="form-control" type="text" name="job_label"
placeholder="e.g. Nightly web-server backup" />
</div>
</div>
</div>
<div class="action-bar">
<button type="submit" id="submitBtn" class="btn btn-primary">
<span id="submitText">🚀 Create Job</span>
<span id="submitSpinner" class="spinner" style="display:none;"></span>
</button>
<a href="/vms" class="btn btn-ghost">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function selectSchedule(type) {
['now','daily','weekly','interval'].forEach(t => {
document.getElementById('opt-' + t).classList.remove('selected');
const d = document.getElementById('detail-' + t);
if (d) d.classList.remove('visible');
});
document.getElementById('opt-' + type).classList.add('selected');
document.getElementById('opt-' + type).querySelector('input').checked = true;
const detail = document.getElementById('detail-' + type);
if (detail) detail.classList.add('visible');
}
function toggleSFTP() {
const sec = document.getElementById('sftpSection');
const ico = document.getElementById('sftpToggleIcon');
sec.classList.toggle('visible');
ico.textContent = sec.classList.contains('visible') ? '▼' : '▶';
}
document.getElementById('jobForm').addEventListener('submit', function() {
document.getElementById('submitText').textContent = 'Starting…';
document.getElementById('submitSpinner').style.display = 'inline-block';
document.getElementById('submitBtn').disabled = true;
});
{% if show_schedule %}
selectSchedule('daily');
{% endif %}
// Pre-fill dest from ?dest= query param (e.g. from NFS manager "Use as Target")
const urlDest = new URLSearchParams(window.location.search).get('dest');
if (urlDest) document.getElementById('dest').value = urlDest;
// Load NFS mounts for quick-select
fetch('/api/nfs')
.then(r => r.json())
.then(mounts => {
if (!mounts || !mounts.length) return;
const wrap = document.getElementById('nfsTargets');
const list = document.getElementById('nfsMountList');
mounts.forEach(m => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn btn-secondary btn-sm';
btn.innerHTML = `📡 ${m.mountpoint} <span style="color:var(--text-muted);font-size:11px;margin-left:4px;">${m.free_gb}GB free</span>`;
btn.onclick = () => {
document.getElementById('dest').value = m.mountpoint;
list.querySelectorAll('button').forEach(b => b.style.borderColor = '');
btn.style.borderColor = 'var(--accent)';
};
list.appendChild(btn);
});
wrap.style.display = '';
})
.catch(() => {});
</script>
{% endblock %}