docs: publish Preview 19 dogfood evidence
Export the reviewed allowlisted snapshot from private source commit 05928cebd01b586cf9e9d4b8c8537a7605a6068c. This records the exact candidate, bounded capacity result, stateful migration scratch requirement, authenticated batch identity proof, and immediate live acceptance evidence. AI-Assisted: OpenAI Codex Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func App(view AppView)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head><?~ Head(view.Head) ?></head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<?~ SiteHeader(true, "overview", view.Organization.ID) ?>
|
||||
<main id="main">
|
||||
<section class="app-intro" aria-labelledby="overview-title">
|
||||
<div><p class="eyebrow">Organization overview</p><h1 id="overview-title"><?= view.Organization.Name ?></h1><p>Welcome, <?= view.DisplayName ?>. Last refreshed <?= view.RefreshedAt ?>.</p></div>
|
||||
<form class="logout" method="post" action="/logout/">
|
||||
<input type="hidden" name="csrf_token" value="<?= view.CSRFToken ?>">
|
||||
<button class="secondary" type="submit">Sign out</button>
|
||||
</form>
|
||||
</section>
|
||||
<? if len(view.Organizations) > 1 { ?>
|
||||
<nav class="organization-nav" aria-label="Organizations">
|
||||
<span>Organization:</span>
|
||||
<? for _, organization := range view.Organizations { ?>
|
||||
<? if organization.Selected { ?>
|
||||
<a href="/app/?organization=<?= organization.ID ?>" aria-current="page"><?= organization.Name ?></a>
|
||||
<? } else { ?>
|
||||
<a href="/app/?organization=<?= organization.ID ?>"><?= organization.Name ?></a>
|
||||
<? } ?>
|
||||
<? } ?>
|
||||
</nav>
|
||||
<? } ?>
|
||||
<? if view.PendingBatches > 0 { ?>
|
||||
<aside class="live-status" role="status">
|
||||
<p><strong>Durable evidence is still being indexed.</strong> Accepted batches safely stored: <?= view.PendingBatches ?>. Recent queries may lag by <?= view.ProjectionLag ?>.</p>
|
||||
</aside>
|
||||
<? } ?>
|
||||
<aside class="live-status" data-events-url="<?= view.EventsURL ?>" hidden>
|
||||
<p><strong>New observations are available.</strong> <a href="/app/?organization=<?= view.Organization.ID ?>">Refresh this overview</a>.</p>
|
||||
</aside>
|
||||
<section class="signal-grid" aria-label="Recent telemetry">
|
||||
<? for _, signal := range view.Signals { ?>
|
||||
<article class="signal-card" id="<?= signal.ID ?>">
|
||||
<p class="eyebrow"><?= signal.Name ?></p>
|
||||
<h2>Recent <?= signal.Name ?></h2>
|
||||
<p><?= signal.Description ?></p>
|
||||
<details><summary>Query</summary><pre><code><?= signal.Query ?></code></pre></details>
|
||||
<?~ ResultTable(signal.Table) ?>
|
||||
</article>
|
||||
<? } ?>
|
||||
</section>
|
||||
<section class="section-grid" aria-labelledby="saved-work">
|
||||
<div><p class="eyebrow">Saved work</p><h2 id="saved-work">Queries and dashboards</h2></div>
|
||||
<div class="saved-grid">
|
||||
<section aria-labelledby="saved-queries"><h3 id="saved-queries">Saved queries</h3>
|
||||
<? if len(view.SavedQueries) == 0 { ?><p>No saved queries yet.</p><? } ?>
|
||||
<? for _, item := range view.SavedQueries { ?><article><h4><?= item.Name ?></h4><p><?= item.Description ?></p><pre><code><?= item.Query ?></code></pre></article><? } ?>
|
||||
</section>
|
||||
<section aria-labelledby="dashboards"><h3 id="dashboards">Dashboards</h3>
|
||||
<? if len(view.Dashboards) == 0 { ?><p>No dashboards yet.</p><? } ?>
|
||||
<? for _, item := range view.Dashboards { ?><article><h4><a href="/app/dashboards/<?= item.Slug ?>/?organization=<?= view.Organization.ID ?>"><?= item.Name ?></a></h4><p><?= item.Description ?></p><p><?= item.PanelCount ?> panels</p></article><? } ?>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<section class="section-grid" aria-labelledby="incident-work">
|
||||
<div><p class="eyebrow">Incident response</p><h2 id="incident-work">Attention without noise</h2></div>
|
||||
<div><p><?= view.OpenIncidents ?> open incidents are visible in the authorized inbox. Alert rules execute the same bounded saved-query AST used everywhere else.</p><p><a class="button secondary" href="<?= view.IncidentsURL ?>">Open incident inbox</a></p></div>
|
||||
</section>
|
||||
<? if view.CanManage { ?>
|
||||
<section class="section-grid" aria-labelledby="manage-work">
|
||||
<div><p class="eyebrow">Organization workshop</p><h2 id="manage-work">Save a reusable view</h2><p>Assisted controls and typed text enter the same parser and versioned AST. Every form is an ordinary server request; nothing is generated or executed on page load.</p></div>
|
||||
<div class="editor-grid">
|
||||
<form class="editor-card builder-card" method="post" action="/app/queries/builder/">
|
||||
<h3>Build a query</h3>
|
||||
<p>Choose one bounded evidence source, an optional filter, and an optional summary. The saved result remains ordinary reviewable query text.</p>
|
||||
<input type="hidden" name="organization_id" value="<?= view.Organization.ID ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?= view.ManageCSRF ?>">
|
||||
<div class="field-grid">
|
||||
<div><label for="builder-name">Name</label><input id="builder-name" name="name" required maxlength="128"></div>
|
||||
<div><label for="builder-description">Description</label><textarea id="builder-description" name="description" required maxlength="1024"></textarea></div>
|
||||
<div><label for="builder-signal">Evidence</label><select id="builder-signal" name="signal" required><option value="logs">Logs</option><option value="metrics">Metrics</option><option value="traces">Traces</option><option value="deployments">Deployments</option></select></div>
|
||||
<div><label for="builder-window">Lookback</label><select id="builder-window" name="window" required><option value="15m">15 minutes</option><option value="1h" selected>1 hour</option><option value="6h">6 hours</option><option value="24h">24 hours</option><option value="168h">7 days</option></select></div>
|
||||
<div><label for="builder-filter-field">Filter field <span class="optional">(optional)</span></label><select id="builder-filter-field" name="filter_field"><option value="">No filter</option><option value="service">Service</option><option value="project">Project</option><option value="environment">Environment</option><option value="route">HTTP route</option><option value="status">HTTP status</option><option value="duration">Duration</option><option value="name">Name</option><option value="severity">Severity</option><option value="value">Metric value</option><option value="trace_id">Trace ID</option><option value="correlation_id">Correlation ID</option></select></div>
|
||||
<div><label for="builder-filter-operator">Filter comparison</label><select id="builder-filter-operator" name="filter_operator" required><option value="==">equals</option><option value="!=">does not equal</option><option value=">=">at least</option><option value="<=">at most</option><option value=">">greater than</option><option value="<">less than</option></select></div>
|
||||
<div><label for="builder-filter-value">Filter value <span class="optional">(optional)</span></label><input id="builder-filter-value" name="filter_value" maxlength="256"></div>
|
||||
<div><label for="builder-aggregate">Summary</label><select id="builder-aggregate" name="aggregate" required><option value="none">No summary</option><option value="count">Count</option><option value="min">Minimum</option><option value="max">Maximum</option><option value="sum">Sum</option><option value="avg">Average</option><option value="p50">50th percentile</option><option value="p95">95th percentile</option><option value="p99">99th percentile</option></select></div>
|
||||
<div><label for="builder-aggregate-field">Numeric summary field <span class="optional">(not used by count)</span></label><select id="builder-aggregate-field" name="aggregate_field"><option value="">No field</option><option value="value">Metric value</option><option value="duration">Duration</option><option value="status">HTTP status</option></select></div>
|
||||
<div><label for="builder-group">Group by <span class="optional">(optional)</span></label><select id="builder-group" name="group_by"><option value="">No grouping</option><option value="service">Service</option><option value="project">Project</option><option value="environment">Environment</option><option value="route">HTTP route</option><option value="status">HTTP status</option><option value="name">Name</option><option value="severity">Severity</option></select></div>
|
||||
<div><label for="builder-bucket">Time bucket <span class="optional">(optional)</span></label><select id="builder-bucket" name="bucket"><option value="">No time bucket</option><option value="1m">1 minute</option><option value="5m">5 minutes</option><option value="15m">15 minutes</option><option value="1h">1 hour</option></select></div>
|
||||
<div><label for="builder-limit">Maximum rows</label><select id="builder-limit" name="limit" required><option value="10">10</option><option value="20">20</option><option value="50" selected>50</option><option value="100">100</option><option value="250">250</option></select></div>
|
||||
</div>
|
||||
<button type="submit">Build and save query</button>
|
||||
</form>
|
||||
<form class="editor-card" method="post" action="/app/queries/">
|
||||
<h3>Write a query</h3>
|
||||
<input type="hidden" name="organization_id" value="<?= view.Organization.ID ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?= view.ManageCSRF ?>">
|
||||
<label for="query-name">Name</label><input id="query-name" name="name" required maxlength="128">
|
||||
<label for="query-description">Description</label><textarea id="query-description" name="description" required maxlength="1024"></textarea>
|
||||
<label for="query-text">Typed query</label><textarea id="query-text" name="query" required maxlength="16384">logs | window 1h | limit 50</textarea>
|
||||
<button type="submit">Save query</button>
|
||||
</form>
|
||||
<form class="editor-card" method="post" action="/app/dashboards/">
|
||||
<h3>Create a one-panel dashboard</h3>
|
||||
<input type="hidden" name="organization_id" value="<?= view.Organization.ID ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?= view.ManageCSRF ?>">
|
||||
<label for="dashboard-slug">URL slug</label><input id="dashboard-slug" name="slug" required maxlength="63" pattern="[a-z][a-z0-9-]+">
|
||||
<label for="dashboard-name">Name</label><input id="dashboard-name" name="name" required maxlength="128">
|
||||
<label for="dashboard-description">Description</label><textarea id="dashboard-description" name="description" required maxlength="1024"></textarea>
|
||||
<label for="panel-title">Panel title</label><input id="panel-title" name="panel_title" required maxlength="128">
|
||||
<label for="saved-query">Saved query</label>
|
||||
<select id="saved-query" name="saved_query_id" required>
|
||||
<option value="">Choose a saved query</option>
|
||||
<? for _, item := range view.SavedQueries { ?><option value="<?= item.ID ?>"><?= item.Name ?></option><? } ?>
|
||||
</select>
|
||||
<label for="visualization">Presentation</label>
|
||||
<select id="visualization" name="visualization" required><option value="table">Table</option><option value="stat">Single statistic with table</option><option value="timeseries">Time series table</option></select>
|
||||
<? if len(view.SavedQueries) == 0 { ?><button type="submit" disabled>Create dashboard</button><? } else { ?><button type="submit">Create dashboard</button><? } ?>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<? } ?>
|
||||
</main>
|
||||
<?~ SiteFooter() ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,497 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 ab6403781ccb2a659b749730243555dcb8a3f319720ad6a972252eb30847c5f4
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func App(view AppView) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/app.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<!doctype html>\n<html lang=\"en\">\n<head>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:9:11
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (Head(view.Head))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:9:29
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</head>\n<body>\n <a class=\"skip-link\" href=\"#main\">Skip to content</a>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:12:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteHeader(true, "overview", view.Organization.ID))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:12:60
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <main id=\"main\">\n <section class=\"app-intro\" aria-labelledby=\"overview-title\">\n <div><p class=\"eyebrow\">Organization overview</p><h1 id=\"overview-title\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:15:84
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Organization.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:15:109
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</h1><p>Welcome, "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:15:130
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.DisplayName)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:15:149
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, ". Last refreshed "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:15:170
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.RefreshedAt)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:15:189
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, ".</p></div>\n <form class=\"logout\" method=\"post\" action=\"/logout/\">\n <input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:17:59
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.CSRFToken)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:17:76
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <button class=\"secondary\" type=\"submit\">Sign out</button>\n </form>\n </section>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:21:8
|
||||
if len(view.Organizations) > 1 {
|
||||
//line internal/site/app.sando:21:43
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <nav class=\"organization-nav\" aria-label=\"Organizations\">\n <span>Organization:</span>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:24:10
|
||||
for _, organization := range view.Organizations {
|
||||
//line internal/site/app.sando:24:62
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:25:12
|
||||
if organization.Selected {
|
||||
//line internal/site/app.sando:25:41
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <a href=\"/app/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:26:43
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:26:61
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" aria-current=\"page\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:26:87
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (organization.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:26:107
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</a>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:27:12
|
||||
} else {
|
||||
//line internal/site/app.sando:27:23
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <a href=\"/app/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:28:43
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:28:61
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:28:67
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (organization.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:28:87
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</a>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:29:12
|
||||
}
|
||||
//line internal/site/app.sando:29:16
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:30:10
|
||||
}
|
||||
//line internal/site/app.sando:30:14
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </nav>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:32:8
|
||||
}
|
||||
//line internal/site/app.sando:32:12
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:33:8
|
||||
if view.PendingBatches > 0 {
|
||||
//line internal/site/app.sando:33:39
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <aside class=\"live-status\" role=\"status\">\n <p><strong>Durable evidence is still being indexed.</strong> Accepted batches safely stored: "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:35:104
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.PendingBatches)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:35:126
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, ". Recent queries may lag by "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:35:158
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.ProjectionLag)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:35:179
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, ".</p>\n </aside>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:37:8
|
||||
}
|
||||
//line internal/site/app.sando:37:12
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <aside class=\"live-status\" data-events-url=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:38:53
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.EventsURL)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:38:70
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" hidden>\n <p><strong>New observations are available.</strong> <a href=\"/app/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:39:91
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:39:114
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Refresh this overview</a>.</p>\n </aside>\n <section class=\"signal-grid\" aria-label=\"Recent telemetry\">\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:42:8
|
||||
for _, signal := range view.Signals {
|
||||
//line internal/site/app.sando:42:48
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <article class=\"signal-card\" id=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:43:44
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (signal.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:43:56
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <p class=\"eyebrow\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:44:32
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (signal.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:44:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p>\n <h2>Recent "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:45:24
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (signal.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:45:38
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</h2>\n <p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:46:16
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (signal.Description)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:46:37
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p>\n <details><summary>Query</summary><pre><code>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:47:57
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (signal.Query)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:47:72
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</code></pre></details>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:48:13
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (ResultTable(signal.Table))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:48:41
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </article>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:50:8
|
||||
}
|
||||
//line internal/site/app.sando:50:12
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </section>\n <section class=\"section-grid\" aria-labelledby=\"saved-work\">\n <div><p class=\"eyebrow\">Saved work</p><h2 id=\"saved-work\">Queries and dashboards</h2></div>\n <div class=\"saved-grid\">\n <section aria-labelledby=\"saved-queries\"><h3 id=\"saved-queries\">Saved queries</h3>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:56:12
|
||||
if len(view.SavedQueries) == 0 {
|
||||
//line internal/site/app.sando:56:47
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p>No saved queries yet.</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:56:78
|
||||
}
|
||||
//line internal/site/app.sando:56:82
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:57:12
|
||||
for _, item := range view.SavedQueries {
|
||||
//line internal/site/app.sando:57:55
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<article><h4>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:57:72
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (item.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:57:84
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</h4><p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:57:96
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (item.Description)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:57:115
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p><pre><code>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:57:134
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (item.Query)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:57:147
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</code></pre></article>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:57:173
|
||||
}
|
||||
//line internal/site/app.sando:57:177
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </section>\n <section aria-labelledby=\"dashboards\"><h3 id=\"dashboards\">Dashboards</h3>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:60:12
|
||||
if len(view.Dashboards) == 0 {
|
||||
//line internal/site/app.sando:60:45
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p>No dashboards yet.</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:60:73
|
||||
}
|
||||
//line internal/site/app.sando:60:77
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:12
|
||||
for _, item := range view.Dashboards {
|
||||
//line internal/site/app.sando:61:53
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<article><h4><a href=\"/app/dashboards/"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:95
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (item.Slug)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:107
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:126
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:149
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:155
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (item.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:167
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</a></h4><p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:183
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (item.Description)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:202
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p><p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:213
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (item.PanelCount)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:231
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, " panels</p></article>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:61:255
|
||||
}
|
||||
//line internal/site/app.sando:61:259
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </section>\n </div>\n </section>\n <section class=\"section-grid\" aria-labelledby=\"incident-work\">\n <div><p class=\"eyebrow\">Incident response</p><h2 id=\"incident-work\">Attention without noise</h2></div>\n <div><p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:67:19
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.OpenIncidents)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:67:40
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, " open incidents are visible in the authorized inbox. Alert rules execute the same bounded saved-query AST used everywhere else.</p><p><a class=\"button secondary\" href=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:67:212
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.IncidentsURL)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:67:232
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Open incident inbox</a></p></div>\n </section>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:69:8
|
||||
if view.CanManage {
|
||||
//line internal/site/app.sando:69:30
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <section class=\"section-grid\" aria-labelledby=\"manage-work\">\n <div><p class=\"eyebrow\">Organization workshop</p><h2 id=\"manage-work\">Save a reusable view</h2><p>Assisted controls and typed text enter the same parser and versioned AST. Every form is an ordinary server request; nothing is generated or executed on page load.</p></div>\n <div class=\"editor-grid\">\n <form class=\"editor-card builder-card\" method=\"post\" action=\"/app/queries/builder/\">\n <h3>Build a query</h3>\n <p>Choose one bounded evidence source, an optional filter, and an optional summary. The saved result remains ordinary reviewable query text.</p>\n <input type=\"hidden\" name=\"organization_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:76:66
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:76:89
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:77:61
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ManageCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:77:79
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <div class=\"field-grid\">\n <div><label for=\"builder-name\">Name</label><input id=\"builder-name\" name=\"name\" required maxlength=\"128\"></div>\n <div><label for=\"builder-description\">Description</label><textarea id=\"builder-description\" name=\"description\" required maxlength=\"1024\"></textarea></div>\n <div><label for=\"builder-signal\">Evidence</label><select id=\"builder-signal\" name=\"signal\" required><option value=\"logs\">Logs</option><option value=\"metrics\">Metrics</option><option value=\"traces\">Traces</option><option value=\"deployments\">Deployments</option></select></div>\n <div><label for=\"builder-window\">Lookback</label><select id=\"builder-window\" name=\"window\" required><option value=\"15m\">15 minutes</option><option value=\"1h\" selected>1 hour</option><option value=\"6h\">6 hours</option><option value=\"24h\">24 hours</option><option value=\"168h\">7 days</option></select></div>\n <div><label for=\"builder-filter-field\">Filter field <span class=\"optional\">(optional)</span></label><select id=\"builder-filter-field\" name=\"filter_field\"><option value=\"\">No filter</option><option value=\"service\">Service</option><option value=\"project\">Project</option><option value=\"environment\">Environment</option><option value=\"route\">HTTP route</option><option value=\"status\">HTTP status</option><option value=\"duration\">Duration</option><option value=\"name\">Name</option><option value=\"severity\">Severity</option><option value=\"value\">Metric value</option><option value=\"trace_id\">Trace ID</option><option value=\"correlation_id\">Correlation ID</option></select></div>\n <div><label for=\"builder-filter-operator\">Filter comparison</label><select id=\"builder-filter-operator\" name=\"filter_operator\" required><option value=\"==\">equals</option><option value=\"!=\">does not equal</option><option value=\">=\">at least</option><option value=\"<=\">at most</option><option value=\">\">greater than</option><option value=\"<\">less than</option></select></div>\n <div><label for=\"builder-filter-value\">Filter value <span class=\"optional\">(optional)</span></label><input id=\"builder-filter-value\" name=\"filter_value\" maxlength=\"256\"></div>\n <div><label for=\"builder-aggregate\">Summary</label><select id=\"builder-aggregate\" name=\"aggregate\" required><option value=\"none\">No summary</option><option value=\"count\">Count</option><option value=\"min\">Minimum</option><option value=\"max\">Maximum</option><option value=\"sum\">Sum</option><option value=\"avg\">Average</option><option value=\"p50\">50th percentile</option><option value=\"p95\">95th percentile</option><option value=\"p99\">99th percentile</option></select></div>\n <div><label for=\"builder-aggregate-field\">Numeric summary field <span class=\"optional\">(not used by count)</span></label><select id=\"builder-aggregate-field\" name=\"aggregate_field\"><option value=\"\">No field</option><option value=\"value\">Metric value</option><option value=\"duration\">Duration</option><option value=\"status\">HTTP status</option></select></div>\n <div><label for=\"builder-group\">Group by <span class=\"optional\">(optional)</span></label><select id=\"builder-group\" name=\"group_by\"><option value=\"\">No grouping</option><option value=\"service\">Service</option><option value=\"project\">Project</option><option value=\"environment\">Environment</option><option value=\"route\">HTTP route</option><option value=\"status\">HTTP status</option><option value=\"name\">Name</option><option value=\"severity\">Severity</option></select></div>\n <div><label for=\"builder-bucket\">Time bucket <span class=\"optional\">(optional)</span></label><select id=\"builder-bucket\" name=\"bucket\"><option value=\"\">No time bucket</option><option value=\"1m\">1 minute</option><option value=\"5m\">5 minutes</option><option value=\"15m\">15 minutes</option><option value=\"1h\">1 hour</option></select></div>\n <div><label for=\"builder-limit\">Maximum rows</label><select id=\"builder-limit\" name=\"limit\" required><option value=\"10\">10</option><option value=\"20\">20</option><option value=\"50\" selected>50</option><option value=\"100\">100</option><option value=\"250\">250</option></select></div>\n </div>\n <button type=\"submit\">Build and save query</button>\n </form>\n <form class=\"editor-card\" method=\"post\" action=\"/app/queries/\">\n <h3>Write a query</h3>\n <input type=\"hidden\" name=\"organization_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:96:66
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:96:89
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:97:61
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ManageCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:97:79
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <label for=\"query-name\">Name</label><input id=\"query-name\" name=\"name\" required maxlength=\"128\">\n <label for=\"query-description\">Description</label><textarea id=\"query-description\" name=\"description\" required maxlength=\"1024\"></textarea>\n <label for=\"query-text\">Typed query</label><textarea id=\"query-text\" name=\"query\" required maxlength=\"16384\">logs | window 1h | limit 50</textarea>\n <button type=\"submit\">Save query</button>\n </form>\n <form class=\"editor-card\" method=\"post\" action=\"/app/dashboards/\">\n <h3>Create a one-panel dashboard</h3>\n <input type=\"hidden\" name=\"organization_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:105:66
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:105:89
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:106:61
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ManageCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:106:79
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <label for=\"dashboard-slug\">URL slug</label><input id=\"dashboard-slug\" name=\"slug\" required maxlength=\"63\" pattern=\"[a-z][a-z0-9-]+\">\n <label for=\"dashboard-name\">Name</label><input id=\"dashboard-name\" name=\"name\" required maxlength=\"128\">\n <label for=\"dashboard-description\">Description</label><textarea id=\"dashboard-description\" name=\"description\" required maxlength=\"1024\"></textarea>\n <label for=\"panel-title\">Panel title</label><input id=\"panel-title\" name=\"panel_title\" required maxlength=\"128\">\n <label for=\"saved-query\">Saved query</label>\n <select id=\"saved-query\" name=\"saved_query_id\" required>\n <option value=\"\">Choose a saved query</option>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:114:16
|
||||
for _, item := range view.SavedQueries {
|
||||
//line internal/site/app.sando:114:59
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<option value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:114:78
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (item.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:114:88
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:114:94
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (item.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:114:106
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:114:118
|
||||
}
|
||||
//line internal/site/app.sando:114:122
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </select>\n <label for=\"visualization\">Presentation</label>\n <select id=\"visualization\" name=\"visualization\" required><option value=\"table\">Table</option><option value=\"stat\">Single statistic with table</option><option value=\"timeseries\">Time series table</option></select>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:118:14
|
||||
if len(view.SavedQueries) == 0 {
|
||||
//line internal/site/app.sando:118:49
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<button type=\"submit\" disabled>Create dashboard</button>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:118:108
|
||||
} else {
|
||||
//line internal/site/app.sando:118:119
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<button type=\"submit\">Create dashboard</button>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:118:169
|
||||
}
|
||||
//line internal/site/app.sando:118:173
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </form>\n </div>\n </section>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:122:8
|
||||
}
|
||||
//line internal/site/app.sando:122:12
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </main>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:124:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteFooter())); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/app.sando:124:22
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n</body>\n</html>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Package site contains Observatory's typed Sandwich Hime interface and its
|
||||
// immutable browser assets. HTTP policy remains owned by internal/httpserver.
|
||||
package site
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
_ "embed"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
//go:embed assets/site.css
|
||||
var style []byte
|
||||
|
||||
//go:embed assets/site.js
|
||||
var script []byte
|
||||
|
||||
//go:embed assets/observatory.svg
|
||||
var icon []byte
|
||||
|
||||
type Assets struct {
|
||||
StylePath string
|
||||
ScriptPath string
|
||||
IconPath string
|
||||
}
|
||||
|
||||
func AssetPaths() Assets {
|
||||
return Assets{StylePath: fingerprintedPath("site", "css", style), ScriptPath: fingerprintedPath("site", "js", script), IconPath: fingerprintedPath("observatory", "svg", icon)}
|
||||
}
|
||||
|
||||
func Asset(path string) (body []byte, contentType string, ok bool) {
|
||||
assets := AssetPaths()
|
||||
switch path {
|
||||
case assets.StylePath:
|
||||
return style, "text/css; charset=utf-8", true
|
||||
case assets.ScriptPath:
|
||||
return script, "text/javascript; charset=utf-8", true
|
||||
case assets.IconPath:
|
||||
return icon, "image/svg+xml", true
|
||||
default:
|
||||
return nil, "", false
|
||||
}
|
||||
}
|
||||
|
||||
func fingerprintedPath(name, extension string, body []byte) string {
|
||||
digest := sha256.Sum256(body)
|
||||
return "/assets/" + name + "-" + hex.EncodeToString(digest[:8]) + "." + extension
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-labelledby="title description">
|
||||
<title id="title">Gamertan Observatory</title>
|
||||
<desc id="description">Three calm observation rings around one bright point.</desc>
|
||||
<rect width="512" height="512" rx="96" fill="#111715"/>
|
||||
<g fill="none" stroke="#b5e3c5" stroke-width="24">
|
||||
<circle cx="256" cy="256" r="168"/>
|
||||
<ellipse cx="256" cy="256" rx="168" ry="72"/>
|
||||
<ellipse cx="256" cy="256" rx="72" ry="168"/>
|
||||
</g>
|
||||
<circle cx="256" cy="256" r="28" fill="#f7c873"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 610 B |
@@ -0,0 +1,37 @@
|
||||
/* SPDX-License-Identifier: AGPL-3.0-only */
|
||||
:root{color-scheme:dark;--ink:#f6f0e2;--muted:#bfb8aa;--paper:#111715;--panel:#18221f;--line:#4f665e;--accent:#b5e3c5;--focus:#f7c873;font:100%/1.6 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
*{box-sizing:border-box}
|
||||
html{background:var(--paper);color:var(--ink)}
|
||||
body{margin:0;min-width:18rem}
|
||||
a{color:var(--accent);text-underline-offset:.2em}
|
||||
a:hover{text-decoration-thickness:.16em}
|
||||
button,input,select,textarea{font:inherit}
|
||||
button,.button{display:inline-block;border:.12rem solid var(--ink);border-radius:.2rem;background:var(--ink);color:var(--paper);padding:.65rem 1rem;font-weight:700;text-decoration:none;cursor:pointer}
|
||||
button.secondary,.secondary{background:transparent;color:var(--ink)}
|
||||
:focus-visible{outline:.2rem solid var(--focus);outline-offset:.2rem}
|
||||
.skip-link{position:absolute;left:1rem;top:-8rem;background:var(--ink);color:var(--paper);padding:.6rem;z-index:10}.skip-link:focus{top:1rem}
|
||||
.site-header,.site-footer,main{width:min(92rem,100%);margin-inline:auto;padding-inline:clamp(1rem,4vw,3rem)}
|
||||
.site-header{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding-block:1.25rem;border-bottom:.08rem solid var(--line)}
|
||||
.site-header nav{display:flex;gap:1rem}.wordmark{color:var(--ink);font-weight:800;text-decoration:none;letter-spacing:.02em}
|
||||
.site-footer{margin-top:4rem;padding-block:2rem;border-top:.08rem solid var(--line);color:var(--muted)}
|
||||
.hero{padding-block:clamp(4rem,12vw,9rem);max-width:72rem}.hero h1{font-size:clamp(3rem,10vw,8rem);line-height:.9;letter-spacing:-.055em;max-width:11ch;margin:.15em 0}.lede{font-size:clamp(1.15rem,2vw,1.5rem);max-width:48rem;color:var(--muted)}
|
||||
.eyebrow{text-transform:uppercase;letter-spacing:.14em;font-size:.78rem;font-weight:800;color:var(--accent)}
|
||||
.section-grid{display:grid;grid-template-columns:minmax(14rem,1fr) minmax(0,2fr);gap:clamp(2rem,6vw,7rem);padding-block:clamp(3rem,7vw,6rem);border-top:.08rem solid var(--line)}
|
||||
h1,h2,h3,h4{line-height:1.1;text-wrap:balance}h2{font-size:clamp(2rem,5vw,4.5rem);margin:.15em 0}h3{font-size:1.4rem}.prose{max-width:52rem}.cards{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}.cards article,.auth-card,.signal-card,.saved-grid>section,.live-status{background:var(--panel);border:.08rem solid var(--line);padding:clamp(1rem,3vw,2rem)}
|
||||
.auth-shell{min-height:70vh;display:grid;place-items:center}.auth-card{width:min(34rem,100%);margin-block:4rem}.auth-card h1{font-size:clamp(2.3rem,8vw,4rem)}
|
||||
form{display:grid;gap:.55rem}label{font-weight:700;margin-top:.6rem}input,select,textarea{width:100%;border:.1rem solid var(--line);background:var(--paper);color:var(--ink);padding:.7rem;border-radius:.2rem}textarea{min-height:7rem;resize:vertical}.form-error{border-left:.3rem solid #ff9b8f;padding:.7rem;background:#301b1b}
|
||||
.app-intro{display:flex;align-items:end;justify-content:space-between;gap:2rem;padding-block:3rem}.app-intro h1{font-size:clamp(2.6rem,7vw,6rem);margin:.1em 0}.logout{display:block}.organization-nav{display:flex;flex-wrap:wrap;gap:.7rem 1rem;align-items:center;border-block:.08rem solid var(--line);padding-block:1rem}.organization-nav a[aria-current="page"]{color:var(--ink);font-weight:800}
|
||||
.live-status{margin-block:1rem}.live-status p{margin:0}.signal-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem;padding-block:2rem}.signal-card{min-width:0}.signal-card h2{font-size:clamp(1.8rem,4vw,3rem)}
|
||||
details{margin-block:1rem}pre{max-width:100%;overflow:auto;background:#0a0f0d;border:.08rem solid var(--line);padding:.8rem;white-space:pre-wrap;overflow-wrap:anywhere}
|
||||
.table-scroll{max-width:100%;overflow:auto;border:.08rem solid var(--line)}table{width:100%;border-collapse:collapse;min-width:40rem}caption{text-align:left;padding:.7rem;font-weight:800}th,td{text-align:left;vertical-align:top;border-top:.06rem solid var(--line);padding:.55rem .7rem;overflow-wrap:anywhere}th{color:var(--accent)}.unit{font-weight:400;color:var(--muted)}
|
||||
.saved-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem}.saved-grid article+article{border-top:.08rem solid var(--line);margin-top:1rem;padding-top:1rem}
|
||||
.editor-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem}.editor-card{background:var(--panel);border:.08rem solid var(--line);padding:clamp(1rem,3vw,2rem)}.builder-card{grid-column:1/-1}.field-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.6rem 1rem}.optional{color:var(--muted);font-size:.9em;font-weight:400}button:disabled{cursor:not-allowed;opacity:.55}.breadcrumbs{display:flex;flex-wrap:wrap;gap:.5rem;padding-top:1.5rem}.dashboard-panels{display:grid;gap:1rem}.stat{font-size:clamp(2rem,8vw,5rem);font-weight:800;line-height:1;margin:.5em 0}
|
||||
.meter-figure{margin:1.5rem 0}.meter-figure figcaption{font-weight:800;margin-bottom:.75rem}.meter-chart{display:grid;gap:.7rem;list-style:none;margin:0;padding:0}.meter-chart li{display:grid;grid-template-columns:minmax(8rem,1fr) minmax(10rem,3fr) minmax(5rem,auto);gap:.8rem;align-items:center}.meter-label,.meter-value{overflow-wrap:anywhere}.meter-value{font-variant-numeric:tabular-nums;text-align:right}meter{width:100%;height:1.2rem;accent-color:var(--accent)}
|
||||
.incident-list{display:grid;gap:1rem}.incident-card{background:var(--panel);border:.08rem solid var(--line);border-left:.35rem solid var(--accent);padding:clamp(1rem,3vw,2rem)}.incident-card[data-state="firing"]{border-left-color:#ff9b8f}.incident-card[data-state="resolved"]{opacity:.78}.incident-card dl,.saved-grid dl{display:grid;gap:.5rem;margin-block:1rem}.incident-card dl div,.saved-grid dl div{display:grid;grid-template-columns:minmax(8rem,1fr) minmax(0,2fr);gap:1rem}.incident-card dt,.saved-grid dt{font-weight:800}.incident-card dd,.saved-grid dd{margin:0}.incident-actions{display:flex;align-items:end;flex-wrap:wrap;gap:.75rem}.incident-actions form{display:flex;align-items:end;gap:.45rem}.incident-actions label{margin:0}.incident-actions select{width:auto;min-width:10rem}
|
||||
.offline-control{display:grid;grid-template-columns:minmax(14rem,1fr) minmax(0,2fr);gap:clamp(2rem,6vw,7rem);margin-block:1rem;padding:clamp(1rem,3vw,2rem);border:.08rem solid var(--line);background:var(--panel)}.offline-control h2{font-size:clamp(1.5rem,3vw,2.5rem)}.offline-control [role="status"]{color:var(--muted)}
|
||||
.explore-layout{display:grid;grid-template-columns:minmax(16rem,1fr) minmax(0,2fr);gap:clamp(2rem,6vw,7rem);padding-block:2rem;border-top:.08rem solid var(--line)}.explore-layout h2,.query-results h2{font-size:clamp(2rem,5vw,4rem)}.query-workbench textarea{min-height:14rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.field-help{color:var(--muted)}.quick-queries{display:flex;flex-wrap:wrap;gap:.6rem;margin-block:1.5rem}.quick-queries form{display:block}.query-results{padding-block:3rem;border-top:.08rem solid var(--line)}.query-results-heading{display:grid;grid-template-columns:minmax(14rem,1fr) minmax(0,2fr);gap:clamp(2rem,6vw,7rem);align-items:end}.query-stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.75rem;margin:0}.query-stats div{border-left:.18rem solid var(--line);padding-left:.75rem}.query-stats dt{color:var(--muted);font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.08em}.query-stats dd{margin:.15rem 0 0;font-size:1.15rem;font-variant-numeric:tabular-nums;font-weight:800}
|
||||
@media(max-width:55rem){.section-grid,.cards,.signal-grid,.saved-grid,.editor-grid,.field-grid,.offline-control,.explore-layout,.query-results-heading{grid-template-columns:1fr}.query-stats{grid-template-columns:repeat(2,minmax(0,1fr))}.app-intro{align-items:start;flex-direction:column}.hero h1{letter-spacing:-.035em}.site-header{align-items:flex-start;flex-direction:column}.site-header nav{flex-wrap:wrap}.meter-chart li{grid-template-columns:1fr}.meter-value{text-align:left}.incident-card dl div{grid-template-columns:1fr;gap:.1rem}.incident-actions,.incident-actions form{align-items:stretch;flex-direction:column}.incident-actions select{width:100%}}
|
||||
@media(max-width:24rem){:root{font-size:95%}.hero h1{font-size:2.75rem;letter-spacing:-.025em}.site-header,.site-footer,main{padding-inline:.8rem}}
|
||||
@media(prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;animation:none!important;transition:none!important}}
|
||||
@media(forced-colors:active){button,.button,.auth-card,.signal-card,.saved-grid>section,.editor-card,.live-status,.incident-card,.offline-control,.query-stats div{border:1px solid CanvasText}.eyebrow,a{color:LinkText}}
|
||||
@media print{*{color:#000!important;background:#fff!important}.site-header nav,.logout,.live-status,.button,script{display:none!important}.site-header,.site-footer,.section-grid{border-color:#777}.signal-grid,.section-grid{display:block}.signal-card{break-inside:avoid;margin-block:1rem}.table-scroll{overflow:visible}table{min-width:0;font-size:9pt}}
|
||||
@@ -0,0 +1,146 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
(() => {
|
||||
"use strict";
|
||||
const serviceWorker = "serviceWorker" in navigator
|
||||
? navigator.serviceWorker.register("/service-worker.js", {scope: "/"})
|
||||
: Promise.reject(new Error("service workers unavailable"));
|
||||
|
||||
const messageWorker = async message => {
|
||||
const registration = await serviceWorker;
|
||||
const worker = registration.active || registration.waiting || registration.installing;
|
||||
if (!worker) throw new Error("service worker unavailable");
|
||||
return await new Promise((resolve, reject) => {
|
||||
const channel = new MessageChannel();
|
||||
const timeout = window.setTimeout(() => reject(new Error("service worker timeout")), 5000);
|
||||
channel.port1.onmessage = event => {
|
||||
window.clearTimeout(timeout);
|
||||
event.data && event.data.ok ? resolve(event.data) : reject(new Error("service worker request failed"));
|
||||
};
|
||||
worker.postMessage(message, [channel.port2]);
|
||||
});
|
||||
};
|
||||
|
||||
const decodeURLBase64 = value => {
|
||||
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
||||
const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
|
||||
const decoded = window.atob(padded);
|
||||
return Uint8Array.from(decoded, character => character.charCodeAt(0));
|
||||
};
|
||||
|
||||
const pushRequest = async (button, action, subscription) => {
|
||||
const serialized = subscription.toJSON();
|
||||
const status = action === "status";
|
||||
const response = await fetch(status ? "/api/v1/push/subscription/status" : "/api/v1/push/subscription", {
|
||||
method: action === "delete" ? "DELETE" : "POST",
|
||||
credentials: "same-origin",
|
||||
cache: "no-store",
|
||||
headers: {"Content-Type": "application/json", "X-CSRF-Token": button.dataset.pushCsrf},
|
||||
body: JSON.stringify({
|
||||
organization_id: button.dataset.pushOrganization,
|
||||
endpoint: subscription.endpoint,
|
||||
keys: action === "save" ? serialized.keys : {p256dh: "", auth: ""}
|
||||
})
|
||||
});
|
||||
if (!response.ok) throw new Error("push subscription request failed");
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
for (const button of document.querySelectorAll("[data-cache-inbox]")) {
|
||||
const status = document.querySelector("[data-cache-status]");
|
||||
serviceWorker.then(() => {
|
||||
button.disabled = false;
|
||||
if (status) status.textContent = "No private incident copy has been saved by this control yet.";
|
||||
}).catch(() => {
|
||||
if (status) status.textContent = "Offline saving is unavailable in this browser.";
|
||||
});
|
||||
button.addEventListener("click", async () => {
|
||||
button.disabled = true;
|
||||
if (status) status.textContent = "Saving a private, read-only incident snapshot…";
|
||||
try {
|
||||
await messageWorker({type: "cache-inbox", source: button.dataset.offlineSource, target: button.dataset.offlineTarget});
|
||||
if (status) status.textContent = "This incident inbox is available offline on this browser.";
|
||||
} catch (_) {
|
||||
if (status) status.textContent = "The offline incident snapshot could not be saved.";
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const form of document.querySelectorAll('form[action="/logout/"]')) {
|
||||
form.addEventListener("submit", async event => {
|
||||
if (form.dataset.privateCacheCleared === "true") return;
|
||||
event.preventDefault();
|
||||
try { await messageWorker({type: "clear-private"}); } catch (_) {}
|
||||
form.dataset.privateCacheCleared = "true";
|
||||
form.requestSubmit();
|
||||
});
|
||||
}
|
||||
|
||||
for (const button of document.querySelectorAll("[data-push-toggle]")) {
|
||||
const status = document.querySelector("[data-push-status]");
|
||||
const ready = serviceWorker.then(async registration => {
|
||||
if (!("PushManager" in window) || !("Notification" in window)) throw new Error("push unavailable");
|
||||
const existing = await registration.pushManager.getSubscription();
|
||||
const registered = existing ? (await pushRequest(button, "status", existing)).subscribed === true : false;
|
||||
button.dataset.pushRegistered = registered ? "true" : "false";
|
||||
button.textContent = registered ? "Disable private incident nudges" : "Enable private incident nudges";
|
||||
button.disabled = false;
|
||||
if (status) status.textContent = registered ? "This browser is subscribed for this organization." : "This browser is not subscribed for this organization.";
|
||||
return registration;
|
||||
});
|
||||
ready.catch(() => { if (status) status.textContent = "Web Push is unavailable in this browser."; });
|
||||
button.addEventListener("click", async () => {
|
||||
button.disabled = true;
|
||||
try {
|
||||
const registration = await ready;
|
||||
let subscription = await registration.pushManager.getSubscription();
|
||||
if (subscription && button.dataset.pushRegistered === "true") {
|
||||
const result = await pushRequest(button, "delete", subscription);
|
||||
if (!result.remaining) await subscription.unsubscribe();
|
||||
button.dataset.pushRegistered = "false";
|
||||
button.textContent = "Enable private incident nudges";
|
||||
if (status) status.textContent = "This browser is no longer subscribed for this organization.";
|
||||
} else {
|
||||
let createdNow = false;
|
||||
if (!subscription) {
|
||||
const permission = await Notification.requestPermission();
|
||||
if (permission !== "granted") throw new Error("notification permission not granted");
|
||||
subscription = await registration.pushManager.subscribe({userVisibleOnly: true, applicationServerKey: decodeURLBase64(button.dataset.pushPublicKey)});
|
||||
createdNow = true;
|
||||
}
|
||||
try {
|
||||
await pushRequest(button, "save", subscription);
|
||||
} catch (error) {
|
||||
if (createdNow) await subscription.unsubscribe();
|
||||
throw error;
|
||||
}
|
||||
button.dataset.pushRegistered = "true";
|
||||
button.textContent = "Disable private incident nudges";
|
||||
if (status) status.textContent = "This browser will receive generic incident nudges.";
|
||||
}
|
||||
} catch (_) {
|
||||
if (status) status.textContent = "The browser push setting could not be changed.";
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const incidentCount = document.querySelector("[data-open-incident-count]");
|
||||
if (incidentCount && "setAppBadge" in navigator) {
|
||||
const count = Number.parseInt(incidentCount.dataset.openIncidentCount, 10);
|
||||
if (Number.isSafeInteger(count) && count >= 0) {
|
||||
const update = count === 0 && "clearAppBadge" in navigator ? navigator.clearAppBadge() : navigator.setAppBadge(count);
|
||||
Promise.resolve(update).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
for (const status of document.querySelectorAll(".live-status[data-events-url]")) {
|
||||
const source = new EventSource(status.dataset.eventsUrl);
|
||||
source.addEventListener("refresh", () => {
|
||||
status.hidden = false;
|
||||
});
|
||||
window.addEventListener("pagehide", () => source.close(), {once: true});
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAssetsAreContentAddressedAndBounded(t *testing.T) {
|
||||
paths := AssetPaths()
|
||||
for _, test := range []struct {
|
||||
path, suffix, contentType string
|
||||
body []byte
|
||||
}{{paths.StylePath, ".css", "text/css; charset=utf-8", style}, {paths.ScriptPath, ".js", "text/javascript; charset=utf-8", script}, {paths.IconPath, ".svg", "image/svg+xml", icon}} {
|
||||
t.Run(test.suffix, func(t *testing.T) {
|
||||
if len(test.body) == 0 || len(test.body) > 64<<10 || !strings.HasSuffix(test.path, test.suffix) {
|
||||
t.Fatalf("path=%q bytes=%d", test.path, len(test.body))
|
||||
}
|
||||
digest := sha256.Sum256(test.body)
|
||||
if !strings.Contains(test.path, hex.EncodeToString(digest[:8])) {
|
||||
t.Fatalf("asset path %q does not contain content digest", test.path)
|
||||
}
|
||||
body, contentType, ok := Asset(test.path)
|
||||
if !ok || contentType != test.contentType || string(body) != string(test.body) {
|
||||
t.Fatalf("asset lookup ok=%v type=%q", ok, contentType)
|
||||
}
|
||||
})
|
||||
}
|
||||
if _, _, ok := Asset("/assets/site.css"); ok {
|
||||
t.Fatal("unversioned asset path was accepted")
|
||||
}
|
||||
css := string(style)
|
||||
for _, required := range []string{"@media(max-width:24rem)", "@media(prefers-reduced-motion:reduce)", "@media(forced-colors:active)", "@media print", ".table-scroll", ".explore-layout", ".query-stats"} {
|
||||
if !strings.Contains(css, required) {
|
||||
t.Fatalf("responsive CSS missing %q", required)
|
||||
}
|
||||
}
|
||||
if strings.Contains(strings.ToLower(css), "position:sticky") {
|
||||
t.Fatal("mobile-hostile sticky table behavior returned")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func Dashboard(view DashboardView)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head><?~ Head(view.Head) ?></head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<?~ SiteHeader(true, "dashboards", view.Organization.ID) ?>
|
||||
<main id="main">
|
||||
<nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/app/?organization=<?= view.Organization.ID ?>">Overview</a> <span aria-hidden="true">/</span> <span aria-current="page"><?= view.Name ?></span></nav>
|
||||
<header class="app-intro"><div><p class="eyebrow"><?= view.Organization.Name ?> · dashboard</p><h1><?= view.Name ?></h1><p><?= view.Description ?></p></div><p><a class="button secondary" href="<?= view.ExportURL ?>">Export JSON</a></p></header>
|
||||
<section class="dashboard-panels" aria-label="Dashboard panels">
|
||||
<? if len(view.Panels) == 0 { ?><p>This dashboard has no panels yet.</p><? } ?>
|
||||
<? for _, panel := range view.Panels { ?>
|
||||
<article class="signal-card">
|
||||
<p class="eyebrow"><?= panel.Visualization ?></p><h2><?= panel.Title ?></h2>
|
||||
<? if panel.Stat != "" { ?><p class="stat" aria-label="Current statistic"><?= panel.Stat ?></p><? } ?>
|
||||
<? if len(panel.Chart.Points) > 0 { ?>
|
||||
<figure class="meter-figure">
|
||||
<figcaption><?= panel.Chart.Label ?></figcaption>
|
||||
<ol class="meter-chart">
|
||||
<? for _, point := range panel.Chart.Points { ?>
|
||||
<li><span class="meter-label"><?= point.Label ?></span><meter min="0" max="<?= point.Maximum ?>" value="<?= point.Value ?>"><?= point.Display ?></meter><span class="meter-value"><?= point.Display ?></span></li>
|
||||
<? } ?>
|
||||
</ol>
|
||||
</figure>
|
||||
<? } ?>
|
||||
<details><summary>Query</summary><pre><code><?= panel.Query ?></code></pre></details>
|
||||
<?~ ResultTable(panel.Table) ?>
|
||||
</article>
|
||||
<? } ?>
|
||||
</section>
|
||||
<? if view.CanManage { ?>
|
||||
<section class="section-grid" aria-labelledby="edit-dashboard">
|
||||
<div><p class="eyebrow">Dashboard workshop</p><h2 id="edit-dashboard">Revise this view</h2><p>Every change includes revision <?= view.Revision ?>. If someone else saves first, Observatory asks you to reload instead of overwriting their work.</p></div>
|
||||
<div class="editor-grid">
|
||||
<form class="editor-card" method="post" action="/app/dashboards/<?= view.Slug ?>/">
|
||||
<h3>Dashboard details</h3>
|
||||
<input type="hidden" name="organization_id" value="<?= view.Organization.ID ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?= view.ManageCSRF ?>">
|
||||
<input type="hidden" name="dashboard_id" value="<?= view.ID ?>">
|
||||
<input type="hidden" name="expected_revision" value="<?= view.Revision ?>">
|
||||
<label for="dashboard-edit-slug">URL slug</label><input id="dashboard-edit-slug" name="slug" value="<?= view.Slug ?>" required maxlength="63" pattern="[a-z][a-z0-9-]+">
|
||||
<label for="dashboard-edit-name">Name</label><input id="dashboard-edit-name" name="name" value="<?= view.Name ?>" required maxlength="128">
|
||||
<label for="dashboard-edit-description">Description</label><textarea id="dashboard-edit-description" name="description" required maxlength="1024"><?= view.Description ?></textarea>
|
||||
<button type="submit">Update dashboard details</button>
|
||||
</form>
|
||||
<? for index, panel := range view.Panels { ?>
|
||||
<section class="editor-card" aria-labelledby="panel-editor-<?= index ?>">
|
||||
<h3 id="panel-editor-<?= index ?>">Panel <?= index + 1 ?></h3>
|
||||
<form method="post" action="/app/dashboards/<?= view.Slug ?>/panels/<?= panel.ID ?>/">
|
||||
<input type="hidden" name="organization_id" value="<?= view.Organization.ID ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?= view.ManageCSRF ?>">
|
||||
<input type="hidden" name="dashboard_id" value="<?= view.ID ?>">
|
||||
<input type="hidden" name="expected_revision" value="<?= view.Revision ?>">
|
||||
<label for="panel-title-<?= index ?>">Title</label><input id="panel-title-<?= index ?>" name="panel_title" value="<?= panel.Title ?>" required maxlength="128">
|
||||
<label for="panel-query-<?= index ?>">Saved query</label>
|
||||
<select id="panel-query-<?= index ?>" name="saved_query_id" required>
|
||||
<? for _, item := range view.SavedQueries { ?>
|
||||
<? if item.ID == panel.SavedQueryID { ?><option value="<?= item.ID ?>" selected><?= item.Name ?></option><? } else { ?><option value="<?= item.ID ?>"><?= item.Name ?></option><? } ?>
|
||||
<? } ?>
|
||||
</select>
|
||||
<label for="panel-visualization-<?= index ?>">Presentation</label>
|
||||
<select id="panel-visualization-<?= index ?>" name="visualization" required>
|
||||
<? if panel.Visualization == "table" { ?><option value="table" selected>Table</option><? } else { ?><option value="table">Table</option><? } ?>
|
||||
<? if panel.Visualization == "stat" { ?><option value="stat" selected>Single statistic with table</option><? } else { ?><option value="stat">Single statistic with table</option><? } ?>
|
||||
<? if panel.Visualization == "timeseries" { ?><option value="timeseries" selected>Time series with table</option><? } else { ?><option value="timeseries">Time series with table</option><? } ?>
|
||||
</select>
|
||||
<button type="submit">Update panel</button>
|
||||
</form>
|
||||
<details>
|
||||
<summary>Remove this panel</summary>
|
||||
<p>This removes the panel from the dashboard definition. The saved query remains available.</p>
|
||||
<form method="post" action="/app/dashboards/<?= view.Slug ?>/panels/<?= panel.ID ?>/remove/">
|
||||
<input type="hidden" name="organization_id" value="<?= view.Organization.ID ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?= view.ManageCSRF ?>">
|
||||
<input type="hidden" name="dashboard_id" value="<?= view.ID ?>">
|
||||
<input type="hidden" name="expected_revision" value="<?= view.Revision ?>">
|
||||
<button class="secondary" type="submit">Confirm panel removal</button>
|
||||
</form>
|
||||
</details>
|
||||
</section>
|
||||
<? } ?>
|
||||
<? if len(view.Panels) < 16 { ?>
|
||||
<form class="editor-card" method="post" action="/app/dashboards/<?= view.Slug ?>/panels/">
|
||||
<h3>Add a panel</h3>
|
||||
<input type="hidden" name="organization_id" value="<?= view.Organization.ID ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?= view.ManageCSRF ?>">
|
||||
<input type="hidden" name="dashboard_id" value="<?= view.ID ?>">
|
||||
<input type="hidden" name="expected_revision" value="<?= view.Revision ?>">
|
||||
<label for="panel-add-title">Title</label><input id="panel-add-title" name="panel_title" required maxlength="128">
|
||||
<label for="panel-add-query">Saved query</label>
|
||||
<select id="panel-add-query" name="saved_query_id" required><option value="">Choose a saved query</option><? for _, item := range view.SavedQueries { ?><option value="<?= item.ID ?>"><?= item.Name ?></option><? } ?></select>
|
||||
<label for="panel-add-visualization">Presentation</label>
|
||||
<select id="panel-add-visualization" name="visualization" required><option value="table">Table</option><option value="stat">Single statistic with table</option><option value="timeseries">Time series with table</option></select>
|
||||
<? if len(view.SavedQueries) == 0 { ?><button type="submit" disabled>Add panel</button><? } else { ?><button type="submit">Add panel</button><? } ?>
|
||||
</form>
|
||||
<? } ?>
|
||||
</div>
|
||||
</section>
|
||||
<? } ?>
|
||||
</main>
|
||||
<?~ SiteFooter() ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,735 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 5a4729bb7867878ea92e49dd8dc66f388fe8fd255ab32292283ca573d17153d9
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func Dashboard(view DashboardView) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/dashboard.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<!doctype html>\n<html lang=\"en\">\n<head>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:9:11
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (Head(view.Head))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:9:29
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</head>\n<body>\n <a class=\"skip-link\" href=\"#main\">Skip to content</a>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:12:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteHeader(true, "dashboards", view.Organization.ID))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:12:62
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <main id=\"main\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\"><a href=\"/app/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:14:86
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:14:109
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Overview</a> <span aria-hidden=\"true\">/</span> <span aria-current=\"page\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:14:188
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:14:200
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</span></nav>\n <header class=\"app-intro\"><div><p class=\"eyebrow\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:15:59
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Organization.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:15:84
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, " · dashboard</p><h1>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:15:109
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:15:121
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</h1><p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:15:133
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Description)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:15:152
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p></div><p><a class=\"button secondary\" href=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:15:203
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.ExportURL)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:15:220
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Export JSON</a></p></header>\n <section class=\"dashboard-panels\" aria-label=\"Dashboard panels\">\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:17:8
|
||||
if len(view.Panels) == 0 {
|
||||
//line internal/site/dashboard.sando:17:37
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p>This dashboard has no panels yet.</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:17:80
|
||||
}
|
||||
//line internal/site/dashboard.sando:17:84
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:18:8
|
||||
for _, panel := range view.Panels {
|
||||
//line internal/site/dashboard.sando:18:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <article class=\"signal-card\">\n <p class=\"eyebrow\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:20:32
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (panel.Visualization)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:20:54
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p><h2>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:20:66
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (panel.Title)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:20:80
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</h2>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:21:12
|
||||
if panel.Stat != "" {
|
||||
//line internal/site/dashboard.sando:21:36
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p class=\"stat\" aria-label=\"Current statistic\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:21:87
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (panel.Stat)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:21:100
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:21:107
|
||||
}
|
||||
//line internal/site/dashboard.sando:21:111
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:22:12
|
||||
if len(panel.Chart.Points) > 0 {
|
||||
//line internal/site/dashboard.sando:22:47
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <figure class=\"meter-figure\">\n <figcaption>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:24:27
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (panel.Chart.Label)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:24:47
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</figcaption>\n <ol class=\"meter-chart\">\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:26:14
|
||||
for _, point := range panel.Chart.Points {
|
||||
//line internal/site/dashboard.sando:26:59
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <li><span class=\"meter-label\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:27:47
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (point.Label)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:27:61
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</span><meter min=\"0\" max=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:27:92
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (point.Maximum)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:27:108
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:27:121
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (point.Value)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:27:135
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:27:141
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (point.Display)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:27:157
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</meter><span class=\"meter-value\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:27:195
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (point.Display)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:27:211
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</span></li>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:28:14
|
||||
}
|
||||
//line internal/site/dashboard.sando:28:18
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </ol>\n </figure>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:31:12
|
||||
}
|
||||
//line internal/site/dashboard.sando:31:16
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <details><summary>Query</summary><pre><code>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:32:57
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (panel.Query)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:32:71
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</code></pre></details>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:33:13
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (ResultTable(panel.Table))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:33:40
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </article>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:35:8
|
||||
}
|
||||
//line internal/site/dashboard.sando:35:12
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </section>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:37:8
|
||||
if view.CanManage {
|
||||
//line internal/site/dashboard.sando:37:30
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <section class=\"section-grid\" aria-labelledby=\"edit-dashboard\">\n <div><p class=\"eyebrow\">Dashboard workshop</p><h2 id=\"edit-dashboard\">Revise this view</h2><p>Every change includes revision "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:39:136
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Revision)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:39:152
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, ". If someone else saves first, Observatory asks you to reload instead of overwriting their work.</p></div>\n <div class=\"editor-grid\">\n <form class=\"editor-card\" method=\"post\" action=\"/app/dashboards/"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:41:77
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Slug)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:41:89
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "/\">\n <h3>Dashboard details</h3>\n <input type=\"hidden\" name=\"organization_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:43:66
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:43:89
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:44:61
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ManageCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:44:79
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"dashboard_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:45:63
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:45:73
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"expected_revision\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:46:68
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Revision)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:46:84
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <label for=\"dashboard-edit-slug\">URL slug</label><input id=\"dashboard-edit-slug\" name=\"slug\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:47:115
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Slug)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:47:127
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" required maxlength=\"63\" pattern=\"[a-z][a-z0-9-]+\">\n <label for=\"dashboard-edit-name\">Name</label><input id=\"dashboard-edit-name\" name=\"name\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:48:111
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:48:123
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" required maxlength=\"128\">\n <label for=\"dashboard-edit-description\">Description</label><textarea id=\"dashboard-edit-description\" name=\"description\" required maxlength=\"1024\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:49:161
|
||||
if __himesan_error := __himesan_sando.WriteRCDATA(__himesan_writer, (view.Description)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:49:180
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</textarea>\n <button type=\"submit\">Update dashboard details</button>\n </form>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:52:12
|
||||
for index, panel := range view.Panels {
|
||||
//line internal/site/dashboard.sando:52:54
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <section class=\"editor-card\" aria-labelledby=\"panel-editor-"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:53:72
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (index)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:53:80
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <h3 id=\"panel-editor-"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:54:36
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (index)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:54:44
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Panel "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:54:56
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (index + 1)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:54:68
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</h3>\n <form method=\"post\" action=\"/app/dashboards/"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:55:59
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Slug)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:55:71
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "/panels/"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:55:83
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (panel.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:55:94
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "/\">\n <input type=\"hidden\" name=\"organization_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:56:68
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:56:91
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:57:63
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ManageCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:57:81
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"dashboard_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:58:65
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:58:75
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"expected_revision\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:59:70
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Revision)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:59:86
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <label for=\"panel-title-"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:60:41
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (index)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:60:49
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Title</label><input id=\"panel-title-"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:60:91
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (index)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:60:99
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" name=\"panel_title\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:60:131
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (panel.Title)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:60:145
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" required maxlength=\"128\">\n <label for=\"panel-query-"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:61:41
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (index)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:61:49
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Saved query</label>\n <select id=\"panel-query-"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:62:41
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (index)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:62:49
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" name=\"saved_query_id\" required>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:63:16
|
||||
for _, item := range view.SavedQueries {
|
||||
//line internal/site/dashboard.sando:63:59
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:18
|
||||
if item.ID == panel.SavedQueryID {
|
||||
//line internal/site/dashboard.sando:64:55
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<option value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:74
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (item.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:84
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" selected>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:99
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (item.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:111
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:123
|
||||
} else {
|
||||
//line internal/site/dashboard.sando:64:134
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<option value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:153
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (item.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:163
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:169
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (item.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:181
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:193
|
||||
}
|
||||
//line internal/site/dashboard.sando:64:197
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:65:16
|
||||
}
|
||||
//line internal/site/dashboard.sando:65:20
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </select>\n <label for=\"panel-visualization-"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:67:49
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (index)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:67:57
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Presentation</label>\n <select id=\"panel-visualization-"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:68:49
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (index)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:68:57
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" name=\"visualization\" required>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:69:18
|
||||
if panel.Visualization == "table" {
|
||||
//line internal/site/dashboard.sando:69:56
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<option value=\"table\" selected>Table</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:69:104
|
||||
} else {
|
||||
//line internal/site/dashboard.sando:69:115
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<option value=\"table\">Table</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:69:154
|
||||
}
|
||||
//line internal/site/dashboard.sando:69:158
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:70:18
|
||||
if panel.Visualization == "stat" {
|
||||
//line internal/site/dashboard.sando:70:55
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<option value=\"stat\" selected>Single statistic with table</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:70:124
|
||||
} else {
|
||||
//line internal/site/dashboard.sando:70:135
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<option value=\"stat\">Single statistic with table</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:70:195
|
||||
}
|
||||
//line internal/site/dashboard.sando:70:199
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:71:18
|
||||
if panel.Visualization == "timeseries" {
|
||||
//line internal/site/dashboard.sando:71:61
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<option value=\"timeseries\" selected>Time series with table</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:71:131
|
||||
} else {
|
||||
//line internal/site/dashboard.sando:71:142
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<option value=\"timeseries\">Time series with table</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:71:203
|
||||
}
|
||||
//line internal/site/dashboard.sando:71:207
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </select>\n <button type=\"submit\">Update panel</button>\n </form>\n <details>\n <summary>Remove this panel</summary>\n <p>This removes the panel from the dashboard definition. The saved query remains available.</p>\n <form method=\"post\" action=\"/app/dashboards/"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:78:61
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Slug)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:78:73
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "/panels/"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:78:85
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (panel.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:78:96
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "/remove/\">\n <input type=\"hidden\" name=\"organization_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:79:70
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:79:93
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:80:65
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ManageCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:80:83
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"dashboard_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:81:67
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:81:77
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"expected_revision\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:82:72
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Revision)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:82:88
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <button class=\"secondary\" type=\"submit\">Confirm panel removal</button>\n </form>\n </details>\n </section>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:87:12
|
||||
}
|
||||
//line internal/site/dashboard.sando:87:16
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:88:12
|
||||
if len(view.Panels) < 16 {
|
||||
//line internal/site/dashboard.sando:88:41
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <form class=\"editor-card\" method=\"post\" action=\"/app/dashboards/"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:89:77
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Slug)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:89:89
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "/panels/\">\n <h3>Add a panel</h3>\n <input type=\"hidden\" name=\"organization_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:91:66
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:91:89
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:92:61
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ManageCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:92:79
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"dashboard_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:93:63
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:93:73
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"expected_revision\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:94:68
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Revision)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:94:84
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <label for=\"panel-add-title\">Title</label><input id=\"panel-add-title\" name=\"panel_title\" required maxlength=\"128\">\n <label for=\"panel-add-query\">Saved query</label>\n <select id=\"panel-add-query\" name=\"saved_query_id\" required><option value=\"\">Choose a saved query</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:97:120
|
||||
for _, item := range view.SavedQueries {
|
||||
//line internal/site/dashboard.sando:97:163
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<option value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:97:182
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (item.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:97:192
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:97:198
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (item.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:97:210
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:97:222
|
||||
}
|
||||
//line internal/site/dashboard.sando:97:226
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</select>\n <label for=\"panel-add-visualization\">Presentation</label>\n <select id=\"panel-add-visualization\" name=\"visualization\" required><option value=\"table\">Table</option><option value=\"stat\">Single statistic with table</option><option value=\"timeseries\">Time series with table</option></select>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:100:14
|
||||
if len(view.SavedQueries) == 0 {
|
||||
//line internal/site/dashboard.sando:100:49
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<button type=\"submit\" disabled>Add panel</button>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:100:101
|
||||
} else {
|
||||
//line internal/site/dashboard.sando:100:112
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<button type=\"submit\">Add panel</button>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:100:155
|
||||
}
|
||||
//line internal/site/dashboard.sando:100:159
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </form>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:102:12
|
||||
}
|
||||
//line internal/site/dashboard.sando:102:16
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </div>\n </section>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:105:8
|
||||
}
|
||||
//line internal/site/dashboard.sando:105:12
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </main>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:107:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteFooter())); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/dashboard.sando:107:22
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n</body>\n</html>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func Explore(view ExploreView)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head><?~ Head(view.Head) ?></head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<?~ SiteHeader(true, "explore", view.Organization.ID) ?>
|
||||
<main id="main">
|
||||
<nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/app/?organization=<?= view.Organization.ID ?>">Overview</a><span aria-hidden="true">/</span><span aria-current="page">Explore</span></nav>
|
||||
<section class="app-intro" aria-labelledby="explore-title">
|
||||
<div><p class="eyebrow">Query workshop · <?= view.Organization.Name ?></p><h1 id="explore-title">Follow the evidence.</h1><p>Welcome, <?= view.DisplayName ?>. Write one bounded query, run it on the server, and inspect both the result and its cost.</p></div>
|
||||
</section>
|
||||
<aside class="live-status" data-events-url="<?= view.EventsURL ?>" hidden>
|
||||
<p><strong>New observations are available.</strong> Run the query again when you are ready; Observatory will not replace the result beneath you.</p>
|
||||
</aside>
|
||||
<section class="explore-layout" aria-labelledby="query-title">
|
||||
<div>
|
||||
<p class="eyebrow">Typed query</p>
|
||||
<h2 id="query-title">Ask a bounded question</h2>
|
||||
<p>Queries stay out of the URL and access log. The same parser, authorization scope, sensitivity rules, and execution limits used by the API are applied here.</p>
|
||||
<div class="quick-queries" aria-label="Example queries">
|
||||
<form method="post" action="/app/explore/?organization=<?= view.Organization.ID ?>"><input type="hidden" name="csrf_token" value="<?= view.CSRFToken ?>"><input type="hidden" name="query" value="logs | window 1h | limit 50"><button class="secondary" type="submit">Recent logs</button></form>
|
||||
<form method="post" action="/app/explore/?organization=<?= view.Organization.ID ?>"><input type="hidden" name="csrf_token" value="<?= view.CSRFToken ?>"><input type="hidden" name="query" value="metrics | window 1h | limit 50"><button class="secondary" type="submit">Recent metrics</button></form>
|
||||
<form method="post" action="/app/explore/?organization=<?= view.Organization.ID ?>"><input type="hidden" name="csrf_token" value="<?= view.CSRFToken ?>"><input type="hidden" name="query" value="traces | window 1h | limit 50"><button class="secondary" type="submit">Recent traces</button></form>
|
||||
<form method="post" action="/app/explore/?organization=<?= view.Organization.ID ?>"><input type="hidden" name="csrf_token" value="<?= view.CSRFToken ?>"><input type="hidden" name="query" value="deployments | window 24h | limit 50"><button class="secondary" type="submit">Recent deployments</button></form>
|
||||
</div>
|
||||
</div>
|
||||
<form class="editor-card query-workbench" method="post" action="/app/explore/?organization=<?= view.Organization.ID ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?= view.CSRFToken ?>">
|
||||
<label for="explore-query">Query</label>
|
||||
<textarea id="explore-query" name="query" required maxlength="16384" spellcheck="false"><?= view.Query ?></textarea>
|
||||
<p class="field-help">Begin with <code>logs</code>, <code>metrics</code>, <code>traces</code>, or <code>deployments</code>. Every query has bounded time, rows, bytes, and memory.</p>
|
||||
<button type="submit">Run query</button>
|
||||
</form>
|
||||
</section>
|
||||
<? if view.ErrorMessage != "" { ?><p class="form-error" role="alert"><?= view.ErrorMessage ?></p><? } ?>
|
||||
<? if view.Executed { ?>
|
||||
<section class="query-results" aria-labelledby="results-title">
|
||||
<div class="query-results-heading"><div><p class="eyebrow">Authorized result</p><h2 id="results-title">Query results</h2></div>
|
||||
<dl class="query-stats">
|
||||
<div><dt>Scanned rows</dt><dd><?= view.Stats.ScannedRows ?></dd></div>
|
||||
<div><dt>Matched rows</dt><dd><?= view.Stats.MatchedRows ?></dd></div>
|
||||
<div><dt>Scanned bytes</dt><dd><?= view.Stats.ScannedBytes ?></dd></div>
|
||||
<div><dt>Execution</dt><dd><?= view.Stats.Duration ?></dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
<? if view.Stats.Truncated { ?><p class="live-status"><strong>Result limit reached.</strong> Refine the query or deliberately choose a different bounded limit.</p><? } ?>
|
||||
<? if view.Stats.Approximate { ?><p class="live-status"><strong>Approximate result.</strong> This answer uses an authorized aggregate projection.</p><? } ?>
|
||||
<?~ ResultTable(view.Table) ?>
|
||||
</section>
|
||||
<? } ?>
|
||||
</main>
|
||||
<?~ SiteFooter() ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,269 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 970611f5a27597a2257f7ce7f38bc1184b99fa0801b9b0e3d09ea8bd53986827
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func Explore(view ExploreView) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/explore.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<!doctype html>\n<html lang=\"en\">\n<head>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:9:11
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (Head(view.Head))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:9:29
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</head>\n<body>\n <a class=\"skip-link\" href=\"#main\">Skip to content</a>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:12:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteHeader(true, "explore", view.Organization.ID))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:12:59
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <main id=\"main\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\"><a href=\"/app/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:14:86
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:14:109
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Overview</a><span aria-hidden=\"true\">/</span><span aria-current=\"page\">Explore</span></nav>\n <section class=\"app-intro\" aria-labelledby=\"explore-title\">\n <div><p class=\"eyebrow\">Query workshop · "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:16:53
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Organization.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:16:78
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p><h1 id=\"explore-title\">Follow the evidence.</h1><p>Welcome, "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:16:146
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.DisplayName)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:16:165
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, ". Write one bounded query, run it on the server, and inspect both the result and its cost.</p></div>\n </section>\n <aside class=\"live-status\" data-events-url=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:18:53
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.EventsURL)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:18:70
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" hidden>\n <p><strong>New observations are available.</strong> Run the query again when you are ready; Observatory will not replace the result beneath you.</p>\n </aside>\n <section class=\"explore-layout\" aria-labelledby=\"query-title\">\n <div>\n <p class=\"eyebrow\">Typed query</p>\n <h2 id=\"query-title\">Ask a bounded question</h2>\n <p>Queries stay out of the URL and access log. The same parser, authorization scope, sensitivity rules, and execution limits used by the API are applied here.</p>\n <div class=\"quick-queries\" aria-label=\"Example queries\">\n <form method=\"post\" action=\"/app/explore/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:27:70
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:27:93
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:27:145
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.CSRFToken)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:27:162
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"query\" value=\"logs | window 1h | limit 50\"><button class=\"secondary\" type=\"submit\">Recent logs</button></form>\n <form method=\"post\" action=\"/app/explore/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:28:70
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:28:93
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:28:145
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.CSRFToken)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:28:162
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"query\" value=\"metrics | window 1h | limit 50\"><button class=\"secondary\" type=\"submit\">Recent metrics</button></form>\n <form method=\"post\" action=\"/app/explore/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:29:70
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:29:93
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:29:145
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.CSRFToken)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:29:162
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"query\" value=\"traces | window 1h | limit 50\"><button class=\"secondary\" type=\"submit\">Recent traces</button></form>\n <form method=\"post\" action=\"/app/explore/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:30:70
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:30:93
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:30:145
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.CSRFToken)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:30:162
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"query\" value=\"deployments | window 24h | limit 50\"><button class=\"secondary\" type=\"submit\">Recent deployments</button></form>\n </div>\n </div>\n <form class=\"editor-card query-workbench\" method=\"post\" action=\"/app/explore/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:33:102
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:33:125
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:34:59
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.CSRFToken)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:34:76
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <label for=\"explore-query\">Query</label>\n <textarea id=\"explore-query\" name=\"query\" required maxlength=\"16384\" spellcheck=\"false\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:36:101
|
||||
if __himesan_error := __himesan_sando.WriteRCDATA(__himesan_writer, (view.Query)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:36:114
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</textarea>\n <p class=\"field-help\">Begin with <code>logs</code>, <code>metrics</code>, <code>traces</code>, or <code>deployments</code>. Every query has bounded time, rows, bytes, and memory.</p>\n <button type=\"submit\">Run query</button>\n </form>\n </section>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:41:8
|
||||
if view.ErrorMessage != "" {
|
||||
//line internal/site/explore.sando:41:39
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p class=\"form-error\" role=\"alert\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:41:78
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.ErrorMessage)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:41:98
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:41:105
|
||||
}
|
||||
//line internal/site/explore.sando:41:109
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:42:8
|
||||
if view.Executed {
|
||||
//line internal/site/explore.sando:42:29
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <section class=\"query-results\" aria-labelledby=\"results-title\">\n <div class=\"query-results-heading\"><div><p class=\"eyebrow\">Authorized result</p><h2 id=\"results-title\">Query results</h2></div>\n <dl class=\"query-stats\">\n <div><dt>Scanned rows</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:46:45
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Stats.ScannedRows)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:46:70
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div>\n <div><dt>Matched rows</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:47:45
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Stats.MatchedRows)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:47:70
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div>\n <div><dt>Scanned bytes</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:48:46
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Stats.ScannedBytes)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:48:72
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div>\n <div><dt>Execution</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:49:42
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Stats.Duration)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:49:64
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div>\n </dl>\n </div>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:52:10
|
||||
if view.Stats.Truncated {
|
||||
//line internal/site/explore.sando:52:38
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p class=\"live-status\"><strong>Result limit reached.</strong> Refine the query or deliberately choose a different bounded limit.</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:52:173
|
||||
}
|
||||
//line internal/site/explore.sando:52:177
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:53:10
|
||||
if view.Stats.Approximate {
|
||||
//line internal/site/explore.sando:53:40
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p class=\"live-status\"><strong>Approximate result.</strong> This answer uses an authorized aggregate projection.</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:53:159
|
||||
}
|
||||
//line internal/site/explore.sando:53:163
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:54:11
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (ResultTable(view.Table))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:54:37
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </section>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:56:8
|
||||
}
|
||||
//line internal/site/explore.sando:56:12
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </main>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:58:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteFooter())); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/explore.sando:58:22
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n</body>\n</html>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func Head(view HeadView)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="<?= view.Description ?>">
|
||||
<link rel="canonical" href="<?= view.CanonicalURL ?>">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<link rel="stylesheet" href="<?= view.Assets.StylePath ?>">
|
||||
<script src="<?= view.Assets.ScriptPath ?>" defer></script>
|
||||
<title><?= view.Title ?></title>
|
||||
@@ -0,0 +1,69 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 131f6248343bf4c87cd3a2a525a2050cee13ad29a2e178d5ef4e996c65590dc9
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func Head(view HeadView) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/head.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/head.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"description\" content=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/head.sando:9:39
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Description)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/head.sando:9:58
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n<link rel=\"canonical\" href=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/head.sando:10:33
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.CanonicalURL)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/head.sando:10:53
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n<link rel=\"manifest\" href=\"/manifest.webmanifest\">\n<link rel=\"stylesheet\" href=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/head.sando:12:34
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Assets.StylePath)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/head.sando:12:58
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n<script src=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/head.sando:13:18
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Assets.ScriptPath)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/head.sando:13:43
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" defer></script>\n<title>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/head.sando:14:12
|
||||
if __himesan_error := __himesan_sando.WriteRCDATA(__himesan_writer, (view.Title)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/head.sando:14:25
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</title>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func IncidentInbox(view IncidentInboxView)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head><?~ Head(view.Head) ?></head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<?~ SiteHeader(true, "incidents", view.Organization.ID) ?>
|
||||
<main id="main" data-open-incident-count="<?= view.OpenCount ?>">
|
||||
<nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/app/?organization=<?= view.Organization.ID ?>">Overview</a><span aria-hidden="true">/</span><span aria-current="page">Incidents</span></nav>
|
||||
<section class="app-intro" aria-labelledby="incident-title">
|
||||
<div><p class="eyebrow">Incident inbox · <?= view.Organization.Name ?></p><h1 id="incident-title">What needs attention?</h1><p>Welcome, <?= view.DisplayName ?>. Rules use bounded saved queries; this inbox keeps the human response durable and reviewable.</p></div>
|
||||
</section>
|
||||
<aside class="live-status" data-events-url="<?= view.EventsURL ?>" hidden><p><strong>Incident evidence changed.</strong> <a href="/app/incidents/?organization=<?= view.Organization.ID ?>">Refresh this inbox</a>.</p></aside>
|
||||
<section class="offline-control" aria-labelledby="offline-copy-title"><div><h2 id="offline-copy-title">Keep a careful offline copy</h2><p id="offline-copy-note">This is opt-in. The saved copy excludes response controls, CSRF material, query text, telemetry values, actors, and project, environment, or service identifiers.</p></div><div><button class="secondary" type="button" data-cache-inbox data-offline-source="<?= view.OfflineURL ?>" data-offline-target="<?= view.CacheKey ?>" aria-describedby="offline-copy-note" disabled>Save this inbox for offline use</button><p data-cache-status role="status" aria-live="polite">Offline saving requires service-worker support.</p></div></section>
|
||||
<? if view.PushPublicKey != "" { ?><section class="offline-control" aria-labelledby="push-title"><div><h2 id="push-title">Ask this browser to nudge you</h2><p id="push-note">Optional Web Push sends one fixed message through the browser vendor: “Gamertan Observatory needs your attention.” It never includes an organization, host, service, severity, rule, incident identifier, count, or telemetry text.</p></div><div><button class="secondary" type="button" data-push-toggle data-push-public-key="<?= view.PushPublicKey ?>" data-push-organization="<?= view.Organization.ID ?>" data-push-csrf="<?= view.PushCSRF ?>" aria-describedby="push-note" disabled>Enable private incident nudges</button><p data-push-status role="status" aria-live="polite">Push requires service-worker and notification support.</p></div></section><? } ?>
|
||||
<section class="section-grid" aria-labelledby="active-incidents">
|
||||
<div><p class="eyebrow">Durable state</p><h2 id="active-incidents">Incidents</h2></div>
|
||||
<div class="incident-list">
|
||||
<? if len(view.Incidents) == 0 { ?><p>No incidents have been opened.</p><? } ?>
|
||||
<? for _, incident := range view.Incidents { ?>
|
||||
<article class="incident-card" data-state="<?= incident.State ?>">
|
||||
<div><p class="eyebrow"><?= incident.Severity ?> · <?= incident.State ?></p><h3><?= incident.Title ?></h3></div>
|
||||
<dl><div><dt>Started</dt><dd><?= incident.StartedAt ?></dd></div><div><dt>Updated</dt><dd><?= incident.UpdatedAt ?></dd></div><? if incident.SilencedUntil != "" { ?><div><dt>Silenced until</dt><dd><?= incident.SilencedUntil ?></dd></div><? } ?></dl>
|
||||
<? if view.CanManage && incident.State != "resolved" { ?>
|
||||
<div class="incident-actions" aria-label="Actions for <?= incident.Title ?>">
|
||||
<form method="post" action="/app/incidents/<?= incident.ID ?>/"><input type="hidden" name="organization_id" value="<?= view.Organization.ID ?>"><input type="hidden" name="csrf_token" value="<?= view.ManageCSRF ?>"><input type="hidden" name="action" value="acknowledge"><input type="hidden" name="silence_duration" value=""><button class="secondary" type="submit">Acknowledge</button></form>
|
||||
<form method="post" action="/app/incidents/<?= incident.ID ?>/"><input type="hidden" name="organization_id" value="<?= view.Organization.ID ?>"><input type="hidden" name="csrf_token" value="<?= view.ManageCSRF ?>"><input type="hidden" name="action" value="silence"><label for="silence-<?= incident.ID ?>">Silence for</label><select id="silence-<?= incident.ID ?>" name="silence_duration"><option value="15m">15 minutes</option><option value="1h">1 hour</option><option value="6h">6 hours</option><option value="24h">24 hours</option><option value="168h">7 days</option></select><button class="secondary" type="submit">Silence</button></form>
|
||||
<form method="post" action="/app/incidents/<?= incident.ID ?>/"><input type="hidden" name="organization_id" value="<?= view.Organization.ID ?>"><input type="hidden" name="csrf_token" value="<?= view.ManageCSRF ?>"><input type="hidden" name="action" value="resolve"><input type="hidden" name="silence_duration" value=""><button class="secondary" type="submit">Resolve</button></form>
|
||||
</div>
|
||||
<? } ?>
|
||||
</article>
|
||||
<? } ?>
|
||||
</div>
|
||||
</section>
|
||||
<section class="section-grid" aria-labelledby="alert-rules">
|
||||
<div><p class="eyebrow">Bounded evaluation</p><h2 id="alert-rules">Alert rules</h2></div>
|
||||
<div class="saved-grid">
|
||||
<? if len(view.Rules) == 0 { ?><p>No alert rules yet.</p><? } ?>
|
||||
<? for _, rule := range view.Rules { ?><article><p class="eyebrow"><?= rule.Severity ?></p><h3><?= rule.Name ?></h3><p><?= rule.Description ?></p><dl><div><dt>Interval</dt><dd><?= rule.Interval ?></dd></div><div><dt>Status</dt><dd><? if rule.Enabled { ?>Enabled<? } else { ?>Disabled<? } ?></dd></div><? if rule.LastEvaluatedAt != "" { ?><div><dt>Last evaluated</dt><dd><?= rule.LastEvaluatedAt ?></dd></div><? } ?><? if rule.LastError != "" { ?><div><dt>Last result</dt><dd>Query unavailable</dd></div><? } ?></dl></article><? } ?>
|
||||
</div>
|
||||
</section>
|
||||
<? if view.CanManage { ?>
|
||||
<section class="section-grid" aria-labelledby="create-alert">
|
||||
<div><p class="eyebrow">Organization workshop</p><h2 id="create-alert">Create an alert rule</h2><p>A rule counts rows returned by one saved query. Consecutive matches allow brief noise to remain pending before an incident fires.</p></div>
|
||||
<form class="editor-card" method="post" action="/app/alert-rules/">
|
||||
<input type="hidden" name="organization_id" value="<?= view.Organization.ID ?>"><input type="hidden" name="csrf_token" value="<?= view.ManageCSRF ?>">
|
||||
<label for="alert-name">Name</label><input id="alert-name" name="name" required maxlength="128">
|
||||
<label for="alert-description">Description</label><textarea id="alert-description" name="description" required maxlength="1024"></textarea>
|
||||
<label for="alert-query">Saved query</label><select id="alert-query" name="saved_query_id" required><option value="">Choose a saved query</option><? for _, item := range view.SavedQueries { ?><option value="<?= item.ID ?>"><?= item.Name ?></option><? } ?></select>
|
||||
<div class="field-grid"><div><label for="alert-severity">Severity</label><select id="alert-severity" name="severity" required><option value="information">Information</option><option value="warning" selected>Warning</option><option value="critical">Critical</option></select></div><div><label for="alert-interval">Evaluate every</label><select id="alert-interval" name="evaluation_interval" required><option value="15s">15 seconds</option><option value="30s">30 seconds</option><option value="1m" selected>1 minute</option><option value="5m">5 minutes</option><option value="15m">15 minutes</option></select></div><div><label for="alert-matches">Minimum matching rows</label><input id="alert-matches" type="number" name="minimum_matches" min="1" max="100000" value="1" required></div><div><label for="alert-confirmations">Consecutive evaluations</label><input id="alert-confirmations" type="number" name="required_consecutive" min="1" max="10" value="2" required></div></div>
|
||||
<? if len(view.SavedQueries) == 0 { ?><button type="submit" disabled>Create alert rule</button><? } else { ?><button type="submit">Create alert rule</button><? } ?>
|
||||
</form>
|
||||
</section>
|
||||
<? } ?>
|
||||
</main>
|
||||
<?~ SiteFooter() ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,529 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 df123546e9ef420a7ce574a528a984505176d15c8e6233582468bde1e5bc9ea6
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func IncidentInbox(view IncidentInboxView) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/incidents.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<!doctype html>\n<html lang=\"en\">\n<head>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:9:11
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (Head(view.Head))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:9:29
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</head>\n<body>\n <a class=\"skip-link\" href=\"#main\">Skip to content</a>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:12:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteHeader(true, "incidents", view.Organization.ID))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:12:61
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <main id=\"main\" data-open-incident-count=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:13:49
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.OpenCount)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:13:66
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\"><a href=\"/app/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:14:86
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:14:109
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Overview</a><span aria-hidden=\"true\">/</span><span aria-current=\"page\">Incidents</span></nav>\n <section class=\"app-intro\" aria-labelledby=\"incident-title\">\n <div><p class=\"eyebrow\">Incident inbox · "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:16:53
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Organization.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:16:78
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p><h1 id=\"incident-title\">What needs attention?</h1><p>Welcome, "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:16:148
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.DisplayName)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:16:167
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, ". Rules use bounded saved queries; this inbox keeps the human response durable and reviewable.</p></div>\n </section>\n <aside class=\"live-status\" data-events-url=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:18:53
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.EventsURL)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:18:70
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" hidden><p><strong>Incident evidence changed.</strong> <a href=\"/app/incidents/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:18:168
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:18:191
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Refresh this inbox</a>.</p></aside>\n <section class=\"offline-control\" aria-labelledby=\"offline-copy-title\"><div><h2 id=\"offline-copy-title\">Keep a careful offline copy</h2><p id=\"offline-copy-note\">This is opt-in. The saved copy excludes response controls, CSRF material, query text, telemetry values, actors, and project, environment, or service identifiers.</p></div><div><button class=\"secondary\" type=\"button\" data-cache-inbox data-offline-source=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:19:424
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.OfflineURL)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:19:442
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" data-offline-target=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:19:469
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.CacheKey)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:19:485
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" aria-describedby=\"offline-copy-note\" disabled>Save this inbox for offline use</button><p data-cache-status role=\"status\" aria-live=\"polite\">Offline saving requires service-worker support.</p></div></section>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:20:8
|
||||
if view.PushPublicKey != "" {
|
||||
//line internal/site/incidents.sando:20:40
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<section class=\"offline-control\" aria-labelledby=\"push-title\"><div><h2 id=\"push-title\">Ask this browser to nudge you</h2><p id=\"push-note\">Optional Web Push sends one fixed message through the browser vendor: “Gamertan Observatory needs your attention.” It never includes an organization, host, service, severity, rule, incident identifier, count, or telemetry text.</p></div><div><button class=\"secondary\" type=\"button\" data-push-toggle data-push-public-key=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:20:508
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.PushPublicKey)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:20:529
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" data-push-organization=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:20:559
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:20:582
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" data-push-csrf=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:20:604
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.PushCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:20:620
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" aria-describedby=\"push-note\" disabled>Enable private incident nudges</button><p data-push-status role=\"status\" aria-live=\"polite\">Push requires service-worker and notification support.</p></div></section>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:20:829
|
||||
}
|
||||
//line internal/site/incidents.sando:20:833
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <section class=\"section-grid\" aria-labelledby=\"active-incidents\">\n <div><p class=\"eyebrow\">Durable state</p><h2 id=\"active-incidents\">Incidents</h2></div>\n <div class=\"incident-list\">\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:24:10
|
||||
if len(view.Incidents) == 0 {
|
||||
//line internal/site/incidents.sando:24:42
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p>No incidents have been opened.</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:24:82
|
||||
}
|
||||
//line internal/site/incidents.sando:24:86
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:25:10
|
||||
for _, incident := range view.Incidents {
|
||||
//line internal/site/incidents.sando:25:54
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <article class=\"incident-card\" data-state=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:26:56
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (incident.State)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:26:73
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <div><p class=\"eyebrow\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:27:39
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.Severity)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:27:59
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, " · "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:27:67
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.State)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:27:84
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p><h3>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:27:96
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.Title)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:27:113
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</h3></div>\n <dl><div><dt>Started</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:28:44
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.StartedAt)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:28:65
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div><div><dt>Updated</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:28:105
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.UpdatedAt)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:28:126
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:28:140
|
||||
if incident.SilencedUntil != "" {
|
||||
//line internal/site/incidents.sando:28:176
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<div><dt>Silenced until</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:28:212
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.SilencedUntil)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:28:237
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:28:251
|
||||
}
|
||||
//line internal/site/incidents.sando:28:255
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dl>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:29:14
|
||||
if view.CanManage && incident.State != "resolved" {
|
||||
//line internal/site/incidents.sando:29:68
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <div class=\"incident-actions\" aria-label=\"Actions for "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:30:69
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (incident.Title)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:30:86
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <form method=\"post\" action=\"/app/incidents/"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:31:60
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (incident.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:31:74
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "/\"><input type=\"hidden\" name=\"organization_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:31:132
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:31:155
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:31:207
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ManageCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:31:225
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"action\" value=\"acknowledge\"><input type=\"hidden\" name=\"silence_duration\" value=\"\"><button class=\"secondary\" type=\"submit\">Acknowledge</button></form>\n <form method=\"post\" action=\"/app/incidents/"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:32:60
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (incident.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:32:74
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "/\"><input type=\"hidden\" name=\"organization_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:32:132
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:32:155
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:32:207
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ManageCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:32:225
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"action\" value=\"silence\"><label for=\"silence-"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:32:302
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (incident.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:32:316
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Silence for</label><select id=\"silence-"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:32:361
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (incident.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:32:375
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\" name=\"silence_duration\"><option value=\"15m\">15 minutes</option><option value=\"1h\">1 hour</option><option value=\"6h\">6 hours</option><option value=\"24h\">24 hours</option><option value=\"168h\">7 days</option></select><button class=\"secondary\" type=\"submit\">Silence</button></form>\n <form method=\"post\" action=\"/app/incidents/"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:33:60
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (incident.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:33:74
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "/\"><input type=\"hidden\" name=\"organization_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:33:132
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:33:155
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:33:207
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ManageCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:33:225
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"action\" value=\"resolve\"><input type=\"hidden\" name=\"silence_duration\" value=\"\"><button class=\"secondary\" type=\"submit\">Resolve</button></form>\n </div>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:35:14
|
||||
}
|
||||
//line internal/site/incidents.sando:35:18
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </article>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:37:10
|
||||
}
|
||||
//line internal/site/incidents.sando:37:14
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </div>\n </section>\n <section class=\"section-grid\" aria-labelledby=\"alert-rules\">\n <div><p class=\"eyebrow\">Bounded evaluation</p><h2 id=\"alert-rules\">Alert rules</h2></div>\n <div class=\"saved-grid\">\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:43:10
|
||||
if len(view.Rules) == 0 {
|
||||
//line internal/site/incidents.sando:43:38
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p>No alert rules yet.</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:43:67
|
||||
}
|
||||
//line internal/site/incidents.sando:43:71
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:10
|
||||
for _, rule := range view.Rules {
|
||||
//line internal/site/incidents.sando:44:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<article><p class=\"eyebrow\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:78
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (rule.Severity)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:94
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p><h3>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:106
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (rule.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:118
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</h3><p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:130
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (rule.Description)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:149
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p><dl><div><dt>Interval</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:187
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (rule.Interval)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:203
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div><div><dt>Status</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:241
|
||||
if rule.Enabled {
|
||||
//line internal/site/incidents.sando:44:261
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "Enabled"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:271
|
||||
} else {
|
||||
//line internal/site/incidents.sando:44:282
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "Disabled"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:293
|
||||
}
|
||||
//line internal/site/incidents.sando:44:297
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:311
|
||||
if rule.LastEvaluatedAt != "" {
|
||||
//line internal/site/incidents.sando:44:345
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<div><dt>Last evaluated</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:381
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (rule.LastEvaluatedAt)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:404
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:418
|
||||
}
|
||||
//line internal/site/incidents.sando:44:425
|
||||
if rule.LastError != "" {
|
||||
//line internal/site/incidents.sando:44:453
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<div><dt>Last result</dt><dd>Query unavailable</dd></div>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:513
|
||||
}
|
||||
//line internal/site/incidents.sando:44:517
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dl></article>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:44:535
|
||||
}
|
||||
//line internal/site/incidents.sando:44:539
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </div>\n </section>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:47:8
|
||||
if view.CanManage {
|
||||
//line internal/site/incidents.sando:47:30
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <section class=\"section-grid\" aria-labelledby=\"create-alert\">\n <div><p class=\"eyebrow\">Organization workshop</p><h2 id=\"create-alert\">Create an alert rule</h2><p>A rule counts rows returned by one saved query. Consecutive matches allow brief noise to remain pending before an incident fires.</p></div>\n <form class=\"editor-card\" method=\"post\" action=\"/app/alert-rules/\">\n <input type=\"hidden\" name=\"organization_id\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:51:64
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Organization.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:51:87
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:51:139
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.ManageCSRF)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:51:157
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <label for=\"alert-name\">Name</label><input id=\"alert-name\" name=\"name\" required maxlength=\"128\">\n <label for=\"alert-description\">Description</label><textarea id=\"alert-description\" name=\"description\" required maxlength=\"1024\"></textarea>\n <label for=\"alert-query\">Saved query</label><select id=\"alert-query\" name=\"saved_query_id\" required><option value=\"\">Choose a saved query</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:54:158
|
||||
for _, item := range view.SavedQueries {
|
||||
//line internal/site/incidents.sando:54:201
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<option value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:54:220
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (item.ID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:54:230
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:54:236
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (item.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:54:248
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</option>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:54:260
|
||||
}
|
||||
//line internal/site/incidents.sando:54:264
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</select>\n <div class=\"field-grid\"><div><label for=\"alert-severity\">Severity</label><select id=\"alert-severity\" name=\"severity\" required><option value=\"information\">Information</option><option value=\"warning\" selected>Warning</option><option value=\"critical\">Critical</option></select></div><div><label for=\"alert-interval\">Evaluate every</label><select id=\"alert-interval\" name=\"evaluation_interval\" required><option value=\"15s\">15 seconds</option><option value=\"30s\">30 seconds</option><option value=\"1m\" selected>1 minute</option><option value=\"5m\">5 minutes</option><option value=\"15m\">15 minutes</option></select></div><div><label for=\"alert-matches\">Minimum matching rows</label><input id=\"alert-matches\" type=\"number\" name=\"minimum_matches\" min=\"1\" max=\"100000\" value=\"1\" required></div><div><label for=\"alert-confirmations\">Consecutive evaluations</label><input id=\"alert-confirmations\" type=\"number\" name=\"required_consecutive\" min=\"1\" max=\"10\" value=\"2\" required></div></div>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:56:12
|
||||
if len(view.SavedQueries) == 0 {
|
||||
//line internal/site/incidents.sando:56:47
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<button type=\"submit\" disabled>Create alert rule</button>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:56:107
|
||||
} else {
|
||||
//line internal/site/incidents.sando:56:118
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<button type=\"submit\">Create alert rule</button>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:56:169
|
||||
}
|
||||
//line internal/site/incidents.sando:56:173
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </form>\n </section>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:59:8
|
||||
}
|
||||
//line internal/site/incidents.sando:59:12
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </main>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:61:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteFooter())); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/incidents.sando:61:22
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n</body>\n</html>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func Landing(view LandingView)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head><?~ Head(view.Head) ?></head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<?~ SiteHeader(false, "", "") ?>
|
||||
<main id="main">
|
||||
<section class="hero" aria-labelledby="hero-title">
|
||||
<p class="eyebrow">Logs · metrics · traces · deployments</p>
|
||||
<h1 id="hero-title">Keep the evidence close.</h1>
|
||||
<p class="lede">Gamertan Observatory is a self-hosted observability platform being built for small, carefully operated Linux systems. It keeps raw evidence replayable, tenant boundaries explicit, and queries understandable.</p>
|
||||
<p><a class="button" href="/login/">Open Observatory</a></p>
|
||||
</section>
|
||||
<section class="section-grid" aria-labelledby="working-model">
|
||||
<div><p class="eyebrow">Working model</p><h2 id="working-model">One view without one giant trust boundary.</h2></div>
|
||||
<div class="cards">
|
||||
<article><h3>Evidence first</h3><p>Checksummed raw segments are committed before projections acknowledge an ingest. Indexes and dashboards remain rebuildable views.</p></article>
|
||||
<article><h3>Scoped by the server</h3><p>Organization and resource identity come from enrolled sources and authenticated grants—not from telemetry payloads or query text.</p></article>
|
||||
<article><h3>Bounded by default</h3><p>Ingestion, storage, queries, subscribers, and fields have explicit limits. Sensitive data requires a separate permission.</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section class="section-grid" aria-labelledby="preview-status">
|
||||
<div><p class="eyebrow">Current status</p><h2 id="preview-status">A private development preview.</h2></div>
|
||||
<div class="prose"><p>The foundations are under active development and are not a public release yet. Logs, metrics, traces, deployment records, organizations, typed queries, schema activation, and persisted dashboard definitions are being integrated before the first preview.</p><p>The interface remains useful without JavaScript. Progressive live updates add a small convenience; they do not own navigation, querying, or incident history.</p></div>
|
||||
</section>
|
||||
</main>
|
||||
<?~ SiteFooter() ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,53 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 7a68dcec190a95c15b15d7bba50a9ba2a067a270c7d1f00b0349f4fd3784ba3b
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func Landing(view LandingView) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/landing.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/landing.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<!doctype html>\n<html lang=\"en\">\n<head>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/landing.sando:9:11
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (Head(view.Head))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/landing.sando:9:29
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</head>\n<body>\n <a class=\"skip-link\" href=\"#main\">Skip to content</a>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/landing.sando:12:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteHeader(false, "", ""))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/landing.sando:12:35
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <main id=\"main\">\n <section class=\"hero\" aria-labelledby=\"hero-title\">\n <p class=\"eyebrow\">Logs · metrics · traces · deployments</p>\n <h1 id=\"hero-title\">Keep the evidence close.</h1>\n <p class=\"lede\">Gamertan Observatory is a self-hosted observability platform being built for small, carefully operated Linux systems. It keeps raw evidence replayable, tenant boundaries explicit, and queries understandable.</p>\n <p><a class=\"button\" href=\"/login/\">Open Observatory</a></p>\n </section>\n <section class=\"section-grid\" aria-labelledby=\"working-model\">\n <div><p class=\"eyebrow\">Working model</p><h2 id=\"working-model\">One view without one giant trust boundary.</h2></div>\n <div class=\"cards\">\n <article><h3>Evidence first</h3><p>Checksummed raw segments are committed before projections acknowledge an ingest. Indexes and dashboards remain rebuildable views.</p></article>\n <article><h3>Scoped by the server</h3><p>Organization and resource identity come from enrolled sources and authenticated grants—not from telemetry payloads or query text.</p></article>\n <article><h3>Bounded by default</h3><p>Ingestion, storage, queries, subscribers, and fields have explicit limits. Sensitive data requires a separate permission.</p></article>\n </div>\n </section>\n <section class=\"section-grid\" aria-labelledby=\"preview-status\">\n <div><p class=\"eyebrow\">Current status</p><h2 id=\"preview-status\">A private development preview.</h2></div>\n <div class=\"prose\"><p>The foundations are under active development and are not a public release yet. Logs, metrics, traces, deployment records, organizations, typed queries, schema activation, and persisted dashboard definitions are being integrated before the first preview.</p><p>The interface remains useful without JavaScript. Progressive live updates add a small convenience; they do not own navigation, querying, or incident history.</p></div>\n </section>\n </main>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/landing.sando:33:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteFooter())); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/landing.sando:33:22
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n</body>\n</html>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func Login(view LoginView)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head><?~ Head(view.Head) ?></head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<?~ SiteHeader(false, "", "") ?>
|
||||
<main id="main" class="auth-shell">
|
||||
<section class="auth-card" aria-labelledby="login-title">
|
||||
<p class="eyebrow">Private workshop</p>
|
||||
<h1 id="login-title">Sign in to Observatory</h1>
|
||||
<p>Use the local account created by <code>observatory admin bootstrap</code>.</p>
|
||||
<? if view.ErrorMessage != "" { ?><p class="form-error" role="alert"><?= view.ErrorMessage ?></p><? } ?>
|
||||
<form method="post" action="/login/">
|
||||
<input type="hidden" name="csrf_token" value="<?= view.CSRFToken ?>">
|
||||
<label for="identifier">Username or email</label>
|
||||
<input id="identifier" name="identifier" autocomplete="username" required maxlength="320">
|
||||
<label for="password">Password</label>
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" required maxlength="1024">
|
||||
<button type="submit">Sign in</button>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
<?~ SiteFooter() ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,81 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 dd75f02614b8688552456737ff2bfd5e9625897254989ab8ae2098b5fd5fccc9
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func Login(view LoginView) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/login.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<!doctype html>\n<html lang=\"en\">\n<head>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:9:11
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (Head(view.Head))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:9:29
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</head>\n<body>\n <a class=\"skip-link\" href=\"#main\">Skip to content</a>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:12:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteHeader(false, "", ""))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:12:35
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <main id=\"main\" class=\"auth-shell\">\n <section class=\"auth-card\" aria-labelledby=\"login-title\">\n <p class=\"eyebrow\">Private workshop</p>\n <h1 id=\"login-title\">Sign in to Observatory</h1>\n <p>Use the local account created by <code>observatory admin bootstrap</code>.</p>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:18:10
|
||||
if view.ErrorMessage != "" {
|
||||
//line internal/site/login.sando:18:41
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p class=\"form-error\" role=\"alert\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:18:80
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.ErrorMessage)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:18:100
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:18:107
|
||||
}
|
||||
//line internal/site/login.sando:18:111
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <form method=\"post\" action=\"/login/\">\n <input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:20:59
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.CSRFToken)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:20:76
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <label for=\"identifier\">Username or email</label>\n <input id=\"identifier\" name=\"identifier\" autocomplete=\"username\" required maxlength=\"320\">\n <label for=\"password\">Password</label>\n <input id=\"password\" name=\"password\" type=\"password\" autocomplete=\"current-password\" required maxlength=\"1024\">\n <button type=\"submit\">Sign in</button>\n </form>\n </section>\n </main>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:29:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteFooter())); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/login.sando:29:22
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n</body>\n</html>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func Offline(view OfflineView)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<!doctype html><html lang="en"><head><?~ Head(view.Head) ?></head><body><a class="skip-link" href="#main">Skip to content</a><?~ SiteHeader(false, "", "") ?><main id="main"><section class="hero"><p class="eyebrow">Offline</p><h1>The evidence is still safe.</h1><p class="lede">Observatory cannot reach the server right now. A previously saved incident inbox remains available from its usual address; otherwise, reconnect before viewing private organization data.</p><p><a class="button secondary" href="/app/">Try Observatory again</a></p></section></main><?~ SiteFooter() ?></body></html>
|
||||
@@ -0,0 +1,53 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 00fe79c601abcfc98b766fbb7332552b31cc92968251972bd24715f990812f09
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func Offline(view OfflineView) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/offline.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<!doctype html><html lang=\"en\"><head>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline.sando:7:42
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (Head(view.Head))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline.sando:7:60
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</head><body><a class=\"skip-link\" href=\"#main\">Skip to content</a>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline.sando:7:130
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteHeader(false, "", ""))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline.sando:7:158
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<main id=\"main\"><section class=\"hero\"><p class=\"eyebrow\">Offline</p><h1>The evidence is still safe.</h1><p class=\"lede\">Observatory cannot reach the server right now. A previously saved incident inbox remains available from its usual address; otherwise, reconnect before viewing private organization data.</p><p><a class=\"button secondary\" href=\"/app/\">Try Observatory again</a></p></section></main>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline.sando:7:561
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteFooter())); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline.sando:7:576
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</body></html>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func OfflineIncidentInbox(view OfflineIncidentView)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head><?~ Head(view.Head) ?></head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<?~ SiteHeader(true, "incidents", view.Organization.ID) ?>
|
||||
<main id="main" data-open-incident-count="<?= len(view.Incidents) ?>">
|
||||
<nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/app/">Overview</a><span aria-hidden="true">/</span><span aria-current="page">Saved incident inbox</span></nav>
|
||||
<section class="app-intro" aria-labelledby="offline-incident-title"><div><p class="eyebrow">Offline snapshot · <?= view.Organization.Name ?></p><h1 id="offline-incident-title">Saved incident inbox</h1><p>Captured <?= view.CapturedAt ?>. Response actions are intentionally unavailable offline.</p></div></section>
|
||||
<section class="section-grid" aria-labelledby="saved-incidents"><div><p class="eyebrow">Read-only</p><h2 id="saved-incidents">Incidents</h2></div><div class="incident-list">
|
||||
<? if len(view.Incidents) == 0 { ?><p>No open incidents were present in this snapshot.</p><? } ?>
|
||||
<? for _, incident := range view.Incidents { ?><article class="incident-card" data-state="<?= incident.State ?>"><p class="eyebrow"><?= incident.Severity ?> · <?= incident.State ?></p><h3><?= incident.Title ?></h3><dl><div><dt>Started</dt><dd><?= incident.StartedAt ?></dd></div><div><dt>Updated</dt><dd><?= incident.UpdatedAt ?></dd></div><? if incident.SilencedUntil != "" { ?><div><dt>Silenced until</dt><dd><?= incident.SilencedUntil ?></dd></div><? } ?></dl></article><? } ?>
|
||||
</div></section>
|
||||
<section class="section-grid" aria-labelledby="offline-boundary"><div><p class="eyebrow">Privacy boundary</p><h2 id="offline-boundary">This copy stays on this browser.</h2></div><div><p>It contains the organization name, incident titles, and states, but no response controls, CSRF tokens, query text, telemetry values, actor identifiers, or project, environment, or service identifiers. Signing out while online clears saved private snapshots.</p><p><a class="button secondary" href="/app/">Reconnect and refresh</a></p></div></section>
|
||||
</main>
|
||||
<?~ SiteFooter() ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,169 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 47e648ceed0893244cf41692e7a3d7ecc66a0cba09554e45b0d3dda3567bdfd7
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func OfflineIncidentInbox(view OfflineIncidentView) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/offline_incidents.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<!doctype html>\n<html lang=\"en\">\n<head>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:9:11
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (Head(view.Head))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:9:29
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</head>\n<body>\n <a class=\"skip-link\" href=\"#main\">Skip to content</a>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:12:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteHeader(true, "incidents", view.Organization.ID))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:12:61
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <main id=\"main\" data-open-incident-count=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:13:49
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (len(view.Incidents))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:13:71
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\"><a href=\"/app/\">Overview</a><span aria-hidden=\"true\">/</span><span aria-current=\"page\">Saved incident inbox</span></nav>\n <section class=\"app-intro\" aria-labelledby=\"offline-incident-title\"><div><p class=\"eyebrow\">Offline snapshot · "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:15:121
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Organization.Name)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:15:146
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p><h1 id=\"offline-incident-title\">Saved incident inbox</h1><p>Captured "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:15:223
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.CapturedAt)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:15:241
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, ". Response actions are intentionally unavailable offline.</p></div></section>\n <section class=\"section-grid\" aria-labelledby=\"saved-incidents\"><div><p class=\"eyebrow\">Read-only</p><h2 id=\"saved-incidents\">Incidents</h2></div><div class=\"incident-list\">\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:17:10
|
||||
if len(view.Incidents) == 0 {
|
||||
//line internal/site/offline_incidents.sando:17:42
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p>No open incidents were present in this snapshot.</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:17:100
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:17:104
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:10
|
||||
for _, incident := range view.Incidents {
|
||||
//line internal/site/offline_incidents.sando:18:54
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<article class=\"incident-card\" data-state=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:101
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (incident.State)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:118
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\"><p class=\"eyebrow\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:143
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.Severity)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:163
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, " · "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:171
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.State)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:188
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p><h3>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:200
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.Title)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:217
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</h3><dl><div><dt>Started</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:255
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.StartedAt)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:276
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div><div><dt>Updated</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:316
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.UpdatedAt)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:337
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:351
|
||||
if incident.SilencedUntil != "" {
|
||||
//line internal/site/offline_incidents.sando:18:387
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<div><dt>Silenced until</dt><dd>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:423
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (incident.SilencedUntil)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:448
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dd></div>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:462
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:466
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</dl></article>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:484
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:18:488
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </div></section>\n <section class=\"section-grid\" aria-labelledby=\"offline-boundary\"><div><p class=\"eyebrow\">Privacy boundary</p><h2 id=\"offline-boundary\">This copy stays on this browser.</h2></div><div><p>It contains the organization name, incident titles, and states, but no response controls, CSRF tokens, query text, telemetry values, actor identifiers, or project, environment, or service identifiers. Signing out while online clears saved private snapshots.</p><p><a class=\"button secondary\" href=\"/app/\">Reconnect and refresh</a></p></div></section>\n </main>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:22:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteFooter())); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/offline_incidents.sando:22:22
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n</body>\n</html>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func Password(view PasswordView)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head><?~ Head(view.Head) ?></head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<?~ SiteHeader(false, "", "") ?>
|
||||
<main id="main" class="auth-shell">
|
||||
<section class="auth-card" aria-labelledby="password-title">
|
||||
<p class="eyebrow">One careful first step</p>
|
||||
<h1 id="password-title">Choose your password</h1>
|
||||
<p>The bootstrap credential is single-purpose. Replace it now; Observatory will end every session and ask you to sign in again.</p>
|
||||
<? if view.ErrorMessage != "" { ?><p class="form-error" role="alert"><?= view.ErrorMessage ?></p><? } ?>
|
||||
<form method="post" action="/account/password/">
|
||||
<input type="hidden" name="csrf_token" value="<?= view.CSRFToken ?>">
|
||||
<label for="current-password">Temporary password</label>
|
||||
<input id="current-password" name="current_password" type="password" autocomplete="current-password" required maxlength="1024">
|
||||
<label for="new-password">New password</label>
|
||||
<input id="new-password" name="new_password" type="password" autocomplete="new-password" required minlength="12" maxlength="1024">
|
||||
<label for="confirm-password">Confirm new password</label>
|
||||
<input id="confirm-password" name="confirm_password" type="password" autocomplete="new-password" required minlength="12" maxlength="1024">
|
||||
<button type="submit">Change password and sign out</button>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
<?~ SiteFooter() ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,81 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 75a8b350f1635bc7a9ba4feb50aa4bc3b151783a3ebee72fc0e1947f7787c320
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func Password(view PasswordView) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/password.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<!doctype html>\n<html lang=\"en\">\n<head>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:9:11
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (Head(view.Head))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:9:29
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</head>\n<body>\n <a class=\"skip-link\" href=\"#main\">Skip to content</a>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:12:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteHeader(false, "", ""))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:12:35
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <main id=\"main\" class=\"auth-shell\">\n <section class=\"auth-card\" aria-labelledby=\"password-title\">\n <p class=\"eyebrow\">One careful first step</p>\n <h1 id=\"password-title\">Choose your password</h1>\n <p>The bootstrap credential is single-purpose. Replace it now; Observatory will end every session and ask you to sign in again.</p>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:18:10
|
||||
if view.ErrorMessage != "" {
|
||||
//line internal/site/password.sando:18:41
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<p class=\"form-error\" role=\"alert\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:18:80
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.ErrorMessage)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:18:100
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</p>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:18:107
|
||||
}
|
||||
//line internal/site/password.sando:18:111
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <form method=\"post\" action=\"/account/password/\">\n <input type=\"hidden\" name=\"csrf_token\" value=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:20:59
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.CSRFToken)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:20:76
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <label for=\"current-password\">Temporary password</label>\n <input id=\"current-password\" name=\"current_password\" type=\"password\" autocomplete=\"current-password\" required maxlength=\"1024\">\n <label for=\"new-password\">New password</label>\n <input id=\"new-password\" name=\"new_password\" type=\"password\" autocomplete=\"new-password\" required minlength=\"12\" maxlength=\"1024\">\n <label for=\"confirm-password\">Confirm new password</label>\n <input id=\"confirm-password\" name=\"confirm_password\" type=\"password\" autocomplete=\"new-password\" required minlength=\"12\" maxlength=\"1024\">\n <button type=\"submit\">Change password and sign out</button>\n </form>\n </section>\n </main>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:31:7
|
||||
if __himesan_error := __himesan_sando.Render(__himesan_render_context, __himesan_writer, (SiteFooter())); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/password.sando:31:22
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n</body>\n</html>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func WebManifest() []byte {
|
||||
assets := AssetPaths()
|
||||
return []byte(fmt.Sprintf(`{"id":"/app/","name":"Gamertan Observatory","short_name":"Observatory","description":"A self-hosted, organization-aware observability workshop.","start_url":"/app/","scope":"/","display":"standalone","background_color":"#111715","theme_color":"#111715","icons":[{"src":%q,"sizes":"any","type":"image/svg+xml","purpose":"any maskable"}]}`+"\n", assets.IconPath))
|
||||
}
|
||||
|
||||
func ServiceWorker() []byte {
|
||||
assets := AssetPaths()
|
||||
revisionSource := append(append(append([]byte(nil), style...), script...), icon...)
|
||||
revision := sha256.Sum256(revisionSource)
|
||||
return []byte(fmt.Sprintf(`/* SPDX-License-Identifier: AGPL-3.0-only */
|
||||
"use strict";
|
||||
const SHELL_CACHE = %q;
|
||||
const PRIVATE_CACHE = "observatory-private-v1";
|
||||
const PUSH_MESSAGE = %q;
|
||||
const PUSH_ICON = %q;
|
||||
const SHELL = [%q,%q,%q,%q];
|
||||
self.addEventListener("install", event => {
|
||||
event.waitUntil(caches.open(SHELL_CACHE).then(cache => cache.addAll(SHELL)));
|
||||
self.skipWaiting();
|
||||
});
|
||||
self.addEventListener("activate", event => {
|
||||
event.waitUntil((async () => {
|
||||
for (const name of await caches.keys()) {
|
||||
if (name.startsWith("observatory-shell-") && name !== SHELL_CACHE) await caches.delete(name);
|
||||
}
|
||||
await self.clients.claim();
|
||||
})());
|
||||
});
|
||||
self.addEventListener("fetch", event => {
|
||||
const request = event.request;
|
||||
if (request.method !== "GET") return;
|
||||
const url = new URL(request.url);
|
||||
if (url.origin !== self.location.origin) return;
|
||||
if (request.mode === "navigate") {
|
||||
event.respondWith((async () => {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.status < 500) return response;
|
||||
const saved = await caches.open(PRIVATE_CACHE).then(cache => cache.match(request));
|
||||
return saved || response;
|
||||
} catch (_) {
|
||||
const saved = await caches.open(PRIVATE_CACHE).then(cache => cache.match(request));
|
||||
return saved || await caches.match("/offline/");
|
||||
}
|
||||
})());
|
||||
return;
|
||||
}
|
||||
if (SHELL.includes(url.pathname)) {
|
||||
event.respondWith(caches.match(request).then(saved => saved || fetch(request)));
|
||||
}
|
||||
});
|
||||
self.addEventListener("message", event => {
|
||||
const reply = value => { if (event.ports[0]) event.ports[0].postMessage(value); };
|
||||
if (!event.data || typeof event.data.type !== "string") return;
|
||||
if (event.data.type === "clear-private") {
|
||||
event.waitUntil(caches.delete(PRIVATE_CACHE).then(() => reply({ok:true})));
|
||||
return;
|
||||
}
|
||||
if (event.data.type !== "cache-inbox") return;
|
||||
event.waitUntil((async () => {
|
||||
try {
|
||||
const source = new URL(event.data.source, self.location.origin);
|
||||
const target = new URL(event.data.target, self.location.origin);
|
||||
const valid = source.origin === self.location.origin && target.origin === self.location.origin &&
|
||||
source.pathname === "/app/incidents/offline/" && target.pathname === "/app/incidents/" &&
|
||||
source.search === target.search && source.searchParams.size === 1 && source.searchParams.has("organization");
|
||||
if (!valid) throw new Error("invalid inbox cache request");
|
||||
const response = await fetch(source.href, {credentials:"include",cache:"no-store"});
|
||||
if (!response.ok || !(response.headers.get("content-type") || "").startsWith("text/html")) throw new Error("offline inbox unavailable");
|
||||
const cache = await caches.open(PRIVATE_CACHE);
|
||||
await cache.put(new Request(target.href, {method:"GET"}), response);
|
||||
reply({ok:true});
|
||||
} catch (_) { reply({ok:false}); }
|
||||
})());
|
||||
});
|
||||
self.addEventListener("push", event => {
|
||||
event.waitUntil(self.registration.showNotification(PUSH_MESSAGE, {icon:PUSH_ICON,badge:PUSH_ICON,tag:"observatory-attention",renotify:true,data:{url:"/app/"}}));
|
||||
});
|
||||
self.addEventListener("notificationclick", event => {
|
||||
event.notification.close();
|
||||
event.waitUntil((async () => {
|
||||
for (const client of await self.clients.matchAll({type:"window",includeUncontrolled:true})) {
|
||||
if (new URL(client.url).origin === self.location.origin) {
|
||||
await client.navigate("/app/");
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
return self.clients.openWindow("/app/");
|
||||
})());
|
||||
});
|
||||
`, "observatory-shell-"+hex.EncodeToString(revision[:8]), "Gamertan Observatory needs your attention.", assets.IconPath, "/offline/", assets.StylePath, assets.ScriptPath, assets.IconPath))
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestManifestAndServiceWorkerUseExactContentAddressedShell(t *testing.T) {
|
||||
var manifest struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
StartURL string `json:"start_url"`
|
||||
Scope string `json:"scope"`
|
||||
Display string `json:"display"`
|
||||
Icons []struct {
|
||||
Source string `json:"src"`
|
||||
Sizes string `json:"sizes"`
|
||||
Type string `json:"type"`
|
||||
Purpose string `json:"purpose"`
|
||||
} `json:"icons"`
|
||||
}
|
||||
if err := json.Unmarshal(WebManifest(), &manifest); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assets := AssetPaths()
|
||||
if manifest.ID != "/app/" || manifest.Name != "Gamertan Observatory" || manifest.StartURL != "/app/" || manifest.Scope != "/" || manifest.Display != "standalone" || len(manifest.Icons) != 1 || manifest.Icons[0].Source != assets.IconPath || manifest.Icons[0].Sizes != "any" || manifest.Icons[0].Type != "image/svg+xml" || manifest.Icons[0].Purpose != "any maskable" {
|
||||
t.Fatalf("manifest=%+v", manifest)
|
||||
}
|
||||
|
||||
worker := string(ServiceWorker())
|
||||
for _, required := range []string{"/offline/", assets.StylePath, assets.ScriptPath, assets.IconPath, "cache-inbox", "clear-private", "credentials:\"include\"", "source.pathname === \"/app/incidents/offline/\"", "target.pathname === \"/app/incidents/\"", "Gamertan Observatory needs your attention.", `self.addEventListener("push"`, `self.addEventListener("notificationclick"`, `data:{url:"/app/"}`} {
|
||||
if !strings.Contains(worker, required) {
|
||||
t.Fatalf("service worker missing %q", required)
|
||||
}
|
||||
}
|
||||
for _, forbidden := range []string{"query_text", "csrf_token", "telemetry", "incident.title", "organization_id", "service_id", "severity", "rule_id", "console.log", "eval("} {
|
||||
if strings.Contains(worker, forbidden) {
|
||||
t.Fatalf("service worker contains forbidden %q", forbidden)
|
||||
}
|
||||
}
|
||||
if strings.Count(worker, "Gamertan Observatory needs your attention.") != 1 {
|
||||
t.Fatal("service worker did not contain exactly one fixed notification message")
|
||||
}
|
||||
if first, second := string(ServiceWorker()), string(ServiceWorker()); first != second {
|
||||
t.Fatal("service worker output is not deterministic")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func ResultTable(view TableView)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<div class="table-scroll" tabindex="0" role="region" aria-label="<?= view.Caption ?>">
|
||||
<table>
|
||||
<caption><?= view.Caption ?></caption>
|
||||
<thead><tr>
|
||||
<? for _, column := range view.Columns { ?>
|
||||
<th scope="col"><?= column.Label ?><? if column.Unit != "" { ?> <span class="unit">(<?= column.Unit ?>)</span><? } ?></th>
|
||||
<? } ?>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<? for _, row := range view.Rows { ?>
|
||||
<tr><? for _, value := range row.Values { ?><td><?= value ?></td><? } ?></tr>
|
||||
<? } ?>
|
||||
<? if len(view.Rows) == 0 { ?>
|
||||
<tr><td colspan="<?= len(view.Columns) ?>"><?= view.Empty ?></td></tr>
|
||||
<? } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,141 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 bf3381b912a1264323672beac6aeed2a2ddb770904404f9597645ac09d2981ae
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func ResultTable(view TableView) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/result_table.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<div class=\"table-scroll\" tabindex=\"0\" role=\"region\" aria-label=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:7:70
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (view.Caption)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:7:85
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">\n <table>\n <caption>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:9:18
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Caption)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:9:33
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</caption>\n <thead><tr>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:11:8
|
||||
for _, column := range view.Columns {
|
||||
//line internal/site/result_table.sando:11:48
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <th scope=\"col\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:12:27
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (column.Label)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:12:45
|
||||
if column.Unit != "" {
|
||||
//line internal/site/result_table.sando:12:70
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, " <span class=\"unit\">("); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:12:95
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (column.Unit)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:12:109
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, ")</span>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:12:120
|
||||
}
|
||||
//line internal/site/result_table.sando:12:124
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</th>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:13:8
|
||||
}
|
||||
//line internal/site/result_table.sando:13:12
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </tr></thead>\n <tbody>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:16:8
|
||||
for _, row := range view.Rows {
|
||||
//line internal/site/result_table.sando:16:42
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <tr>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:17:14
|
||||
for _, value := range row.Values {
|
||||
//line internal/site/result_table.sando:17:51
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<td>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:17:59
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (value)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:17:67
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</td>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:17:75
|
||||
}
|
||||
//line internal/site/result_table.sando:17:79
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</tr>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:18:8
|
||||
}
|
||||
//line internal/site/result_table.sando:18:12
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:19:8
|
||||
if len(view.Rows) == 0 {
|
||||
//line internal/site/result_table.sando:19:35
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <tr><td colspan=\""); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:20:28
|
||||
if __himesan_error := __himesan_sando.WriteAttr(__himesan_writer, (len(view.Columns))); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:20:48
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:20:54
|
||||
if __himesan_error := __himesan_sando.WriteText(__himesan_writer, (view.Empty)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:20:67
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "</td></tr>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/result_table.sando:21:8
|
||||
}
|
||||
//line internal/site/result_table.sando:21:12
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </tbody>\n </table>\n</div>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func SiteFooter()
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<footer class="site-footer">
|
||||
<p>Self-hosted, evidence-minded observability. Built and tended by Gamertan.</p>
|
||||
</footer>
|
||||
@@ -0,0 +1,29 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 df9cf0c1ef86d0317606ba4eccb618cfe2d0925ef83dd572df78beaa6d2c254f
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func SiteFooter() __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/site_footer.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_footer.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<footer class=\"site-footer\">\n <p>Self-hosted, evidence-minded observability. Built and tended by Gamertan.</p>\n</footer>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?sando go
|
||||
package site
|
||||
|
||||
func SiteHeader(authenticated bool, current string, organizationID string)
|
||||
?>
|
||||
<?# SPDX-License-Identifier: AGPL-3.0-only ?>
|
||||
<header class="site-header">
|
||||
<? if authenticated { ?>
|
||||
<a class="wordmark" href="/app/?organization=<?= organizationID ?>">Gamertan Observatory</a>
|
||||
<nav aria-label="Primary">
|
||||
<? if current == "overview" { ?><a aria-current="page" href="/app/?organization=<?= organizationID ?>">Overview</a><? } else { ?><a href="/app/?organization=<?= organizationID ?>">Overview</a><? } ?>
|
||||
<? if current == "explore" { ?><a aria-current="page" href="/app/explore/?organization=<?= organizationID ?>">Explore</a><? } else { ?><a href="/app/explore/?organization=<?= organizationID ?>">Explore</a><? } ?>
|
||||
<? if current == "dashboards" { ?><a aria-current="page" href="/app/?organization=<?= organizationID ?>#dashboards">Dashboards</a><? } else { ?><a href="/app/?organization=<?= organizationID ?>#dashboards">Dashboards</a><? } ?>
|
||||
<? if current == "incidents" { ?><a aria-current="page" href="/app/incidents/?organization=<?= organizationID ?>">Incidents</a><? } else { ?><a href="/app/incidents/?organization=<?= organizationID ?>">Incidents</a><? } ?>
|
||||
</nav>
|
||||
<? } else { ?>
|
||||
<a class="wordmark" href="/">Gamertan Observatory</a>
|
||||
<nav aria-label="Primary"><a href="/login/">Sign in</a></nav>
|
||||
<? } ?>
|
||||
</header>
|
||||
@@ -0,0 +1,191 @@
|
||||
// Code generated by himesan; DO NOT EDIT.
|
||||
// himesan:compiler v1.0.0-beta.2
|
||||
// himesan:runtime-abi sando.v1
|
||||
// himesan:source-sha256 5e30e1ad98c586ffdc566d3688d3288d918faf5645804cd18ef6233a1e250e6b
|
||||
|
||||
package site
|
||||
|
||||
import (
|
||||
__himesan_context "context"
|
||||
__himesan_sando "gamertan.com/sandwich-hime/sando"
|
||||
__himesan_io "io"
|
||||
)
|
||||
|
||||
var _ = __himesan_sando.ABISandoV1
|
||||
|
||||
func SiteHeader(authenticated bool, current string, organizationID string) __himesan_sando.Component {
|
||||
return __himesan_sando.ComponentFunc(func(__himesan_render_context __himesan_context.Context, __himesan_writer __himesan_io.Writer) error {
|
||||
_ = __himesan_render_context
|
||||
//line internal/site/site_header.sando:5:3
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:6:46
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n<header class=\"site-header\">\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:8:6
|
||||
if authenticated {
|
||||
//line internal/site/site_header.sando:8:27
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <a class=\"wordmark\" href=\"/app/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:9:54
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (organizationID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:9:71
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Gamertan Observatory</a>\n <nav aria-label=\"Primary\">\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:11:10
|
||||
if current == "overview" {
|
||||
//line internal/site/site_header.sando:11:39
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<a aria-current=\"page\" href=\"/app/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:11:91
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (organizationID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:11:108
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Overview</a>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:11:125
|
||||
} else {
|
||||
//line internal/site/site_header.sando:11:136
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<a href=\"/app/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:11:168
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (organizationID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:11:185
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Overview</a>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:11:202
|
||||
}
|
||||
//line internal/site/site_header.sando:11:206
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:12:10
|
||||
if current == "explore" {
|
||||
//line internal/site/site_header.sando:12:38
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<a aria-current=\"page\" href=\"/app/explore/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:12:98
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (organizationID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:12:115
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Explore</a>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:12:131
|
||||
} else {
|
||||
//line internal/site/site_header.sando:12:142
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<a href=\"/app/explore/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:12:182
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (organizationID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:12:199
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Explore</a>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:12:215
|
||||
}
|
||||
//line internal/site/site_header.sando:12:219
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:13:10
|
||||
if current == "dashboards" {
|
||||
//line internal/site/site_header.sando:13:41
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<a aria-current=\"page\" href=\"/app/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:13:93
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (organizationID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:13:110
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "#dashboards\">Dashboards</a>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:13:140
|
||||
} else {
|
||||
//line internal/site/site_header.sando:13:151
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<a href=\"/app/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:13:183
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (organizationID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:13:200
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "#dashboards\">Dashboards</a>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:13:230
|
||||
}
|
||||
//line internal/site/site_header.sando:13:234
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:14:10
|
||||
if current == "incidents" {
|
||||
//line internal/site/site_header.sando:14:40
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<a aria-current=\"page\" href=\"/app/incidents/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:14:102
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (organizationID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:14:119
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Incidents</a>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:14:137
|
||||
} else {
|
||||
//line internal/site/site_header.sando:14:148
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "<a href=\"/app/incidents/?organization="); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:14:190
|
||||
if __himesan_error := __himesan_sando.WriteURL(__himesan_writer, (organizationID)); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:14:207
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\">Incidents</a>"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:14:225
|
||||
}
|
||||
//line internal/site/site_header.sando:14:229
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n </nav>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:16:6
|
||||
} else {
|
||||
//line internal/site/site_header.sando:16:17
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n <a class=\"wordmark\" href=\"/\">Gamertan Observatory</a>\n <nav aria-label=\"Primary\"><a href=\"/login/\">Sign in</a></nav>\n "); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
//line internal/site/site_header.sando:19:6
|
||||
}
|
||||
//line internal/site/site_header.sando:19:10
|
||||
if __himesan_error := __himesan_sando.WriteString(__himesan_writer, "\n</header>\n"); __himesan_error != nil {
|
||||
return __himesan_error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package site
|
||||
|
||||
type HeadView struct {
|
||||
Title string
|
||||
Description string
|
||||
CanonicalURL string
|
||||
Assets Assets
|
||||
}
|
||||
|
||||
type LandingView struct{ Head HeadView }
|
||||
|
||||
type LoginView struct {
|
||||
Head HeadView
|
||||
CSRFToken string
|
||||
ErrorMessage string
|
||||
}
|
||||
|
||||
type PasswordView struct {
|
||||
Head HeadView
|
||||
CSRFToken string
|
||||
ErrorMessage string
|
||||
}
|
||||
|
||||
type OrganizationOption struct {
|
||||
ID string
|
||||
Name string
|
||||
Selected bool
|
||||
}
|
||||
|
||||
type TableColumn struct {
|
||||
Label string
|
||||
Unit string
|
||||
}
|
||||
|
||||
type TableRow struct{ Values []string }
|
||||
|
||||
type TableView struct {
|
||||
Caption string
|
||||
Columns []TableColumn
|
||||
Rows []TableRow
|
||||
Empty string
|
||||
}
|
||||
|
||||
type SignalView struct {
|
||||
ID string
|
||||
Name string
|
||||
Description string
|
||||
Query string
|
||||
Table TableView
|
||||
}
|
||||
|
||||
type SavedQuerySummary struct {
|
||||
ID string
|
||||
Name string
|
||||
Description string
|
||||
Query string
|
||||
}
|
||||
|
||||
type DashboardSummary struct {
|
||||
Slug string
|
||||
Name string
|
||||
Description string
|
||||
PanelCount int
|
||||
}
|
||||
|
||||
type AppView struct {
|
||||
Head HeadView
|
||||
DisplayName string
|
||||
Organizations []OrganizationOption
|
||||
Organization OrganizationOption
|
||||
Signals []SignalView
|
||||
SavedQueries []SavedQuerySummary
|
||||
Dashboards []DashboardSummary
|
||||
CSRFToken string
|
||||
ManageCSRF string
|
||||
CanManage bool
|
||||
EventsURL string
|
||||
RefreshedAt string
|
||||
ProjectionLag string
|
||||
PendingBatches int
|
||||
IncidentsURL string
|
||||
OpenIncidents int
|
||||
}
|
||||
|
||||
type QueryStatsView struct {
|
||||
ScannedRows int
|
||||
MatchedRows int
|
||||
ScannedBytes string
|
||||
Duration string
|
||||
Truncated bool
|
||||
Approximate bool
|
||||
}
|
||||
|
||||
type ExploreView struct {
|
||||
Head HeadView
|
||||
DisplayName string
|
||||
Organization OrganizationOption
|
||||
Query string
|
||||
CSRFToken string
|
||||
EventsURL string
|
||||
Executed bool
|
||||
ErrorMessage string
|
||||
Table TableView
|
||||
Stats QueryStatsView
|
||||
}
|
||||
|
||||
type IncidentSummary struct {
|
||||
ID string
|
||||
Title string
|
||||
State string
|
||||
Severity string
|
||||
StartedAt string
|
||||
UpdatedAt string
|
||||
SilencedUntil string
|
||||
}
|
||||
|
||||
type AlertRuleSummary struct {
|
||||
Name string
|
||||
Description string
|
||||
Severity string
|
||||
Enabled bool
|
||||
Interval string
|
||||
LastEvaluatedAt string
|
||||
LastError string
|
||||
}
|
||||
|
||||
type IncidentInboxView struct {
|
||||
Head HeadView
|
||||
DisplayName string
|
||||
Organization OrganizationOption
|
||||
Incidents []IncidentSummary
|
||||
Rules []AlertRuleSummary
|
||||
SavedQueries []SavedQuerySummary
|
||||
CanManage bool
|
||||
ManageCSRF string
|
||||
EventsURL string
|
||||
OfflineURL string
|
||||
CacheKey string
|
||||
OpenCount int
|
||||
PushPublicKey string
|
||||
PushCSRF string
|
||||
}
|
||||
|
||||
type OfflineIncidentView struct {
|
||||
Head HeadView
|
||||
Organization OrganizationOption
|
||||
Incidents []IncidentSummary
|
||||
CapturedAt string
|
||||
}
|
||||
|
||||
type OfflineView struct{ Head HeadView }
|
||||
|
||||
type PanelView struct {
|
||||
ID string
|
||||
SavedQueryID string
|
||||
Title string
|
||||
Visualization string
|
||||
Query string
|
||||
Stat string
|
||||
Chart ChartView
|
||||
Table TableView
|
||||
}
|
||||
|
||||
type ChartPoint struct {
|
||||
Label string
|
||||
Value string
|
||||
Maximum string
|
||||
Display string
|
||||
}
|
||||
|
||||
type ChartView struct {
|
||||
Label string
|
||||
Points []ChartPoint
|
||||
}
|
||||
|
||||
type DashboardView struct {
|
||||
Head HeadView
|
||||
DisplayName string
|
||||
Organization OrganizationOption
|
||||
ID string
|
||||
Slug string
|
||||
Revision int
|
||||
Name string
|
||||
Description string
|
||||
ExportURL string
|
||||
Panels []PanelView
|
||||
SavedQueries []SavedQuerySummary
|
||||
CanManage bool
|
||||
ManageCSRF string
|
||||
}
|
||||
Reference in New Issue
Block a user