This commit is contained in:
delete 2026-06-10 20:51:17 +03:00
commit b15b95781c
108 changed files with 14802 additions and 0 deletions

View file

@ -0,0 +1,39 @@
{{define "content"}}
<section class="hero">
<p class="eyebrow">{{.Eyebrow}}</p>
<h1>{{.Heading}}</h1>
<p class="lede">{{.Description}}</p>
</section>
<section class="metric-grid">
{{range .Metrics}}
<article class="metric-card">
<p class="metric-value">{{.Value}}</p>
<h2>{{.Label}}</h2>
<p>{{.Description}}</p>
</article>
{{end}}
</section>
<section class="admin-grid">
<article class="callout">
<h2>Authenticated User</h2>
<p class="meta-line"><strong>Email:</strong> {{if .CurrentUser}}{{.CurrentUser.Email}}{{else}}Unavailable{{end}}</p>
<p class="meta-line"><strong>Role:</strong> {{if .CurrentUser}}{{.CurrentUser.Role}}{{else}}Unknown{{end}}</p>
</article>
<article class="callout">
<h2>Session And Storage</h2>
<p>Protected admin routes continue to use the existing session context. Release uploads now persist files under the private artifact directory rather than the public static web root.</p>
</article>
</section>
<section class="link-grid">
{{range .Links}}
<article class="link-card">
<h2><a href="{{.Href}}">{{.Label}}</a></h2>
<p>{{.Description}}</p>
</article>
{{end}}
</section>
{{end}}

View file

