mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-25 00:44:16 +02:00
Add files via upload
This commit is contained in:
@@ -56,7 +56,7 @@ body {
|
||||
header {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 24px 32px;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -289,7 +289,7 @@ header {
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: var(--bg-primary);
|
||||
background: var(--bg-secondary);
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -755,6 +755,67 @@ header {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 登录遮罩 */
|
||||
.login-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(6px);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1200;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 12px;
|
||||
padding: 32px 28px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
border: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.login-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 0.9375rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.login-error {
|
||||
color: var(--error-color);
|
||||
background: rgba(220, 53, 69, 0.08);
|
||||
border: 1px solid rgba(220, 53, 69, 0.4);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.login-submit {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 模态框样式 */
|
||||
.modal {
|
||||
display: none;
|
||||
@@ -1251,9 +1312,12 @@ header {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 20px;
|
||||
background: rgba(0, 102, 255, 0.06);
|
||||
border-bottom: 1px solid rgba(0, 102, 255, 0.15);
|
||||
padding: 10px 16px;
|
||||
margin: 12px 0;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid rgba(0, 102, 255, 0.15);
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@@ -1261,20 +1325,22 @@ header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
gap: 12px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid rgba(0, 102, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.active-task-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.active-task-status {
|
||||
@@ -1288,7 +1354,7 @@ header {
|
||||
}
|
||||
|
||||
.active-task-message {
|
||||
font-size: 0.875rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -1299,7 +1365,7 @@ header {
|
||||
.active-task-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -1409,6 +1475,19 @@ header {
|
||||
box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.2);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.password-hint {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-muted);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tools-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
+371
-30
@@ -1,3 +1,9 @@
|
||||
const AUTH_STORAGE_KEY = 'cyberstrike-auth';
|
||||
let authToken = null;
|
||||
let authTokenExpiry = null;
|
||||
let authPromise = null;
|
||||
let authPromiseResolvers = [];
|
||||
let isAppInitialized = false;
|
||||
|
||||
// 当前对话ID
|
||||
let currentConversationId = null;
|
||||
@@ -7,6 +13,280 @@ const progressTaskState = new Map();
|
||||
let activeTaskInterval = null;
|
||||
const ACTIVE_TASK_REFRESH_INTERVAL = 10000; // 10秒检查一次,提供更实时的任务状态反馈
|
||||
|
||||
function isTokenValid() {
|
||||
return !!authToken && authTokenExpiry instanceof Date && authTokenExpiry.getTime() > Date.now();
|
||||
}
|
||||
|
||||
function saveAuth(token, expiresAt) {
|
||||
const expiry = expiresAt instanceof Date ? expiresAt : new Date(expiresAt);
|
||||
authToken = token;
|
||||
authTokenExpiry = expiry;
|
||||
try {
|
||||
localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify({
|
||||
token,
|
||||
expiresAt: expiry.toISOString(),
|
||||
}));
|
||||
} catch (error) {
|
||||
console.warn('无法持久化认证信息:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function clearAuthStorage() {
|
||||
authToken = null;
|
||||
authTokenExpiry = null;
|
||||
try {
|
||||
localStorage.removeItem(AUTH_STORAGE_KEY);
|
||||
} catch (error) {
|
||||
console.warn('无法清除认证信息:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function loadAuthFromStorage() {
|
||||
try {
|
||||
const raw = localStorage.getItem(AUTH_STORAGE_KEY);
|
||||
if (!raw) {
|
||||
return false;
|
||||
}
|
||||
const stored = JSON.parse(raw);
|
||||
if (!stored.token || !stored.expiresAt) {
|
||||
clearAuthStorage();
|
||||
return false;
|
||||
}
|
||||
const expiry = new Date(stored.expiresAt);
|
||||
if (Number.isNaN(expiry.getTime())) {
|
||||
clearAuthStorage();
|
||||
return false;
|
||||
}
|
||||
authToken = stored.token;
|
||||
authTokenExpiry = expiry;
|
||||
return isTokenValid();
|
||||
} catch (error) {
|
||||
console.error('读取认证信息失败:', error);
|
||||
clearAuthStorage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAuthPromises(success) {
|
||||
authPromiseResolvers.forEach(resolve => resolve(success));
|
||||
authPromiseResolvers = [];
|
||||
authPromise = null;
|
||||
}
|
||||
|
||||
function showLoginOverlay(message = '') {
|
||||
const overlay = document.getElementById('login-overlay');
|
||||
const errorBox = document.getElementById('login-error');
|
||||
const passwordInput = document.getElementById('login-password');
|
||||
if (!overlay) {
|
||||
return;
|
||||
}
|
||||
overlay.style.display = 'flex';
|
||||
if (errorBox) {
|
||||
if (message) {
|
||||
errorBox.textContent = message;
|
||||
errorBox.style.display = 'block';
|
||||
} else {
|
||||
errorBox.textContent = '';
|
||||
errorBox.style.display = 'none';
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (passwordInput) {
|
||||
passwordInput.focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function hideLoginOverlay() {
|
||||
const overlay = document.getElementById('login-overlay');
|
||||
const errorBox = document.getElementById('login-error');
|
||||
const passwordInput = document.getElementById('login-password');
|
||||
if (overlay) {
|
||||
overlay.style.display = 'none';
|
||||
}
|
||||
if (errorBox) {
|
||||
errorBox.textContent = '';
|
||||
errorBox.style.display = 'none';
|
||||
}
|
||||
if (passwordInput) {
|
||||
passwordInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function ensureAuthPromise() {
|
||||
if (!authPromise) {
|
||||
authPromise = new Promise(resolve => {
|
||||
authPromiseResolvers.push(resolve);
|
||||
});
|
||||
}
|
||||
return authPromise;
|
||||
}
|
||||
|
||||
async function ensureAuthenticated() {
|
||||
if (isTokenValid()) {
|
||||
return true;
|
||||
}
|
||||
showLoginOverlay();
|
||||
await ensureAuthPromise();
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleUnauthorized({ message = '认证已过期,请重新登录', silent = false } = {}) {
|
||||
clearAuthStorage();
|
||||
authPromise = null;
|
||||
authPromiseResolvers = [];
|
||||
if (!silent) {
|
||||
showLoginOverlay(message);
|
||||
} else {
|
||||
showLoginOverlay();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function apiFetch(url, options = {}) {
|
||||
await ensureAuthenticated();
|
||||
const opts = { ...options };
|
||||
const headers = new Headers(options && options.headers ? options.headers : undefined);
|
||||
if (authToken && !headers.has('Authorization')) {
|
||||
headers.set('Authorization', `Bearer ${authToken}`);
|
||||
}
|
||||
opts.headers = headers;
|
||||
|
||||
const response = await fetch(url, opts);
|
||||
if (response.status === 401) {
|
||||
handleUnauthorized();
|
||||
throw new Error('未授权访问');
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
async function submitLogin(event) {
|
||||
event.preventDefault();
|
||||
const passwordInput = document.getElementById('login-password');
|
||||
const errorBox = document.getElementById('login-error');
|
||||
const submitBtn = document.querySelector('.login-submit');
|
||||
|
||||
if (!passwordInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const password = passwordInput.value.trim();
|
||||
if (!password) {
|
||||
if (errorBox) {
|
||||
errorBox.textContent = '请输入密码';
|
||||
errorBox.style.display = 'block';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ password }),
|
||||
});
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok || !result.token) {
|
||||
if (errorBox) {
|
||||
errorBox.textContent = result.error || '登录失败,请检查密码';
|
||||
errorBox.style.display = 'block';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
saveAuth(result.token, result.expires_at);
|
||||
hideLoginOverlay();
|
||||
resolveAuthPromises(true);
|
||||
if (!isAppInitialized) {
|
||||
await bootstrapApp();
|
||||
} else {
|
||||
await refreshAppData();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error);
|
||||
if (errorBox) {
|
||||
errorBox.textContent = '登录失败,请稍后重试';
|
||||
errorBox.style.display = 'block';
|
||||
}
|
||||
} finally {
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAppData(showTaskErrors = false) {
|
||||
await Promise.allSettled([
|
||||
loadConversations(),
|
||||
loadActiveTasks(showTaskErrors),
|
||||
]);
|
||||
}
|
||||
|
||||
async function bootstrapApp() {
|
||||
if (!isAppInitialized) {
|
||||
initializeChatUI();
|
||||
isAppInitialized = true;
|
||||
}
|
||||
await refreshAppData();
|
||||
}
|
||||
|
||||
function initializeChatUI() {
|
||||
const chatInputEl = document.getElementById('chat-input');
|
||||
if (chatInputEl) {
|
||||
chatInputEl.style.height = '44px';
|
||||
}
|
||||
|
||||
const messagesDiv = document.getElementById('chat-messages');
|
||||
if (messagesDiv && messagesDiv.childElementCount === 0) {
|
||||
addMessage('assistant', '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。');
|
||||
}
|
||||
|
||||
loadActiveTasks(true);
|
||||
if (activeTaskInterval) {
|
||||
clearInterval(activeTaskInterval);
|
||||
}
|
||||
activeTaskInterval = setInterval(() => loadActiveTasks(), ACTIVE_TASK_REFRESH_INTERVAL);
|
||||
}
|
||||
|
||||
function setupLoginUI() {
|
||||
const loginForm = document.getElementById('login-form');
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener('submit', submitLogin);
|
||||
}
|
||||
}
|
||||
|
||||
async function initializeApp() {
|
||||
setupLoginUI();
|
||||
const hasStoredAuth = loadAuthFromStorage();
|
||||
if (hasStoredAuth && isTokenValid()) {
|
||||
try {
|
||||
const response = await apiFetch('/api/auth/validate', {
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.ok) {
|
||||
hideLoginOverlay();
|
||||
resolveAuthPromises(true);
|
||||
await bootstrapApp();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('本地会话已失效,需重新登录');
|
||||
}
|
||||
}
|
||||
|
||||
clearAuthStorage();
|
||||
showLoginOverlay();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initializeApp);
|
||||
|
||||
|
||||
function registerProgressTask(progressId, conversationId = null) {
|
||||
const state = progressTaskState.get(progressId) || {};
|
||||
state.conversationId = conversationId !== undefined && conversationId !== null
|
||||
@@ -45,7 +325,7 @@ function finalizeProgressTask(progressId, finalLabel = '已完成') {
|
||||
}
|
||||
|
||||
async function requestCancel(conversationId) {
|
||||
const response = await fetch('/api/agent-loop/cancel', {
|
||||
const response = await apiFetch('/api/agent-loop/cancel', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -81,7 +361,7 @@ async function sendMessage() {
|
||||
let mcpExecutionIds = [];
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/agent-loop/stream', {
|
||||
const response = await apiFetch('/api/agent-loop/stream', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -950,7 +1230,7 @@ chatInput.addEventListener('keydown', function(e) {
|
||||
// 显示MCP调用详情
|
||||
async function showMCPDetail(executionId) {
|
||||
try {
|
||||
const response = await fetch(`/api/monitor/execution/${executionId}`);
|
||||
const response = await apiFetch(`/api/monitor/execution/${executionId}`);
|
||||
const exec = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
@@ -1064,7 +1344,7 @@ function startNewConversation() {
|
||||
// 加载对话列表
|
||||
async function loadConversations() {
|
||||
try {
|
||||
const response = await fetch('/api/conversations?limit=50');
|
||||
const response = await apiFetch('/api/conversations?limit=50');
|
||||
const conversations = await response.json();
|
||||
|
||||
const listContainer = document.getElementById('conversations-list');
|
||||
@@ -1179,7 +1459,7 @@ async function loadConversations() {
|
||||
// 加载对话
|
||||
async function loadConversation(conversationId) {
|
||||
try {
|
||||
const response = await fetch(`/api/conversations/${conversationId}`);
|
||||
const response = await apiFetch(`/api/conversations/${conversationId}`);
|
||||
const conversation = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -1230,7 +1510,7 @@ async function deleteConversation(conversationId) {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/conversations/${conversationId}`, {
|
||||
const response = await apiFetch(`/api/conversations/${conversationId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
@@ -1268,7 +1548,7 @@ function updateActiveConversation() {
|
||||
async function loadActiveTasks(showErrors = false) {
|
||||
const bar = document.getElementById('active-tasks-bar');
|
||||
try {
|
||||
const response = await fetch('/api/agent-loop/tasks');
|
||||
const response = await apiFetch('/api/agent-loop/tasks');
|
||||
const result = await response.json().catch(() => ({}));
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -1386,7 +1666,7 @@ window.onclick = function(event) {
|
||||
// 加载配置
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const response = await fetch('/api/config');
|
||||
const response = await apiFetch('/api/config');
|
||||
if (!response.ok) {
|
||||
throw new Error('获取配置失败');
|
||||
}
|
||||
@@ -1520,7 +1800,7 @@ async function applySettings() {
|
||||
});
|
||||
|
||||
// 更新配置
|
||||
const updateResponse = await fetch('/api/config', {
|
||||
const updateResponse = await apiFetch('/api/config', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -1534,7 +1814,7 @@ async function applySettings() {
|
||||
}
|
||||
|
||||
// 应用配置
|
||||
const applyResponse = await fetch('/api/config/apply', {
|
||||
const applyResponse = await apiFetch('/api/config/apply', {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
@@ -1551,24 +1831,85 @@ async function applySettings() {
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 加载对话列表
|
||||
loadConversations();
|
||||
|
||||
// 初始化 textarea 高度
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
if (chatInput) {
|
||||
chatInput.style.height = '44px';
|
||||
}
|
||||
|
||||
// 添加欢迎消息
|
||||
addMessage('assistant', '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。');
|
||||
|
||||
loadActiveTasks(true);
|
||||
if (activeTaskInterval) {
|
||||
clearInterval(activeTaskInterval);
|
||||
}
|
||||
activeTaskInterval = setInterval(() => loadActiveTasks(), ACTIVE_TASK_REFRESH_INTERVAL);
|
||||
});
|
||||
function resetPasswordForm() {
|
||||
const currentInput = document.getElementById('auth-current-password');
|
||||
const newInput = document.getElementById('auth-new-password');
|
||||
const confirmInput = document.getElementById('auth-confirm-password');
|
||||
|
||||
[currentInput, newInput, confirmInput].forEach(input => {
|
||||
if (input) {
|
||||
input.value = '';
|
||||
input.classList.remove('error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function changePassword() {
|
||||
const currentInput = document.getElementById('auth-current-password');
|
||||
const newInput = document.getElementById('auth-new-password');
|
||||
const confirmInput = document.getElementById('auth-confirm-password');
|
||||
const submitBtn = document.querySelector('.change-password-submit');
|
||||
|
||||
[currentInput, newInput, confirmInput].forEach(input => input && input.classList.remove('error'));
|
||||
|
||||
const currentPassword = currentInput?.value.trim() || '';
|
||||
const newPassword = newInput?.value.trim() || '';
|
||||
const confirmPassword = confirmInput?.value.trim() || '';
|
||||
|
||||
let hasError = false;
|
||||
|
||||
if (!currentPassword) {
|
||||
currentInput?.classList.add('error');
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (!newPassword || newPassword.length < 8) {
|
||||
newInput?.classList.add('error');
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
confirmInput?.classList.add('error');
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
alert('请正确填写当前密码和新密码,新密码至少 8 位且需要两次输入一致。');
|
||||
return;
|
||||
}
|
||||
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiFetch('/api/auth/change-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
oldPassword: currentPassword,
|
||||
newPassword: newPassword
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || '修改密码失败');
|
||||
}
|
||||
|
||||
alert('密码已更新,请使用新密码重新登录。');
|
||||
resetPasswordForm();
|
||||
handleUnauthorized({ message: '密码已更新,请使用新密码重新登录。', silent: false });
|
||||
closeSettings();
|
||||
} catch (error) {
|
||||
console.error('修改密码失败:', error);
|
||||
alert('修改密码失败: ' + error.message);
|
||||
} finally {
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,23 @@
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="login-overlay" class="login-overlay" style="display: none;">
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<h2>登录 CyberStrike</h2>
|
||||
<p class="login-subtitle">请输入配置中的访问密码</p>
|
||||
</div>
|
||||
<form id="login-form" class="login-form">
|
||||
<div class="form-group">
|
||||
<label for="login-password">密码</label>
|
||||
<input type="password" id="login-password" placeholder="输入登录密码" required autocomplete="current-password" />
|
||||
</div>
|
||||
<div id="login-error" class="login-error" role="alert" style="display: none;"></div>
|
||||
<button type="submit" class="btn-primary login-submit">登录</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="header-content">
|
||||
@@ -106,6 +123,30 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 安全设置 -->
|
||||
<div class="settings-section">
|
||||
<h3>安全设置</h3>
|
||||
<div class="settings-form">
|
||||
<div class="form-group">
|
||||
<label for="auth-current-password">当前密码</label>
|
||||
<input type="password" id="auth-current-password" placeholder="输入当前登录密码" autocomplete="current-password" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="auth-new-password">新密码</label>
|
||||
<input type="password" id="auth-new-password" placeholder="设置新密码(至少 8 位)" autocomplete="new-password" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="auth-confirm-password">确认新密码</label>
|
||||
<input type="password" id="auth-confirm-password" placeholder="再次输入新密码" autocomplete="new-password" />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn-secondary" type="button" onclick="resetPasswordForm()">清空</button>
|
||||
<button class="btn-primary change-password-submit" type="button" onclick="changePassword()">修改密码</button>
|
||||
</div>
|
||||
<p class="password-hint">修改密码后,需要使用新密码重新登录。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-secondary" onclick="closeSettings()">取消</button>
|
||||
|
||||
Reference in New Issue
Block a user