Merge pull request #979 from SuanmoSuanyangTechnology/feature/apikey_zy
feat(web): create api support rate_limit & daily_request_limit config
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
import { type FC, type ReactNode, useEffect, useState } from 'react';
|
import { type FC, type ReactNode, useEffect, useState } from 'react';
|
||||||
import { Slider, type SliderSingleProps, Flex, InputNumber, type InputNumberProps } from 'antd';
|
import { Slider, type SliderSingleProps, Flex, InputNumber, type InputNumberProps } from 'antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
/** Props interface for RbSlider component */
|
/** Props interface for RbSlider component */
|
||||||
interface RbSliderProps extends SliderSingleProps {
|
interface RbSliderProps extends SliderSingleProps {
|
||||||
@@ -41,12 +42,13 @@ const RbSlider: FC<RbSliderProps> = ({
|
|||||||
step = 0.01,
|
step = 0.01,
|
||||||
size = 'default' ,
|
size = 'default' ,
|
||||||
isInput = false,
|
isInput = false,
|
||||||
className = '',
|
className = 'rb:pl-1!',
|
||||||
prefix,
|
prefix,
|
||||||
inputClassName,
|
inputClassName,
|
||||||
disabled,
|
disabled,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [curValue, setCurValue] = useState<SliderSingleProps['value']>(0)
|
const [curValue, setCurValue] = useState<SliderSingleProps['value']>(0)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurValue(value)
|
setCurValue(value)
|
||||||
@@ -102,6 +104,7 @@ const RbSlider: FC<RbSliderProps> = ({
|
|||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
prefix={prefix}
|
prefix={prefix}
|
||||||
className={`${inputClassName || '' } rb:w-20!`}
|
className={`${inputClassName || '' } rb:w-20!`}
|
||||||
|
placeholder={t('common.pleaseEnter')}
|
||||||
/>
|
/>
|
||||||
: <div className="rb:text-[14px] rb:text-[#155EEF] rb:leading-5">{curValue || min}</div>
|
: <div className="rb:text-[14px] rb:text-[#155EEF] rb:leading-5">{curValue || min}</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,16 +106,28 @@ const ApiKeyDetailModal = forwardRef<ApiKeyModalRef, { handleCopy: (content: str
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data.expires_at && <>
|
<div className="rb:text-[#5B6167] rb:font-medium rb:leading-5 rb:my-4">{t('apiKey.advancedSettings')}</div>
|
||||||
<div className="rb:text-[#5B6167] rb:font-medium rb:leading-5 rb:my-4">{t('apiKey.advancedSettings')}</div>
|
|
||||||
|
|
||||||
|
{data.expires_at &&
|
||||||
<div className="rb:flex rb:justify-between rb:gap-5 rb:font-regular rb:text-[14px] rb:mt-3">
|
<div className="rb:flex rb:justify-between rb:gap-5 rb:font-regular rb:text-[14px] rb:mt-3">
|
||||||
<span className="rb:text-[#5B6167]">{t(`apiKey.expires_at`)}</span>
|
<span className="rb:text-[#5B6167]">{t(`apiKey.expires_at`)}</span>
|
||||||
<span>
|
<span>
|
||||||
{data.expires_at ? formatDateTime(data.expires_at as number, 'YYYY-MM-DD HH:mm:ss') : '-'}
|
{data.expires_at ? formatDateTime(data.expires_at as number, 'YYYY-MM-DD HH:mm:ss') : '-'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</>}
|
}
|
||||||
|
<div className="rb:flex rb:justify-between rb:gap-5 rb:font-regular rb:text-[14px] rb:mt-3">
|
||||||
|
<span className="rb:text-[#5B6167]">{t(`application.qpsLimit`)}</span>
|
||||||
|
<span>
|
||||||
|
{data.rate_limit} {t('application.qpsLimitUnit')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="rb:flex rb:justify-between rb:gap-5 rb:font-regular rb:text-[14px] rb:mt-3">
|
||||||
|
<span className="rb:text-[#5B6167]">{t(`application.dailyUsageLimit`)}</span>
|
||||||
|
<span>
|
||||||
|
{data.daily_request_limit} {t('application.dailyUsageLimitUnit')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</RbModal>
|
</RbModal>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type { ApiKey, ApiKeyModalRef } from '../types';
|
|||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
import { createApiKey, updateApiKey } from '@/api/apiKey';
|
import { createApiKey, updateApiKey } from '@/api/apiKey';
|
||||||
import { stringRegExp } from '@/utils/validator';
|
import { stringRegExp } from '@/utils/validator';
|
||||||
|
import RbSlider from '@/components/RbSlider'
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
@@ -57,11 +58,10 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
|
|||||||
*/
|
*/
|
||||||
const handleOpen = (apiKey?: ApiKey) => {
|
const handleOpen = (apiKey?: ApiKey) => {
|
||||||
if (apiKey?.id) {
|
if (apiKey?.id) {
|
||||||
const { scopes = [], expires_at } = apiKey
|
const { scopes = [], expires_at, ...rest } = apiKey
|
||||||
// Edit mode - populate form with existing data
|
// Edit mode - populate form with existing data
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
name: apiKey.name,
|
...rest,
|
||||||
description: apiKey.description,
|
|
||||||
memory: scopes.includes('memory'),
|
memory: scopes.includes('memory'),
|
||||||
rag: scopes.includes('rag'),
|
rag: scopes.includes('rag'),
|
||||||
expires_at: expires_at ? dayjs(expires_at) : undefined
|
expires_at: expires_at ? dayjs(expires_at) : undefined
|
||||||
@@ -126,6 +126,10 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
|
|||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
|
initialValues={{
|
||||||
|
rate_limit: 50,
|
||||||
|
daily_request_limit: 100000
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="rb:text-[#5B6167] rb:font-medium rb:leading-5 rb:mb-4">{t('apiKey.baseInfo')}</div>
|
<div className="rb:text-[#5B6167] rb:font-medium rb:leading-5 rb:mb-4">{t('apiKey.baseInfo')}</div>
|
||||||
<FormItem
|
<FormItem
|
||||||
@@ -179,6 +183,36 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
|
|||||||
disabledDate={(current) => current && current < dayjs().subtract(1, 'day').endOf('day')}
|
disabledDate={(current) => current && current < dayjs().subtract(1, 'day').endOf('day')}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="rate_limit"
|
||||||
|
label={<>{t(`application.qpsLimit`)}({t('application.qpsLimitTip')}, {t('application.qpsLimitUnit')})</>}
|
||||||
|
extra={t('application.qpsLimitDesc')}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: t('common.pleaseEnter') },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<RbSlider
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
isInput={true}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="daily_request_limit"
|
||||||
|
label={<>{t(`application.dailyUsageLimit`)} ({t('application.dailyUsageLimitUnit')})</>}
|
||||||
|
extra={t('application.dailyUsageLimitDesc')}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: t('common.pleaseEnter') },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<RbSlider
|
||||||
|
min={100}
|
||||||
|
max={100000}
|
||||||
|
step={100}
|
||||||
|
isInput={true}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
</Form>
|
</Form>
|
||||||
</RbModal>
|
</RbModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -195,53 +195,55 @@ const Api: FC<{ application: Application | null }> = ({ application }) => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{/* API Key List */}
|
{/* API Key List */}
|
||||||
{apiKeyList.sort((a, b) => b.created_at - a.created_at).map(item => (
|
<Flex vertical gap={12}>
|
||||||
<div key={item.id} className="rb:p-4 rb-border rb:rounded-xl">
|
{apiKeyList.sort((a, b) => b.created_at - a.created_at).map(item => (
|
||||||
<Flex align="center" justify="space-between">
|
<div key={item.id} className="rb:p-4 rb-border rb:rounded-xl">
|
||||||
<Flex vertical className="rb:max-w-[calc(100%-92px)]" gap={4}>
|
<Flex align="center" justify="space-between">
|
||||||
<div className="rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap rb:flex-1 rb:leading-5 rb:font-medium">{item.name}</div>
|
<Flex vertical className="rb:max-w-[calc(100%-92px)]" gap={4}>
|
||||||
<div className="rb:text-[#5B6167] rb:leading-4.5">ID: {item.id}</div>
|
<div className="rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap rb:flex-1 rb:leading-5 rb:font-medium">{item.name}</div>
|
||||||
</Flex>
|
<div className="rb:text-[#5B6167] rb:leading-4.5">ID: {item.id}</div>
|
||||||
<Space size={12}>
|
|
||||||
<div
|
|
||||||
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
|
||||||
onClick={() => handleEdit(item)}
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
|
||||||
onClick={() => handleDelete(item)}
|
|
||||||
></div>
|
|
||||||
</Space>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Row className="rb:mt-4">
|
|
||||||
<Col span={8}>
|
|
||||||
<Row className="rb:px-4 rb:py-2">
|
|
||||||
<Col span={12}>
|
|
||||||
<div className="rb:font-[MiSans-Bold] rb:font-bold rb:text-[16px] rb:leading-5.5">{item.total_requests}</div>
|
|
||||||
<div className="rb:mt-1 rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5">{t('application.apiKeyRequestTotal')}</div>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<div className="rb:font-[MiSans-Bold] rb:font-bold rb:text-[16px] rb:leading-5.5">{item.rate_limit}</div>
|
|
||||||
<div className="rb:mt-1 rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5">{t('application.qpsLimit')}</div>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
<Col span={16}>
|
|
||||||
<Flex align="center" justify="space-between" className="rb:text-[#5B6167] rb:py-5! rb:px-4! rb:bg-white rb-border rb:rounded-lg rb:leading-5">
|
|
||||||
{maskApiKeys(item.api_key)}
|
|
||||||
|
|
||||||
<Button className="rb:px-2! rb:h-7! rb:group rb:-mt-1.75!" onClick={() => handleCopy(item.api_key)}>
|
|
||||||
<div
|
|
||||||
className="rb:w-4 rb:h-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/copy.svg')] rb:group-hover:bg-[url('@/assets/images/copy_active.svg')]"
|
|
||||||
></div>
|
|
||||||
{t('common.copy')}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Col>
|
<Space size={12}>
|
||||||
</Row>
|
<div
|
||||||
</div>
|
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
||||||
))}
|
onClick={() => handleEdit(item)}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
||||||
|
onClick={() => handleDelete(item)}
|
||||||
|
></div>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Row className="rb:mt-4">
|
||||||
|
<Col span={8}>
|
||||||
|
<Row className="rb:px-4 rb:py-2">
|
||||||
|
<Col span={12}>
|
||||||
|
<div className="rb:font-[MiSans-Bold] rb:font-bold rb:text-[16px] rb:leading-5.5">{item.total_requests}</div>
|
||||||
|
<div className="rb:mt-1 rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5">{t('application.apiKeyRequestTotal')}</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<div className="rb:font-[MiSans-Bold] rb:font-bold rb:text-[16px] rb:leading-5.5">{item.rate_limit}</div>
|
||||||
|
<div className="rb:mt-1 rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5">{t('application.qpsLimit')}</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
<Col span={16}>
|
||||||
|
<Flex align="center" justify="space-between" className="rb:text-[#5B6167] rb:py-5! rb:px-4! rb:bg-white rb-border rb:rounded-lg rb:leading-5">
|
||||||
|
{maskApiKeys(item.api_key)}
|
||||||
|
|
||||||
|
<Button className="rb:px-2! rb:h-7! rb:group rb:-mt-1.75!" onClick={() => handleCopy(item.api_key)}>
|
||||||
|
<div
|
||||||
|
className="rb:w-4 rb:h-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/copy.svg')] rb:group-hover:bg-[url('@/assets/images/copy_active.svg')]"
|
||||||
|
></div>
|
||||||
|
{t('common.copy')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
</RbCard>
|
</RbCard>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import type { Application } from '@/views/ApplicationManagement/types'
|
|||||||
import type { ApiKeyModalRef } from '../types'
|
import type { ApiKeyModalRef } from '../types'
|
||||||
import { createApiKey } from '@/api/apiKey';
|
import { createApiKey } from '@/api/apiKey';
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
|
import RbSlider from '@/components/RbSlider'
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
@@ -97,6 +98,10 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, ApiKeyModalProps>(({
|
|||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
|
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
|
||||||
|
initialValues={{
|
||||||
|
rate_limit: 50,
|
||||||
|
daily_request_limit: 100000
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{/* Key name */}
|
{/* Key name */}
|
||||||
<FormItem
|
<FormItem
|
||||||
@@ -116,6 +121,36 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, ApiKeyModalProps>(({
|
|||||||
>
|
>
|
||||||
<Input.TextArea placeholder={t('application.apiKeyDescPlaceholder')} />
|
<Input.TextArea placeholder={t('application.apiKeyDescPlaceholder')} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="rate_limit"
|
||||||
|
label={<>{t(`application.qpsLimit`)}({t('application.qpsLimitTip')}, {t('application.qpsLimitUnit')})</>}
|
||||||
|
extra={t('application.qpsLimitDesc')}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: t('common.pleaseEnter') },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<RbSlider
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
isInput={true}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="daily_request_limit"
|
||||||
|
label={<>{t(`application.dailyUsageLimit`)} ({t('application.dailyUsageLimitUnit')})</>}
|
||||||
|
extra={t('application.dailyUsageLimitDesc')}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: t('common.pleaseEnter') },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<RbSlider
|
||||||
|
min={100}
|
||||||
|
max={100000}
|
||||||
|
step={100}
|
||||||
|
isInput={true}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
</Form>
|
</Form>
|
||||||
</RbModal>
|
</RbModal>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user