Files
ewoooc/templates/login_history.html
OoO 2c869edcb1
All checks were successful
CD Pipeline / deploy (push) Successful in 1m0s
統一系統管理頁新版殼層
2026-05-14 00:43:55 +08:00

164 lines
5.4 KiB
HTML

{% extends "ewoooc_base.html" %}
{% block title %}登入歷史 - EwoooC{% endblock %}
{% block extra_css %}
<style>
@media (max-width: 760px) {
.login-history-table {
min-width: 0;
border-collapse: separate;
border-spacing: 0;
}
.login-history-table,
.login-history-table tbody,
.login-history-table tr,
.login-history-table td {
display: block;
width: 100%;
}
.login-history-table thead {
display: none;
}
.login-history-table tbody {
display: grid;
gap: 10px;
padding: 12px;
}
.login-history-table tbody tr {
overflow: hidden;
border: 1px solid var(--momo-border-light, #e5dccd);
border-radius: 8px;
background: var(--momo-bg-surface, #fffaf1);
}
.login-history-table tbody td {
display: grid;
grid-template-columns: 5.6rem minmax(0, 1fr);
gap: 10px;
padding: 10px 12px;
border-bottom: 1px solid var(--momo-border-light, #e5dccd);
text-align: left !important;
overflow-wrap: anywhere;
}
.login-history-table tbody td:last-child {
border-bottom: 0;
}
.login-history-table tbody td::before {
color: var(--momo-text-tertiary, #9a8f80);
font-family: var(--momo-font-mono, monospace);
font-size: 11px;
font-weight: 800;
}
.login-history-table tbody td:nth-child(1)::before { content: "時間"; }
.login-history-table tbody td:nth-child(2)::before { content: "帳號"; }
.login-history-table tbody td:nth-child(3)::before { content: "狀態"; }
.login-history-table tbody td:nth-child(4)::before { content: "IP"; }
.login-history-table tbody td:nth-child(5)::before { content: "原因"; }
.login-history-table tbody td:nth-child(6)::before { content: "裝置"; }
.login-history-table tbody td[colspan] {
display: block;
}
.login-history-table tbody td[colspan]::before {
content: none;
}
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<div class="page-header">
<h1><i class="fas fa-clock-rotate-left me-2"></i>登入歷史</h1>
<p>系統登入記錄與異常嘗試追蹤</p>
</div>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-list me-2"></i>最近記錄</span>
<select class="form-select form-select-sm" id="historyLimit" style="width: 120px;">
<option value="50">50 筆</option>
<option value="100" selected>100 筆</option>
<option value="200">200 筆</option>
</select>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 login-history-table">
<thead class="table-light">
<tr>
<th>時間</th>
<th>帳號</th>
<th>狀態</th>
<th>IP</th>
<th>原因</th>
<th>User Agent</th>
</tr>
</thead>
<tbody id="loginHistoryBody">
<tr>
<td colspan="6" class="text-center py-4">
<div class="spinner-border text-primary" role="status"></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
const statusClass = {
success: 'text-bg-success',
failed: 'text-bg-danger',
locked: 'text-bg-warning'
};
function escapeHtml(value) {
return String(value ?? '').replace(/[&<>"']/g, char => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
}[char]));
}
async function loadLoginHistory() {
const limit = document.getElementById('historyLimit').value;
const tbody = document.getElementById('loginHistoryBody');
const response = await fetch(`/api/login_history?limit=${limit}`);
const result = await response.json();
if (!result.success) {
tbody.innerHTML = `<tr><td colspan="6" class="text-center text-danger py-4">${escapeHtml(result.message)}</td></tr>`;
return;
}
if (!result.data.length) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted py-4">尚無登入記錄</td></tr>';
return;
}
tbody.innerHTML = result.data.map(item => `
<tr>
<td>${escapeHtml(item.login_time ? new Date(item.login_time).toLocaleString('zh-TW') : '')}</td>
<td>${escapeHtml(item.username_attempted || item.user_id || '')}</td>
<td><span class="badge ${statusClass[item.status] || 'text-bg-secondary'}">${escapeHtml(item.status)}</span></td>
<td>${escapeHtml(item.ip_address)}</td>
<td>${escapeHtml(item.failure_reason)}</td>
<td class="text-truncate" style="max-width: 360px;">${escapeHtml(item.user_agent)}</td>
</tr>
`).join('');
}
document.getElementById('historyLimit').addEventListener('change', loadLoginHistory);
document.addEventListener('DOMContentLoaded', loadLoginHistory);
</script>
{% endblock %}