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

6.1 KB · 139 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 site you're collecting events from, event totals, and seven-day activity state.</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          <span class="search-icon">
 22            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
 23              <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
 24            </svg>
 25          </span>
 26          <input type="text" class="form-control search-input" name="q" id="id_search" placeholder="grep properties..." value="{{ .Query }}" />
 27        </form>
 28        <button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePropertyAdd" aria-expanded="false" aria-controls="collapsePropertyAdd" onclick="window.collectorQueue && collectorQueue.push({event: 'add_property_toggle'})">
 29          + new
 30        </button>
 31      </div>
 32    </div>
 33  </div>
 34
 35  <div class="collapse mb-4" id="collapsePropertyAdd">
 36    <div class="bg-surface border-subtle rounded border p-3">
 37      <div class="section-label mb-2">register property</div>
 38      <p class="text-muted small mb-3">Give it any friendly name. You'll get a collector ID to embed right after you save.</p>
 39      <form method="POST" action="/properties" class="dashboard-toolbar">
 40        <input type="text" name="name" id="id_name" class="form-control flex-grow-1" placeholder="e.g. marketing-site" required>
 41        <button type="submit" class="btn btn-primary">Add →</button>
 42      </form>
 43    </div>
 44  </div>
 45
 46  <div class="row g-3 mb-4">
 47    <div class="col-6 col-md-3">
 48      <div class="metric-tile metric-accent-green">
 49        <div class="metric-label">total properties</div>
 50        <div class="metric-value">{{ .Totals.Properties }}</div>
 51      </div>
 52    </div>
 53    <div class="col-6 col-md-3">
 54      <div class="metric-tile metric-accent-green">
 55        <div class="metric-label">total events</div>
 56        <div class="metric-value">{{ .Totals.Events }}</div>
 57      </div>
 58    </div>
 59    <div class="col-6 col-md-3">
 60      <div class="metric-tile metric-accent-green">
 61        <div class="metric-label">page views</div>
 62        <div class="metric-value">{{ .Totals.PageViews }}</div>
 63      </div>
 64    </div>
 65    <div class="col-6 col-md-3">
 66      <div class="metric-tile metric-accent-amber">
 67        <div class="metric-label">sessions</div>
 68        <div class="metric-value">{{ .Totals.Sessions }}</div>
 69      </div>
 70    </div>
 71  </div>
 72
 73  <div class="section-label mb-2">registered properties</div>
 74  {{ range .Properties }}
 75  <div class="property-row {{ if not .IsActive }}is-quiet{{ end }}">
 76    <div class="pr-main">
 77      <div class="pr-title">
 78        <span class="status-dot {{ if .IsActive }}is-up{{ else }}is-idle{{ end }}" aria-hidden="true"></span>
 79        <a href="/{{ .ID }}" class="text-truncate" onclick="window.collectorQueue && collectorQueue.push({event: 'view_property'})">{{ .Name }}</a>
 80      </div>
 81      <div class="pr-id"><span class="pr-id-k">id</span>{{ .ID }}</div>
 82    </div>
 83    <div class="pr-side">
 84      <div class="pr-meta">
 85        <div class="pr-meta-cell">
 86          <span class="pr-meta-k">state</span>
 87          <span class="pr-meta-v {{ if .IsActive }}text-green{{ else }}text-muted{{ end }}">{{ if .IsActive }}live{{ else }}quiet{{ end }}</span>
 88        </div>
 89        <div class="pr-meta-cell">
 90          <span class="pr-meta-k">events</span>
 91          <span class="pr-meta-v">{{ .TotalEvents }}</span>
 92        </div>
 93        <div class="pr-meta-cell">
 94          <span class="pr-meta-k">views</span>
 95          <span class="pr-meta-v">{{ .TotalPageViews }}</span>
 96        </div>
 97        <div class="pr-meta-cell">
 98          <span class="pr-meta-k">sessions</span>
 99          <span class="pr-meta-v">{{ .TotalSessionStarts }}</span>
100        </div>
101      </div>
102      <div class="pr-actions">
103        {{ if not .IsProtected }}
104        <button type="button" class="btn btn-sm btn-outline-danger" data-bs-toggle="modal" data-bs-target="#delete-modal-{{ .ID }}">
105          Delete
106        </button>
107        <div class="modal fade" id="delete-modal-{{ .ID }}" tabindex="-1" aria-hidden="true">
108          <div class="modal-dialog">
109            <div class="modal-content">
110              <div class="modal-header">
111                <h5 class="modal-title text-white">Confirm delete</h5>
112                <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
113              </div>
114              <div class="modal-body">
115                Are you sure you want to delete <strong class="text-white">{{ .Name }}</strong>? All collected events will be removed.
116              </div>
117              <div class="modal-footer">
118                <button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">Cancel</button>
119                <form method="post" action="/properties/{{ .ID }}/delete" class="d-inline">
120                  <button type="submit" class="btn btn-outline-danger">Delete</button>
121                </form>
122              </div>
123            </div>
124          </div>
125        </div>
126        {{ end }}
127        <a href="/{{ .ID }}" class="btn btn-sm btn-outline-light">View →</a>
128      </div>
129    </div>
130  </div>
131  {{ else }}
132  <div class="text-center py-5 text-muted">
133    <div class="section-label mb-3" style="justify-content:center;">no properties yet</div>
134    <p class="mb-0 small">Click <strong>+ new</strong> above to register your first site.</p>
135  </div>
136  {{ end }}
137</div>
138{{ end }}