|
|
|
|
@@ -32,47 +32,6 @@
|
|
|
|
|
</section>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<section class="grid" aria-label="Host summary">
|
|
|
|
|
<div class="metric"><div class="label">Snapshots</div><div class="value">{{ counts.snapshots }}</div></div>
|
|
|
|
|
<div class="metric"><div class="label">Runs</div><div class="value">{{ counts.runs }}</div></div>
|
|
|
|
|
<div class="metric"><div class="label">Queued</div><div class="value">{{ counts.queued_runs }}</div></div>
|
|
|
|
|
<div class="metric"><div class="label">Running</div><div class="value">{{ counts.running_runs }}</div></div>
|
|
|
|
|
<div class="metric"><div class="label">Failed Runs</div><div class="value">{{ counts.failed_runs }}</div></div>
|
|
|
|
|
<div class="metric"><div class="label">Incomplete</div><div class="value">{{ counts.incomplete_snapshots }}</div></div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<div class="two-col">
|
|
|
|
|
<section class="panel">
|
|
|
|
|
<h2>Config</h2>
|
|
|
|
|
<div class="stack">
|
|
|
|
|
<div><strong>Address:</strong> {{ host.address }}</div>
|
|
|
|
|
<div><strong>Enabled:</strong> {{ host.enabled|yesno:"yes,no" }}</div>
|
|
|
|
|
<div><strong>SSH key:</strong> {{ host.ssh_credential|default:"global default" }}</div>
|
|
|
|
|
<div><strong>SSH:</strong> {{ host.ssh_user|default:"global" }}{% if host.ssh_port %}:{{ host.ssh_port }}{% endif %}</div>
|
|
|
|
|
<div><strong>Backup source:</strong> {{ host.source_root|default:"global default" }}</div>
|
|
|
|
|
<div><strong>Retention:</strong> daily {{ host.retention_daily }}, weekly {{ host.retention_weekly }}, monthly {{ host.retention_monthly }}, yearly {{ host.retention_yearly }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="panel">
|
|
|
|
|
<h2>Schedule</h2>
|
|
|
|
|
{% if schedule %}
|
|
|
|
|
<div class="stack">
|
|
|
|
|
<div><strong>Schedule expression:</strong> {{ schedule.cron_expr }}</div>
|
|
|
|
|
<div class="muted">Evaluated by the pobsync scheduler service.</div>
|
|
|
|
|
<div><strong>Enabled:</strong> {{ schedule.enabled|yesno:"yes,no" }}</div>
|
|
|
|
|
<div><strong>Next run:</strong> {% if next_run_at %}{{ next_run_at|date:"Y-m-d H:i T" }} <span class="muted">{{ scheduler_timezone }}</span>{% endif %}</div>
|
|
|
|
|
<div><strong>Prune:</strong> {{ schedule.prune|yesno:"yes,no" }}</div>
|
|
|
|
|
<div><strong>Last status:</strong> {{ schedule.last_status|default:"" }}</div>
|
|
|
|
|
<div><strong>Last started:</strong> {{ schedule.last_started_at|default:"" }}</div>
|
|
|
|
|
<div><strong>Last finished:</strong> {{ schedule.last_finished_at|default:"" }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% else %}
|
|
|
|
|
<p class="muted">No schedule configured.</p>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% if retention_warning.has_warning %}
|
|
|
|
|
<section class="panel highlight warning">
|
|
|
|
|
<h2>Retention Warnings</h2>
|
|
|
|
|
@@ -101,60 +60,137 @@
|
|
|
|
|
</section>
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
|
|
{% if effective_config %}
|
|
|
|
|
<section class="panel">
|
|
|
|
|
<h2>Effective Config</h2>
|
|
|
|
|
<div class="two-col">
|
|
|
|
|
<div class="stack">
|
|
|
|
|
<div><strong>Backup source:</strong> {{ effective_config.source_root }}</div>
|
|
|
|
|
<div><strong>Destination subdir:</strong> {{ effective_config.destination_subdir|default:"none" }}</div>
|
|
|
|
|
<div><strong>SSH:</strong> {{ effective_config.ssh.user }}@{{ host.address }}:{{ effective_config.ssh.port }}</div>
|
|
|
|
|
<div><strong>SSH key:</strong> {{ effective_config.ssh.credential|default:"none selected" }}</div>
|
|
|
|
|
<div><strong>SSH options:</strong> {{ effective_config.ssh.options|join:" " }}</div>
|
|
|
|
|
<div><strong>Rsync binary:</strong> {{ effective_config.rsync.binary }}</div>
|
|
|
|
|
<div><strong>Rsync args:</strong> {{ effective_config.rsync.args|join:" " }}</div>
|
|
|
|
|
<div><strong>Timeout:</strong> {{ effective_config.rsync.timeout_seconds }}s</div>
|
|
|
|
|
<div><strong>Bandwidth limit:</strong> {{ effective_config.rsync.bwlimit_kbps }} KB/s</div>
|
|
|
|
|
<section class="host-control-grid" aria-label="Host control workspace">
|
|
|
|
|
<article class="panel host-control-panel">
|
|
|
|
|
<h2>Host Status</h2>
|
|
|
|
|
<div class="host-control-primary">
|
|
|
|
|
<div>
|
|
|
|
|
<strong>Retention:</strong>
|
|
|
|
|
d{{ effective_config.retention.daily }}
|
|
|
|
|
w{{ effective_config.retention.weekly }}
|
|
|
|
|
m{{ effective_config.retention.monthly }}
|
|
|
|
|
y{{ effective_config.retention.yearly }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stack">
|
|
|
|
|
<div><strong>Includes:</strong> {{ effective_config.includes|length }}</div>
|
|
|
|
|
{% if effective_config.includes %}
|
|
|
|
|
<pre>{{ effective_config.includes|join:" " }}</pre>
|
|
|
|
|
{% if host.enabled %}
|
|
|
|
|
<span class="status ok">enabled</span>
|
|
|
|
|
{% else %}
|
|
|
|
|
<div class="muted">No include rules configured.</div>
|
|
|
|
|
<span class="status failed">disabled</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
<div><strong>Excludes:</strong> {{ effective_config.excludes|length }}</div>
|
|
|
|
|
{% if effective_config.excludes %}
|
|
|
|
|
<pre>{{ effective_config.excludes|join:" " }}</pre>
|
|
|
|
|
<span class="muted">{{ host.address }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
{% if active_run %}
|
|
|
|
|
<a class="status-summary {{ active_run.status }}" href="{% url 'run_detail' active_run.id %}">
|
|
|
|
|
<span class="status {{ active_run.status }}">{{ active_run.status }}</span>
|
|
|
|
|
<strong>Run {{ active_run.id }} is active.</strong>
|
|
|
|
|
</a>
|
|
|
|
|
{% elif counts.failed_runs %}
|
|
|
|
|
<a class="status-summary failed" href="{% url 'runs_list' %}?host={{ host.host }}&status=failed&review=needed">
|
|
|
|
|
<span class="status failed">failed</span>
|
|
|
|
|
<strong>{{ counts.failed_runs }} failed run{{ counts.failed_runs|pluralize }} need review.</strong>
|
|
|
|
|
</a>
|
|
|
|
|
{% elif retention_warning.has_warning %}
|
|
|
|
|
<span class="status-summary warning">
|
|
|
|
|
<span class="status warning">warning</span>
|
|
|
|
|
<strong>Retention needs attention.</strong>
|
|
|
|
|
</span>
|
|
|
|
|
{% else %}
|
|
|
|
|
<div class="muted">No exclude rules configured.</div>
|
|
|
|
|
<span class="status-summary success">
|
|
|
|
|
<span class="status ok">ok</span>
|
|
|
|
|
<strong>No active blockers for this host.</strong>
|
|
|
|
|
</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="host-control-meta">
|
|
|
|
|
<div><span class="label">Snapshots</span><strong>{{ counts.snapshots }}</strong></div>
|
|
|
|
|
<div><span class="label">Runs</span><strong>{{ counts.runs }}</strong></div>
|
|
|
|
|
<div><span class="label">Incomplete</span><strong>{{ counts.incomplete_snapshots }}</strong></div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
<section class="panel">
|
|
|
|
|
<h2>Snapshot Discovery</h2>
|
|
|
|
|
<div class="stack">
|
|
|
|
|
<div><strong>Backup root:</strong> {{ discovery.backup_root|default:"" }}</div>
|
|
|
|
|
<div><strong>Host root:</strong> {{ discovery.host_root|default:"" }}</div>
|
|
|
|
|
<div><strong>Status:</strong> {{ discovery.message }}</div>
|
|
|
|
|
{% if discovery.kind_counts %}
|
|
|
|
|
<div><strong>On disk:</strong>
|
|
|
|
|
scheduled {{ discovery.kind_counts.scheduled|default:0 }},
|
|
|
|
|
manual {{ discovery.kind_counts.manual|default:0 }},
|
|
|
|
|
incomplete {{ discovery.kind_counts.incomplete|default:0 }}
|
|
|
|
|
</div>
|
|
|
|
|
<article class="panel host-control-panel">
|
|
|
|
|
<h2>Backup Control</h2>
|
|
|
|
|
<div class="operator-state">
|
|
|
|
|
{% if active_run %}
|
|
|
|
|
<span class="status {{ active_run.status }}">{{ active_run.status }}</span>
|
|
|
|
|
<a href="{% url 'run_detail' active_run.id %}">Run {{ active_run.id }}</a>
|
|
|
|
|
{% elif has_global_config and host.enabled %}
|
|
|
|
|
<span class="status {{ backup_gate.state }}">{{ backup_gate.state }}</span>
|
|
|
|
|
<span class="muted">{{ backup_gate.message }}</span>
|
|
|
|
|
{% elif not host.enabled %}
|
|
|
|
|
<span class="status failed">disabled</span>
|
|
|
|
|
{% elif not has_global_config %}
|
|
|
|
|
<span class="status failed">missing global config</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
<section class="actions inline" aria-label="Quick backup actions">
|
|
|
|
|
<form class="inline-form" method="post" action="{% url 'queue_manual_backup' host.host %}">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<input type="hidden" name="dry_run" value="on">
|
|
|
|
|
<input type="hidden" name="verbose_output" value="on">
|
|
|
|
|
<input type="hidden" name="prune_max_delete" value="10">
|
|
|
|
|
<button type="submit" class="secondary" {% if not can_queue_dry_run %}disabled{% endif %}>Queue dry-run</button>
|
|
|
|
|
</form>
|
|
|
|
|
<form class="inline-form" method="post" action="{% url 'queue_manual_backup' host.host %}">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<input type="hidden" name="prune_max_delete" value="10">
|
|
|
|
|
<button type="submit" {% if not can_queue_real_backup %}disabled{% endif %}>Queue backup</button>
|
|
|
|
|
</form>
|
|
|
|
|
</section>
|
|
|
|
|
{% if active_run %}
|
|
|
|
|
<p class="muted">Wait for the active run to finish, or cancel it from the run detail page.</p>
|
|
|
|
|
{% elif not can_queue_dry_run or not can_queue_real_backup %}
|
|
|
|
|
{% if not has_global_config %}
|
|
|
|
|
<p class="muted">Create the default global config before queueing backups.</p>
|
|
|
|
|
{% elif not host.enabled %}
|
|
|
|
|
<p class="muted">Enable this host before queueing backups.</p>
|
|
|
|
|
{% elif backup_gate.real_blockers %}
|
|
|
|
|
<p class="muted">Real backups are blocked by failed preflight checks. Dry-runs may still be available when storage-only checks fail.</p>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% endif %}
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
<article class="panel host-control-panel">
|
|
|
|
|
<h2>Schedule <a class="button-link secondary compact" href="{% url 'edit_host_schedule' host.host %}">Edit</a></h2>
|
|
|
|
|
{% if schedule %}
|
|
|
|
|
<div class="host-control-meta">
|
|
|
|
|
<div><span class="label">Schedule expression</span><strong>{{ schedule.cron_expr }}</strong></div>
|
|
|
|
|
<div><span class="label">Next run</span><strong>{% if next_run_at %}{{ next_run_at|date:"Y-m-d H:i T" }}{% else %}none{% endif %}</strong></div>
|
|
|
|
|
<div><span class="label">Timezone</span><strong>{{ scheduler_timezone }}</strong></div>
|
|
|
|
|
<div><span class="label">Prune</span><strong>{{ schedule.prune|yesno:"yes,no" }}</strong></div>
|
|
|
|
|
<div><span class="label">Last status</span><strong>{{ schedule.last_status|default:"none" }}</strong></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="muted">Evaluated by the pobsync scheduler service.</p>
|
|
|
|
|
{% else %}
|
|
|
|
|
<p class="muted">No schedule configured.</p>
|
|
|
|
|
<a class="button-link secondary compact" href="{% url 'edit_host_schedule' host.host %}">Add schedule</a>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
<article class="panel host-control-panel">
|
|
|
|
|
<h2>Current Activity</h2>
|
|
|
|
|
{% if latest_runs %}
|
|
|
|
|
{% with run=latest_runs.0 %}
|
|
|
|
|
<a class="activity-row" href="{% url 'run_detail' run.id %}">
|
|
|
|
|
<span class="status {{ run.status }}">{{ run.status }}</span>
|
|
|
|
|
<span>
|
|
|
|
|
<strong>Run {{ run.id }}</strong>
|
|
|
|
|
<span class="muted">{{ run.run_type }} · {{ run.started_at|default:run.created_at }}</span>
|
|
|
|
|
</span>
|
|
|
|
|
</a>
|
|
|
|
|
{% endwith %}
|
|
|
|
|
{% else %}
|
|
|
|
|
<p class="muted">No backup runs recorded for this host.</p>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if stats_summary.latest_run.duration_seconds is not None %}
|
|
|
|
|
<div class="host-control-meta">
|
|
|
|
|
<div><span class="label">Latest duration</span><strong>{{ stats_summary.latest_run.duration_seconds }}s</strong></div>
|
|
|
|
|
<div><span class="label">New data</span><strong>{{ stats_summary.latest_run.rsync.literal_data_bytes|filesizeformat }}</strong></div>
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</article>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="grid" aria-label="Host summary">
|
|
|
|
|
<div class="metric"><div class="label">Snapshots</div><div class="value">{{ counts.snapshots }}</div></div>
|
|
|
|
|
<div class="metric"><div class="label">Runs</div><div class="value">{{ counts.runs }}</div></div>
|
|
|
|
|
<div class="metric"><div class="label">Queued</div><div class="value">{{ counts.queued_runs }}</div></div>
|
|
|
|
|
<div class="metric"><div class="label">Running</div><div class="value">{{ counts.running_runs }}</div></div>
|
|
|
|
|
<div class="metric"><div class="label">Failed Runs</div><div class="value">{{ counts.failed_runs }}</div></div>
|
|
|
|
|
<div class="metric"><div class="label">Incomplete</div><div class="value">{{ counts.incomplete_snapshots }}</div></div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
{% if stats_summary.runs %}
|
|
|
|
|
@@ -268,50 +304,94 @@
|
|
|
|
|
</section>
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
|
|
<div class="two-col">
|
|
|
|
|
<section class="panel">
|
|
|
|
|
<h2>Backup Control</h2>
|
|
|
|
|
<div class="operator-state">
|
|
|
|
|
{% if active_run %}
|
|
|
|
|
<span class="status {{ active_run.status }}">{{ active_run.status }}</span>
|
|
|
|
|
<a href="{% url 'run_detail' active_run.id %}">Run {{ active_run.id }}</a>
|
|
|
|
|
{% elif has_global_config and host.enabled %}
|
|
|
|
|
<span class="status {{ backup_gate.state }}">{{ backup_gate.state }}</span>
|
|
|
|
|
<span class="muted">{{ backup_gate.message }}</span>
|
|
|
|
|
{% elif not host.enabled %}
|
|
|
|
|
<span class="status failed">disabled</span>
|
|
|
|
|
{% elif not has_global_config %}
|
|
|
|
|
<span class="status failed">missing global config</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
<h2>Configuration</h2>
|
|
|
|
|
<div class="host-control-meta">
|
|
|
|
|
<div><span class="label">Address</span><strong>{{ host.address }}</strong></div>
|
|
|
|
|
<div><span class="label">SSH key</span><strong>{{ host.ssh_credential|default:"global default" }}</strong></div>
|
|
|
|
|
<div><span class="label">SSH</span><strong>{{ host.ssh_user|default:"global" }}{% if host.ssh_port %}:{{ host.ssh_port }}{% endif %}</strong></div>
|
|
|
|
|
<div><span class="label">Backup source</span><strong>{{ host.source_root|default:"global default" }}</strong></div>
|
|
|
|
|
<div><span class="label">Retention</span><strong>d{{ host.retention_daily }} w{{ host.retention_weekly }} m{{ host.retention_monthly }} y{{ host.retention_yearly }}</strong></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="actions inline">
|
|
|
|
|
<a class="button-link secondary compact" href="{% url 'edit_host_config' host.host %}">Edit config</a>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<section class="actions inline" aria-label="Quick backup actions">
|
|
|
|
|
<form class="inline-form" method="post" action="{% url 'queue_manual_backup' host.host %}">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<input type="hidden" name="dry_run" value="on">
|
|
|
|
|
<input type="hidden" name="verbose_output" value="on">
|
|
|
|
|
<input type="hidden" name="prune_max_delete" value="10">
|
|
|
|
|
<button type="submit" class="secondary" {% if not can_queue_dry_run %}disabled{% endif %}>Queue dry-run</button>
|
|
|
|
|
</form>
|
|
|
|
|
<form class="inline-form" method="post" action="{% url 'queue_manual_backup' host.host %}">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<input type="hidden" name="prune_max_delete" value="10">
|
|
|
|
|
<button type="submit" {% if not can_queue_real_backup %}disabled{% endif %}>Queue backup</button>
|
|
|
|
|
</form>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
{% if active_run %}
|
|
|
|
|
<p class="muted">Wait for the active run to finish, or cancel it from the run detail page.</p>
|
|
|
|
|
{% elif not can_queue_dry_run or not can_queue_real_backup %}
|
|
|
|
|
{% if not has_global_config %}
|
|
|
|
|
<p class="muted">Create the default global config before queueing backups.</p>
|
|
|
|
|
{% elif not host.enabled %}
|
|
|
|
|
<p class="muted">Enable this host before queueing backups.</p>
|
|
|
|
|
{% elif backup_gate.real_blockers %}
|
|
|
|
|
<p class="muted">Real backups are blocked by failed preflight checks. Dry-runs may still be available when storage-only checks fail.</p>
|
|
|
|
|
<section class="panel">
|
|
|
|
|
<h2>Snapshot Storage</h2>
|
|
|
|
|
<div class="host-control-meta">
|
|
|
|
|
<div><span class="label">Backup root</span><strong>{{ discovery.backup_root|default:"" }}</strong></div>
|
|
|
|
|
<div><span class="label">Host root</span><strong>{{ discovery.host_root|default:"" }}</strong></div>
|
|
|
|
|
<div><span class="label">Status</span><strong>{{ discovery.message }}</strong></div>
|
|
|
|
|
{% if discovery.kind_counts %}
|
|
|
|
|
<div>
|
|
|
|
|
<span class="label">On disk</span>
|
|
|
|
|
<strong>
|
|
|
|
|
scheduled {{ discovery.kind_counts.scheduled|default:0 }},
|
|
|
|
|
manual {{ discovery.kind_counts.manual|default:0 }},
|
|
|
|
|
incomplete {{ discovery.kind_counts.incomplete|default:0 }}
|
|
|
|
|
</strong>
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="actions inline">
|
|
|
|
|
<form method="post" action="{% url 'discover_host_snapshots' host.host %}">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<button type="submit" class="secondary compact">Discover snapshots</button>
|
|
|
|
|
</form>
|
|
|
|
|
<form method="post" action="{% url 'prepare_host_directories' host.host %}">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<button type="submit" class="secondary compact">Prepare directories</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% if effective_config %}
|
|
|
|
|
<section class="panel">
|
|
|
|
|
<h2>Effective Config</h2>
|
|
|
|
|
<div class="two-col">
|
|
|
|
|
<div class="stack">
|
|
|
|
|
<div><strong>Backup source:</strong> {{ effective_config.source_root }}</div>
|
|
|
|
|
<div><strong>Destination subdir:</strong> {{ effective_config.destination_subdir|default:"none" }}</div>
|
|
|
|
|
<div><strong>SSH:</strong> {{ effective_config.ssh.user }}@{{ host.address }}:{{ effective_config.ssh.port }}</div>
|
|
|
|
|
<div><strong>SSH key:</strong> {{ effective_config.ssh.credential|default:"none selected" }}</div>
|
|
|
|
|
<div><strong>SSH options:</strong> {{ effective_config.ssh.options|join:" " }}</div>
|
|
|
|
|
<div><strong>Rsync binary:</strong> {{ effective_config.rsync.binary }}</div>
|
|
|
|
|
<div><strong>Rsync args:</strong> {{ effective_config.rsync.args|join:" " }}</div>
|
|
|
|
|
<div><strong>Timeout:</strong> {{ effective_config.rsync.timeout_seconds }}s</div>
|
|
|
|
|
<div><strong>Bandwidth limit:</strong> {{ effective_config.rsync.bwlimit_kbps }} KB/s</div>
|
|
|
|
|
<div>
|
|
|
|
|
<strong>Retention:</strong>
|
|
|
|
|
d{{ effective_config.retention.daily }}
|
|
|
|
|
w{{ effective_config.retention.weekly }}
|
|
|
|
|
m{{ effective_config.retention.monthly }}
|
|
|
|
|
y{{ effective_config.retention.yearly }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stack">
|
|
|
|
|
<div><strong>Includes:</strong> {{ effective_config.includes|length }}</div>
|
|
|
|
|
{% if effective_config.includes %}
|
|
|
|
|
<pre>{{ effective_config.includes|join:" " }}</pre>
|
|
|
|
|
{% else %}
|
|
|
|
|
<div class="muted">No include rules configured.</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
<div><strong>Excludes:</strong> {{ effective_config.excludes|length }}</div>
|
|
|
|
|
{% if effective_config.excludes %}
|
|
|
|
|
<pre>{{ effective_config.excludes|join:" " }}</pre>
|
|
|
|
|
{% else %}
|
|
|
|
|
<div class="muted">No exclude rules configured.</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
|
|
<h3>Advanced Options</h3>
|
|
|
|
|
<section class="panel">
|
|
|
|
|
<h2>Backup Options</h2>
|
|
|
|
|
<p class="muted">Use this when the quick actions above need a custom label, include/exclude override, or prune limit.</p>
|
|
|
|
|
<form method="post" action="{% url 'queue_manual_backup' host.host %}" class="form-grid">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
{{ manual_backup_form.non_field_errors }}
|
|
|
|
|
|