provider-button.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import dayjs from "dayjs";
  2. import useSWR, { mutate } from "swr";
  3. import { useState } from "react";
  4. import {
  5. Button,
  6. IconButton,
  7. List,
  8. ListItem,
  9. ListItemText,
  10. styled,
  11. Box,
  12. alpha,
  13. Typography,
  14. Divider,
  15. LinearProgress,
  16. } from "@mui/material";
  17. import { RefreshRounded } from "@mui/icons-material";
  18. import { useTranslation } from "react-i18next";
  19. import { useLockFn } from "ahooks";
  20. import { getProxyProviders, proxyProviderUpdate } from "@/services/api";
  21. import { BaseDialog } from "../base";
  22. import parseTraffic from "@/utils/parse-traffic";
  23. export const ProviderButton = () => {
  24. const { t } = useTranslation();
  25. const { data } = useSWR("getProxyProviders", getProxyProviders);
  26. const [open, setOpen] = useState(false);
  27. const hasProvider = Object.keys(data || {}).length > 0;
  28. const handleUpdate = useLockFn(async (key: string) => {
  29. await proxyProviderUpdate(key);
  30. await mutate("getProxies");
  31. await mutate("getProxyProviders");
  32. });
  33. if (!hasProvider) return null;
  34. return (
  35. <>
  36. <Button
  37. size="small"
  38. variant="outlined"
  39. sx={{ textTransform: "capitalize" }}
  40. onClick={() => setOpen(true)}
  41. >
  42. {t("Provider")}
  43. </Button>
  44. <BaseDialog
  45. open={open}
  46. title={
  47. <Box display="flex" justifyContent="space-between" gap={1}>
  48. <Typography variant="h6">{t("Proxy Provider")}</Typography>
  49. <Button
  50. variant="contained"
  51. onClick={async () => {
  52. Object.entries(data || {}).forEach(async ([key, item]) => {
  53. await proxyProviderUpdate(key);
  54. await mutate("getProxies");
  55. await mutate("getProxyProviders");
  56. });
  57. }}
  58. >
  59. {t("Update All")}
  60. </Button>
  61. </Box>
  62. }
  63. contentSx={{ width: 400 }}
  64. disableOk
  65. cancelBtn={t("Cancel")}
  66. onClose={() => setOpen(false)}
  67. onCancel={() => setOpen(false)}
  68. >
  69. <List sx={{ py: 0, minHeight: 250 }}>
  70. {Object.entries(data || {}).map(([key, item]) => {
  71. const time = dayjs(item.updatedAt);
  72. const sub = item.subscriptionInfo;
  73. const hasSubInfo = !!sub;
  74. const upload = sub?.Upload || 0;
  75. const download = sub?.Download || 0;
  76. const total = sub?.Total || 0;
  77. const expire = sub?.Expire || 0;
  78. const progress = Math.round(
  79. ((download + upload) * 100) / (total + 0.1)
  80. );
  81. return (
  82. <>
  83. <ListItem
  84. sx={(theme) => ({
  85. p: 0,
  86. borderRadius: "10px",
  87. boxShadow: theme.shadows[2],
  88. mb: 1,
  89. })}
  90. key={key}
  91. >
  92. <ListItemText
  93. sx={{ px: 1 }}
  94. primary={
  95. <>
  96. <Typography
  97. variant="h6"
  98. component="span"
  99. noWrap
  100. title={key}
  101. >
  102. {key}
  103. </Typography>
  104. </>
  105. }
  106. secondary={
  107. <>
  108. <StyledTypeBox component="span">
  109. {item.vehicleType}
  110. </StyledTypeBox>
  111. <StyledTypeBox component="span">
  112. {t("Update At")} {time.fromNow()}
  113. </StyledTypeBox>
  114. {hasSubInfo && (
  115. <>
  116. <Box sx={{ ...boxStyle, fontSize: 14 }}>
  117. <span title="Used / Total">
  118. {parseTraffic(upload + download)} /{" "}
  119. {parseTraffic(total)}
  120. </span>
  121. <span title="Expire Time">
  122. {parseExpire(expire)}
  123. </span>
  124. </Box>
  125. <LinearProgress
  126. variant="determinate"
  127. value={progress}
  128. color="inherit"
  129. />
  130. </>
  131. )}
  132. </>
  133. }
  134. />
  135. <Divider orientation="vertical" flexItem />
  136. <IconButton
  137. size="small"
  138. color="inherit"
  139. title="Update Provider"
  140. onClick={() => handleUpdate(key)}
  141. >
  142. <RefreshRounded />
  143. </IconButton>
  144. </ListItem>
  145. </>
  146. );
  147. })}
  148. </List>
  149. </BaseDialog>
  150. </>
  151. );
  152. };
  153. const StyledTypeBox = styled(Box)(({ theme }) => ({
  154. display: "inline-block",
  155. border: "1px solid #ccc",
  156. borderColor: alpha(theme.palette.primary.main, 0.5),
  157. color: alpha(theme.palette.primary.main, 0.8),
  158. borderRadius: 4,
  159. fontSize: 10,
  160. marginRight: "4px",
  161. padding: "0 2px",
  162. lineHeight: 1.25,
  163. }));
  164. const boxStyle = {
  165. height: 26,
  166. display: "flex",
  167. alignItems: "center",
  168. justifyContent: "space-between",
  169. };
  170. function parseExpire(expire?: number) {
  171. if (!expire) return "-";
  172. return dayjs(expire * 1000).format("YYYY-MM-DD");
  173. }