summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/connections.js38
-rw-r--r--src/components/Button.js29
-rw-r--r--src/components/Button.module.css8
-rw-r--r--src/components/ConnectionTable.js10
-rw-r--r--src/components/Connections.js28
-rw-r--r--src/components/ModalCloseAllConnections.js44
-rw-r--r--src/components/ModalCloseAllConnections.module.css23
-rw-r--r--src/components/Proxies.js17
-rw-r--r--src/components/Proxies.module.css7
-rw-r--r--src/components/Root.css9
-rw-r--r--src/components/Rules.js19
-rw-r--r--src/components/Rules.module.css6
-rw-r--r--src/components/SvgYacd.js4
13 files changed, 203 insertions, 39 deletions
diff --git a/src/api/connections.js b/src/api/connections.js
index 594fc5e..fbc9abd 100644
--- a/src/api/connections.js
+++ b/src/api/connections.js
@@ -1,10 +1,39 @@
+import { getURLAndInit } from 'm/request-helper';
+
const endpoint = '/connections';
let fetched = false;
let subscribers = [];
+// see also https://github.com/Dreamacro/clash/blob/dev/constant/metadata.go#L41
+type UUID = string;
+type ConnectionItem = {
+ id: UUID,
+ metadata: {
+ network: 'tcp' | 'udp',
+ type: 'HTTP' | 'HTTP Connect' | 'Socks5' | 'Redir' | 'Unknown',
+ sourceIP: string,
+ destinationIP: string,
+ sourcePort: string,
+ destinationPort: string,
+ host: string
+ },
+ upload: number,
+ download: number,
+ // e.g. "2019-11-30T22:48:13.416668+08:00",
+ start: string,
+ chains: Array<string>,
+ // e.g. 'Match', 'DomainKeyword'
+ rule: string
+};
+type ConnectionsData = {
+ downloadTotal: number,
+ uploadTotal: number,
+ connections: Array<ConnectionItem>
+};
+
function appendData(s) {
- let o;
+ let o: ConnectionsData;
try {
o = JSON.parse(s);
} catch (err) {
@@ -48,4 +77,9 @@ function subscribe(listener) {
};
}
-export { fetchData };
+async function closeAllConnections(apiConfig) {
+ const { url, init } = getURLAndInit(apiConfig);
+ return await fetch(url + endpoint, { ...init, method: 'DELETE' });
+}
+
+export { fetchData, closeAllConnections };
diff --git a/src/components/Button.js b/src/components/Button.js
index d7928c7..f56049e 100644
--- a/src/components/Button.js
+++ b/src/components/Button.js
@@ -1,20 +1,29 @@
import React from 'react';
-import PropTypes from 'prop-types';
import s0 from 'c/Button.module.css';
const noop = () => {};
-const Button = React.memo(function Button({ label, onClick = noop }) {
+const { memo, forwardRef } = React;
+
+function Button({ children, label, onClick = noop }, ref) {
+ return (
+ <button className={s0.btn} ref={ref} onClick={onClick}>
+ {children || label}
+ </button>
+ );
+}
+
+function WithIcon({ text, icon, onClick = noop }, ref) {
return (
- <button className={s0.btn} onClick={onClick}>
- {label}
+ <button className={s0.btn} ref={ref} onClick={onClick}>
+ <div className={s0.withIconWrapper}>
+ {icon}
+ <span className={s0.txt}>{text}</span>
+ </div>
</button>
);
-});
+}
-Button.propTypes = {
- label: PropTypes.string.isRequired,
- onClick: PropTypes.func
-};
+export const ButtonWithIcon = memo(forwardRef(WithIcon));
-export default Button;
+export default memo(forwardRef(Button));
diff --git a/src/components/Button.module.css b/src/components/Button.module.css
index 8fb6a92..205bfe9 100644
--- a/src/components/Button.module.css
+++ b/src/components/Button.module.css
@@ -25,3 +25,11 @@
padding: 6px 12px;
}
}
+
+.withIconWrapper {
+ display: flex;
+ align-items: center;
+ .txt {
+ margin-left: 5px;
+ }
+}
diff --git a/src/components/ConnectionTable.js b/src/components/ConnectionTable.js
index 17293c0..2e7ba7f 100644
--- a/src/components/ConnectionTable.js
+++ b/src/components/ConnectionTable.js
@@ -34,6 +34,13 @@ function renderCell(cell, now) {
}
}
+const tableState = {
+ sortBy: [
+ // maintain a more stable order
+ { id: 'start', desc: true }
+ ]
+};
+
function Table({ data }) {
const now = new Date();
const {
@@ -45,7 +52,8 @@ function Table({ data }) {
} = useTable(
{
columns,
- data
+ data,
+ initialState: tableState
},
useSortBy
);
diff --git a/src/components/Connections.js b/src/components/Connections.js
index 2607606..8953722 100644
--- a/src/components/Connections.js
+++ b/src/components/Connections.js
@@ -4,12 +4,15 @@ import ConnectionTable from 'c/ConnectionTable';
import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight';
import { useStoreState } from 'm/store';
import { getClashAPIConfig } from 'd/app';
+import { X as IconClose } from 'react-feather';
import SvgYacd from './SvgYacd';
+import { ButtonWithIcon } from './Button';
+import ModalCloseAllConnections from './ModalCloseAllConnections';
import * as connAPI from '../api/connections';
import s from './Connections.module.css';
-const { useEffect, useState, useRef } = React;
+const { useEffect, useState, useRef, useCallback, useMemo } = React;
const paddingBottom = 30;
@@ -31,6 +34,17 @@ function Conn() {
const [refContainer, containerHeight] = useRemainingViewPortHeight();
const config = useStoreState(getClashAPIConfig);
const [conns, setConns] = useState([]);
+ const [isCloseAllModalOpen, setIsCloseAllModalOpen] = useState(false);
+ const openCloseAllModal = useCallback(() => setIsCloseAllModalOpen(true), []);
+ const closeCloseAllModal = useCallback(
+ () => setIsCloseAllModalOpen(false),
+ []
+ );
+ const closeAllConnections = useCallback(() => {
+ connAPI.closeAllConnections(config);
+ closeCloseAllModal();
+ }, [config, closeCloseAllModal]);
+ const iconClose = useMemo(() => <IconClose width={16} />, []);
const prevConnsRef = useRef(conns);
useEffect(() => {
function read({ connections }) {
@@ -65,6 +79,18 @@ function Conn() {
)}
</div>
</div>
+ <div className="fabgrp">
+ <ButtonWithIcon
+ text="Close"
+ icon={iconClose}
+ onClick={openCloseAllModal}
+ />
+ </div>
+ <ModalCloseAllConnections
+ isOpen={isCloseAllModalOpen}
+ primaryButtonOnTap={closeAllConnections}
+ onRequestClose={closeCloseAllModal}
+ />
</div>
);
}
diff --git a/src/components/ModalCloseAllConnections.js b/src/components/ModalCloseAllConnections.js
new file mode 100644
index 0000000..8a06393
--- /dev/null
+++ b/src/components/ModalCloseAllConnections.js
@@ -0,0 +1,44 @@
+import React from 'react';
+
+import Modal from 'react-modal';
+import Button from './Button';
+import cx from 'classnames';
+
+import modalStyle from './Modal.module.css';
+import s from './ModalCloseAllConnections.module.css';
+
+const { useRef, useCallback, useMemo } = React;
+
+export default function Comp({ isOpen, onRequestClose, primaryButtonOnTap }) {
+ const primaryButtonRef = useRef(null);
+ const onAfterOpen = useCallback(() => {
+ primaryButtonRef.current.focus();
+ }, []);
+ const className = useMemo(
+ () => ({
+ base: cx(modalStyle.content, s.cnt),
+ afterOpen: s.afterOpen,
+ beforeClose: ''
+ }),
+ []
+ );
+ return (
+ <Modal
+ isOpen={isOpen}
+ onRequestClose={onRequestClose}
+ onAfterOpen={onAfterOpen}
+ className={className}
+ overlayClassName={cx(modalStyle.overlay, s.overlay)}
+ >
+ <p>Are you sure you want to close all connections?</p>
+ <div className={s.btngrp}>
+ <Button onClick={primaryButtonOnTap} ref={primaryButtonRef}>
+ I'm sure
+ </Button>
+ {/* im lazy :) */}
+ <div style={{ width: 20 }} />
+ <Button onClick={onRequestClose}>No</Button>
+ </div>
+ </Modal>
+ );
+}
diff --git a/src/components/ModalCloseAllConnections.module.css b/src/components/ModalCloseAllConnections.module.css
new file mode 100644
index 0000000..f3b54c1
--- /dev/null
+++ b/src/components/ModalCloseAllConnections.module.css
@@ -0,0 +1,23 @@
+.overlay {
+ background-color: rgba(0, 0, 0, 0.6);
+}
+.cnt {
+ background-color: var(--bg-modal);
+ color: var(--color-text);
+ max-width: 300px;
+ line-height: 1.4;
+ transform: translate(-50%, -50%) scale(1.5);
+ opacity: 0.6;
+ transition: all 0.3s ease;
+}
+.afterOpen {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+}
+
+.btngrp {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-top: 30px;
+}
diff --git a/src/components/Proxies.js b/src/components/Proxies.js
index c66ed83..fef6a14 100644
--- a/src/components/Proxies.js
+++ b/src/components/Proxies.js
@@ -1,9 +1,10 @@
-import React, { useEffect } from 'react';
+import React from 'react';
import { useActions, useStoreState } from 'm/store';
import ContentHeader from 'c/ContentHeader';
import ProxyGroup from 'c/ProxyGroup';
-import Button from 'c/Button';
+import { ButtonWithIcon } from 'c/Button';
+import { Zap } from 'react-feather';
import s0 from 'c/Proxies.module.css';
@@ -14,6 +15,8 @@ import {
requestDelayAll
} from 'd/proxies';
+const { useEffect, useMemo } = React;
+
const mapStateToProps = s => ({
proxies: getProxies(s),
groupNames: getProxyGroupNames(s)
@@ -33,13 +36,19 @@ export default function Proxies() {
})();
}, [fetchProxies, requestDelayAll]);
const { groupNames } = useStoreState(mapStateToProps);
+ const icon = useMemo(() => <Zap width={16} />, []);
return (
<>
<ContentHeader title="Proxies" />
<div className={s0.body}>
- <div className={s0.fabgrp}>
- <Button label="Test Latency" onClick={requestDelayAll} />
+ <div className="fabgrp">
+ <ButtonWithIcon
+ text="Test Latency"
+ icon={icon}
+ onClick={requestDelayAll}
+ />
+ {/* <Button onClick={requestDelayAll}>Test Latency</Button> */}
</div>
{groupNames.map(groupName => {
return (
diff --git a/src/components/Proxies.module.css b/src/components/Proxies.module.css
index a832ebe..72b70fb 100644
--- a/src/components/Proxies.module.css
+++ b/src/components/Proxies.module.css
@@ -8,10 +8,3 @@
padding: 10px 40px;
}
}
-
-.fabgrp {
- position: fixed;
- z-index: 1;
- right: 20px;
- bottom: 20px;
-}
diff --git a/src/components/Root.css b/src/components/Root.css
index 88b3d6c..ae25dca 100644
--- a/src/components/Root.css
+++ b/src/components/Root.css
@@ -92,6 +92,7 @@ body.dark {
--color-btn-fg: #bebebe;
--color-bg-proxy-selected: #303030;
--color-row-odd: #282828;
+ --bg-modal: #1f1f20;
}
body.light {
@@ -109,4 +110,12 @@ body.light {
--color-btn-fg: #101010;
--color-bg-proxy-selected: #cfcfcf;
--color-row-odd: #f5f5f5;
+ --bg-modal: #fbfbfb;
+}
+
+/* TODO remove fabgrp in component css files */
+.fabgrp {
+ position: fixed;
+ right: 20px;
+ bottom: 20px;
}
diff --git a/src/components/Rules.js b/src/components/Rules.js
index 96056ee..2e2db3c 100644
--- a/src/components/Rules.js
+++ b/src/components/Rules.js
@@ -1,7 +1,8 @@
-import React, { memo, useEffect } from 'react';
+import React from 'react';
import { useActions, useStoreState } from 'm/store';
-import Button from 'c/Button';
+import { ButtonWithIcon } from 'c/Button';
import { FixedSizeList as List, areEqual } from 'react-window';
+import { RotateCw } from 'react-feather';
import ContentHeader from 'c/ContentHeader';
import Rule from 'c/Rule';
@@ -10,7 +11,9 @@ import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight';
import { getRules, fetchRules, fetchRulesOnce } from 'd/rules';
-import s0 from './Rules.module.css';
+const { memo, useEffect, useMemo } = React;
+
+// import s from './Rules.module.css';
const paddingBottom = 30;
const mapStateToProps = s => ({
@@ -43,7 +46,7 @@ export default function Rules() {
fetchRulesOnce();
}, [fetchRulesOnce]);
const [refRulesContainer, containerHeight] = useRemainingViewPortHeight();
-
+ const refreshIcon = useMemo(() => <RotateCw width={16} />, []);
return (
<div>
<ContentHeader title="Rules" />
@@ -60,8 +63,12 @@ export default function Rules() {
{Row}
</List>
</div>
- <div className={s0.fabgrp}>
- <Button label="Refresh" onClick={fetchRules} />
+ <div className="fabgrp">
+ <ButtonWithIcon
+ text="Refresh"
+ icon={refreshIcon}
+ onClick={fetchRules}
+ />
</div>
</div>
);
diff --git a/src/components/Rules.module.css b/src/components/Rules.module.css
index 1fb94eb..79a9626 100644
--- a/src/components/Rules.module.css
+++ b/src/components/Rules.module.css
@@ -1,5 +1 @@
-.fabgrp {
- position: fixed;
- right: 20px;
- bottom: 20px;
-}
+/* */
diff --git a/src/components/SvgYacd.js b/src/components/SvgYacd.js
index 42cd425..b1bc8f0 100644
--- a/src/components/SvgYacd.js
+++ b/src/components/SvgYacd.js
@@ -12,8 +12,6 @@ function SvgYacd({
c1 = '#eee'
}) {
const faceClasName = cx({ [s.path]: animate });
- // fill="#2A477A"
-
return (
<svg
width={width}
@@ -26,7 +24,7 @@ function SvgYacd({
<path
d="M71.689 53.055c9.23-1.487 25.684 27.263 41.411 56.663 18.572-8.017 71.708-7.717 93.775 0 4.714-15.612 31.96-57.405 41.626-56.663 3.992.088 13.07 31.705 23.309 94.96 2.743 16.949 7.537 47.492 14.38 91.63-42.339 17.834-84.37 26.751-126.095 26.751-41.724 0-83.756-8.917-126.095-26.751C52.973 116.244 65.536 54.047 71.689 53.055z"
stroke={c1}
- strokeWidth="2"
+ strokeWidth="4"
strokeLinecap="round"
fill={c0}
className={faceClasName}