Files
jessikitty f7c20de5ac Add owner-only logging and Brickset API call tracking
LIM configured no logging at all, so the instruction fetcher had no trail to follow. Adds a rotating file handler on the root logger plus two owner-only admin pages.

- Admin > Logs: filter by level, source and text; tracebacks kept with their error; auto-refresh, raw download, rotated-file browsing and rotate-now

- Admin > Logs > Brickset API calls: every outbound call recorded with method, params, caller, duration, result count and error, plus daily quota accounting (only getSets counts) and a comparison against getKeyUsageStats

- New owner concept (services/ownership.py): a single account, distinct from is_admin, since these pages expose other admins activity. Defaults to the first admin; flask owner show/set to change

- Services now log through module loggers; retention setting and pruning for call records

- New CLI: flask owner show/set, flask logs path/prune

- New table brickset_api_calls, created automatically at boot; LOG_* env vars, /data/logs in Docker
2026-09-27 10:42:49 +10:00

188 lines
8.9 KiB
HTML

{% extends "base.html" %}
{% block title %}Site Settings - Admin{% endblock %}
{% block content %}
<div class="row mb-4">
<div class="col">
<h1><i class="bi bi-sliders"></i> Site Settings</h1>
</div>
<div class="col-auto">
<a href="{{ url_for('instruction_fetch.index') }}" class="btn btn-outline-warning me-2">
<i class="bi bi-cloud-arrow-down"></i> Instruction Fetcher
</a>
{% if current_user_is_owner %}
<a href="{{ url_for('logs.index') }}" class="btn btn-outline-dark me-2">
<i class="bi bi-journal-text"></i> Logs
</a>
{% endif %}
<a href="{{ url_for('backup.index') }}" class="btn btn-outline-danger me-2">
<i class="bi bi-archive"></i> Backup &amp; Restore
</a>
<a href="{{ url_for('admin.dashboard') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Back
</a>
</div>
</div>
<div class="row">
<div class="col-lg-7">
<form method="POST">
<input type="hidden" name="action" value="save">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-person-plus"></i> Accounts</h5>
</div>
<div class="card-body">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch"
id="allow_registration" name="allow_registration"
{% if settings.allow_registration in ['true', '1', 'yes', 'on'] %}checked{% endif %}>
<label class="form-check-label" for="allow_registration">
<strong>Allow self-registration</strong>
</label>
</div>
<div class="form-text">
When off, the Register page is hidden and blocked, and you create
accounts yourself from
<a href="{{ url_for('admin.users') }}">User Management</a>.
The very first account on a fresh install is always allowed,
so this can't lock you out.
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-eye"></i> Set visibility</h5>
</div>
<div class="card-body">
<label class="form-label"><strong>Default for newly added sets</strong></label>
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="default_visibility"
id="vis_private" value="private"
{% if settings.default_visibility == 'private' %}checked{% endif %}>
<label class="form-check-label" for="vis_private">
<i class="bi bi-lock-fill"></i> <strong>Private</strong>
&mdash; only the owner and administrators.
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="default_visibility"
id="vis_shared" value="shared"
{% if settings.default_visibility == 'shared' %}checked{% endif %}>
<label class="form-check-label" for="vis_shared">
<i class="bi bi-people-fill"></i> <strong>Shared</strong>
&mdash; the owner picks which users can see each set.
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="radio" name="default_visibility"
id="vis_public" value="public"
{% if settings.default_visibility == 'public' %}checked{% endif %}>
<label class="form-check-label" for="vis_public">
<i class="bi bi-globe"></i> <strong>Public</strong>
&mdash; every logged-in user.
</label>
</div>
<div class="alert alert-light border small mb-0">
Visibility only controls who can <em>see</em> a set and its files.
Only the owner and administrators can ever change one.
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-signpost-split"></i> Navigation</h5>
</div>
<div class="card-body">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch"
id="home_redirect_dashboard" name="home_redirect_dashboard"
{% if settings.home_redirect_dashboard in ['true', '1', 'yes', 'on'] %}checked{% endif %}>
<label class="form-check-label" for="home_redirect_dashboard">
<strong>Send logged-in users straight to their dashboard</strong>
</label>
</div>
<div class="form-text">
Skips the welcome page for anyone already signed in. Logged-out
visitors still see it.
</div>
</div>
</div>
<button type="submit" class="btn btn-danger">
<i class="bi bi-check-lg"></i> Save settings
</button>
</form>
</div>
<div class="col-lg-5">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">System Information</h5>
</div>
<div class="card-body">
<table class="table table-sm mb-0">
<tr><td>Total Users:</td><td><strong>{{ stats.total_users }}</strong></td></tr>
<tr><td>Total Sets:</td><td><strong>{{ stats.total_sets }}</strong></td></tr>
<tr><td>Total Instructions:</td><td><strong>{{ stats.total_instructions }}</strong></td></tr>
<tr><td>Storage Used:</td>
<td><strong>{{ (stats.total_storage / 1024 / 1024) | round(2) }} MB</strong></td></tr>
</table>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-collection"></i> Existing sets</h5>
</div>
<div class="card-body">
<table class="table table-sm">
<tr>
<td><i class="bi bi-lock-fill"></i> Private</td>
<td class="text-end"><strong>{{ visibility_counts.get('private', 0) }}</strong></td>
</tr>
<tr>
<td><i class="bi bi-people-fill"></i> Shared</td>
<td class="text-end"><strong>{{ visibility_counts.get('shared', 0) }}</strong></td>
</tr>
<tr>
<td><i class="bi bi-globe"></i> Public</td>
<td class="text-end"><strong>{{ visibility_counts.get('public', 0) }}</strong></td>
</tr>
</table>
<p class="small text-muted">
Sets that existed before visibility was added were made
<strong>public</strong>, so nothing disappeared from anyone's library.
Change them all at once here, then adjust individual sets from their
own pages.
</p>
<form method="POST"
onsubmit="return confirm('Change the visibility of EVERY set? This cannot be undone.');">
<input type="hidden" name="action" value="apply_visibility">
<div class="input-group">
<select name="bulk_visibility" class="form-select">
{% for v in visibilities %}
<option value="{{ v }}">{{ v|capitalize }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-outline-danger">
Apply to all
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<p class="text-center text-muted small">Created by: Jess Rogerson (yelling commands at Claude.AI)</p>
{% endblock %}