280 lines
7.7 KiB
TypeScript
280 lines
7.7 KiB
TypeScript
"use client"
|
|
|
|
import {
|
|
getDataSyncServerList,
|
|
updateSyncServerLocation,
|
|
} from "@/components/label/api/sync"
|
|
import { Response } from "@/components/label/api/sync/typing"
|
|
import useAuth from "@/components/label/hooks/useAuth"
|
|
import {
|
|
ActionIcon,
|
|
Badge,
|
|
Button,
|
|
Group,
|
|
Modal,
|
|
Paper,
|
|
Select,
|
|
Stack,
|
|
TextInput,
|
|
} from "@mantine/core"
|
|
import { notifications } from "@mantine/notifications"
|
|
import { IconEdit, IconRefresh, IconSearch } from "@tabler/icons-react"
|
|
import { DataTable, DataTableColumn } from "mantine-datatable"
|
|
import { useCallback, useEffect, useMemo, useState } from "react"
|
|
|
|
export default function TeamSyncServerPage() {
|
|
const { isShow } = useAuth()
|
|
|
|
const [records, setRecords] = useState<Response.SyncServer[]>([])
|
|
const [loading, setLoading] = useState(false)
|
|
const [totalItems, setTotalItems] = useState(0)
|
|
const [page, setPage] = useState(1)
|
|
const [pageSize, setPageSize] = useState(20)
|
|
|
|
const [location, setLocation] = useState("")
|
|
const [appliedLocation, setAppliedLocation] = useState("")
|
|
const [online, setOnline] = useState<string | null>(null)
|
|
const [appliedOnline, setAppliedOnline] = useState<string | null>(null)
|
|
|
|
const [open, setOpen] = useState(false)
|
|
const [editingRecord, setEditingRecord] = useState<Response.SyncServer | null>(null)
|
|
const [editingLocation, setEditingLocation] = useState("")
|
|
const [submitting, setSubmitting] = useState(false)
|
|
|
|
const load = useCallback(async () => {
|
|
try {
|
|
setLoading(true)
|
|
const params: any = {
|
|
page_number: page,
|
|
page_size: pageSize,
|
|
}
|
|
if (appliedLocation.trim()) params.location = appliedLocation.trim()
|
|
if (appliedOnline === "true") params.is_online = true
|
|
if (appliedOnline === "false") params.is_online = false
|
|
|
|
const res = await getDataSyncServerList(params)
|
|
setRecords(res?.sync_servers ?? [])
|
|
setTotalItems(res?.total_items ?? 0)
|
|
} catch (e) {
|
|
setRecords([])
|
|
setTotalItems(0)
|
|
notifications.show({
|
|
color: "red",
|
|
title: "加载失败",
|
|
message: e instanceof Error ? e.message : "请求失败",
|
|
})
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [appliedLocation, appliedOnline, page, pageSize])
|
|
|
|
useEffect(() => {
|
|
load()
|
|
}, [load])
|
|
|
|
const columns = useMemo(() => {
|
|
const cols: DataTableColumn<Response.SyncServer>[] = [
|
|
{ accessor: "id", title: "ID", width: 80 },
|
|
{ accessor: "ip", title: "IP", width: 180 },
|
|
{ accessor: "location", title: "IP所在地", width: 180 },
|
|
{
|
|
accessor: "is_online",
|
|
title: "状态",
|
|
width: 100,
|
|
render: (record) =>
|
|
record.is_online ? (
|
|
<Badge color="green" variant="light">
|
|
在线
|
|
</Badge>
|
|
) : (
|
|
<Badge color="red" variant="light">
|
|
离线
|
|
</Badge>
|
|
),
|
|
},
|
|
{ accessor: "create_at", title: "创建时间", width: 180 },
|
|
{ accessor: "update_at", title: "更新时间", width: 180 },
|
|
{
|
|
accessor: "operation",
|
|
title: "操作",
|
|
width: 100,
|
|
textAlign: "center",
|
|
render: (record) =>
|
|
isShow("server_edit") ? (
|
|
<ActionIcon
|
|
variant="subtle"
|
|
color="blue"
|
|
onClick={() => {
|
|
setEditingRecord(record)
|
|
setEditingLocation(record.location ?? "")
|
|
setOpen(true)
|
|
}}>
|
|
<IconEdit size={16} />
|
|
</ActionIcon>
|
|
) : null,
|
|
},
|
|
]
|
|
return cols
|
|
}, [isShow])
|
|
|
|
const saveLocation = async () => {
|
|
if (!editingRecord?.id) return
|
|
try {
|
|
setSubmitting(true)
|
|
await updateSyncServerLocation(editingLocation.trim(), editingRecord.id)
|
|
notifications.show({
|
|
color: "green",
|
|
title: "操作成功",
|
|
message: "已更新同步服务所在地",
|
|
})
|
|
setOpen(false)
|
|
setEditingRecord(null)
|
|
setEditingLocation("")
|
|
load()
|
|
} catch (e) {
|
|
notifications.show({
|
|
color: "red",
|
|
title: "更新失败",
|
|
message: e instanceof Error ? e.message : "请求失败",
|
|
})
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Stack w="100%" h="calc(100vh - 56px)" p="md" gap="md">
|
|
<Paper
|
|
withBorder
|
|
p="md"
|
|
radius="md"
|
|
style={{
|
|
borderColor:
|
|
"light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
|
|
}}>
|
|
<Group wrap="wrap">
|
|
<TextInput
|
|
placeholder="请输入IP所在地"
|
|
size="xs"
|
|
radius="xs"
|
|
value={location}
|
|
onChange={(e) => setLocation(e.currentTarget.value)}
|
|
/>
|
|
<Select
|
|
placeholder="状态"
|
|
size="xs"
|
|
radius="xs"
|
|
data={[
|
|
{ label: "在线", value: "true" },
|
|
{ label: "离线", value: "false" },
|
|
]}
|
|
clearable
|
|
value={online}
|
|
onChange={setOnline}
|
|
/>
|
|
<Button
|
|
size="xs"
|
|
radius="xs"
|
|
leftSection={<IconSearch size={16} />}
|
|
onClick={() => {
|
|
setAppliedLocation(location)
|
|
setAppliedOnline(online)
|
|
setPage(1)
|
|
}}>
|
|
查询
|
|
</Button>
|
|
<Button
|
|
variant="default"
|
|
size="xs"
|
|
radius="xs"
|
|
leftSection={<IconRefresh size={16} />}
|
|
onClick={() => {
|
|
setLocation("")
|
|
setOnline(null)
|
|
setAppliedLocation("")
|
|
setAppliedOnline(null)
|
|
setPage(1)
|
|
setPageSize(20)
|
|
}}>
|
|
重置
|
|
</Button>
|
|
</Group>
|
|
</Paper>
|
|
|
|
<Paper
|
|
withBorder
|
|
p="md"
|
|
radius="md"
|
|
style={{
|
|
borderColor:
|
|
"light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
|
|
flex: 1,
|
|
minHeight: 0,
|
|
display: "flex",
|
|
}}>
|
|
<DataTable<Response.SyncServer>
|
|
withTableBorder
|
|
withRowBorders
|
|
fetching={loading}
|
|
records={records}
|
|
columns={columns}
|
|
totalRecords={totalItems}
|
|
recordsPerPage={pageSize}
|
|
page={page}
|
|
onPageChange={setPage}
|
|
onRecordsPerPageChange={(v) => {
|
|
setPageSize(v)
|
|
setPage(1)
|
|
}}
|
|
recordsPerPageOptions={[10, 20, 50, 100]}
|
|
noRecordsText="暂无数据"
|
|
scrollAreaProps={{ type: "auto" }}
|
|
style={{ width: "100%" }}
|
|
/>
|
|
</Paper>
|
|
|
|
<Modal
|
|
opened={open}
|
|
onClose={() => {
|
|
setOpen(false)
|
|
setEditingRecord(null)
|
|
setEditingLocation("")
|
|
}}
|
|
title="更新同步服务所在地"
|
|
centered
|
|
size={520}
|
|
closeOnClickOutside={false}>
|
|
<Stack gap="sm">
|
|
<TextInput
|
|
label="所在地"
|
|
size="xs"
|
|
radius="xs"
|
|
value={editingLocation}
|
|
onChange={(e) => setEditingLocation(e.currentTarget.value)}
|
|
/>
|
|
<Group justify="flex-end">
|
|
<Button
|
|
variant="default"
|
|
size="xs"
|
|
radius="xs"
|
|
onClick={() => {
|
|
setOpen(false)
|
|
setEditingRecord(null)
|
|
setEditingLocation("")
|
|
}}>
|
|
取消
|
|
</Button>
|
|
<Button
|
|
size="xs"
|
|
radius="xs"
|
|
onClick={saveLocation}
|
|
loading={submitting}>
|
|
确定
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</Modal>
|
|
</Stack>
|
|
)
|
|
}
|