From df247bbfb636e99114e24bb1c83e4c0ae67f7b3a Mon Sep 17 00:00:00 2001 From: MartialBE Date: Sat, 6 Apr 2024 17:16:47 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20support=20token=20limit=20i?= =?UTF-8?q?p=20range=20and=20models?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/Token/component/EditModal.js | 254 ++++++++++-------- 1 file changed, 148 insertions(+), 106 deletions(-) diff --git a/web/berry/src/views/Token/component/EditModal.js b/web/berry/src/views/Token/component/EditModal.js index a5e08be1..2685eab3 100644 --- a/web/berry/src/views/Token/component/EditModal.js +++ b/web/berry/src/views/Token/component/EditModal.js @@ -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 = ; +const checkedIcon = ; +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 ( - + - {tokenId ? "编辑令牌" : "新建令牌"} + {tokenId ? '编辑令牌' : '新建令牌'} - - 注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。 - - - {({ - errors, - handleBlur, - handleChange, - handleSubmit, - touched, - values, - setFieldError, - setFieldValue, - isSubmitting, - }) => ( + 注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。 + + {({ errors, handleBlur, handleChange, handleSubmit, touched, values, setFieldError, setFieldValue, isSubmitting }) => (
- + 名称 { 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 }) => { )} + + { + const event = { + target: { + name: 'models', + value: value + } + }; + handleChange(event); + }} + onBlur={handleBlur} + // filterSelectedOptions + disableCloseOnSelect + renderInput={(params) => } + 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 }) => ( +
  • + + {option} +
  • + )} + /> + {errors.models ? ( + + {errors.models} + + ) : ( + 请选择允许使用的模型,留空则不进行限制 + )} +
    + + IP 限制 + + {touched.subnet && errors.subnet ? ( + + {errors.subnet} + + ) : ( + + 请输入允许访问的网段,例如:192.168.0.0/24,请使用英文逗号分隔多个网段 + + )} + {values.expired_time !== -1 && ( - - + + { 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'] + } }} /> {errors.expired_time && ( - + {errors.expired_time} )} @@ -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); } }} - />{" "} + />{' '} 永不过期 - - - 额度 - + + 额度 - {renderQuotaWithPrompt(values.remain_quota)} - - } + endAdornment={{renderQuotaWithPrompt(values.remain_quota)}} 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 && ( - + {errors.remain_quota} )} @@ -243,19 +291,13 @@ const EditModal = ({ open, tokenId, onCancel, onOk }) => { { - setFieldValue("unlimited_quota", !values.unlimited_quota); + setFieldValue('unlimited_quota', !values.unlimited_quota); }} - />{" "} + />{' '} 无限额度 - @@ -273,5 +315,5 @@ EditModal.propTypes = { open: PropTypes.bool, tokenId: PropTypes.number, onCancel: PropTypes.func, - onOk: PropTypes.func, + onOk: PropTypes.func };