Split embedded and standalone chart surfaces
This commit is contained in:
10
web/embed.go
10
web/embed.go
@@ -16,6 +16,8 @@ var pageTemplate = template.Must(template.New("view.html").Funcs(template.FuncMa
|
||||
"joinLines": joinLines,
|
||||
}).ParseFS(content, "templates/view.html"))
|
||||
|
||||
var uploadTemplate = template.Must(template.New("upload.html").ParseFS(content, "templates/upload.html"))
|
||||
|
||||
func Render(data any) ([]byte, error) {
|
||||
var out strings.Builder
|
||||
if err := pageTemplate.ExecuteTemplate(&out, "view.html", data); err != nil {
|
||||
@@ -24,6 +26,14 @@ func Render(data any) ([]byte, error) {
|
||||
return []byte(out.String()), nil
|
||||
}
|
||||
|
||||
func RenderUpload(data any) ([]byte, error) {
|
||||
var out strings.Builder
|
||||
if err := uploadTemplate.ExecuteTemplate(&out, "upload.html", data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(out.String()), nil
|
||||
}
|
||||
|
||||
func Static() http.Handler {
|
||||
sub, err := fs.Sub(content, "static")
|
||||
if err != nil {
|
||||
|
||||
@@ -52,9 +52,10 @@ body {
|
||||
padding: 20px 0 32px;
|
||||
}
|
||||
|
||||
.input-panel,
|
||||
.empty-panel,
|
||||
.meta-panel,
|
||||
.section-card {
|
||||
.section-card,
|
||||
.upload-panel {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
@@ -63,30 +64,63 @@ body {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.input-label,
|
||||
.empty-panel h2,
|
||||
.section-card h2,
|
||||
.meta-panel h2 {
|
||||
.meta-panel h2,
|
||||
.upload-panel h2 {
|
||||
display: block;
|
||||
margin: 0 0 12px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
.empty-panel p,
|
||||
.upload-panel p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.upload-panel {
|
||||
width: min(680px, 100%);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.upload-dropzone {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 18px;
|
||||
background: #faf6ef;
|
||||
}
|
||||
|
||||
.upload-dropzone input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 220px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
background: #fcfaf6;
|
||||
color: var(--ink);
|
||||
font: 13px/1.5 "SFMono-Regular", Menlo, Monaco, Consolas, monospace;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.input-actions {
|
||||
margin-top: 12px;
|
||||
.upload-eyebrow {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
color: var(--accent);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
button {
|
||||
.upload-dropzone strong {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.upload-actions {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.upload-actions button {
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: var(--accent);
|
||||
@@ -98,7 +132,7 @@ button {
|
||||
}
|
||||
|
||||
.error-box {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--crit-fg);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
@@ -121,6 +155,24 @@ button {
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.sections-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-card-half {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.section-card-full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.kv-table,
|
||||
.data-table {
|
||||
width: 100%;
|
||||
@@ -154,6 +206,19 @@ button {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table-group + .table-group {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.table-group h3 {
|
||||
margin: 0 0 8px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.data-table thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@@ -198,6 +263,10 @@ button {
|
||||
width: min(100vw - 20px, 1500px);
|
||||
}
|
||||
|
||||
.sections-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: 18px 14px 14px;
|
||||
}
|
||||
@@ -206,9 +275,15 @@ button {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.input-panel,
|
||||
.empty-panel,
|
||||
.meta-panel,
|
||||
.section-card {
|
||||
.section-card,
|
||||
.upload-panel {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.section-card-half,
|
||||
.section-card-full {
|
||||
grid-column: auto;
|
||||
}
|
||||
}
|
||||
|
||||
38
web/templates/upload.html
Normal file
38
web/templates/upload.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ .Title }}</title>
|
||||
<link rel="stylesheet" href="/static/view.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<h1>{{ .Title }}</h1>
|
||||
<p>Read-only viewer for Reanimator JSON snapshots</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="page-main">
|
||||
<section class="upload-panel">
|
||||
<h2>Open Snapshot</h2>
|
||||
<p>Select a Reanimator JSON snapshot to render.</p>
|
||||
<form method="post" action="/render" enctype="multipart/form-data">
|
||||
<label class="upload-dropzone" for="snapshot_file">
|
||||
<input id="snapshot_file" name="snapshot_file" type="file" accept=".json,application/json" required>
|
||||
<span class="upload-eyebrow">Standalone Mode</span>
|
||||
<strong>Choose a snapshot JSON file</strong>
|
||||
<span>The file is rendered read-only and not modified.</span>
|
||||
</label>
|
||||
<div class="upload-actions">
|
||||
<button type="submit">Render Snapshot</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ if .Error }}
|
||||
<div class="error-box">{{ .Error }}</div>
|
||||
{{ end }}
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -15,19 +15,6 @@
|
||||
</header>
|
||||
|
||||
<main class="page-main">
|
||||
<section class="input-panel">
|
||||
<form method="post" action="/render">
|
||||
<label for="snapshot" class="input-label">Snapshot JSON</label>
|
||||
<textarea id="snapshot" name="snapshot" spellcheck="false" placeholder='{"target_host":"...","hardware":{...}}'>{{ .InputJSON }}</textarea>
|
||||
<div class="input-actions">
|
||||
<button type="submit">Render Snapshot</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ if .Error }}
|
||||
<div class="error-box">{{ .Error }}</div>
|
||||
{{ end }}
|
||||
</section>
|
||||
|
||||
{{ if .HasSnapshot }}
|
||||
<section class="meta-panel">
|
||||
<h2>Snapshot Metadata</h2>
|
||||
@@ -49,8 +36,9 @@
|
||||
{{ end }}
|
||||
</nav>
|
||||
|
||||
<div class="sections-grid">
|
||||
{{ range .Sections }}
|
||||
<section class="section-card" id="{{ .ID }}">
|
||||
<section class="section-card {{ if or (eq .ID "board") (eq .ID "firmware") }}section-card-half{{ else }}section-card-full{{ end }}" id="{{ .ID }}">
|
||||
<h2>{{ .Title }}</h2>
|
||||
|
||||
{{ if eq .Kind "object" }}
|
||||
@@ -103,8 +91,59 @@
|
||||
</table>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if eq .Kind "grouped_tables" }}
|
||||
{{ range .Groups }}
|
||||
<div class="table-group">
|
||||
<h3>{{ .Title }}</h3>
|
||||
{{ $group := . }}
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{{ range .Columns }}
|
||||
<th>{{ . }}</th>
|
||||
{{ end }}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .Items }}
|
||||
<tr>
|
||||
{{ $row := . }}
|
||||
{{ range $group.Columns }}
|
||||
<td>
|
||||
{{ $value := index $row.Cells . }}
|
||||
{{ if eq . "status" }}
|
||||
<span class="status-badge {{ statusClass $value }}">{{ $value }}</span>
|
||||
{{ else }}
|
||||
{{ range joinLines $value }}
|
||||
<div>{{ . }}</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</td>
|
||||
{{ end }}
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if .Error }}
|
||||
<section class="error-box">{{ .Error }}</section>
|
||||
{{ end }}
|
||||
|
||||
{{ if not .HasSnapshot }}
|
||||
<section class="empty-panel">
|
||||
<h2>Snapshot Viewer</h2>
|
||||
<p>This page renders one Reanimator snapshot provided by the embedding application.</p>
|
||||
</section>
|
||||
{{ end }}
|
||||
</main>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user