120 lines
3.7 KiB
Go
120 lines
3.7 KiB
Go
package web
|
|
|
|
import (
|
|
"html/template"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"munin/internal/auth"
|
|
"munin/internal/hilfe"
|
|
)
|
|
|
|
// hilfeData sind die Daten einer Hilfeseite.
|
|
type hilfeData struct {
|
|
Seite hilfe.Seite // die angezeigte Seite
|
|
Seiten []hilfe.Seite // alle Seiten, für die Navigation
|
|
HTML template.HTML // der gerenderte Inhalt
|
|
}
|
|
|
|
// handleHilfe zeigt eine Seite der eingebauten Onboarding-Dokumentation.
|
|
//
|
|
// Erreichbar für jede angemeldete Sitzung, unabhängig von Rolle und Projekt:
|
|
// Die Hilfe enthält nichts, was nicht jeder Nutzer wissen darf. Auch ein
|
|
// Plattform-Admin, der noch keinen Workspace gewählt hat, kommt hierher.
|
|
//
|
|
// /hilfe ohne Seitenangabe zeigt die Startseite. Deshalb kommt der Slug aus
|
|
// r.PathValue und fällt auf "start" zurück, wenn er leer ist.
|
|
func (s *Server) handleHilfe(w http.ResponseWriter, r *http.Request) {
|
|
slug := r.PathValue("seite")
|
|
if slug == "" {
|
|
slug = "start"
|
|
}
|
|
|
|
sess, _ := SessionFrom(r)
|
|
|
|
// Eine Admin-Seite antwortet für alle anderen mit 404, genau wie eine
|
|
// Seite, die es nicht gibt — sie steht für sie ja auch nirgends verlinkt.
|
|
leser := hilfe.Leser{PlattformAdmin: sess.IsPlatformAdmin, WorkspaceAdmin: sess.IsWorkspaceAdmin}
|
|
seite, ok := hilfe.Finde(slug)
|
|
if !ok || !seite.DarfLesen(leser) {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
|
|
markdown, err := seite.Markdown()
|
|
if err != nil {
|
|
s.serverError(w, "Hilfeseite konnte nicht geladen werden", err)
|
|
return
|
|
}
|
|
|
|
// Platzhalter ersetzen, damit die Beispiele direkt kopierbar sind: mit der
|
|
// Adresse dieses Servers und dem Bezeichner des eigenen Projekts statt
|
|
// mit "DEIN-SERVER". Das passiert vor dem Rendern — goldmark sieht nur
|
|
// noch fertigen Text und maskiert ihn wie jeden anderen.
|
|
projektSlug := "mein-projekt"
|
|
if sess.Project.Slug != "" {
|
|
projektSlug = sess.Project.Slug
|
|
}
|
|
markdown = strings.NewReplacer(
|
|
"{{SERVER}}", serverURL(r),
|
|
"{{PROJEKT}}", projektSlug,
|
|
).Replace(markdown)
|
|
|
|
inhalt, err := hilfe.Render(markdown)
|
|
if err != nil {
|
|
s.serverError(w, "Hilfeseite konnte nicht dargestellt werden", err)
|
|
return
|
|
}
|
|
|
|
data := pageData{
|
|
Title: seite.Titel,
|
|
Bereich: "hilfe",
|
|
Data: hilfeData{
|
|
Seite: seite,
|
|
Seiten: hilfe.Sichtbar(leser),
|
|
HTML: template.HTML(inhalt),
|
|
},
|
|
}
|
|
|
|
// Ein Projekt-Key soll auch auf der Hilfe seine gewohnte Seitenleiste
|
|
// sehen (Einträge, Graph, Schlagworte). render() übernimmt das Projekt
|
|
// sonst nur aus der URL, und /hilfe hat keins.
|
|
if !sess.IsPlatformAdmin {
|
|
data.Project = sess.Project
|
|
}
|
|
|
|
s.render(w, r, "hilfe.html", http.StatusOK, data)
|
|
}
|
|
|
|
// gueltigerHost erlaubt nur, was in einem Hostnamen mit Port vorkommen kann:
|
|
// Buchstaben, Ziffern, Punkt, Bindestrich, Doppelpunkt und die eckigen
|
|
// Klammern einer IPv6-Adresse.
|
|
var gueltigerHost = regexp.MustCompile(`^[A-Za-z0-9.\-:\[\]]+$`)
|
|
|
|
// serverURL errät die Adresse, unter der dieser Server von außen erreichbar
|
|
// ist — für die Beispiele in der Hilfe.
|
|
//
|
|
// # Warum nur "erraten"?
|
|
//
|
|
// Der Host-Header kommt vom Browser und damit vom Nutzer selbst. Das ist hier
|
|
// unbedenklich: Das Ergebnis landet nur in der Seite, die derselbe Nutzer
|
|
// gerade abruft, und wird nirgends gespeichert. Trotzdem wird der Wert
|
|
// geprüft, und bei allem Ungewöhnlichen steht ein Platzhalter da.
|
|
//
|
|
// Das Schema ist schwerer zu erkennen: Hinter einem Reverse-Proxy, der TLS
|
|
// beendet, kommt die Anfrage hier als http an. Deshalb gilt: https, außer bei
|
|
// localhost ohne TLS — dieselbe Regel wie beim Secure-Flag der Cookies, und
|
|
// deshalb auch dieselbe Funktion (auth.IsLocalhost).
|
|
func serverURL(r *http.Request) string {
|
|
host := r.Host
|
|
if host == "" || !gueltigerHost.MatchString(host) {
|
|
return "https://DEIN-SERVER"
|
|
}
|
|
|
|
schema := "https"
|
|
if r.TLS == nil && auth.IsLocalhost(r) {
|
|
schema = "http"
|
|
}
|
|
return schema + "://" + host
|
|
}
|