135 lines
6.9 KiB
JavaScript
135 lines
6.9 KiB
JavaScript
import React from 'react';
|
|
import { Modal, Avatar, Pagination } from 'antd';
|
|
import { AppstoreOutlined } from '@ant-design/icons';
|
|
import Styles from './styles.module.less';
|
|
const Records = (props) => {
|
|
const { list, oakPagination } = props.data;
|
|
const { pageSize, currentPage, total } = oakPagination || {};
|
|
const { t, revoke, setCurrentPage, setPageSize } = props.methods;
|
|
const handleRevoke = (item) => {
|
|
Modal.confirm({
|
|
title: t('revoke_confirm_title'),
|
|
content: t('revoke_confirm_content', { appName: item.application?.name || t('unknown_app') }),
|
|
okText: t('confirm'),
|
|
cancelText: t('cancel'),
|
|
okButtonProps: { danger: true },
|
|
onOk: () => {
|
|
revoke(item);
|
|
},
|
|
});
|
|
};
|
|
const formatDate = (date) => {
|
|
if (!date)
|
|
return '-';
|
|
return new Date(date).toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
};
|
|
function formateScope(scope) {
|
|
if (!scope || scope.length === 0 || scope.includes('')) {
|
|
return t('full_access');
|
|
}
|
|
return scope.join(', ');
|
|
}
|
|
const getStatusTag = (item) => {
|
|
if (item.token?.revokedAt) {
|
|
return <span className={Styles.statusRevoked}>{t('status_revoked')}</span>;
|
|
}
|
|
if (item.usageState === 'denied') {
|
|
return <span className={Styles.statusDenied}>{t('status_denied')}</span>;
|
|
}
|
|
if (item.token?.refreshExpiresAt && new Date(item.token.refreshExpiresAt) < new Date()) {
|
|
return <span className={Styles.statusExpired}>{t('status_expired')}</span>;
|
|
}
|
|
if (item.usageState === 'granted') {
|
|
return <span className={Styles.statusActive}>{t('status_active')}</span>;
|
|
}
|
|
if (item.usageState === 'revoked') {
|
|
return <span className={Styles.statusRevoked}>{t('status_revoked')}</span>;
|
|
}
|
|
return <span className={Styles.statusUnknown}>{t('status_unknown')}</span>;
|
|
};
|
|
const isRevokable = (item) => {
|
|
return item.usageState === 'granted' && !item.token?.revokedAt;
|
|
};
|
|
return (<div className={Styles.container}>
|
|
<div className={Styles.header}>
|
|
<h2>{t('page_title')}</h2>
|
|
<p className={Styles.description}>{t('page_description')}</p>
|
|
</div>
|
|
|
|
{!list || list.length === 0 ? (<div className={Styles.empty}>
|
|
<div className={Styles.emptyIcon}>🔐</div>
|
|
<p>{t('no_records')}</p>
|
|
</div>) : (<>
|
|
<div className={Styles.listContainer}>
|
|
{list.map((item) => {
|
|
return (<div key={item.id} className={Styles.item}>
|
|
<div className={Styles.itemHeader}>
|
|
<div className={Styles.appInfo}>
|
|
<Avatar src={item.application?.logo} icon={!item.application?.logo && <AppstoreOutlined />} size={48} shape="square" className={Styles.appLogo}/>
|
|
<div className={Styles.appNameWrapper}>
|
|
<h3 className={Styles.appName}>
|
|
{item.application?.name || t('unknown_app')}
|
|
</h3>
|
|
{getStatusTag(item)}
|
|
</div>
|
|
</div>
|
|
{isRevokable(item) && (<button className={Styles.revokeButton} onClick={() => handleRevoke(item)}>
|
|
{t('revoke')}
|
|
</button>)}
|
|
</div>
|
|
|
|
{item.application?.description && (<p className={Styles.appDescription}>
|
|
{item.application?.description}
|
|
</p>)}
|
|
|
|
<div className={Styles.itemDetails}>
|
|
<div className={Styles.detailRow}>
|
|
<span className={Styles.label}>{t('authorized_at')}:</span>
|
|
<span className={Styles.value}>{formatDate(item.authorizedAt)}</span>
|
|
</div>
|
|
|
|
<div className={Styles.detailRow}>
|
|
<span className={Styles.label}>{t('scope')}:</span>
|
|
<span className={Styles.value}>
|
|
{formateScope(item.code?.scope)}
|
|
</span>
|
|
</div>
|
|
|
|
{item.token?.lastUsedAt && (<div className={Styles.detailRow}>
|
|
<span className={Styles.label}>{t('last_used_at')}:</span>
|
|
<span className={Styles.value}>{formatDate(item.token.lastUsedAt)}</span>
|
|
</div>)}
|
|
|
|
{/* {item.token?.accessExpiresAt && (
|
|
<div className={Styles.detailRow}>
|
|
<span className={Styles.label}>{t('access_expires_at')}:</span>
|
|
<span className={Styles.value}>{formatDate(item.token.accessExpiresAt)}</span>
|
|
</div>
|
|
)} */}
|
|
|
|
{item.token?.revokedAt && (<div className={Styles.detailRow}>
|
|
<span className={Styles.label}>{t('revoked_at')}:</span>
|
|
<span className={Styles.value}>{formatDate(item.token.revokedAt)}</span>
|
|
</div>)}
|
|
</div>
|
|
</div>);
|
|
})}
|
|
</div>
|
|
|
|
{total && total > 0 && (<div className={Styles.paginationWrapper}>
|
|
<Pagination current={currentPage} pageSize={pageSize} total={total} onChange={(page) => setCurrentPage(page)} onShowSizeChange={(current, size) => {
|
|
setPageSize(size);
|
|
setCurrentPage(1);
|
|
}} showSizeChanger showQuickJumper showTotal={(total) => t('pagination_total', { total })} pageSizeOptions={['5', '10', '20', '50', '100']} className={Styles.pagination}/>
|
|
</div>)}
|
|
</>)}
|
|
</div>);
|
|
};
|
|
export default Records;
|