music-importer/index.html.tmpl
2026-04-04 00:21:03 -04:00

297 lines
7.5 KiB
Cheetah

<!DOCTYPE html>
<html>
<head>
<title>Music Importer</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body {
font-family: sans-serif;
background: #111;
color: #eee;
text-align: center;
padding: 60px 24px 80px;
margin: 0;
}
h1 { margin-bottom: 32px; }
button {
font-size: 32px;
padding: 20px 40px;
border-radius: 10px;
border: none;
cursor: pointer;
background: #4CAF50;
color: white;
}
button:disabled {
background: #555;
cursor: not-allowed;
}
/* ── Last run summary ── */
.session {
margin: 48px auto 0;
max-width: 820px;
text-align: left;
}
.session-header {
display: flex;
justify-content: space-between;
align-items: baseline;
border-bottom: 1px solid #333;
padding-bottom: 8px;
margin-bottom: 20px;
}
.session-header h2 { margin: 0; font-size: 18px; color: #ccc; }
.session-header .duration { font-size: 13px; color: #666; }
.album {
background: #1a1a1a;
border: 1px solid #2a2a2a;
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 12px;
}
.album-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.album-name {
font-weight: bold;
font-size: 15px;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.badge {
font-size: 11px;
font-weight: bold;
padding: 2px 8px;
border-radius: 4px;
white-space: nowrap;
}
.badge-ok { background: #1e4d2b; color: #4CAF50; }
.badge-warn { background: #4d3a00; color: #f0a500; }
.badge-fatal { background: #4d1a1a; color: #e05050; }
/* ── Metadata row ── */
.metadata {
display: flex;
align-items: baseline;
gap: 14px;
flex-wrap: wrap;
font-size: 12px;
color: #777;
margin-bottom: 12px;
}
.metadata-title {
color: #aaa;
font-size: 13px;
}
.metadata-pill {
display: inline-flex;
align-items: center;
gap: 4px;
background: #222;
border-radius: 4px;
padding: 2px 7px;
font-size: 11px;
}
.pill-label { color: #555; }
.pill-beets { color: #7ec8e3; }
.pill-musicbrainz { color: #c084fc; }
.pill-file_tags { color: #f0a500; }
.pill-unknown { color: #888; }
/* ── Rich info grid ── */
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 6px;
margin-bottom: 12px;
}
.info-card {
background: #222;
border-radius: 6px;
padding: 8px 12px;
font-size: 12px;
}
.info-card-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #555;
margin-bottom: 5px;
}
.info-card-value {
color: #ccc;
font-size: 13px;
font-weight: 600;
}
.info-card-sub {
margin-top: 3px;
color: #666;
font-size: 11px;
line-height: 1.4;
}
.info-ok { color: #4CAF50; }
.info-warn { color: #f0a500; }
.info-dim { color: #555; }
/* ── Pipeline steps ── */
.steps-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #444;
margin-bottom: 6px;
}
.steps {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 6px;
}
.step {
font-size: 12px;
padding: 5px 10px;
border-radius: 5px;
background: #222;
display: flex;
flex-direction: column;
gap: 2px;
}
.step-label { color: #888; }
.step-ok { color: #4CAF50; }
.step-warn { color: #f0a500; }
.step-fatal { color: #e05050; }
.step-err { font-size: 11px; color: #c0392b; margin-top: 2px; word-break: break-word; }
footer {
position: fixed;
bottom: 16px;
width: 100%;
font-size: 13px;
color: #999;
text-align: center;
left: 0;
}
</style>
</head>
<body>
<h1>Music Importer</h1>
<form action="/run" method="POST">
<button type="submit" {{if .Running}}disabled{{end}}>
{{if .Running}}Importer Running...{{else}}Run Importer{{end}}
</button>
</form>
{{with .Session}}
<div class="session">
<div class="session-header">
<h2>Last Run &mdash; {{.StartedAt.Format "Jan 2, 2006 15:04:05"}}</h2>
<span class="duration">{{duration .StartedAt .FinishedAt}}</span>
</div>
{{range .Albums}}{{$album := .}}
<div class="album">
<div class="album-header">
<span class="album-name" title="{{.Path}}">{{.Name}}</span>
{{if .Succeeded}}
{{if .HasWarnings}}
<span class="badge badge-warn">&#9888; warnings</span>
{{else}}
<span class="badge badge-ok">&#10003; ok</span>
{{end}}
{{else}}
<span class="badge badge-fatal">&#10007; failed at {{.FatalStep}}</span>
{{end}}
</div>
{{with .Metadata}}
<div class="metadata">
<span class="metadata-title">{{.Artist}} &mdash; {{.Album}}{{if .Year}} ({{.Year}}){{end}}</span>
{{if $album.MetadataSource}}
<span class="metadata-pill">
<span class="pill-label">via</span>
{{if eq (print $album.MetadataSource) "beets"}}
<span class="pill-beets">beets</span>
{{else if eq (print $album.MetadataSource) "musicbrainz"}}
<span class="pill-musicbrainz">MusicBrainz</span>
{{else if eq (print $album.MetadataSource) "file_tags"}}
<span class="pill-file_tags">file tags</span>
{{else}}
<span class="pill-unknown">unknown</span>
{{end}}
</span>
{{end}}
</div>
{{end}}
{{/* ── Rich info cards ── */}}
<div class="info-grid">
{{/* Tracks */}}
<div class="info-card">
<div class="info-card-label">Tracks</div>
<div class="info-card-value">{{.TrackCount}}</div>
</div>
{{/* Lyrics */}}
<div class="info-card">
<div class="info-card-label">Lyrics</div>
{{if eq .LyricsStats.Total 0}}
<div class="info-card-value info-dim">n/a</div>
{{else}}
<div class="info-card-value {{if gt .LyricsStats.Downloaded 0}}info-ok{{else}}info-dim{{end}}">
{{.LyricsStats.Downloaded}} / {{.LyricsStats.Total}}
</div>
<div class="info-card-sub">
{{if gt .LyricsStats.Synced 0}}<span class="info-ok">{{.LyricsStats.Synced}} synced</span>{{end}}
{{if and (gt .LyricsStats.Synced 0) (gt .LyricsStats.Plain 0)}} &middot; {{end}}
{{if gt .LyricsStats.Plain 0}}<span class="info-warn">{{.LyricsStats.Plain}} plain</span>{{end}}
{{if gt .LyricsStats.AlreadyHad 0}}<span class="info-dim"> {{.LyricsStats.AlreadyHad}} existing</span>{{end}}
{{if gt .LyricsStats.NotFound 0}}<span class="info-dim"> {{.LyricsStats.NotFound}} missing</span>{{end}}
</div>
{{end}}
</div>
{{/* Cover art */}}
<div class="info-card">
<div class="info-card-label">Cover Art</div>
{{if .CoverArtStats.Found}}
{{if .CoverArtStats.Embedded}}
<div class="info-card-value info-ok">Embedded</div>
<div class="info-card-sub info-dim">{{.CoverArtStats.Source}}</div>
{{else}}
<div class="info-card-value info-warn">Found, not embedded</div>
<div class="info-card-sub info-dim">{{.CoverArtStats.Source}}</div>
{{end}}
{{else}}
<div class="info-card-value info-dim">Not found</div>
{{end}}
</div>
</div>
<div class="steps-label">Pipeline</div>
<div class="steps">
{{stepCell "Clean Tags" .CleanTags ""}}
{{stepCell "Metadata" .TagMetadata .FatalStep}}
{{stepCell "Lyrics" .Lyrics ""}}
{{stepCell "ReplayGain" .ReplayGain .FatalStep}}
{{stepCell "Cover Art" .CoverArt .FatalStep}}
{{stepCell "Move" .Move ""}}
</div>
</div>
{{end}}
</div>
{{end}}
<footer>{{.Version}}</footer>
</body>
</html>