This commit is contained in:
@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.156"
|
||||
SYSTEM_VERSION = "V10.157"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -2,12 +2,125 @@
|
||||
|
||||
{% block title %}修改密碼 - EwoooC{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.change-password-page {
|
||||
color: var(--momo-text-primary);
|
||||
}
|
||||
|
||||
.change-password-card {
|
||||
overflow: hidden;
|
||||
background: var(--momo-bg-surface);
|
||||
border: 1px solid var(--momo-border-light);
|
||||
border-radius: var(--momo-radius-md);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.change-password-card__head {
|
||||
background: var(--momo-bg-paper);
|
||||
border-bottom: 1px solid var(--momo-border-light);
|
||||
color: var(--momo-text-primary);
|
||||
}
|
||||
|
||||
.change-password-card__head h5 {
|
||||
color: var(--momo-text-primary);
|
||||
font-family: var(--momo-font-display);
|
||||
font-size: var(--momo-text-title);
|
||||
font-weight: 800;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.password-rule-note {
|
||||
padding: 12px 14px;
|
||||
background: var(--momo-info-bg);
|
||||
border: 1px solid var(--momo-info-border);
|
||||
border-radius: var(--momo-radius-md);
|
||||
color: var(--momo-info-text);
|
||||
}
|
||||
|
||||
.password-rule-note h6 {
|
||||
color: var(--momo-info-text);
|
||||
font-family: var(--momo-font-display);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.change-password-alert {
|
||||
position: relative;
|
||||
padding: 12px 42px 12px 14px;
|
||||
margin-bottom: var(--momo-space-4);
|
||||
border: 1px solid var(--momo-border-light);
|
||||
border-radius: var(--momo-radius-md);
|
||||
background: var(--momo-bg-paper);
|
||||
color: var(--momo-text-primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.change-password-alert--success {
|
||||
background: var(--momo-success-bg);
|
||||
border-color: var(--momo-success-border);
|
||||
color: var(--momo-success-text);
|
||||
}
|
||||
|
||||
.change-password-alert--danger {
|
||||
background: var(--momo-danger-bg);
|
||||
border-color: var(--momo-danger-border);
|
||||
color: var(--momo-danger-text);
|
||||
}
|
||||
|
||||
.change-password-alert--warning {
|
||||
background: var(--momo-warning-bg);
|
||||
border-color: var(--momo-warning-border);
|
||||
color: var(--momo-warning-text);
|
||||
}
|
||||
|
||||
.change-password-strength-bar {
|
||||
border-radius: var(--momo-radius-pill);
|
||||
}
|
||||
|
||||
.change-password-strength-bar--danger {
|
||||
background: var(--momo-danger);
|
||||
}
|
||||
|
||||
.change-password-strength-bar--warning {
|
||||
background: var(--momo-warning);
|
||||
}
|
||||
|
||||
.change-password-strength-bar--success {
|
||||
background: var(--momo-success);
|
||||
}
|
||||
|
||||
.change-password-feedback {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.change-password-feedback.is-danger {
|
||||
color: var(--momo-danger-text);
|
||||
}
|
||||
|
||||
.change-password-feedback.is-warning {
|
||||
color: var(--momo-warning-text);
|
||||
}
|
||||
|
||||
.change-password-feedback.is-success {
|
||||
color: var(--momo-success-text);
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.change-password-page .container {
|
||||
padding-left: var(--momo-space-3);
|
||||
padding-right: var(--momo-space-3);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="change-password-page">
|
||||
<div class="container py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-lg-5">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<div class="card change-password-card">
|
||||
<div class="card-header change-password-card__head">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-key me-2"></i>修改密碼
|
||||
</h5>
|
||||
@@ -49,7 +162,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 密碼要求提示 -->
|
||||
<div class="alert alert-info">
|
||||
<div class="password-rule-note">
|
||||
<h6 class="alert-heading"><i class="fas fa-info-circle me-2"></i>密碼要求</h6>
|
||||
<ul class="mb-0 ps-3">
|
||||
{% for req in password_requirements %}
|
||||
@@ -72,6 +185,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
@@ -168,24 +282,24 @@
|
||||
if (/[0-9]/.test(password)) score++;
|
||||
if (/[!@#$%^&*]/.test(password)) score++;
|
||||
|
||||
let strengthText, strengthClass;
|
||||
let strengthText, strengthTone;
|
||||
if (score < 3) {
|
||||
strengthText = '弱';
|
||||
strengthClass = 'text-danger';
|
||||
strengthTone = 'danger';
|
||||
} else if (score < 5) {
|
||||
strengthText = '中等';
|
||||
strengthClass = 'text-warning';
|
||||
strengthTone = 'warning';
|
||||
} else {
|
||||
strengthText = '強';
|
||||
strengthClass = 'text-success';
|
||||
strengthTone = 'success';
|
||||
}
|
||||
|
||||
strengthDiv.innerHTML = `
|
||||
<div class="progress" style="height: 5px;">
|
||||
<div class="progress-bar bg-${strengthClass.replace('text-', '')}"
|
||||
<div class="progress-bar change-password-strength-bar change-password-strength-bar--${strengthTone}"
|
||||
style="width: ${(score / 6) * 100}%"></div>
|
||||
</div>
|
||||
<small class="${strengthClass}">密碼強度:${strengthText}</small>
|
||||
<small class="change-password-feedback is-${strengthTone}">密碼強度:${strengthText}</small>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -200,16 +314,16 @@
|
||||
}
|
||||
|
||||
if (newPassword === confirmPassword) {
|
||||
matchDiv.innerHTML = '<small class="text-success"><i class="fas fa-check me-1"></i>密碼相符</small>';
|
||||
matchDiv.innerHTML = '<small class="change-password-feedback is-success"><i class="fas fa-check me-1"></i>密碼相符</small>';
|
||||
} else {
|
||||
matchDiv.innerHTML = '<small class="text-danger"><i class="fas fa-times me-1"></i>密碼不相符</small>';
|
||||
matchDiv.innerHTML = '<small class="change-password-feedback is-danger"><i class="fas fa-times me-1"></i>密碼不相符</small>';
|
||||
}
|
||||
}
|
||||
|
||||
function showAlert(message, type) {
|
||||
const container = document.getElementById('alertContainer');
|
||||
container.innerHTML = `
|
||||
<div class="alert alert-${type} alert-dismissible fade show" role="alert">
|
||||
<div class="change-password-alert change-password-alert--${type} alert-dismissible fade show" role="alert">
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,67 @@
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.login-history-page {
|
||||
color: var(--momo-text-primary);
|
||||
}
|
||||
|
||||
.login-history-table-head th {
|
||||
background: var(--momo-bg-paper) !important;
|
||||
color: var(--momo-text-secondary) !important;
|
||||
font-family: var(--momo-font-mono, monospace);
|
||||
font-size: var(--momo-text-label);
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.login-history-empty {
|
||||
padding: var(--momo-space-5) !important;
|
||||
color: var(--momo-text-secondary) !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-history-empty.is-danger {
|
||||
color: var(--momo-danger-text) !important;
|
||||
}
|
||||
|
||||
.login-status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 22px;
|
||||
padding: 3px 8px;
|
||||
border: 1px solid var(--momo-border-light);
|
||||
border-radius: var(--momo-radius-sm);
|
||||
font-family: var(--momo-font-mono, monospace);
|
||||
font-size: var(--momo-text-label);
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.login-status-badge.is-success {
|
||||
background: var(--momo-success-bg);
|
||||
border-color: var(--momo-success-border);
|
||||
color: var(--momo-success-text);
|
||||
}
|
||||
|
||||
.login-status-badge.is-danger {
|
||||
background: var(--momo-danger-bg);
|
||||
border-color: var(--momo-danger-border);
|
||||
color: var(--momo-danger-text);
|
||||
}
|
||||
|
||||
.login-status-badge.is-warning {
|
||||
background: var(--momo-warning-bg);
|
||||
border-color: var(--momo-warning-border);
|
||||
color: var(--momo-warning-text);
|
||||
}
|
||||
|
||||
.login-status-badge.is-muted {
|
||||
background: var(--momo-tag-muted-bg);
|
||||
border-color: var(--momo-tag-muted-border);
|
||||
color: var(--momo-tag-muted-text);
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.login-history-table {
|
||||
min-width: 0;
|
||||
@@ -76,7 +137,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="container-fluid py-4 login-history-page">
|
||||
<div class="page-header">
|
||||
<h1><i class="fas fa-clock-rotate-left me-2"></i>登入歷史</h1>
|
||||
<p>系統登入記錄與異常嘗試追蹤</p>
|
||||
@@ -94,7 +155,7 @@
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 login-history-table">
|
||||
<thead class="table-light">
|
||||
<thead class="login-history-table-head">
|
||||
<tr>
|
||||
<th>時間</th>
|
||||
<th>帳號</th>
|
||||
@@ -121,9 +182,9 @@
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
const statusClass = {
|
||||
success: 'text-bg-success',
|
||||
failed: 'text-bg-danger',
|
||||
locked: 'text-bg-warning'
|
||||
success: 'is-success',
|
||||
failed: 'is-danger',
|
||||
locked: 'is-warning'
|
||||
};
|
||||
|
||||
function escapeHtml(value) {
|
||||
@@ -138,18 +199,18 @@ async function loadLoginHistory() {
|
||||
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>`;
|
||||
tbody.innerHTML = `<tr><td colspan="6" class="login-history-empty is-danger">${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>';
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="login-history-empty">尚無登入記錄</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><span class="login-status-badge ${statusClass[item.status] || 'is-muted'}">${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>
|
||||
|
||||
Reference in New Issue
Block a user