{% extends "base.html" %} {% from "_macros.html" import help %} {% block title %}{{ client.display_name }} — BackupWatch{% endblock %} {% block main_class %}cd2{% endblock %} {% block content %} {# onboarding_state is written by the onboarding workflow; older rows may not carry one, so every use is guarded and falls back to the raw value rather than assuming the vocabulary. #} {% set STATE_LABELS = {'draft': 'Draft', 'observing': 'Observing', 'monitoring': 'Monitoring'} %} ← Clients

{{ client.display_name }}

{% if client.active %}Active{% else %}Paused{% endif %}
View 30-day report Download CSV {% if user.role in ['admin', 'tech'] %}{% endif %}
{% if err %}{% endif %} {% if msg %}{% endif %} {% if user.role in ['admin', 'tech'] %} {% endif %}

30-day history

latest run per job, per day · click a day for details
success warning failure missed
{% if chart_svg %}
{{ chart_svg|safe }}
Todaynewest ←  ·  → oldest{{ hist_end|date }}
{% else %}
No backup history yet — events will appear here once this client's jobs report in.
{% endif %}

Jobs

{% set active_jobs = client.jobs|selectattr('active')|list|length %} {{ client.jobs|length }} job{{ '' if client.jobs|length == 1 else 's' }}{% if client.jobs %} · {{ 'all active' if active_jobs == client.jobs|length else (active_jobs ~ ' active') }}{% endif %} {% if user.role in ['admin', 'tech'] %}{% endif %}
NAMEVENDORTARGETSCHEDULELAST STATUSSTATUS
{% for j in client.jobs %}
▸ {{ j.name }} {%- if j.auto_created %}auto{% endif %} {%- if j.silenced %}silenced{% endif %} {%- set jst = j.onboarding_state %} {%- if jst %}{{ STATE_LABELS.get(jst|string, jst|string) }}{% endif %} {{ j.vendor|vendor }} {{ j.target or "—" }} {% if j.frequency == 'interval' %}every {{ j.interval_minutes }}m, {{ j.window_start }}–{{ j.window_end }}{% else %}daily by {{ j.expected_time }} (+{{ j.grace_hours }}h){% endif %} {% set lv = last_by_job.get(j.id) %}{% if lv %}{{ lv.status|upper }}{% else %}—{% endif %} {% if j.active %}Active{% else %}Paused{% endif %}
{% set and_rules = j.rules|rejectattr('learned')|list %} {% set or_rules = j.rules|selectattr('learned')|list %} {% if and_rules %}
AND RULES · REPORT EMAILS ARE ASSIGNED TO THIS JOB WHEN ALL RULES MATCH
{% for r in and_rules %}
{{ r.field|capitalize }}{{ r.operator }}{{ r.value }}
{% endfor %} {% endif %} {% if or_rules %}
OR RULES · ANY ONE MATCHING IS ENOUGH
{% for r in or_rules %}
{{ r.field|capitalize }}{{ r.operator }}{{ r.value }}{% if r.machine_learned() %}learned{% endif %}
{% endfor %} {% endif %} {% if not j.rules %}
No rules — this job will never auto-match. Add one on the job page.
{% endif %}
{% else %}
No jobs yet{% if user.role in ['admin', 'tech'] %} — click + Add job to create the first one{% endif %}.
{% endfor %}
{% if user.role in ['admin', 'tech'] %}
1Template 2Schedule 3Matching rules

Start from a template

Templates pre-fill the schedule and the matching rules for common report emails — scoped to this client where possible. You can adjust everything before saving.

Pick a template to continue
{% endif %} {% include "_sched_toggle.html" %} {% endblock %}