v1.0.0
Some checks failed
release-tag / release-image (push) Successful in 4m8s
build-binaries / build (, amd64, linux) (push) Failing after 40s
build-binaries / build (, arm, 7, linux) (push) Failing after 37s
build-binaries / build (, arm64, linux) (push) Failing after 37s
build-binaries / build (.exe, amd64, windows) (push) Failing after 36s
build-binaries / release (push) Has been skipped

This commit is contained in:
2025-10-05 10:01:28 +02:00
parent eff64482a1
commit c094b36af9
5 changed files with 513 additions and 18 deletions

117
main.go
View File

@@ -245,12 +245,12 @@ var baseTpl = `{{define "base"}}
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{block "title" .}}Meldesystem{{end}}</title>
<title>{{block "title" .}}Advocacy Watchlist{{end}}</title>
<style>
:root { --bg:#0f172a; --card:#111827; --muted:#9ca3af; --text:#e5e7eb; --acc:#06b6d4; --ok:#10b981; --warn:#f59e0b; --err:#ef4444; }
*{box-sizing:border-box} body{margin:0;background:linear-gradient(180deg,#0b1220,#0f172a);color:var(--text);font:16px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial}
a{color:var(--acc);text-decoration:none} a:hover{text-decoration:underline}
.container{max-width:980px;margin:40px auto;padding:0 16px}
.container{max-width:1200px;margin:40px auto;padding:0 16px}
header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}
.brand{font-weight:800;font-size:20px;letter-spacing:.2px}
.card{background:var(--card);border:1px solid #1f2937;border-radius:16px;box-shadow:0 10px 30px rgba(0,0,0,.25);padding:20px;margin-bottom:16px}
@@ -278,7 +278,7 @@ var baseTpl = `{{define "base"}}
<body>
<div class="container">
<header>
<div class="brand">Meldesystem</div>
<div class="brand">Advocacy Watchlist</div>
<div class="flex">
{{if .IsAdmin}}<a class="badge" href="/users">👥 Nutzer</a>{{end}}
<a class="badge" href="/audit">🪪 Audit</a>
@@ -288,7 +288,7 @@ var baseTpl = `{{define "base"}}
</div>
</header>
{{template "content" .}}
<footer>Ohne externe Assets · Zeit: {{.Now}}</footer>
<footer>Advocacy Watchlist · Zeit: {{.Now}}</footer>
</div>
</body>
</html>
@@ -300,12 +300,24 @@ var loginTpl = `{{define "login"}}
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Login · Meldesystem</title>
<style>*{box-sizing:border-box} body{margin:0;display:grid;place-items:center;height:100vh;background:linear-gradient(180deg,#0b1220,#0f172a);color:#e5e7eb;font:16px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial} .card{background:#111827;border:1px solid #1f2937;border-radius:16px;box-shadow:0 10px 30px rgba(0,0,0,.25);padding:24px;width:min(420px,94vw)} input{width:100%;max-width:100%;display:block;margin:0;border:1px solid #273244;border-radius:12px;background:#0b1220;color:#e5e7eb;padding:12px 14px} label{display:block;margin:8px 0 6px;color:#9ca3af} button{width:100%;margin-top:14px;padding:12px;border:0;border-radius:12px;background:#06b6d4;color:#001018;font-weight:800;cursor:pointer} .muted{color:#9ca3af;font-size:12px;text-align:center;margin-top:10px}</style>
<title>Login · Advocacy Watchlist</title>
<style>
*{box-sizing:border-box}
body{margin:0;display:grid;place-items:center;height:100vh;background:linear-gradient(180deg,#0b1220,#0f172a);color:#e5e7eb;font:16px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial}
.card{background:#111827;border:1px solid #1f2937;border-radius:16px;box-shadow:0 10px 30px rgba(0,0,0,.25);padding:24px;width:min(420px,94vw)}
input{width:100%;max-width:100%;display:block;margin:0;border:1px solid #273244;border-radius:12px;background:#0b1220;color:#e5e7eb;padding:12px 14px}
label{display:block;margin:8px 0 6px;color:#9ca3af}
button{width:100%;margin-top:14px;padding:12px;border:0;border-radius:12px;background:#06b6d4;color:#001018;font-weight:800;cursor:pointer}
.badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;background:#0b1220;border:1px solid #1f2937;color:#9ca3af;font-size:12px} <!-- NEU -->
.muted{color:#9ca3af;font-size:12px;text-align:center;margin-top:10px}
</style>
</head>
<body>
<div class="card">
<h2 style="margin:0 0 8px">Anmeldung</h2>
<div style="margin-top:12px;text-align:center">
<a class="badge" href="/public">🌐 View public Entries</a>
</div>
<form method="POST" action="/login">
<label>Nutzername</label>
<input name="username" required />
@@ -422,7 +434,7 @@ var dashboardTpl = `{{define "dashboard"}}{{template "base" .}}{{end}}
<button type="submit">Bestätigen</button>
</form>
{{else}}
<span class="badge">⏳ Wartet auf Bestätigung durch andere</span>
<span class="badge">⏳ Wartet auf Bestätigung</span>
{{end}}
{{else}}
<span class="badge">✔ Bereits bestätigt</span>
@@ -443,6 +455,65 @@ var dashboardTpl = `{{define "dashboard"}}{{template "base" .}}{{end}}
</div>
{{end}}`
var publicTpl = `{{define "public_page"}}
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Advocacy Watchlist - Public</title>
<style>
:root { --bg:#0f172a; --card:#111827; --muted:#9ca3af; --text:#e5e7eb; --acc:#06b6d4; }
*{box-sizing:border-box} body{margin:0;background:linear-gradient(180deg,#0b1220,#0f172a);color:var(--text);font:16px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial}
.container{max-width:880px;margin:40px auto;padding:0 16px}
header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}
.card{background:#111827;border:1px solid #1f2937;border-radius:16px;box-shadow:0 10px 30px rgba(0,0,0,.25);padding:20px;margin-bottom:16px}
.row{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
input[type=text]{flex:1 1 260px;padding:12px 14px;border:1px solid #273244;border-radius:12px;background:#0b1220;color:#e5e7eb}
button{padding:10px 14px;border:0;border-radius:12px;background:#06b6d4;color:#001018;font-weight:700;cursor:pointer}
table{width:100%;border-collapse:separate;border-spacing:0}
th,td{padding:12px 10px;border-bottom:1px solid #1f2937;font-size:14px}
th{text-align:left;color:#9ca3af;font-weight:600}
.muted{color:#9ca3af}
</style>
</head>
<body>
<div class="container">
<header>
<div class="brand">Advocacy Watchlist</div>
<div class="flex">
<a class="badge" href="/">🔒 Login</a>
</div>
</header>
<div class="card">
<h2 style="margin:0 0 10px">Public list of confirmed entries</h2>
<form class="row" method="GET" action="/public">
<input name="q" placeholder="Search…" value="{{.Q}}" />
<button type="submit">Filter</button>
</form>
<div class="muted" style="margin-top:8px">Only <b>confirmed</b> entries are displayed.</div>
</div>
<div class="card">
<table>
<thead><tr><th>Benutzername</th><th>Confirmed on</th></tr></thead>
<tbody>
{{range .Rows}}
<tr>
<td>{{.Name}}</td>
<td class="muted">{{.Since}}</td>
</tr>
{{else}}
<tr><td colspan="2" class="muted">No matching entries</td></tr>
{{end}}
</tbody>
</table>
</div>
</div>
</body>
</html>
{{end}}`
var auditTpl = `{{define "audit_page"}}
<!doctype html>
<html lang="de">
@@ -625,8 +696,10 @@ func main() {
template.Must(tpl.New("audit_bundle").Parse(auditTpl))
template.Must(tpl.New("users_bundle").Parse(usersTpl))
template.Must(tpl.New("me_bundle").Parse(meTpl))
template.Must(tpl.New("public_bundle").Parse(publicTpl))
http.HandleFunc("/", withAuth(handleIndex))
http.HandleFunc("/public", handlePublic)
http.HandleFunc("/reasons/add", withAuth(handleReasonAdd))
http.HandleFunc("/reasons/delete", withAuth(handleReasonDelete))
http.HandleFunc("/report", withAuth(handleReport))
@@ -895,6 +968,36 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
_ = tpl.ExecuteTemplate(w, "dashboard", data)
}
func handlePublic(w http.ResponseWriter, r *http.Request) {
q := strings.TrimSpace(r.URL.Query().Get("q"))
ql := strings.ToLower(q)
mu.Lock()
reports := make([]Report, 0, len(db.Reports))
for _, rep := range db.Reports {
if rep.Status != StatusBestaetigt {
continue // nur bestätigte öffentlich anzeigen
}
if q == "" || strings.Contains(strings.ToLower(rep.Name), ql) {
reports = append(reports, rep)
}
}
mu.Unlock()
rows := make([]map[string]any, 0, len(reports))
for _, rep := range reports {
rows = append(rows, map[string]any{
"Name": template.HTMLEscapeString(rep.Name),
"Since": time.Unix(rep.ConfirmedAt, 0).Format("02.01.2006 15:04"),
})
}
_ = tpl.ExecuteTemplate(w, "public_page", map[string]any{
"Q": q,
"Rows": rows,
})
}
func handleReport(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)