mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
Adjust styles to match the design system (#11118)
### What problem does this PR solve? - Modify and adjust styles (CSS vars, components) to match the design system - Adjust file and directory structure of admin UI ### Type of change - [x] Refactoring
This commit is contained in:
@ -2,6 +2,9 @@ import { cn } from '@/lib/utils';
|
|||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { useIsDarkTheme } from '../theme-provider';
|
import { useIsDarkTheme } from '../theme-provider';
|
||||||
|
|
||||||
|
import noDataIcon from './no data bri.svg';
|
||||||
|
import noDataIconDark from './no data.svg';
|
||||||
|
|
||||||
type EmptyProps = {
|
type EmptyProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@ -10,6 +13,14 @@ type EmptyProps = {
|
|||||||
const EmptyIcon = () => {
|
const EmptyIcon = () => {
|
||||||
const isDarkTheme = useIsDarkTheme();
|
const isDarkTheme = useIsDarkTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
className="h-20"
|
||||||
|
src={isDarkTheme ? noDataIconDark : noDataIcon}
|
||||||
|
alt={t('common.noData')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width="184"
|
width="184"
|
||||||
@ -67,13 +78,14 @@ const Empty = (props: EmptyProps) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col justify-center items-center text-center gap-3',
|
'flex flex-col justify-center items-center text-center gap-2',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<EmptyIcon />
|
<EmptyIcon />
|
||||||
|
|
||||||
{!children && (
|
{!children && (
|
||||||
<div className="empty-text mt-4 text-text-secondary">
|
<div className="empty-text text-text-secondary">
|
||||||
{t('common.noData')}
|
{t('common.noData')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
24
web/src/components/empty/no data bri.svg
Normal file
24
web/src/components/empty/no data bri.svg
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<svg width="42" height="52" viewBox="0 0 42 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M11.175 16.22L6 30V42C6 43.0609 6.31607 44.0783 6.87868 44.8284C7.44129 45.5786 8.20435 46 9 46H33C33.7956 46 34.5587 45.5786 35.1213 44.8284C35.6839 44.0783 36 43.0609 36 42V30L30.825 16.22C30.5766 15.5536 30.1938 14.9927 29.7194 14.6006C29.2451 14.2084 28.6981 14.0004 28.14 14H13.86C13.3019 14.0004 12.7549 14.2084 12.2806 14.6006C11.8062 14.9927 11.4234 15.5536 11.175 16.22Z" stroke="url(#paint0_linear_493_44259)" stroke-opacity="0.2" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.175 8.22L6 22V34C6 35.0609 6.31607 36.0783 6.87868 36.8284C7.44129 37.5786 8.20435 38 9 38H33C33.7957 38 34.5587 37.5786 35.1213 36.8284C35.6839 36.0783 36 35.0609 36 34V22L30.825 8.22C30.5766 7.55357 30.1938 6.99274 29.7194 6.60056C29.2451 6.20838 28.6981 6.00039 28.14 6H13.86C13.3019 6.00039 12.7549 6.20838 12.2806 6.60056C11.8062 6.99274 11.4234 7.55357 11.175 8.22Z" fill="white" stroke="url(#paint1_linear_493_44259)" stroke-opacity="0.8" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.175 12.22L6 26V38C6 39.0609 6.31607 40.0783 6.87868 40.8284C7.44129 41.5786 8.20435 42 9 42H33C33.7957 42 34.5587 41.5786 35.1213 40.8284C35.6839 40.0783 36 39.0609 36 38V26L30.825 12.22C30.5766 11.5536 30.1938 10.9927 29.7194 10.6006C29.2451 10.2084 28.6981 10.0004 28.14 10H13.86C13.3019 10.0004 12.7549 10.2084 12.2806 10.6006C11.8062 10.9927 11.4234 11.5536 11.175 12.22Z" fill="white" stroke="url(#paint2_linear_493_44259)" stroke-opacity="0.5" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M36 22.1641H27L24 28.1641H18L15 22.1641H6M36 22.1641V34.1641C36 35.2249 35.6839 36.2423 35.1213 36.9925C34.5587 37.7426 33.7956 38.1641 33 38.1641H9C8.20435 38.1641 7.44129 37.7426 6.87868 36.9925C6.31607 36.2423 6 35.2249 6 34.1641V22.1641M36 22.1641L30.825 8.38406C30.5766 7.71764 30.1938 7.1568 29.7194 6.76462C29.2451 6.37244 28.6981 6.16446 28.14 6.16406H13.86C13.3019 6.16446 12.7549 6.37244 12.2806 6.76462C11.8062 7.1568 11.4234 7.71764 11.175 8.38406L6 22.1641" stroke="url(#paint3_linear_493_44259)" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_493_44259" x1="21" y1="14" x2="9.71922" y2="50.3541" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.195167" stop-color="#161618" stop-opacity="0"/>
|
||||||
|
<stop offset="1" stop-color="#C2C2C2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear_493_44259" x1="21" y1="6" x2="9.71922" y2="42.3541" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.195167" stop-color="white" stop-opacity="0"/>
|
||||||
|
<stop offset="1" stop-color="#C2C2C2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear_493_44259" x1="21" y1="10" x2="9.71922" y2="46.3541" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.195167" stop-color="#161618" stop-opacity="0"/>
|
||||||
|
<stop offset="1" stop-color="#C2C2C2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint3_linear_493_44259" x1="21" y1="6.16406" x2="21" y2="38.1641" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#161618"/>
|
||||||
|
<stop offset="1" stop-color="#7B7B7C"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
24
web/src/components/empty/no data.svg
Normal file
24
web/src/components/empty/no data.svg
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<svg width="42" height="52" viewBox="0 0 42 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M11.175 16.22L6 30V42C6 43.0609 6.31607 44.0783 6.87868 44.8284C7.44129 45.5786 8.20435 46 9 46H33C33.7956 46 34.5587 45.5786 35.1213 44.8284C35.6839 44.0783 36 43.0609 36 42V30L30.825 16.22C30.5766 15.5536 30.1938 14.9927 29.7194 14.6006C29.2451 14.2084 28.6981 14.0004 28.14 14H13.86C13.3019 14.0004 12.7549 14.2084 12.2806 14.6006C11.8062 14.9927 11.4234 15.5536 11.175 16.22Z" stroke="url(#paint0_linear_486_6708)" stroke-opacity="0.2" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.175 8.22L6 22V34C6 35.0609 6.31607 36.0783 6.87868 36.8284C7.44129 37.5786 8.20435 38 9 38H33C33.7957 38 34.5587 37.5786 35.1213 36.8284C35.6839 36.0783 36 35.0609 36 34V22L30.825 8.22C30.5766 7.55357 30.1938 6.99274 29.7194 6.60056C29.2451 6.20838 28.6981 6.00039 28.14 6H13.86C13.3019 6.00039 12.7549 6.20838 12.2806 6.60056C11.8062 6.99274 11.4234 7.55357 11.175 8.22Z" fill="#222224" stroke="url(#paint1_linear_486_6708)" stroke-opacity="0.8" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.175 12.22L6 26V38C6 39.0609 6.31607 40.0783 6.87868 40.8284C7.44129 41.5786 8.20435 42 9 42H33C33.7957 42 34.5587 41.5786 35.1213 40.8284C35.6839 40.0783 36 39.0609 36 38V26L30.825 12.22C30.5766 11.5536 30.1938 10.9927 29.7194 10.6006C29.2451 10.2084 28.6981 10.0004 28.14 10H13.86C13.3019 10.0004 12.7549 10.2084 12.2806 10.6006C11.8062 10.9927 11.4234 11.5536 11.175 12.22Z" fill="#222224" stroke="url(#paint2_linear_486_6708)" stroke-opacity="0.5" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M36 22.1641H27L24 28.1641H18L15 22.1641H6M36 22.1641V34.1641C36 35.2249 35.6839 36.2423 35.1213 36.9925C34.5587 37.7426 33.7956 38.1641 33 38.1641H9C8.20435 38.1641 7.44129 37.7426 6.87868 36.9925C6.31607 36.2423 6 35.2249 6 34.1641V22.1641M36 22.1641L30.825 8.38406C30.5766 7.71764 30.1938 7.1568 29.7194 6.76462C29.2451 6.37244 28.6981 6.16446 28.14 6.16406H13.86C13.3019 6.16446 12.7549 6.37244 12.2806 6.76462C11.8062 7.1568 11.4234 7.71764 11.175 8.38406L6 22.1641" stroke="url(#paint3_linear_486_6708)" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_486_6708" x1="21" y1="14" x2="9.71922" y2="50.3541" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.195167" stop-color="white" stop-opacity="0"/>
|
||||||
|
<stop offset="1" stop-color="#C2C2C2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear_486_6708" x1="21" y1="6" x2="9.71922" y2="42.3541" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.195167" stop-color="white" stop-opacity="0"/>
|
||||||
|
<stop offset="1" stop-color="#C2C2C2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear_486_6708" x1="21" y1="10" x2="9.71922" y2="46.3541" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.195167" stop-color="white" stop-opacity="0"/>
|
||||||
|
<stop offset="1" stop-color="#C2C2C2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint3_linear_486_6708" x1="21" y1="6.16406" x2="21" y2="38.1641" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white"/>
|
||||||
|
<stop offset="1" stop-color="#7B7B7C"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
@ -1,4 +1,5 @@
|
|||||||
import { useIsDarkTheme } from '@/components/theme-provider';
|
import { useIsDarkTheme } from '@/components/theme-provider';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
import { parseColorToRGB } from '@/utils/common-util';
|
import { parseColorToRGB } from '@/utils/common-util';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ const Spotlight: React.FC<SpotlightProps> = ({
|
|||||||
: '194, 221, 243';
|
: '194, 221, 243';
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`absolute inset-0 opacity-80 ${className} rounded-lg`}
|
className={cn('absolute inset-0 opacity-80 rounded-lg', className)}
|
||||||
style={{
|
style={{
|
||||||
backdropFilter: 'blur(30px)',
|
backdropFilter: 'blur(30px)',
|
||||||
zIndex: -1,
|
zIndex: -1,
|
||||||
|
|||||||
@ -39,7 +39,7 @@ const AvatarFallback = React.forwardRef<
|
|||||||
<AvatarPrimitive.Fallback
|
<AvatarPrimitive.Fallback
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-full w-full items-center justify-center rounded-full bg-muted',
|
'flex h-full w-full items-center justify-center rounded-full bg-bg-member',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -4,16 +4,18 @@ import * as React from 'react';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors outline-none focus:outline-none',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
'border-transparent bg-bg-primary text-text-primary hover:bg-primary/80',
|
||||||
secondary:
|
secondary:
|
||||||
'border-transparent bg-bg-card text-text-sub-title-invert hover:bg-secondary/80 rounded-md',
|
'border-transparent bg-bg-card text-text-secondary rounded-md',
|
||||||
|
success:
|
||||||
|
'border-transparent bg-state-success/5 text-state-success rounded-md',
|
||||||
destructive:
|
destructive:
|
||||||
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
'border-transparent bg-state-error/5 text-state-error rounded-md',
|
||||||
outline: 'text-foreground',
|
outline: 'text-foreground',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,28 +3,56 @@ import { cva, type VariantProps } from 'class-variance-authority';
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Loader2, Plus } from 'lucide-react';
|
import { LucideLoader2, Plus } from 'lucide-react';
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
cn(
|
||||||
|
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-0',
|
||||||
|
'disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 shrink-0 [&_svg]:shrink-0',
|
||||||
|
),
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
'bg-text-primary text-bg-base shadow-xs hover:bg-text-primary/90 focus-visible:bg-text-primary/90',
|
||||||
destructive:
|
|
||||||
'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
destructive: `
|
||||||
outline:
|
bg-state-error text-white shadow-xs
|
||||||
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
hover:bg-state-error/90 focus-visible:ring-state-error/20 dark:focus-visible:ring-state-error/40
|
||||||
|
`,
|
||||||
|
outline: `
|
||||||
|
text-text-secondary bg-bg-input border-0.5 border-border-button
|
||||||
|
hover:text-text-primary hover:bg-border-button hover:border-border-default
|
||||||
|
focus-visible:text-text-primary focus-visible:bg-border-button focus-visible:border-border-button
|
||||||
|
`,
|
||||||
secondary:
|
secondary:
|
||||||
'bg-bg-input text-text-primary shadow-xs hover:bg-bg-input/80 border border-border-button',
|
'bg-bg-input text-text-primary shadow-xs hover:bg-bg-input/80 border border-border-button',
|
||||||
ghost:
|
|
||||||
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
ghost: `
|
||||||
|
text-text-secondary
|
||||||
|
hover:bg-border-button hover:text-text-primary
|
||||||
|
focus-visible:text-text-primary focus-visible:bg-border-button
|
||||||
|
`,
|
||||||
|
|
||||||
link: 'text-primary underline-offset-4 hover:underline',
|
link: 'text-primary underline-offset-4 hover:underline',
|
||||||
icon: 'bg-colors-background-inverse-standard text-foreground hover:bg-colors-background-inverse-standard/80',
|
icon: 'bg-colors-background-inverse-standard text-foreground hover:bg-colors-background-inverse-standard/80',
|
||||||
dashed: 'border border-dashed border-input hover:bg-accent',
|
dashed: 'border border-dashed border-input hover:bg-accent',
|
||||||
transparent: 'bg-transparent hover:bg-accent border',
|
|
||||||
danger: 'bg-transparent border border-state-error text-state-error',
|
transparent: `
|
||||||
|
text-text-secondary bg-transparent border-0.5 border-border-button
|
||||||
|
hover:text-text-primary hover:bg-border-button
|
||||||
|
focus-visible:text-text-primary focus-visible:bg-border-button focus-visible:border-border-button
|
||||||
|
`,
|
||||||
|
|
||||||
|
danger: `
|
||||||
|
bg-transparent border border-state-error text-state-error
|
||||||
|
hover:bg-state-error/10 focus-visible:bg-state-error/10
|
||||||
|
`,
|
||||||
|
|
||||||
|
highlighted: `
|
||||||
|
bg-text-primary text-bg-base border-b-4 border-b-accent-primary
|
||||||
|
hover:bg-text-primary/90 focus-visible:bg-text-primary/90
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: 'h-8 px-2.5 py-1.5 ',
|
default: 'h-8 px-2.5 py-1.5 ',
|
||||||
@ -45,46 +73,48 @@ export interface ButtonProps
|
|||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
VariantProps<typeof buttonVariants> {
|
VariantProps<typeof buttonVariants> {
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
block?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
(
|
||||||
|
{
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
asChild = false,
|
||||||
|
loading = false,
|
||||||
|
disabled = false,
|
||||||
|
block = false,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
const Comp = asChild ? Slot : 'button';
|
const Comp = asChild ? Slot : 'button';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-bg-card',
|
'bg-bg-card',
|
||||||
|
{ 'block w-full': block },
|
||||||
buttonVariants({ variant, size, className }),
|
buttonVariants({ variant, size, className }),
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
disabled={loading || disabled}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
{loading && <LucideLoader2 className="animate-spin" />}
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Button.displayName = 'Button';
|
Button.displayName = 'Button';
|
||||||
|
|
||||||
export const ButtonLoading = React.forwardRef<
|
export const ButtonLoading = Button;
|
||||||
HTMLButtonElement,
|
|
||||||
Omit<ButtonProps, 'asChild'> & { loading?: boolean }
|
|
||||||
>(
|
|
||||||
(
|
|
||||||
{ className, variant, size, children, loading = false, disabled, ...props },
|
|
||||||
ref,
|
|
||||||
) => {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
disabled={loading || disabled}
|
|
||||||
>
|
|
||||||
{loading && <Loader2 className="animate-spin" />}
|
|
||||||
{children}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
ButtonLoading.displayName = 'ButtonLoading';
|
ButtonLoading.displayName = 'ButtonLoading';
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const Card = React.forwardRef<
|
|||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-lg border-border-default border shadow-sm bg-bg-input',
|
'rounded-lg border-border-button border-0.5 shadow-sm bg-bg-input transition-shadow',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -60,7 +60,11 @@ const CardContent = React.forwardRef<
|
|||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn('p-6 pt-0 transition-shadow', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
));
|
));
|
||||||
CardContent.displayName = 'CardContent';
|
CardContent.displayName = 'CardContent';
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,11 @@ const Checkbox = React.forwardRef<
|
|||||||
<CheckboxPrimitive.Root
|
<CheckboxPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'peer h-3.5 w-3.5 shrink-0 rounded-sm border border-text-secondary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
|
'peer size-4 shrink-0 rounded-sm border border-border-button outline-0 transition-colors bg-bg-component',
|
||||||
|
'hover:border-border-default hover:bg-border-button',
|
||||||
|
'focus-visible:border-border-default focus-visible:bg-border-default',
|
||||||
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
'data-[state=checked]:text-text-primary data-[state=checked]:border-border-default',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -21,7 +25,7 @@ const Checkbox = React.forwardRef<
|
|||||||
<CheckboxPrimitive.Indicator
|
<CheckboxPrimitive.Indicator
|
||||||
className={cn('flex items-center justify-center text-current')}
|
className={cn('flex items-center justify-center text-current')}
|
||||||
>
|
>
|
||||||
<Check className="h-3.5 w-3.5" />
|
<Check className="size-3" />
|
||||||
</CheckboxPrimitive.Indicator>
|
</CheckboxPrimitive.Indicator>
|
||||||
</CheckboxPrimitive.Root>
|
</CheckboxPrimitive.Root>
|
||||||
));
|
));
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
|
|||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Overlay
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
'fixed inset-0 z-50 bg-black/50 backdrop-blur-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -38,13 +38,20 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-xl translate-x-[-50%] translate-y-[-50%] gap-4 border bg-bg-base p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
'outline-0 fixed left-[50%] top-[50%] z-50 grid w-full max-w-xl translate-x-[-50%] translate-y-[-50%] gap-4 border bg-bg-base p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
<DialogPrimitive.Close
|
||||||
|
className="
|
||||||
|
absolute right-4 top-4 p-2 rounded-sm opacity-70 outline-none text-text-secondary transition-colors
|
||||||
|
hover:bg-border-button hover:text-text-primary
|
||||||
|
focus-visible:bg-border-button focus-visible:text-text-primary
|
||||||
|
disabled:pointer-events-none data-[state=open]:bg-bg-accent data-[state=open]:text-muted-foreground
|
||||||
|
"
|
||||||
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
@ -102,7 +109,7 @@ const DialogDescription = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DialogPrimitive.Description
|
<DialogPrimitive.Description
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn('text-sm text-muted-foreground', className)}
|
className={cn('text-sm text-text-primary', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|||||||
@ -65,7 +65,7 @@ const DropdownMenuContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-bg-base p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -97,19 +97,18 @@ const FormLabel = React.forwardRef<
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
}
|
}
|
||||||
>(({ className, tooltip, required = false, ...props }, ref) => {
|
>(({ className, tooltip, required = false, ...props }, ref) => {
|
||||||
const { error, formItemId } = useFormField();
|
const { formItemId } = useFormField();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Label
|
<Label
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(error && 'text-destructive', className, 'flex pb-0.5')}
|
className={cn(className, 'flex pb-0.5')}
|
||||||
htmlFor={formItemId}
|
htmlFor={formItemId}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<section>
|
{required && <span className="text-state-error">*</span>}
|
||||||
{required && <span className="text-destructive">*</span>}
|
{props.children}
|
||||||
{props.children}
|
|
||||||
</section>
|
|
||||||
{tooltip && <FormTooltip tooltip={tooltip}></FormTooltip>}
|
{tooltip && <FormTooltip tooltip={tooltip}></FormTooltip>}
|
||||||
</Label>
|
</Label>
|
||||||
);
|
);
|
||||||
@ -171,7 +170,7 @@ const FormMessage = React.forwardRef<
|
|||||||
<p
|
<p
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formMessageId}
|
id={formMessageId}
|
||||||
className={cn('text-sm font-medium text-destructive', className)}
|
className={cn('text-sm font-medium text-state-error', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{body}
|
{body}
|
||||||
|
|||||||
@ -3,14 +3,17 @@ import * as React from 'react';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Eye, EyeOff, Search } from 'lucide-react';
|
import { Eye, EyeOff, Search } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { Button } from './button';
|
||||||
|
|
||||||
export interface InputProps
|
export interface InputProps
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'prefix'> {
|
||||||
value?: string | number | readonly string[] | undefined;
|
value?: string | number | readonly string[] | undefined;
|
||||||
|
prefix?: React.ReactNode;
|
||||||
|
suffix?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, type, value, onChange, ...props }, ref) => {
|
({ className, type, value, onChange, prefix, suffix, ...props }, ref) => {
|
||||||
const isControlled = value !== undefined;
|
const isControlled = value !== undefined;
|
||||||
const { defaultValue, ...restProps } = props;
|
const { defaultValue, ...restProps } = props;
|
||||||
const inputValue = isControlled ? value : defaultValue;
|
const inputValue = isControlled ? value : defaultValue;
|
||||||
@ -29,99 +32,80 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
onChange?.(e);
|
onChange?.(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
|
||||||
<>
|
const isPasswordInput = type === 'password';
|
||||||
{type !== 'password' && (
|
|
||||||
<input
|
const inputEl = (
|
||||||
type={type === 'password' && showPassword ? 'text' : type}
|
<input
|
||||||
className={cn(
|
ref={ref}
|
||||||
'flex h-8 w-full rounded-md border border-input bg-bg-input px-2 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-text-disabled focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 text-text-primary',
|
type={isPasswordInput && showPassword ? 'text' : type}
|
||||||
className,
|
className={cn(
|
||||||
)}
|
'flex h-8 w-full rounded-md border-0.5 border-input bg-bg-input px-3 py-2 outline-none text-sm text-text-primary',
|
||||||
ref={ref}
|
'file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-text-disabled',
|
||||||
value={inputValue ?? ''}
|
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent-primary',
|
||||||
onChange={handleChange}
|
'disabled:cursor-not-allowed disabled:opacity-50 transition-colors',
|
||||||
{...restProps}
|
{
|
||||||
/>
|
'pl-12': !!prefix,
|
||||||
|
'pr-12': !!suffix || isPasswordInput,
|
||||||
|
'pr-24': !!suffix && isPasswordInput,
|
||||||
|
},
|
||||||
|
className,
|
||||||
)}
|
)}
|
||||||
{type === 'password' && (
|
value={inputValue ?? ''}
|
||||||
<div className="relative w-full">
|
onChange={handleChange}
|
||||||
<input
|
{...restProps}
|
||||||
type={type === 'password' && showPassword ? 'text' : type}
|
/>
|
||||||
className={cn(
|
);
|
||||||
'flex h-8 w-full rounded-md border border-input bg-bg-input px-2 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-text-disabled focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 text-text-primary',
|
|
||||||
className,
|
if (prefix || suffix || isPasswordInput) {
|
||||||
)}
|
return (
|
||||||
ref={ref}
|
<div className="relative">
|
||||||
value={inputValue ?? ''}
|
{prefix && (
|
||||||
onChange={handleChange}
|
<span className="absolute left-0 top-[50%] translate-y-[-50%]">
|
||||||
{...restProps}
|
{prefix}
|
||||||
/>
|
</span>
|
||||||
<button
|
)}
|
||||||
|
{inputEl}
|
||||||
|
{suffix && (
|
||||||
|
<span
|
||||||
|
className={cn('absolute right-0 top-[50%] translate-y-[-50%]', {
|
||||||
|
'right-14': isPasswordInput,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{suffix}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isPasswordInput && (
|
||||||
|
<Button
|
||||||
|
variant="transparent"
|
||||||
type="button"
|
type="button"
|
||||||
className="absolute inset-y-0 right-0 pr-3 flex items-center"
|
className="border-0 absolute right-1 top-[50%] translate-y-[-50%]"
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
>
|
>
|
||||||
{showPassword ? (
|
{showPassword ? (
|
||||||
<EyeOff className="h-4 w-4 text-text-secondary" />
|
<EyeOff className="size-[1em]" />
|
||||||
) : (
|
) : (
|
||||||
<Eye className="h-4 w-4 text-text-secondary" />
|
<Eye className="size-[1em]" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</>
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
return inputEl;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Input.displayName = 'Input';
|
Input.displayName = 'Input';
|
||||||
|
|
||||||
export interface ExpandedInputProps extends Omit<InputProps, 'prefix'> {
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
prefix?: React.ReactNode;
|
export interface ExpandedInputProps extends InputProps {}
|
||||||
suffix?: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ExpandedInput = ({
|
const ExpandedInput = Input;
|
||||||
suffix,
|
|
||||||
prefix,
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: ExpandedInputProps) => {
|
|
||||||
return (
|
|
||||||
<div className="relative">
|
|
||||||
<span
|
|
||||||
className={cn({
|
|
||||||
['absolute left-3 top-[50%] translate-y-[-50%]']: prefix,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{prefix}
|
|
||||||
</span>
|
|
||||||
<Input
|
|
||||||
className={cn(
|
|
||||||
{ 'pr-8': !!suffix, 'pl-8': !!prefix },
|
|
||||||
'bg-bg-base',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
></Input>
|
|
||||||
<span
|
|
||||||
className={cn({
|
|
||||||
['absolute right-3 top-[50%] translate-y-[-50%]']: suffix,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{suffix}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SearchInput = (props: InputProps) => {
|
const SearchInput = (props: InputProps) => {
|
||||||
return (
|
return <Input {...props} prefix={<Search className="ml-3 size-[1em]" />} />;
|
||||||
<ExpandedInput
|
|
||||||
prefix={<Search className="size-3.5" />}
|
|
||||||
{...props}
|
|
||||||
></ExpandedInput>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Value = string | readonly string[] | number | undefined;
|
type Value = string | readonly string[] | number | undefined;
|
||||||
|
|||||||
@ -46,6 +46,7 @@ const PaginationLink = ({
|
|||||||
...props
|
...props
|
||||||
}: PaginationLinkProps) => (
|
}: PaginationLinkProps) => (
|
||||||
<a
|
<a
|
||||||
|
href="#"
|
||||||
aria-current={isActive ? 'page' : undefined}
|
aria-current={isActive ? 'page' : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
'size-8',
|
'size-8',
|
||||||
@ -70,7 +71,7 @@ const PaginationPrevious = ({
|
|||||||
className={cn('gap-1 pl-2.5', className)}
|
className={cn('gap-1 pl-2.5', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="size-4" />
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
);
|
);
|
||||||
PaginationPrevious.displayName = 'PaginationPrevious';
|
PaginationPrevious.displayName = 'PaginationPrevious';
|
||||||
@ -85,7 +86,7 @@ const PaginationNext = ({
|
|||||||
className={cn('gap-1 pr-2.5', className)}
|
className={cn('gap-1 pr-2.5', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRight className="size-4" />
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
);
|
);
|
||||||
PaginationNext.displayName = 'PaginationNext';
|
PaginationNext.displayName = 'PaginationNext';
|
||||||
@ -96,7 +97,7 @@ const PaginationEllipsis = ({
|
|||||||
}: React.ComponentProps<'span'>) => (
|
}: React.ComponentProps<'span'>) => (
|
||||||
<span
|
<span
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className={cn('flex h-9 w-9 items-center justify-center', className)}
|
className={cn('flex items-center justify-center', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
|||||||
@ -39,7 +39,11 @@ const PopoverContent = React.forwardRef<
|
|||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
'z-50 w-72 rounded-md border-0.5 border-border-button bg-bg-base p-4 text-text-primary shadow-lg outline-none',
|
||||||
|
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
||||||
|
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||||
|
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-9',
|
||||||
|
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -27,7 +27,12 @@ function RadioGroupItem({
|
|||||||
<RadioGroupPrimitive.Item
|
<RadioGroupPrimitive.Item
|
||||||
data-slot="radio-group-item"
|
data-slot="radio-group-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
'border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
'text-primary aspect-square size-4 shrink-0 rounded-full',
|
||||||
|
'transition-all outline-none border border-border-button bg-bg-component',
|
||||||
|
'hover:border-border-default hover:bg-border-default',
|
||||||
|
'focus-visible:border-border-default focus-visible:bg-border-default',
|
||||||
|
'aria-[invalid]:border-state-error aria-[invalid]:bg-state-error/5 aria-[invalid]:text-state-error',
|
||||||
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -36,7 +41,7 @@ function RadioGroupItem({
|
|||||||
data-slot="radio-group-indicator"
|
data-slot="radio-group-indicator"
|
||||||
className="relative flex items-center justify-center"
|
className="relative flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
<CircleIcon className="fill-current absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
||||||
</RadioGroupPrimitive.Indicator>
|
</RadioGroupPrimitive.Indicator>
|
||||||
</RadioGroupPrimitive.Item>
|
</RadioGroupPrimitive.Item>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -49,7 +49,7 @@ function Radio({ value, checked, disabled, onChange, children }: RadioProps) {
|
|||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-4 w-4 items-center justify-center rounded-full border border-border transition-colors',
|
'flex h-4 w-4 items-center justify-center rounded-full border border-border transition-colors',
|
||||||
'peer ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
'peer outline-none focus-visible:border-border-button',
|
||||||
isChecked && 'border-primary bg-primary/10',
|
isChecked && 'border-primary bg-primary/10',
|
||||||
mergedDisabled && 'border-muted',
|
mergedDisabled && 'border-muted',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export type RAGFlowPaginationType = {
|
|||||||
|
|
||||||
export function RAGFlowPagination({
|
export function RAGFlowPagination({
|
||||||
current = 1,
|
current = 1,
|
||||||
pageSize = 10,
|
pageSize = 5,
|
||||||
total = 0,
|
total = 0,
|
||||||
onChange,
|
onChange,
|
||||||
showSizeChanger = true,
|
showSizeChanger = true,
|
||||||
@ -172,13 +172,14 @@ export function RAGFlowPagination({
|
|||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
</PaginationContent>
|
</PaginationContent>
|
||||||
</Pagination>
|
</Pagination>
|
||||||
|
|
||||||
{showSizeChanger && (
|
{showSizeChanger && (
|
||||||
<RAGFlowSelect
|
<RAGFlowSelect
|
||||||
options={sizeChangerOptions}
|
options={sizeChangerOptions}
|
||||||
value={currentPageSize}
|
value={currentPageSize}
|
||||||
onChange={handlePageSizeChange}
|
onChange={handlePageSizeChange}
|
||||||
triggerClassName="bg-bg-card"
|
triggerClassName="bg-bg-card border-transparent"
|
||||||
></RAGFlowSelect>
|
/>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -26,7 +26,11 @@ const SelectTrigger = React.forwardRef<
|
|||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-8 w-full items-center bg-bg-input justify-between rounded-md border border-input px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
'flex h-8 w-full items-center bg-bg-input justify-between rounded-md border-0.5 border-border-button',
|
||||||
|
'px-3 py-2 text-sm outline-none transition-colors text-text-secondary placeholder:text-muted-foreground',
|
||||||
|
'hover:text-text-primary hover:bg-border-button',
|
||||||
|
'focus-visible:text-text-primary focus-visible:bg-border-button',
|
||||||
|
'disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -91,7 +95,11 @@ const SelectContent = React.forwardRef<
|
|||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border-0.5 border-border-card bg-bg-base text-popover-foreground shadow-md',
|
||||||
|
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
||||||
|
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||||
|
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
||||||
|
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
position === 'popper' &&
|
position === 'popper' &&
|
||||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||||
className,
|
className,
|
||||||
@ -134,7 +142,8 @@ const SelectItem = React.forwardRef<
|
|||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-2 text-sm outline-none text-text-secondary',
|
||||||
|
'focus:bg-border-button focus:text-text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -11,16 +11,23 @@ const Switch = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SwitchPrimitives.Root
|
<SwitchPrimitives.Root
|
||||||
className={cn(
|
className={cn(
|
||||||
'peer inline-flex h-3.5 w-6 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-accent-primary data-[state=unchecked]:bg-text-sub-title',
|
'group/switch inline-flex h-4 w-7 shrink-0 cursor-pointer items-center rounded-full',
|
||||||
|
'border-2 border-transparent overflow-hidden transition-colors',
|
||||||
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary',
|
||||||
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
'data-[state=checked]:bg-accent-primary data-[state=unchecked]:bg-text-sub-title',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<SwitchPrimitives.Thumb
|
<SwitchPrimitives.Thumb
|
||||||
className={cn(
|
className="
|
||||||
'pointer-events-none block size-3 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-2 data-[state=unchecked]:translate-x-0',
|
pointer-events-none block w-3 h-3 rounded-full bg-white shadow-lg ring-0 transition-all ease-out
|
||||||
)}
|
group-hover/switch:w-4 group-focus-visible/switch:w-4
|
||||||
|
data-[state=checked]:translate-x-3 data-[state=unchecked]:translate-x-0
|
||||||
|
group-hover/switch:data-[state=checked]:translate-x-2 group-focus-visible/switch:data-[state=checked]:translate-x-2
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</SwitchPrimitives.Root>
|
</SwitchPrimitives.Root>
|
||||||
));
|
));
|
||||||
|
|||||||
@ -27,7 +27,10 @@ const TableHeader = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<thead
|
<thead
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn('[&_tr]:border-b top-0 sticky bg-bg-title z-10', className)}
|
className={cn(
|
||||||
|
'[&_tr]:border-b-0.5 top-0 sticky bg-bg-title z-10',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
@ -52,7 +55,7 @@ const TableFooter = React.forwardRef<
|
|||||||
<tfoot
|
<tfoot
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',
|
'border-t-0.5 border-border-button bg-muted/50 font-medium [&>tr]:last:border-b-0',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -67,7 +70,7 @@ const TableRow = React.forwardRef<
|
|||||||
<tr
|
<tr
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'border-b border-border-button transition-colors hover:bg-bg-card data-[state=selected]:bg-bg-card',
|
'border-b-0.5 border-border-button transition-colors hover:bg-bg-card data-[state=selected]:bg-bg-card',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -82,7 +85,7 @@ const TableHead = React.forwardRef<
|
|||||||
<th
|
<th
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-12 px-4 text-left align-middle font-normal text-text-secondary [&:has([role=checkbox])]:pr-0 ',
|
'first-of-type:pl-6 last-of-type:pr-6 h-14 px-4 text-left align-middle font-normal text-text-secondary [&:has([role=checkbox])]:pr-0 ',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -97,7 +100,7 @@ const TableCell = React.forwardRef<
|
|||||||
<td
|
<td
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'p-4 align-middle [&:has([role=checkbox])]:pr-0 text-text-primary font-normal',
|
'first-of-type:pl-6 last-of-type:pr-6 px-4 py-3 align-middle [&:has([role=checkbox])]:pr-0 text-text-primary font-normal',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -29,7 +29,11 @@ const TabsTrigger = React.forwardRef<
|
|||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-bg-base data-[state=active]:text-text-primary data-[state=active]:shadow-sm',
|
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5',
|
||||||
|
'text-sm font-medium ring-offset-background transition-all',
|
||||||
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||||
|
'disabled:pointer-events-none disabled:opacity-50',
|
||||||
|
'data-[state=active]:bg-bg-base data-[state=active]:text-text-primary data-[state=active]:shadow-sm',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -1931,13 +1931,14 @@ Important structured information may include: names, dates, locations, events, k
|
|||||||
roles: 'Roles',
|
roles: 'Roles',
|
||||||
monitoring: 'Monitoring',
|
monitoring: 'Monitoring',
|
||||||
|
|
||||||
|
back: 'Back',
|
||||||
active: 'Active',
|
active: 'Active',
|
||||||
inactive: 'Inactive',
|
inactive: 'Inactive',
|
||||||
enable: 'Enable',
|
enable: 'Enable',
|
||||||
disable: 'Disable',
|
disable: 'Disable',
|
||||||
all: 'All',
|
all: 'All',
|
||||||
actions: 'Actions',
|
actions: 'Actions',
|
||||||
newUser: 'New User',
|
newUser: 'New user',
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
nickname: 'Nickname',
|
nickname: 'Nickname',
|
||||||
@ -1956,6 +1957,7 @@ Important structured information may include: names, dates, locations, events, k
|
|||||||
lastUpdateTime: 'Last update time',
|
lastUpdateTime: 'Last update time',
|
||||||
|
|
||||||
isAnonymous: 'Is Anonymous',
|
isAnonymous: 'Is Anonymous',
|
||||||
|
isSuperuser: 'Is Superuser',
|
||||||
|
|
||||||
deleteUser: 'Delete user',
|
deleteUser: 'Delete user',
|
||||||
deleteUserConfirmation: 'Are you sure you want to delete this user?',
|
deleteUserConfirmation: 'Are you sure you want to delete this user?',
|
||||||
@ -1990,7 +1992,7 @@ Important structured information may include: names, dates, locations, events, k
|
|||||||
deleteWhitelistEmailConfirmation:
|
deleteWhitelistEmailConfirmation:
|
||||||
'Are you sure you want to delete this email from whitelist? This action cannot be undone.',
|
'Are you sure you want to delete this email from whitelist? This action cannot be undone.',
|
||||||
|
|
||||||
importWhitelist: 'Import whitelist (excel)',
|
importWhitelist: 'Import whitelist (Excel)',
|
||||||
importSelectExcelFile: 'Excel file (.xlsx)',
|
importSelectExcelFile: 'Excel file (.xlsx)',
|
||||||
importOverwriteExistingEmails: 'Overwrite existing emails',
|
importOverwriteExistingEmails: 'Overwrite existing emails',
|
||||||
importInvalidExcelFile: 'Please select a valid Excel file',
|
importInvalidExcelFile: 'Please select a valid Excel file',
|
||||||
|
|||||||
@ -15,15 +15,18 @@ const ThemeSwitch = forwardRef<
|
|||||||
return (
|
return (
|
||||||
<Root
|
<Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn('relative rounded-full', className)}
|
className={cn(
|
||||||
|
'group/theme-switch relative rounded-full outline-none self-center focus-visible:ring-1 focus-visible:ring-accent-primary',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
checked={isDark}
|
checked={isDark}
|
||||||
onCheckedChange={(value) =>
|
onCheckedChange={(value) =>
|
||||||
setTheme(value ? ThemeEnum.Dark : ThemeEnum.Light)
|
setTheme(value ? ThemeEnum.Dark : ThemeEnum.Light)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="px-3 py-2 rounded-full border border-border-button bg-bg-card transition-[background-color] duration-200">
|
<div className="self-center p-3 py-2 rounded-full bg-bg-card transition-[background-color] duration-300">
|
||||||
<div className="flex items-center justify-between gap-4 relative z-[1] text-text-disabled transition-[text-color] duration-200">
|
<div className="h-full flex items-center justify-between gap-4 relative z-[1] text-text-disabled transition-[text-color] duration-300 delay-75">
|
||||||
<LucideSun
|
<LucideSun
|
||||||
className={cn('size-[1em]', !isDark && 'text-text-primary')}
|
className={cn('size-[1em]', !isDark && 'text-text-primary')}
|
||||||
/>
|
/>
|
||||||
@ -35,11 +38,22 @@ const ThemeSwitch = forwardRef<
|
|||||||
|
|
||||||
<Thumb
|
<Thumb
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute top-0 left-0 w-[calc(50%+.25rem)] h-full rounded-full bg-bg-base border border-border-button',
|
'absolute top-0 left-0 w-[calc(50%+.25rem)] p-0.5 h-full rounded-full overflow-hidden',
|
||||||
'transition-all duration-200',
|
'transition-all ease-out duration-300',
|
||||||
{ 'left-[calc(50%-.25rem)]': isDark },
|
'group-hover/theme-switch:w-[calc(50%+.66rem)] group-focus-visible/theme-switch:w-[calc(50%+.66rem)]',
|
||||||
|
{
|
||||||
|
'left-[calc(50%-.25rem)] group-hover/theme-switch:left-[calc(50%-.66rem)] group-focus-visible/theme-switch:left-[calc(50%-.66rem)]':
|
||||||
|
isDark,
|
||||||
|
},
|
||||||
)}
|
)}
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
size-full rounded-full bg-bg-base shadow-md
|
||||||
|
transition-colors ease-out duration-300 delay-75
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</Thumb>
|
||||||
</Root>
|
</Root>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -18,12 +18,7 @@ import {
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import {
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
Tabs,
|
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
} from '@/components/ui/tabs-underlined';
|
|
||||||
|
|
||||||
import { listResources } from '@/services/admin-service';
|
import { listResources } from '@/services/admin-service';
|
||||||
import { PERMISSION_TYPES, formMergeDefaultValues } from '../utils';
|
import { PERMISSION_TYPES, formMergeDefaultValues } from '../utils';
|
||||||
@ -106,12 +101,12 @@ export const CreateRoleForm = ({
|
|||||||
<Label>{t('admin.resources')}</Label>
|
<Label>{t('admin.resources')}</Label>
|
||||||
|
|
||||||
<Tabs defaultValue={resourceTypes?.[0]} className="w-full mt-2">
|
<Tabs defaultValue={resourceTypes?.[0]} className="w-full mt-2">
|
||||||
<TabsList className="p-0 mb-2 gap-4 bg-transparent">
|
<TabsList className="p-0 mb-2 gap-4 bg-transparent justify-start">
|
||||||
{resourceTypes?.map((resourceType) => (
|
{resourceTypes?.map((resourceType) => (
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
key={resourceType}
|
key={resourceType}
|
||||||
value={resourceType}
|
value={resourceType}
|
||||||
className="text-text-secondary !border-border-button data-[state=active]:bg-bg-card data-[state=active]:text-text-primary"
|
className="text-text-secondary border-0.5 border-border-button data-[state=active]:bg-bg-card"
|
||||||
>
|
>
|
||||||
{t(`admin.resourceType.${resourceType.toLowerCase()}`)}
|
{t(`admin.resourceType.${resourceType.toLowerCase()}`)}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
|||||||
@ -153,7 +153,11 @@ export const CreateUserForm = ({
|
|||||||
<SelectItem key={role.id} value={role.role_name}>
|
<SelectItem key={role.id} value={role.role_name}>
|
||||||
{role.role_name}
|
{role.role_name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)) ?? (
|
||||||
|
<div className="text-text-secondary px-2 py-6 text-sm text-center">
|
||||||
|
{t('common.noData')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
import { useMemo } from 'react';
|
||||||
import message from '@/components/ui/message';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { cn } from '@/lib/utils';
|
import { NavLink, Outlet, useNavigate } from 'umi';
|
||||||
import { Routes } from '@/routes';
|
|
||||||
import { logout } from '@/services/admin-service';
|
|
||||||
import authorizationUtil from '@/utils/authorization-util';
|
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LucideMonitor,
|
LucideMonitor,
|
||||||
LucideServerCrash,
|
LucideServerCrash,
|
||||||
@ -12,13 +11,18 @@ import {
|
|||||||
LucideUserCog,
|
LucideUserCog,
|
||||||
LucideUserStar,
|
LucideUserStar,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { NavLink, Outlet, useNavigate } from 'umi';
|
|
||||||
import ThemeSwitch from './components/theme-switch';
|
|
||||||
import { IS_ENTERPRISE } from './utils';
|
|
||||||
|
|
||||||
const AdminLayout = () => {
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Routes } from '@/routes';
|
||||||
|
import { logout } from '@/services/admin-service';
|
||||||
|
|
||||||
|
import authorizationUtil from '@/utils/authorization-util';
|
||||||
|
|
||||||
|
import ThemeSwitch from '../components/theme-switch';
|
||||||
|
import { IS_ENTERPRISE } from '../utils';
|
||||||
|
|
||||||
|
const AdminNavigationLayout = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@ -61,8 +65,6 @@ const AdminLayout = () => {
|
|||||||
mutationKey: ['adminLogout'],
|
mutationKey: ['adminLogout'],
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
await logout();
|
await logout();
|
||||||
|
|
||||||
message.success(t('message.logout'));
|
|
||||||
authorizationUtil.removeAll();
|
authorizationUtil.removeAll();
|
||||||
navigate(Routes.Admin);
|
navigate(Routes.Admin);
|
||||||
},
|
},
|
||||||
@ -90,6 +92,7 @@ const AdminLayout = () => {
|
|||||||
'hover:bg-bg-card focus:bg-bg-card focus-visible:bg-bg-card',
|
'hover:bg-bg-card focus:bg-bg-card focus-visible:bg-bg-card',
|
||||||
'hover:text-text-primary focus:text-text-primary focus-visible:text-text-primary',
|
'hover:text-text-primary focus:text-text-primary focus-visible:text-text-primary',
|
||||||
'active:text-text-primary',
|
'active:text-text-primary',
|
||||||
|
'transition-colors',
|
||||||
{
|
{
|
||||||
'bg-bg-card text-text-primary': isActive,
|
'bg-bg-card text-text-primary': isActive,
|
||||||
},
|
},
|
||||||
@ -105,14 +108,18 @@ const AdminLayout = () => {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="mt-auto space-y-4">
|
<div className="mt-auto space-y-4">
|
||||||
<div className="text-right">
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-accent-primary">
|
||||||
|
vmm.ss.rr-nnn-commithash
|
||||||
|
</span>
|
||||||
|
|
||||||
<ThemeSwitch />
|
<ThemeSwitch />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
className="block w-full dark:border-border-button"
|
block
|
||||||
onClick={() => logoutMutation.mutate()}
|
onClick={() => logoutMutation.mutate()}
|
||||||
>
|
>
|
||||||
{t('header.logout')}
|
{t('header.logout')}
|
||||||
@ -127,4 +134,4 @@ const AdminLayout = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AdminLayout;
|
export default AdminNavigationLayout;
|
||||||
7
web/src/pages/admin/layouts/root-layout.tsx
Normal file
7
web/src/pages/admin/layouts/root-layout.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Outlet } from 'umi';
|
||||||
|
|
||||||
|
const AdminRootLayout = () => {
|
||||||
|
return <Outlet />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminRootLayout;
|
||||||
@ -1,18 +1,16 @@
|
|||||||
import { type AxiosResponseHeaders } from 'axios';
|
import { type AxiosResponseHeaders } from 'axios';
|
||||||
import { useEffect, useId, useState } from 'react';
|
import { useEffect, useId } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'umi';
|
import { useNavigate } from 'umi';
|
||||||
|
|
||||||
import { LucideEye, LucideEyeOff } from 'lucide-react';
|
|
||||||
|
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import Spotlight from '@/components/spotlight';
|
import Spotlight from '@/components/spotlight';
|
||||||
import { ButtonLoading } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import {
|
import {
|
||||||
@ -43,8 +41,6 @@ function AdminLogin() {
|
|||||||
const { t } = useTranslation('translation', { keyPrefix: 'login' });
|
const { t } = useTranslation('translation', { keyPrefix: 'login' });
|
||||||
const { isLogin } = useAuth();
|
const { isLogin } = useAuth();
|
||||||
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
|
|
||||||
const loginMutation = useMutation({
|
const loginMutation = useMutation({
|
||||||
mutationKey: ['adminLogin'],
|
mutationKey: ['adminLogin'],
|
||||||
mutationFn: async (params: { email: string; password: string }) => {
|
mutationFn: async (params: { email: string; password: string }) => {
|
||||||
@ -113,7 +109,7 @@ function AdminLogin() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea className="w-screen h-screen">
|
<ScrollArea className="w-screen h-screen">
|
||||||
<div className="relative">
|
<div className="relative h-max min-h-[100vh]">
|
||||||
<Spotlight opcity={0.4} coverage={60} color="rgb(128, 255, 248)" />
|
<Spotlight opcity={0.4} coverage={60} color="rgb(128, 255, 248)" />
|
||||||
<Spotlight
|
<Spotlight
|
||||||
opcity={0.3}
|
opcity={0.3}
|
||||||
@ -143,9 +139,9 @@ function AdminLogin() {
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-center w-screen min-h-[1050px]">
|
<div className="flex items-center justify-center w-screen">
|
||||||
<div className="w-full max-w-[540px]">
|
<div className="w-full max-w-[540px] mt-72 mb-48">
|
||||||
<Card className="w-full bg-bg-component backdrop-blur-sm rounded-2xl border border-border-button">
|
<Card className="w-full bg-bg-component rounded-2xl shadow-none backdrop-blur-sm">
|
||||||
<CardContent className="px-10 pt-14 pb-10">
|
<CardContent className="px-10 pt-14 pb-10">
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
@ -164,7 +160,7 @@ function AdminLogin() {
|
|||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
className="h-10 px-2.5"
|
className="h-10"
|
||||||
placeholder={t('emailPlaceholder')}
|
placeholder={t('emailPlaceholder')}
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
{...field}
|
{...field}
|
||||||
@ -184,26 +180,13 @@ function AdminLogin() {
|
|||||||
<FormLabel required>{t('passwordLabel')}</FormLabel>
|
<FormLabel required>{t('passwordLabel')}</FormLabel>
|
||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="relative">
|
<Input
|
||||||
<Input
|
{...field}
|
||||||
className="h-10 px-2.5"
|
className="h-10"
|
||||||
type={showPassword ? 'text' : 'password'}
|
type="password"
|
||||||
placeholder={t('passwordPlaceholder')}
|
placeholder={t('passwordPlaceholder')}
|
||||||
autoComplete="password"
|
autoComplete="password"
|
||||||
{...field}
|
/>
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="absolute inset-y-0 right-0 pr-3 flex items-center"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
>
|
|
||||||
{showPassword ? (
|
|
||||||
<LucideEyeOff className="h-4 w-4 text-gray-500" />
|
|
||||||
) : (
|
|
||||||
<LucideEye className="h-4 w-4 text-gray-500" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -218,10 +201,10 @@ function AdminLogin() {
|
|||||||
<FormItem className="!mt-5">
|
<FormItem className="!mt-5">
|
||||||
<FormLabel
|
<FormLabel
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center hover:text-text-primary',
|
'transition-colors',
|
||||||
field.value
|
field.value
|
||||||
? 'text-text-primary'
|
? 'text-text-primary'
|
||||||
: 'text-text-disabled',
|
: 'text-text-secondary',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -241,19 +224,17 @@ function AdminLogin() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<CardFooter className="px-10 pt-8 pb-14">
|
<CardFooter className="px-10 pt-8 pb-14">
|
||||||
<ButtonLoading
|
<Button
|
||||||
form={formId}
|
form={formId}
|
||||||
|
variant="highlighted"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="
|
block
|
||||||
w-full h-10
|
|
||||||
bg-metallic-gradient border-b-[#00BEB4] border-b-2
|
|
||||||
hover:bg-metallic-gradient hover:border-b-[#02bcdd]
|
|
||||||
"
|
|
||||||
type="submit"
|
type="submit"
|
||||||
|
className="font-medium"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
{t('login')}
|
{t('login')}
|
||||||
</ButtonLoading>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@ -3,11 +3,14 @@ import { Card, CardContent } from '@/components/ui/card';
|
|||||||
|
|
||||||
function AdminMonitoring() {
|
function AdminMonitoring() {
|
||||||
return (
|
return (
|
||||||
<Card className="!shadow-none relative h-full border border-border-button bg-transparent rounded-xl overflow-x-hidden overflow-y-auto">
|
<Card className="!shadow-none relative h-full border-0.5 border-border-button bg-transparent rounded-xl overflow-x-hidden overflow-y-auto">
|
||||||
<Spotlight />
|
<Spotlight />
|
||||||
|
|
||||||
<CardContent className="size-full p-0">
|
<CardContent className="size-full p-0">
|
||||||
<iframe />
|
<iframe
|
||||||
|
className="size-full"
|
||||||
|
src={`${location.protocol}//${location.hostname}:9090/alerts`}
|
||||||
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -18,15 +18,9 @@ import {
|
|||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { LoadingButton } from '@/components/ui/loading-button';
|
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import {
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
Tabs,
|
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
} from '@/components/ui/tabs-underlined';
|
|
||||||
import { LucideEdit3, LucideTrash2, LucideUserPlus } from 'lucide-react';
|
import { LucideEdit3, LucideTrash2, LucideUserPlus } from 'lucide-react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -149,7 +143,7 @@ function AdminRoles() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card className="!shadow-none relative w-full h-full border border-border-button bg-transparent rounded-xl">
|
<Card className="!shadow-none relative w-full h-full border-0.5 border-border-button bg-transparent rounded-xl">
|
||||||
<Spotlight />
|
<Spotlight />
|
||||||
|
|
||||||
<ScrollArea className="size-full">
|
<ScrollArea className="size-full">
|
||||||
@ -170,9 +164,9 @@ function AdminRoles() {
|
|||||||
roleList.map((role) => (
|
roleList.map((role) => (
|
||||||
<Card
|
<Card
|
||||||
key={role.id}
|
key={role.id}
|
||||||
className="group border border-border-default bg-transparent dark:hover:bg-bg-card transition-color duration-150"
|
className="group/role border-0.5 border-border-default bg-transparent dark:hover:bg-bg-card transition-color duration-150"
|
||||||
>
|
>
|
||||||
<CardHeader className="space-y-0 flex flex-row gap-4 items-center border-b border-border-button">
|
<CardHeader className="space-y-0 flex flex-row gap-4 items-center border-b-0.5 border-border-button">
|
||||||
<div className="space-y-1.5 w-0 flex-1">
|
<div className="space-y-1.5 w-0 flex-1">
|
||||||
<CardTitle className="font-normal text-xl">
|
<CardTitle className="font-normal text-xl">
|
||||||
{role.role_name}
|
{role.role_name}
|
||||||
@ -187,7 +181,10 @@ function AdminRoles() {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
className="ml-2 p-0 border-0 size-[1em] align-middle opacity-0 group-hover:opacity-100 group-focus-within:opacity-100"
|
className="
|
||||||
|
ml-2 p-0 border-0 size-[1em] align-middle opacity-0
|
||||||
|
group-hover/role:opacity-100 group-focus-within/role:opacity-100
|
||||||
|
"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditRoleDescriptionModalOpen(true);
|
setEditRoleDescriptionModalOpen(true);
|
||||||
setRoleToMakeAction(role);
|
setRoleToMakeAction(role);
|
||||||
@ -202,7 +199,7 @@ function AdminRoles() {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="ml-auto opacity-0 group-hover:opacity-100 group-focus-within:opacity-100"
|
className="ml-auto opacity-0 group-hover/role:opacity-100 group-focus-within/role:opacity-100"
|
||||||
disabled={deleteRoleMutation.isPending}
|
disabled={deleteRoleMutation.isPending}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDeleteModalOpen(true);
|
setDeleteModalOpen(true);
|
||||||
@ -218,12 +215,12 @@ function AdminRoles() {
|
|||||||
className="h-full flex flex-col"
|
className="h-full flex flex-col"
|
||||||
defaultValue={resourceTypes?.[0]}
|
defaultValue={resourceTypes?.[0]}
|
||||||
>
|
>
|
||||||
<TabsList className="p-0 mb-2 gap-4 bg-transparent">
|
<TabsList className="p-0 mb-2 gap-4 bg-transparent justify-start">
|
||||||
{resourceTypes?.map((resourceName) => (
|
{resourceTypes?.map((resourceName) => (
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
key={resourceName}
|
key={resourceName}
|
||||||
value={resourceName}
|
value={resourceName}
|
||||||
className="text-text-secondary !border-border-button data-[state=active]:bg-bg-card data-[state=active]:text-text-primary"
|
className="text-text-secondary border-0.5 border-border-button data-[state=active]:bg-bg-card"
|
||||||
>
|
>
|
||||||
{t(
|
{t(
|
||||||
`admin.resourceType.${resourceName.toLowerCase()}`,
|
`admin.resourceType.${resourceName.toLowerCase()}`,
|
||||||
@ -290,7 +287,7 @@ function AdminRoles() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>{t('admin.addNewRole')}</DialogTitle>
|
<DialogTitle>{t('admin.addNewRole')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@ -309,14 +306,14 @@ function AdminRoles() {
|
|||||||
{t('admin.cancel')}
|
{t('admin.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<LoadingButton
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
form={createRoleForm.id}
|
form={createRoleForm.id}
|
||||||
className="px-4 h-10"
|
className="px-4 h-10"
|
||||||
variant="default"
|
variant="default"
|
||||||
>
|
>
|
||||||
{t('admin.confirm')}
|
{t('admin.confirm')}
|
||||||
</LoadingButton>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -334,7 +331,7 @@ function AdminRoles() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>{t('admin.editRoleDescription')}</DialogTitle>
|
<DialogTitle>{t('admin.editRoleDescription')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@ -372,13 +369,13 @@ function AdminRoles() {
|
|||||||
{t('admin.cancel')}
|
{t('admin.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<LoadingButton
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
form={editRoleDescriptionFormId}
|
form={editRoleDescriptionFormId}
|
||||||
className="px-4 h-10"
|
className="px-4 h-10"
|
||||||
>
|
>
|
||||||
{t('admin.confirm')}
|
{t('admin.confirm')}
|
||||||
</LoadingButton>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -393,7 +390,7 @@ function AdminRoles() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>{t('admin.deleteRole')}</DialogTitle>
|
<DialogTitle>{t('admin.deleteRole')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@ -402,7 +399,7 @@ function AdminRoles() {
|
|||||||
{t('admin.deleteRoleConfirmation')}
|
{t('admin.deleteRoleConfirmation')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
|
||||||
<div className="rounded-lg mt-6 p-4 border border-border-button">
|
<div className="rounded-lg mt-6 p-4 border-0.5 border-border-button">
|
||||||
{roleToMakeAction?.role_name}
|
{roleToMakeAction?.role_name}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -417,7 +414,7 @@ function AdminRoles() {
|
|||||||
{t('admin.cancel')}
|
{t('admin.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<LoadingButton
|
<Button
|
||||||
className="px-4 h-10"
|
className="px-4 h-10"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -428,7 +425,7 @@ function AdminRoles() {
|
|||||||
loading={deleteRoleMutation.isPending}
|
loading={deleteRoleMutation.isPending}
|
||||||
>
|
>
|
||||||
{t('admin.delete')}
|
{t('admin.delete')}
|
||||||
</LoadingButton>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -44,15 +44,18 @@ function ServiceDetail({ content }: ServiceDetailProps) {
|
|||||||
|
|
||||||
if (isPlainObject(content)) {
|
if (isPlainObject(content)) {
|
||||||
return (
|
return (
|
||||||
<dl className="text-sm text-text-primary grid grid-cols-[auto,1fr] border border-card rounded-xl overflow-hidden bg-bg-card">
|
<dl className="text-sm text-text-primary grid grid-cols-[minmax(20%,auto),1fr] rounded-xl overflow-hidden bg-bg-card">
|
||||||
{Object.entries<any>(content).map(([key, value]) => (
|
{Object.entries<any>(content).map(([key, value]) => (
|
||||||
<div key={key} className="contents">
|
<div
|
||||||
<dt className="px-3 py-2 bg-bg-card">
|
key={key}
|
||||||
|
className="contents [:not(:last-child)]>*]border-b-0.5 [:not(:last-child)>*]:border-border-button"
|
||||||
|
>
|
||||||
|
<dt className="px-4 py-2.5 bg-bg-card">
|
||||||
<pre>
|
<pre>
|
||||||
<code>{key}</code>
|
<code>{key}</code>
|
||||||
</pre>
|
</pre>
|
||||||
</dt>
|
</dt>
|
||||||
<dd className="px-3 py-2">
|
<dd className="px-4 py-2.5">
|
||||||
<pre>
|
<pre>
|
||||||
<code>{JSON.stringify(value)}</code>
|
<code>{JSON.stringify(value)}</code>
|
||||||
</pre>
|
</pre>
|
||||||
@ -65,8 +68,8 @@ function ServiceDetail({ content }: ServiceDetailProps) {
|
|||||||
|
|
||||||
if (typeof content === 'string') {
|
if (typeof content === 'string') {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg p-4 border border-border-button bg-bg-input">
|
<div className="rounded-lg p-4 bg-bg-card text-sm text-text-primary">
|
||||||
<pre className="text-sm">
|
<pre>
|
||||||
<code>
|
<code>
|
||||||
{typeof content === 'string'
|
{typeof content === 'string'
|
||||||
? content
|
? content
|
||||||
|
|||||||
@ -15,17 +15,14 @@ import {
|
|||||||
LucideClipboardList,
|
LucideClipboardList,
|
||||||
LucideDot,
|
LucideDot,
|
||||||
LucideFilter,
|
LucideFilter,
|
||||||
LucideSearch,
|
|
||||||
LucideSettings2,
|
LucideSettings2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
|
|
||||||
import Spotlight from '@/components/spotlight';
|
import Spotlight from '@/components/spotlight';
|
||||||
import { TableEmpty } from '@/components/table-skeleton';
|
import { TableEmpty } from '@/components/table-skeleton';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge, BadgeProps } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -41,7 +38,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Input } from '@/components/ui/input';
|
import { SearchInput } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
@ -127,17 +124,17 @@ function AdminServiceStatus() {
|
|||||||
}),
|
}),
|
||||||
columnHelper.accessor('host', {
|
columnHelper.accessor('host', {
|
||||||
header: t('admin.host'),
|
header: t('admin.host'),
|
||||||
cell: ({ row }) => (
|
cell: ({ cell }) => (
|
||||||
<Badge variant="secondary" className="font-normal text-text-primary">
|
<Badge variant="secondary">
|
||||||
<i>{row.getValue('host')}</i>
|
<i>{cell.getValue()}</i>
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('port', {
|
columnHelper.accessor('port', {
|
||||||
header: t('admin.port'),
|
header: t('admin.port'),
|
||||||
cell: ({ row }) => (
|
cell: ({ cell }) => (
|
||||||
<Badge variant="secondary" className="font-normal text-text-primary">
|
<Badge variant="secondary">
|
||||||
<i>{row.getValue('port')}</i>
|
<i>{cell.getValue()}</i>
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
@ -145,15 +142,14 @@ function AdminServiceStatus() {
|
|||||||
header: t('admin.status'),
|
header: t('admin.status'),
|
||||||
cell: ({ cell }) => (
|
cell: ({ cell }) => (
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant={
|
||||||
className={cn(
|
|
||||||
'pl-2 font-normal text-sm text-text-primary capitalize',
|
|
||||||
{
|
{
|
||||||
alive: 'bg-state-success-5 text-state-success',
|
alive: 'success',
|
||||||
timeout: 'bg-state-error-5 text-state-error',
|
timeout: 'destructive',
|
||||||
fail: 'bg-gray-500/5 text-text-disable',
|
fail: 'grey',
|
||||||
}[cell.getValue()],
|
}[cell.getValue()] as BadgeProps['variant']
|
||||||
)}
|
}
|
||||||
|
className="pl-[.5em] capitalize"
|
||||||
>
|
>
|
||||||
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
||||||
{t(`admin.${cell.getValue()}`)}
|
{t(`admin.${cell.getValue()}`)}
|
||||||
@ -165,7 +161,7 @@ function AdminServiceStatus() {
|
|||||||
id: 'actions',
|
id: 'actions',
|
||||||
header: t('admin.actions'),
|
header: t('admin.actions'),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity duration-100">
|
<div className="opacity-0 group-hover/row:opacity-100 group-focus-within/row:opacity-100 transition-opacity">
|
||||||
<Button
|
<Button
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
size="icon"
|
size="icon"
|
||||||
@ -206,6 +202,8 @@ function AdminServiceStatus() {
|
|||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
|
||||||
|
enableSorting: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -216,7 +214,7 @@ function AdminServiceStatus() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card className="!shadow-none relative h-full border border-border-button bg-transparent rounded-xl">
|
<Card className="!shadow-none relative h-full bg-transparent rounded-xl overflow-hidden">
|
||||||
<Spotlight />
|
<Spotlight />
|
||||||
|
|
||||||
<ScrollArea className="size-full">
|
<ScrollArea className="size-full">
|
||||||
@ -229,16 +227,20 @@ function AdminServiceStatus() {
|
|||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="dark:bg-bg-input dark:border-border-button text-text-secondary"
|
className="border-0.5"
|
||||||
|
// className="
|
||||||
|
// text-text-secondary
|
||||||
|
// dark:bg-bg-input dark:border-border-button
|
||||||
|
// hover:bg-border-button dark:hover:bg-border-button
|
||||||
|
// focus-visible:ring-0 focus-visible:text-text-primary
|
||||||
|
// focus-visible:bg-border-button focus-visible:border-border-button
|
||||||
|
// "
|
||||||
>
|
>
|
||||||
<LucideFilter className="h-4 w-4" />
|
<LucideFilter className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
|
||||||
<PopoverContent
|
<PopoverContent align="end">
|
||||||
align="end"
|
|
||||||
className="bg-bg-base text-text-secondary"
|
|
||||||
>
|
|
||||||
<div className="p-2 space-y-6">
|
<div className="p-2 space-y-6">
|
||||||
<section>
|
<section>
|
||||||
<div className="font-bold mb-3">
|
<div className="font-bold mb-3">
|
||||||
@ -247,9 +249,9 @@ function AdminServiceStatus() {
|
|||||||
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={
|
value={
|
||||||
table
|
(table
|
||||||
.getColumn('service_type')!
|
.getColumn('service_type')!
|
||||||
?.getFilterValue() as string
|
?.getFilterValue() as string) ?? ''
|
||||||
}
|
}
|
||||||
onValueChange={
|
onValueChange={
|
||||||
table.getColumn('service_type')!?.setFilterValue
|
table.getColumn('service_type')!?.setFilterValue
|
||||||
@ -291,15 +293,12 @@ function AdminServiceStatus() {
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<div className="relative w-56">
|
<SearchInput
|
||||||
<LucideSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
className="w-56 h-10 bg-bg-input border-border-button"
|
||||||
<Input
|
placeholder={t('header.search')}
|
||||||
className="pl-10 h-10 bg-bg-input border-border-button"
|
value={table.getState().globalFilter}
|
||||||
placeholder={t('header.search')}
|
onChange={(e) => table.setGlobalFilter(e.target.value)}
|
||||||
value={table.getState().globalFilter}
|
/>
|
||||||
onChange={(e) => table.setGlobalFilter(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
@ -345,7 +344,7 @@ function AdminServiceStatus() {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{table.getRowModel().rows?.length ? (
|
{table.getRowModel().rows?.length ? (
|
||||||
table.getRowModel().rows.map((row) => (
|
table.getRowModel().rows.map((row) => (
|
||||||
<TableRow key={row.id} className="group">
|
<TableRow key={row.id} className="group/row">
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<TableCell key={cell.id}>
|
<TableCell key={cell.id}>
|
||||||
{flexRender(
|
{flexRender(
|
||||||
@ -389,12 +388,12 @@ function AdminServiceStatus() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>{t('admin.extraInfo')}</DialogTitle>
|
<DialogTitle>{t('admin.extraInfo')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 pt-6 pb-4">
|
<section className="px-12 pt-6 pb-4">
|
||||||
<div className="rounded-lg p-4 border border-border-button bg-bg-input">
|
<div className="rounded-lg p-4 bg-bg-input">
|
||||||
<pre className="text-sm">
|
<pre className="text-sm">
|
||||||
<code>
|
<code>
|
||||||
{JSON.stringify(itemToMakeAction?.extra ?? {}, null, 2)}
|
{JSON.stringify(itemToMakeAction?.extra ?? {}, null, 2)}
|
||||||
@ -425,7 +424,7 @@ function AdminServiceStatus() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<Trans i18nKey="admin.serviceDetail">
|
<Trans i18nKey="admin.serviceDetail">
|
||||||
{{ name: itemToMakeAction?.name }}
|
{{ name: itemToMakeAction?.name }}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate, useParams } from 'umi';
|
import { useNavigate, useParams } from 'umi';
|
||||||
|
|
||||||
import { LucideArrowLeft, LucideDot, LucideUser2 } from 'lucide-react';
|
import { LucideArrowLeft, LucideDot } from 'lucide-react';
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import {
|
import {
|
||||||
@ -13,11 +13,10 @@ import {
|
|||||||
useReactTable,
|
useReactTable,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { Routes } from '@/routes';
|
import { Routes } from '@/routes';
|
||||||
|
|
||||||
|
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||||
import Spotlight from '@/components/spotlight';
|
import Spotlight from '@/components/spotlight';
|
||||||
import { Avatar } from '@/components/ui/avatar';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
@ -27,16 +26,11 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableHead,
|
// TableHead,
|
||||||
TableHeader,
|
// TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table';
|
} from '@/components/ui/table';
|
||||||
import {
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
Tabs,
|
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
} from '@/components/ui/tabs-underlined';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getUserDetails,
|
getUserDetails,
|
||||||
@ -44,8 +38,12 @@ import {
|
|||||||
listUserDatasets,
|
listUserDatasets,
|
||||||
} from '@/services/admin-service';
|
} from '@/services/admin-service';
|
||||||
|
|
||||||
|
import { TableEmpty } from '@/components/table-skeleton';
|
||||||
import EnterpriseFeature from './components/enterprise-feature';
|
import EnterpriseFeature from './components/enterprise-feature';
|
||||||
import { getSortIcon, parseBooleanish } from './utils';
|
import {
|
||||||
|
// getSortIcon,
|
||||||
|
parseBooleanish,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
const ASSET_NAMES = ['dataset', 'flow'];
|
const ASSET_NAMES = ['dataset', 'flow'];
|
||||||
|
|
||||||
@ -60,6 +58,20 @@ function UserDatasetTable(props: {
|
|||||||
|
|
||||||
const columnDefs = useMemo(
|
const columnDefs = useMemo(
|
||||||
() => [
|
() => [
|
||||||
|
datasetColumnHelper.accessor('name', {
|
||||||
|
header: t('admin.name'),
|
||||||
|
cell: ({ row, cell }) => (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<RAGFlowAvatar
|
||||||
|
avatar={row.original.avatar}
|
||||||
|
name={cell.getValue()}
|
||||||
|
/>
|
||||||
|
<span>{cell.getValue()}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
// #region
|
||||||
|
/*
|
||||||
datasetColumnHelper.accessor('name', {
|
datasetColumnHelper.accessor('name', {
|
||||||
header: t('admin.name'),
|
header: t('admin.name'),
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
@ -69,20 +81,15 @@ function UserDatasetTable(props: {
|
|||||||
cell: ({ cell }) => {
|
cell: ({ cell }) => {
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant={parseBooleanish(cell.getValue()) ? 'success' : 'destructive'}
|
||||||
className={cn(
|
className="pl-[.35em]"
|
||||||
'font-normal text-sm pl-2',
|
|
||||||
parseBooleanish(cell.getValue())
|
|
||||||
? 'bg-state-success-5 text-state-success'
|
|
||||||
: 'bg-state-error-5 text-state-error',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
||||||
{t(
|
{t(
|
||||||
parseBooleanish(cell.getValue())
|
parseBooleanish(cell.getValue())
|
||||||
? 'admin.active'
|
? 'admin.active'
|
||||||
: 'admin.inactive',
|
: 'admin.inactive',
|
||||||
)}
|
)}"
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -111,6 +118,8 @@ function UserDatasetTable(props: {
|
|||||||
header: t('admin.permission'),
|
header: t('admin.permission'),
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
}),
|
}),
|
||||||
|
*/
|
||||||
|
// #endregion
|
||||||
],
|
],
|
||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
@ -121,12 +130,14 @@ function UserDatasetTable(props: {
|
|||||||
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
|
||||||
|
enableSorting: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
{/* <TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
<TableRow key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header) => (
|
{headerGroup.headers.map((header) => (
|
||||||
@ -152,7 +163,8 @@ function UserDatasetTable(props: {
|
|||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableHeader>
|
</TableHeader> */}
|
||||||
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{table.getRowModel().rows?.length ? (
|
{table.getRowModel().rows?.length ? (
|
||||||
table.getRowModel().rows.map((row) => (
|
table.getRowModel().rows.map((row) => (
|
||||||
@ -165,14 +177,7 @@ function UserDatasetTable(props: {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableEmpty columnsLength={columnDefs.length} />
|
||||||
<TableCell
|
|
||||||
colSpan={table.getAllColumns().length}
|
|
||||||
className="h-24 text-center"
|
|
||||||
>
|
|
||||||
{t('common.noData')}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
@ -197,6 +202,20 @@ function UserAgentTable(props: { data?: AdminService.ListUserAgentItem[] }) {
|
|||||||
|
|
||||||
const columnDefs = useMemo(
|
const columnDefs = useMemo(
|
||||||
() => [
|
() => [
|
||||||
|
agentColumnHelper.accessor('title', {
|
||||||
|
header: t('admin.agentTitle'),
|
||||||
|
cell: ({ row, cell }) => (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<RAGFlowAvatar
|
||||||
|
avatar={row.original.avatar}
|
||||||
|
name={cell.getValue()}
|
||||||
|
/>
|
||||||
|
<span>{cell.getValue()}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
// #region
|
||||||
|
/*
|
||||||
agentColumnHelper.accessor('title', {
|
agentColumnHelper.accessor('title', {
|
||||||
header: t('admin.agentTitle'),
|
header: t('admin.agentTitle'),
|
||||||
}),
|
}),
|
||||||
@ -206,6 +225,8 @@ function UserAgentTable(props: { data?: AdminService.ListUserAgentItem[] }) {
|
|||||||
agentColumnHelper.accessor('canvas_category', {
|
agentColumnHelper.accessor('canvas_category', {
|
||||||
header: t('admin.canvasCategory'),
|
header: t('admin.canvasCategory'),
|
||||||
}),
|
}),
|
||||||
|
*/
|
||||||
|
// #endregion
|
||||||
],
|
],
|
||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
@ -216,12 +237,14 @@ function UserAgentTable(props: { data?: AdminService.ListUserAgentItem[] }) {
|
|||||||
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
|
||||||
|
enableSorting: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
{/* <TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
<TableRow key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header) => (
|
{headerGroup.headers.map((header) => (
|
||||||
@ -232,20 +255,13 @@ function UserAgentTable(props: { data?: AdminService.ListUserAgentItem[] }) {
|
|||||||
header.column.columnDef.header,
|
header.column.columnDef.header,
|
||||||
header.getContext(),
|
header.getContext(),
|
||||||
)}
|
)}
|
||||||
{/* {header.column.getCanFilter() && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
>
|
|
||||||
<LucideFilter />
|
|
||||||
</Button>
|
|
||||||
)} */}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableHeader>
|
</TableHeader> */}
|
||||||
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{table.getRowModel().rows?.length ? (
|
{table.getRowModel().rows?.length ? (
|
||||||
@ -259,14 +275,7 @@ function UserAgentTable(props: { data?: AdminService.ListUserAgentItem[] }) {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableRow key="empty">
|
<TableEmpty columnsLength={columnDefs.length} />
|
||||||
<TableCell
|
|
||||||
colSpan={table.getAllColumns().length}
|
|
||||||
className="h-24 text-center"
|
|
||||||
>
|
|
||||||
{t('common.noData')}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
@ -319,32 +328,28 @@ function AdminUserDetail() {
|
|||||||
onClick={() => navigate(`${Routes.AdminUserManagement}`)}
|
onClick={() => navigate(`${Routes.AdminUserManagement}`)}
|
||||||
>
|
>
|
||||||
<LucideArrowLeft />
|
<LucideArrowLeft />
|
||||||
<span>{t('admin.userManagement')}</span>
|
<span>{t('admin.back')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<Card className="!shadow-none relative h-0 basis-0 grow flex flex-col bg-transparent border dark:border-border-button overflow-hidden">
|
<Card className="!shadow-none relative h-0 basis-0 grow flex flex-col bg-transparent border-0.5 border-border-button overflow-hidden">
|
||||||
<Spotlight />
|
<Spotlight />
|
||||||
|
|
||||||
<CardHeader className="pb-10 border-b dark:border-border-button space-y-8">
|
<CardHeader className="pb-10 border-b-0.5 dark:border-border-button space-y-8">
|
||||||
<section className="flex items-center gap-4 text-base">
|
<section className="flex items-center gap-4 text-base">
|
||||||
<Avatar className="justify-center items-center bg-bg-group uppercase">
|
<RAGFlowAvatar
|
||||||
{detail?.email
|
avatar={detail?.avatar}
|
||||||
.split('@')[0]
|
name={detail?.email}
|
||||||
.replace(/[^0-9a-z]/gi, '')
|
isPerson
|
||||||
.slice(0, 2) || <LucideUser2 />}
|
/>
|
||||||
</Avatar>
|
|
||||||
|
|
||||||
<span>{detail?.email}</span>
|
<span>{detail?.email}</span>
|
||||||
|
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant={
|
||||||
className={cn(
|
parseBooleanish(detail?.is_active) ? 'success' : 'destructive'
|
||||||
'font-normal text-sm pl-2',
|
}
|
||||||
parseBooleanish(detail?.is_active)
|
className="pl-[.5em]"
|
||||||
? 'bg-state-success-5 text-state-success'
|
|
||||||
: '',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
||||||
{t(
|
{t(
|
||||||
@ -355,11 +360,11 @@ function AdminUserDetail() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
<EnterpriseFeature>
|
<EnterpriseFeature>
|
||||||
{() => (
|
{() =>
|
||||||
<Badge variant="secondary" className="font-normal text-sm">
|
detail?.role && (
|
||||||
{detail?.role}
|
<Badge variant="secondary">{detail?.role}</Badge>
|
||||||
</Badge>
|
)
|
||||||
)}
|
}
|
||||||
</EnterpriseFeature>
|
</EnterpriseFeature>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -398,16 +403,23 @@ function AdminUserDetail() {
|
|||||||
</div>
|
</div>
|
||||||
<div>{t(detail?.is_anonymous ? 'admin.yes' : 'admin.no')}</div>
|
<div>{t(detail?.is_anonymous ? 'admin.yes' : 'admin.no')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="text-sm text-text-secondary mb-2">
|
||||||
|
{t('admin.isSuperuser')}
|
||||||
|
</div>
|
||||||
|
<div>{t(detail?.is_superuser ? 'admin.yes' : 'admin.no')}</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="h-0 basis-0 grow pt-6">
|
<CardContent className="h-0 basis-0 grow pt-6">
|
||||||
<Tabs className="h-full flex flex-col" defaultValue="dataset">
|
<Tabs className="h-full flex flex-col" defaultValue="dataset">
|
||||||
<TabsList className="p-0 mb-2 gap-4 bg-transparent">
|
<TabsList className="p-0 mb-2 gap-4 bg-transparent justify-start">
|
||||||
{ASSET_NAMES.map((name) => (
|
{ASSET_NAMES.map((name) => (
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
key={name}
|
key={name}
|
||||||
className="border-border-button data-[state=active]:bg-bg-card"
|
className="text-text-secondary border-0.5 border-border-button data-[state=active]:bg-bg-card"
|
||||||
value={name}
|
value={name}
|
||||||
>
|
>
|
||||||
{t(`header.${name}`)}
|
{t(`header.${name}`)}
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import {
|
|||||||
LucideUserPlus,
|
LucideUserPlus,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { rsaPsw } from '@/utils';
|
import { rsaPsw } from '@/utils';
|
||||||
|
|
||||||
import Spotlight from '@/components/spotlight';
|
import Spotlight from '@/components/spotlight';
|
||||||
@ -39,14 +38,18 @@ import {
|
|||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { LoadingButton } from '@/components/ui/loading-button';
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
@ -55,13 +58,6 @@ import {
|
|||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select';
|
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@ -213,6 +209,15 @@ function AdminUserManagement() {
|
|||||||
}),
|
}),
|
||||||
columnHelper.accessor('nickname', {
|
columnHelper.accessor('nickname', {
|
||||||
header: t('admin.nickname'),
|
header: t('admin.nickname'),
|
||||||
|
cell: ({ row, cell }) => (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>{cell.getValue()}</span>
|
||||||
|
|
||||||
|
{row.original.is_superuser ? (
|
||||||
|
<Badge variant="secondary">{t('admin.superuser')}</Badge>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
...(IS_ENTERPRISE
|
...(IS_ENTERPRISE
|
||||||
@ -220,30 +225,29 @@ function AdminUserManagement() {
|
|||||||
columnHelper.accessor('role', {
|
columnHelper.accessor('role', {
|
||||||
header: t('admin.role'),
|
header: t('admin.role'),
|
||||||
cell: ({ row, cell }) => (
|
cell: ({ row, cell }) => (
|
||||||
<Select
|
<DropdownMenu>
|
||||||
value={cell.getValue()}
|
<DropdownMenuTrigger asChild>
|
||||||
onValueChange={(value) => {
|
<Button variant="ghost" className="min-w-16">
|
||||||
if (!updateUserRoleMutation.isPending) {
|
{cell.getValue()}
|
||||||
updateUserRoleMutation.mutate({
|
</Button>
|
||||||
email: row.original.email,
|
</DropdownMenuTrigger>
|
||||||
role: value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={updateUserRoleMutation.isPending}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-10">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
|
|
||||||
<SelectContent className="bg-bg-base">
|
<DropdownMenuContent>
|
||||||
{roleList?.map(({ id, role_name }) => (
|
{roleList?.map(({ id, role_name }) => (
|
||||||
<SelectItem key={id} value={role_name}>
|
<DropdownMenuItem
|
||||||
|
key={id}
|
||||||
|
onClick={() => {
|
||||||
|
updateUserRoleMutation.mutate({
|
||||||
|
email: row.original.email,
|
||||||
|
role: role_name,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
{role_name}
|
{role_name}
|
||||||
</SelectItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</DropdownMenuContent>
|
||||||
</Select>
|
</DropdownMenu>
|
||||||
),
|
),
|
||||||
filterFn: createColumnFilterFn(
|
filterFn: createColumnFilterFn(
|
||||||
(row, id, filterValue) => row.getValue(id) === filterValue,
|
(row, id, filterValue) => row.getValue(id) === filterValue,
|
||||||
@ -275,13 +279,8 @@ function AdminUserManagement() {
|
|||||||
header: t('admin.status'),
|
header: t('admin.status'),
|
||||||
cell: ({ cell }) => (
|
cell: ({ cell }) => (
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant={parseBooleanish(cell.getValue()) ? 'success' : 'secondary'}
|
||||||
className={cn(
|
className="pl-[.5em]"
|
||||||
'pl-2 font-normal text-sm',
|
|
||||||
parseBooleanish(cell.getValue())
|
|
||||||
? 'bg-state-success-5 text-state-success'
|
|
||||||
: '',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
||||||
{t(
|
{t(
|
||||||
@ -304,11 +303,11 @@ function AdminUserManagement() {
|
|||||||
id: 'actions',
|
id: 'actions',
|
||||||
header: t('admin.actions'),
|
header: t('admin.actions'),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity duration-100">
|
<div className="opacity-0 group-hover/row:opacity-100 group-focus-within/row:opacity-100 transition-opacity">
|
||||||
<Button
|
<Button
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="border-0 text-text-secondary"
|
className="border-0"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(`${Routes.AdminUserManagement}/${row.original.email}`)
|
navigate(`${Routes.AdminUserManagement}/${row.original.email}`)
|
||||||
}
|
}
|
||||||
@ -318,7 +317,7 @@ function AdminUserManagement() {
|
|||||||
<Button
|
<Button
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="border-0 text-text-secondary"
|
className="border-0"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUserToMakeAction(row.original);
|
setUserToMakeAction(row.original);
|
||||||
setPasswordModalOpen(true);
|
setPasswordModalOpen(true);
|
||||||
@ -327,9 +326,9 @@ function AdminUserManagement() {
|
|||||||
<LucideUserLock />
|
<LucideUserLock />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="transparent"
|
variant="danger"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="border-0 text-text-secondary"
|
className="border-0"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUserToMakeAction(row.original);
|
setUserToMakeAction(row.original);
|
||||||
setDeleteModalOpen(true);
|
setDeleteModalOpen(true);
|
||||||
@ -358,7 +357,7 @@ function AdminUserManagement() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card className="!shadow-none relative h-full border border-border-button bg-transparent rounded-xl overflow-x-hidden overflow-y-auto">
|
<Card className="!shadow-none relative h-full bg-transparent overflow-hidden">
|
||||||
<Spotlight />
|
<Spotlight />
|
||||||
|
|
||||||
<ScrollArea className="size-full">
|
<ScrollArea className="size-full">
|
||||||
@ -493,8 +492,8 @@ function AdminUserManagement() {
|
|||||||
{() => <col className="w-[12%]" />}
|
{() => <col className="w-[12%]" />}
|
||||||
</EnterpriseFeature>
|
</EnterpriseFeature>
|
||||||
|
|
||||||
<col className="w-[10%]" />
|
<col className="w-[8%]" />
|
||||||
<col className="w-[12%]" />
|
<col className="w-[15%]" />
|
||||||
<col className="w-52" />
|
<col className="w-52" />
|
||||||
</colgroup>
|
</colgroup>
|
||||||
|
|
||||||
@ -518,7 +517,7 @@ function AdminUserManagement() {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{table.getRowModel().rows?.length ? (
|
{table.getRowModel().rows?.length ? (
|
||||||
table.getRowModel().rows.map((row) => (
|
table.getRowModel().rows.map((row) => (
|
||||||
<TableRow key={row.id} className="group">
|
<TableRow key={row.id} className="group/row">
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<TableCell key={cell.id}>
|
<TableCell key={cell.id}>
|
||||||
{flexRender(
|
{flexRender(
|
||||||
@ -555,18 +554,16 @@ function AdminUserManagement() {
|
|||||||
{/* Delete Confirmation Modal */}
|
{/* Delete Confirmation Modal */}
|
||||||
<Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}>
|
<Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}>
|
||||||
<DialogContent className="p-0 border-border-button">
|
<DialogContent className="p-0 border-border-button">
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>{t('admin.deleteUser')}</DialogTitle>
|
<DialogTitle>{t('admin.deleteUser')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4">
|
<section className="px-12 py-4 text-text-primary text-sm">
|
||||||
<DialogDescription className="text-text-primary">
|
{t('admin.deleteUserConfirmation')}
|
||||||
{t('admin.deleteUserConfirmation')}
|
|
||||||
|
|
||||||
<div className="rounded-lg mt-6 p-4 border border-border-button">
|
<div className="rounded-lg mt-6 p-4 border-0.5 border-border-button">
|
||||||
{userToMakeAction?.email}
|
{userToMakeAction?.email}
|
||||||
</div>
|
</div>
|
||||||
</DialogDescription>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||||
@ -579,7 +576,7 @@ function AdminUserManagement() {
|
|||||||
{t('admin.cancel')}
|
{t('admin.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<LoadingButton
|
<Button
|
||||||
className="px-4 h-10"
|
className="px-4 h-10"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -590,7 +587,7 @@ function AdminUserManagement() {
|
|||||||
loading={deleteUserMutation.isPending}
|
loading={deleteUserMutation.isPending}
|
||||||
>
|
>
|
||||||
{t('admin.delete')}
|
{t('admin.delete')}
|
||||||
</LoadingButton>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -605,7 +602,7 @@ function AdminUserManagement() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>{t('admin.changePassword')}</DialogTitle>
|
<DialogTitle>{t('admin.changePassword')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@ -637,7 +634,7 @@ function AdminUserManagement() {
|
|||||||
{t('admin.cancel')}
|
{t('admin.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<LoadingButton
|
<Button
|
||||||
form={changePasswordForm.id}
|
form={changePasswordForm.id}
|
||||||
className="px-4 h-10"
|
className="px-4 h-10"
|
||||||
variant="default"
|
variant="default"
|
||||||
@ -646,7 +643,7 @@ function AdminUserManagement() {
|
|||||||
loading={changePasswordMutation.isPending}
|
loading={changePasswordMutation.isPending}
|
||||||
>
|
>
|
||||||
{t('admin.changePassword')}
|
{t('admin.changePassword')}
|
||||||
</LoadingButton>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -660,7 +657,7 @@ function AdminUserManagement() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent className="p-0 border-border-button">
|
<DialogContent className="p-0 border-border-button">
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>{t('admin.createNewUser')}</DialogTitle>
|
<DialogTitle>{t('admin.createNewUser')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@ -684,7 +681,7 @@ function AdminUserManagement() {
|
|||||||
{t('admin.cancel')}
|
{t('admin.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<LoadingButton
|
<Button
|
||||||
form={createUserForm.id}
|
form={createUserForm.id}
|
||||||
type="submit"
|
type="submit"
|
||||||
className="px-4 h-10"
|
className="px-4 h-10"
|
||||||
@ -693,7 +690,7 @@ function AdminUserManagement() {
|
|||||||
loading={createUserMutation.isPending}
|
loading={createUserMutation.isPending}
|
||||||
>
|
>
|
||||||
{t('admin.confirm')}
|
{t('admin.confirm')}
|
||||||
</LoadingButton>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -40,8 +40,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Input } from '@/components/ui/input';
|
import { SearchInput } from '@/components/ui/input';
|
||||||
import { LoadingButton } from '@/components/ui/loading-button';
|
|
||||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import {
|
import {
|
||||||
@ -63,6 +62,7 @@ import {
|
|||||||
|
|
||||||
import { EMPTY_DATA, createFuzzySearchFn, getSortIcon } from './utils';
|
import { EMPTY_DATA, createFuzzySearchFn, getSortIcon } from './utils';
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import useCreateEmailForm from './forms/email-form';
|
import useCreateEmailForm from './forms/email-form';
|
||||||
import useImportExcelForm, {
|
import useImportExcelForm, {
|
||||||
ImportExcelFormData,
|
ImportExcelFormData,
|
||||||
@ -157,18 +157,14 @@ function AdminWhitelist() {
|
|||||||
email: item.email,
|
email: item.email,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const YYYY = String(now.getFullYear()).padStart(4, '0');
|
|
||||||
const MM = String(now.getMonth()).padStart(2, '0');
|
|
||||||
const dd = String(now.getDate()).padStart(2, '0');
|
|
||||||
const HH = String(now.getHours()).padStart(2, '0');
|
|
||||||
const mm = String(now.getMinutes()).padStart(2, '0');
|
|
||||||
const ss = String(now.getSeconds()).padStart(2, '0');
|
|
||||||
|
|
||||||
const worksheet = XLSX.utils.json_to_sheet(columnData);
|
const worksheet = XLSX.utils.json_to_sheet(columnData);
|
||||||
const workbook = XLSX.utils.book_new();
|
const workbook = XLSX.utils.book_new();
|
||||||
|
|
||||||
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
|
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
|
||||||
XLSX.writeFile(workbook, `whitelist_${YYYY}${MM}${dd}${HH}${mm}${ss}.xlsx`);
|
XLSX.writeFile(
|
||||||
|
workbook,
|
||||||
|
`whitelist_${dayjs(new Date()).format('YYYYMMDDHHmmss')}.xlsx`,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const columnDefs = useMemo(
|
const columnDefs = useMemo(
|
||||||
@ -187,11 +183,11 @@ function AdminWhitelist() {
|
|||||||
id: 'actions',
|
id: 'actions',
|
||||||
header: t('admin.actions'),
|
header: t('admin.actions'),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity duration-100">
|
<div className="opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity">
|
||||||
<Button
|
<Button
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="border-0 text-text-secondary"
|
className="border-0"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setItemToMakeAction(row.original);
|
setItemToMakeAction(row.original);
|
||||||
setEditModalOpen(true);
|
setEditModalOpen(true);
|
||||||
@ -200,9 +196,9 @@ function AdminWhitelist() {
|
|||||||
<LucideUserPen />
|
<LucideUserPen />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="transparent"
|
variant="danger"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="border-0 text-text-secondary"
|
className="border-0"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setItemToMakeAction(row.original);
|
setItemToMakeAction(row.original);
|
||||||
setDeleteModalOpen(true);
|
setDeleteModalOpen(true);
|
||||||
@ -227,11 +223,13 @@ function AdminWhitelist() {
|
|||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
|
||||||
|
enableSorting: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card className="!shadow-none relative h-full border border-border-button bg-transparent rounded-xl overflow-x-hidden overflow-y-auto">
|
<Card className="!shadow-none relative h-full border-0.5 border-border-button bg-transparent rounded-xl overflow-x-hidden overflow-y-auto">
|
||||||
<Spotlight />
|
<Spotlight />
|
||||||
|
|
||||||
<ScrollArea className="size-full">
|
<ScrollArea className="size-full">
|
||||||
@ -239,15 +237,13 @@ function AdminWhitelist() {
|
|||||||
<CardTitle>{t('admin.whitelistManagement')}</CardTitle>
|
<CardTitle>{t('admin.whitelistManagement')}</CardTitle>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="relative w-56">
|
<SearchInput
|
||||||
<LucideSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
className="w-56 h-10 bg-bg-input border-border-button"
|
||||||
<Input
|
placeholder={t('header.search')}
|
||||||
className="pl-10 h-10 bg-bg-input border-border-button"
|
value={table.getState().globalFilter}
|
||||||
placeholder={t('header.search')}
|
onChange={(e) => table.setGlobalFilter(e.target.value)}
|
||||||
value={table.getState().globalFilter}
|
prefix={<LucideSearch className="size-3.5" />}
|
||||||
onChange={(e) => table.setGlobalFilter(e.target.value)}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@ -272,7 +268,7 @@ function AdminWhitelist() {
|
|||||||
onClick={() => setCreateModalOpen(true)}
|
onClick={() => setCreateModalOpen(true)}
|
||||||
>
|
>
|
||||||
<LucidePlus />
|
<LucidePlus />
|
||||||
{t('admin.createEmail')}
|
{t('admin.newUser')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@ -360,18 +356,18 @@ function AdminWhitelist() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>{t('admin.deleteEmail')}</DialogTitle>
|
<DialogTitle>{t('admin.deleteEmail')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4">
|
<section className="px-12 py-4">
|
||||||
<DialogDescription className="text-text-primary">
|
<DialogDescription className="text-text-primary">
|
||||||
{t('admin.deleteWhitelistEmailConfirmation')}
|
{t('admin.deleteWhitelistEmailConfirmation')}
|
||||||
|
|
||||||
<div className="rounded-lg mt-6 p-4 border border-border-button">
|
|
||||||
{itemToMakeAction?.email}
|
|
||||||
</div>
|
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
|
||||||
|
<div className="rounded-lg mt-6 p-4 border-0.5 border-border-button">
|
||||||
|
{itemToMakeAction?.email}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||||
@ -384,7 +380,7 @@ function AdminWhitelist() {
|
|||||||
{t('admin.cancel')}
|
{t('admin.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<LoadingButton
|
<Button
|
||||||
className="px-4 h-10"
|
className="px-4 h-10"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -398,7 +394,7 @@ function AdminWhitelist() {
|
|||||||
loading={deleteWhitelistEntryMutation.isPending}
|
loading={deleteWhitelistEntryMutation.isPending}
|
||||||
>
|
>
|
||||||
{t('admin.delete')}
|
{t('admin.delete')}
|
||||||
</LoadingButton>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -413,7 +409,7 @@ function AdminWhitelist() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>{t('admin.createEmail')}</DialogTitle>
|
<DialogTitle>{t('admin.createEmail')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@ -434,7 +430,7 @@ function AdminWhitelist() {
|
|||||||
{t('admin.cancel')}
|
{t('admin.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<LoadingButton
|
<Button
|
||||||
form={createEmailForm.id}
|
form={createEmailForm.id}
|
||||||
type="submit"
|
type="submit"
|
||||||
className="px-4 h-10"
|
className="px-4 h-10"
|
||||||
@ -443,7 +439,7 @@ function AdminWhitelist() {
|
|||||||
loading={createWhitelistEntryMutation.isPending}
|
loading={createWhitelistEntryMutation.isPending}
|
||||||
>
|
>
|
||||||
{t('admin.confirm')}
|
{t('admin.confirm')}
|
||||||
</LoadingButton>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -459,7 +455,7 @@ function AdminWhitelist() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>{t('admin.editEmail')}</DialogTitle>
|
<DialogTitle>{t('admin.editEmail')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@ -487,7 +483,7 @@ function AdminWhitelist() {
|
|||||||
{t('admin.cancel')}
|
{t('admin.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<LoadingButton
|
<Button
|
||||||
form={editEmailForm.id}
|
form={editEmailForm.id}
|
||||||
type="submit"
|
type="submit"
|
||||||
className="px-4 h-10"
|
className="px-4 h-10"
|
||||||
@ -496,7 +492,7 @@ function AdminWhitelist() {
|
|||||||
loading={updateWhitelistEntryMutation.isPending}
|
loading={updateWhitelistEntryMutation.isPending}
|
||||||
>
|
>
|
||||||
{t('admin.confirm')}
|
{t('admin.confirm')}
|
||||||
</LoadingButton>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -511,7 +507,7 @@ function AdminWhitelist() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>{t('admin.importWhitelist')}</DialogTitle>
|
<DialogTitle>{t('admin.importWhitelist')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@ -532,7 +528,7 @@ function AdminWhitelist() {
|
|||||||
{t('admin.cancel')}
|
{t('admin.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<LoadingButton
|
<Button
|
||||||
form={importExcelForm.id}
|
form={importExcelForm.id}
|
||||||
type="submit"
|
type="submit"
|
||||||
className="px-4 h-10"
|
className="px-4 h-10"
|
||||||
@ -541,7 +537,7 @@ function AdminWhitelist() {
|
|||||||
loading={importExcelMutation.isPending}
|
loading={importExcelMutation.isPending}
|
||||||
>
|
>
|
||||||
{t('admin.import')}
|
{t('admin.import')}
|
||||||
</LoadingButton>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { Routes } from '@/routes';
|
|||||||
import authorizationUtil from '@/utils/authorization-util';
|
import authorizationUtil from '@/utils/authorization-util';
|
||||||
import { Navigate, Outlet } from 'umi';
|
import { Navigate, Outlet } from 'umi';
|
||||||
|
|
||||||
export default () => {
|
export default function AuthorizedAdminWrapper() {
|
||||||
const isLogin = !!authorizationUtil.getAuthorization();
|
const isLogin = !!authorizationUtil.getAuthorization();
|
||||||
|
|
||||||
return isLogin ? <Outlet /> : <Navigate to={Routes.Admin} />;
|
return isLogin ? <Outlet /> : <Navigate to={Routes.Admin} />;
|
||||||
};
|
}
|
||||||
@ -418,50 +418,50 @@ const routes = [
|
|||||||
// Admin routes
|
// Admin routes
|
||||||
{
|
{
|
||||||
path: Routes.Admin,
|
path: Routes.Admin,
|
||||||
component: `@/pages/admin`,
|
|
||||||
layout: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.AdminUserManagement}/:id`,
|
|
||||||
layout: false,
|
|
||||||
wrappers: ['@/wrappers/authAdmin'],
|
|
||||||
component: `@/pages/admin/user-detail`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: Routes.Admin,
|
|
||||||
component: `@/pages/admin/layout`,
|
|
||||||
layout: false,
|
layout: false,
|
||||||
|
component: `@/pages/admin/layouts/root-layout`,
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: Routes.AdminServices,
|
path: '',
|
||||||
component: `@/pages/admin/service-status`,
|
component: `@/pages/admin/login`,
|
||||||
wrappers: ['@/wrappers/authAdmin'],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: Routes.AdminUserManagement,
|
path: `${Routes.AdminUserManagement}/:id`,
|
||||||
component: `@/pages/admin/users`,
|
wrappers: ['@/pages/admin/wrappers/authorized'],
|
||||||
wrappers: ['@/wrappers/authAdmin'],
|
component: `@/pages/admin/user-detail`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: Routes.Admin,
|
||||||
|
component: `@/pages/admin/layouts/navigation-layout`,
|
||||||
|
wrappers: ['@/pages/admin/wrappers/authorized'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: Routes.AdminServices,
|
||||||
|
component: `@/pages/admin/service-status`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: Routes.AdminUserManagement,
|
||||||
|
component: `@/pages/admin/users`,
|
||||||
|
},
|
||||||
|
|
||||||
...(IS_ENTERPRISE
|
...(IS_ENTERPRISE
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
path: Routes.AdminWhitelist,
|
path: Routes.AdminWhitelist,
|
||||||
component: `@/pages/admin/whitelist`,
|
component: `@/pages/admin/whitelist`,
|
||||||
wrappers: ['@/wrappers/authAdmin'],
|
},
|
||||||
},
|
{
|
||||||
{
|
path: Routes.AdminRoles,
|
||||||
path: Routes.AdminRoles,
|
component: `@/pages/admin/roles`,
|
||||||
component: `@/pages/admin/roles`,
|
},
|
||||||
wrappers: ['@/wrappers/authAdmin'],
|
{
|
||||||
},
|
path: Routes.AdminMonitoring,
|
||||||
{
|
component: `@/pages/admin/monitoring`,
|
||||||
path: Routes.AdminMonitoring,
|
},
|
||||||
component: `@/pages/admin/monitoring`,
|
]
|
||||||
wrappers: ['@/wrappers/authAdmin'],
|
: []),
|
||||||
},
|
],
|
||||||
]
|
},
|
||||||
: []),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -101,6 +101,8 @@ request.interceptors.response.use(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
getSystemVersion: _getSystemVersion,
|
||||||
|
|
||||||
adminLogin,
|
adminLogin,
|
||||||
adminLogout,
|
adminLogout,
|
||||||
adminListUsers,
|
adminListUsers,
|
||||||
@ -257,15 +259,5 @@ export const importWhitelistFromExcel = (file: File) => {
|
|||||||
return request.post<ResponseData<never>>(adminImportWhitelist, fd);
|
return request.post<ResponseData<never>>(adminImportWhitelist, fd);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export const getSystemVersion = () =>
|
||||||
login,
|
request.get<ResponseData<string>>(_getSystemVersion);
|
||||||
logout,
|
|
||||||
listUsers,
|
|
||||||
createUser,
|
|
||||||
getUserDetails,
|
|
||||||
updateUserStatus,
|
|
||||||
updateUserPassword,
|
|
||||||
deleteUser,
|
|
||||||
listUserDatasets,
|
|
||||||
listUserAgents,
|
|
||||||
};
|
|
||||||
|
|||||||
3
web/src/services/admin.service.d.ts
vendored
3
web/src/services/admin.service.d.ts
vendored
@ -32,6 +32,7 @@ declare module AdminService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UserDetail = {
|
export type UserDetail = {
|
||||||
|
avatar?: string;
|
||||||
create_date: string;
|
create_date: string;
|
||||||
email: string;
|
email: string;
|
||||||
is_active: '0' | '1';
|
is_active: '0' | '1';
|
||||||
@ -46,6 +47,7 @@ declare module AdminService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ListUserDatasetItem = {
|
export type ListUserDatasetItem = {
|
||||||
|
avatar?: string;
|
||||||
chunk_num: number;
|
chunk_num: number;
|
||||||
create_date: string;
|
create_date: string;
|
||||||
doc_num: number;
|
doc_num: number;
|
||||||
@ -58,6 +60,7 @@ declare module AdminService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ListUserAgentItem = {
|
export type ListUserAgentItem = {
|
||||||
|
avatar?: string;
|
||||||
canvas_category: 'agent';
|
canvas_category: 'agent';
|
||||||
permission: 'string';
|
permission: 'string';
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@ -27,6 +27,9 @@ module.exports = {
|
|||||||
'4xl': '1980px',
|
'4xl': '1980px',
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
|
borderWidth: {
|
||||||
|
0.5: '0.5px',
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
border: 'var(--border-default)',
|
border: 'var(--border-default)',
|
||||||
input: 'hsl(var(--input))',
|
input: 'hsl(var(--input))',
|
||||||
@ -75,7 +78,9 @@ module.exports = {
|
|||||||
'bg-list': {
|
'bg-list': {
|
||||||
DEFAULT: 'rgb(var(--bg-list) / <alpha-value>)',
|
DEFAULT: 'rgb(var(--bg-list) / <alpha-value>)',
|
||||||
},
|
},
|
||||||
'text-primary': 'var(--text-primary)',
|
'text-primary': {
|
||||||
|
DEFAULT: 'rgb(var(--text-primary) / <alpha-value>)',
|
||||||
|
},
|
||||||
'text-secondary': 'var(--text-secondary)',
|
'text-secondary': 'var(--text-secondary)',
|
||||||
'text-disabled': 'var(--text-disabled)',
|
'text-disabled': 'var(--text-disabled)',
|
||||||
'text-input-tip': 'var(--text-input-tip)',
|
'text-input-tip': 'var(--text-input-tip)',
|
||||||
@ -175,7 +180,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
'metallic-gradient':
|
'metallic-gradient':
|
||||||
'linear-gradient(104deg, var(--text-primary) 30%, var(--metallic) 50%, var(--text-primary) 70%)',
|
'linear-gradient(104deg, rgb(var(--text-primary)) 30%, var(--metallic) 50%, rgb(var(--text-primary)) 70%)',
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: `var(--radius)`,
|
lg: `var(--radius)`,
|
||||||
|
|||||||
@ -99,7 +99,9 @@
|
|||||||
--bg-canvas: 246 246 247;
|
--bg-canvas: 246 246 247;
|
||||||
--bg-list: 246 246 247;
|
--bg-list: 246 246 247;
|
||||||
/* Button ,Body text, Input completed text */
|
/* Button ,Body text, Input completed text */
|
||||||
--text-primary: #161618;
|
|
||||||
|
/* #161618 */
|
||||||
|
--text-primary: 22 22 24;
|
||||||
--text-secondary: #75787a;
|
--text-secondary: #75787a;
|
||||||
--text-disabled: #b2b5b7;
|
--text-disabled: #b2b5b7;
|
||||||
/* input placeholder color */
|
/* input placeholder color */
|
||||||
@ -231,7 +233,9 @@
|
|||||||
--bg-input: rgba(255, 255, 255, 0.05);
|
--bg-input: rgba(255, 255, 255, 0.05);
|
||||||
--bg-canvas: 0 0 0;
|
--bg-canvas: 0 0 0;
|
||||||
--bg-list: 56 56 58;
|
--bg-list: 56 56 58;
|
||||||
--text-primary: #f6f6f7;
|
|
||||||
|
/* #f6f6f7 */
|
||||||
|
--text-primary: 246 246 247;
|
||||||
--text-secondary: #b2b5b7;
|
--text-secondary: #b2b5b7;
|
||||||
--text-disabled: #75787a;
|
--text-disabled: #75787a;
|
||||||
--text-input-tip: #75787a;
|
--text-input-tip: #75787a;
|
||||||
|
|||||||
Reference in New Issue
Block a user