@ -0,0 +1,200 @@
{{define "content"}}
<section class="hero hero-split">
<div>
<p class="eyebrow">{{.Eyebrow}}</p>
<h1>{{.Heading}}</h1>
<p class="lede">{{.Description}}</p>
</div>
{{if .APIKey}}
<div class="hero-meta">
<span class="pill {{if .APIKey.IsActive}}pill-success{{else}}pill-muted{{end}}">
{{if .APIKey.IsActive}}Active{{else}}Revoked{{end}}
</span>
<p class="resource-slug">{{.APIKey.KeyPrefix}}</p>
</div>
{{end}}
</section>
{{if .Form.RevealKey}}
<section class="alert-card alert-success">
<p><strong>Copy this API key now:</strong> <code class="secret-code">{{.Form.RevealKey}}</code></p>
<p>This is the only time the full raw key will be shown. Only the hash and short prefix are stored.</p>
</section>
{{end}}
{{if .Form.Error}}
<section class="alert-card alert-error">
<p>{{.Form.Error}}</p>
</section>
{{end}}
<section class="section-grid">
<article class="section-card">
<div class="section-heading">
<h2>Key Details</h2>
<p>Choose a stable label, minimum permissions, and one scope mode for this key.</p>
</div>
<form class="stack-form" method="post" action="{{.Form.Action}}">
{{template "csrf_field" .}}
<label class="field">
<span>Name</span>
<input type="text" name="name" value="{{.Form.Name}}" maxlength="120" required>
</label>
<label class="field">
<span>Description</span>
<textarea name="description" rows="4">{{.Form.Description}}</textarea>
</label>
<label class="field">
<span>Scope mode</span>
<select name="scope_mode" required>
<option value="all_projects" {{if eq .Form.ScopeMode "all_projects"}}selected{{end}}>all_projects</option>
<option value="project_allow_list" {{if eq .Form.ScopeMode "project_allow_list"}}selected{{end}}>project_allow_list</option>
<option value="project_deny_list" {{if eq .Form.ScopeMode "project_deny_list"}}selected{{end}}>project_deny_list</option>
<option value="tag_allow_list" {{if eq .Form.ScopeMode "tag_allow_list"}}selected{{end}}>tag_allow_list</option>
<option value="tag_deny_list" {{if eq .Form.ScopeMode "tag_deny_list"}}selected{{end}}>tag_deny_list</option>
</select>
</label>
<label class="field">
<span>Expires at</span>
<input type="text" name="expires_at" value="{{.Form.ExpiresAt}}" placeholder="2026-12-31T23:59:59Z or 2026-12-31">
</label>
<fieldset class="choice-group">
<legend>Permissions</legend>
<label class="choice-row">
<input type="checkbox" name="can_download" value="1" {{if .Form.CanDownload}}checked{{end}}>
<span><strong>updates.read</strong> style access for listing or downloading releases.</span>
</label>
<label class="choice-row">
<input type="checkbox" name="can_upload" value="1" {{if .Form.CanUpload}}checked{{end}}>
<span>Upload release artifacts.</span>
</label>
<label class="choice-row">
<input type="checkbox" name="can_delete" value="1" {{if .Form.CanDelete}}checked{{end}}>
<span>Delete or disable release records later.</span>
</label>
<label class="choice-row">
<input type="checkbox" name="can_manage_projects" value="1" {{if .Form.CanManageProjects}}checked{{end}}>
<span>Create or edit projects.</span>
</label>
</fieldset>
<fieldset class="choice-group">
<legend>Project rules</legend>
<p class="helper-copy">Used only for <code>project_allow_list</code> and <code>project_deny_list</code>.</p>
{{if .ProjectChoices}}
<div class="choice-list">
{{range .ProjectChoices}}
<label class="choice-row">
<input type="checkbox" name="project_id" value="{{.Project.ID}}" {{if .Selected}}checked{{end}}>
<span>{{.Project.Name}} <code>{{.Project.Slug}}</code> {{if not .Project.IsActive}}(archived){{end}}</span>
</label>
{{end}}
</div>
{{else}}
<p class="empty-copy">No projects exist yet.</p>
{{end}}
</fieldset>
<fieldset class="choice-group">
<legend>Tag rules</legend>
<p class="helper-copy">Used only for <code>tag_allow_list</code> and <code>tag_deny_list</code>.</p>
{{if .TagChoices}}
<div class="choice-list">
{{range .TagChoices}}
<label class="choice-row">
<input type="checkbox" name="tag_id" value="{{.Tag.ID}}" {{if .Selected}}checked{{end}}>
<span>{{.Tag.Name}} <code>{{.Tag.Slug}}</code></span>
</label>
{{end}}
</div>
{{else}}
<p class="empty-copy">No tags exist yet.</p>
{{end}}
</fieldset>
<div class="button-row">
<button class="button" type="submit">{{.Form.SubmitLabel}}</button>
<a class="button button-secondary" href="/admin/api-keys">Back to API keys</a>
</div>
</form>
</article>
{{if .APIKey}}
<article class="section-card">
<div class="section-heading">
<h2>Lifecycle</h2>
<p>Disabled or expired keys are rejected before any project scope checks happen.</p>
</div>
<p class="meta-line"><strong>Created:</strong> {{.APIKey.CreatedAt.Format "2006-01-02 15:04 UTC"}}</p>
<p class="meta-line"><strong>Updated:</strong> {{.APIKey.UpdatedAt.Format "2006-01-02 15:04 UTC"}}</p>
<p class="meta-line"><strong>Last used:</strong> {{if .APIKey.LastUsedAt}}{{.APIKey.LastUsedAt.Format "2006-01-02 15:04 UTC"}}{{else}}Never{{end}}</p>
<p class="meta-line"><strong>Expires:</strong> {{if .APIKey.ExpiresAt}}{{.APIKey.ExpiresAt.Format "2006-01-02 15:04 UTC"}}{{else}}No expiry{{end}}</p>
<form class="stack-form" method="post" action="{{.ToggleAction}}">
{{template "csrf_field" .}}
<input type="hidden" name="state" value="{{.ToggleState}}">
<button class="button button-secondary" type="submit">{{.ToggleLabel}}</button>
</form>
</article>
{{end}}
</section>
{{if .APIKey}}
<section class="section-card section-card-wide">
<div class="section-heading">
<h2>Effective Access Preview</h2>
<p>This preview shows the active projects currently reachable after scope mode evaluation.</p>
</div>
{{if .AccessibleProjects}}
<div class="resource-grid compact-grid">
{{range .AccessibleProjects}}
<article class="resource-card">
<div class="resource-header">
<div>
<h3>{{.Name}}</h3>
<p class="resource-slug">{{.Slug}}</p>
</div>
<span class="pill pill-success">Active</span>
</div>
<p class="resource-description">{{if .Description}}{{.Description}}{{else}}No description yet.{{end}}</p>
</article>
{{end}}
</div>
{{else}}
<p class="empty-copy">No active projects currently match this key.</p>
{{end}}
</section>
<section class="section-card section-card-wide">
<div class="section-heading">
<h2>Client API Quick Start</h2>
<p>Clients should authenticate with a bearer API key and then discover projects before requesting metadata or downloading an artifact.</p>
</div>
<div class="endpoint-list">
<p class="meta-line"><strong>Bearer header:</strong> <code>Authorization: Bearer {{if .Form.RevealKey}}{{.Form.RevealKey}}{{else}}&lt;paste-api-key&gt;{{end}}</code></p>
<p class="meta-line"><strong>Accessible projects:</strong> <code>{{.BaseURL}}/api/v1/projects</code></p>
</div>
<pre class="code-block"><code>curl -H "Authorization: Bearer {{if .Form.RevealKey}}{{.Form.RevealKey}}{{else}}&lt;paste-api-key&gt;{{end}}" \
{{.BaseURL}}/api/v1/projects</code></pre>
{{if .AccessibleProjects}}
<div class="endpoint-list">
{{range .AccessibleProjects}}
<p class="meta-line"><strong>{{.Name}} latest metadata:</strong> <code>{{printf "%s/api/v1/projects/%s/releases/latest" $.BaseURL .Slug}}</code></p>
{{end}}
</div>
{{else}}
<p class="empty-copy">This key does not currently expose any active projects, so client metadata and download lookups will return no accessible resources.</p>
{{end}}
</section>
{{end}}
{{end}}

