feat: v0.4.0 主题切换 + LLM 全面优化 + 登录会话 + 用户管理/LDAP
主题: - 深色/浅色/跟随系统(useTheme:本地缓存先行+远端同步+matchMedia), 侧边栏快捷切换,设置页三档即选即生效 LLM(参考 opsxdb/aiops): - llm.Client.ChatStream 流式(SSE 增量解析,透传 reasoning_content, ctx 控制超时);POST /api/ai/chat/stream(平台实时数据注入降幻觉, 未配 LLM 降级规则 JSON,指令解析 action 不变) - test-llm 支持草稿直测(保存前验证,apiKey 空/掩码回退已存) - 发布失败自动 AI 分析日志尾部(结论/根因/建议),发布记录与发布 Tab 展示分析卡片 - 设置页 Provider 预设(DeepSeek/GLM/GLM编码/Qwen/Kimi/Ollama/自定义) 自动填 endpoint+模型+获取 Key 直达链接 登录与会话: - HMAC 签名 token(7 天,密钥派生自 encryption.key); authMiddleware 保护 /api/*(豁免登录/健康/SSE日志流/入站webhook) - 登录页 /login + 未登录跳转 + 登出;401 自动清理跳登录 用户管理与 LDAP(借鉴 LokiStack): - /users 页面:本地用户 CRUD、一次性密码、角色切换、来源徽章 (本地·蓝 | LDAP导入·紫);LDAP 导入按钮仅在启用且配置齐备时显示 - LDAP 配置卡(未启用仅提示语)、测试连接草稿直测+可选测试账号 - go-ldap:管理 Bind/用户搜索(防注入)/用户 DN 直连验证; 批量导入冲突策略(已LDAP跳过/本地同名转LDAP/新建); 登录分流本地 argon2id 或 LDAP 目录验证 + 首登 JIT 建号
This commit is contained in:
1 parent
555934cc64
commit
97193eb090
24 files changed
+2422
-98
No files matched your search
@@ -0,0 +1,88 @@
|
||||
import { useState } from 'react';
|
||||
import { Rocket, Loader2, KeyRound, User } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { apiClient, auth } from '@/lib/api';
|
||||
|
||||
export default function Login({ onSuccess }: { onSuccess: () => void }) {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const submit = async (e?: React.FormEvent) => {
|
||||
e?.preventDefault();
|
||||
if (!username.trim() || !password) {
|
||||
toast.error('请输入用户名和密码');
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await apiClient.login(username.trim(), password);
|
||||
auth.setToken(res.token);
|
||||
toast.success(`欢迎,${res.nickname || res.username}`);
|
||||
onSuccess();
|
||||
} catch (err) {
|
||||
toast.error('登录失败', { description: (err as Error).message });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const inputStyle = { background: 'var(--panel-bg-secondary)', borderColor: 'var(--border-color-light)', color: 'var(--text-primary)' };
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen flex items-center justify-center" style={{ background: 'var(--canvas-bg)' }}>
|
||||
<div className="w-[380px] rounded-xl border shadow-lg p-8" style={{ background: 'var(--panel-bg)', borderColor: 'var(--border-color)' }}>
|
||||
<div className="flex flex-col items-center mb-6">
|
||||
<div className="w-12 h-12 rounded-xl flex items-center justify-center mb-3" style={{ background: 'linear-gradient(135deg, #0969da, #8250df)' }}>
|
||||
<Rocket className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h1 className="text-[18px] font-bold" style={{ color: 'var(--text-primary)' }}>FlowStack</h1>
|
||||
<p className="text-[12px] mt-1" style={{ color: 'var(--text-tertiary)' }}>发布系统 · 请登录</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={submit} className="space-y-4">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[12px]" style={{ color: 'var(--text-secondary)' }}>用户名</label>
|
||||
<div className="relative">
|
||||
<User className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2" style={{ color: 'var(--text-tertiary)' }} />
|
||||
<input
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoFocus
|
||||
className="w-full pl-9 pr-3 py-2 text-[13px] rounded-md border outline-none"
|
||||
style={inputStyle}
|
||||
placeholder="admin"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[12px]" style={{ color: 'var(--text-secondary)' }}>密码</label>
|
||||
<div className="relative">
|
||||
<KeyRound className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2" style={{ color: 'var(--text-tertiary)' }} />
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full pl-9 pr-3 py-2 text-[13px] rounded-md border outline-none"
|
||||
style={inputStyle}
|
||||
placeholder="初始密码见 data/initial-admin-password.txt"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full py-2.5 text-[13px] font-medium rounded-md text-white disabled:opacity-60 flex items-center justify-center gap-2"
|
||||
style={{ background: 'var(--accent-blue)' }}
|
||||
>
|
||||
{loading && <Loader2 className="w-4 h-4 animate-spin" />}
|
||||
登 录
|
||||
</button>
|
||||
</form>
|
||||
<p className="text-[11px] text-center mt-4" style={{ color: 'var(--text-tertiary)' }}>
|
||||
支持本地账号与 LDAP 账号(启用后自动识别)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user