chore: access setting notes
This commit is contained in:
@@ -98,7 +98,8 @@ const CustomItem = styled.div`
|
|||||||
background-color: var(--ant-color-bg-elevated);
|
background-color: var(--ant-color-bg-elevated);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ExtraContent = () => {
|
export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
|
||||||
|
const { isDarkTheme } = props;
|
||||||
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
||||||
const [modal, contextHolder] = Modal.useModal();
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
const [version] = useAtom(GPUStackVersionAtom);
|
const [version] = useAtom(GPUStackVersionAtom);
|
||||||
@@ -130,6 +131,16 @@ export const ExtraContent = () => {
|
|||||||
initialState?.currentUser?.is_admin
|
initialState?.currentUser?.is_admin
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const avatarStyle = useMemo(() => {
|
||||||
|
if (isDarkTheme) {
|
||||||
|
return {
|
||||||
|
color: 'var(--ant-color-text)',
|
||||||
|
border: 'none'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}, [isDarkTheme]);
|
||||||
|
|
||||||
const showVersion = () => {
|
const showVersion = () => {
|
||||||
saveScrollHeight();
|
saveScrollHeight();
|
||||||
modal.info({
|
modal.info({
|
||||||
@@ -260,6 +271,7 @@ export const ExtraContent = () => {
|
|||||||
<span className="flex-center gap-8">
|
<span className="flex-center gap-8">
|
||||||
<Avatar
|
<Avatar
|
||||||
size={24}
|
size={24}
|
||||||
|
style={{ ...avatarStyle }}
|
||||||
src={initialState?.currentUser?.avatar}
|
src={initialState?.currentUser?.avatar}
|
||||||
icon={
|
icon={
|
||||||
<IconFont type="icon-user-filled" className="font-size-24" />
|
<IconFont type="icon-user-filled" className="font-size-24" />
|
||||||
@@ -309,6 +321,7 @@ export const ExtraContent = () => {
|
|||||||
<IconWrapper>
|
<IconWrapper>
|
||||||
<Avatar
|
<Avatar
|
||||||
size={24}
|
size={24}
|
||||||
|
style={{ ...avatarStyle }}
|
||||||
src={initialState?.currentUser?.avatar}
|
src={initialState?.currentUser?.avatar}
|
||||||
icon={<IconFont type="icon-user-filled" className="font-size-24" />}
|
icon={<IconFont type="icon-user-filled" className="font-size-24" />}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -516,7 +516,12 @@ export default (props: any) => {
|
|||||||
onCollapse={onCollapse}
|
onCollapse={onCollapse}
|
||||||
onMenuHeaderClick={onMenuHeaderClick}
|
onMenuHeaderClick={onMenuHeaderClick}
|
||||||
menuHeaderRender={renderMenuHeader}
|
menuHeaderRender={renderMenuHeader}
|
||||||
extra={[<ExtraContent key="extra-content" />]}
|
extra={[
|
||||||
|
<ExtraContent
|
||||||
|
key="extra-content"
|
||||||
|
isDarkTheme={userSettings.isDarkTheme}
|
||||||
|
/>
|
||||||
|
]}
|
||||||
collapsed={userSettings.collapsed}
|
collapsed={userSettings.collapsed}
|
||||||
onPageChange={onPageChange}
|
onPageChange={onPageChange}
|
||||||
formatMessage={formatMessage}
|
formatMessage={formatMessage}
|
||||||
|
|||||||
@@ -178,6 +178,10 @@ export default {
|
|||||||
'models.table.accessScope': 'Access Scope',
|
'models.table.accessScope': 'Access Scope',
|
||||||
'models.table.accessScope.all': 'All users',
|
'models.table.accessScope.all': 'All users',
|
||||||
'models.table.userSelection': 'User Selection',
|
'models.table.userSelection': 'User Selection',
|
||||||
|
'models.button.accessSettings.tips':
|
||||||
|
'Changes to access settings take effect after one minute.',
|
||||||
|
'models.table.userSelection.tips':
|
||||||
|
'Administrators always have access to all models.',
|
||||||
'models.table.filterByName': 'Filter by username',
|
'models.table.filterByName': 'Filter by username',
|
||||||
'models.table.admin': 'Admin',
|
'models.table.admin': 'Admin',
|
||||||
'models.table.noselected': 'No users selected',
|
'models.table.noselected': 'No users selected',
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export default {
|
|||||||
'playground.audio.stoprecord': 'Stop Recording',
|
'playground.audio.stoprecord': 'Stop Recording',
|
||||||
'playground.audio.generating.tips': 'Generated text will appear here.',
|
'playground.audio.generating.tips': 'Generated text will appear here.',
|
||||||
'playground.audio.uploadfile.tips':
|
'playground.audio.uploadfile.tips':
|
||||||
'Upload an audio file, supported formats: {formats}',
|
'Upload an audio file, supported formats: {formats}.',
|
||||||
'playground.input.multiplePaste': 'Batch Input Mode',
|
'playground.input.multiplePaste': 'Batch Input Mode',
|
||||||
'playground.input.multiplePaste.tips':
|
'playground.input.multiplePaste.tips':
|
||||||
'When enabled, pasted multi-line text will be automatically split by newline into separate entries in the form.',
|
'When enabled, pasted multi-line text will be automatically split by newline into separate entries in the form.',
|
||||||
|
|||||||
@@ -179,6 +179,10 @@ export default {
|
|||||||
'models.table.accessScope': 'Access Scope',
|
'models.table.accessScope': 'Access Scope',
|
||||||
'models.table.accessScope.all': 'All users',
|
'models.table.accessScope.all': 'All users',
|
||||||
'models.table.userSelection': 'User Selection',
|
'models.table.userSelection': 'User Selection',
|
||||||
|
'models.button.accessSettings.tips':
|
||||||
|
'Changes to access settings take effect after one minute.',
|
||||||
|
'models.table.userSelection.tips':
|
||||||
|
'Administrators always have access to all models.',
|
||||||
'models.table.filterByName': 'Filter by username',
|
'models.table.filterByName': 'Filter by username',
|
||||||
'models.table.admin': 'Admin',
|
'models.table.admin': 'Admin',
|
||||||
'models.table.noselected': 'No users selected',
|
'models.table.noselected': 'No users selected',
|
||||||
@@ -338,4 +342,6 @@ export default {
|
|||||||
// 60. models.form.backend.mindie': 'Only supports Ascend NPUs.',
|
// 60. models.form.backend.mindie': 'Only supports Ascend NPUs.',
|
||||||
// 61. 'models.form.backend.sglang': 'Built-in support for NVIDIA/AMD GPUs and Ascend NPUs.',
|
// 61. 'models.form.backend.sglang': 'Built-in support for NVIDIA/AMD GPUs and Ascend NPUs.',
|
||||||
// 62. 'models.form.backend_parameters.vllm.tips': 'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
|
// 62. 'models.form.backend_parameters.vllm.tips': 'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
|
||||||
|
// 63. 'models.button.accessSettings.tips': 'Changes to access settings take effect after one minute.',
|
||||||
|
// 64. 'models.table.userSelection.tips': 'Administrators always have access to all models.',
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -178,6 +178,10 @@ export default {
|
|||||||
'models.table.accessScope': 'Область доступа',
|
'models.table.accessScope': 'Область доступа',
|
||||||
'models.table.accessScope.all': 'Все пользователи',
|
'models.table.accessScope.all': 'Все пользователи',
|
||||||
'models.table.userSelection': 'Выбор пользователей',
|
'models.table.userSelection': 'Выбор пользователей',
|
||||||
|
'models.button.accessSettings.tips':
|
||||||
|
'Changes to access settings take effect after one minute.',
|
||||||
|
'models.table.userSelection.tips':
|
||||||
|
'Administrators always have access to all models.',
|
||||||
'models.table.filterByName': 'Фильтр по имени пользователя',
|
'models.table.filterByName': 'Фильтр по имени пользователя',
|
||||||
'models.table.admin': 'Администратор',
|
'models.table.admin': 'Администратор',
|
||||||
'models.table.noselected': 'Пользователи не выбраны',
|
'models.table.noselected': 'Пользователи не выбраны',
|
||||||
@@ -266,4 +270,6 @@ export default {
|
|||||||
// 6. 'models.form.backend.sglang': 'Built-in support for NVIDIA/AMD GPUs and Ascend NPUs.',
|
// 6. 'models.form.backend.sglang': 'Built-in support for NVIDIA/AMD GPUs and Ascend NPUs.',
|
||||||
// 7. 'models.form.gpusAllocationType.auto.tips': 'The system automatically calculates the GPU count per replica, using powers of two by default and capped by the selected GPUs.',
|
// 7. 'models.form.gpusAllocationType.auto.tips': 'The system automatically calculates the GPU count per replica, using powers of two by default and capped by the selected GPUs.',
|
||||||
// 8. 'models.form.backend_parameters.vllm.tips': 'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
|
// 8. 'models.form.backend_parameters.vllm.tips': 'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
|
||||||
|
// 9. 'models.button.accessSettings.tips': 'Changes to access settings take effect after one minute.',
|
||||||
|
// 10. 'models.table.userSelection.tips': 'Administrators always have access to all models.',
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ export default {
|
|||||||
'models.table.accessScope': '访问范围',
|
'models.table.accessScope': '访问范围',
|
||||||
'models.table.accessScope.all': '所有用户',
|
'models.table.accessScope.all': '所有用户',
|
||||||
'models.table.userSelection': '用户选择',
|
'models.table.userSelection': '用户选择',
|
||||||
|
'models.button.accessSettings.tips': '访问设置的更改将在一分钟后生效。',
|
||||||
|
'models.table.userSelection.tips': '管理员始终拥有对所有模型的访问权限。',
|
||||||
'models.table.filterByName': '按用户名筛选',
|
'models.table.filterByName': '按用户名筛选',
|
||||||
'models.table.admin': '管理员',
|
'models.table.admin': '管理员',
|
||||||
'models.table.noselected': '未选择用户',
|
'models.table.noselected': '未选择用户',
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ const Label = styled.div`
|
|||||||
const AllowModelsForm: React.FC<{
|
const AllowModelsForm: React.FC<{
|
||||||
currentData?: Partial<ListItem> | null;
|
currentData?: Partial<ListItem> | null;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
}> = ({ currentData, action }) => {
|
onValuesChange?: (changedValues: any, allValues: any) => void;
|
||||||
|
}> = ({ currentData, action, onValuesChange }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { getRuleMessage } = useAppUtils();
|
const { getRuleMessage } = useAppUtils();
|
||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
@@ -102,6 +103,10 @@ const AllowModelsForm: React.FC<{
|
|||||||
})}
|
})}
|
||||||
onSelectChange={(selectedKeys) => {
|
onSelectChange={(selectedKeys) => {
|
||||||
form.setFieldsValue({ allowed_model_names: selectedKeys });
|
form.setFieldsValue({ allowed_model_names: selectedKeys });
|
||||||
|
onValuesChange?.(
|
||||||
|
{ allowed_model_names: selectedKeys },
|
||||||
|
form.getFieldsValue()
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import AllowModelsForm from './allow-models';
|
|||||||
const APIKeyForm: React.FC<{
|
const APIKeyForm: React.FC<{
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
currentData?: Partial<ListItem> | null;
|
currentData?: Partial<ListItem> | null;
|
||||||
}> = ({ action, currentData }) => {
|
onValuesChange?: (changedValues: any, allValues: any) => void;
|
||||||
|
}> = ({ action, currentData, onValuesChange }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -70,6 +71,7 @@ const APIKeyForm: React.FC<{
|
|||||||
<AllowModelsForm
|
<AllowModelsForm
|
||||||
currentData={currentData}
|
currentData={currentData}
|
||||||
action={action}
|
action={action}
|
||||||
|
onValuesChange={onValuesChange}
|
||||||
></AllowModelsForm>
|
></AllowModelsForm>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import AlertBlockInfo from '@/components/alert-info/block';
|
||||||
import CopyButton from '@/components/copy-button';
|
import CopyButton from '@/components/copy-button';
|
||||||
import ModalFooter from '@/components/modal-footer';
|
import ModalFooter from '@/components/modal-footer';
|
||||||
import GSDrawer from '@/components/scroller-modal/gs-drawer';
|
import GSDrawer from '@/components/scroller-modal/gs-drawer';
|
||||||
@@ -9,7 +10,7 @@ import { useIntl } from '@umijs/max';
|
|||||||
import { Form, Tag } from 'antd';
|
import { Form, Tag } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { createApisKey, updateApisKey } from '../../apis';
|
import { createApisKey, updateApisKey } from '../../apis';
|
||||||
import { expirationOptions } from '../../config';
|
import { expirationOptions } from '../../config';
|
||||||
import { FormData, ListItem } from '../../config/types';
|
import { FormData, ListItem } from '../../config/types';
|
||||||
@@ -37,6 +38,11 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
const [showKey, setShowKey] = useState(false);
|
const [showKey, setShowKey] = useState(false);
|
||||||
const [apikeyValue, setAPIKeyValue] = useState('');
|
const [apikeyValue, setAPIKeyValue] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [isChanged, setIsChanged] = useState(false);
|
||||||
|
const cacheFormRef = useRef<{
|
||||||
|
allowed_type: string;
|
||||||
|
allowed_model_names: string[];
|
||||||
|
}>({} as any);
|
||||||
|
|
||||||
const getExpireValue = (val: number | null) => {
|
const getExpireValue = (val: number | null) => {
|
||||||
const expires_in = val;
|
const expires_in = val;
|
||||||
@@ -128,6 +134,21 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
setShowKey(false);
|
setShowKey(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
|
||||||
|
const initialValues = cacheFormRef.current;
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
});
|
||||||
|
console.log('initialValues', initialValues, allValues);
|
||||||
|
if (
|
||||||
|
_.isEqual(initialValues, _.pick(allValues, Object.keys(initialValues)))
|
||||||
|
) {
|
||||||
|
setIsChanged(false);
|
||||||
|
} else {
|
||||||
|
setIsChanged(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const initValues = () => {
|
const initValues = () => {
|
||||||
if (action === PageAction.CREATE && open) {
|
if (action === PageAction.CREATE && open) {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
@@ -146,10 +167,19 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
allowed_model_names: currentData.allowed_model_names || []
|
allowed_model_names: currentData.allowed_model_names || []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheFormRef.current = {
|
||||||
|
allowed_type: form.getFieldValue('allowed_type'),
|
||||||
|
allowed_model_names: form.getFieldValue('allowed_model_names')
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initValues();
|
initValues();
|
||||||
|
if (!open) {
|
||||||
|
setIsChanged(false);
|
||||||
|
cacheFormRef.current = {} as any;
|
||||||
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -183,29 +213,45 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
}}
|
}}
|
||||||
footer={
|
footer={
|
||||||
!showKey ? (
|
!showKey ? (
|
||||||
<ModalFooter
|
<>
|
||||||
onOk={handleSumit}
|
{isChanged && (
|
||||||
onCancel={onCancel}
|
<div style={{ marginInline: 24 }}>
|
||||||
loading={loading}
|
<AlertBlockInfo
|
||||||
style={{
|
type="warning"
|
||||||
padding: '16px 24px',
|
style={{ marginBottom: 16 }}
|
||||||
display: 'flex',
|
contentStyle={{ paddingInline: 0 }}
|
||||||
justifyContent: 'flex-end'
|
message={intl.formatMessage({
|
||||||
}}
|
id: 'models.button.accessSettings.tips'
|
||||||
></ModalFooter>
|
})}
|
||||||
|
></AlertBlockInfo>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<ModalFooter
|
||||||
|
onOk={handleSumit}
|
||||||
|
onCancel={onCancel}
|
||||||
|
loading={loading}
|
||||||
|
style={{
|
||||||
|
padding: '16px 24px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
}}
|
||||||
|
></ModalFooter>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<ModalFooter
|
<>
|
||||||
onOk={handleDone}
|
<ModalFooter
|
||||||
onCancel={onCancel}
|
onOk={handleDone}
|
||||||
loading={loading}
|
onCancel={onCancel}
|
||||||
okText={intl.formatMessage({ id: 'common.button.done' })}
|
loading={loading}
|
||||||
showCancelBtn={false}
|
okText={intl.formatMessage({ id: 'common.button.done' })}
|
||||||
style={{
|
showCancelBtn={false}
|
||||||
padding: '16px 24px',
|
style={{
|
||||||
display: 'flex',
|
padding: '16px 24px',
|
||||||
justifyContent: 'flex-end'
|
display: 'flex',
|
||||||
}}
|
justifyContent: 'flex-end'
|
||||||
></ModalFooter>
|
}}
|
||||||
|
></ModalFooter>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -213,10 +259,15 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
name="addAPIKey"
|
name="addAPIKey"
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={handleOnOk}
|
onFinish={handleOnOk}
|
||||||
|
onValuesChange={handleOnValuesChange}
|
||||||
preserve={false}
|
preserve={false}
|
||||||
>
|
>
|
||||||
{!showKey && (
|
{!showKey && (
|
||||||
<APIKeyForm action={action} currentData={currentData}></APIKeyForm>
|
<APIKeyForm
|
||||||
|
action={action}
|
||||||
|
currentData={currentData}
|
||||||
|
onValuesChange={handleOnValuesChange}
|
||||||
|
></APIKeyForm>
|
||||||
)}
|
)}
|
||||||
{showKey && action === PageAction.CREATE && (
|
{showKey && action === PageAction.CREATE && (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
|
|||||||
@@ -243,9 +243,6 @@ const ClusterCreate = () => {
|
|||||||
const info = await queryClusterToken({ id: res.id });
|
const info = await queryClusterToken({ id: res.id });
|
||||||
setRegistrationInfo({
|
setRegistrationInfo({
|
||||||
...info,
|
...info,
|
||||||
image: info.container_registry
|
|
||||||
? `${info.container_registry}/${info.image}`
|
|
||||||
: info.image,
|
|
||||||
cluster_id: res.id
|
cluster_id: res.id
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -68,9 +68,6 @@ const AddWorker: React.FC<AddWorkerProps> = (props) => {
|
|||||||
firstLoad.current = false;
|
firstLoad.current = false;
|
||||||
setRegistrationInfo({
|
setRegistrationInfo({
|
||||||
...data,
|
...data,
|
||||||
image: data.container_registry
|
|
||||||
? `${data.container_registry}/${data.image}`
|
|
||||||
: data.image,
|
|
||||||
cluster_id: value
|
cluster_id: value
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -3,14 +3,24 @@ import { PageAction } from '@/config';
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import TransferInner from '@/pages/_components/transfer';
|
import TransferInner from '@/pages/_components/transfer';
|
||||||
import { queryUsersList } from '@/pages/users/apis';
|
import { queryUsersList } from '@/pages/users/apis';
|
||||||
import { DownOutlined } from '@ant-design/icons';
|
import { DownOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Checkbox, Dropdown, DropdownProps, Empty, Form, Radio } from 'antd';
|
import {
|
||||||
|
Checkbox,
|
||||||
|
Dropdown,
|
||||||
|
DropdownProps,
|
||||||
|
Empty,
|
||||||
|
Form,
|
||||||
|
Radio,
|
||||||
|
RadioChangeEvent,
|
||||||
|
Tooltip
|
||||||
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -30,10 +40,11 @@ interface AccessControlFormProps {
|
|||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
currentData?: ListItem | null;
|
currentData?: ListItem | null;
|
||||||
onFinish: (values: AccessControlFormData) => void;
|
onFinish: (values: AccessControlFormData) => void;
|
||||||
|
onValuesChange?: (changedValues: any, allValues: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
||||||
const { action, currentData, onFinish } = props;
|
const { action, currentData, onFinish, onValuesChange } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const accessPolicy = Form.useWatch('access_policy', form);
|
const accessPolicy = Form.useWatch('access_policy', form);
|
||||||
@@ -47,6 +58,7 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
|||||||
const [queryParams, setQueryParams] = useState<Global.SearchParams>({
|
const [queryParams, setQueryParams] = useState<Global.SearchParams>({
|
||||||
page: -1
|
page: -1
|
||||||
});
|
});
|
||||||
|
const formDataCacheRef = useRef<AccessControlFormData | null>(null);
|
||||||
|
|
||||||
const dataList = useMemo(() => {
|
const dataList = useMemo(() => {
|
||||||
if (filterInUsers.size === 0) {
|
if (filterInUsers.size === 0) {
|
||||||
@@ -85,7 +97,7 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnChange = (
|
const handleOnChange = async (
|
||||||
nextTargetKeys: TransferKey[],
|
nextTargetKeys: TransferKey[],
|
||||||
direction: string,
|
direction: string,
|
||||||
removeKeys: TransferKey[]
|
removeKeys: TransferKey[]
|
||||||
@@ -93,6 +105,10 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
|||||||
setTargetKeys(nextTargetKeys);
|
setTargetKeys(nextTargetKeys);
|
||||||
const users = nextTargetKeys.map((key) => ({ id: key }));
|
const users = nextTargetKeys.map((key) => ({ id: key }));
|
||||||
form.setFieldsValue({ users });
|
form.setFieldsValue({ users });
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
});
|
||||||
|
onValuesChange?.({ users }, form.getFieldsValue());
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSearch = (dir: 'left' | 'right', value: string) => {
|
const onSearch = (dir: 'left' | 'right', value: string) => {
|
||||||
@@ -115,6 +131,23 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnPolicyChange = async (e: RadioChangeEvent) => {
|
||||||
|
console.log('policy changed:', e.target.value);
|
||||||
|
const policy = e.target.value;
|
||||||
|
if (policy === 'allowed_users') {
|
||||||
|
form.setFieldsValue({ users: formDataCacheRef.current?.users || [] });
|
||||||
|
} else {
|
||||||
|
formDataCacheRef.current = {
|
||||||
|
access_policy: policy,
|
||||||
|
users: form.getFieldValue('users') || []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
});
|
||||||
|
onValuesChange?.({ access_policy: policy }, form.getFieldsValue());
|
||||||
|
};
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
submit: () => {
|
submit: () => {
|
||||||
form.submit();
|
form.submit();
|
||||||
@@ -222,16 +255,18 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
|||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={onFinish}
|
onFinish={onFinish}
|
||||||
preserve={false}
|
preserve={true}
|
||||||
clearOnDestroy={true}
|
clearOnDestroy={true}
|
||||||
scrollToFirstError={true}
|
scrollToFirstError={true}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
|
users: [],
|
||||||
access_policy: action === PageAction.CREATE ? 'authed' : undefined
|
access_policy: action === PageAction.CREATE ? 'authed' : undefined
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Label>{intl.formatMessage({ id: 'models.table.accessScope' })}</Label>
|
<Label>{intl.formatMessage({ id: 'models.table.accessScope' })}</Label>
|
||||||
<Form.Item<AccessControlFormData> name="access_policy" noStyle>
|
<Form.Item<AccessControlFormData> name="access_policy" noStyle>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
|
onChange={handleOnPolicyChange}
|
||||||
style={{ marginBottom: 12 }}
|
style={{ marginBottom: 12 }}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
@@ -267,6 +302,13 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
|||||||
<>
|
<>
|
||||||
<Label>
|
<Label>
|
||||||
{intl.formatMessage({ id: 'models.table.userSelection' })}
|
{intl.formatMessage({ id: 'models.table.userSelection' })}
|
||||||
|
<Tooltip
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'models.table.userSelection.tips'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<QuestionCircleOutlined style={{ marginLeft: 4 }} />
|
||||||
|
</Tooltip>
|
||||||
</Label>
|
</Label>
|
||||||
<Form.Item<AccessControlFormData> name="users">
|
<Form.Item<AccessControlFormData> name="users">
|
||||||
<TransferInner
|
<TransferInner
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import AlertBlockInfo from '@/components/alert-info/block';
|
||||||
import ModalFooter from '@/components/modal-footer';
|
import ModalFooter from '@/components/modal-footer';
|
||||||
import ScrollerModal from '@/components/scroller-modal';
|
import ScrollerModal from '@/components/scroller-modal';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { useRef } from 'react';
|
import _ from 'lodash';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { updateModelAccessUser } from '../../apis';
|
import { updateModelAccessUser } from '../../apis';
|
||||||
import { AccessControlFormData, ListItem } from '../../config/types';
|
import { AccessControlFormData, ListItem } from '../../config/types';
|
||||||
import AccessControlForm from './form';
|
import AccessControlForm from './form';
|
||||||
@@ -13,6 +15,8 @@ const AccessControlModal: React.FC<
|
|||||||
> = ({ open, title, currentData, action, onCancel }) => {
|
> = ({ open, title, currentData, action, onCancel }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const form = useRef<any>(null);
|
const form = useRef<any>(null);
|
||||||
|
const [isChanged, setIsChanged] = useState(false);
|
||||||
|
const formCacheRef = useRef<AccessControlFormData | null>(null);
|
||||||
|
|
||||||
const handleSumit = () => {
|
const handleSumit = () => {
|
||||||
form.current?.submit();
|
form.current?.submit();
|
||||||
@@ -36,6 +40,32 @@ const AccessControlModal: React.FC<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
|
||||||
|
console.log('changedValues', changedValues, allValues);
|
||||||
|
const initialValues = formCacheRef.current;
|
||||||
|
if (_.isEqual(initialValues, allValues)) {
|
||||||
|
setIsChanged(false);
|
||||||
|
} else {
|
||||||
|
setIsChanged(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setIsChanged(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
const initialValues = form.current?.getFieldsValue();
|
||||||
|
formCacheRef.current = {
|
||||||
|
access_policy: initialValues.access_policy,
|
||||||
|
users: initialValues.users || []
|
||||||
|
};
|
||||||
|
}, 200);
|
||||||
|
} else {
|
||||||
|
formCacheRef.current = null;
|
||||||
|
setIsChanged(false);
|
||||||
|
}
|
||||||
|
}, [open, currentData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollerModal
|
<ScrollerModal
|
||||||
title={title}
|
title={title}
|
||||||
@@ -48,7 +78,19 @@ const AccessControlModal: React.FC<
|
|||||||
keyboard={false}
|
keyboard={false}
|
||||||
width={700}
|
width={700}
|
||||||
footer={
|
footer={
|
||||||
<ModalFooter onOk={handleSumit} onCancel={onCancel}></ModalFooter>
|
<>
|
||||||
|
{isChanged && (
|
||||||
|
<AlertBlockInfo
|
||||||
|
type="warning"
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
contentStyle={{ paddingInline: 0 }}
|
||||||
|
message={intl.formatMessage({
|
||||||
|
id: 'models.button.accessSettings.tips'
|
||||||
|
})}
|
||||||
|
></AlertBlockInfo>
|
||||||
|
)}
|
||||||
|
<ModalFooter onOk={handleSumit} onCancel={onCancel}></ModalFooter>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AccessControlForm
|
<AccessControlForm
|
||||||
@@ -56,6 +98,7 @@ const AccessControlModal: React.FC<
|
|||||||
currentData={currentData}
|
currentData={currentData}
|
||||||
action={action as PageActionType}
|
action={action as PageActionType}
|
||||||
onFinish={handleOnFinish}
|
onFinish={handleOnFinish}
|
||||||
|
onValuesChange={handleOnValuesChange}
|
||||||
/>
|
/>
|
||||||
</ScrollerModal>
|
</ScrollerModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -110,10 +110,14 @@ const ScheduleTypeForm: React.FC = () => {
|
|||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 100);
|
setTimeout(resolve, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (value === ScheduleValueMap.Auto) {
|
if (value === ScheduleValueMap.Auto) {
|
||||||
onValuesChange?.({}, form.getFieldsValue());
|
onValuesChange?.({}, form.getFieldsValue());
|
||||||
} else if (value === ScheduleValueMap.Manual) {
|
} else if (value === ScheduleValueMap.Manual) {
|
||||||
form.setFieldValue(['gpu_selector', 'gpus_per_replica'], null);
|
form.setFieldValue('gpu_selector', {
|
||||||
|
gpu_ids: [],
|
||||||
|
gpus_per_replica: null
|
||||||
|
});
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
setContentPaddingBottom();
|
setContentPaddingBottom();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user