View file

@ -0,0 +1,49 @@
{{define "content"}}
<section class="hero">
<p class="eyebrow">{{.Eyebrow}}</p>
<h1>{{.Heading}}</h1>
<p class="lede">{{.Description}}</p>
</section>
<section class="toolbar">
<a class="button" href="/admin/api-keys/new">Create API key</a>
</section>
{{if .APIKeys}}
<section class="resource-grid">
{{range .APIKeys}}
<article class="resource-card">
<div class="resource-header">
<div>
<h2><a href="/admin/api-keys/{{.APIKey.ID}}">{{.APIKey.Name}}</a></h2>
<p class="resource-slug">{{.APIKey.KeyPrefix}}</p>
</div>
<span class="pill {{if .APIKey.IsActive}}pill-success{{else}}pill-muted{{end}}">
{{if .APIKey.IsActive}}Active{{else}}Revoked{{end}}
</span>
</div>
<p class="resource-description">{{if .APIKey.Description}}{{.APIKey.Description}}{{else}}No description yet.{{end}}</p>
<p class="meta-line"><strong>Scope:</strong> <code>{{.APIKey.ScopeMode}}</code></p>
<p class="meta-line"><strong>Rule rows:</strong> {{.ProjectRuleCount}} project, {{.TagRuleCount}} tag</p>
<p class="meta-line"><strong>Permissions:</strong>
{{if .APIKey.CanDownload}}download{{else}}no-download{{end}},
{{if .APIKey.CanUpload}}upload{{else}}no-upload{{end}},
{{if .APIKey.CanDelete}}delete{{else}}no-delete{{end}},
{{if .APIKey.CanManageProjects}}manage-projects{{else}}no-manage-projects{{end}}
</p>
<p class="meta-line"><strong>Updated:</strong> {{.APIKey.UpdatedAt.Format "2006-01-02 15:04 UTC"}}</p>
<p class="meta-line"><strong>Last used:</strong> {{if .APIKey.LastUsedAt}}{{.APIKey.LastUsedAt.Format "2006-01-02 15:04 UTC"}}{{else}}Never{{end}}</p>
<p class="meta-line"><strong>Expires:</strong> {{if .APIKey.ExpiresAt}}{{.APIKey.ExpiresAt.Format "2006-01-02 15:04 UTC"}}{{else}}No expiry{{end}}</p>
<a class="text-link" href="/admin/api-keys/{{.APIKey.ID}}">Open API key</a>
</article>
{{end}}
</section>
{{else}}
<section class="empty-state">
<h2>No API keys yet</h2>
<p>Create the first key to let client applications authenticate against the project, metadata, and download endpoints.</p>
</section>
{{end}}
{{end}}

