mirror of
https://github.com/rustfs/console.git
synced 2026-08-28 19:47:21 +08:00
252 lines
10 KiB
TypeScript
252 lines
10 KiB
TypeScript
"use client"
|
|
|
|
import Link from "next/link"
|
|
import Image from "next/image"
|
|
import { useTranslation } from "react-i18next"
|
|
import { RiSettings3Line } from "@remixicon/react"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
import { Field, FieldContent, FieldLabel } from "@/components/ui/field"
|
|
import { ThemeSwitcher } from "@/components/theme-switcher"
|
|
import { LanguageSwitcher } from "@/components/language-switcher"
|
|
import { ThemeLogo } from "@/components/theme/logo"
|
|
import { AuthHeroStatic } from "@/components/auth/heroes/hero-static"
|
|
import { buildRoute } from "@/lib/routes"
|
|
import { getThemeManifest } from "@/lib/theme/manifest"
|
|
|
|
import type { OidcProvider } from "@/types/config"
|
|
|
|
export type LoginMethod = "accessKeyAndSecretKey" | "sts"
|
|
|
|
export interface LoginFormProps {
|
|
method: LoginMethod
|
|
setMethod: (m: LoginMethod) => void
|
|
accessKeyAndSecretKey: { accessKeyId: string; secretAccessKey: string }
|
|
setAccessKeyAndSecretKey: React.Dispatch<React.SetStateAction<{ accessKeyId: string; secretAccessKey: string }>>
|
|
sts: {
|
|
accessKeyId: string
|
|
secretAccessKey: string
|
|
sessionToken: string
|
|
}
|
|
setSts: React.Dispatch<
|
|
React.SetStateAction<{
|
|
accessKeyId: string
|
|
secretAccessKey: string
|
|
sessionToken: string
|
|
}>
|
|
>
|
|
handleLogin: (e: React.FormEvent) => void
|
|
oidcProviders?: OidcProvider[]
|
|
onOidcLogin?: (providerId: string) => void
|
|
}
|
|
|
|
export function LoginForm({
|
|
method,
|
|
setMethod,
|
|
accessKeyAndSecretKey,
|
|
setAccessKeyAndSecretKey,
|
|
sts,
|
|
setSts,
|
|
handleLogin,
|
|
oidcProviders,
|
|
onOidcLogin,
|
|
}: LoginFormProps) {
|
|
const { t } = useTranslation()
|
|
const theme = getThemeManifest()
|
|
const website = theme.links.website ?? "https://www.rustfs.com"
|
|
|
|
return (
|
|
<div className="relative flex min-h-screen flex-col items-center justify-center bg-gray-100 dark:bg-neutral-800 lg:p-20">
|
|
<Image
|
|
src={buildRoute("/backgrounds/scillate.svg")}
|
|
alt=""
|
|
fill
|
|
className="absolute inset-0 z-0 opacity-45 object-cover"
|
|
/>
|
|
<div className="z-10 mx-auto flex max-h-[85vh] w-full max-w-7xl flex-1 flex-col overflow-hidden rounded-lg shadow-lg dark:border-neutral-700 dark:bg-neutral-800 lg:flex-row">
|
|
<div className="hidden w-1/2 lg:block">
|
|
<AuthHeroStatic />
|
|
</div>
|
|
<div className="relative flex w-full flex-col items-center justify-center bg-white dark:border-neutral-700 dark:bg-neutral-900 lg:w-1/2">
|
|
<div className="absolute end-4 top-4 z-10">
|
|
<Link
|
|
href="/config"
|
|
className="inline-flex h-8 w-8 items-center justify-center rounded-lg border border-gray-200 bg-white text-gray-600 shadow-sm transition-colors hover:bg-gray-50 hover:text-gray-800 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-400 dark:hover:bg-neutral-800 dark:hover:text-white"
|
|
title={t("Server Configuration")}
|
|
>
|
|
<RiSettings3Line className="text-sm" />
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="max-w-sm w-full space-y-6 p-4 sm:p-7">
|
|
<ThemeLogo width={112} height={24} className="max-w-28" />
|
|
|
|
<div className="space-y-4">
|
|
<Tabs value={method} onValueChange={(v) => setMethod(v as LoginMethod)} className="flex flex-col gap-4">
|
|
<TabsList className="w-full">
|
|
<TabsTrigger className="w-1/2" value="accessKeyAndSecretKey">
|
|
{t("Key Login")}
|
|
</TabsTrigger>
|
|
<TabsTrigger className="w-1/2" value="sts">
|
|
{t("STS Login")}
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<form onSubmit={handleLogin} autoComplete="off">
|
|
<div className="grid gap-y-6">
|
|
{method === "accessKeyAndSecretKey" ? (
|
|
<>
|
|
<Field>
|
|
<FieldLabel htmlFor="accessKey">{t("Account")}</FieldLabel>
|
|
<FieldContent>
|
|
<Input
|
|
id="accessKey"
|
|
value={accessKeyAndSecretKey.accessKeyId}
|
|
onChange={(e) =>
|
|
setAccessKeyAndSecretKey((prev) => ({
|
|
...prev,
|
|
accessKeyId: e.target.value,
|
|
}))
|
|
}
|
|
autoComplete="username"
|
|
type="text"
|
|
placeholder={t("Please enter account")}
|
|
/>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel htmlFor="secretKey">{t("Key")}</FieldLabel>
|
|
<FieldContent>
|
|
<Input
|
|
id="secretKey"
|
|
value={accessKeyAndSecretKey.secretAccessKey}
|
|
onChange={(e) =>
|
|
setAccessKeyAndSecretKey((prev) => ({
|
|
...prev,
|
|
secretAccessKey: e.target.value,
|
|
}))
|
|
}
|
|
autoComplete="current-password"
|
|
type="password"
|
|
placeholder={t("Please enter key")}
|
|
/>
|
|
</FieldContent>
|
|
</Field>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Field>
|
|
<FieldLabel htmlFor="stsAccessKey">{t("STS Username")}</FieldLabel>
|
|
<FieldContent>
|
|
<Input
|
|
id="stsAccessKey"
|
|
value={sts.accessKeyId}
|
|
onChange={(e) =>
|
|
setSts((prev) => ({
|
|
...prev,
|
|
accessKeyId: e.target.value,
|
|
}))
|
|
}
|
|
autoComplete="new-password"
|
|
type="text"
|
|
placeholder={t("Please enter STS username")}
|
|
/>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel htmlFor="stsSecretKey">{t("STS Key")}</FieldLabel>
|
|
<FieldContent>
|
|
<Input
|
|
id="stsSecretKey"
|
|
value={sts.secretAccessKey}
|
|
onChange={(e) =>
|
|
setSts((prev) => ({
|
|
...prev,
|
|
secretAccessKey: e.target.value,
|
|
}))
|
|
}
|
|
autoComplete="new-password"
|
|
type="password"
|
|
placeholder={t("Please enter STS key")}
|
|
/>
|
|
</FieldContent>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel htmlFor="sessionToken">{t("STS Session Token")}</FieldLabel>
|
|
<FieldContent>
|
|
<Input
|
|
id="sessionToken"
|
|
value={sts.sessionToken}
|
|
onChange={(e) =>
|
|
setSts((prev) => ({
|
|
...prev,
|
|
sessionToken: e.target.value,
|
|
}))
|
|
}
|
|
autoComplete="new-password"
|
|
type="text"
|
|
placeholder={t("Please enter STS session token")}
|
|
/>
|
|
</FieldContent>
|
|
</Field>
|
|
</>
|
|
)}
|
|
|
|
<Button type="submit" variant="default" className="w-full justify-center">
|
|
{t("Login")}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Tabs>
|
|
</div>
|
|
|
|
{oidcProviders && oidcProviders.length > 0 && onOidcLogin && (
|
|
<div className="space-y-3">
|
|
<div className="relative flex items-center">
|
|
<div className="flex-grow border-t border-gray-200 dark:border-neutral-700" />
|
|
<span className="mx-3 flex-shrink text-xs text-gray-500 dark:text-neutral-500">
|
|
{t("Or continue with")}
|
|
</span>
|
|
<div className="flex-grow border-t border-gray-200 dark:border-neutral-700" />
|
|
</div>
|
|
<div className="grid gap-2">
|
|
{oidcProviders.map((provider) => (
|
|
<Button
|
|
key={provider.provider_id}
|
|
type="button"
|
|
variant="outline"
|
|
className="w-full justify-center"
|
|
onClick={() => onOidcLogin(provider.provider_id)}
|
|
>
|
|
{t("Login with {name}", { name: provider.display_name })}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<p className="text-sm text-gray-600 dark:text-neutral-400">
|
|
{t("Login Problems?")}{" "}
|
|
<Link href={website} className="text-blue-600 hover:underline">
|
|
{t("Get Help")}
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mx-auto flex w-1/2 items-center justify-around gap-4">
|
|
<div className="inline-flex">
|
|
<ThemeSwitcher />
|
|
</div>
|
|
<div className="inline-flex">
|
|
<LanguageSwitcher />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|