repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

5.0 KB · 117 lines · HTML Raw History
  1{{ define "breadcrumbs" }}
  2<nav aria-label="breadcrumb">
  3  <ol class="breadcrumb mb-0">
  4    <li class="breadcrumb-item"><a href="/">Home</a></li>
  5    <li class="breadcrumb-item active" aria-current="page">{{ .Title }}</li>
  6  </ol>
  7</nav>
  8{{ end }}
  9
 10{{ define "main" }}
 11<div class="container my-4">
 12  <div class="row align-items-center g-3 mb-4">
 13    <div class="col-md-5">
 14      <div class="section-label mb-1">operator</div>
 15      <h1 class="fw-bolder text-white mb-0" style="letter-spacing: -0.01em;">{{ .Title }}</h1>
 16      <p class="text-muted mb-0 small mt-1">Every URL you've registered, current probe state, and audit signals.</p>
 17    </div>
 18    <div class="col-md-7">
 19      <div class="dashboard-toolbar">
 20        <form method="get" class="search-form flex-grow-1">
 21          <input type="text" class="form-control search-input" name="q" id="id_search" placeholder="grep properties..." value="{{ .Query }}" />
 22        </form>
 23        <button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePropertyAdd" aria-expanded="false" aria-controls="collapsePropertyAdd">
 24          + new
 25        </button>
 26      </div>
 27    </div>
 28  </div>
 29
 30  <div class="collapse mb-4" id="collapsePropertyAdd">
 31    <div class="bg-surface border-subtle rounded border p-3">
 32      <div class="section-label mb-2">register property</div>
 33      {{/* https:// is not a preference. The checker speaks HTTP/2 over TLS and
 34           nothing else, so an http:// property could never pass a check. */}}
 35      <p class="text-muted small mb-3">Any public https:// URL. Probe begins within 3 minutes.</p>
 36      <form method="POST" action="/properties" class="dashboard-toolbar">
 37        <input type="url" name="url" id="id_url" class="form-control flex-grow-1" placeholder="https://example.com" pattern="https://.*" required>
 38        <button type="submit" class="btn btn-primary">Add →</button>
 39      </form>
 40    </div>
 41  </div>
 42
 43  <div class="section-label mb-2">registered properties</div>
 44  {{ range .Properties }}
 45    <div class="property-row {{ if ne .CurrentStatus 200 }}is-down{{ end }}">
 46      <div class="pr-main">
 47        <div class="pr-title">
 48          <span class="status-dot {{ if eq .CurrentStatus 200 }}is-up{{ else }}is-down{{ end }}" aria-hidden="true"></span>
 49          <a href="/{{ .ID }}" class="text-truncate">{{ .Name }}</a>
 50        </div>
 51        <div class="pr-url">{{ .URL }}</div>
 52        {{ template "uptimeStrip" . }}
 53      </div>
 54      <div class="pr-side">
 55        <div class="pr-meta">
 56          <div class="pr-meta-cell">
 57            <span class="pr-meta-k">status</span>
 58            {{ if eq .CurrentStatus 200 }}
 59              <span class="chip chip-ok">ok · 200</span>
 60            {{ else }}
 61              <span class="chip chip-down">down · {{ .CurrentStatus }}</span>
 62            {{ end }}
 63          </div>
 64          <div class="pr-meta-cell">
 65            <span class="pr-meta-k">uptime</span>
 66            {{ if .RecentUptimePct }}
 67              <span class="chip chip-{{ uptimeClass .RecentUptimePct }}">{{ pct1 .RecentUptimePct }}%</span>
 68            {{ else }}
 69              <span class="chip chip-muted"></span>
 70            {{ end }}
 71          </div>
 72          <div class="pr-meta-cell">
 73            <span class="pr-meta-k">sec</span>
 74            {{ if .HasSecurityIssue }}
 75              <span class="chip chip-warn">issues</span>
 76            {{ else }}
 77              <span class="chip chip-ok">ok</span>
 78            {{ end }}
 79          </div>
 80          <div class="pr-meta-cell">
 81            <span class="pr-meta-k">seo</span>
 82            <span class="chip chip-{{ countClass (len .CrawlerInsights) 25 100 }}">{{ len .CrawlerInsights }}</span>
 83          </div>
 84          <div class="pr-meta-cell">
 85            <span class="pr-meta-k">lh</span>
 86            {{ if .AvgLighthouseScore }}
 87              <span class="chip chip-{{ lighthouseClass .AvgLighthouseScore }}">{{ .AvgLighthouseScore }}%</span>
 88            {{ else }}
 89              <span class="chip chip-muted"></span>
 90            {{ end }}
 91          </div>
 92        </div>
 93        <div class="pr-actions">
 94          {{ if not .IsProtected }}
 95          <form method="POST" action="/properties/{{ .ID }}/delete" class="d-inline" onsubmit="return confirm('Delete {{ .URL }} and all its checks?');">
 96            <button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
 97          </form>
 98          {{ end }}
 99          <a href="/{{ .ID }}" class="btn btn-sm btn-outline-light">View →</a>
100        </div>
101      </div>
102    </div>
103  {{ else }}
104    <div class="text-center py-5 text-muted">
105      <div class="section-label mb-3" style="justify-content:center;">
106        {{ if .Query }}no properties match{{ else }}no properties yet{{ end }}
107      </div>
108      {{ if .Query }}
109      <p class="mb-0 small">Nothing registered matches <strong>{{ .Query }}</strong>. <a href="/properties" class="link-footer">Clear the filter</a>.</p>
110      {{ else }}
111      <p class="mb-0 small">Click <strong>+ new</strong> above to register your first URL.</p>
112      {{ end }}
113    </div>
114  {{ end }}
115</div>
116{{ end }}