为 Web UI 添加完整的登录验证系统,遵循 CQRS 架构模式。 主要功能: - 创建登录页面 UI (web/login.html) - 美观的渐变背景设计 - 密钥输入和验证 - 错误提示和加载状态 - 实现登录验证 (遵循 CQRS) - 新增 LoginQuery 和 LoginHandler (internal/api/handlers/auth_handlers.go) - 新增 AuthEndpoint (internal/api/endpoints/auth_endpoints.go) - 注册登录接口 /auth/login (无需授权) - 更新主页面 (web/index.html) - 添加登录状态检查 - 未登录显示提示信息 - 所有 API 请求自动携带 X-API-Key 头 - 添加退出登录功能 - 401 错误自动跳转登录页 - 更新路由配置 (cmd/server/main.go) - 添加 /auth/login 公开路由 - 注册登录处理器和端点 - 新增登录文档 (docs/LOGIN_GUIDE.md) - 完整的使用说明 - 技术实现细节 - API 接口说明 安全特性: - 密钥存储在 localStorage - 自动处理登录过期 - 支持主动退出登录 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
213 lines
7.1 KiB
HTML
213 lines
7.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>RustFS - 用户登录</title>
|
|
<!-- Bootstrap CSS -->
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<!-- Font Awesome -->
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
|
<style>
|
|
body {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-family: "Microsoft YaHei", sans-serif;
|
|
}
|
|
.login-card {
|
|
background: white;
|
|
border-radius: 20px;
|
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
padding: 40px;
|
|
width: 100%;
|
|
max-width: 450px;
|
|
}
|
|
.login-header {
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
}
|
|
.login-header i {
|
|
font-size: 3rem;
|
|
color: #667eea;
|
|
margin-bottom: 15px;
|
|
}
|
|
.login-header h2 {
|
|
color: #333;
|
|
font-weight: bold;
|
|
margin-bottom: 10px;
|
|
}
|
|
.login-header p {
|
|
color: #666;
|
|
font-size: 0.95rem;
|
|
}
|
|
.form-control {
|
|
border-radius: 10px;
|
|
padding: 12px 15px;
|
|
border: 2px solid #e0e0e0;
|
|
transition: all 0.3s;
|
|
}
|
|
.form-control:focus {
|
|
border-color: #667eea;
|
|
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
|
}
|
|
.input-group-text {
|
|
background: white;
|
|
border: 2px solid #e0e0e0;
|
|
border-right: none;
|
|
border-radius: 10px 0 0 10px;
|
|
}
|
|
.input-group .form-control {
|
|
border-left: none;
|
|
border-radius: 0 10px 10px 0;
|
|
}
|
|
.input-group .form-control:focus {
|
|
border-color: #667eea;
|
|
}
|
|
.btn-login {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
border: none;
|
|
border-radius: 10px;
|
|
padding: 12px;
|
|
font-weight: bold;
|
|
color: white;
|
|
width: 100%;
|
|
transition: transform 0.2s;
|
|
}
|
|
.btn-login:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
|
|
color: white;
|
|
}
|
|
.btn-login:disabled {
|
|
background: #ccc;
|
|
transform: none;
|
|
}
|
|
.alert {
|
|
border-radius: 10px;
|
|
display: none;
|
|
}
|
|
.footer {
|
|
text-align: center;
|
|
margin-top: 25px;
|
|
color: #999;
|
|
font-size: 0.85rem;
|
|
}
|
|
[v-cloak] { display: none; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app" class="container" v-cloak>
|
|
<div class="login-card">
|
|
<div class="login-header">
|
|
<i class="fas fa-cloud"></i>
|
|
<h2>RustFS 文件管理</h2>
|
|
<p>请输入 API 密钥以访问系统</p>
|
|
</div>
|
|
|
|
<div class="alert alert-danger" role="alert" :style="{ display: showError ? 'block' : 'none' }">
|
|
<i class="fas fa-exclamation-circle me-2"></i>{{ errorMessage }}
|
|
</div>
|
|
|
|
<form @submit.prevent="login">
|
|
<div class="mb-4">
|
|
<label class="form-label fw-bold">API 密钥</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text">
|
|
<i class="fas fa-key"></i>
|
|
</span>
|
|
<input
|
|
type="password"
|
|
class="form-control"
|
|
placeholder="请输入 API 密钥"
|
|
v-model="apiKey"
|
|
:disabled="loading"
|
|
required
|
|
autofocus
|
|
>
|
|
</div>
|
|
<div class="form-text text-muted mt-2">
|
|
<i class="fas fa-info-circle me-1"></i>
|
|
密钥格式: xn001624.
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-login" :disabled="loading">
|
|
<span v-if="loading">
|
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
|
登录中...
|
|
</span>
|
|
<span v-else>
|
|
<i class="fas fa-sign-in-alt me-2"></i>登 录
|
|
</span>
|
|
</button>
|
|
</form>
|
|
|
|
<div class="footer">
|
|
<p class="mb-1">Powered by RustFS & Gin</p>
|
|
<a href="/swagger/index.html" target="_blank" class="text-decoration-none">
|
|
<i class="fas fa-book me-1"></i>API 文档
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
<script>
|
|
const { createApp, ref } = Vue;
|
|
|
|
createApp({
|
|
setup() {
|
|
const apiKey = ref('');
|
|
const loading = ref(false);
|
|
const showError = ref(false);
|
|
const errorMessage = ref('');
|
|
|
|
const login = async () => {
|
|
if (!apiKey.value.trim()) {
|
|
showError.value = true;
|
|
errorMessage.value = '请输入 API 密钥';
|
|
return;
|
|
}
|
|
|
|
loading.value = true;
|
|
showError.value = false;
|
|
|
|
try {
|
|
const res = await axios.post('/auth/login', {
|
|
api_key: apiKey.value
|
|
});
|
|
|
|
if (res.data.success) {
|
|
// 保存 token 到 localStorage
|
|
localStorage.setItem('rustfs_token', res.data.token);
|
|
// 跳转到主页面
|
|
window.location.href = '/web/';
|
|
} else {
|
|
showError.value = true;
|
|
errorMessage.value = res.data.message || '登录失败';
|
|
}
|
|
} catch (err) {
|
|
showError.value = true;
|
|
errorMessage.value = err.response?.data?.error || err.message || '登录请求失败';
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
return {
|
|
apiKey,
|
|
loading,
|
|
showError,
|
|
errorMessage,
|
|
login
|
|
};
|
|
}
|
|
}).mount('#app');
|
|
</script>
|
|
</body>
|
|
</html>
|