162 lines
7.7 KiB
TypeScript
162 lines
7.7 KiB
TypeScript
'use client'
|
||
// shared/ui/login-page.tsx — 统一登录页组件
|
||
// 支持 SSO、LDAP 和 localadmin(通过 ?method=local URL 参数激活)三种登录方式
|
||
import { useState, useEffect } from 'react'
|
||
|
||
interface LoginPageProps {
|
||
siteName: string // 站点名称,如 "IT 工单跟踪系统"、"告警监控中心"
|
||
title?: string // 副标题(可选),如 "monitor-ai"
|
||
redirectPath?: string // 登录后跳转路径,默认 "/"
|
||
}
|
||
|
||
export default function LoginPage({ siteName, title, redirectPath = '/' }: LoginPageProps) {
|
||
const [username, setUsername] = useState('')
|
||
const [password, setPassword] = useState('')
|
||
const [error, setError] = useState('')
|
||
const [loading, setLoading] = useState(false)
|
||
const [showLdapForm, setShowLdapForm] = useState(false)
|
||
const [showLocalAdmin, setShowLocalAdmin] = useState(false)
|
||
|
||
useEffect(() => {
|
||
const params = new URLSearchParams(window.location.search)
|
||
if (params.get('method') === 'local') setShowLocalAdmin(true)
|
||
}, [])
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault(); setError(''); setLoading(true)
|
||
try {
|
||
const res = await fetch('/api/auth/login', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ username, password })
|
||
})
|
||
const data = await res.json()
|
||
if (!res.ok) { setError(data.error || '登录失败'); return }
|
||
const params = new URLSearchParams(window.location.search)
|
||
const redirect = params.get('redirect')
|
||
const dest = (redirect && redirect.startsWith('/')) ? redirect : redirectPath
|
||
window.location.href = dest
|
||
} catch { setError('网络错误') } finally { setLoading(false) }
|
||
}
|
||
|
||
function handleSsoLogin() {
|
||
window.location.href = '/api/auth/login/oidc'
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center bg-slate-50 dark:bg-slate-950 px-4">
|
||
<div className="w-full max-w-sm bg-white dark:bg-slate-900 rounded-lg border border-blue-200/50 dark:border-blue-500/20 shadow-lg p-8">
|
||
<h1 className="text-2xl font-bold text-center mb-1 text-slate-900 dark:text-white">{siteName}</h1>
|
||
{title && <p className="text-center text-xs text-slate-400 mb-5">{title}</p>}
|
||
{!title && <div className="mb-5" />}
|
||
|
||
{error && (
|
||
<div className="mb-4 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-600 dark:text-red-400 text-sm">{error}</div>
|
||
)}
|
||
|
||
{!showLdapForm && !showLocalAdmin ? (
|
||
<>
|
||
<button
|
||
onClick={handleSsoLogin}
|
||
className="w-full py-2 px-4 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-medium transition-colors duration-200 mb-3"
|
||
>
|
||
统一认证登录
|
||
</button>
|
||
<p className="text-center text-xs text-slate-400 mb-3">通过 SSO 统一身份认证</p>
|
||
<p className="text-center mb-2">
|
||
<button
|
||
onClick={() => { window.location.href = '/api/auth/login/oidc?switch=1' }}
|
||
className="text-xs text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 underline"
|
||
>
|
||
使用其他账号登录
|
||
</button>
|
||
</p>
|
||
<p className="text-center">
|
||
<button
|
||
onClick={() => setShowLdapForm(true)}
|
||
className="text-xs text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 underline"
|
||
>
|
||
使用 LDAP 直接登录
|
||
</button>
|
||
</p>
|
||
</>
|
||
) : showLocalAdmin ? (
|
||
<>
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">用户名</label>
|
||
<input
|
||
type="text" value={username} onChange={(e) => setUsername(e.target.value)}
|
||
placeholder="请输入用户名"
|
||
className="w-full px-3 py-2 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
required
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">密码</label>
|
||
<input
|
||
type="password" value={password} onChange={(e) => setPassword(e.target.value)}
|
||
placeholder="请输入密码"
|
||
className="w-full px-3 py-2 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
required
|
||
/>
|
||
</div>
|
||
<button
|
||
type="submit" disabled={loading}
|
||
className="w-full py-2 px-4 rounded-lg bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-medium transition-colors duration-200"
|
||
>
|
||
{loading ? '登录中...' : '本地管理员登录'}
|
||
</button>
|
||
</form>
|
||
<p className="text-center mt-3">
|
||
<button
|
||
onClick={() => { window.location.href = '/login' }}
|
||
className="text-xs text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 underline"
|
||
>
|
||
返回普通登录
|
||
</button>
|
||
</p>
|
||
</>
|
||
) : (
|
||
<>
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">用户名</label>
|
||
<input
|
||
type="text" value={username} onChange={(e) => setUsername(e.target.value)}
|
||
placeholder="请输入用户名"
|
||
className="w-full px-3 py-2 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
required
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">密码</label>
|
||
<input
|
||
type="password" value={password} onChange={(e) => setPassword(e.target.value)}
|
||
placeholder="请输入密码"
|
||
className="w-full px-3 py-2 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
required
|
||
/>
|
||
</div>
|
||
<button
|
||
type="submit" disabled={loading}
|
||
className="w-full py-2 px-4 rounded-lg bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-medium transition-colors duration-200"
|
||
>
|
||
{loading ? '登录中...' : '登录'}
|
||
</button>
|
||
</form>
|
||
<p className="text-center mt-3">
|
||
<button
|
||
onClick={() => { setShowLdapForm(false); setError('') }}
|
||
className="text-xs text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 underline"
|
||
>
|
||
返回统一认证登录
|
||
</button>
|
||
</p>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|