mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-05-06 05:38:51 +00:00
feat: oauth2 登录
This commit is contained in:
+29
-1
@@ -1,3 +1,4 @@
|
||||
import { getOauth2RedirectURL, Oauth2RequestType } from "@/api/oauth2"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Form,
|
||||
@@ -9,10 +10,13 @@ import {
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { useAuth } from "@/hooks/useAuth"
|
||||
import useSetting from "@/hooks/useSetting"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import i18next from "i18next"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
import { z } from "zod"
|
||||
|
||||
const formSchema = z.object({
|
||||
@@ -25,7 +29,17 @@ const formSchema = z.object({
|
||||
})
|
||||
|
||||
function Login() {
|
||||
const { login } = useAuth()
|
||||
const { login, loginOauth2 } = useAuth()
|
||||
const { data: settingData } = useSetting()
|
||||
|
||||
useEffect(() => {
|
||||
const oauth2Code = new URLSearchParams(window.location.search).get("code")
|
||||
const oauth2State = new URLSearchParams(window.location.search).get("state")
|
||||
const oauth2Provider = new URLSearchParams(window.location.search).get("provider")
|
||||
if (oauth2Code && oauth2State && oauth2Provider) {
|
||||
loginOauth2(oauth2Provider, oauth2State, oauth2Code)
|
||||
}
|
||||
}, [window.location.search])
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@@ -39,6 +53,15 @@ function Login() {
|
||||
login(values.username, values.password)
|
||||
}
|
||||
|
||||
async function loginWith(provider: string) {
|
||||
try {
|
||||
const redirectUrl = await getOauth2RedirectURL(provider, Oauth2RequestType.LOGIN)
|
||||
window.location.href = redirectUrl.redirect!
|
||||
} catch (error: any) {
|
||||
toast.error(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
@@ -79,6 +102,11 @@ function Login() {
|
||||
<Button type="submit">{t("Login")}</Button>
|
||||
</form>
|
||||
</Form>
|
||||
<div className="mt-4">
|
||||
{settingData?.config?.oauth2_providers?.map((p: string) =>
|
||||
<Button onClick={() => loginWith(p)}>{p}</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+63
-1
@@ -1,16 +1,62 @@
|
||||
import { bindOauth2, getOauth2RedirectURL, Oauth2RequestType, unbindOauth2 } from "@/api/oauth2"
|
||||
import { getProfile } from "@/api/user"
|
||||
import { ProfileCard } from "@/components/profile"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { useMainStore } from "@/hooks/useMainStore"
|
||||
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
||||
import { useServer } from "@/hooks/useServer"
|
||||
import useSetting from "@/hooks/useSetting"
|
||||
import { Boxes, Server } from "lucide-react"
|
||||
import { useEffect } from "react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
export default function ProfilePage() {
|
||||
const { profile } = useMainStore()
|
||||
const { profile, setProfile } = useMainStore()
|
||||
const { servers, serverGroups } = useServer()
|
||||
const { data: settingData } = useSetting()
|
||||
const isDesktop = useMediaQuery("(min-width: 890px)")
|
||||
|
||||
useEffect(() => {
|
||||
const oauth2Code = new URLSearchParams(window.location.search).get("code")
|
||||
const oauth2State = new URLSearchParams(window.location.search).get("state")
|
||||
const oauth2Provider = new URLSearchParams(window.location.search).get("provider")
|
||||
if (oauth2Code && oauth2State && oauth2Provider) {
|
||||
bindOauth2(oauth2Provider, oauth2State, oauth2Code)
|
||||
.catch((error) => {
|
||||
toast.error(error.message)
|
||||
})
|
||||
.then(() => {
|
||||
getProfile().then((profile) => {
|
||||
setProfile(profile)
|
||||
})
|
||||
}).finally(() => {
|
||||
window.history.replaceState({}, document.title, window.location.pathname)
|
||||
})
|
||||
}
|
||||
}, [window.location.search])
|
||||
|
||||
const bindO2 = async (provider: string) => {
|
||||
try {
|
||||
const redirectUrl = await getOauth2RedirectURL(provider, Oauth2RequestType.BIND)
|
||||
window.location.href = redirectUrl.redirect!
|
||||
} catch (error: any) {
|
||||
toast.error(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
const unbindO2 = async (provider: string) => {
|
||||
try {
|
||||
await unbindOauth2(provider)
|
||||
getProfile().then((profile) => {
|
||||
setProfile(profile)
|
||||
})
|
||||
} catch (error: any) {
|
||||
toast.error(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
profile && (
|
||||
<div className={`flex p-8 gap-4 ${isDesktop ? "ml-6" : "flex-col"}`}>
|
||||
@@ -62,6 +108,22 @@ export default function ProfilePage() {
|
||||
{serverGroups?.length || 0}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex gap-2 text-xl items-center">
|
||||
<Boxes /> Oauth2 bindings
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-lg font-semibold">
|
||||
{settingData?.config?.oauth2_providers?.map((provider) => <div>
|
||||
{provider}: {profile.oauth2_bind?.[provider.toLowerCase()]} {profile.oauth2_bind?.[provider.toLowerCase()] ?
|
||||
<Button size="sm" onClick={() => unbindO2(provider)}>Unbind</Button>
|
||||
:
|
||||
<Button size="sm" onClick={() => bindO2(provider)}>Bind</Button>}
|
||||
</div>)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+7
-7
@@ -13,14 +13,14 @@ export default function Root() {
|
||||
const { data: settingData, error } = useSetting()
|
||||
|
||||
useEffect(() => {
|
||||
document.title = settingData?.site_name || "哪吒监控 Nezha Monitoring"
|
||||
}, [settingData?.site_name])
|
||||
document.title = settingData?.config?.site_name || "哪吒监控 Nezha Monitoring"
|
||||
}, [settingData?.config?.site_name])
|
||||
|
||||
useEffect(() => {
|
||||
if (settingData?.custom_code_dashboard) {
|
||||
InjectContext(settingData?.custom_code_dashboard)
|
||||
if (settingData?.config?.custom_code_dashboard) {
|
||||
InjectContext(settingData?.config?.custom_code_dashboard)
|
||||
}
|
||||
}, [settingData?.custom_code_dashboard])
|
||||
}, [settingData?.config?.custom_code_dashboard])
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
@@ -30,8 +30,8 @@ export default function Root() {
|
||||
return null
|
||||
}
|
||||
|
||||
if (settingData?.language && !localStorage.getItem("language")) {
|
||||
i18n.changeLanguage(settingData?.language)
|
||||
if (settingData?.config?.language && !localStorage.getItem("language")) {
|
||||
i18n.changeLanguage(settingData?.config?.language)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+27
-27
@@ -67,31 +67,31 @@ export default function SettingsPage() {
|
||||
resolver: zodResolver(settingFormSchema),
|
||||
defaultValues: config
|
||||
? {
|
||||
...config,
|
||||
language: config.language,
|
||||
site_name: config.site_name || "",
|
||||
user_template:
|
||||
config.user_template ||
|
||||
Object.keys(config.frontend_templates?.filter((t) => !t.is_admin) || {})[0] ||
|
||||
"user-dist",
|
||||
}
|
||||
...config,
|
||||
language: config?.config?.language,
|
||||
site_name: config.config?.site_name || "",
|
||||
user_template:
|
||||
config.config?.user_template ||
|
||||
Object.keys(config.frontend_templates?.filter((t) => !t.is_admin) || {})[0] ||
|
||||
"user-dist",
|
||||
}
|
||||
: {
|
||||
ip_change_notification_group_id: 0,
|
||||
cover: 1,
|
||||
site_name: "",
|
||||
language: "",
|
||||
user_template: "user-dist",
|
||||
},
|
||||
ip_change_notification_group_id: 0,
|
||||
cover: 1,
|
||||
site_name: "",
|
||||
language: "",
|
||||
user_template: "user-dist",
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
form.reset(config)
|
||||
if (config?.config) {
|
||||
form.reset(config?.config)
|
||||
}
|
||||
}, [config, form])
|
||||
}, [config?.config, form])
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof settingFormSchema>) => {
|
||||
try {
|
||||
@@ -172,7 +172,7 @@ export default function SettingsPage() {
|
||||
(t) => t.path === value,
|
||||
)
|
||||
if (template) {
|
||||
form.setValue("user_template", template.path)
|
||||
form.setValue("user_template", template!.path!)
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -188,7 +188,7 @@ export default function SettingsPage() {
|
||||
) || []
|
||||
).map((template) => (
|
||||
<div key={template.path}>
|
||||
<SelectItem value={template.path}>
|
||||
<SelectItem value={template.path!}>
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<div className="font-medium">
|
||||
{template.name}
|
||||
@@ -229,15 +229,15 @@ export default function SettingsPage() {
|
||||
{!config?.frontend_templates?.find(
|
||||
(t) => t.path === field.value,
|
||||
)?.is_official && (
|
||||
<div className="mt-2 text-sm text-yellow-700 dark:text-yellow-200 bg-yellow-100 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-700 rounded-md p-2">
|
||||
<div className="font-medium text-lg mb-1">
|
||||
{t("CommunityThemeWarning")}
|
||||
<div className="mt-2 text-sm text-yellow-700 dark:text-yellow-200 bg-yellow-100 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-700 rounded-md p-2">
|
||||
<div className="font-medium text-lg mb-1">
|
||||
{t("CommunityThemeWarning")}
|
||||
</div>
|
||||
<div className="text-yellow-700 dark:text-yellow-200">
|
||||
{t("CommunityThemeDescription")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-yellow-700 dark:text-yellow-200">
|
||||
{t("CommunityThemeDescription")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user