|
| 1 | +import React, { useRef } from 'react'; |
| 2 | +import CreateApiKeyModal from '@/pages/ApiKey/components/CreateApiKeyModal'; |
| 3 | +import { ListApiKeyOptions, listApiKeys } from '@/services/apikey/listApiKeys'; |
| 4 | +import { deleteApiKey } from '@/services/apikey/deleteApiKey'; |
| 5 | +import { enableApiKey } from '@/services/apikey/enableApiKey'; |
| 6 | +import { disableApiKey } from '@/services/apikey/disableApiKey'; |
| 7 | +import { |
| 8 | + DeleteOutlined, |
| 9 | + EllipsisOutlined, |
| 10 | + PauseCircleOutlined, |
| 11 | + PlayCircleOutlined, |
| 12 | +} from '@ant-design/icons'; |
| 13 | +import type { ActionType, ProColumns } from '@ant-design/pro-components'; |
| 14 | +import { ProTable } from '@ant-design/pro-components'; |
| 15 | +import { FormattedMessage, useIntl, useRequest } from '@umijs/max'; |
| 16 | +import { App, Button, Dropdown, message } from 'antd'; |
| 17 | +import { BASIC_INTL } from '@/constant'; |
| 18 | +import { transformSearchParams } from '@/utils'; |
| 19 | + |
| 20 | +import useStyle from './style'; |
| 21 | + |
| 22 | +export type Props = Record<string, never>; |
| 23 | + |
| 24 | +const INTL = { |
| 25 | + TABLE_TITLE: { |
| 26 | + id: 'apikeys.table.title', |
| 27 | + }, |
| 28 | + KEY: { |
| 29 | + id: 'apikeys.table.key', |
| 30 | + }, |
| 31 | + STATUS: { |
| 32 | + id: 'apikeys.table.status', |
| 33 | + }, |
| 34 | + EXPIRES_AT: { |
| 35 | + id: 'apikeys.table.expiresAt', |
| 36 | + }, |
| 37 | + ENABLE: { |
| 38 | + id: 'apikeys.action.enable', |
| 39 | + }, |
| 40 | + DISABLE: { |
| 41 | + id: 'apikeys.action.disable', |
| 42 | + }, |
| 43 | +}; |
| 44 | + |
| 45 | +const ApiKeyList: React.FC<Props> = () => { |
| 46 | + const { styles } = useStyle(); |
| 47 | + |
| 48 | + const actionRef = useRef<ActionType>(); |
| 49 | + const { modal } = App.useApp(); |
| 50 | + const intl = useIntl(); |
| 51 | + |
| 52 | + const reloadTable = () => { |
| 53 | + if (actionRef.current) { |
| 54 | + actionRef.current.reload(); |
| 55 | + } |
| 56 | + }; |
| 57 | + |
| 58 | + const { run: doDeleteApiKey } = useRequest(deleteApiKey, { |
| 59 | + manual: true, |
| 60 | + onSuccess: () => { |
| 61 | + reloadTable(); |
| 62 | + message.success(intl.formatMessage(BASIC_INTL.DELETE_SUCCESS)); |
| 63 | + }, |
| 64 | + }); |
| 65 | + |
| 66 | + const { run: doEnableApiKey } = useRequest(enableApiKey, { |
| 67 | + manual: true, |
| 68 | + onSuccess: () => { |
| 69 | + reloadTable(); |
| 70 | + message.success('Operation successful'); |
| 71 | + }, |
| 72 | + }); |
| 73 | + |
| 74 | + const { run: doDisableApiKey } = useRequest(disableApiKey, { |
| 75 | + manual: true, |
| 76 | + onSuccess: () => { |
| 77 | + reloadTable(); |
| 78 | + message.success('Operation successful'); |
| 79 | + }, |
| 80 | + }); |
| 81 | + |
| 82 | + const handleListApiKeys = async (opts: ListApiKeyOptions) => { |
| 83 | + opts.fieldSelector = transformSearchParams(opts, ['status', 'name']).join(','); |
| 84 | + const apiKeyList = await listApiKeys(opts); |
| 85 | + return { |
| 86 | + data: apiKeyList.items, |
| 87 | + total: apiKeyList.total, |
| 88 | + }; |
| 89 | + }; |
| 90 | + |
| 91 | + const deleteModalConfig = (records: API.ApiKey[]) => { |
| 92 | + let title = |
| 93 | + records.length === 1 |
| 94 | + ? intl.formatMessage(BASIC_INTL.DELETE_CONFIRM_TITLE, { |
| 95 | + name: records[0].metadata.name, |
| 96 | + }) |
| 97 | + : intl.formatMessage(BASIC_INTL.MULTI_DELETE_CONFIRM_TITLE, { |
| 98 | + count: records.length, |
| 99 | + }); |
| 100 | + return { |
| 101 | + title: title, |
| 102 | + content: ( |
| 103 | + <span> |
| 104 | + <FormattedMessage {...BASIC_INTL.DELETE_CONFIRM_CONTENT} /> |
| 105 | + </span> |
| 106 | + ), |
| 107 | + centered: true, |
| 108 | + onOk: () => { |
| 109 | + doDeleteApiKey(records[0].metadata.instanceId); |
| 110 | + }, |
| 111 | + okText: intl.formatMessage(BASIC_INTL.BTN_DELETE), |
| 112 | + okButtonProps: { danger: true }, |
| 113 | + }; |
| 114 | + }; |
| 115 | + |
| 116 | + const columns: ProColumns<API.ApiKey>[] = [ |
| 117 | + { |
| 118 | + title: <FormattedMessage {...BASIC_INTL.NAME} />, |
| 119 | + dataIndex: ['metadata', 'name'], |
| 120 | + width: 200, |
| 121 | + }, |
| 122 | + { |
| 123 | + title: <FormattedMessage {...INTL.KEY} />, |
| 124 | + dataIndex: 'key', |
| 125 | + renderText: (text: string) => { |
| 126 | + if (!text) return '-'; |
| 127 | + const visibleStart = text.substring(0, 7); |
| 128 | + const visibleEnd = text.substring(text.length - 4); |
| 129 | + return `${visibleStart}***********************${visibleEnd}`; |
| 130 | + }, |
| 131 | + width: 300, |
| 132 | + }, |
| 133 | + { |
| 134 | + title: <FormattedMessage {...INTL.STATUS} />, |
| 135 | + dataIndex: 'status', |
| 136 | + valueEnum: { |
| 137 | + 1: { |
| 138 | + text: <FormattedMessage {...BASIC_INTL.ACTIVED} />, |
| 139 | + status: 'Success', |
| 140 | + }, |
| 141 | + 0: { |
| 142 | + text: <FormattedMessage {...BASIC_INTL.DISABLED} />, |
| 143 | + status: 'Error', |
| 144 | + }, |
| 145 | + 2: { |
| 146 | + text: <FormattedMessage id="apikeys.status.expired" />, |
| 147 | + status: 'Warning', |
| 148 | + }, |
| 149 | + }, |
| 150 | + width: 100, |
| 151 | + }, |
| 152 | + { |
| 153 | + title: <FormattedMessage {...BASIC_INTL.CREATED_AT} />, |
| 154 | + dataIndex: ['metadata', 'createdAt'], |
| 155 | + valueType: 'date', |
| 156 | + width: 120, |
| 157 | + renderText: (text: string) => { |
| 158 | + if (!text) return '-'; |
| 159 | + const date = new Date(text); |
| 160 | + return date.toLocaleDateString(intl.locale); |
| 161 | + }, |
| 162 | + }, |
| 163 | + { |
| 164 | + title: <FormattedMessage {...INTL.EXPIRES_AT} />, |
| 165 | + dataIndex: 'expiresAt', |
| 166 | + valueType: 'date', |
| 167 | + width: 120, |
| 168 | + renderText: (text: string) => { |
| 169 | + if (!text) return intl.formatMessage({ id: 'apikeys.expiresAt.never' }); |
| 170 | + const date = new Date(text); |
| 171 | + return date.toLocaleDateString(intl.locale); |
| 172 | + }, |
| 173 | + }, |
| 174 | + { |
| 175 | + title: <FormattedMessage {...BASIC_INTL.TITLE_OPTION} />, |
| 176 | + dataIndex: 'option', |
| 177 | + valueType: 'option', |
| 178 | + width: 80, |
| 179 | + fixed: 'right', |
| 180 | + render: (_, record: API.ApiKey) => [ |
| 181 | + <Dropdown |
| 182 | + key="dropdown" |
| 183 | + trigger={['click']} |
| 184 | + placement="bottom" |
| 185 | + menu={{ |
| 186 | + items: [ |
| 187 | + { |
| 188 | + key: 'enable', |
| 189 | + icon: <PlayCircleOutlined />, |
| 190 | + label: intl.formatMessage(INTL.ENABLE), |
| 191 | + onClick: () => { |
| 192 | + doEnableApiKey(record.metadata.instanceId); |
| 193 | + }, |
| 194 | + disabled: record.status === 1, |
| 195 | + }, |
| 196 | + { |
| 197 | + key: 'disable', |
| 198 | + icon: <PauseCircleOutlined />, |
| 199 | + label: intl.formatMessage(INTL.DISABLE), |
| 200 | + onClick: () => { |
| 201 | + doDisableApiKey(record.metadata.instanceId); |
| 202 | + }, |
| 203 | + disabled: record.status === 0 || record.status === 2, |
| 204 | + }, |
| 205 | + { |
| 206 | + type: 'divider', |
| 207 | + }, |
| 208 | + { |
| 209 | + key: 'delete', |
| 210 | + icon: <DeleteOutlined />, |
| 211 | + label: intl.formatMessage(BASIC_INTL.BTN_DELETE), |
| 212 | + onClick: () => { |
| 213 | + modal.confirm(deleteModalConfig([record])); |
| 214 | + }, |
| 215 | + }, |
| 216 | + ], |
| 217 | + }} |
| 218 | + > |
| 219 | + <Button shape="default" type="text" icon={<EllipsisOutlined />}></Button> |
| 220 | + </Dropdown>, |
| 221 | + ], |
| 222 | + }, |
| 223 | + ]; |
| 224 | + |
| 225 | + return ( |
| 226 | + <div> |
| 227 | + <ProTable<API.ApiKey, ListApiKeyOptions> |
| 228 | + size="small" |
| 229 | + className={styles.container} |
| 230 | + headerTitle={intl.formatMessage(INTL.TABLE_TITLE)} |
| 231 | + actionRef={actionRef} |
| 232 | + columns={columns} |
| 233 | + rowKey={(record) => record?.metadata?.instanceId ?? ''} |
| 234 | + search={{ labelWidth: 'auto' }} |
| 235 | + request={handleListApiKeys} |
| 236 | + rowSelection={{}} |
| 237 | + pagination={{ |
| 238 | + defaultPageSize: 10, |
| 239 | + showSizeChanger: true, |
| 240 | + pageSizeOptions: [10, 20, 30, 50], |
| 241 | + }} |
| 242 | + toolBarRender={() => [ |
| 243 | + <CreateApiKeyModal |
| 244 | + key="create" |
| 245 | + onFinish={async () => { |
| 246 | + reloadTable(); |
| 247 | + return true; |
| 248 | + }} |
| 249 | + />, |
| 250 | + ]} |
| 251 | + /> |
| 252 | + </div> |
| 253 | + ); |
| 254 | +}; |
| 255 | + |
| 256 | +export default ApiKeyList; |
0 commit comments