Files
FabledSteward/steward/templates/settings/general.html
T
bvandeusenandClaude Opus 5.5 24700ee608
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 56s
CI / unit (push) Successful in 1m24s
CI / publish (push) Successful in 1m9s
feat(settings): Settings brought into line with the rest of Steward (milestone 493)
The last page of #5214's walk. Settings still described Steward as it was
before monitors were unified and SNMP was rebuilt, drew its tabs below the
breadcrumb, and showed the report schedule in UTC.

- Tabs (#5377): settings/_tabs.html is a settings_tabs() macro called in
  each page's section_tabs block, above the trail, as Ansible's are. The
  breadcrumb guard also fails any page that {% include %}s a tab strip.
- Report time (#5378): reports.schedule_tz keeps the zone of whoever saved
  the schedule; check_and_send compares in it (report_due), and the form shows
  the day and hour on the reader's clock (schedule_in) under their zone's
  name. "Last sent" uses |when.
- Wording (#5379): the report contents list what the report holds (monitors,
  not ping-enabled hosts); "Ping latency" is "Response time" and says what it
  grades; plugin detail says enabling loads now and disabling needs a
  restart; the monitor interval says it applies on the next check (the
  scheduler reads it every pass); the dead list-config branch is gone.
- Traefik (#5380): the 5xx and 4xx cutoffs are settings
  (traefik.threshold.error_5xx_pct 0.1, error_4xx_pct 1.0) in a "Traefik
  routers" group on Thresholds while the plugin runs, read by router_state
  through error_cutoffs(). The Traefik page has a Thresholds button for
  admins, to /settings/thresholds/#traefik.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:28:43 -04:00

95 lines
5.2 KiB
HTML

{# steward/templates/settings/general.html #}
{% extends "base.html" %}
{% from "_macros.html" import crumbs %}
{% block title %}Settings — General — Steward{% endblock %}
{% from "settings/_tabs.html" import settings_tabs %}
{% block section_tabs %}{{ settings_tabs("general") }}{% endblock %}
{% block breadcrumb %}{{ crumbs([("Settings", "/settings/"), ("General", "")]) }}{% endblock %}
{% block content %}
<form method="post" action="/settings/general/">{{ csrf_field() }}
<div class="card" style="max-width:640px;">
<h2 class="section-title" style="margin-bottom:1.25rem;">General</h2>
<div class="form-group">
<label>Public base URL</label>
<input type="text" name="general.public_base_url"
value="{{ settings['general.public_base_url'] }}"
placeholder="https://monitor.example.com">
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">
External URL where this Steward instance is reachable from the outside.
Leave blank to use whatever hostname you're currently browsing via.
Used by install scripts, share links, and alert notifications.
</div>
</div>
<div class="form-group">
<label>Trusted reverse proxies</label>
<input type="number" name="general.trusted_proxy_hops" min="0" max="5"
value="{{ settings['general.trusted_proxy_hops'] }}" style="max-width:120px;">
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">
How many proxies sit in front of Steward (e.g. 1 for Traefik, 2 for Cloudflare in front of Traefik).
Steward believes their X-Forwarded-For and X-Forwarded-Proto headers up to that many hops: it marks
the login cookie Secure and sends HSTS only when they report HTTPS, and throttles logins by the address
they report. 0 trusts no proxy.
</div>
</div>
<div class="form-group">
<label>Session lifetime <span style="color:var(--text-muted);font-size:0.8rem;">(hours)</span></label>
<input type="number" name="session.lifetime_hours" min="1" max="720"
value="{{ settings['session.lifetime_hours'] }}" style="max-width:120px;">
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">A login lasts this long; then Steward asks for the password again.</div>
</div>
<div class="form-group">
<label>Failed logins before a pause</label>
<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap;">
<input type="number" name="login.max_failures" min="1" max="100"
value="{{ settings['login.max_failures'] }}" style="max-width:90px;">
<span style="font-size:0.85rem;color:var(--text-muted);">failures from one address pause its logins for</span>
<input type="number" name="login.lockout_minutes" min="1" max="1440"
value="{{ settings['login.lockout_minutes'] }}" style="max-width:90px;">
<span style="font-size:0.85rem;color:var(--text-muted);">minutes</span>
</div>
</div>
<div class="form-group">
<label>Data retention <span style="color:var(--text-muted);font-size:0.8rem;">(days)</span></label>
<input type="number" name="data.retention_days" min="1"
value="{{ settings['data.retention_days'] }}" style="max-width:120px;">
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">Monitor results, Ansible runs and their logs, alert history, and Traefik and UniFi samples older than this are pruned by the hourly cleanup. Host metrics, Docker data and container logs have their own windows under Settings → Thresholds.</div>
</div>
<div class="form-group">
<label>Audit log retention <span style="color:var(--text-muted);font-size:0.8rem;">(days)</span></label>
<input type="number" name="audit.retention_days" min="1"
value="{{ settings['audit.retention_days'] }}" style="max-width:120px;">
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">Who changed what. Kept longer than operational data; entries older than this are pruned by the hourly cleanup.</div>
</div>
<div class="form-group">
<label>Monitor poll interval <span style="color:var(--text-muted);font-size:0.8rem;">(seconds)</span></label>
<input type="number" name="monitors.poll_interval_seconds" min="10"
value="{{ settings['monitors.poll_interval_seconds'] }}" style="max-width:120px;">
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">How often each monitor is checked, unless it sets its own. Takes effect on the next check.</div>
</div>
<div class="form-group">
<label>Log level</label>
<select name="log.level" style="max-width:160px;">
{% for level in log_levels %}
<option value="{{ level }}" {% if settings['log.level'] == level %}selected{% endif %}>{{ level | capitalize }}</option>
{% endfor %}
</select>
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">What reaches the container log. Applies when saved. Warning hides the line logged for each request.</div>
</div>
</div>
<div style="margin-top:1rem;display:flex;align-items:center;gap:1rem;">
<button type="submit" class="btn">Save</button>
<span style="font-size:0.82rem;color:var(--text-muted);">Changes to session lifetime take effect immediately.</span>
</div>
</form>
{% endblock %}