diff --git a/src/components/connection/connection-item.tsx b/src/components/connection/connection-item.tsx index 419233ccfbe277748e818dc366df7b5cf04eb37f..088ef55b24d3ecd231e0d938e1ff3b67a58ac946 100644 --- a/src/components/connection/connection-item.tsx +++ b/src/components/connection/connection-item.tsx @@ -1,4 +1,20 @@ +import dayjs from "dayjs"; +import { useLockFn } from "ahooks"; +import { styled, Box, ListItem, IconButton, ListItemText } from "@mui/material"; +import { CloseRounded } from "@mui/icons-material"; import { ApiType } from "../../services/types"; +import { deleteConnection } from "../../services/api"; + +const Tag = styled(Box)(({ theme }) => ({ + display: "inline-block", + fontSize: "12px", + padding: "0 4px", + lineHeight: 1.375, + border: "1px solid #ccc", + borderRadius: 4, + marginRight: "0.1em", + transform: "scale(0.92)", +})); interface Props { value: ApiType.ConnectionsItem; @@ -7,7 +23,34 @@ interface Props { const ConnectionItem = (props: Props) => { const { value } = props; - return <div>{value.metadata.host || value.metadata.destinationIP}</div>; + const onDelete = useLockFn(async () => deleteConnection(value.id)); + + return ( + <ListItem + dense + secondaryAction={ + <IconButton edge="end" onClick={onDelete}> + <CloseRounded /> + </IconButton> + } + > + <ListItemText + primary={value.metadata.host || value.metadata.destinationIP} + secondary={ + <Box> + <Tag sx={{ textTransform: "uppercase", color: "success" }}> + {value.metadata.network} + </Tag> + <Tag>{value.metadata.type}</Tag> + {value.chains.length > 0 && ( + <Tag>{value.chains[value.chains.length - 1]}</Tag> + )} + <Tag>{dayjs(value.start).fromNow()}</Tag> + </Box> + } + /> + </ListItem> + ); }; export default ConnectionItem; diff --git a/src/locales/en.json b/src/locales/en.json index aba0ea191256a6f257c7e07bd926058ca4c6c92f..fc27befe3b928847d113ecb8986f542fcde21c63 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -17,6 +17,7 @@ "Profile URL": "Profile URL", "Import": "Import", "New": "New", + "Close All": "Close All", "Settings": "Settings", "Clash Setting": "Clash Setting", diff --git a/src/locales/zh.json b/src/locales/zh.json index a51b13264bf0a7cfaf0d608e9d0c0d00c3957a9a..737a03dbbcd7f2e677bd825aee24097330f29aec 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -17,6 +17,7 @@ "Profile URL": "é…置文件链接", "Import": "导入", "New": "新建", + "Close All": "å…³é—全部", "Settings": "设置", "Clash Setting": "Clash 设置", diff --git a/src/pages/connections.tsx b/src/pages/connections.tsx index 16ec9367d4f75e0b18bc23c9d94690c6a777d34a..0c4e8665ab7b9b4e3e0b9bedf0d4403b92710ada 100644 --- a/src/pages/connections.tsx +++ b/src/pages/connections.tsx @@ -1,9 +1,10 @@ import { useEffect, useState } from "react"; -import { Paper } from "@mui/material"; +import { useLockFn } from "ahooks"; +import { Button, Paper } from "@mui/material"; import { Virtuoso } from "react-virtuoso"; import { useTranslation } from "react-i18next"; import { ApiType } from "../services/types"; -import { getInfomation } from "../services/api"; +import { closeAllConnections, getInfomation } from "../services/api"; import BasePage from "../components/base/base-page"; import ConnectionItem from "../components/connection/connection-item"; @@ -29,10 +30,26 @@ const ConnectionsPage = () => { return () => ws?.close(); }, []); + const onCloseAll = useLockFn(closeAllConnections); + return ( - <BasePage title={t("Connections")} contentStyle={{ height: "100%" }}> + <BasePage + title={t("Connections")} + contentStyle={{ height: "100%" }} + header={ + <Button + size="small" + sx={{ mt: 1 }} + variant="contained" + onClick={onCloseAll} + > + {t("Close All")} + </Button> + } + > <Paper sx={{ boxShadow: 2, height: "100%" }}> <Virtuoso + initialTopMostItemIndex={999} data={conn.connections} itemContent={(index, item) => <ConnectionItem value={item} />} /> diff --git a/src/services/api.ts b/src/services/api.ts index 192a8a8156e66349a02bf3c23cdb9a306b07e278..8a73c4d65ff8ca78ae2a74ead5405d0bcdc034c6 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -141,3 +141,15 @@ export async function getProviders() { const response = await instance.get<any, any>("/providers/proxies"); return response.providers as any; } + +// Close specific connection +export async function deleteConnection(id: string) { + const instance = await getAxios(); + await instance.delete<any, any>(`/connections/${encodeURIComponent(id)}`); +} + +// Close all connections +export async function closeAllConnections() { + const instance = await getAxios(); + await instance.delete<any, any>(`/connections`); +}