View file

@ -0,0 +1,16 @@
{{define "content"}}
<section class="hero">
<p class="eyebrow">{{.Eyebrow}}</p>
<h1>{{.Heading}}</h1>
<p class="lede">{{.Description}}</p>
</section>
<section class="link-grid">
{{range .Links}}
<article class="link-card">
<h2><a href="{{.Href}}">{{.Label}}</a></h2>
<p>{{.Description}}</p>
</article>
{{end}}
</section>
{{end}}

View file

@ -0,0 +1,38 @@
{{define "content"}}
<section class="hero">
<p class="eyebrow">{{.Eyebrow}}</p>
<h1>{{.Heading}}</h1>
<p class="lede">{{.Description}}</p>
</section>
{{if .Login.Error}}
<section class="alert-card alert-error">
<p>{{.Login.Error}}</p>
</section>
{{end}}
{{if .Login.SetupHint}}
<section class="alert-card">
<p>{{.Login.SetupHint}}</p>
</section>
{{end}}
<section class="form-shell">
<form class="stack-form" method="post" action="{{.Login.Action}}">
{{template "csrf_field" .}}
<input type="hidden" name="next" value="{{.Login.Next}}">
<label class="field">
<span>Email</span>
<input type="email" name="email" value="{{.Login.Email}}" autocomplete="username" required>
</label>
<label class="field">
<span>Password</span>
<input type="password" name="password" autocomplete="current-password" required>
</label>
<button class="button" type="submit">Log in</button>
</form>
</section>
{{end}}

View file

