|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect } from 'react'; |
|
|
import { useTranslation } from 'react-i18next'; |
|
|
import { Modal } from '@douyinfe/semi-ui'; |
|
|
import { |
|
|
API, |
|
|
copy, |
|
|
showError, |
|
|
showSuccess, |
|
|
encodeToBase64, |
|
|
} from '../../helpers'; |
|
|
import { ITEMS_PER_PAGE } from '../../constants'; |
|
|
import { useTableCompactMode } from '../common/useTableCompactMode'; |
|
|
|
|
|
export const useTokensData = (openFluentNotification) => { |
|
|
const { t } = useTranslation(); |
|
|
|
|
|
|
|
|
const [tokens, setTokens] = useState([]); |
|
|
const [loading, setLoading] = useState(true); |
|
|
const [activePage, setActivePage] = useState(1); |
|
|
const [tokenCount, setTokenCount] = useState(0); |
|
|
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); |
|
|
const [searching, setSearching] = useState(false); |
|
|
|
|
|
|
|
|
const [selectedKeys, setSelectedKeys] = useState([]); |
|
|
|
|
|
|
|
|
const [showEdit, setShowEdit] = useState(false); |
|
|
const [editingToken, setEditingToken] = useState({ |
|
|
id: undefined, |
|
|
}); |
|
|
|
|
|
|
|
|
const [compactMode, setCompactMode] = useTableCompactMode('tokens'); |
|
|
const [showKeys, setShowKeys] = useState({}); |
|
|
|
|
|
|
|
|
const [formApi, setFormApi] = useState(null); |
|
|
const formInitValues = { |
|
|
searchKeyword: '', |
|
|
searchToken: '', |
|
|
}; |
|
|
|
|
|
|
|
|
const getFormValues = () => { |
|
|
const formValues = formApi ? formApi.getValues() : {}; |
|
|
return { |
|
|
searchKeyword: formValues.searchKeyword || '', |
|
|
searchToken: formValues.searchToken || '', |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const closeEdit = () => { |
|
|
setShowEdit(false); |
|
|
setTimeout(() => { |
|
|
setEditingToken({ |
|
|
id: undefined, |
|
|
}); |
|
|
}, 500); |
|
|
}; |
|
|
|
|
|
|
|
|
const syncPageData = (payload) => { |
|
|
setTokens(payload.items || []); |
|
|
setTokenCount(payload.total || 0); |
|
|
setActivePage(payload.page || 1); |
|
|
setPageSize(payload.page_size || pageSize); |
|
|
}; |
|
|
|
|
|
|
|
|
const loadTokens = async (page = 1, size = pageSize) => { |
|
|
setLoading(true); |
|
|
const res = await API.get(`/api/token/?p=${page}&size=${size}`); |
|
|
const { success, message, data } = res.data; |
|
|
if (success) { |
|
|
syncPageData(data); |
|
|
} else { |
|
|
showError(message); |
|
|
} |
|
|
setLoading(false); |
|
|
}; |
|
|
|
|
|
|
|
|
const refresh = async (page = activePage) => { |
|
|
await loadTokens(page); |
|
|
setSelectedKeys([]); |
|
|
}; |
|
|
|
|
|
|
|
|
const copyText = async (text) => { |
|
|
if (await copy(text)) { |
|
|
showSuccess(t('已复制到剪贴板!')); |
|
|
} else { |
|
|
Modal.error({ |
|
|
title: t('无法复制到剪贴板,请手动复制'), |
|
|
content: text, |
|
|
size: 'large', |
|
|
}); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const onOpenLink = async (type, url, record) => { |
|
|
if (url && url.startsWith('fluent')) { |
|
|
openFluentNotification(record.key); |
|
|
return; |
|
|
} |
|
|
let status = localStorage.getItem('status'); |
|
|
let serverAddress = ''; |
|
|
if (status) { |
|
|
status = JSON.parse(status); |
|
|
serverAddress = status.server_address; |
|
|
} |
|
|
if (serverAddress === '') { |
|
|
serverAddress = window.location.origin; |
|
|
} |
|
|
if (url.includes('{cherryConfig}') === true) { |
|
|
let cherryConfig = { |
|
|
id: 'new-api', |
|
|
baseUrl: serverAddress, |
|
|
apiKey: 'sk-' + record.key, |
|
|
}; |
|
|
let encodedConfig = encodeURIComponent( |
|
|
encodeToBase64(JSON.stringify(cherryConfig)), |
|
|
); |
|
|
url = url.replaceAll('{cherryConfig}', encodedConfig); |
|
|
} else { |
|
|
let encodedServerAddress = encodeURIComponent(serverAddress); |
|
|
url = url.replaceAll('{address}', encodedServerAddress); |
|
|
url = url.replaceAll('{key}', 'sk-' + record.key); |
|
|
} |
|
|
|
|
|
window.open(url, '_blank'); |
|
|
}; |
|
|
|
|
|
|
|
|
const manageToken = async (id, action, record) => { |
|
|
setLoading(true); |
|
|
let data = { id }; |
|
|
let res; |
|
|
switch (action) { |
|
|
case 'delete': |
|
|
res = await API.delete(`/api/token/${id}/`); |
|
|
break; |
|
|
case 'enable': |
|
|
data.status = 1; |
|
|
res = await API.put('/api/token/?status_only=true', data); |
|
|
break; |
|
|
case 'disable': |
|
|
data.status = 2; |
|
|
res = await API.put('/api/token/?status_only=true', data); |
|
|
break; |
|
|
} |
|
|
const { success, message } = res.data; |
|
|
if (success) { |
|
|
showSuccess('操作成功完成!'); |
|
|
let token = res.data.data; |
|
|
let newTokens = [...tokens]; |
|
|
if (action !== 'delete') { |
|
|
record.status = token.status; |
|
|
} |
|
|
setTokens(newTokens); |
|
|
} else { |
|
|
showError(message); |
|
|
} |
|
|
setLoading(false); |
|
|
}; |
|
|
|
|
|
|
|
|
const searchTokens = async () => { |
|
|
const { searchKeyword, searchToken } = getFormValues(); |
|
|
if (searchKeyword === '' && searchToken === '') { |
|
|
await loadTokens(1); |
|
|
return; |
|
|
} |
|
|
setSearching(true); |
|
|
const res = await API.get( |
|
|
`/api/token/search?keyword=${searchKeyword}&token=${searchToken}`, |
|
|
); |
|
|
const { success, message, data } = res.data; |
|
|
if (success) { |
|
|
setTokens(data); |
|
|
setTokenCount(data.length); |
|
|
setActivePage(1); |
|
|
} else { |
|
|
showError(message); |
|
|
} |
|
|
setSearching(false); |
|
|
}; |
|
|
|
|
|
|
|
|
const sortToken = (key) => { |
|
|
if (tokens.length === 0) return; |
|
|
setLoading(true); |
|
|
let sortedTokens = [...tokens]; |
|
|
sortedTokens.sort((a, b) => { |
|
|
return ('' + a[key]).localeCompare(b[key]); |
|
|
}); |
|
|
if (sortedTokens[0].id === tokens[0].id) { |
|
|
sortedTokens.reverse(); |
|
|
} |
|
|
setTokens(sortedTokens); |
|
|
setLoading(false); |
|
|
}; |
|
|
|
|
|
|
|
|
const handlePageChange = (page) => { |
|
|
loadTokens(page, pageSize).then(); |
|
|
}; |
|
|
|
|
|
const handlePageSizeChange = async (size) => { |
|
|
setPageSize(size); |
|
|
await loadTokens(1, size); |
|
|
}; |
|
|
|
|
|
|
|
|
const rowSelection = { |
|
|
onSelect: (record, selected) => {}, |
|
|
onSelectAll: (selected, selectedRows) => {}, |
|
|
onChange: (selectedRowKeys, selectedRows) => { |
|
|
setSelectedKeys(selectedRows); |
|
|
}, |
|
|
}; |
|
|
|
|
|
|
|
|
const handleRow = (record, index) => { |
|
|
if (record.status !== 1) { |
|
|
return { |
|
|
style: { |
|
|
background: 'var(--semi-color-disabled-border)', |
|
|
}, |
|
|
}; |
|
|
} else { |
|
|
return {}; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const batchDeleteTokens = async () => { |
|
|
if (selectedKeys.length === 0) { |
|
|
showError(t('请先选择要删除的令牌!')); |
|
|
return; |
|
|
} |
|
|
setLoading(true); |
|
|
try { |
|
|
const ids = selectedKeys.map((token) => token.id); |
|
|
const res = await API.post('/api/token/batch', { ids }); |
|
|
if (res?.data?.success) { |
|
|
const count = res.data.data || 0; |
|
|
showSuccess(t('已删除 {{count}} 个令牌!', { count })); |
|
|
await refresh(); |
|
|
setTimeout(() => { |
|
|
if (tokens.length === 0 && activePage > 1) { |
|
|
refresh(activePage - 1); |
|
|
} |
|
|
}, 100); |
|
|
} else { |
|
|
showError(res?.data?.message || t('删除失败')); |
|
|
} |
|
|
} catch (error) { |
|
|
showError(error.message); |
|
|
} finally { |
|
|
setLoading(false); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const batchCopyTokens = (copyType) => { |
|
|
if (selectedKeys.length === 0) { |
|
|
showError(t('请至少选择一个令牌!')); |
|
|
return; |
|
|
} |
|
|
|
|
|
Modal.info({ |
|
|
title: t('复制令牌'), |
|
|
icon: null, |
|
|
content: t('请选择你的复制方式'), |
|
|
footer: ( |
|
|
<div className='flex gap-2'> |
|
|
<button |
|
|
className='px-3 py-1 bg-gray-200 rounded' |
|
|
onClick={async () => { |
|
|
let content = ''; |
|
|
for (let i = 0; i < selectedKeys.length; i++) { |
|
|
content += |
|
|
selectedKeys[i].name + ' sk-' + selectedKeys[i].key + '\n'; |
|
|
} |
|
|
await copyText(content); |
|
|
Modal.destroyAll(); |
|
|
}} |
|
|
> |
|
|
{t('名称+密钥')} |
|
|
</button> |
|
|
<button |
|
|
className='px-3 py-1 bg-blue-500 text-white rounded' |
|
|
onClick={async () => { |
|
|
let content = ''; |
|
|
for (let i = 0; i < selectedKeys.length; i++) { |
|
|
content += 'sk-' + selectedKeys[i].key + '\n'; |
|
|
} |
|
|
await copyText(content); |
|
|
Modal.destroyAll(); |
|
|
}} |
|
|
> |
|
|
{t('仅密钥')} |
|
|
</button> |
|
|
</div> |
|
|
), |
|
|
}); |
|
|
}; |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
loadTokens(1) |
|
|
.then() |
|
|
.catch((reason) => { |
|
|
showError(reason); |
|
|
}); |
|
|
}, [pageSize]); |
|
|
|
|
|
return { |
|
|
|
|
|
tokens, |
|
|
loading, |
|
|
activePage, |
|
|
tokenCount, |
|
|
pageSize, |
|
|
searching, |
|
|
|
|
|
|
|
|
selectedKeys, |
|
|
setSelectedKeys, |
|
|
|
|
|
|
|
|
showEdit, |
|
|
setShowEdit, |
|
|
editingToken, |
|
|
setEditingToken, |
|
|
closeEdit, |
|
|
|
|
|
|
|
|
compactMode, |
|
|
setCompactMode, |
|
|
showKeys, |
|
|
setShowKeys, |
|
|
|
|
|
|
|
|
formApi, |
|
|
setFormApi, |
|
|
formInitValues, |
|
|
getFormValues, |
|
|
|
|
|
|
|
|
loadTokens, |
|
|
refresh, |
|
|
copyText, |
|
|
onOpenLink, |
|
|
manageToken, |
|
|
searchTokens, |
|
|
sortToken, |
|
|
handlePageChange, |
|
|
handlePageSizeChange, |
|
|
rowSelection, |
|
|
handleRow, |
|
|
batchDeleteTokens, |
|
|
batchCopyTokens, |
|
|
syncPageData, |
|
|
|
|
|
|
|
|
t, |
|
|
}; |
|
|
}; |
|
|
|