fierj
PublicTiny personal git forge
40e5273a9a6361cb659398d8d690d5b0a9b03de1
diff --git a/handlers.go b/handlers.go
index 917915b..e3e5f51 100644
--- a/handlers.go
+++ b/handlers.go
@@ -164,8 +164,9 @@ func Tree(cfg Config, tmpl *template.Template) http.HandlerFunc {
}
}
if git.IsEmpty() {
- tmpl.ExecuteTemplate(w, "tree.html", map[string]any{
+ tmpl.ExecuteTemplate(w, "tree.html", withUser(r, map[string]any{
"Repo": git.Repo(),
+ "IsPrivate": git.IsPrivate(),
"Ref": ref,
"Path": "",
"Entries": []FileEntry{},
@@ -176,7 +177,7 @@ func Tree(cfg Config, tmpl *template.Template) http.HandlerFunc {
"Tags": []string{},
"Commits": 0,
"Empty": true,
- })
+ }))
return
}
entries, err := git.List(ref, treePath)
@@ -202,8 +203,9 @@ func Tree(cfg Config, tmpl *template.Template) http.HandlerFunc {
fileEntries = append(fileEntries, fe)
}
readme := git.Readme(ref, treePath)
- tmpl.ExecuteTemplate(w, "tree.html", map[string]any{
+ tmpl.ExecuteTemplate(w, "tree.html", withUser(r, map[string]any{
"Repo": git.Repo(),
+ "IsPrivate": git.IsPrivate(),
"Ref": ref,
"Path": treePath,
"Breadcrumb": Breadcrumb(treePath),
@@ -216,7 +218,7 @@ func Tree(cfg Config, tmpl *template.Template) http.HandlerFunc {
"Commits": git.CommitCount(ref),
"Readme": readme,
"ActiveTab": "code",
- })
+ }))
}
}
@@ -237,8 +239,9 @@ func Blob(cfg Config, tmpl *template.Template) http.HandlerFunc {
renderError(w, tmpl, http.StatusNotFound, err.Error())
return
}
- tmpl.ExecuteTemplate(w, "blob.html", map[string]any{
+ tmpl.ExecuteTemplate(w, "blob.html", withUser(r, map[string]any{
"Repo": git.Repo(),
+ "IsPrivate": git.IsPrivate(),
"Ref": ref,
"Path": filePath,
"Breadcrumb": Breadcrumb(filePath),
@@ -248,7 +251,7 @@ func Blob(cfg Config, tmpl *template.Template) http.HandlerFunc {
"Tags": git.Tags(),
"Commits": git.CommitCount(ref),
"ActiveTab": "code",
- })
+ }))
}
}
@@ -267,6 +270,7 @@ func Log(cfg Config, tmpl *template.Template) http.HandlerFunc {
data := map[string]any{
"User": User(r),
"Repo": git.Repo(),
+ "IsPrivate": git.IsPrivate(),
"Ref": ref,
"CommitList": commits,
"Description": git.Description(),
@@ -291,8 +295,9 @@ func Diff(cfg Config, tmpl *template.Template) http.HandlerFunc {
renderError(w, tmpl, http.StatusNotFound, "commit not found")
return
}
- if err := tmpl.ExecuteTemplate(w, "commit.html", map[string]any{
+ if err := tmpl.ExecuteTemplate(w, "commit.html", withUser(r, map[string]any{
"Repo": git.Repo(),
+ "IsPrivate": git.IsPrivate(),
"Ref": ref,
"Diff": commit,
"Hash": ref,
@@ -301,7 +306,7 @@ func Diff(cfg Config, tmpl *template.Template) http.HandlerFunc {
"Tags": git.Tags(),
"Commits": git.CommitCount(ref),
"ActiveTab": "commits",
- }); err != nil {
+ })); err != nil {
slog.Error("failed to execute template", "error", err)
}
}
@@ -356,15 +361,16 @@ func Refs(cfg Config, tmpl *template.Template) http.HandlerFunc {
return
}
ref := git.DefaultBranch()
- tmpl.ExecuteTemplate(w, "refs.html", map[string]any{
+ tmpl.ExecuteTemplate(w, "refs.html", withUser(r, map[string]any{
"Repo": git.Repo(),
+ "IsPrivate": git.IsPrivate(),
"Ref": ref,
"Branches": git.Branches(),
"Tags": git.Tags(),
"Description": git.Description(),
"Commits": git.CommitCount(ref),
"ActiveTab": "refs",
- })
+ }))
}
}
@@ -376,15 +382,19 @@ func SettingsGet(cfg Config, tmpl *template.Template) http.HandlerFunc {
}
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
meta := git.LoadMeta()
+ ref := git.DefaultBranch()
tmpl.ExecuteTemplate(w, "settings.html", map[string]any{
- "Repo": git.Repo(),
- "Ref": git.DefaultBranch(),
- "Description": meta.Description,
- "IsPrivate": meta.IsPrivate,
- "AuthorizedKeys": strings.Join(meta.AuthorizedKeys, "\n"),
+ "Repo": git.Repo(),
+ "Ref": ref,
+ "IsPrivate": meta.IsPrivate,
+ "Description": meta.Description,
+ "Branches": git.Branches(),
+ "Tags": git.Tags(),
+ "Commits": git.CommitCount(ref),
+ "AuthorizedKeys": strings.Join(meta.AuthorizedKeys, "\n"),
"ProtectedBranches": strings.Join(meta.ProtectedBranches, "\n"),
- "ActiveTab": "settings",
- "User": User(r),
+ "ActiveTab": "settings",
+ "User": User(r),
})
}
}
diff --git a/templates/error.html b/templates/error.html
index b09548e..3272f20 100644
--- a/templates/error.html
+++ b/templates/error.html
@@ -1,9 +1,9 @@
{{template "head" .}}
{{define "title"}}Error {{.Code}} - fierj{{end}}
-<div style="text-align:center;padding:3rem 1rem;">
- <h1 style="font-size:3rem;font-weight:300;color:var(--text-secondary);margin-bottom:0.5rem;">{{.Code}}</h1>
- <p style="font-size:1.1rem;color:var(--text-secondary);margin-bottom:1.5rem;">{{.Message}}</p>
- <a href="/" class="btn" style="display:inline-flex;">← Back to repositories</a>
+<div class="error-page">
+ <h1>{{.Code}}</h1>
+ <p>{{.Message}}</p>
+ <a href="/" class="btn">← Back to repositories</a>
</div>
{{template "foot" .}}
diff --git a/templates/layout.html b/templates/layout.html
index 9a123ca..20c1d72 100644
--- a/templates/layout.html
+++ b/templates/layout.html
@@ -146,6 +146,15 @@
text-decoration: underline;
}
+ /* Default icon size; contexts below override where needed
+ (branch-picker/clone-btn use 14px, visibility badges 12px). */
+ .icon {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+ vertical-align: text-bottom;
+ }
+
/* Top bar */
.topbar {
background: var(--bg-topbar);
@@ -159,6 +168,7 @@
}
.topbar a,
+ .topbar .nav-link,
.topbar .logo {
color: var(--text-topbar);
text-decoration: none;
@@ -177,6 +187,9 @@
}
.topbar .nav-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
font-size: 0.85rem;
padding: var(--space-xs) var(--space-sm);
border-radius: var(--radius-sm);
@@ -202,6 +215,22 @@
background: rgba(255, 255, 255, 0.1);
}
+ .topbar .nav-link-muted {
+ opacity: 0.7;
+ }
+
+ .topbar .nav-form {
+ display: inline;
+ }
+
+ .topbar .nav-form .nav-link {
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 0.85rem;
+ font-family: inherit;
+ }
+
/* Main container */
.container {
max-width: var(--max-width);
@@ -231,7 +260,10 @@
font-weight: 600;
}
- .repo-header .visibility {
+ .visibility {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.3em;
font-size: 0.7rem;
border: 1px solid var(--border);
border-radius: 2em;
@@ -240,6 +272,17 @@
font-weight: 500;
}
+ .visibility .icon {
+ width: 12px;
+ height: 12px;
+ }
+
+ .visibility-private {
+ color: var(--text);
+ border-color: var(--border);
+ background: var(--bg-inset);
+ }
+
.repo-desc {
color: var(--text-secondary);
margin-bottom: var(--space-md);
@@ -322,6 +365,14 @@
flex: 1;
}
+ .code-bar .breadcrumb {
+ margin-bottom: 0;
+ }
+
+ .clone-btn.icon-btn {
+ text-decoration: none;
+ }
+
.clone-btn {
display: inline-flex;
align-items: center;
@@ -335,6 +386,11 @@
color: var(--text-secondary);
}
+ .clone-btn .icon {
+ width: 14px;
+ height: 14px;
+ }
+
/* File table */
.file-table {
border: 1px solid var(--border);
@@ -506,7 +562,7 @@
}
/* Forms */
- input,
+ input:not([type="checkbox"]):not([type="radio"]),
textarea {
font-family: var(--font-sans);
font-size: 0.9rem;
@@ -518,13 +574,21 @@
width: 100%;
}
- input:focus,
+ input:not([type="checkbox"]):not([type="radio"]):focus,
textarea:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-subtle);
}
+ input[type="checkbox"],
+ input[type="radio"] {
+ width: 1rem;
+ height: 1rem;
+ flex-shrink: 0;
+ accent-color: var(--accent);
+ }
+
label {
font-size: 0.85rem;
font-weight: 600;
@@ -536,6 +600,87 @@
margin-bottom: var(--space-md);
}
+ .form-narrow {
+ max-width: 600px;
+ }
+
+ /* Auth pages (login/setup) */
+ .auth-card {
+ max-width: 360px;
+ margin: var(--space-xl) auto;
+ }
+
+ .auth-card h1 {
+ margin-bottom: var(--space-lg);
+ text-align: center;
+ }
+
+ .auth-card .subtitle {
+ text-align: center;
+ color: var(--text-secondary);
+ margin-bottom: var(--space-lg);
+ }
+
+ .auth-card .btn {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .alert-danger {
+ background: var(--danger);
+ color: #fff;
+ padding: var(--space-sm) var(--space-md);
+ border-radius: var(--radius);
+ margin-bottom: var(--space-md);
+ font-size: 0.85rem;
+ }
+
+ /* Error page */
+ .error-page {
+ text-align: center;
+ padding: 3rem 1rem;
+ }
+
+ .error-page h1 {
+ font-size: 3rem;
+ font-weight: 300;
+ color: var(--text-secondary);
+ margin-bottom: 0.5rem;
+ }
+
+ .error-page p {
+ font-size: 1.1rem;
+ color: var(--text-secondary);
+ margin-bottom: 1.5rem;
+ }
+
+ .error-page .btn {
+ display: inline-flex;
+ }
+
+ /* Checkbox / radio row: input and its text sit side by side,
+ unlike a regular label which stacks above its field */
+ .form-check {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ font-size: 0.9rem;
+ font-weight: 400;
+ margin: 0;
+ cursor: pointer;
+ }
+
+ .hint {
+ font-size: 0.8rem;
+ color: var(--text-secondary);
+ margin-top: var(--space-xs);
+ }
+
+ .mono-input {
+ font-family: var(--font-mono);
+ font-size: 0.8rem;
+ }
+
/* Commit log */
.commit-list {
border: 1px solid var(--border);
@@ -573,6 +718,106 @@
font-size: 0.8rem;
}
+ /* Cards & simple layout grids (users.html, etc.) */
+ .card {
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: var(--space-lg);
+ background: var(--bg);
+ }
+
+ .card h2,
+ .section-title {
+ font-size: 1rem;
+ margin-bottom: var(--space-md);
+ }
+
+ .grid-2 {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: var(--space-xl);
+ }
+
+ .list-box {
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ background: var(--bg);
+ }
+
+ .list-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--space-sm) var(--space-md);
+ border-bottom: 1px solid var(--border-subtle);
+ }
+
+ .list-row:last-child {
+ border-bottom: none;
+ }
+
+ .list-row .tag-self {
+ font-size: 0.8rem;
+ color: var(--text-tertiary);
+ }
+
+ .btn-sm {
+ padding: 2px 10px;
+ font-size: 0.8rem;
+ }
+
+ .mt-xl {
+ margin-top: var(--space-xl);
+ }
+
+ @media (max-width: 600px) {
+ .grid-2 {
+ grid-template-columns: 1fr;
+ }
+ }
+
+ /* Repo list (repos.html) */
+ .page-header {
+ margin-bottom: var(--space-lg);
+ font-size: 1.5rem;
+ }
+
+ .repo-list {
+ display: grid;
+ gap: var(--space-md);
+ }
+
+ .repo-card {
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: var(--space-md) var(--space-lg);
+ background: var(--bg);
+ }
+
+ .repo-card-header {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ }
+
+ .repo-card-header > .icon {
+ width: 16px;
+ height: 16px;
+ color: var(--text-secondary);
+ flex-shrink: 0;
+ }
+
+ .repo-card-header a {
+ font-weight: 600;
+ font-size: 1rem;
+ }
+
+ .repo-card-desc {
+ color: var(--text-secondary);
+ margin-top: var(--space-xs);
+ font-size: 0.85rem;
+ }
+
/* Empty state */
.empty-state {
border: 1px dashed var(--border);
@@ -662,7 +907,7 @@
}
.threads-state-toggle a.active {
- color: var(--text-primary);
+ color: var(--text);
font-weight: 600;
}
@@ -700,6 +945,11 @@
margin-top: 2px;
}
+ .icon-ref {
+ color: var(--text-secondary);
+ margin-top: 2px;
+ }
+
.thread-list-content {
flex: 1;
min-width: 0;
@@ -708,7 +958,7 @@
.thread-title {
font-weight: 600;
- color: var(--text-primary);
+ color: var(--text);
text-decoration: none;
display: block;
}
@@ -798,7 +1048,7 @@
display: flex;
justify-content: space-between;
padding: var(--space-sm) var(--space-md);
- background: var(--bg-secondary);
+ background: var(--bg-subtle);
border-bottom: 1px solid var(--border);
font-size: 0.85rem;
border-radius: var(--radius) var(--radius) 0 0;
@@ -819,8 +1069,8 @@
padding: var(--space-sm);
border: 1px solid var(--border);
border-radius: var(--radius);
- background: var(--bg-primary);
- color: var(--text-primary);
+ background: var(--bg);
+ color: var(--text);
resize: vertical;
}
@@ -831,8 +1081,8 @@
}
.btn-secondary {
- background: var(--bg-secondary);
- color: var(--text-primary);
+ background: var(--bg-subtle);
+ color: var(--text);
border: 1px solid var(--border);
padding: 6px 14px;
border-radius: var(--radius);
@@ -853,12 +1103,12 @@
color: var(--text-secondary);
margin-bottom: var(--space-md);
padding: var(--space-sm) var(--space-md);
- background: var(--bg-secondary);
+ background: var(--bg-subtle);
border-radius: var(--radius);
}
.patch-branches-hint code {
- background: var(--bg-primary);
+ background: var(--bg);
padding: 2px 6px;
border-radius: 3px;
font-size: 0.8rem;
@@ -897,12 +1147,15 @@
fierj
</a>
<div class="spacer"></div>
- <a class="nav-link" href="/new">+ New</a>
+ <a class="nav-link" href="/new">
+ <svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z"/></svg>
+ New
+ </a>
{{if .User}}
<a class="nav-link" href="/users">Users</a>
- <span class="nav-link" style="opacity:0.7;">{{.User}}</span>
- <form method="post" action="/logout" style="display:inline;">
- <button type="submit" class="nav-link" style="background:none;border:none;cursor:pointer;font-size:0.85rem;">Sign out</button>
+ <span class="nav-link nav-link-muted">{{.User}}</span>
+ <form method="post" action="/logout" class="nav-form">
+ <button type="submit" class="nav-link">Sign out</button>
</form>
{{else}}
<a class="nav-link" href="/login">Sign in</a>
@@ -920,9 +1173,19 @@
</main>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/highlight.min.js"></script>
- <link rel="stylesheet"
+ <link id="hljs-theme" rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github-dark.min.css">
<script>
+ function hljsHref(theme) {
+ return theme === 'light'
+ ? 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github.min.css'
+ : 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github-dark.min.css';
+ }
+ function resolvedTheme() {
+ const attr = document.documentElement.getAttribute('data-theme');
+ if (attr) return attr;
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+ }
function toggleTheme() {
const html = document.documentElement;
const current = html.getAttribute('data-theme');
@@ -930,10 +1193,12 @@
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'light' : 'dark'));
html.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
+ document.getElementById('hljs-theme').href = hljsHref(next);
}
(function () {
const saved = localStorage.getItem('theme');
if (saved) document.documentElement.setAttribute('data-theme', saved);
+ document.getElementById('hljs-theme').href = hljsHref(resolvedTheme());
})();
document.querySelectorAll('pre code').forEach(el => hljs.highlightElement(el));
document.querySelectorAll('.markdown-body').forEach(el => {
diff --git a/templates/login.html b/templates/login.html
index dc515d3..c496a59 100644
--- a/templates/login.html
+++ b/templates/login.html
@@ -1,11 +1,11 @@
{{template "head" .}}
{{define "title"}}sign in - fierj{{end}}
-<div style="max-width:360px;margin:var(--space-xl) auto;">
- <h1 style="margin-bottom:var(--space-lg);text-align:center;">Sign in to fierj</h1>
+<div class="auth-card">
+ <h1>Sign in to fierj</h1>
{{if .Error}}
- <div style="background:var(--danger);color:#fff;padding:var(--space-sm) var(--space-md);border-radius:var(--radius);margin-bottom:var(--space-md);font-size:0.85rem;">
+ <div class="alert-danger">
{{.Error}}
</div>
{{end}}
@@ -19,7 +19,7 @@
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
</div>
- <button type="submit" class="btn" style="width:100%;">Sign in</button>
+ <button type="submit" class="btn">Sign in</button>
</form>
</div>
{{template "foot" .}}
diff --git a/templates/new_repo.html b/templates/new_repo.html
index c5b2e40..5f88440 100644
--- a/templates/new_repo.html
+++ b/templates/new_repo.html
@@ -1,18 +1,18 @@
{{template "head" .}}
{{define "title"}}new repository - fierj{{end}}
-<h1 style="margin-bottom: var(--space-lg);">New Repository</h1>
-<form method="post" style="max-width: 520px;">
+<h1 class="page-header">New Repository</h1>
+<form method="post" class="form-narrow">
<div class="form-group">
<label for="import_url">Import from URL (optional)</label>
<input type="text" id="import_url" name="import_url" placeholder="https://github.com/user/repo.git">
- <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: var(--space-xs);">
+ <div class="hint">
Leave blank to create an empty repository.
</div>
</div>
<div class="form-group">
<label for="name">Repository name</label>
<input type="text" id="name" name="name" placeholder="my-project" {{if eq .FromImport "true"}}value="{{.SuggestedName}}"{{end}}>
- <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: var(--space-xs);">
+ <div class="hint">
Required unless importing — then it's derived from the URL.
</div>
</div>
diff --git a/templates/partials.html b/templates/partials.html
index ba54680..9aedaa5 100644
--- a/templates/partials.html
+++ b/templates/partials.html
@@ -6,7 +6,14 @@
d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z" />
</svg>
<h1><a href="/{{.Repo}}">{{.Repo}}</a></h1>
+ {{if .IsPrivate}}
+ <span class="visibility visibility-private">
+ <svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z"/></svg>
+ Private
+ </span>
+ {{else}}
<span class="visibility">Public</span>
+ {{end}}
</div>
{{if .Description}}<p class="repo-desc">{{.Description}}</p>{{end}}
{{end}}
@@ -23,21 +30,19 @@
<a href="/{{.Repo}}/log/{{.Ref}}" {{if eq .ActiveTab "commits" }} class="active" {{end}}>
<svg class="icon" viewBox="0 0 16 16" fill="currentColor">
<path
- d="M1.643 3.143.427 1.927A.25.25 0 0 1 .604 1.5h3.71a.25.25 0 0 1 .177.427L3.275 3.143a3.5 3.5 0 1 0 5.6 1.476.75.75 0 1 1 1.388.568A5 5 0 1 1 1.643 3.143ZM8 4a.75.75 0 0 1 .75.75v2.44l1.53 1.53a.75.75 0 0 1-1.06 1.06l-1.72-1.72V4.75A.75.75 0 0 1 8 4Z" />
+ d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z" />
</svg>
Commits
{{if .Commits}}<span class="count">{{.Commits}}</span>{{end}}
</a>
- {{if or .Branches .Tags}}
<a href="/{{.Repo}}/refs" {{if eq .ActiveTab "refs" }} class="active" {{end}}>
<svg class="icon" viewBox="0 0 16 16" fill="currentColor">
<path
d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Z" />
</svg>
Refs
- <span class="count">{{len .Branches}}{{if .Tags}}+{{len .Tags}}{{end}}</span>
+ {{if or .Branches .Tags}}<span class="count">{{len .Branches}}{{if .Tags}}+{{len .Tags}}{{end}}</span>{{end}}
</a>
- {{end}}
<a href="/{{.Repo}}/threads" {{if eq .ActiveTab "threads" }} class="active" {{end}}>
<svg class="icon" viewBox="0 0 16 16" fill="currentColor">
<path
@@ -48,22 +53,24 @@
<a href="/{{.Repo}}/patches" {{if eq .ActiveTab "patches" }} class="active" {{end}}>
<svg class="icon" viewBox="0 0 16 16" fill="currentColor">
<path
- d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354Z" />
+ d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z" />
</svg>
Patches
</a>
+ {{if $.User}}
<a href="/{{.Repo}}/jobs" {{if eq .ActiveTab "jobs" }} class="active" {{end}}>
<svg class="icon" viewBox="0 0 16 16" fill="currentColor">
- <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM4.5 7.5a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1h-7Z"/>
+ <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z"/>
</svg>
Jobs
</a>
<a href="/{{.Repo}}/settings" {{if eq .ActiveTab "settings" }} class="active" {{end}}>
<svg class="icon" viewBox="0 0 16 16" fill="currentColor">
- <path d="M8 0a2 2 0 0 1 2 2v.586l2.293-2.293a1 1 0 1 1 1.414 1.414L11.414 4H12a2 2 0 0 1 2 2v3.5a2.5 2.5 0 0 1-2.5 2.5H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 13.043V12H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h.586L.293 1.707a1 1 0 1 1 1.414-1.414L4 2.586V2a2 2 0 0 1 2-2Z"/>
+ <path d="M8 0a8.2 8.2 0 0 1 .701.031C9.444.095 9.99.645 10.16 1.29l.288 1.107c.018.066.079.158.212.224.231.114.454.243.668.386.123.082.233.09.299.071l1.103-.303c.644-.176 1.392.021 1.82.63.27.385.506.792.704 1.218.315.675.111 1.422-.364 1.891l-.814.806c-.049.048-.098.147-.088.294.016.257.016.515 0 .772-.01.147.038.246.088.294l.814.806c.475.469.679 1.216.364 1.891a7.977 7.977 0 0 1-.704 1.217c-.428.61-1.176.807-1.82.63l-1.102-.302c-.067-.019-.177-.011-.3.071a5.909 5.909 0 0 1-.668.386c-.133.066-.194.158-.211.224l-.29 1.106c-.168.646-.715 1.196-1.458 1.26a8.006 8.006 0 0 1-1.402 0c-.743-.064-1.289-.614-1.458-1.26l-.289-1.106c-.018-.066-.079-.158-.212-.224a5.738 5.738 0 0 1-.668-.386c-.123-.082-.233-.09-.299-.071l-1.103.303c-.644.176-1.392-.021-1.82-.63a8.12 8.12 0 0 1-.704-1.218c-.315-.675-.111-1.422.363-1.891l.815-.806c.05-.048.098-.147.088-.294a6.214 6.214 0 0 1 0-.772c.01-.147-.038-.246-.088-.294l-.815-.806C.635 6.045.431 5.298.746 4.623a7.92 7.92 0 0 1 .704-1.217c.428-.61 1.176-.807 1.82-.63l1.102.302c.067.019.177.011.3-.071.214-.143.437-.272.668-.386.133-.066.194-.158.211-.224l.29-1.106C6.009.645 6.556.095 7.299.03 7.53.01 7.764 0 8 0Zm-.571 1.525c-.036.003-.108.036-.137.146l-.289 1.105c-.147.561-.549.967-.998 1.189-.173.086-.34.183-.5.29-.417.278-.97.423-1.529.27l-1.103-.303c-.109-.03-.175.016-.195.045-.22.312-.412.644-.573.99-.014.031-.021.11.059.19l.815.806c.411.406.562.957.53 1.456a4.709 4.709 0 0 0 0 .582c.032.499-.119 1.05-.53 1.456l-.815.806c-.081.08-.073.159-.059.19.162.346.353.677.573.989.02.03.085.076.195.046l1.102-.303c.56-.153 1.113-.008 1.53.27.161.107.328.204.501.29.447.222.85.629.997 1.189l.289 1.105c.029.109.101.143.137.146a6.6 6.6 0 0 0 1.142 0c.036-.003.108-.036.137-.146l.289-1.105c.147-.561.549-.967.998-1.189.173-.086.34-.183.5-.29.417-.278.97-.423 1.529-.27l1.103.303c.109.029.175-.016.195-.045.22-.313.411-.644.573-.99.014-.031.021-.11-.059-.19l-.815-.806c-.411-.406-.562-.957-.53-1.456a4.709 4.709 0 0 0 0-.582c-.032-.499.119-1.05.53-1.456l.815-.806c.081-.08.073-.159.059-.19a6.464 6.464 0 0 0-.573-.989c-.02-.03-.085-.076-.195-.046l-1.102.303c-.56.153-1.113.008-1.53-.27a4.44 4.44 0 0 0-.501-.29c-.447-.222-.85-.629-.997-1.189l-.289-1.105c-.029-.11-.101-.143-.137-.146a6.6 6.6 0 0 0-1.142 0ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM9.5 8a1.5 1.5 0 1 0-3.001.001A1.5 1.5 0 0 0 9.5 8Z"/>
</svg>
Settings
</a>
+ {{end}}
</nav>
{{end}}
diff --git a/templates/refs.html b/templates/refs.html
index e2d0cf8..446863d 100644
--- a/templates/refs.html
+++ b/templates/refs.html
@@ -10,10 +10,10 @@
<div class="thread-list">
{{range .Branches}}
<div class="thread-list-item">
- <svg class="icon thread-icon-open" viewBox="0 0 16 16" fill="currentColor"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Z"/></svg>
+ <svg class="icon icon-ref" viewBox="0 0 16 16" fill="currentColor"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Z"/></svg>
<div class="thread-list-content">
<a href="/{{$.Repo}}/tree/{{.}}" class="thread-title">{{.}}</a>
- {{if eq . $.DefaultBranch}}<span class="label label-default">default</span>{{end}}
+ {{if eq . $.DefaultBranch}}<span class="label-default">default</span>{{end}}
</div>
</div>
{{end}}
@@ -26,7 +26,7 @@
<div class="thread-list">
{{range .Tags}}
<div class="thread-list-item">
- <svg class="icon thread-icon-open" viewBox="0 0 16 16" fill="currentColor"><path d="M1 7.775V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 0 1 0 2.474l-5.026 5.026a1.75 1.75 0 0 1-2.474 0l-6.25-6.25A1.752 1.752 0 0 1 1 7.775Zm1.5 0c0 .066.026.13.073.177l6.25 6.25a.25.25 0 0 0 .354 0l5.025-5.025a.25.25 0 0 0 0-.354l-6.25-6.25a.25.25 0 0 0-.177-.073H2.75a.25.25 0 0 0-.25.25ZM6 5a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z"/></svg>
+ <svg class="icon icon-ref" viewBox="0 0 16 16" fill="currentColor"><path d="M1 7.775V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 0 1 0 2.474l-5.026 5.026a1.75 1.75 0 0 1-2.474 0l-6.25-6.25A1.752 1.752 0 0 1 1 7.775Zm1.5 0c0 .066.026.13.073.177l6.25 6.25a.25.25 0 0 0 .354 0l5.025-5.025a.25.25 0 0 0 0-.354l-6.25-6.25a.25.25 0 0 0-.177-.073H2.75a.25.25 0 0 0-.25.25ZM6 5a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z"/></svg>
<div class="thread-list-content">
<a href="/{{$.Repo}}/tree/{{.}}" class="thread-title">{{.}}</a>
</div>
diff --git a/templates/repos.html b/templates/repos.html
index 5816688..cefef3e 100644
--- a/templates/repos.html
+++ b/templates/repos.html
@@ -1,21 +1,26 @@
{{template "head" .}}
{{define "title"}}repositories - fierj{{end}}
-<h1 style="margin-bottom: var(--space-lg);">Repositories</h1>
+<h1 class="page-header">Repositories</h1>
{{if .Repos}}
-<div style="display: grid; gap: var(--space-md);">
+<div class="repo-list">
{{range .Repos}}
- <div
- style="border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-md) var(--space-lg); background: var(--bg);">
- <div style="display: flex; align-items: center; gap: var(--space-sm);">
- <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="color: var(--text-secondary);">
+ <div class="repo-card">
+ <div class="repo-card-header">
+ <svg class="icon" viewBox="0 0 16 16" fill="currentColor">
<path
d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z" />
</svg>
- <a href="/{{.Name}}" style="font-weight: 600; font-size: 1rem;">{{.Name}}</a>
- <span style="font-size: 0.7rem; border: 1px solid var(--border); border-radius: 2em; padding: 0.1rem 0.5rem; color: var(--text-secondary);">{{if .IsPrivate}}Private{{else}}Public{{end}}</span>
+ <a href="/{{.Name}}">{{.Name}}</a>
+ {{if .IsPrivate}}
+ <span class="visibility visibility-private">
+ <svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z"/></svg>
+ Private
+ </span>
+ {{else}}
+ <span class="visibility">Public</span>
+ {{end}}
</div>
- {{if .Description}}<p style="color: var(--text-secondary); margin-top: var(--space-xs); font-size: 0.85rem;">
- {{.Description}}</p>{{end}}
+ {{if .Description}}<p class="repo-card-desc">{{.Description}}</p>{{end}}
</div>
{{end}}
</div>
diff --git a/templates/settings.html b/templates/settings.html
index 298e97d..b791219 100644
--- a/templates/settings.html
+++ b/templates/settings.html
@@ -3,16 +3,16 @@
{{template "repo-header" .}}
{{template "repo-tabs" .}}
-<h1 style="margin-bottom:var(--space-lg);font-size:1.25rem;">Repository settings</h1>
+<h1 class="page-header" style="font-size:1.25rem;">Repository settings</h1>
-<form method="post" style="max-width:600px;">
+<form method="post" class="form-narrow">
<div class="form-group">
<label for="description">Description</label>
<input type="text" id="description" name="description" value="{{.Description}}">
</div>
<div class="form-group">
- <label>
+ <label class="form-check">
<input type="checkbox" name="is_private" value="true" {{if .IsPrivate}}checked{{end}}>
Private repository (only visible to logged-in users)
</label>
@@ -20,16 +20,16 @@
<div class="form-group">
<label for="authorized_keys">Authorized SSH keys (one per line)</label>
- <textarea id="authorized_keys" name="authorized_keys" rows="6" style="font-family:var(--font-mono);font-size:0.8rem;">{{.AuthorizedKeys}}</textarea>
- <div style="font-size:0.8rem;color:var(--text-secondary);margin-top:var(--space-xs);">
+ <textarea id="authorized_keys" name="authorized_keys" rows="6" class="mono-input">{{.AuthorizedKeys}}</textarea>
+ <div class="hint">
Users with these keys can push to this repo. Leave empty to allow anyone with SSH access.
</div>
</div>
<div class="form-group">
<label for="protected_branches">Protected branches (one per line)</label>
- <textarea id="protected_branches" name="protected_branches" rows="4" placeholder="main release/*" style="font-family:var(--font-mono);font-size:0.8rem;">{{.ProtectedBranches}}</textarea>
- <div style="font-size:0.8rem;color:var(--text-secondary);margin-top:var(--space-xs);">
+ <textarea id="protected_branches" name="protected_branches" rows="4" placeholder="main release/*" class="mono-input">{{.ProtectedBranches}}</textarea>
+ <div class="hint">
Branches that require authorized keys to push. Supports wildcards (e.g. <code>release/*</code>).
</div>
</div>
diff --git a/templates/setup.html b/templates/setup.html
index 113e392..43e8b00 100644
--- a/templates/setup.html
+++ b/templates/setup.html
@@ -1,14 +1,14 @@
{{template "head" .}}
{{define "title"}}setup - fierj{{end}}
-<div style="max-width:360px;margin:var(--space-xl) auto;">
- <h1 style="margin-bottom:var(--space-sm);text-align:center;">Welcome to fierj</h1>
- <p style="text-align:center;color:var(--text-secondary);margin-bottom:var(--space-lg);">
+<div class="auth-card">
+ <h1 style="margin-bottom:var(--space-sm);">Welcome to fierj</h1>
+ <p class="subtitle">
Create the first admin account to get started.
</p>
{{if .Error}}
- <div style="background:var(--danger);color:#fff;padding:var(--space-sm) var(--space-md);border-radius:var(--radius);margin-bottom:var(--space-md);font-size:0.85rem;">
+ <div class="alert-danger">
{{.Error}}
</div>
{{end}}
@@ -26,7 +26,7 @@
<label for="confirm">Confirm password</label>
<input type="password" id="confirm" name="confirm" required minlength="6">
</div>
- <button type="submit" class="btn" style="width:100%;">Create account</button>
+ <button type="submit" class="btn">Create account</button>
</form>
</div>
{{template "foot" .}}
diff --git a/templates/tree.html b/templates/tree.html
index 795a2df..310d000 100644
--- a/templates/tree.html
+++ b/templates/tree.html
@@ -18,18 +18,18 @@ git push -u origin main</code></pre>{{end}}
{{.Ref}}
</span>
{{if .Breadcrumb}}
- <span class="breadcrumb" style="font-size:0.85rem;color:var(--text-secondary);">
+ <span class="breadcrumb">
<a href="/{{.Repo}}">{{.Repo}}</a>
{{range .Breadcrumb}} / <a href="/{{$.Repo}}/tree/{{$.Ref}}/{{.URL}}">{{.Name}}</a>{{end}}
</span>
{{end}}
- <span class="code-bar spacer"></span>
+ <span class="spacer"></span>
<span class="clone-btn">
- <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M4.72 3.22a.75.75 0 0 1 1.06 1.06L2.06 8l3.72 3.72a.75.75 0 1 1-1.06 1.06L.47 8.53a.75.75 0 0 1 0-1.06Zm6.56 0a.75.75 0 1 0-1.06 1.06L13.94 8l-3.72 3.72a.75.75 0 1 0 1.06 1.06l4.25-4.25a.75.75 0 0 0 0-1.06Z"/></svg>
+ <svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M4.72 3.22a.75.75 0 0 1 1.06 1.06L2.06 8l3.72 3.72a.75.75 0 1 1-1.06 1.06L.47 8.53a.75.75 0 0 1 0-1.06Zm6.56 0a.75.75 0 1 0-1.06 1.06L13.94 8l-3.72 3.72a.75.75 0 1 0 1.06 1.06l4.25-4.25a.75.75 0 0 0 0-1.06Z"/></svg>
{{if .CloneSSH}}{{.CloneSSH}}{{end}}
</span>
- <a href="/{{.Repo}}/feed.xml" title="RSS feed" class="clone-btn" style="text-decoration:none;">
- <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2.002 2.725a.75.75 0 0 1 .797-.699C8.79 2.42 13.58 7.21 13.974 13.201a.75.75 0 0 1-1.497.098 10.502 10.502 0 0 0-9.776-9.776.747.747 0 0 1-.7-.798ZM2.84 7.05h-.002a7.002 7.002 0 0 1 6.113 6.111.75.75 0 0 1-1.5.008 5.501 5.501 0 0 0-4.8-4.8.75.75 0 0 1 .189-1.319ZM2 13a1 1 0 1 0 2 0 1 1 0 0 0-2 0Z"/></svg>
+ <a href="/{{.Repo}}/feed.xml" title="RSS feed" class="clone-btn icon-btn">
+ <svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M2.002 2.725a.75.75 0 0 1 .797-.699C8.79 2.42 13.58 7.21 13.974 13.201a.75.75 0 0 1-1.497.098 10.502 10.502 0 0 0-9.776-9.776.747.747 0 0 1-.7-.798ZM2.84 7.05h-.002a7.002 7.002 0 0 1 6.113 6.111.75.75 0 0 1-1.5.008 5.501 5.501 0 0 0-4.8-4.8.75.75 0 0 1 .189-1.319ZM2 13a1 1 0 1 0 2 0 1 1 0 0 0-2 0Z"/></svg>
</a>
</div>
diff --git a/templates/users.html b/templates/users.html
index 585b009..c3392fd 100644
--- a/templates/users.html
+++ b/templates/users.html
@@ -1,12 +1,12 @@
{{template "head" .}}
{{define "title"}}users - fierj{{end}}
-<h1 style="margin-bottom:var(--space-lg);">Users</h1>
+<h1 class="page-header">Users</h1>
-<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-xl);">
+<div class="grid-2">
<!-- Add user -->
- <div style="border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-lg);">
- <h2 style="font-size:1rem;margin-bottom:var(--space-md);">Add user</h2>
+ <div class="card">
+ <h2>Add user</h2>
<form method="post">
<input type="hidden" name="action" value="add">
<div class="form-group">
@@ -22,8 +22,8 @@
</div>
<!-- Change own password -->
- <div style="border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-lg);">
- <h2 style="font-size:1rem;margin-bottom:var(--space-md);">Change your password</h2>
+ <div class="card">
+ <h2>Change your password</h2>
<form method="post">
<input type="hidden" name="action" value="change-password">
<div class="form-group">
@@ -36,20 +36,20 @@
</div>
<!-- User list -->
-<div style="margin-top:var(--space-xl);">
- <h2 style="font-size:1rem;margin-bottom:var(--space-md);">All users ({{len .Users}})</h2>
- <div style="border:1px solid var(--border);border-radius:var(--radius);">
+<div class="mt-xl">
+ <h2 class="section-title">All users ({{len .Users}})</h2>
+ <div class="list-box">
{{range .Users}}
- <div style="display:flex;align-items:center;justify-content:space-between;padding:var(--space-sm) var(--space-md);border-bottom:1px solid var(--border-subtle);">
+ <div class="list-row">
<span>{{.}}</span>
{{if ne . $.User}}
- <form method="post" style="display:inline;" onsubmit="return confirm('Remove {{.}}?')">
+ <form method="post" onsubmit="return confirm('Remove {{.}}?')">
<input type="hidden" name="action" value="remove">
<input type="hidden" name="username" value="{{.}}">
- <button type="submit" class="btn-subtle" style="padding:2px 10px;font-size:0.8rem;">Remove</button>
+ <button type="submit" class="btn-subtle btn-sm">Remove</button>
</form>
{{else}}
- <span style="font-size:0.8rem;color:var(--text-tertiary);">you</span>
+ <span class="tag-self">you</span>
{{end}}
</div>
{{end}}