@ -0,0 +1,200 @@
{{define "content"}}
<section class="hero hero-split">
<div>
<p class="eyebrow">{{.Eyebrow}}</p>
<h1>{{.Heading}}</h1>
<p class="lede">{{.Description}}</p>
</div>
<div class="hero-meta">
<span class="pill {{if .Project.IsActive}}pill-success{{else}}pill-muted{{end}}">
{{if .Project.IsActive}}Active{{else}}Archived{{end}}
</span>
<p class="resource-slug">{{.Project.Slug}}</p>
</div>
</section>
{{if .Form.Error}}
<section class="alert-card alert-error">
<p>{{.Form.Error}}</p>
</section>
{{end}}
<section class="section-grid">
<article class="section-card">
<div class="section-heading">
<h2>Project Details</h2>
<p>Edit the stable metadata used by the admin UI and future client endpoints.</p>
</div>
<form class="stack-form" method="post" action="{{.Form.Action}}">
{{template "csrf_field" .}}
<label class="field">
<span>Name</span>
<input type="text" name="name" value="{{.Form.Name}}" maxlength="120" required>
</label>
<label class="field">
<span>Slug</span>
<input type="text" name="slug" value="{{.Form.Slug}}" maxlength="120" required>
</label>
<label class="field">
<span>Description</span>
<textarea name="description" rows="4">{{.Form.Description}}</textarea>
</label>
<button class="button" type="submit">{{.Form.SubmitLabel}}</button>
</form>
</article>
<article class="section-card">
<div class="section-heading">
<h2>Lifecycle</h2>
<p>Archive projects to hide them operationally without deleting their data.</p>
</div>
<p class="meta-line"><strong>Created:</strong> {{.Project.CreatedAt.Format "2006-01-02 15:04 UTC"}}</p>
<p class="meta-line"><strong>Updated:</strong> {{.Project.UpdatedAt.Format "2006-01-02 15:04 UTC"}}</p>
<form class="stack-form" method="post" action="{{.ArchiveAction}}">
{{template "csrf_field" .}}
<input type="hidden" name="state" value="{{.ArchiveState}}">
<button class="button button-secondary" type="submit">{{.ArchiveLabel}}</button>
</form>
</article>
</section>
<section class="section-grid">
<article class="section-card">
<div class="section-heading">
<h2>Project Tags</h2>
<p>Tags are available from day one so projects can be grouped immediately.</p>
</div>
{{if .Tags}}
<div class="chip-list">
{{range .Tags}}
<form class="chip-form" method="post" action="/admin/projects/{{$.Project.ID}}/tags/{{.ID}}/detach">
{{template "csrf_field" $}}
<span class="chip-label">{{.Name}}</span>
<button class="chip-remove" type="submit">Detach</button>
</form>
{{end}}
</div>
{{else}}
<p class="empty-copy">No tags attached yet.</p>
{{end}}
{{if .AvailableTags}}
<form class="inline-form" method="post" action="{{.AttachTagAction}}">
{{template "csrf_field" .}}
<label class="field">
<span>Attach tag</span>
<select name="tag_id" required>
<option value="">Choose a tag</option>
{{range .AvailableTags}}
<option value="{{.ID}}">{{.Name}} ({{.Slug}})</option>
{{end}}
</select>
</label>
<button class="button" type="submit">Attach tag</button>
</form>
{{else}}
<p class="empty-copy">All existing tags are already attached. <a href="/admin/tags/new">Create another tag</a> if you need a new grouping.</p>
{{end}}
</article>
<article class="section-card">
<div class="section-heading">
<h2>Release Upload</h2>
<p>Uploads are stored outside the public web root and each release records its checksum, size, and storage path.</p>
</div>
{{if .Upload.Error}}
<section class="alert-card alert-error compact-alert">
<p>{{.Upload.Error}}</p>
</section>
{{end}}
<form class="stack-form" method="post" action="{{.Upload.Action}}" enctype="multipart/form-data">
{{template "csrf_field" .}}
<label class="field">
<span>Version</span>
<input type="text" name="version" value="{{.Upload.Version}}" maxlength="120" placeholder="1.0.0" required>
</label>
<label class="field">
<span>Build</span>
<input type="text" name="build" value="{{.Upload.Build}}" maxlength="120" placeholder="optional build label">
</label>
<label class="field">
<span>Release notes</span>
<textarea name="release_notes" rows="4">{{.Upload.ReleaseNotes}}</textarea>
</label>
<label class="field">
<span>Artifact file</span>
<input type="file" name="artifact" required>
</label>
<p class="helper-copy">Maximum upload size: {{.Upload.MaxUploadMB}} MB.</p>
<button class="button" type="submit">Upload release</button>
</form>
</article>
</section>
<section class="section-card section-card-wide">
<div class="section-heading">
<h2>Release History</h2>
<p>Each release row keeps the sanitized filename, checksum, file size, content type, and private storage path for later download endpoints.</p>
</div>
{{if .Releases}}
<div class="release-list">
{{range .Releases}}
<article class="release-card">
<div class="release-head">
<h3>{{.Release.Version}}{{if .Release.Build}} <span class="release-build">{{.Release.Build}}</span>{{end}}</h3>
<span class="pill pill-soft">{{.Release.ContentType}}</span>
</div>
<p class="meta-line"><strong>File:</strong> {{.Release.Filename}}</p>
<p class="meta-line"><strong>Checksum:</strong> <code>{{.Release.ChecksumSHA256}}</code></p>
<p class="meta-line"><strong>Storage path:</strong> <code>{{.Release.StoragePath}}</code></p>
<p class="meta-line"><strong>Size:</strong> {{.Release.SizeBytes}} bytes</p>
<p class="meta-line"><strong>Uploaded by:</strong> {{if .UploadedByEmail}}{{.UploadedByEmail}}{{else}}Unknown{{end}}</p>
<p class="meta-line"><strong>Created:</strong> {{.Release.CreatedAt.Format "2006-01-02 15:04 UTC"}}</p>
{{if .Release.ReleaseNotes}}
<p class="release-notes">{{.Release.ReleaseNotes}}</p>
{{end}}
</article>
{{end}}
</div>
{{else}}
<p class="empty-copy">No releases uploaded for this project yet.</p>
{{end}}
</section>
<section class="section-card section-card-wide">
<div class="section-heading">
<h2>Client API</h2>
<p>Use these bearer-authenticated endpoints from client applications after you create an API key with download access.</p>
</div>
{{if .Project.IsActive}}
<div class="endpoint-list">
<p class="meta-line"><strong>Project listing:</strong> <code>{{.BaseURL}}/api/v1/projects</code></p>
<p class="meta-line"><strong>Latest release metadata:</strong> <code>{{printf "%s/api/v1/projects/%s/releases/latest" .BaseURL .Project.Slug}}</code></p>
{{if .LatestRelease}}
<p class="meta-line"><strong>Release metadata:</strong> <code>{{printf "%s/api/v1/releases/%d" .BaseURL .LatestRelease.ID}}</code></p>
<p class="meta-line"><strong>Artifact download:</strong> <code>{{printf "%s/api/v1/releases/%d/download" .BaseURL .LatestRelease.ID}}</code></p>
<p class="meta-line"><strong>Current latest:</strong> Version {{.LatestRelease.Version}}{{if .LatestRelease.Build}} (build {{.LatestRelease.Build}}){{end}}, release ID {{.LatestRelease.ID}}.</p>
{{else}}
<p class="empty-copy">Upload the first release to activate the metadata and download endpoints for this project.</p>
{{end}}
</div>
{{else}}
<p class="empty-copy">This project is archived, so client API lookups and downloads will hide it until you restore the project.</p>
{{end}}
</section>
{{end}}

