repos
/ status-rust master

status-rust

mirror archived upstream

Single-binary self-hosted uptime monitoring and status pages on Rust axum: HTTP probes, Lighthouse audits, SEO crawler, and PDF reports.

axumdockerrustself-hostedsqlitestatus-pageuptime-monitoringvite

6.0 KB · 140 lines · HTML Raw History
  1{% extends "base.html" %}
  2
  3{% block extra_css %}
  4<link rel="stylesheet" href="{{ vite_asset('static_src/properties/index.js', 'css') }}">
  5{% endblock %}
  6
  7{% block breadcrumbs %}
  8<nav aria-label="breadcrumb">
  9  <ol class="breadcrumb mb-0">
 10    <li class="breadcrumb-item"><a href="/">Home</a></li>
 11    <li class="breadcrumb-item active" aria-current="page">{{ title }}</li>
 12  </ol>
 13</nav>
 14{% endblock %}
 15
 16{% block main %}
 17<div class="container my-4">
 18  <div class="row align-items-center g-3 mb-4">
 19    <div class="col-md-5">
 20      <div class="section-label mb-1">operator</div>
 21      <h1 class="fw-bolder text-white mb-0" style="letter-spacing: -0.01em;">{{ title }}</h1>
 22      <p class="text-muted mb-0 small mt-1">Every URL you've registered, current probe state, and audit signals.</p>
 23    </div>
 24    <div class="col-md-7">
 25      <div class="dashboard-toolbar">
 26        <form method="get" class="search-form flex-grow-1">
 27          <input type="text" class="form-control search-input" name="q" id="id_search" placeholder="grep properties..."{% if q %} value="{{ q }}"{% endif %} />
 28        </form>
 29        <button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePropertyAdd" aria-expanded="false" aria-controls="collapsePropertyAdd">
 30          + new
 31        </button>
 32      </div>
 33    </div>
 34  </div>
 35
 36  <div class="collapse mb-4" id="collapsePropertyAdd">
 37    <div class="bg-surface border-subtle rounded border p-3">
 38      <div class="section-label mb-2">register property</div>
 39      <p class="text-muted small mb-3">Any public URL. Probe begins within 3 minutes.</p>
 40      <form method="POST" action="{{ url_for('properties') }}" class="dashboard-toolbar">
 41        <input type="url" name="url" id="id_url" class="form-control flex-grow-1" placeholder="https://example.com" required>
 42        <button type="submit" class="btn btn-primary">Add →</button>
 43      </form>
 44    </div>
 45  </div>
 46
 47  <div class="section-label mb-2">registered properties</div>
 48  {% for property in properties %}
 49    <div class="property-row {% if property.current_status != 200 %}is-down{% endif %}">
 50      <div class="pr-main">
 51        <div class="pr-title">
 52          <span class="status-dot {% if property.current_status == 200 %}is-up{% else %}is-down{% endif %}" aria-hidden="true"></span>
 53          <a href="/{{ property.id }}" class="text-truncate">{{ property.name }}</a>
 54        </div>
 55        <div class="pr-url">{{ property.url }}</div>
 56        <div class="uptime-strip" aria-label="Recent checks">
 57          {% for tick in property.recent_tick_stream %}
 58            <span class="uptime-tick is-{{ tick }}"></span>
 59          {% else %}
 60            {% for i in range(30) %}<span class="uptime-tick is-none"></span>{% endfor %}
 61          {% endfor %}
 62        </div>
 63      </div>
 64      <div class="pr-side">
 65        <div class="pr-meta">
 66          <div class="pr-meta-cell">
 67            <span class="pr-meta-k">status</span>
 68            {% if property.current_status == 200 %}
 69              <span class="chip chip-ok">ok · 200</span>
 70            {% else %}
 71              <span class="chip chip-down">down · {{ property.current_status }}</span>
 72            {% endif %}
 73          </div>
 74          <div class="pr-meta-cell">
 75            <span class="pr-meta-k">uptime</span>
 76            {% if property.recent_uptime_pct is not none %}
 77              {% if property.recent_uptime_pct >= 99 %}
 78                <span class="chip chip-ok">{{ property.recent_uptime_pct }}%</span>
 79              {% elif property.recent_uptime_pct < 95 %}
 80                <span class="chip chip-down">{{ property.recent_uptime_pct }}%</span>
 81              {% else %}
 82                <span class="chip chip-warn">{{ property.recent_uptime_pct }}%</span>
 83              {% endif %}
 84            {% else %}
 85              <span class="chip chip-muted"></span>
 86            {% endif %}
 87          </div>
 88          <div class="pr-meta-cell">
 89            <span class="pr-meta-k">sec</span>
 90            {% if property.has_security_issue %}
 91              <span class="chip chip-warn">issues</span>
 92            {% else %}
 93              <span class="chip chip-ok">ok</span>
 94            {% endif %}
 95          </div>
 96          <div class="pr-meta-cell">
 97            <span class="pr-meta-k">seo</span>
 98            {% set insights = property.crawler_insights | length %}
 99            {% if insights > 100 %}
100              <span class="chip chip-down">{{ insights }}</span>
101            {% elif insights > 25 %}
102              <span class="chip chip-warn">{{ insights }}</span>
103            {% else %}
104              <span class="chip chip-ok">{{ insights }}</span>
105            {% endif %}
106          </div>
107          <div class="pr-meta-cell">
108            <span class="pr-meta-k">lh</span>
109            {% if property.avg_lighthouse_score %}
110              {% if property.avg_lighthouse_score >= 90 %}
111                <span class="chip chip-ok">{{ property.avg_lighthouse_score }}%</span>
112              {% elif property.avg_lighthouse_score >= 80 %}
113                <span class="chip chip-warn">{{ property.avg_lighthouse_score }}%</span>
114              {% else %}
115                <span class="chip chip-down">{{ property.avg_lighthouse_score }}%</span>
116              {% endif %}
117            {% else %}
118              <span class="chip chip-muted"></span>
119            {% endif %}
120          </div>
121        </div>
122        <div class="pr-actions">
123          {% if not property.is_protected %}
124          <form method="POST" action="/properties/{{ property.id }}/delete" class="d-inline" onsubmit="return confirm('Delete {{ property.url }} and all its checks?');">
125            <button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
126          </form>
127          {% endif %}
128          <a href="/{{ property.id }}" class="btn btn-sm btn-outline-light">View →</a>
129        </div>
130      </div>
131    </div>
132  {% else %}
133    <div class="text-center py-5 text-muted">
134      <div class="section-label mb-3" style="justify-content:center;">no properties yet</div>
135      <p class="mb-0 small">Click <strong>+ new</strong> above to register your first URL.</p>
136    </div>
137  {% endfor %}
138</div>
139{% endblock %}