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:
parent
dca064427e
commit
9bb22eb17b
|
|
@ -31,8 +31,14 @@ FRONTEND_URL=https://cloud.example.com
|
|||
# Max Upload-Groesse in MB
|
||||
MAX_UPLOAD_SIZE_MB=500
|
||||
|
||||
# Zeitzone (prozessweit) - z.B. Europe/Berlin, Europe/Vienna, UTC
|
||||
# Zeitzone (prozessweit) - IANA-Format "Region/Stadt".
|
||||
# Wirkt auf datetime.now(), strftime %Z und Kalender/Task-Zeitstempel.
|
||||
# Haeufige Werte:
|
||||
# Europe/Berlin, Europe/Vienna, Europe/Zurich, Europe/Amsterdam,
|
||||
# Europe/Paris, Europe/London, Europe/Madrid, Europe/Rome,
|
||||
# Europe/Warsaw, Europe/Prague, Europe/Copenhagen, Europe/Stockholm,
|
||||
# UTC, America/New_York, America/Los_Angeles, Asia/Tokyo, Australia/Sydney
|
||||
# Vollstaendige Liste: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
TZ=Europe/Berlin
|
||||
|
||||
# NTP-Server zum Pruefen der Uhrzeit beim Start (nicht-invasiver Offset-Check
|
||||
|
|
|
|||
30
README.md
30
README.md
|
|
@ -191,6 +191,36 @@ docker-compose up --build -d
|
|||
|
||||
**Ohne OnlyOffice** (`ONLYOFFICE_URL` leer) werden Office-Dateien in einer einfachen Vorschau angezeigt. **Mit OnlyOffice** erhaelt man einen vollwertigen Editor (wie Google Docs).
|
||||
|
||||
### Zeitzone & NTP
|
||||
|
||||
In der `.env` stehen zwei Variablen die die Systemzeit betreffen:
|
||||
|
||||
```env
|
||||
TZ=Europe/Berlin
|
||||
NTP_SERVER=ptbtime1.ptb.de
|
||||
```
|
||||
|
||||
**`TZ`** setzt die prozessweite Zeitzone (wirkt auf Log-Zeitstempel, Kalender/Task-Zeiten, `datetime.now()`). IANA-Format `Region/Stadt`.
|
||||
|
||||
Haeufige Werte:
|
||||
|
||||
| Region | Beispielwerte |
|
||||
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Deutschland | `Europe/Berlin` |
|
||||
| DACH/EU | `Europe/Vienna`, `Europe/Zurich`, `Europe/Amsterdam`, `Europe/Paris`, `Europe/London`, `Europe/Madrid`, `Europe/Rome`, `Europe/Warsaw` |
|
||||
| Nord-EU | `Europe/Copenhagen`, `Europe/Stockholm`, `Europe/Helsinki`, `Europe/Oslo` |
|
||||
| Sonstige | `UTC`, `America/New_York`, `America/Los_Angeles`, `Asia/Tokyo`, `Australia/Sydney` |
|
||||
|
||||
Vollstaendige Liste: <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
|
||||
|
||||
**`NTP_SERVER`** wird beim Start abgefragt, um die Abweichung der Systemuhr zu pruefen. Bei Drift > 5 s erscheint eine Warnung im Log. **Hinweis:** Im Container wird die Uhr dadurch nicht gesetzt (benoetigt `CAP_SYS_TIME`) - auf dem Host sollte ein NTP-Daemon laufen. Der Check dient nur zur Sichtbarkeit.
|
||||
|
||||
Default: `ptbtime1.ptb.de` (offizielle deutsche Zeitreferenz der Physikalisch-Technischen Bundesanstalt, Stratum 1, sehr hohe Verfuegbarkeit).
|
||||
|
||||
Alternativen: `ptbtime2.ptb.de`, `ptbtime3.ptb.de`, `de.pool.ntp.org`, `time.cloudflare.com`. Leerlassen um den Check zu deaktivieren.
|
||||
|
||||
Aktuelle Werte sind im Admin-Bereich unter **Einstellungen > System** einsehbar.
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Dateien
|
||||
|
|
|
|||
|
|
@ -145,6 +145,12 @@ def delete_user(user_id):
|
|||
@api_bp.route('/settings', methods=['GET'])
|
||||
@admin_required
|
||||
def get_settings():
|
||||
import time as _time
|
||||
from datetime import datetime as _dt
|
||||
try:
|
||||
tzname = _time.strftime('%Z')
|
||||
except Exception:
|
||||
tzname = ''
|
||||
return jsonify({
|
||||
'public_registration': AppSettings.get_bool('public_registration', default=True),
|
||||
'system_smtp_host': AppSettings.get('system_smtp_host', ''),
|
||||
|
|
@ -155,6 +161,11 @@ def get_settings():
|
|||
'system_email_from': AppSettings.get('system_email_from', ''),
|
||||
'onlyoffice_url': os.environ.get('ONLYOFFICE_URL', ''),
|
||||
'onlyoffice_configured': bool(os.environ.get('ONLYOFFICE_URL', '')),
|
||||
# Read-only system info aus der .env
|
||||
'timezone': os.environ.get('TZ', 'Europe/Berlin'),
|
||||
'timezone_abbr': tzname,
|
||||
'server_time': _dt.now().isoformat(timespec='seconds'),
|
||||
'ntp_server': os.environ.get('NTP_SERVER', ''),
|
||||
}), 200
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue