Add configurable TIMEZONE for web UI display (UTC storage preserved)

This commit is contained in:
2026-07-17 12:02:58 +07:00
parent 559025d5f7
commit 513b8ede84
12 changed files with 131 additions and 7 deletions
+1 -1
View File
@@ -22,7 +22,7 @@
<tbody>
{% for a in audit %}
<tr>
<td class="text-nowrap">{{ a.timestamp.strftime('%Y-%m-%d %H:%M:%S') if a.timestamp else '-' }}</td>
<td class="text-nowrap">{{ a.timestamp | localtime('%Y-%m-%d %H:%M:%S') }}</td>
<td>{{ a.username or '-' }}</td>
<td><span class="badge bg-info">{{ a.action }}</span></td>
<td class="text-muted small">{{ a.details or '-' }}</td>
+10
View File
@@ -31,6 +31,15 @@
<label class="form-label">NOTIFICATION_TELEGRAM_CHAT_ID</label>
<input type="text" class="form-control" id="NOTIFICATION_TELEGRAM_CHAT_ID" value="{{ settings.NOTIFICATION_TELEGRAM_CHAT_ID or '' }}">
</div>
<div class="mb-3">
<label class="form-label">Часовой пояс</label>
<select class="form-select" id="TIMEZONE">
{% for tz in timezones %}
<option value="{{ tz }}" {% if settings.TIMEZONE == tz %}selected{% endif %}>{{ tz }}</option>
{% endfor %}
</select>
<div class="form-text">UTC хранится как есть, в веб-интерфейсе время отображается в выбранном поясе.</div>
</div>
<button type="submit" class="btn btn-primary">Сохранить</button>
</form>
<div id="settings-error" class="alert alert-danger mt-3 d-none"></div>
@@ -51,6 +60,7 @@ document.getElementById('settings-form').addEventListener('submit', async (e) =>
NOTIFICATION_WEBHOOK_URL: document.getElementById('NOTIFICATION_WEBHOOK_URL').value,
NOTIFICATION_TELEGRAM_BOT_TOKEN: document.getElementById('NOTIFICATION_TELEGRAM_BOT_TOKEN').value,
NOTIFICATION_TELEGRAM_CHAT_ID: document.getElementById('NOTIFICATION_TELEGRAM_CHAT_ID').value,
TIMEZONE: document.getElementById('TIMEZONE').value,
};
const res = await fetch('/api/settings', {
method: 'POST',
+1 -1
View File
@@ -50,7 +50,7 @@
<td>
<span class="badge {% if u.role == 'admin' %}bg-danger{% else %}bg-secondary{% endif %}">{{ u.role }}</span>
</td>
<td>{{ u.created_at.strftime('%Y-%m-%d %H:%M') if u.created_at else '-' }}</td>
<td>{{ u.created_at | localtime }}</td>
<td>
<button class="btn btn-sm btn-outline-primary change-role" data-id="{{ u.id }}" data-username="{{ u.username }}" data-role="{{ u.role }}">Роль</button>
<button class="btn btn-sm btn-outline-warning reset-password" data-id="{{ u.id }}" data-username="{{ u.username }}">Пароль</button>
+3
View File
@@ -7,6 +7,9 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
<link rel="stylesheet" href="/static/style.css">
<script>
window.SERVER_TIMEZONE = "{{ current_timezone() }}";
</script>
{% block head %}{% endblock %}
</head>
<body>
+18 -2
View File
@@ -86,7 +86,7 @@
<td>{{ c.current_cpu_percent | round(1) if c.current_cpu_percent is not none else '-' }}%</td>
<td>{{ c.current_ram_percent | round(1) if c.current_ram_percent is not none else '-' }}%</td>
<td>{{ c.os_info or '-' }}</td>
<td class="last-seen">{% if c.last_seen %}{{ c.last_seen.strftime('%Y-%m-%d %H:%M') }}{% else %}-{% endif %}</td>
<td class="last-seen">{{ c.last_seen | localtime }}</td>
<td>
<a href="/computers/{{ c.id }}" class="btn btn-sm btn-outline-primary">Детали</a>
{% if user.role == 'admin' %}
@@ -116,6 +116,22 @@ function formatCurrentUser(value) {
return escapeHtml(value);
}
function formatServerTime(isoString) {
if (!isoString) return '-';
const d = new Date(isoString);
const parts = new Intl.DateTimeFormat('sv-SE', {
timeZone: window.SERVER_TIMEZONE,
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit',
hour12: false,
}).formatToParts(d);
const p = {};
for (const part of parts) {
if (part.type !== 'literal') p[part.type] = part.value;
}
return `${p.year}-${p.month}-${p.day} ${p.hour}:${p.minute}`;
}
function currentQuery() {
return new URLSearchParams(window.location.search).toString();
}
@@ -173,7 +189,7 @@ async function loadDashboard() {
<td>${c.current_cpu_percent != null ? c.current_cpu_percent.toFixed(1) : '-'}%</td>
<td>${c.current_ram_percent != null ? c.current_ram_percent.toFixed(1) : '-'}%</td>
<td>${escapeHtml(c.os_info || '-')}</td>
<td class="last-seen">${c.last_seen ? c.last_seen.replace('T', ' ').slice(0, 16) : '-'}</td>
<td class="last-seen">${formatServerTime(c.last_seen)}</td>
<td>
<a href="/computers/${c.id}" class="btn btn-sm btn-outline-primary">Детали</a>
${isAdmin ? `<button class="btn btn-sm btn-outline-danger delete-pc" data-id="${c.id}" data-hostname="${escapeHtml(c.hostname)}">Удалить</button>` : ''}
+1 -1
View File
@@ -19,7 +19,7 @@
<div>
<h6 class="card-title mb-1">{{ n.title }}</h6>
<p class="card-text mb-1 text-muted small">{{ n.message }}</p>
<p class="card-text"><small class="text-muted">{{ n.sent_at.strftime('%Y-%m-%d %H:%M') if n.sent_at else '-' }}</small></p>
<p class="card-text"><small class="text-muted">{{ n.sent_at | localtime }}</small></p>
</div>
{% if not n.is_read %}
<button class="btn btn-sm btn-outline-success mark-read" data-id="{{ n.id }}">Прочитать</button>