feat: Admin-Sicht System-Zeit + TZ-Liste in README/.env.example

- /api/settings gibt zusaetzlich timezone, timezone_abbr, server_time,
  ntp_server zurueck (alle read-only, aus Config/ENV).
- AdminView zeigt neuen Abschnitt "System-Zeit" mit Zeitzone, aktueller
  Server-Zeit und NTP-Server samt Hinweis "wird in der .env festgelegt".
- .env.example: Liste gaengiger TZ-Werte mit Link zur IANA-Vollliste.
- README.md: neuer Abschnitt "Zeitzone & NTP" mit Werte-Tabelle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-04-14 15:19:40 +02:00
parent dca064427e
commit 9bb22eb17b
4 changed files with 93 additions and 1 deletions
+45
View File
@@ -37,6 +37,28 @@
</div>
</div>
<!-- System-Info: Zeitzone & NTP (read-only) -->
<div class="admin-section">
<h3>System-Zeit</h3>
<p class="hint">Wird in der <code>.env</code> festgelegt (Keys <code>TZ</code> und <code>NTP_SERVER</code>).
Aenderungen erfordern einen Neustart des Backends.</p>
<div class="sysinfo">
<div class="sysinfo-row">
<span class="sysinfo-label">Zeitzone:</span>
<code>{{ settings.timezone || '—' }}</code>
<span v-if="settings.timezone_abbr" class="sysinfo-extra">({{ settings.timezone_abbr }})</span>
</div>
<div class="sysinfo-row">
<span class="sysinfo-label">Aktuelle Server-Zeit:</span>
<code>{{ formatServerTime(settings.server_time) }}</code>
</div>
<div class="sysinfo-row">
<span class="sysinfo-label">NTP-Server:</span>
<code>{{ settings.ntp_server || '(deaktiviert)' }}</code>
</div>
</div>
</div>
<!-- System Email -->
<div class="admin-section">
<h3>System-E-Mail (SMTP)</h3>
@@ -551,6 +573,17 @@ const smtpForm = ref({
const smtpPasswordSet = ref(false)
const onlyofficeConfigured = ref(false)
const onlyofficeUrl = ref('')
const settings = ref({ timezone: '', timezone_abbr: '', server_time: '', ntp_server: '' })
function formatServerTime(iso) {
if (!iso) return ''
try {
return new Date(iso).toLocaleString('de-DE', {
day: '2-digit', month: '2-digit', year: 'numeric',
hour: '2-digit', minute: '2-digit', second: '2-digit',
})
} catch { return iso }
}
const smtpTesting = ref(false)
// Backup & Restore
@@ -660,6 +693,12 @@ async function loadSettings() {
smtpPasswordSet.value = res.data.system_smtp_password_set
onlyofficeConfigured.value = res.data.onlyoffice_configured
onlyofficeUrl.value = res.data.onlyoffice_url || ''
settings.value = {
timezone: res.data.timezone || '',
timezone_abbr: res.data.timezone_abbr || '',
server_time: res.data.server_time || '',
ntp_server: res.data.ntp_server || '',
}
} catch { /* first load, defaults */ }
}
@@ -1216,6 +1255,12 @@ onMounted(() => {
.field-row { display: flex; gap: 0.75rem; align-items: flex-end; }
.flex-grow { flex: 1; }
.hint { font-size: 0.85rem; color: var(--p-text-muted-color); margin: 0 0 0.75rem; }
.hint code { background: var(--p-surface-100); padding: 0.05rem 0.35rem; border-radius: 3px; font-size: 0.8rem; }
.sysinfo { display: flex; flex-direction: column; gap: 0.4rem; font-size: 0.875rem; }
.sysinfo-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.sysinfo-label { min-width: 180px; color: var(--p-text-muted-color); }
.sysinfo code { background: var(--p-surface-100); padding: 0.15rem 0.5rem; border-radius: 4px; }
.sysinfo-extra { color: var(--p-text-muted-color); font-size: 0.8rem; }
.invite-section { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--p-surface-200); }
.invite-section h4 { margin: 0 0 0.25rem; font-size: 0.95rem; }
.invite-row { display: flex; gap: 0.5rem; align-items: flex-start; }