Files
web-petting/templates/admin/settings.html
T

347 lines
21 KiB
HTML
Raw Permalink Normal View History

2026-04-29 17:49:07 +03:00
{% extends "admin/layout.html" %}
{% let active_page = "settings" %}
{% block title %}{{ t.settings_title }}{% endblock %}
{% block content %}
2026-08-09 03:19:47 +01:00
<div class="page-head">
2026-08-09 01:32:10 +01:00
<div>
<h1>{{ t.settings_title }}</h1>
<p>{{ t.settings_intro }}</p>
</div>
2026-04-29 17:49:07 +03:00
</div>
{% if saved %}
2026-08-09 03:19:47 +01:00
<div class="notification is-success is-light admin-message">{{ t.settings_saved }}</div>
2026-04-29 17:49:07 +03:00
{% endif %}
2026-08-09 00:15:40 +01:00
{% if let Some(message) = error %}
2026-08-09 03:19:47 +01:00
<div class="notification is-danger is-light admin-message">{{ message }}</div>
2026-08-09 00:15:40 +01:00
{% endif %}
2026-04-29 17:49:07 +03:00
2026-08-09 03:19:47 +01:00
<form id="settingsForm" class="admin-form" method="post" action="/admin/settings/save">
<section class="admin-section">
<header class="admin-section-head">
<span class="admin-section-icon" aria-hidden="true">🌐</span>
2026-08-09 01:32:10 +01:00
<div>
<h2>{{ t.settings_section_general }}</h2>
<p>{{ t.settings_section_general_help }}</p>
</div>
</header>
2026-08-09 03:19:47 +01:00
<div class="admin-section-body">
<div class="admin-grid">
<div class="field admin-field admin-field-wide">
2026-08-09 01:32:10 +01:00
<label class="label" for="contactInfo">{{ t.settings_contact_info }}</label>
<div class="control">
<input id="contactInfo" class="input" type="text" name="contact_info" placeholder="+7 999 123-45-67 / info@example.com" value="{% for s in &settings %}{% if s.key == "contact_info" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field">
2026-08-09 01:32:10 +01:00
<label class="label" for="siteDomain">{{ t.settings_site_domain }}</label>
<div class="control">
<input id="siteDomain" class="input" type="url" name="site_domain" placeholder="https://example.com" value="{% for s in &settings %}{% if s.key == "site_domain" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field">
2026-08-09 01:32:10 +01:00
<label class="label" for="timezone">{{ t.settings_timezone }}</label>
<div class="control">
<input id="timezone" class="input" type="text" name="timezone" placeholder="Asia/Vladivostok" value="{% for s in &settings %}{% if s.key == "timezone" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field admin-field-wide">
2026-08-09 01:32:10 +01:00
<label class="label" for="pricingInfo">{{ t.settings_pricing_info }}</label>
<div class="control">
<textarea id="pricingInfo" class="textarea" name="pricing_info" rows="3" placeholder="от 600 рублей за визит">{% for s in &settings %}{% if s.key == "pricing_info" %}{{ s.value }}{% endif %}{% endfor %}</textarea>
</div>
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field admin-field-wide">
2026-08-09 01:32:10 +01:00
<label class="label" for="seoKeywordsInput">{{ t.settings_seo_keywords }}</label>
<div class="control">
<textarea id="seoKeywordsInput" class="textarea" name="seo_keywords" rows="3" placeholder="зооняня Хабаровск, присмотр за питомцем Хабаровск, догситтер Хабаровск">{% for s in &settings %}{% if s.key == "seo_keywords" %}{{ s.value }}{% endif %}{% endfor %}</textarea>
</div>
<div id="seoPreview" class="seo-preview" hidden></div>
</div>
2026-04-29 17:49:07 +03:00
</div>
</div>
2026-08-09 01:32:10 +01:00
</section>
2026-08-09 03:19:47 +01:00
<section class="admin-section">
<header class="admin-section-head">
<span class="admin-section-icon" aria-hidden="true">☁️</span>
2026-08-09 01:32:10 +01:00
<div>
2026-08-09 03:19:47 +01:00
<div class="admin-title-row">
2026-08-09 01:32:10 +01:00
<h2>{{ t.settings_section_storage }}</h2>
2026-08-09 03:19:47 +01:00
<span class="status-pill{% if r2_enabled_checked %} is-enabled{% endif %}">
2026-08-09 01:32:10 +01:00
{% if r2_enabled_checked %}{{ t.settings_status_enabled }}{% else %}{{ t.settings_status_disabled }}{% endif %}
</span>
</div>
<p>{{ t.settings_r2_help }}</p>
</div>
</header>
2026-08-09 03:19:47 +01:00
<div class="admin-section-body">
<label class="admin-toggle" for="r2Enabled">
2026-08-09 01:32:10 +01:00
<input id="r2Enabled" type="checkbox" name="r2_enabled" value="true"{% if r2_enabled_checked %} checked{% endif %}>
2026-08-09 03:19:47 +01:00
<span class="admin-toggle-track" aria-hidden="true"><span></span></span>
2026-08-09 01:32:10 +01:00
<span>
<strong>{{ t.settings_r2_enabled }}</strong>
<small>{{ t.settings_r2_help }}</small>
</span>
</label>
2026-08-09 03:19:47 +01:00
<div class="admin-note admin-note-info">{{ t.settings_r2_migration_help }}</div>
2026-08-09 01:32:10 +01:00
2026-08-09 03:19:47 +01:00
<div class="admin-grid">
<div class="field admin-field">
2026-08-09 01:32:10 +01:00
<label class="label" for="r2AccountId">{{ t.settings_r2_account_id }}</label>
<div class="control">
<input id="r2AccountId" class="input is-family-monospace" type="text" name="r2_account_id" maxlength="32" autocomplete="off" spellcheck="false" placeholder="0123456789abcdef0123456789abcdef" value="{% for s in &settings %}{% if s.key == "r2_account_id" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field">
2026-08-09 01:32:10 +01:00
<label class="label" for="r2Bucket">{{ t.settings_r2_bucket }}</label>
<div class="control">
<input id="r2Bucket" class="input is-family-monospace" type="text" name="r2_bucket" autocomplete="off" spellcheck="false" placeholder="pet-media" value="{% for s in &settings %}{% if s.key == "r2_bucket" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field">
2026-08-09 01:32:10 +01:00
<label class="label" for="r2AccessKey">{{ t.settings_r2_access_key_id }}</label>
<div class="control">
<input id="r2AccessKey" class="input is-family-monospace" type="text" name="r2_access_key_id" autocomplete="off" spellcheck="false" value="{% for s in &settings %}{% if s.key == "r2_access_key_id" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field">
2026-08-09 01:32:10 +01:00
<label class="label" for="r2Secret">{{ t.settings_r2_secret_access_key }}</label>
<div class="control secret-control">
<input id="r2Secret" class="input is-family-monospace" type="password" name="r2_secret_access_key" autocomplete="new-password" placeholder="••••••••••••••••">
</div>
2026-08-09 03:19:47 +01:00
<p class="field-help">{% if r2_secret_configured %}{{ t.settings_secret_saved }}{% else %}{{ t.settings_secret_not_set }}{% endif %}</p>
2026-08-09 01:32:10 +01:00
{% if r2_secret_configured %}
2026-08-09 03:19:47 +01:00
<label class="admin-check admin-check-danger"><input type="checkbox" name="clear_r2_secret_access_key" value="true"><span class="admin-check-box"></span><span>{{ t.settings_secret_clear }}</span></label>
2026-08-09 01:32:10 +01:00
{% endif %}
</div>
2026-05-11 11:34:11 +01:00
</div>
</div>
2026-08-09 01:32:10 +01:00
</section>
2026-08-09 03:19:47 +01:00
<section class="admin-section">
<header class="admin-section-head">
<span class="admin-section-icon" aria-hidden="true">🔔</span>
2026-08-09 01:32:10 +01:00
<div>
2026-08-09 03:19:47 +01:00
<div class="admin-title-row">
2026-08-09 01:32:10 +01:00
<h2>{{ t.settings_section_notifications }}</h2>
2026-08-09 03:19:47 +01:00
<span class="status-pill{% if client_notifications_checked %} is-enabled{% endif %}">
2026-08-09 01:32:10 +01:00
{% if client_notifications_checked %}{{ t.settings_status_enabled }}{% else %}{{ t.settings_status_disabled }}{% endif %}
</span>
</div>
<p>{{ t.settings_section_notifications_help }}</p>
</div>
2026-08-09 01:32:10 +01:00
</header>
2026-08-09 03:19:47 +01:00
<div class="admin-section-body">
<label class="admin-toggle" for="clientNotifications">
2026-08-09 01:32:10 +01:00
<input id="clientNotifications" type="checkbox" name="client_notifications_enabled" value="true"{% if client_notifications_checked %} checked{% endif %}>
2026-08-09 03:19:47 +01:00
<span class="admin-toggle-track" aria-hidden="true"><span></span></span>
2026-08-09 01:32:10 +01:00
<span>
<strong>{{ t.settings_client_notifications_enabled }}</strong>
<small>{{ t.settings_client_notifications_help }}</small>
</span>
</label>
2026-08-09 03:19:47 +01:00
<div class="admin-subsection">
2026-08-09 01:32:10 +01:00
<h3>Web Push / VAPID</h3>
2026-08-09 03:19:47 +01:00
<div class="admin-note admin-note-warning">
<p>{{ t.settings_vapid_warning }}</p>
2026-08-09 01:32:10 +01:00
<p>{{ t.settings_vapid_generate }} <code>cargo run --bin generate_vapid</code></p>
</div>
2026-08-09 03:19:47 +01:00
<div class="admin-grid">
<div class="field admin-field admin-field-wide">
2026-08-09 01:32:10 +01:00
<label class="label" for="vapidPublic">{{ t.settings_vapid_public_key }}</label>
<div class="control">
<input id="vapidPublic" class="input is-family-monospace" type="text" name="vapid_public_key" autocomplete="off" spellcheck="false" value="{% for s in &settings %}{% if s.key == "vapid_public_key" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field admin-field-wide">
2026-08-09 01:32:10 +01:00
<label class="label" for="vapidPrivate">{{ t.settings_vapid_private_key }}</label>
<div class="control secret-control">
<input id="vapidPrivate" class="input is-family-monospace" type="password" name="vapid_private_key" autocomplete="new-password" placeholder="••••••••••••••••">
</div>
2026-08-09 03:19:47 +01:00
<p class="field-help">{% if vapid_private_key_configured %}{{ t.settings_secret_saved }}{% else %}{{ t.settings_secret_not_set }}{% endif %}</p>
2026-08-09 01:32:10 +01:00
{% if vapid_private_key_configured %}
2026-08-09 03:19:47 +01:00
<label class="admin-check admin-check-danger"><input type="checkbox" name="clear_vapid_private_key" value="true"><span class="admin-check-box"></span><span>{{ t.settings_secret_clear }}</span></label>
2026-08-09 01:32:10 +01:00
{% endif %}
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field admin-field-wide">
2026-08-09 01:32:10 +01:00
<label class="label" for="vapidSubject">{{ t.settings_vapid_subject }}</label>
<div class="control">
<input id="vapidSubject" class="input" type="text" name="vapid_subject" placeholder="mailto:admin@example.com" value="{% for s in &settings %}{% if s.key == "vapid_subject" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
</div>
2026-08-09 01:32:10 +01:00
2026-08-09 03:19:47 +01:00
<details class="admin-details">
2026-08-09 01:32:10 +01:00
<summary>{{ t.settings_push_subscribers }} <span>{{ push_subscribers.len() }}</span></summary>
2026-08-09 03:19:47 +01:00
<div class="admin-details-body">
{% if push_subscribers.is_empty() %}
2026-08-09 03:19:47 +01:00
<p class="field-help">{{ t.settings_push_no_subscribers }}</p>
{% else %}
2026-08-09 03:19:47 +01:00
<div class="table-wrap">
2026-08-09 01:32:10 +01:00
<table class="table is-fullwidth is-striped is-narrow">
<thead><tr>
<th>{{ t.settings_push_client }}</th>
<th>{{ t.settings_push_devices }}</th>
<th>{{ t.settings_push_language }}</th>
<th>{{ t.settings_push_updated }}</th>
</tr></thead>
<tbody>
{% for subscriber in &push_subscribers %}
<tr>
<td>{{ subscriber.client_name }}</td>
<td>{{ subscriber.device_count }}</td>
<td>{{ subscriber.languages }}</td>
<td class="is-nowrap">{{ subscriber.last_updated }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</details>
2026-08-09 01:32:10 +01:00
</div>
2026-05-19 00:16:22 +03:00
2026-08-09 03:19:47 +01:00
<div class="admin-subsection">
2026-08-09 01:32:10 +01:00
<h3>Telegram</h3>
2026-08-09 03:19:47 +01:00
<div class="field admin-field admin-field-wide">
2026-08-09 01:32:10 +01:00
<label class="label" for="telegramToken">{{ t.settings_telegram_bot_token }}</label>
<div class="control secret-control">
<input id="telegramToken" class="input is-family-monospace" type="password" name="telegram_bot_token" autocomplete="new-password" placeholder="••••••••••••••••">
2026-05-19 00:57:05 +03:00
</div>
2026-08-09 03:19:47 +01:00
<p class="field-help">{% if telegram_bot_token_configured %}{{ t.settings_secret_saved }}{% else %}{{ t.settings_secret_not_set }}{% endif %}</p>
2026-08-09 01:32:10 +01:00
{% if telegram_bot_token_configured %}
2026-08-09 03:19:47 +01:00
<label class="admin-check admin-check-danger"><input type="checkbox" name="clear_telegram_bot_token" value="true"><span class="admin-check-box"></span><span>{{ t.settings_secret_clear }}</span></label>
2026-08-09 01:32:10 +01:00
{% endif %}
2026-05-19 00:57:05 +03:00
</div>
</div>
2026-08-09 01:32:10 +01:00
</div>
</section>
2026-05-19 00:57:05 +03:00
2026-08-09 03:19:47 +01:00
<section class="admin-section">
<header class="admin-section-head">
<span class="admin-section-icon" aria-hidden="true">🛡️</span>
2026-08-09 01:32:10 +01:00
<div>
<h2>{{ t.settings_section_captcha }}</h2>
<p>{{ t.settings_section_captcha_help }}</p>
</div>
</header>
2026-08-09 03:19:47 +01:00
<div class="admin-section-body">
<div class="admin-grid">
<div class="field admin-field">
2026-08-09 01:32:10 +01:00
<label class="label" for="turnstileSiteKey">{{ t.settings_turnstile_site_key }}</label>
<div class="control">
<input id="turnstileSiteKey" class="input is-family-monospace" type="text" name="turnstile_site_key" autocomplete="off" spellcheck="false" value="{% for s in &settings %}{% if s.key == "turnstile_site_key" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field">
2026-08-09 01:32:10 +01:00
<label class="label" for="turnstileSecret">{{ t.settings_turnstile_secret_key }}</label>
<div class="control secret-control">
<input id="turnstileSecret" class="input is-family-monospace" type="password" name="turnstile_secret_key" autocomplete="new-password" placeholder="••••••••••••••••">
</div>
2026-08-09 03:19:47 +01:00
<p class="field-help">{% if turnstile_secret_configured %}{{ t.settings_secret_saved }}{% else %}{{ t.settings_secret_not_set }}{% endif %}</p>
2026-08-09 01:32:10 +01:00
{% if turnstile_secret_configured %}
2026-08-09 03:19:47 +01:00
<label class="admin-check admin-check-danger"><input type="checkbox" name="clear_turnstile_secret_key" value="true"><span class="admin-check-box"></span><span>{{ t.settings_secret_clear }}</span></label>
2026-08-09 01:32:10 +01:00
{% endif %}
</div>
</div>
</div>
</section>
2026-08-09 03:19:47 +01:00
<section class="admin-section">
<header class="admin-section-head">
<span class="admin-section-icon" aria-hidden="true">🔐</span>
2026-08-09 01:32:10 +01:00
<div>
<h2>{{ t.settings_section_oidc }}</h2>
<p>{{ t.settings_section_oidc_help }}</p>
</div>
</header>
2026-08-09 03:19:47 +01:00
<div class="admin-section-body">
<div class="admin-toggle-list">
<label class="admin-toggle" for="passwordAuth">
2026-08-09 01:32:10 +01:00
<input id="passwordAuth" type="checkbox" name="auth_password_enabled" value="true"{% if auth_password_checked %} checked{% endif %}>
2026-08-09 03:19:47 +01:00
<span class="admin-toggle-track" aria-hidden="true"><span></span></span>
2026-08-09 01:32:10 +01:00
<span><strong>{{ t.settings_auth_password_enabled }}</strong></span>
</label>
2026-08-09 03:19:47 +01:00
<label class="admin-toggle" for="ssoAuth">
2026-08-09 01:32:10 +01:00
<input id="ssoAuth" type="checkbox" name="auth_sso_enabled" value="true"{% if auth_sso_checked %} checked{% endif %}>
2026-08-09 03:19:47 +01:00
<span class="admin-toggle-track" aria-hidden="true"><span></span></span>
2026-08-09 01:32:10 +01:00
<span><strong>{{ t.settings_auth_sso_enabled }}</strong></span>
</label>
</div>
2026-08-09 03:19:47 +01:00
<div class="admin-grid">
<div class="field admin-field admin-field-wide">
2026-08-09 01:32:10 +01:00
<label class="label" for="oidcIssuer">{{ t.settings_oidc_issuer_url }}</label>
<div class="control">
<input id="oidcIssuer" class="input" type="url" name="oidc_issuer_url" placeholder="https://keycloak.example.com/realms/myrealm" value="{% for s in &settings %}{% if s.key == "oidc_issuer_url" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field">
2026-08-09 01:32:10 +01:00
<label class="label" for="oidcClientId">{{ t.settings_oidc_client_id }}</label>
<div class="control">
<input id="oidcClientId" class="input is-family-monospace" type="text" name="oidc_client_id" autocomplete="off" spellcheck="false" value="{% for s in &settings %}{% if s.key == "oidc_client_id" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field">
2026-08-09 01:32:10 +01:00
<label class="label" for="oidcClientSecret">{{ t.settings_oidc_client_secret }}</label>
<div class="control secret-control">
<input id="oidcClientSecret" class="input is-family-monospace" type="password" name="oidc_client_secret" autocomplete="new-password" placeholder="••••••••••••••••">
</div>
2026-08-09 03:19:47 +01:00
<p class="field-help">{% if oidc_client_secret_configured %}{{ t.settings_secret_saved }}{% else %}{{ t.settings_secret_not_set }}{% endif %}</p>
2026-08-09 01:32:10 +01:00
{% if oidc_client_secret_configured %}
2026-08-09 03:19:47 +01:00
<label class="admin-check admin-check-danger"><input type="checkbox" name="clear_oidc_client_secret" value="true"><span class="admin-check-box"></span><span>{{ t.settings_secret_clear }}</span></label>
2026-08-09 01:32:10 +01:00
{% endif %}
</div>
2026-08-09 03:19:47 +01:00
<div class="field admin-field admin-field-wide">
2026-08-09 01:32:10 +01:00
<label class="label" for="oidcGroups">{{ t.settings_oidc_allowed_groups }}</label>
<div class="control">
<input id="oidcGroups" class="input" type="text" name="oidc_allowed_groups" placeholder="admins, web-petting" value="{% for s in &settings %}{% if s.key == "oidc_allowed_groups" %}{{ s.value }}{% endif %}{% endfor %}">
</div>
</div>
</div>
</div>
</section>
2026-08-09 03:19:47 +01:00
<div class="sticky-actions">
2026-08-09 01:32:10 +01:00
<button type="submit" class="button is-primary">{{ t.settings_save }}</button>
</div>
</form>
<script>
(function() {
2026-08-09 01:32:10 +01:00
var colors = [
'rgba(124,108,255,.18)', 'rgba(255,82,135,.15)',
'rgba(255,140,38,.18)', 'rgba(0,180,150,.15)',
'rgba(77,166,255,.18)', 'rgba(255,179,64,.18)',
'rgba(176,108,255,.16)', 'rgba(34,180,130,.16)'
];
2026-08-09 01:32:10 +01:00
var input = document.getElementById('seoKeywordsInput');
var preview = document.getElementById('seoPreview');
2026-08-09 01:32:10 +01:00
function renderSeoPreview() {
var words = input.value.split(',').map(function(value) { return value.trim(); }).filter(Boolean);
preview.replaceChildren();
preview.hidden = words.length === 0;
words.forEach(function(word, index) {
var tag = document.createElement('span');
tag.textContent = word;
tag.style.background = colors[index % colors.length];
tag.style.borderRadius = '4px';
tag.style.padding = '2px 6px';
tag.style.margin = '2px';
preview.appendChild(tag);
});
}
2026-08-09 01:32:10 +01:00
input.addEventListener('input', renderSeoPreview);
renderSeoPreview();
})();
</script>
2026-04-29 17:49:07 +03:00
{% endblock %}