feat: support token limit ip range and models

This commit is contained in:
MartialBE 2024-04-06 17:16:47 +08:00
parent eb482990c1
commit df247bbfb6
No known key found for this signature in database
GPG Key ID: F5A7AC860020C896

View File

@ -1,9 +1,9 @@
import PropTypes from "prop-types";
import * as Yup from "yup";
import { Formik } from "formik";
import { useTheme } from "@mui/material/styles";
import { useState, useEffect } from "react";
import dayjs from "dayjs";
import PropTypes from 'prop-types';
import * as Yup from 'yup';
import { Formik } from 'formik';
import { useTheme } from '@mui/material/styles';
import { useState, useEffect } from 'react';
import dayjs from 'dayjs';
import {
Dialog,
DialogTitle,
@ -16,53 +16,66 @@ import {
InputLabel,
OutlinedInput,
InputAdornment,
Autocomplete,
Checkbox,
TextField,
Switch,
FormHelperText,
} from "@mui/material";
FormHelperText
} from '@mui/material';
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker";
import { renderQuotaWithPrompt, showSuccess, showError } from "utils/common";
import { API } from "utils/api";
require("dayjs/locale/zh-cn");
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
import { renderQuotaWithPrompt, showSuccess, showError } from 'utils/common';
import { API } from 'utils/api';
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
import { createFilterOptions } from '@mui/material/Autocomplete';
require('dayjs/locale/zh-cn');
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
const filter = createFilterOptions();
const validationSchema = Yup.object().shape({
is_edit: Yup.boolean(),
name: Yup.string().required("名称 不能为空"),
remain_quota: Yup.number().min(0, "必须大于等于0"),
name: Yup.string().required('名称 不能为空'),
remain_quota: Yup.number().min(0, '必须大于等于0'),
expired_time: Yup.number(),
unlimited_quota: Yup.boolean(),
unlimited_quota: Yup.boolean()
});
const originInputs = {
is_edit: false,
name: "",
name: '',
remain_quota: 0,
expired_time: -1,
unlimited_quota: false,
subnet: '',
models: []
};
const EditModal = ({ open, tokenId, onCancel, onOk }) => {
const theme = useTheme();
const [inputs, setInputs] = useState(originInputs);
const [modelOptions, setModelOptions] = useState([]);
const submit = async (values, { setErrors, setStatus, setSubmitting }) => {
setSubmitting(true);
values.remain_quota = parseInt(values.remain_quota);
let res;
let models = values.models.join(',');
if (values.is_edit) {
res = await API.put(`/api/token/`, { ...values, id: parseInt(tokenId) });
res = await API.put(`/api/token/`, { ...values, id: parseInt(tokenId), models: models });
} else {
res = await API.post(`/api/token/`, values);
res = await API.post(`/api/token/`, { ...values, models: models });
}
const { success, message } = res.data;
if (success) {
if (values.is_edit) {
showSuccess("令牌更新成功!");
showSuccess('令牌更新成功!');
} else {
showSuccess("令牌创建成功,请在列表页面点击复制获取令牌!");
showSuccess('令牌创建成功,请在列表页面点击复制获取令牌!');
}
setSubmitting(false);
setStatus({ success: true });
@ -78,61 +91,55 @@ const EditModal = ({ open, tokenId, onCancel, onOk }) => {
const { success, message, data } = res.data;
if (success) {
data.is_edit = true;
if (data.models === '') {
data.models = [];
} else {
data.models = data.models.split(',');
}
setInputs(data);
} else {
showError(message);
}
};
const loadAvailableModels = async () => {
let res = await API.get(`/api/user/available_models`);
const { success, message, data } = res.data;
if (success) {
setModelOptions(data);
} else {
showError(message);
}
};
useEffect(() => {
if (tokenId) {
loadToken().then();
} else {
setInputs({...originInputs});
setInputs({ ...originInputs });
}
loadAvailableModels().then();
}, [tokenId]);
return (
<Dialog open={open} onClose={onCancel} fullWidth maxWidth={"md"}>
<Dialog open={open} onClose={onCancel} fullWidth maxWidth={'md'}>
<DialogTitle
sx={{
margin: "0px",
margin: '0px',
fontWeight: 700,
lineHeight: "1.55556",
padding: "24px",
fontSize: "1.125rem",
lineHeight: '1.55556',
padding: '24px',
fontSize: '1.125rem'
}}
>
{tokenId ? "编辑令牌" : "新建令牌"}
{tokenId ? '编辑令牌' : '新建令牌'}
</DialogTitle>
<Divider />
<DialogContent>
<Alert severity="info">
注意令牌的额度仅用于限制令牌本身的最大额度使用量实际的使用受到账户的剩余额度限制
</Alert>
<Formik
initialValues={inputs}
enableReinitialize
validationSchema={validationSchema}
onSubmit={submit}
>
{({
errors,
handleBlur,
handleChange,
handleSubmit,
touched,
values,
setFieldError,
setFieldValue,
isSubmitting,
}) => (
<Alert severity="info">注意令牌的额度仅用于限制令牌本身的最大额度使用量实际的使用受到账户的剩余额度限制</Alert>
<Formik initialValues={inputs} enableReinitialize validationSchema={validationSchema} onSubmit={submit}>
{({ errors, handleBlur, handleChange, handleSubmit, touched, values, setFieldError, setFieldValue, isSubmitting }) => (
<form noValidate onSubmit={handleSubmit}>
<FormControl
fullWidth
error={Boolean(touched.name && errors.name)}
sx={{ ...theme.typography.otherInput }}
>
<FormControl fullWidth error={Boolean(touched.name && errors.name)} sx={{ ...theme.typography.otherInput }}>
<InputLabel htmlFor="channel-name-label">名称</InputLabel>
<OutlinedInput
id="channel-name-label"
@ -142,7 +149,7 @@ const EditModal = ({ open, tokenId, onCancel, onOk }) => {
name="name"
onBlur={handleBlur}
onChange={handleChange}
inputProps={{ autoComplete: "name" }}
inputProps={{ autoComplete: 'name' }}
aria-describedby="helper-text-channel-name-label"
/>
{touched.name && errors.name && (
@ -151,42 +158,99 @@ const EditModal = ({ open, tokenId, onCancel, onOk }) => {
</FormHelperText>
)}
</FormControl>
<FormControl fullWidth sx={{ ...theme.typography.otherInput }}>
<Autocomplete
multiple
freeSolo
id="channel-models-label"
options={modelOptions}
value={values.models}
onChange={(e, value) => {
const event = {
target: {
name: 'models',
value: value
}
};
handleChange(event);
}}
onBlur={handleBlur}
// filterSelectedOptions
disableCloseOnSelect
renderInput={(params) => <TextField {...params} name="models" error={Boolean(errors.models)} label="模型范围" />}
filterOptions={(options, params) => {
const filtered = filter(options, params);
const { inputValue } = params;
const isExisting = options.some((option) => inputValue === option);
if (inputValue !== '' && !isExisting) {
filtered.push(inputValue);
}
return filtered;
}}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox icon={icon} checkedIcon={checkedIcon} style={{ marginRight: 8 }} checked={selected} />
{option}
</li>
)}
/>
{errors.models ? (
<FormHelperText error id="helper-tex-channel-models-label">
{errors.models}
</FormHelperText>
) : (
<FormHelperText id="helper-tex-channel-models-label">请选择允许使用的模型留空则不进行限制</FormHelperText>
)}
</FormControl>
<FormControl fullWidth error={Boolean(touched.subnet && errors.subnet)} sx={{ ...theme.typography.otherInput }}>
<InputLabel htmlFor="channel-subnet-label">IP 限制</InputLabel>
<OutlinedInput
id="channel-subnet-label"
label="IP 限制"
type="text"
value={values.subnet}
name="subnet"
onBlur={handleBlur}
onChange={handleChange}
inputProps={{ autoComplete: 'subnet' }}
aria-describedby="helper-text-channel-subnet-label"
/>
{touched.subnet && errors.subnet ? (
<FormHelperText error id="helper-tex-channel-subnet-label">
{errors.subnet}
</FormHelperText>
) : (
<FormHelperText id="helper-tex-channel-subnet-label">
请输入允许访问的网段例如192.168.0.0/24请使用英文逗号分隔多个网段
</FormHelperText>
)}
</FormControl>
{values.expired_time !== -1 && (
<FormControl
fullWidth
error={Boolean(touched.expired_time && errors.expired_time)}
sx={{ ...theme.typography.otherInput }}
>
<LocalizationProvider
dateAdapter={AdapterDayjs}
adapterLocale={"zh-cn"}
>
<FormControl fullWidth error={Boolean(touched.expired_time && errors.expired_time)} sx={{ ...theme.typography.otherInput }}>
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale={'zh-cn'}>
<DateTimePicker
label="过期时间"
ampm={false}
value={dayjs.unix(values.expired_time)}
onError={(newError) => {
if (newError === null) {
setFieldError("expired_time", null);
setFieldError('expired_time', null);
} else {
setFieldError("expired_time", "无效的日期");
setFieldError('expired_time', '无效的日期');
}
}}
onChange={(newValue) => {
setFieldValue("expired_time", newValue.unix());
setFieldValue('expired_time', newValue.unix());
}}
slotProps={{
actionBar: {
actions: ["today", "accept"],
},
actions: ['today', 'accept']
}
}}
/>
</LocalizationProvider>
{errors.expired_time && (
<FormHelperText
error
id="helper-tex-channel-expired_time-label"
>
<FormHelperText error id="helper-tex-channel-expired_time-label">
{errors.expired_time}
</FormHelperText>
)}
@ -196,35 +260,22 @@ const EditModal = ({ open, tokenId, onCancel, onOk }) => {
checked={values.expired_time === -1}
onClick={() => {
if (values.expired_time === -1) {
setFieldValue(
"expired_time",
Math.floor(Date.now() / 1000)
);
setFieldValue('expired_time', Math.floor(Date.now() / 1000));
} else {
setFieldValue("expired_time", -1);
setFieldValue('expired_time', -1);
}
}}
/>{" "}
/>{' '}
永不过期
<FormControl
fullWidth
error={Boolean(touched.remain_quota && errors.remain_quota)}
sx={{ ...theme.typography.otherInput }}
>
<InputLabel htmlFor="channel-remain_quota-label">
额度
</InputLabel>
<FormControl fullWidth error={Boolean(touched.remain_quota && errors.remain_quota)} sx={{ ...theme.typography.otherInput }}>
<InputLabel htmlFor="channel-remain_quota-label">额度</InputLabel>
<OutlinedInput
id="channel-remain_quota-label"
label="额度"
type="number"
value={values.remain_quota}
name="remain_quota"
endAdornment={
<InputAdornment position="end">
{renderQuotaWithPrompt(values.remain_quota)}
</InputAdornment>
}
endAdornment={<InputAdornment position="end">{renderQuotaWithPrompt(values.remain_quota)}</InputAdornment>}
onBlur={handleBlur}
onChange={handleChange}
aria-describedby="helper-text-channel-remain_quota-label"
@ -232,10 +283,7 @@ const EditModal = ({ open, tokenId, onCancel, onOk }) => {
/>
{touched.remain_quota && errors.remain_quota && (
<FormHelperText
error
id="helper-tex-channel-remain_quota-label"
>
<FormHelperText error id="helper-tex-channel-remain_quota-label">
{errors.remain_quota}
</FormHelperText>
)}
@ -243,19 +291,13 @@ const EditModal = ({ open, tokenId, onCancel, onOk }) => {
<Switch
checked={values.unlimited_quota === true}
onClick={() => {
setFieldValue("unlimited_quota", !values.unlimited_quota);
setFieldValue('unlimited_quota', !values.unlimited_quota);
}}
/>{" "}
/>{' '}
无限额度
<DialogActions>
<Button onClick={onCancel}>取消</Button>
<Button
disableElevation
disabled={isSubmitting}
type="submit"
variant="contained"
color="primary"
>
<Button disableElevation disabled={isSubmitting} type="submit" variant="contained" color="primary">
提交
</Button>
</DialogActions>
@ -273,5 +315,5 @@ EditModal.propTypes = {
open: PropTypes.bool,
tokenId: PropTypes.number,
onCancel: PropTypes.func,
onOk: PropTypes.func,
onOk: PropTypes.func
};