feat(proxy): finish proxy page ui and api support
This commit is contained in:
parent
0a3c59450b
commit
5b3f63ef02
@ -18,6 +18,7 @@
|
|||||||
"react": "^17.0.0",
|
"react": "^17.0.0",
|
||||||
"react-dom": "^17.0.0",
|
"react-dom": "^17.0.0",
|
||||||
"react-router-dom": "^6.0.2",
|
"react-router-dom": "^6.0.2",
|
||||||
|
"react-virtuoso": "^2.3.1",
|
||||||
"recoil": "^0.5.2"
|
"recoil": "^0.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
142
src/components/proxy-group.tsx
Normal file
142
src/components/proxy-group.tsx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Virtuoso } from "react-virtuoso";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Collapse,
|
||||||
|
Divider,
|
||||||
|
IconButton,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
SendRounded,
|
||||||
|
ExpandLessRounded,
|
||||||
|
ExpandMoreRounded,
|
||||||
|
MyLocationRounded,
|
||||||
|
NetworkCheckRounded,
|
||||||
|
CheckCircleOutlineRounded,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import services from "../services";
|
||||||
|
import type { ProxyItem, ProxyGroupItem } from "../services/proxy";
|
||||||
|
|
||||||
|
interface ItemProps {
|
||||||
|
proxy: ProxyItem;
|
||||||
|
selected: boolean;
|
||||||
|
onClick?: (name: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Item = ({ proxy, selected, onClick }: ItemProps) => {
|
||||||
|
return (
|
||||||
|
<ListItem sx={{ py: 0, pl: 4 }}>
|
||||||
|
<ListItemButton
|
||||||
|
selected={selected}
|
||||||
|
onClick={() => onClick?.(proxy.name)}
|
||||||
|
sx={{ borderRadius: 1, py: 0.5 }}
|
||||||
|
>
|
||||||
|
<ListItemText title={proxy.name} secondary={proxy.name} />
|
||||||
|
<ListItemIcon
|
||||||
|
sx={{ justifyContent: "flex-end", color: "primary.main" }}
|
||||||
|
>
|
||||||
|
{selected && <CheckCircleOutlineRounded sx={{ fontSize: 16 }} />}
|
||||||
|
</ListItemIcon>
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
group: ProxyGroupItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProxyGroup = ({ group }: Props) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [now, setNow] = useState(group.now);
|
||||||
|
|
||||||
|
const proxies = group.all ?? [];
|
||||||
|
|
||||||
|
const onUpdate = async (name: string) => {
|
||||||
|
// can not call update
|
||||||
|
if (group.type !== "Selector") {
|
||||||
|
// Todo
|
||||||
|
// error Tips
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const oldValue = now;
|
||||||
|
try {
|
||||||
|
setNow(name);
|
||||||
|
await services.updateProxy(group.name, name);
|
||||||
|
} catch {
|
||||||
|
setNow(oldValue);
|
||||||
|
// Todo
|
||||||
|
// error tips
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ListItem button onClick={() => setOpen(!open)}>
|
||||||
|
<ListItemText
|
||||||
|
primary={group.name}
|
||||||
|
secondary={
|
||||||
|
<>
|
||||||
|
<SendRounded color="primary" sx={{ mr: 1, fontSize: 14 }} />
|
||||||
|
<span>{now}</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
secondaryTypographyProps={{
|
||||||
|
sx: { display: "flex", alignItems: "center" },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{open ? <ExpandLessRounded /> : <ExpandMoreRounded />}
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||||
|
<Box sx={{ pl: 4, pr: 3, my: 0.5 }}>
|
||||||
|
<IconButton size="small" title="location">
|
||||||
|
<MyLocationRounded />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton size="small" title="check">
|
||||||
|
<NetworkCheckRounded />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{proxies.length >= 10 ? (
|
||||||
|
<Virtuoso
|
||||||
|
style={{ height: "400px", marginBottom: "4px" }}
|
||||||
|
totalCount={proxies.length}
|
||||||
|
itemContent={(index) => (
|
||||||
|
<Item
|
||||||
|
proxy={proxies[index]}
|
||||||
|
selected={proxies[index].name === now}
|
||||||
|
onClick={onUpdate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<List
|
||||||
|
component="div"
|
||||||
|
disablePadding
|
||||||
|
sx={{ maxHeight: "400px", overflow: "auto", mb: "4px" }}
|
||||||
|
>
|
||||||
|
{proxies.map((proxy) => (
|
||||||
|
<Item
|
||||||
|
key={proxy.name}
|
||||||
|
proxy={proxy}
|
||||||
|
selected={proxy.name === now}
|
||||||
|
onClick={onUpdate}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Divider variant="middle" />
|
||||||
|
</Collapse>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProxyGroup;
|
@ -1,5 +1,33 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Box, List, Typography } from "@mui/material";
|
||||||
|
import services from "../services";
|
||||||
|
import ProxyGroup from "../components/proxy-group";
|
||||||
|
import type { ProxyGroupItem } from "../services/proxy";
|
||||||
|
|
||||||
const ProxyPage = () => {
|
const ProxyPage = () => {
|
||||||
return <h1>Proxy</h1>;
|
const [groups, setGroups] = useState<ProxyGroupItem[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Todo
|
||||||
|
// result cache
|
||||||
|
services.getProxyInfo().then((res) => {
|
||||||
|
setGroups(res.groups);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ width: 0.9, maxWidth: "850px", mx: "auto", mb: 2 }}>
|
||||||
|
<Typography variant="h4" component="h1" sx={{ py: 2 }}>
|
||||||
|
Proxy Groups
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<List sx={{ borderRadius: 1, boxShadow: 2 }}>
|
||||||
|
{groups.map((group) => (
|
||||||
|
<ProxyGroup key={group.name} group={group} />
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProxyPage;
|
export default ProxyPage;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import * as proxy from "./proxy";
|
||||||
import * as traffic from "./traffic";
|
import * as traffic from "./traffic";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
...proxy,
|
||||||
...traffic,
|
...traffic,
|
||||||
};
|
};
|
||||||
|
47
src/services/proxy.ts
Normal file
47
src/services/proxy.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import axiosIns from "./base";
|
||||||
|
|
||||||
|
export interface ProxyItem {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
udp: boolean;
|
||||||
|
history: {
|
||||||
|
time: string;
|
||||||
|
delay: number;
|
||||||
|
}[];
|
||||||
|
all?: string[];
|
||||||
|
now?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProxyGroupItem = Omit<ProxyItem, "all" | "now"> & {
|
||||||
|
all?: ProxyItem[];
|
||||||
|
now?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Get the Proxy infomation
|
||||||
|
export async function getProxyInfo() {
|
||||||
|
const response = (await axiosIns.get("/proxies")) as any;
|
||||||
|
const results = (response?.proxies ?? {}) as Record<string, ProxyItem>;
|
||||||
|
|
||||||
|
const global = results["GLOBAL"] || results["global"];
|
||||||
|
const proxies = Object.values(results).filter((each) => each.all == null);
|
||||||
|
|
||||||
|
const groups = Object.values(results).filter(
|
||||||
|
(each) => each.name.toLocaleUpperCase() !== "GLOBAL" && each.all != null
|
||||||
|
) as ProxyGroupItem[];
|
||||||
|
|
||||||
|
groups.forEach((each) => {
|
||||||
|
// @ts-ignore
|
||||||
|
each.all = each.all?.map((item) => results[item]).filter((e) => e);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
global,
|
||||||
|
groups,
|
||||||
|
proxies,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the Proxy Choose
|
||||||
|
export async function updateProxy(group: string, proxy: string) {
|
||||||
|
return axiosIns.put(`/proxies/${group}`, { name: proxy });
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user