View file

@ -0,0 +1,38 @@
{{define "content"}}
<section class="hero">
<p class="eyebrow">{{.Eyebrow}}</p>
<h1>{{.Heading}}</h1>
<p class="lede">{{.Description}}</p>
</section>
{{if .Form.Error}}
<section class="alert-card alert-error">
<p>{{.Form.Error}}</p>
</section>
{{end}}
<section class="form-shell form-shell-wide">
<form class="stack-form" method="post" action="{{.Form.Action}}">
{{template "csrf_field" .}}
<label class="field">
<span>Name</span>
<input type="text" name="name" value="{{.Form.Name}}" maxlength="120" required>
</label>
<label class="field">
<span>Slug</span>
<input type="text" name="slug" value="{{.Form.Slug}}" maxlength="120" placeholder="auto-generated from the name">
</label>
<label class="field">
<span>Description</span>
<textarea name="description" rows="5">{{.Form.Description}}</textarea>
</label>
<div class="button-row">
<button class="button" type="submit">{{.Form.SubmitLabel}}</button>
<a class="button button-secondary" href="/admin/projects">Back to projects</a>
</div>
</form>
</section>
{{end}}

View file

@ -0,0 +1,44 @@
{{define "content"}}
<section class="hero">
<p class="eyebrow">{{.Eyebrow}}</p>
<h1>{{.Heading}}</h1>
<p class="lede">{{.Description}}</p>
</section>
<section class="toolbar">
<a class="button" href="/admin/projects/new">Create project</a>
</section>
{{if .Projects}}
<section class="resource-grid">
{{range .Projects}}
<article class="resource-card">
<div class="resource-header">
<div>
<h2><a href="/admin/projects/{{.Project.ID}}">{{.Project.Name}}</a></h2>
<p class="resource-slug">{{.Project.Slug}}</p>
</div>
<span class="pill {{if .Project.IsActive}}pill-success{{else}}pill-muted{{end}}">
{{if .Project.IsActive}}Active{{else}}Archived{{end}}
</span>
</div>
<p class="resource-description">{{if .Project.Description}}{{.Project.Description}}{{else}}No description yet.{{end}}</p>
<div class="stat-row">
<span>{{.TagCount}} tags</span>
<span>{{.ReleaseCount}} releases</span>
<span>Updated {{.Project.UpdatedAt.Format "2006-01-02 15:04 UTC"}}</span>
</div>
<a class="text-link" href="/admin/projects/{{.Project.ID}}">Open project</a>
</article>
{{end}}
</section>
{{else}}
<section class="empty-state">
<h2>No projects yet</h2>
<p>Create the first project to start grouping releases and tags.</p>
</section>
{{end}}
{{end}}

View file

