This commit is contained in:
Mateusz Gruszczyński committed 2026-11-08 08:43:14 +01:00
1 parent 19dfc51360
commit b6e8ddb9c7
31 files changed
+1080 -32

No files matched your search

+40 -5
View File
@@ -203,6 +203,7 @@
['users', T('Skrzynki', 'Mailboxes')],
['backends', T('Backendy', 'Backends')],
['activesync', 'ActiveSync'],
['autodiscover', 'Autodiscover'],
['policies', T('Polityki', 'Policies')],
['security', T('Bezpieczeństwo', 'Security')],
['logs', T('Logi i audyt', 'Logs & audit')],
@@ -645,7 +646,7 @@
['carddav.include_regex', 'Include regex', 'text'], ['carddav.exclude_regex', 'Exclude regex', 'text'], ['carddav.timeout_secs', 'Timeout (s)', 'number'],
], testBlock('carddav')), { bind: bindTests });
PAGES.activesync = (v) => settingsPage(v, 'PROTOCOL', 'ActiveSync', T('Logowanie, protokół, synchronizacja, push i Autodiscover.', 'Login, protocol, synchronisation, push and Autodiscover.'), (s) =>
PAGES.activesync = (v) => settingsPage(v, 'PROTOCOL', 'ActiveSync', T('Logowanie, protokół, synchronizacja i push.', 'Login, protocol, synchronisation and push.'), (s) =>
section(s, T('Ogólne', 'General'), '', [
['general.server_name', T('Nazwa serwera', 'Server name'), 'text'], ['general.default_timezone', T('Domyślna strefa czasowa', 'Default time zone'), 'tz'],
['general.login_format', T('Format loginu', 'Login format'), 'select', { options: [['as_given', T('jak podany przez urządzenie', 'as given by device')], ['local_part', T('tylko część lokalna (bez @domena)', 'local part only')], ['full_email', T('pełny e-mail (dopisz domenę)', 'full e-mail (append domain)')]], help: T('Odpowiednik USE_FULLEMAIL_FOR_LOGIN. Prefiks DOMENA\\ jest zawsze usuwany.', 'Equivalent of USE_FULLEMAIL_FOR_LOGIN. DOMAIN\\ prefixes are always stripped.') }],
@@ -668,10 +669,6 @@
['ping.lifetime_min', T('Min. heartbeat (s)', 'Min heartbeat (s)'), 'number'], ['ping.lifetime_max', T('Maks. heartbeat (s)', 'Max heartbeat (s)'), 'number'],
['ping.max_folders', T('Maks. folderów', 'Max folders'), 'number'],
]) +
section(s, 'Autodiscover', T('Automatyczna konfiguracja kont na telefonach (/autodiscover/autodiscover.xml).', 'Automatic account configuration (/autodiscover/autodiscover.xml).'), [
['autodiscover.enabled', T('Włączony', 'Enabled'), 'bool'], ['autodiscover.require_auth', T('Wymagaj poprawnego hasła', 'Require valid password'), 'bool'],
['autodiscover.server_url', T('Publiczny URL ActiveSync', 'Public ActiveSync URL'), 'text', { wide: true, ph: 'https://mail.example.com/Microsoft-Server-ActiveSync', help: T('Puste = wyznaczony z nagłówka Host.', 'Empty = derived from the Host header.') }],
]) +
section(s, T('Logowanie i retencja', 'Logging & retention'), '', [
['logging.log_wbxml', T('Loguj zdekodowany WBXML (debug, zawiera treść poczty!)', 'Log decoded WBXML (debug, contains mail data!)'), 'bool'],
['logging.debug_users', T('Użytkownicy z logowaniem debug', 'Debug users'), 'list'],
@@ -700,6 +697,44 @@
['reqencsmimemessages', T('Wymagaj szyfrowania S/MIME', 'Require encrypted S/MIME'), 'bool'], ['allowsmimesoftcerts', T('Certyfikaty programowe S/MIME', 'S/MIME soft certs'), 'bool'],
];
// Autodiscover: settings, served domains and DNS / endpoint diagnostics
const LEVEL_LABEL = { ok: 'OK', warn: T('UWAGA', 'WARN'), fail: T('BŁĄD', 'FAIL'), info: 'INFO' };
const LEVEL_CLASS = { ok: 'ok', warn: 'warn', fail: 'bad', info: 'info' };
function diagHtml(r) {
const rows = r.checks.map((c) => `<tr><td><span class="pill ${LEVEL_CLASS[c.level] || 'muted'}">${esc(LEVEL_LABEL[c.level] || c.level)}</span></td><td class="wrap mono-cell">${esc(c.title)}</td><td class="wrap">${esc(c.detail)}</td></tr>`).join('');
const verdictFail = r.checks[0] && r.checks[0].level === 'fail';
const advice = verdictFail ? `<h3 class="sub-h">${T('Zalecane rekordy DNS (wystarczy jeden z nich)', 'Recommended DNS records (one of them is enough)')}</h3><pre class="code">${esc(r.dns_records.join('\n'))}</pre>
<h3 class="sub-h">${T('Przykład nginx dla autodiscover.' + r.domain, 'nginx example for autodiscover.' + r.domain)}</h3><pre class="code">${esc(r.nginx)}</pre>` : '';
return `<h3 class="sub-h">@${esc(r.domain)}</h3><table><thead><tr><th>${T('Wynik', 'Result')}</th><th>${T('Sprawdzenie', 'Check')}</th><th>${T('Szczegóły', 'Details')}</th></tr></thead><tbody>${rows}</tbody></table>${advice}`;
}
PAGES.autodiscover = (v) => settingsPage(v, 'PROTOCOL', 'Autodiscover',
T('Automatyczna konfiguracja kont na telefonach: domeny, wymagane rekordy DNS i diagnostyka endpointów.', 'Automatic account setup on phones: served domains, required DNS records and endpoint diagnostics.'),
(s) => section(s, T('Ustawienia', 'Settings'), T('Telefon szuka Autodiscover pod domeną adresu e-mail (np. user@example.com → https://example.com/autodiscover/…, https://autodiscover.example.com/autodiscover/… lub rekord SRV), a nie pod nazwą serwera.', 'Phones look for Autodiscover on the domain of the e-mail address (e.g. user@example.com → https://example.com/autodiscover/…, https://autodiscover.example.com/autodiscover/… or an SRV record), not on the server name.'), [
['autodiscover.enabled', T('Włączony', 'Enabled'), 'bool'], ['autodiscover.require_auth', T('Wymagaj poprawnego hasła', 'Require valid password'), 'bool'],
['autodiscover.server_url', T('Publiczny URL ActiveSync', 'Public ActiveSync URL'), 'text', { wide: true, ph: 'https://mail.example.com/Microsoft-Server-ActiveSync', help: T('Adres przekazywany telefonom. Puste = z nagłówka Host żądania (zwykle autodiscover.<domena>, co bywa błędne).', 'Address handed to phones. Empty = from the Host header of the request (usually autodiscover.<domain>, which is often wrong).') }],
['autodiscover.domains', T('Domeny e-mail', 'E-mail domains'), 'list', { ph: 'example.com\nexample.org https://mail.example.org/Microsoft-Server-ActiveSync', help: T('Jedna domena na linię, opcjonalnie z własnym adresem ActiveSync po spacji (domyślnie: Publiczny URL powyżej). Gdy lista nie jest pusta, żądania dla innych domen są odrzucane (pusta = wszystkie domeny). Zapisz zmiany przed uruchomieniem diagnostyki.', 'One domain per line, optionally followed by its own ActiveSync URL after a space (default: the Public URL above). When the list is not empty, requests for other domains are refused (empty = all domains). Save before running diagnostics.') }],
], `<div class="diag-bar"><button class="outline" id="diag-all">${T('Uruchom diagnostykę wszystkich domen', 'Run diagnostics for all domains')}</button><span class="fine" id="diag-hint"></span></div><div id="diag-out"></div>`),
{ bind: (form) => {
const doms = (S.settings.autodiscover.domains || []).filter(Boolean).map((e) => e.trim().split(/[\s=]+/)[0].toLowerCase().replace(/\.$/, ''));
const out = $('#diag-out', form);
$('#diag-hint', form).textContent = doms.length ? doms.map((d) => '@' + d).join(' ') : T('Dodaj domeny i zapisz, aby uruchomić diagnostykę.', 'Add domains and save to run diagnostics.');
$('#diag-all', form).disabled = !doms.length;
$('#diag-all', form).addEventListener('click', async (e) => {
const b = e.target;
b.disabled = true; out.innerHTML = '';
for (const d of doms) {
const holder = document.createElement('div');
holder.innerHTML = `<div class="result">${T('Sprawdzam', 'Checking')} ${esc(d)}…</div>`;
out.appendChild(holder);
try { holder.innerHTML = diagHtml(await api('POST', '/api/autodiscover/check', { domain: d })); }
catch (err) { holder.innerHTML = `<div class="result bad">${esc(d)}: ${esc(err.message)}</div>`; }
}
b.disabled = false;
});
} });
PAGES.policies = async (v) => {
let sel = 0;
await settingsPage(v, 'PROVISIONING', T('Polityki urządzeń', 'Device policies'), T('Polityki bezpieczeństwa przekazywane urządzeniom (odpowiednik policies.ini). Zmiana polityki wymusza ponowny provisioning urządzeń.', 'Security policies pushed to devices (policies.ini equivalent). Changing a policy forces devices to re-provision.'), (s) => {