file-system/web/login.html
root 54f66c56ed feat: 添加 Web UI 登录功能
为 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>
2026-01-05 20:27:45 +08:00

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>