338 lines
14 KiB
JavaScript
338 lines
14 KiB
JavaScript
import AddOutlined from "@mui/icons-material/AddOutlined";
|
|
import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined";
|
|
import FormatListBulletedOutlined from "@mui/icons-material/FormatListBulletedOutlined";
|
|
import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
|
|
import RestartAltOutlined from "@mui/icons-material/RestartAltOutlined";
|
|
import SaveOutlined from "@mui/icons-material/SaveOutlined";
|
|
import Dialog from "@mui/material/Dialog";
|
|
import DialogActions from "@mui/material/DialogActions";
|
|
import DialogContent from "@mui/material/DialogContent";
|
|
import DialogTitle from "@mui/material/DialogTitle";
|
|
import TextField from "@mui/material/TextField";
|
|
import Tooltip from "@mui/material/Tooltip";
|
|
import { Button } from "@/shared/ui/Button.jsx";
|
|
import { DataState } from "@/shared/ui/DataState.jsx";
|
|
import { DataTable } from "@/shared/ui/DataTable.jsx";
|
|
import { IconButton } from "@/shared/ui/IconButton.jsx";
|
|
import { AdminListBody, AdminListPage, AdminListToolbar } from "@/shared/ui/AdminListLayout.jsx";
|
|
import { formatMillis } from "@/shared/utils/time.js";
|
|
import { useRegionBlockPage } from "@/features/region-blocks/hooks/useRegionBlockPage.js";
|
|
import styles from "@/features/region-blocks/region-blocks.module.css";
|
|
|
|
export function RegionBlockPage() {
|
|
const page = useRegionBlockPage();
|
|
const columns = buildColumns(page);
|
|
const whitelistColumns = buildWhitelistColumns(page);
|
|
const userWhitelistColumns = buildUserWhitelistColumns(page);
|
|
|
|
return (
|
|
<AdminListPage>
|
|
<AdminListToolbar
|
|
actions={
|
|
<>
|
|
<Button
|
|
disabled={!page.dirty || page.saving}
|
|
startIcon={<RestartAltOutlined fontSize="small" />}
|
|
onClick={page.resetDraft}
|
|
>
|
|
重置
|
|
</Button>
|
|
<Button
|
|
disabled={page.loading || page.saving}
|
|
startIcon={<RefreshOutlined fontSize="small" />}
|
|
onClick={page.reload}
|
|
>
|
|
刷新
|
|
</Button>
|
|
<Button
|
|
disabled={!page.abilities.canUpdate || !page.dirty || page.saving}
|
|
startIcon={<SaveOutlined fontSize="small" />}
|
|
variant="primary"
|
|
onClick={page.save}
|
|
>
|
|
保存
|
|
</Button>
|
|
</>
|
|
}
|
|
filters={
|
|
<div className={styles.forms}>
|
|
<form className={styles.addForm} onSubmit={page.submitInput}>
|
|
<TextField
|
|
className={styles.keywordInput}
|
|
disabled={!page.abilities.canUpdate || page.saving}
|
|
label="屏蔽词"
|
|
placeholder="国家码或国家名称"
|
|
size="small"
|
|
value={page.keywordInput}
|
|
onChange={(event) => page.setKeywordInput(event.target.value)}
|
|
/>
|
|
<Button
|
|
disabled={!page.abilities.canUpdate || page.saving}
|
|
startIcon={<AddOutlined fontSize="small" />}
|
|
type="submit"
|
|
variant="primary"
|
|
>
|
|
添加屏蔽词
|
|
</Button>
|
|
</form>
|
|
<form className={styles.addForm} onSubmit={page.submitIPInput}>
|
|
<TextField
|
|
className={styles.ipInput}
|
|
disabled={!page.abilities.canUpdate || page.saving}
|
|
label="IP白名单"
|
|
placeholder="203.0.113.10"
|
|
size="small"
|
|
value={page.ipInput}
|
|
onChange={(event) => page.setIPInput(event.target.value)}
|
|
/>
|
|
<Button
|
|
disabled={!page.abilities.canUpdate || page.saving}
|
|
startIcon={<AddOutlined fontSize="small" />}
|
|
type="submit"
|
|
variant="primary"
|
|
>
|
|
添加IP
|
|
</Button>
|
|
</form>
|
|
<Button
|
|
disabled={page.loading}
|
|
startIcon={<FormatListBulletedOutlined fontSize="small" />}
|
|
onClick={page.openWhitelistDialog}
|
|
>
|
|
白名单IP列表
|
|
</Button>
|
|
<form className={styles.addForm} onSubmit={page.submitUserIDInput}>
|
|
<TextField
|
|
className={styles.userInput}
|
|
disabled={!page.abilities.canUpdate || page.saving}
|
|
label="用户ID/短ID/靓号白名单"
|
|
placeholder="900025 或 VIP2026"
|
|
size="small"
|
|
value={page.userIDInput}
|
|
onChange={(event) => page.setUserIDInput(event.target.value)}
|
|
/>
|
|
<Button
|
|
disabled={!page.abilities.canUpdate || page.saving}
|
|
startIcon={<AddOutlined fontSize="small" />}
|
|
type="submit"
|
|
variant="primary"
|
|
>
|
|
添加用户
|
|
</Button>
|
|
</form>
|
|
</div>
|
|
}
|
|
/>
|
|
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
|
|
<AdminListBody>
|
|
<div className={styles.tableGroup}>
|
|
<h2 className={styles.tableTitle}>地区屏蔽</h2>
|
|
<DataTable
|
|
columns={columns}
|
|
items={page.draftItems}
|
|
minWidth="880px"
|
|
rowKey={(item) => item.blockId || item.keyword}
|
|
/>
|
|
</div>
|
|
<div className={styles.tableGroup}>
|
|
<h2 className={styles.tableTitle}>IP白名单</h2>
|
|
<DataTable
|
|
columns={whitelistColumns}
|
|
items={page.draftWhitelistItems}
|
|
minWidth="720px"
|
|
rowKey={(item) => item.whitelistId || item.ipAddress}
|
|
/>
|
|
</div>
|
|
<div className={styles.tableGroup}>
|
|
<h2 className={styles.tableTitle}>用户ID/短ID/靓号白名单</h2>
|
|
<DataTable
|
|
columns={userWhitelistColumns}
|
|
items={page.draftWhitelistUserItems}
|
|
minWidth="860px"
|
|
rowKey={(item) => item.whitelistId || item.userId}
|
|
/>
|
|
</div>
|
|
</AdminListBody>
|
|
</DataState>
|
|
<WhitelistDialog page={page} />
|
|
</AdminListPage>
|
|
);
|
|
}
|
|
|
|
function buildColumns(page) {
|
|
return [
|
|
{
|
|
key: "keyword",
|
|
label: "屏蔽词",
|
|
width: "minmax(220px, 1.2fr)",
|
|
render: (item) => <KeywordCell item={item} />,
|
|
},
|
|
{
|
|
key: "countryCode",
|
|
label: "国家码",
|
|
width: "minmax(120px, 0.6fr)",
|
|
render: (item) => item.countryCode || "保存后解析",
|
|
},
|
|
{
|
|
key: "updatedAtMs",
|
|
label: "更新时间",
|
|
width: "minmax(180px, 0.85fr)",
|
|
render: (item) => (item.updatedAtMs ? formatMillis(item.updatedAtMs) : "-"),
|
|
},
|
|
{
|
|
key: "actions",
|
|
label: "操作",
|
|
width: "minmax(96px, 0.4fr)",
|
|
render: (item) => (
|
|
<Tooltip arrow title="移除">
|
|
<span>
|
|
<IconButton
|
|
disabled={!page.abilities.canUpdate || page.saving}
|
|
label="移除"
|
|
tone="danger"
|
|
onClick={() => page.removeKeyword(item.keyword)}
|
|
>
|
|
<DeleteOutlineOutlined fontSize="small" />
|
|
</IconButton>
|
|
</span>
|
|
</Tooltip>
|
|
),
|
|
},
|
|
];
|
|
}
|
|
|
|
function buildWhitelistColumns(page) {
|
|
return [
|
|
{
|
|
key: "ipAddress",
|
|
label: "IP",
|
|
width: "minmax(220px, 1fr)",
|
|
render: (item) => <KeywordCell item={{ keyword: item.ipAddress, enabled: item.enabled }} />,
|
|
},
|
|
{
|
|
key: "updatedAtMs",
|
|
label: "更新时间",
|
|
width: "minmax(180px, 0.8fr)",
|
|
render: (item) => (item.updatedAtMs ? formatMillis(item.updatedAtMs) : "-"),
|
|
},
|
|
{
|
|
key: "actions",
|
|
label: "操作",
|
|
width: "minmax(96px, 0.4fr)",
|
|
render: (item) => (
|
|
<Tooltip arrow title="移除">
|
|
<span>
|
|
<IconButton
|
|
disabled={!page.abilities.canUpdate || page.saving}
|
|
label="移除"
|
|
tone="danger"
|
|
onClick={() => page.removeIP(item.ipAddress)}
|
|
>
|
|
<DeleteOutlineOutlined fontSize="small" />
|
|
</IconButton>
|
|
</span>
|
|
</Tooltip>
|
|
),
|
|
},
|
|
];
|
|
}
|
|
|
|
function buildUserWhitelistColumns(page) {
|
|
return [
|
|
{
|
|
key: "userId",
|
|
label: "内部用户ID",
|
|
width: "minmax(220px, 1fr)",
|
|
render: (item) => <KeywordCell item={{ keyword: item.userId, enabled: item.enabled }} />,
|
|
},
|
|
{
|
|
key: "displayUserId",
|
|
label: "当前短ID/靓号",
|
|
width: "minmax(180px, 0.8fr)",
|
|
render: (item) => item.displayUserId || (String(item.whitelistId || "").startsWith("draft-user-") ? "保存后解析" : "-"),
|
|
},
|
|
{
|
|
key: "updatedAtMs",
|
|
label: "更新时间",
|
|
width: "minmax(180px, 0.8fr)",
|
|
render: (item) => (item.updatedAtMs ? formatMillis(item.updatedAtMs) : "-"),
|
|
},
|
|
{
|
|
key: "actions",
|
|
label: "操作",
|
|
width: "minmax(96px, 0.4fr)",
|
|
render: (item) => (
|
|
<Tooltip arrow title="移除">
|
|
<span>
|
|
<IconButton
|
|
disabled={!page.abilities.canUpdate || page.saving}
|
|
label="移除"
|
|
tone="danger"
|
|
onClick={() => page.removeUserID(item.userId)}
|
|
>
|
|
<DeleteOutlineOutlined fontSize="small" />
|
|
</IconButton>
|
|
</span>
|
|
</Tooltip>
|
|
),
|
|
},
|
|
];
|
|
}
|
|
|
|
function buildWhitelistDialogColumns() {
|
|
return [
|
|
{
|
|
key: "ipAddress",
|
|
label: "IP",
|
|
width: "minmax(220px, 1fr)",
|
|
render: (item) => <KeywordCell item={{ keyword: item.ipAddress, enabled: item.enabled }} />,
|
|
},
|
|
{
|
|
key: "updatedAtMs",
|
|
label: "更新时间",
|
|
width: "minmax(180px, 0.8fr)",
|
|
render: (item) => (item.updatedAtMs ? formatMillis(item.updatedAtMs) : "-"),
|
|
},
|
|
];
|
|
}
|
|
|
|
function WhitelistDialog({ page }) {
|
|
const columns = buildWhitelistDialogColumns();
|
|
|
|
return (
|
|
<Dialog
|
|
maxWidth="md"
|
|
open={page.whitelistDialogOpen}
|
|
slotProps={{ paper: { className: styles.whitelistDialogPaper } }}
|
|
fullWidth
|
|
onClose={page.closeWhitelistDialog}
|
|
>
|
|
<DialogTitle className={styles.whitelistDialogTitle}>
|
|
<span>白名单IP列表</span>
|
|
<small>共 {page.draftWhitelistItems.length} 个</small>
|
|
</DialogTitle>
|
|
<DialogContent className={styles.whitelistDialogContent}>
|
|
<DataTable
|
|
columns={columns}
|
|
emptyLabel="当前无数据"
|
|
items={page.draftWhitelistItems}
|
|
minWidth="520px"
|
|
rowKey={(item) => item.whitelistId || item.ipAddress}
|
|
/>
|
|
</DialogContent>
|
|
<DialogActions className={styles.whitelistDialogActions}>
|
|
<Button onClick={page.closeWhitelistDialog}>关闭</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
function KeywordCell({ item }) {
|
|
return (
|
|
<div className={styles.keywordCell}>
|
|
<span className={styles.keyword}>{item.keyword}</span>
|
|
<span className={styles.meta}>{item.enabled ? "启用" : "停用"}</span>
|
|
</div>
|
|
);
|
|
}
|