mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-06-19 09:47:29 +02:00
feat(web): make footer legal links configurable
Add a "main.footer-links" config option so the footer Imprint and Privacy Policy links can point at internal pages (default) or external URLs. External http(s) targets open in a new tab; empty/unset values fall back to the built-in /imprint and /privacy routes, keeping the existing ./var/*.tmpl override mechanism intact. Closes #517 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: c5dca72c848f
This commit is contained in:
@@ -18,6 +18,10 @@
|
||||
"trigger": 180,
|
||||
"resolutions": [240, 60]
|
||||
},
|
||||
"footer-links": {
|
||||
"imprint": "/imprint",
|
||||
"privacy": "/privacy"
|
||||
},
|
||||
"api-subjects": {
|
||||
"subject-job-event": "cc.job.event",
|
||||
"subject-node-state": "cc.node.state"
|
||||
|
||||
@@ -37,6 +37,13 @@ cp privacy.tmpl /opt/monitoring/cc-backend/var/
|
||||
# Ensure your logo, and any images you use in your login template has a suitable size.
|
||||
cp -R img /opt/monitoring/cc-backend/img
|
||||
|
||||
# 4b. (Optional) Instead of overriding imprint.tmpl/privacy.tmpl, you can point the
|
||||
# footer links to external pages via "main.footer-links" in config.json:
|
||||
# "footer-links": { "imprint": "https://example.com/imprint", "privacy": "https://example.com/privacy" }
|
||||
# Values may be internal paths (default "/imprint", "/privacy") or external URLs;
|
||||
# external URLs (http/https) open in a new browser tab. An empty value falls back
|
||||
# to the internal page.
|
||||
|
||||
# 5. Copy the systemd service unit file. You may adopt it to your needs.
|
||||
sudo cp ./init/clustercockpit.service /etc/systemd/system/clustercockpit.service
|
||||
|
||||
|
||||
@@ -80,6 +80,18 @@ type ProgramConfig struct {
|
||||
|
||||
// Database tuning configuration
|
||||
DbConfig *DbConfig `json:"db-config"`
|
||||
|
||||
// Optional external/legal links shown in the footer.
|
||||
FooterLinks FooterLinksConfig `json:"footer-links"`
|
||||
}
|
||||
|
||||
// FooterLinksConfig configures the legal/footer links rendered in the UI.
|
||||
// Each value may be an internal path (e.g. "/imprint") or an external URL.
|
||||
type FooterLinksConfig struct {
|
||||
// Target URL/path for the "Imprint" footer entry.
|
||||
Imprint string `json:"imprint"`
|
||||
// Target URL/path for the "Privacy Policy" footer entry.
|
||||
Privacy string `json:"privacy"`
|
||||
}
|
||||
|
||||
type DbConfig struct {
|
||||
@@ -145,6 +157,10 @@ var Keys ProgramConfig = ProgramConfig{
|
||||
SessionMaxAge: "168h",
|
||||
StopJobsExceedingWalltime: 0,
|
||||
ShortRunningJobsDuration: 5 * 60,
|
||||
FooterLinks: FooterLinksConfig{
|
||||
Imprint: "/imprint",
|
||||
Privacy: "/privacy",
|
||||
},
|
||||
}
|
||||
|
||||
func Init(mainConfig json.RawMessage) {
|
||||
|
||||
@@ -133,6 +133,20 @@ var configSchema = `
|
||||
},
|
||||
"required": ["subject-job-event", "subject-node-state"]
|
||||
},
|
||||
"footer-links": {
|
||||
"description": "Optional footer links for legal pages (imprint/privacy). Each value may be an internal path or an external URL.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"imprint": {
|
||||
"description": "Target URL/path for the footer imprint link.",
|
||||
"type": "string"
|
||||
},
|
||||
"privacy": {
|
||||
"description": "Target URL/path for the footer privacy link.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nodestate-retention": {
|
||||
"description": "Node state retention configuration for cleaning up old node_state rows.",
|
||||
"type": "object",
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
{{block "footer" .}}
|
||||
<footer class="site-footer bg-light">
|
||||
<ul class="footer-list">
|
||||
<li class="footer-list-item"><a class="link-secondary fs-5" href="/imprint" title="Imprint" rel="nofollow">Imprint</a></li>
|
||||
<li class="footer-list-item"><a class="link-secondary fs-5" href="/privacy" title="Privacy Policy" rel="nofollow">Privacy Policy</a></li>
|
||||
<li class="footer-list-item"><a class="link-secondary fs-5" href="{{ .FooterLinks.Imprint.URL }}" title="Imprint"{{ if .FooterLinks.Imprint.External }} target="_blank" rel="noopener noreferrer"{{ else }} rel="nofollow"{{ end }}>Imprint</a></li>
|
||||
<li class="footer-list-item"><a class="link-secondary fs-5" href="{{ .FooterLinks.Privacy.URL }}" title="Privacy Policy"{{ if .FooterLinks.Privacy.External }} target="_blank" rel="noopener noreferrer"{{ else }} rel="nofollow"{{ end }}>Privacy Policy</a></li>
|
||||
</ul>
|
||||
<ul class="build-list">
|
||||
<li class="build-list-item">Version {{ .Build.Version }}</li>
|
||||
|
||||
32
web/web.go
32
web/web.go
@@ -74,6 +74,32 @@ type PlotConfiguration struct {
|
||||
ColorScheme []string `json:"color-scheme"`
|
||||
}
|
||||
|
||||
const (
|
||||
defaultImprintLink = "/imprint"
|
||||
defaultPrivacyLink = "/privacy"
|
||||
)
|
||||
|
||||
// FooterLink is the render-time representation of a single footer legal link.
|
||||
type FooterLink struct {
|
||||
URL string // Resolved target: internal path or external URL.
|
||||
External bool // True if the target is an external URL (opened in a new tab).
|
||||
}
|
||||
|
||||
// FooterLinks holds the resolved legal links shown in the site footer.
|
||||
type FooterLinks struct {
|
||||
Imprint FooterLink
|
||||
Privacy FooterLink
|
||||
}
|
||||
|
||||
// resolveFooterLink falls back to def when v is empty and flags external URLs.
|
||||
func resolveFooterLink(v, def string) FooterLink {
|
||||
if v == "" {
|
||||
v = def
|
||||
}
|
||||
external := strings.HasPrefix(v, "http://") || strings.HasPrefix(v, "https://")
|
||||
return FooterLink{URL: v, External: external}
|
||||
}
|
||||
|
||||
var UIDefaults = WebConfig{
|
||||
JobList: JobListConfig{
|
||||
UsePaging: false,
|
||||
@@ -266,6 +292,7 @@ type Page struct {
|
||||
Config map[string]any // UI settings for the currently logged in user (e.g. line width, ...)
|
||||
Resampling *config.ResampleConfig // If not nil, defines resampling trigger and resolutions
|
||||
Redirect string // The originally requested URL, for intermediate login handling
|
||||
FooterLinks FooterLinks // Resolved legal links for the site footer
|
||||
}
|
||||
|
||||
func RenderTemplate(rw http.ResponseWriter, file string, page *Page) {
|
||||
@@ -289,6 +316,11 @@ func RenderTemplate(rw http.ResponseWriter, file string, page *Page) {
|
||||
}
|
||||
}
|
||||
|
||||
page.FooterLinks = FooterLinks{
|
||||
Imprint: resolveFooterLink(config.Keys.FooterLinks.Imprint, defaultImprintLink),
|
||||
Privacy: resolveFooterLink(config.Keys.FooterLinks.Privacy, defaultPrivacyLink),
|
||||
}
|
||||
|
||||
cclog.Debugf("Page config : %v\n", page.Config)
|
||||
if err := t.Execute(rw, page); err != nil {
|
||||
cclog.Errorf("Template error: %s", err.Error())
|
||||
|
||||
Reference in New Issue
Block a user