feat: admin apikeys

This commit is contained in:
jialin
2026-04-17 20:35:16 +08:00
committed by jialin
parent f45d9bda61
commit 2d726ded30
8 changed files with 114 additions and 36 deletions
+10 -11
View File
@@ -267,7 +267,6 @@ export default [
name: 'accessControl', name: 'accessControl',
path: '/access-control', path: '/access-control',
key: 'accessControl', key: 'accessControl',
access: 'canSeeAdmin',
routes: [ routes: [
{ {
path: '/access-control', path: '/access-control',
@@ -280,20 +279,20 @@ export default [
icon: 'icon-users', icon: 'icon-users',
selectedIcon: 'icon-users-filled', selectedIcon: 'icon-users-filled',
defaultIcon: 'icon-users', defaultIcon: 'icon-users',
access: 'canSeeAdmin',
component: './users' component: './users'
},
{
name: 'apikeys',
path: '/access-control/api-keys',
key: 'apikeys',
selectedIcon: 'icon-key-filled',
icon: 'icon-key',
defaultIcon: 'icon-key',
component: './api-keys'
} }
] ]
}, },
{
name: 'apikeys',
path: '/api-keys',
key: 'apikeys',
hideInMenu: true,
selectedIcon: 'icon-key-filled',
icon: 'icon-key',
defaultIcon: 'icon-key',
component: './api-keys'
},
{ {
name: 'profile', name: 'profile',
path: '/profile', path: '/profile',
+5 -1
View File
@@ -129,6 +129,9 @@ interface FilterBarProps {
onClick: () => void; onClick: () => void;
onClear: () => void; onClear: () => void;
}; };
select?: {
showSearch?: boolean;
};
widths?: { widths?: {
input?: number; input?: number;
select?: number; select?: number;
@@ -148,6 +151,7 @@ export const FilterBar: React.FC<FilterBarProps> = (props) => {
showSelect, showSelect,
buttonText, buttonText,
buttonIcon, buttonIcon,
select,
actionType = 'button', actionType = 'button',
marginBottom = 10, marginBottom = 10,
marginTop = 10, marginTop = 10,
@@ -189,7 +193,7 @@ export const FilterBar: React.FC<FilterBarProps> = (props) => {
{showSelect && ( {showSelect && (
<BaseSelect <BaseSelect
allowClear allowClear
showSearch={false} showSearch={select?.showSearch}
placeholder={selectHolder} placeholder={selectHolder}
style={{ width: widths?.select || 230 }} style={{ width: widths?.select || 230 }}
size="large" size="large"
-14
View File
@@ -215,20 +215,6 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
const userMenu = { const userMenu = {
items: [ items: [
{
key: 'apikeys',
label: (
<span className="flex flex-center">
<IconFont type="icon-key" />
<span className="m-l-8" style={{ marginLeft: 8 }}>
{intl?.formatMessage?.({ id: 'menu.apikeys' })}
</span>
</span>
),
onClick: () => {
history.push('/api-keys');
}
},
{ {
key: 'settings', key: 'settings',
label: ( label: (
+16 -3
View File
@@ -13,6 +13,7 @@ import { ListItem } from '../config/types';
interface ColumnsHookProps { interface ColumnsHookProps {
handleSelect: (val: string, record: ListItem) => void; handleSelect: (val: string, record: ListItem) => void;
sortOrder: string[]; sortOrder: string[];
is_admin?: boolean;
} }
const actionList: Global.ActionItem<string>[] = [ const actionList: Global.ActionItem<string>[] = [
@@ -31,7 +32,8 @@ const actionList: Global.ActionItem<string>[] = [
const useModelsColumns = ({ const useModelsColumns = ({
handleSelect, handleSelect,
sortOrder sortOrder,
is_admin
}: ColumnsHookProps): ColumnsType<ListItem> => { }: ColumnsHookProps): ColumnsType<ListItem> => {
const intl = useIntl(); const intl = useIntl();
@@ -69,7 +71,18 @@ const useModelsColumns = ({
dataIndex: 'masked_value', dataIndex: 'masked_value',
key: 'masked_value', key: 'masked_value',
render: (text: string, record: ListItem) => ( render: (text: string, record: ListItem) => (
<AutoTooltip ghost style={{ maxWidth: 400 }}> <AutoTooltip ghost style={{ maxWidth: 200 }}>
{text || '-'}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'users.table.username' }),
dataIndex: 'user_name',
key: 'user_name',
hidden: !is_admin,
render: (text: string, record: ListItem) => (
<AutoTooltip ghost style={{ maxWidth: 200 }}>
{text || '-'} {text || '-'}
</AutoTooltip> </AutoTooltip>
) )
@@ -166,7 +179,7 @@ const useModelsColumns = ({
) )
} }
]; ];
}, [intl, handleSelect]); }, [intl, is_admin, handleSelect]);
}; };
export default useModelsColumns; export default useModelsColumns;
+45 -5
View File
@@ -5,11 +5,13 @@ import { PageAction } from '@/config';
import { PaginationKey } from '@/config/settings'; import { PaginationKey } from '@/config/settings';
import type { PageActionType } from '@/config/types'; import type { PageActionType } from '@/config/types';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
import useQueryUserList from '@/pages/users/services/use-query-user-list';
import { useModel } from '@@/plugin-model';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import useMemoizedFn from 'ahooks/lib/useMemoizedFn'; import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
import { ConfigProvider, Table } from 'antd'; import { ConfigProvider, Table } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import NoResult from '../_components/no-result'; import NoResult from '../_components/no-result';
import PageBox from '../_components/page-box'; import PageBox from '../_components/page-box';
import { deleteApisKey, queryApisKeysList } from './apis'; import { deleteApisKey, queryApisKeysList } from './apis';
@@ -18,6 +20,8 @@ import { ListItem } from './config/types';
import useKeysColumns from './hooks/use-keys-columns'; import useKeysColumns from './hooks/use-keys-columns';
const APIKeys: React.FC = () => { const APIKeys: React.FC = () => {
const { initialState } = useModel('@@initialState');
const currentUser = initialState?.currentUser;
const { const {
TABLE_SORT_DIRECTIONS, TABLE_SORT_DIRECTIONS,
dataSource, dataSource,
@@ -30,13 +34,25 @@ const APIKeys: React.FC = () => {
fetchData, fetchData,
handlePageChange, handlePageChange,
handleTableChange, handleTableChange,
handleQueryChange,
handleSearch, handleSearch,
handleNameChange handleNameChange
} = useTableFetch<ListItem>({ } = useTableFetch<ListItem>({
key: PaginationKey.APIKeys, key: PaginationKey.APIKeys,
fetchAPI: queryApisKeysList, fetchAPI: queryApisKeysList,
deleteAPI: deleteApisKey, deleteAPI: deleteApisKey,
contentForDelete: 'apikeys.table.apikeys' contentForDelete: 'apikeys.table.apikeys',
defaultQueryParams: {
user_id: currentUser?.is_admin ? '*' : undefined
}
});
const {
dataList: userList,
fetchData: fetchUserData,
cancelRequest: cancelUserRequest
} = useQueryUserList({
getLabel: (item) => item.username,
getValue: (item) => item.id
}); });
const intl = useIntl(); const intl = useIntl();
@@ -52,6 +68,15 @@ const APIKeys: React.FC = () => {
currentData: null currentData: null
}); });
useEffect(() => {
fetchUserData({
page: -1
});
return () => {
cancelUserRequest();
};
}, []);
const handleAddKey = () => { const handleAddKey = () => {
setOpenAddModal({ setOpenAddModal({
open: true, open: true,
@@ -101,6 +126,12 @@ const APIKeys: React.FC = () => {
} }
}); });
const handleUserChange = (val: string) => {
handleQueryChange({
user_id: val || '*'
});
};
const renderEmpty = (type?: string) => { const renderEmpty = (type?: string) => {
if (type !== 'Table') return; if (type !== 'Table') return;
return ( return (
@@ -121,7 +152,11 @@ const APIKeys: React.FC = () => {
); );
}; };
const columns = useKeysColumns({ handleSelect: onSelect, sortOrder }); const columns = useKeysColumns({
handleSelect: onSelect,
sortOrder,
is_admin: currentUser?.is_admin
});
return ( return (
<> <>
@@ -129,10 +164,15 @@ const APIKeys: React.FC = () => {
<FilterBar <FilterBar
marginBottom={22} marginBottom={22}
marginTop={30} marginTop={30}
showSelect={currentUser?.is_admin}
selectOptions={userList}
select={{ showSearch: true }}
selectHolder={intl.formatMessage({ id: 'models.table.filterByName' })}
buttonText={intl.formatMessage({ id: 'apikeys.button.create' })} buttonText={intl.formatMessage({ id: 'apikeys.button.create' })}
handleSearch={handleSearch} handleSearch={handleSearch}
handleDeleteByBatch={handleDeleteBatch} handleDeleteByBatch={handleDeleteBatch}
handleClickPrimary={handleAddKey} handleClickPrimary={handleAddKey}
handleSelectChange={handleUserChange}
handleInputChange={handleNameChange} handleInputChange={handleNameChange}
rowSelection={rowSelection} rowSelection={rowSelection}
widths={{ input: 300 }} widths={{ input: 300 }}
@@ -144,14 +184,14 @@ const APIKeys: React.FC = () => {
rowSelection={rowSelection} rowSelection={rowSelection}
loading={{ loading={{
spinning: dataSource.loading, spinning: dataSource.loading,
size: 'middle' size: 'default'
}} }}
sortDirections={TABLE_SORT_DIRECTIONS} sortDirections={TABLE_SORT_DIRECTIONS}
showSorterTooltip={false} showSorterTooltip={false}
rowKey="id" rowKey="id"
onChange={handleTableChange} onChange={handleTableChange}
pagination={{ pagination={{
size: 'middle', size: 'default',
showSizeChanger: true, showSizeChanger: true,
pageSize: queryParams.perPage, pageSize: queryParams.perPage,
current: queryParams.page, current: queryParams.page,
@@ -198,7 +198,7 @@ const ApiAccessInfo = ({ open, data, onClose }: ApiAccessInfoProps) => {
<CreateButton <CreateButton
type="link" type="link"
size="small" size="small"
onClick={() => navigate('/api-keys')} onClick={() => navigate('/access-control/api-keys')}
> >
{intl.formatMessage({ {intl.formatMessage({
id: 'models.table.apiAccessInfo.gotoCreate' id: 'models.table.apiAccessInfo.gotoCreate'
@@ -94,7 +94,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
<Button <Button
type="link" type="link"
size="small" size="small"
href="#/api-keys" href="#/access-control/api-keys"
target="_blank" target="_blank"
style={{ paddingInline: 2 }} style={{ paddingInline: 2 }}
> >
@@ -0,0 +1,36 @@
import { useQueryDataList } from '@/hooks/use-query-data-list';
import { useModel } from '@@/plugin-model';
import { queryUsersList } from '../apis';
import { ListItem } from '../config/types';
export const useQueryUserList = (optons?: {
getLabel?: (item: ListItem) => string;
getValue?: (item: ListItem) => any;
}) => {
const { initialState } = useModel('@@initialState');
const { dataList, loading, fetchData, cancelRequest } = useQueryDataList<
ListItem,
Global.SearchParams
>({
key: 'userList',
fetchList: queryUsersList,
getLabel: optons?.getLabel,
getValue: optons?.getValue
});
const fetchUserList = (params: Global.SearchParams) => {
if (!initialState?.currentUser?.is_admin) return;
return fetchData({
...params
});
};
return {
dataList,
loading,
fetchData: fetchUserList,
cancelRequest
};
};
export default useQueryUserList;