Files
devplacepy/devplacepy/templates/admin_settings.html
T
2026-07-09 02:52:54 +02:00

133 lines
8.4 KiB
HTML

{% extends "admin_base.html" %}
{% block admin_content %}
<div class="admin-toolbar">
<h2>Settings</h2>
</div>
<p class="hint-text admin-settings-intro">Background service parameters (news fetching, intervals, AI grading) are configured per service on the <a href="/admin/services">Services</a> page.</p>
<form method="POST" action="/admin/settings" class="admin-settings-form">
<h3 class="admin-settings-group-title">General</h3>
<div class="admin-field">
<label for="site_name">Site Name</label>
<input type="text" id="site_name" name="site_name" value="{{ settings.get('site_name', 'DevPlace') }}" maxlength="200">
</div>
<div class="admin-field">
<label for="site_description">Site Description</label>
<textarea id="site_description" name="site_description" rows="3" maxlength="500">{{ settings.get('site_description', 'The Developer Social Network') }}</textarea>
</div>
<div class="admin-field">
<label for="site_tagline">Tagline</label>
<input type="text" id="site_tagline" name="site_tagline" value="{{ settings.get('site_tagline', 'Track industry shifts. Discover bold releases. Share what you are building in an open, uncensored environment.') }}" maxlength="500">
</div>
<div class="admin-field">
<label for="site_url">Public URL</label>
<input type="text" id="site_url" name="site_url" value="{{ settings.get('site_url', '') }}" maxlength="300" placeholder="https://devplace.example.com">
<small>Public origin used for absolute links (SEO, container ingress /p/&lt;slug&gt;). Empty = derive from the request or the DEVPLACE_SITE_URL env var.</small>
</div>
<hr class="admin-settings-divider">
<h3 class="admin-settings-group-title">Uploads & Attachments</h3>
<div class="admin-field">
<label for="max_upload_size_mb">Max Upload Size (MB)</label>
<input type="number" id="max_upload_size_mb" name="max_upload_size_mb" value="{{ settings.get('max_upload_size_mb', '10') }}" min="1" max="500" class="admin-settings-num">
<small class="hint-text">Maximum file size per upload in megabytes.</small>
</div>
<div class="admin-field">
<label for="allowed_file_types">Allowed File Types</label>
<input type="text" id="allowed_file_types" name="allowed_file_types" value="{{ settings.get('allowed_file_types', '') }}" maxlength="1000" placeholder="e.g. .png,.jpg,.mp4,.webm (leave empty for default)">
<small class="hint-text">Comma-separated list of allowed extensions. When set, this is the authoritative allowlist; any extension listed here is enabled and its type auto-detected. Leave empty to use the built-in defaults, which include common image and video formats (mp4, webm, ogv, mov, m4v).</small>
</div>
<div class="admin-field">
<label for="max_attachments_per_resource">Max Attachments Per Resource</label>
<input type="number" id="max_attachments_per_resource" name="max_attachments_per_resource" value="{{ settings.get('max_attachments_per_resource', '10') }}" min="1" max="50" class="admin-settings-num">
<small class="hint-text">Maximum number of files that can be attached to a single post, comment, etc.</small>
</div>
<hr class="admin-settings-divider">
<h3 class="admin-settings-group-title">Operational</h3>
<div class="admin-field">
<label for="docs_search_mode">Docs Search</label>
<select id="docs_search_mode" name="docs_search_mode" class="admin-settings-select-wide">
<option value="agent" {% if settings.get('docs_search_mode', 'agent') == 'agent' %}selected{% endif %}>AI agent (conversational)</option>
<option value="bm25" {% if settings.get('docs_search_mode', 'agent') == 'bm25' %}selected{% endif %}>Keyword (BM25)</option>
</select>
<small class="hint-text">AI agent renders the in-page Devii chat on the docs search page. Keyword uses the classic BM25 results list. Users who exceed their daily AI limit (or guests when Devii is disabled) fall back to keyword search automatically.</small>
</div>
<div class="admin-field">
<label for="registration_open">Registration</label>
<select id="registration_open" name="registration_open" class="admin-settings-select">
<option value="1" {% if settings.get('registration_open', '1') == '1' %}selected{% endif %}>Open</option>
<option value="0" {% if settings.get('registration_open', '1') != '1' %}selected{% endif %}>Closed</option>
</select>
<small class="hint-text">When closed, new account sign-ups are rejected.</small>
</div>
<div class="admin-field">
<label for="maintenance_mode">Maintenance Mode</label>
<select id="maintenance_mode" name="maintenance_mode" class="admin-settings-select">
<option value="0" {% if settings.get('maintenance_mode', '0') != '1' %}selected{% endif %}>Disabled</option>
<option value="1" {% if settings.get('maintenance_mode', '0') == '1' %}selected{% endif %}>Enabled</option>
</select>
<small class="hint-text">When enabled, non-admin visitors see the maintenance page. Admins retain full access.</small>
</div>
<div class="admin-field">
<label for="maintenance_message">Maintenance Message</label>
<input type="text" id="maintenance_message" name="maintenance_message" value="{{ settings.get('maintenance_message', 'DevPlace is undergoing scheduled maintenance. Please check back shortly.') }}" maxlength="300">
<small class="hint-text">Shown to visitors while maintenance mode is enabled.</small>
</div>
<div class="admin-field">
<label for="rate_limit_per_minute">Rate Limit (requests)</label>
<input type="number" id="rate_limit_per_minute" name="rate_limit_per_minute" value="{{ settings.get('rate_limit_per_minute', '60') }}" min="1" class="admin-settings-num">
<small class="hint-text">Maximum mutating requests allowed per IP within the window below.</small>
</div>
<div class="admin-field">
<label for="rate_limit_window_seconds">Rate Limit Window (seconds)</label>
<input type="number" id="rate_limit_window_seconds" name="rate_limit_window_seconds" value="{{ settings.get('rate_limit_window_seconds', '60') }}" min="1" class="admin-settings-num">
<small class="hint-text">Rolling window over which the request limit is counted.</small>
</div>
<div class="admin-field">
<label for="session_max_age_days">Session Length (days)</label>
<input type="number" id="session_max_age_days" name="session_max_age_days" value="{{ settings.get('session_max_age_days', '7') }}" min="1" class="admin-settings-num">
<small class="hint-text">Cookie lifetime for a standard sign-in.</small>
</div>
<div class="admin-field">
<label for="session_remember_days">Remember-Me Length (days)</label>
<input type="number" id="session_remember_days" name="session_remember_days" value="{{ settings.get('session_remember_days', '30') }}" min="1" class="admin-settings-num">
<small class="hint-text">Cookie lifetime when "remember me" is checked at login.</small>
</div>
<div class="admin-field">
<label for="outbound_proxy_url">Outbound Proxy URL</label>
<input type="text" id="outbound_proxy_url" name="outbound_proxy_url" value="{{ settings.get('outbound_proxy_url', '') }}" maxlength="500" placeholder="http://user:pass@host:port" class="admin-settings-select-wide">
<small class="hint-text">Routes every outbound stealth-client request (Devii fetch, DeepSearch, news, attachments, and more) through this proxy. Leave empty to connect directly. Needed only when a destination blocks this server's own IP/network (not its browser fingerprint) - most sites never need this.</small>
</div>
<h3 class="admin-settings-group-title admin-settings-group-title-spaced">Custom Code</h3>
<div class="admin-field">
<label for="extra_head">Extra Head</label>
<textarea id="extra_head" name="extra_head" rows="10" spellcheck="false" class="admin-settings-code">{{ settings.get('extra_head', '') }}</textarea>
<small class="hint-text">Raw HTML injected into the page &lt;head&gt; on every page. Put anything here: a &lt;style&gt; block, &lt;script&gt;, &lt;link&gt;, meta tags, or an analytics snippet. Applies site-wide. Leave empty to remove.</small>
</div>
<button type="submit" class="btn btn-primary"><span class="icon">&#x1F4BE;</span>Save Settings</button>
</form>
{% endblock %}