mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-23 15:06:50 +08:00
Add task executor bar chart, add system version string (#11155)
### What problem does this PR solve? - Add task executor bar chart - Add read version string ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -38,7 +38,13 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'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',
|
'outline-0 fixed left-[50%] top-[50%] rounded-lg z-50 grid w-full max-w-xl translate-x-[-50%] translate-y-[-50%] gap-4',
|
||||||
|
'border-0.5 border-border-button bg-bg-base p-6 shadow-lg duration-200 sm:rounded-lg',
|
||||||
|
'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%]',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -66,6 +72,7 @@ const DialogHeader = ({
|
|||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
'-mx-6 -mt-6 p-6 border-b-0.5 border-border-button',
|
||||||
'flex flex-col space-y-1.5 text-center sm:text-left',
|
'flex flex-col space-y-1.5 text-center sm:text-left',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@ -80,6 +87,7 @@ const DialogFooter = ({
|
|||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
// '-mx-6 -mb-6 px-12 pt-4 pb-8',
|
||||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-4',
|
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-4',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
type={isPasswordInput && showPassword ? 'text' : type}
|
type={isPasswordInput && showPassword ? 'text' : type}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
'peer/input',
|
||||||
'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',
|
'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',
|
||||||
'file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-text-disabled',
|
'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-1 focus-visible:ring-accent-primary',
|
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent-primary',
|
||||||
@ -79,7 +80,12 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
<Button
|
<Button
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
type="button"
|
type="button"
|
||||||
className="border-0 absolute right-1 top-[50%] translate-y-[-50%]"
|
className="
|
||||||
|
absolute border-0 right-1 top-[50%] translate-y-[-50%]
|
||||||
|
dark:peer-autofill/input:text-text-secondary-inverse
|
||||||
|
dark:peer-autofill/input:hover:text-text-primary-inverse
|
||||||
|
dark:peer-autofill/input:focus-visible:text-text-primary-inverse
|
||||||
|
"
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
>
|
>
|
||||||
{showPassword ? (
|
{showPassword ? (
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const Switch = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
'group/switch inline-flex h-4 w-7 shrink-0 cursor-pointer items-center rounded-full',
|
'group/switch inline-flex h-4 w-7 shrink-0 cursor-pointer items-center rounded-full',
|
||||||
'border-2 border-transparent overflow-hidden transition-colors',
|
'border-2 border-transparent overflow-hidden transition-colors',
|
||||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary',
|
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary',
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
'data-[state=checked]:bg-accent-primary data-[state=unchecked]:bg-text-sub-title',
|
'data-[state=checked]:bg-accent-primary data-[state=unchecked]:bg-text-sub-title',
|
||||||
className,
|
className,
|
||||||
|
|||||||
@ -1911,7 +1911,7 @@ Important structured information may include: names, dates, locations, events, k
|
|||||||
processing: 'Processing',
|
processing: 'Processing',
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
loginTitle: 'RAGFlow ADMIN',
|
loginTitle: 'Admin Console',
|
||||||
title: 'RAGFlow admin',
|
title: 'RAGFlow admin',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
@ -1998,6 +1998,7 @@ Important structured information may include: names, dates, locations, events, k
|
|||||||
|
|
||||||
extraInfo: 'Extra information',
|
extraInfo: 'Extra information',
|
||||||
serviceDetail: `Service {{name}} detail`,
|
serviceDetail: `Service {{name}} detail`,
|
||||||
|
taskExecutorDetail: 'Task executor detail',
|
||||||
|
|
||||||
whitelistManagement: 'Whitelist management',
|
whitelistManagement: 'Whitelist management',
|
||||||
exportAsExcel: 'Export Excel',
|
exportAsExcel: 'Export Excel',
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { NavLink, Outlet, useNavigate } from 'umi';
|
import { NavLink, Outlet, useNavigate } from 'umi';
|
||||||
|
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LucideMonitor,
|
LucideMonitor,
|
||||||
@ -15,7 +15,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Routes } from '@/routes';
|
import { Routes } from '@/routes';
|
||||||
import { logout } from '@/services/admin-service';
|
import { getSystemVersion, logout } from '@/services/admin-service';
|
||||||
|
|
||||||
import authorizationUtil from '@/utils/authorization-util';
|
import authorizationUtil from '@/utils/authorization-util';
|
||||||
|
|
||||||
@ -26,6 +26,11 @@ const AdminNavigationLayout = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { data: version } = useQuery({
|
||||||
|
queryKey: ['admin/version'],
|
||||||
|
queryFn: async () => (await getSystemVersion())?.data?.data?.version,
|
||||||
|
});
|
||||||
|
|
||||||
const navItems = useMemo(
|
const navItems = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@ -109,8 +114,8 @@ const AdminNavigationLayout = () => {
|
|||||||
|
|
||||||
<div className="mt-auto space-y-4">
|
<div className="mt-auto space-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-accent-primary">
|
<span className="leading-none text-xs text-accent-primary">
|
||||||
vmm.ss.rr-nnn-commithash
|
{version}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<ThemeSwitch />
|
<ThemeSwitch />
|
||||||
|
|||||||
@ -280,24 +280,24 @@ function AdminRoles() {
|
|||||||
{/* Add role modal */}
|
{/* Add role modal */}
|
||||||
<Dialog open={isAddRoleModalOpen} onOpenChange={setAddRoleModalOpen}>
|
<Dialog open={isAddRoleModalOpen} onOpenChange={setAddRoleModalOpen}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="max-w-2xl p-0 border-border-button"
|
className="max-w-2xl"
|
||||||
onAnimationEnd={() => {
|
onAnimationEnd={() => {
|
||||||
if (!isAddRoleModalOpen) {
|
if (!isAddRoleModalOpen) {
|
||||||
createRoleForm.form.reset();
|
createRoleForm.form.reset();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
<DialogHeader>
|
||||||
<DialogTitle>{t('admin.addNewRole')}</DialogTitle>
|
<DialogTitle>{t('admin.addNewRole')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4">
|
<section className="px-6">
|
||||||
<createRoleForm.FormComponent
|
<createRoleForm.FormComponent
|
||||||
onSubmit={createRoleMutation.mutate}
|
onSubmit={createRoleMutation.mutate}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="gap-4 px-6 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="px-4 h-10 dark:border-border-button"
|
className="px-4 h-10 dark:border-border-button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@ -324,18 +324,17 @@ function AdminRoles() {
|
|||||||
onOpenChange={setEditRoleDescriptionModalOpen}
|
onOpenChange={setEditRoleDescriptionModalOpen}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="p-0 border-border-button"
|
|
||||||
onAnimationEnd={() => {
|
onAnimationEnd={() => {
|
||||||
if (!isEditRoleDescriptionModalOpen) {
|
if (!isEditRoleDescriptionModalOpen) {
|
||||||
setRoleToMakeAction(null);
|
setRoleToMakeAction(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
<DialogHeader>
|
||||||
<DialogTitle>{t('admin.editRoleDescription')}</DialogTitle>
|
<DialogTitle>{t('admin.editRoleDescription')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4">
|
<section className="px-6">
|
||||||
<form
|
<form
|
||||||
id={editRoleDescriptionFormId}
|
id={editRoleDescriptionFormId}
|
||||||
onSubmit={(evt) => {
|
onSubmit={(evt) => {
|
||||||
@ -360,7 +359,7 @@ function AdminRoles() {
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="gap-4 px-6 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="px-4 h-10 dark:border-border-button"
|
className="px-4 h-10 dark:border-border-button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@ -383,18 +382,17 @@ function AdminRoles() {
|
|||||||
{/* Delete role modal */}
|
{/* Delete role modal */}
|
||||||
<Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}>
|
<Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="p-0 border-border-button"
|
|
||||||
onAnimationEnd={() => {
|
onAnimationEnd={() => {
|
||||||
if (!deleteModalOpen) {
|
if (!deleteModalOpen) {
|
||||||
setRoleToMakeAction(null);
|
setRoleToMakeAction(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
<DialogHeader>
|
||||||
<DialogTitle>{t('admin.deleteRole')}</DialogTitle>
|
<DialogTitle>{t('admin.deleteRole')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4">
|
<section className="px-6">
|
||||||
<DialogDescription className="text-text-primary">
|
<DialogDescription className="text-text-primary">
|
||||||
{t('admin.deleteRoleConfirmation')}
|
{t('admin.deleteRoleConfirmation')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
@ -404,7 +402,7 @@ function AdminRoles() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="gap-4 px-6 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="px-4 h-10 dark:border-border-button"
|
className="px-4 h-10 dark:border-border-button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@ -68,7 +68,7 @@ function ServiceDetail({ content }: ServiceDetailProps) {
|
|||||||
|
|
||||||
if (typeof content === 'string') {
|
if (typeof content === 'string') {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg p-4 bg-bg-card text-sm text-text-primary">
|
<div className="rounded-xl p-4 bg-bg-card text-sm text-text-primary">
|
||||||
<pre>
|
<pre>
|
||||||
<code>
|
<code>
|
||||||
{typeof content === 'string'
|
{typeof content === 'string'
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
@ -66,7 +67,9 @@ import {
|
|||||||
getSortIcon,
|
getSortIcon,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
|
import JsonView from 'react18-json-view';
|
||||||
import ServiceDetail from './service-detail';
|
import ServiceDetail from './service-detail';
|
||||||
|
import TaskExecutorDetail from './task-executor-detail';
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<AdminService.ListServicesItem>();
|
const columnHelper = createColumnHelper<AdminService.ListServicesItem>();
|
||||||
const globalFilterFn = createFuzzySearchFn<AdminService.ListServicesItem>([
|
const globalFilterFn = createFuzzySearchFn<AdminService.ListServicesItem>([
|
||||||
@ -381,7 +384,7 @@ function AdminServiceStatus() {
|
|||||||
{/* Extra info modal*/}
|
{/* Extra info modal*/}
|
||||||
<Dialog open={extraInfoModalOpen} onOpenChange={setExtraInfoModalOpen}>
|
<Dialog open={extraInfoModalOpen} onOpenChange={setExtraInfoModalOpen}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="p-0 border-border-button"
|
className="flex flex-col max-h-[calc(100vh-4rem)] p-0 overflow-hidden"
|
||||||
onAnimationEnd={() => {
|
onAnimationEnd={() => {
|
||||||
if (!extraInfoModalOpen) {
|
if (!extraInfoModalOpen) {
|
||||||
setItemToMakeAction(null);
|
setItemToMakeAction(null);
|
||||||
@ -392,15 +395,16 @@ function AdminServiceStatus() {
|
|||||||
<DialogTitle>{t('admin.extraInfo')}</DialogTitle>
|
<DialogTitle>{t('admin.extraInfo')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 pt-6 pb-4">
|
<DialogDescription className="sr-only" />
|
||||||
<div className="rounded-lg p-4 bg-bg-input">
|
|
||||||
<pre className="text-sm">
|
<ScrollArea className="h-0 flex-1 grid">
|
||||||
<code>
|
<div className="px-12">
|
||||||
{JSON.stringify(itemToMakeAction?.extra ?? {}, null, 2)}
|
<JsonView
|
||||||
</code>
|
src={itemToMakeAction?.extra ?? {}}
|
||||||
</pre>
|
className="rounded-lg p-4 bg-bg-card break-words text-text-secondary"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</ScrollArea>
|
||||||
|
|
||||||
<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">
|
||||||
<Button
|
<Button
|
||||||
@ -417,7 +421,7 @@ function AdminServiceStatus() {
|
|||||||
{/* Service details modal */}
|
{/* Service details modal */}
|
||||||
<Dialog open={detailModalOpen} onOpenChange={setDetailModalOpen}>
|
<Dialog open={detailModalOpen} onOpenChange={setDetailModalOpen}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex flex-col max-h-[calc(100vh-4rem)] max-w-6xl p-0 border-border-button"
|
className="flex flex-col max-h-[calc(100vh-4rem)] max-w-6xl p-0 overflow-hidden"
|
||||||
onAnimationEnd={() => {
|
onAnimationEnd={() => {
|
||||||
if (!detailModalOpen) {
|
if (!detailModalOpen) {
|
||||||
setItemToMakeAction(null);
|
setItemToMakeAction(null);
|
||||||
@ -426,14 +430,30 @@ function AdminServiceStatus() {
|
|||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<Trans i18nKey="admin.serviceDetail">
|
{itemToMakeAction?.service_type === 'task_executor' ? (
|
||||||
{{ name: itemToMakeAction?.name }}
|
t('admin.taskExecutorDetail')
|
||||||
</Trans>
|
) : (
|
||||||
|
<Trans i18nKey="admin.serviceDetail">
|
||||||
|
{{ name: itemToMakeAction?.name }}
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<ScrollArea className="pt-6 pb-4 px-12 h-0 flex-1 text-text-secondary flex flex-col">
|
<DialogDescription className="sr-only" />
|
||||||
<ServiceDetail content={serviceDetails?.message} />
|
|
||||||
|
<ScrollArea className="h-0 flex-1 text-text-secondary grid">
|
||||||
|
<div className="px-12">
|
||||||
|
{itemToMakeAction?.service_type === 'task_executor' ? (
|
||||||
|
<TaskExecutorDetail
|
||||||
|
content={
|
||||||
|
serviceDetails?.message as AdminService.TaskExecutorInfo
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ServiceDetail content={serviceDetails?.message} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
||||||
<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">
|
||||||
|
|||||||
162
web/src/pages/admin/task-executor-detail.tsx
Normal file
162
web/src/pages/admin/task-executor-detail.tsx
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
import JsonView from 'react18-json-view';
|
||||||
|
import {
|
||||||
|
Bar,
|
||||||
|
BarChart,
|
||||||
|
CartesianGrid,
|
||||||
|
Legend,
|
||||||
|
Rectangle,
|
||||||
|
ResponsiveContainer,
|
||||||
|
Tooltip,
|
||||||
|
XAxis,
|
||||||
|
} from 'recharts';
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
|
||||||
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import { formatDate, formatTime } from '@/utils/date';
|
||||||
|
|
||||||
|
interface TaskExecutorDetailProps {
|
||||||
|
content?: AdminService.TaskExecutorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CustomTooltip({ active, payload, ...restProps }: any) {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
const item = payload.at(0)?.payload ?? {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="border bg-bg-base overflow-hidden shadow-xl">
|
||||||
|
<CardHeader className="px-3 py-2 text-text-primary bg-bg-title">
|
||||||
|
<CardTitle className="text-lg">
|
||||||
|
{formatDate(restProps.label)}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<ScrollArea className="px-3">
|
||||||
|
<div className="max-h-56">
|
||||||
|
<JsonView
|
||||||
|
src={item}
|
||||||
|
displaySize={30}
|
||||||
|
className="py-2 break-words text-text-secondary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CustomAxisTick({ x, y, payload }: any) {
|
||||||
|
return (
|
||||||
|
<g transform={`translate(${x},${y})`}>
|
||||||
|
<text
|
||||||
|
x={0}
|
||||||
|
y={0}
|
||||||
|
dy={8}
|
||||||
|
transform="rotate(60)"
|
||||||
|
textAnchor="start"
|
||||||
|
fill="rgb(var(--text-secondary))"
|
||||||
|
>
|
||||||
|
{formatTime(payload.value)}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TaskExecutorDetail({ content }: TaskExecutorDetailProps) {
|
||||||
|
return (
|
||||||
|
<section className="space-y-8">
|
||||||
|
{Object.entries(content ?? {}).map(([name, data]) => {
|
||||||
|
const items = data.map((x) => ({
|
||||||
|
...x,
|
||||||
|
done: Math.floor(Math.random() * 100),
|
||||||
|
failed: Math.floor(Math.random() * 100),
|
||||||
|
now: dayjs(x.now).valueOf(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const lastItem = items.at(-1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card key={name} className="border-0">
|
||||||
|
<CardHeader className="text-text-primary">
|
||||||
|
<CardTitle>{name}</CardTitle>
|
||||||
|
|
||||||
|
<div className="text-sm text-text-secondary space-x-4">
|
||||||
|
<span>Lag: {lastItem?.lag}</span>
|
||||||
|
<span>Pending: {lastItem?.pending}</span>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<ResponsiveContainer className="min-h-40 w-full">
|
||||||
|
<BarChart
|
||||||
|
data={items}
|
||||||
|
margin={{ bottom: 32 }}
|
||||||
|
className="text-sm text-text-secondary"
|
||||||
|
>
|
||||||
|
<XAxis
|
||||||
|
dataKey="now"
|
||||||
|
type="number"
|
||||||
|
scale="time"
|
||||||
|
domain={['dataMin', 'dataMax']}
|
||||||
|
tick={CustomAxisTick}
|
||||||
|
interval="equidistantPreserveStart"
|
||||||
|
angle={60}
|
||||||
|
minTickGap={16}
|
||||||
|
allowDataOverflow
|
||||||
|
padding={{ left: 24, right: 24 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* <YAxis
|
||||||
|
type="number"
|
||||||
|
tick={{ fill: 'rgb(var(--text-secondary))' }}
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
trigger="click"
|
||||||
|
content={CustomTooltip}
|
||||||
|
wrapperStyle={{
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Legend wrapperStyle={{ bottom: 0 }} />
|
||||||
|
|
||||||
|
<Bar
|
||||||
|
dataKey="done"
|
||||||
|
fill="rgb(var(--state-success))"
|
||||||
|
activeBar={
|
||||||
|
<Rectangle
|
||||||
|
fill="rgb(var(--state-success))"
|
||||||
|
stroke="var(--bg-base)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Bar
|
||||||
|
dataKey="failed"
|
||||||
|
fill="rgb(var(--state-error))"
|
||||||
|
activeBar={
|
||||||
|
<Rectangle
|
||||||
|
fill="rgb(var(--state-error))"
|
||||||
|
stroke="var(--bg-base)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TaskExecutorDetail;
|
||||||
@ -91,6 +91,7 @@ import {
|
|||||||
parseBooleanish,
|
parseBooleanish,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
|
import { DialogDescription } from '@radix-ui/react-dialog';
|
||||||
import EnterpriseFeature from './components/enterprise-feature';
|
import EnterpriseFeature from './components/enterprise-feature';
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<AdminService.ListUsersItem>();
|
const columnHelper = createColumnHelper<AdminService.ListUsersItem>();
|
||||||
@ -210,8 +211,8 @@ function AdminUserManagement() {
|
|||||||
columnHelper.accessor('nickname', {
|
columnHelper.accessor('nickname', {
|
||||||
header: t('admin.nickname'),
|
header: t('admin.nickname'),
|
||||||
cell: ({ row, cell }) => (
|
cell: ({ row, cell }) => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center">
|
||||||
<span>{cell.getValue()}</span>
|
<span className="mr-2 empty:hidden">{cell.getValue()}</span>
|
||||||
|
|
||||||
{row.original.is_superuser ? (
|
{row.original.is_superuser ? (
|
||||||
<Badge variant="secondary">{t('admin.superuser')}</Badge>
|
<Badge variant="secondary">{t('admin.superuser')}</Badge>
|
||||||
@ -553,20 +554,22 @@ 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>
|
||||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
<DialogHeader>
|
||||||
<DialogTitle>{t('admin.deleteUser')}</DialogTitle>
|
<DialogTitle>{t('admin.deleteUser')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4 text-text-primary text-sm">
|
<section className="px-6">
|
||||||
{t('admin.deleteUserConfirmation')}
|
<DialogDescription>
|
||||||
|
{t('admin.deleteUserConfirmation')}
|
||||||
|
</DialogDescription>
|
||||||
|
|
||||||
<div className="rounded-lg mt-6 p-4 border-0.5 border-border-button">
|
<div className="rounded-lg mt-6 p-4 border-0.5 border-border-button">
|
||||||
{userToMakeAction?.email}
|
{userToMakeAction?.email}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="gap-4 px-6 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="px-4 h-10 dark:border-border-button"
|
className="px-4 h-10 dark:border-border-button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@ -595,18 +598,17 @@ function AdminUserManagement() {
|
|||||||
{/* Change Password Modal */}
|
{/* Change Password Modal */}
|
||||||
<Dialog open={passwordModalOpen} onOpenChange={setPasswordModalOpen}>
|
<Dialog open={passwordModalOpen} onOpenChange={setPasswordModalOpen}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="p-0 border-border-button"
|
|
||||||
onAnimationEnd={() => {
|
onAnimationEnd={() => {
|
||||||
if (!passwordModalOpen) {
|
if (!passwordModalOpen) {
|
||||||
changePasswordForm.form.reset();
|
changePasswordForm.form.reset();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
<DialogHeader>
|
||||||
<DialogTitle>{t('admin.changePassword')}</DialogTitle>
|
<DialogTitle>{t('admin.changePassword')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4 text-text-secondary">
|
<section className="px-6">
|
||||||
<changePasswordForm.FormComponent
|
<changePasswordForm.FormComponent
|
||||||
key="changePasswordForm"
|
key="changePasswordForm"
|
||||||
email={userToMakeAction?.email || ''}
|
email={userToMakeAction?.email || ''}
|
||||||
@ -621,7 +623,7 @@ function AdminUserManagement() {
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="gap-4 px-6 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="px-4 h-10 dark:border-border-button"
|
className="px-4 h-10 dark:border-border-button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@ -656,19 +658,19 @@ function AdminUserManagement() {
|
|||||||
createUserForm.form.reset();
|
createUserForm.form.reset();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent className="p-0 border-border-button">
|
<DialogContent>
|
||||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
<DialogHeader>
|
||||||
<DialogTitle>{t('admin.createNewUser')}</DialogTitle>
|
<DialogTitle>{t('admin.createNewUser')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4">
|
<section className="px-6">
|
||||||
<createUserForm.FormComponent
|
<createUserForm.FormComponent
|
||||||
id={createUserForm.id}
|
id={createUserForm.id}
|
||||||
onSubmit={createUserMutation.mutate}
|
onSubmit={createUserMutation.mutate}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="gap-4 px-6 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="px-4 h-10 dark:border-border-button"
|
className="px-4 h-10 dark:border-border-button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@ -349,18 +349,17 @@ function AdminWhitelist() {
|
|||||||
{/* Delete Confirmation Modal */}
|
{/* Delete Confirmation Modal */}
|
||||||
<Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}>
|
<Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="p-0 border-border-button"
|
|
||||||
onAnimationEnd={() => {
|
onAnimationEnd={() => {
|
||||||
if (!deleteModalOpen) {
|
if (!deleteModalOpen) {
|
||||||
setItemToMakeAction(null);
|
setItemToMakeAction(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
<DialogHeader>
|
||||||
<DialogTitle>{t('admin.deleteEmail')}</DialogTitle>
|
<DialogTitle>{t('admin.deleteEmail')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4">
|
<section className="px-6">
|
||||||
<DialogDescription className="text-text-primary">
|
<DialogDescription className="text-text-primary">
|
||||||
{t('admin.deleteWhitelistEmailConfirmation')}
|
{t('admin.deleteWhitelistEmailConfirmation')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
@ -370,7 +369,7 @@ function AdminWhitelist() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="gap-4 px-6 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="px-4 h-10 dark:border-border-button"
|
className="px-4 h-10 dark:border-border-button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@ -402,25 +401,24 @@ function AdminWhitelist() {
|
|||||||
{/* Create Email Modal */}
|
{/* Create Email Modal */}
|
||||||
<Dialog open={createModalOpen} onOpenChange={setCreateModalOpen}>
|
<Dialog open={createModalOpen} onOpenChange={setCreateModalOpen}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="p-0 border-border-button"
|
|
||||||
onAnimationEnd={() => {
|
onAnimationEnd={() => {
|
||||||
if (!createModalOpen) {
|
if (!createModalOpen) {
|
||||||
createEmailForm.form.reset();
|
createEmailForm.form.reset();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
<DialogHeader>
|
||||||
<DialogTitle>{t('admin.createEmail')}</DialogTitle>
|
<DialogTitle>{t('admin.createEmail')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4 text-text-secondary">
|
<section className="px-6">
|
||||||
<createEmailForm.FormComponent
|
<createEmailForm.FormComponent
|
||||||
id={createEmailForm.id}
|
id={createEmailForm.id}
|
||||||
onSubmit={createWhitelistEntryMutation.mutate}
|
onSubmit={createWhitelistEntryMutation.mutate}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="gap-4 px-6 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="px-4 h-10 dark:border-border-button"
|
className="px-4 h-10 dark:border-border-button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@ -447,7 +445,6 @@ function AdminWhitelist() {
|
|||||||
{/* Edit Email Modal */}
|
{/* Edit Email Modal */}
|
||||||
<Dialog open={editModalOpen} onOpenChange={setEditModalOpen}>
|
<Dialog open={editModalOpen} onOpenChange={setEditModalOpen}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="p-0 border-border-button"
|
|
||||||
onAnimationEnd={() => {
|
onAnimationEnd={() => {
|
||||||
if (!editModalOpen) {
|
if (!editModalOpen) {
|
||||||
setItemToMakeAction(null);
|
setItemToMakeAction(null);
|
||||||
@ -455,11 +452,11 @@ function AdminWhitelist() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
<DialogHeader>
|
||||||
<DialogTitle>{t('admin.editEmail')}</DialogTitle>
|
<DialogTitle>{t('admin.editEmail')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4 text-text-secondary">
|
<section className="px-6">
|
||||||
<editEmailForm.FormComponent
|
<editEmailForm.FormComponent
|
||||||
id={editEmailForm.id}
|
id={editEmailForm.id}
|
||||||
onSubmit={(value) => {
|
onSubmit={(value) => {
|
||||||
@ -473,7 +470,7 @@ function AdminWhitelist() {
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="gap-4 px-6 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="px-4 h-10 dark:border-border-button"
|
className="px-4 h-10 dark:border-border-button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@ -500,25 +497,24 @@ function AdminWhitelist() {
|
|||||||
{/* Import Excel Modal */}
|
{/* Import Excel Modal */}
|
||||||
<Dialog open={importModalOpen} onOpenChange={setImportModalOpen}>
|
<Dialog open={importModalOpen} onOpenChange={setImportModalOpen}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="p-0 border-border-button"
|
|
||||||
onAnimationEnd={() => {
|
onAnimationEnd={() => {
|
||||||
if (!importModalOpen) {
|
if (!importModalOpen) {
|
||||||
importExcelForm.form.reset();
|
importExcelForm.form.reset();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
<DialogHeader>
|
||||||
<DialogTitle>{t('admin.importWhitelist')}</DialogTitle>
|
<DialogTitle>{t('admin.importWhitelist')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="px-12 py-4 text-text-secondary">
|
<section className="px-6">
|
||||||
<importExcelForm.FormComponent
|
<importExcelForm.FormComponent
|
||||||
id={importExcelForm.id}
|
id={importExcelForm.id}
|
||||||
onSubmit={importExcelMutation.mutate}
|
onSubmit={importExcelMutation.mutate}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
<DialogFooter className="gap-4 px-6 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="px-4 h-10 dark:border-border-button"
|
className="px-4 h-10 dark:border-border-button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@ -101,8 +101,6 @@ request.interceptors.response.use(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getSystemVersion: _getSystemVersion,
|
|
||||||
|
|
||||||
adminLogin,
|
adminLogin,
|
||||||
adminLogout,
|
adminLogout,
|
||||||
adminListUsers,
|
adminListUsers,
|
||||||
@ -136,6 +134,8 @@ const {
|
|||||||
adminUpdateWhitelistEntry,
|
adminUpdateWhitelistEntry,
|
||||||
adminDeleteWhitelistEntry,
|
adminDeleteWhitelistEntry,
|
||||||
adminImportWhitelist,
|
adminImportWhitelist,
|
||||||
|
|
||||||
|
adminGetSystemVersion,
|
||||||
} = api;
|
} = api;
|
||||||
|
|
||||||
type ResponseData<D = NonNullable<unknown>> = {
|
type ResponseData<D = NonNullable<unknown>> = {
|
||||||
@ -260,4 +260,4 @@ export const importWhitelistFromExcel = (file: File) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getSystemVersion = () =>
|
export const getSystemVersion = () =>
|
||||||
request.get<ResponseData<string>>(_getSystemVersion);
|
request.get<ResponseData<{ version: string }>>(adminGetSystemVersion);
|
||||||
|
|||||||
31
web/src/services/admin.service.d.ts
vendored
31
web/src/services/admin.service.d.ts
vendored
@ -66,6 +66,21 @@ declare module AdminService {
|
|||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TaskExectorHeartbeatItem = {
|
||||||
|
name: string;
|
||||||
|
boot_at: string;
|
||||||
|
now: string;
|
||||||
|
ip_address: string;
|
||||||
|
current: Record<string, object>;
|
||||||
|
done: number;
|
||||||
|
failed: number;
|
||||||
|
lag: number;
|
||||||
|
pending: number;
|
||||||
|
pid: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TaskExecutorInfo = Record<string, TaskExectorHeartbeatItem[]>;
|
||||||
|
|
||||||
export type ListServicesItem = {
|
export type ListServicesItem = {
|
||||||
extra: Record<string, unknown>;
|
extra: Record<string, unknown>;
|
||||||
host: string;
|
host: string;
|
||||||
@ -76,11 +91,17 @@ declare module AdminService {
|
|||||||
status: 'alive' | 'timeout' | 'fail';
|
status: 'alive' | 'timeout' | 'fail';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ServiceDetail = {
|
export type ServiceDetail =
|
||||||
service_name: string;
|
| {
|
||||||
status: 'alive' | 'timeout';
|
service_name: string;
|
||||||
message: string | Record<string, any> | Record<string, any>[];
|
status: 'alive' | 'timeout';
|
||||||
};
|
message: string | Record<string, any> | Record<string, any>[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
service_name: 'task_executor';
|
||||||
|
status: 'alive' | 'timeout';
|
||||||
|
message: AdminService.TaskExecutorInfo;
|
||||||
|
};
|
||||||
|
|
||||||
export type PermissionData = {
|
export type PermissionData = {
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
|
|||||||
@ -271,4 +271,6 @@ export default {
|
|||||||
adminDeleteWhitelistEntry: (email: string) =>
|
adminDeleteWhitelistEntry: (email: string) =>
|
||||||
`${ExternalApi}${api_host}/admin/whitelist/${email}`,
|
`${ExternalApi}${api_host}/admin/whitelist/${email}`,
|
||||||
adminImportWhitelist: `${ExternalApi}${api_host}/admin/whitelist/batch`,
|
adminImportWhitelist: `${ExternalApi}${api_host}/admin/whitelist/batch`,
|
||||||
|
|
||||||
|
adminGetSystemVersion: `${ExternalApi}${api_host}/admin/version`,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -81,7 +81,15 @@ module.exports = {
|
|||||||
'text-primary': {
|
'text-primary': {
|
||||||
DEFAULT: 'rgb(var(--text-primary) / <alpha-value>)',
|
DEFAULT: 'rgb(var(--text-primary) / <alpha-value>)',
|
||||||
},
|
},
|
||||||
'text-secondary': 'var(--text-secondary)',
|
'text-primary-inverse': {
|
||||||
|
DEFAULT: 'rgb(var(--text-primary-inverse) / <alpha-value>)',
|
||||||
|
},
|
||||||
|
'text-secondary': {
|
||||||
|
DEFAULT: 'rgb(var(--text-secondary) / <alpha-value>)',
|
||||||
|
},
|
||||||
|
'text-secondary-inverse': {
|
||||||
|
DEFAULT: 'rgb(var(--text-secondary-inverse) / <alpha-value>)',
|
||||||
|
},
|
||||||
'text-disabled': 'var(--text-disabled)',
|
'text-disabled': 'var(--text-disabled)',
|
||||||
'text-input-tip': 'var(--text-input-tip)',
|
'text-input-tip': 'var(--text-input-tip)',
|
||||||
'border-default': 'var(--border-default)',
|
'border-default': 'var(--border-default)',
|
||||||
|
|||||||
@ -102,7 +102,7 @@
|
|||||||
|
|
||||||
/* #161618 */
|
/* #161618 */
|
||||||
--text-primary: 22 22 24;
|
--text-primary: 22 22 24;
|
||||||
--text-secondary: #75787a;
|
--text-secondary: 117 120 122;
|
||||||
--text-disabled: #b2b5b7;
|
--text-disabled: #b2b5b7;
|
||||||
/* input placeholder color */
|
/* input placeholder color */
|
||||||
--text-input-tip: #b2b5b7;
|
--text-input-tip: #b2b5b7;
|
||||||
@ -128,6 +128,10 @@
|
|||||||
--bg-group: rgba(90, 183, 126, 0.1);
|
--bg-group: rgba(90, 183, 126, 0.1);
|
||||||
--bg-member: rgba(92, 150, 200, 0.1);
|
--bg-member: rgba(92, 150, 200, 0.1);
|
||||||
--bg-department: rgba(136, 102, 211, 0.1);
|
--bg-department: rgba(136, 102, 211, 0.1);
|
||||||
|
|
||||||
|
/* --*-inverse: colors in dark mode */
|
||||||
|
--text-primary-inverse: 246 246 247;
|
||||||
|
--text-secondary-inverse: 178 181 183;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@ -236,12 +240,16 @@
|
|||||||
|
|
||||||
/* #f6f6f7 */
|
/* #f6f6f7 */
|
||||||
--text-primary: 246 246 247;
|
--text-primary: 246 246 247;
|
||||||
--text-secondary: #b2b5b7;
|
--text-secondary: 178 181 183;
|
||||||
--text-disabled: #75787a;
|
--text-disabled: #75787a;
|
||||||
--text-input-tip: #75787a;
|
--text-input-tip: #75787a;
|
||||||
--border-default: rgba(255, 255, 255, 0.2);
|
--border-default: rgba(255, 255, 255, 0.2);
|
||||||
--border-accent: #ffffff;
|
--border-accent: #ffffff;
|
||||||
--border-button: rgba(255, 255, 255, 0.1);
|
--border-button: rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
|
/* *-inverse: colors in light mode */
|
||||||
|
--text-primary-inverse: 22 22 24;
|
||||||
|
--text-secondary-inverse: 117 120 122;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user