@ -0,0 +1,93 @@
{{define "content"}}
<section class="hero">
<p class="eyebrow">{{.Eyebrow}}</p>
<h1>{{.Heading}}</h1>
<p class="lede">{{.Description}}</p>
</section>
{{if .Form.Error}}
<section class="alert-card alert-error">
<p>{{.Form.Error}}</p>
</section>
{{end}}
<section class="section-grid">
<article class="section-card">
<div class="section-heading">
<h2>Tag Details</h2>
<p>Use stable tag slugs so projects and future access rules can reference them predictably.</p>
</div>
<form class="stack-form" method="post" action="{{.Form.Action}}">
{{template "csrf_field" .}}
<label class="field">
<span>Name</span>
<input type="text" name="name" value="{{.Form.Name}}" maxlength="120" required>
</label>
<label class="field">
<span>Slug</span>
<input type="text" name="slug" value="{{.Form.Slug}}" maxlength="120" placeholder="auto-generated from the name">
</label>
<label class="field">
<span>Description</span>
<textarea name="description" rows="4">{{.Form.Description}}</textarea>
</label>
<div class="button-row">
<button class="button" type="submit">{{.Form.SubmitLabel}}</button>
<a class="button button-secondary" href="/admin/tags">Back to tags</a>
</div>
</form>
</article>
{{if .Tag}}
<article class="section-card">
<div class="section-heading">
<h2>Delete Tag</h2>
<p>Tags can only be deleted once they are no longer attached to any project.</p>
</div>
{{if .Form.CanDelete}}
<form class="stack-form" method="post" action="{{.Form.DeleteAction}}">
{{template "csrf_field" .}}
<button class="button button-danger" type="submit">Delete tag</button>
</form>
{{else}}
<p class="empty-copy">Detach this tag from all projects before deleting it.</p>
{{end}}
</article>
{{end}}
</section>
{{if .Tag}}
<section class="section-card section-card-wide">
<div class="section-heading">
<h2>Assigned Projects</h2>
<p>These projects currently use this tag.</p>
</div>
{{if .Projects}}
<div class="resource-grid compact-grid">
{{range .Projects}}
<article class="resource-card">
<div class="resource-header">
<div>
<h3><a href="/admin/projects/{{.ID}}">{{.Name}}</a></h3>
<p class="resource-slug">{{.Slug}}</p>
</div>
<span class="pill {{if .IsActive}}pill-success{{else}}pill-muted{{end}}">
{{if .IsActive}}Active{{else}}Archived{{end}}
</span>
</div>
<p class="resource-description">{{if .Description}}{{.Description}}{{else}}No description yet.{{end}}</p>
</article>
{{end}}
</div>
{{else}}
<p class="empty-copy">This tag is not attached to any project yet.</p>
{{end}}
</section>
{{end}}
{{end}}

View file

@ -0,0 +1,36 @@
{{define "content"}}
<section class="hero">
<p class="eyebrow">{{.Eyebrow}}</p>
<h1>{{.Heading}}</h1>
<p class="lede">{{.Description}}</p>
</section>
<section class="toolbar">
<a class="button" href="/admin/tags/new">Create tag</a>
</section>
{{if .Tags}}
<section class="resource-grid">
{{range .Tags}}
<article class="resource-card">
<div class="resource-header">
<div>
<h2><a href="/admin/tags/{{.Tag.ID}}">{{.Tag.Name}}</a></h2>
<p class="resource-slug">{{.Tag.Slug}}</p>
</div>
<span class="pill pill-soft">{{.ProjectCount}} projects</span>
</div>
<p class="resource-description">{{if .Tag.Description}}{{.Tag.Description}}{{else}}No description yet.{{end}}</p>
<p class="meta-line"><strong>Updated:</strong> {{.Tag.UpdatedAt.Format "2006-01-02 15:04 UTC"}}</p>
<a class="text-link" href="/admin/tags/{{.Tag.ID}}">Open tag</a>
</article>
{{end}}
</section>
{{else}}
<section class="empty-state">
<h2>No tags yet</h2>
<p>Create the first tag so projects can be grouped from day one.</p>
</section>
{{end}}
{{end}}