diff --git a/README.md b/README.md index 7a81422..bb17f2a 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,12 @@ curl http://localhost:2023/tc/api/v1/config/raw -X POST -d 'ls' #{"code":100,"data":"invalid cmd ls"} ``` +For TC command, see: + +* [Set traffic control (tcset command)](https://tcconfig.readthedocs.io/en/latest/pages/usage/tcset/index.html) +* [Delete traffic control (tcdel command)](https://tcconfig.readthedocs.io/en/latest/pages/usage/tcdel/index.html) +* [Display traffic control configurations (tcshow command)](https://tcconfig.readthedocs.io/en/latest/pages/usage/tcshow/index.html) + ## Development in macOS Build UI: diff --git a/main.go b/main.go index eceed69..6fe9d57 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,7 @@ import ( "strings" ) -const version = "1.0.4" +const version = "1.0.5" func main() { ctx := logger.WithContext(context.Background()) diff --git a/tc.go b/tc.go index 29acba1..4cf8338 100644 --- a/tc.go +++ b/tc.go @@ -333,7 +333,7 @@ func (v *NetworkOptions) Execute(ctx context.Context) error { if v.identifyKey != "all" && v.identifyValue == "" { return errors.Errorf("no identifyValue for identifyKey=%v", v.identifyKey) } - if v.identifyKey != "all" && v.identifyKey != "serverPort" && v.identifyKey != "clientPort" && v.identifyKey != "clientIP" { + if v.identifyKey != "all" && v.identifyKey != "serverPort" && v.identifyKey != "clientPort" && v.identifyKey != "clientIp" { return errors.Errorf("invalid identifyKey=%v", v.identifyKey) } if v.strategy == "" { diff --git a/ui/src/components/NetFilter.js b/ui/src/components/NetFilter.js index 63fbe9b..073bd33 100644 --- a/ui/src/components/NetFilter.js +++ b/ui/src/components/NetFilter.js @@ -1,12 +1,10 @@ import React from "react"; import {Col, Form, InputGroup, Row} from "react-bootstrap"; -export default function NetFilter({onChange, gIfaces}) { - const [iface, setIface] = React.useState(); - const [protocol, setProtocol] = React.useState('ip'); - const [direction, setDirection] = React.useState('incoming'); - const [identifyKey, setIdentifyKey] = React.useState('all'); - const [identifyValue, setIdentifyValue] = React.useState(); +export default function NetFilter({gIfaces, + iface, setIface, protocol, setProtocol, direction, + setDirection, identifyKey, setIdentifyKey, identifyValue, setIdentifyValue, + }) { const [ivVisible, setIvVisible] = React.useState(false); const [ivLabel, setIvLabel] = React.useState('IP'); @@ -17,10 +15,6 @@ export default function NetFilter({onChange, gIfaces}) { setIvLabel(nv === "clientIp" ? 'IP' : '端口'); }, [setIdentifyKey, setIvLabel, setIvVisible]); - React.useEffect(() => { - onChange && onChange(iface, protocol, direction, identifyKey, identifyValue); - }, [iface, protocol, direction, identifyKey, identifyValue, onChange]); - return ( @@ -89,7 +83,7 @@ export default function NetFilter({onChange, gIfaces}) { * 请输入{ivLabel} setIdentifyValue(e.target.value)} /> 请输入{ivLabel} diff --git a/ui/src/pages/SingleStategy.js b/ui/src/pages/SingleStategy.js index faca62c..56b5fb4 100644 --- a/ui/src/pages/SingleStategy.js +++ b/ui/src/pages/SingleStategy.js @@ -6,6 +6,7 @@ import NetFilter from "../components/NetFilter"; import {TcConfigQuery} from "../components/TcConfigQuery"; import axios from "axios"; import {useErrorHandler} from "react-error-boundary"; +import {SimpleStrategyStorage} from "../utils"; export default function SingleStategy() { const [scanPanels, setScanPanels] = React.useState([0]); @@ -35,10 +36,15 @@ export default function SingleStategy() { setScanPanels([ref.current.scanPanels.length, ...ref.current.scanPanels]); }, [setScanPanels, ref]); + // Load filter and strategy from storage. + const defaultFilter = SimpleStrategyStorage.loadFilter() || {}; + const defaultStrategy = SimpleStrategyStorage.loadStrategy() || {}; + console.log(`load filter=${JSON.stringify(defaultFilter)}, strategy=${JSON.stringify(defaultStrategy)}`); + return - +

{scanPanels?.length && scanPanels.map(e => { return @@ -56,22 +62,22 @@ export default function SingleStategy() { ; } -function SingleStategySetting() { +function SingleStategySetting({defaultFilter, defaultStrategy}) { const [executing, setExecuting] = React.useState(false); const [refresh, setRefresh] = React.useState(0); const [validated, setValidated] = React.useState(false); const handleError = useErrorHandler(); - const [iface, setIface] = React.useState(); - const [protocol, setProtocol] = React.useState(); - const [direction, setDirection] = React.useState(); - const [identifyKey, setIdentifyKey] = React.useState(); - const [identifyValue, setIdentifyValue] = React.useState(); - const [strategy, setStrategy] = React.useState(); - const [loss, setLoss] = React.useState(); - const [delay, setDelay] = React.useState(); - const [rate, setRate] = React.useState(); - const [delayDistro, setDelayDistro] = React.useState(); + const [iface, setIface] = React.useState(defaultFilter.iface); + const [protocol, setProtocol] = React.useState(defaultFilter.protocol || 'ip'); + const [direction, setDirection] = React.useState(defaultFilter.direction || 'incoming'); + const [identifyKey, setIdentifyKey] = React.useState(defaultFilter.identifyKey || 'all'); + const [identifyValue, setIdentifyValue] = React.useState(defaultFilter.identifyValue); + const [strategy, setStrategy] = React.useState(defaultStrategy.strategy || 'loss'); + const [loss, setLoss] = React.useState(defaultStrategy.loss || '1'); + const [delay, setDelay] = React.useState(defaultStrategy.delay || '1'); + const [rate, setRate] = React.useState(defaultStrategy.rate || '1000000'); + const [delayDistro, setDelayDistro] = React.useState(defaultStrategy.delayDistro); const [gIfaces, setGIfaces] = React.useState(); const [ifbs, setIfbs] = React.useState(); @@ -97,28 +103,6 @@ function SingleStategySetting() { }); }, [refresh, setIfbs, setGIfaces]); - // When user change filters. - const updateFilter = React.useCallback((iface, protocol, direction, identifyKey, identifyValue) => { - setIface(iface); - setProtocol(protocol); - setDirection(direction); - setIdentifyKey(identifyKey); - setIdentifyValue(identifyValue); - console.log(`update filter iface=${iface}, protocol=${protocol}, direction=${direction}, identify=${identifyKey}/${identifyValue}`) - }, [setIface, setProtocol, setDirection, setIdentifyKey, setIdentifyValue]); - - // When user change strategy. - const updateStategy = React.useCallback((strategy, loss, delay, rate, delayDistro) => { - if (delayDistro && Number(delayDistro) > Number(delay)) return alert(`延迟抖动${delayDistro}不能大于延迟${delay}`); - - setStrategy(strategy); - setLoss(loss); - setDelay(delay); - setRate(rate); - setDelayDistro(delayDistro); - console.log(`update strategy strategy=${strategy}, loss=${loss}, delay=${delay}, rate=${rate}, delayDistro=${delayDistro}`); - }, [setStrategy, setLoss, setDelay, setRate]); - // Reset the TC config. const resetNetwork = React.useCallback((e) => { if (!iface) { @@ -148,6 +132,10 @@ function SingleStategySetting() { return alert(`延迟抖动${delayDistro}不能大于延迟${delay}`); } + SimpleStrategyStorage.saveFilter(iface, protocol, direction, identifyKey, identifyValue); + SimpleStrategyStorage.saveStrategy(strategy, loss, delay, rate, delayDistro); + console.log(`save iface=${iface}, protocol=${protocol}, direction=${direction}, identify=${identifyKey}/${identifyValue}, strategy=${strategy}, loss=${loss}, delay=${delay}, rate=${rate}, delayDistro=${delayDistro}`); + setExecuting(true); const queries = [ iface ? `iface=${iface}` : null, @@ -179,12 +167,19 @@ function SingleStategySetting() {

- + {gIfaces && } - + @@ -217,17 +212,9 @@ function SingleStategySetting() { ; } -function StrategySetting({onChange}) { - const [strategy, setStrategy] = React.useState('loss'); - const [loss, setLoss] = React.useState('1'); - const [delay, setDelay] = React.useState('1'); - const [rate, setRate] = React.useState('1000000'); - const [delayDistro, setDelayDistro] = React.useState(); - - React.useEffect(() => { - onChange && onChange(strategy, loss, delay, rate, delayDistro); - }, [strategy, loss, delay, rate, delayDistro, onChange]); - +function StrategySetting({ + strategy, setStrategy, loss, setLoss, delay, setDelay, rate, setRate, delayDistro, setDelayDistro, + }) { return @@ -278,7 +265,7 @@ function StrategySetting({onChange}) { 延迟抖动 * 可选, 延迟区间为[{Number(delay)-Number(delayDistro || 0)}, {Number(delay)+Number(delayDistro || 0)}]正态分布 - setDelayDistro(e.target.value)} /> diff --git a/ui/src/utils.js b/ui/src/utils.js index 5576e93..a415793 100644 --- a/ui/src/utils.js +++ b/ui/src/utils.js @@ -4,7 +4,7 @@ export const Utils = { // copy({id: 0}, ['msg': 'hi']) // Return an object: // {id: 0, msg: 'hi'} - copy(from, extras) { + copy: (from, extras) => { let cp = Utils.merge({}, from); for (let i = 0; i < extras?.length; i += 2) { @@ -19,7 +19,7 @@ export const Utils = { return cp; }, // Merge two object, rewrite dst by src fields. - merge(dst, src) { + merge: (dst, src) => { if (typeof dst !== 'object') return src; if (typeof src !== 'object') return src; @@ -33,3 +33,30 @@ export const Utils = { return cp; } }; + +export const SimpleStrategyStorage = { + saveFilter: (iface, protocol, direction, identifyKey, identifyValue) => { + localStorage.setItem('TC_UI_SIMPLE_STRATEGY_FILTER', JSON.stringify({ + iface, protocol, direction, identifyKey, identifyValue, + })); + }, + loadFilter: () => { + const info = localStorage.getItem('TC_UI_SIMPLE_STRATEGY_FILTER'); + return info ? JSON.parse(info) : null; + }, + clearFilter: () => { + localStorage.removeItem('TC_UI_SIMPLE_STRATEGY_FILTER'); + }, + saveStrategy: (strategy, loss, delay, rate, delayDistro) => { + localStorage.setItem('TC_UI_SIMPLE_STRATEGY_STRATEGY', JSON.stringify({ + strategy, loss, delay, rate, delayDistro, + })); + }, + loadStrategy: () => { + const info = localStorage.getItem('TC_UI_SIMPLE_STRATEGY_STRATEGY'); + return info ? JSON.parse(info) : null; + }, + clearStrategy: () => { + localStorage.removeItem('TC_UI_SIMPLE_STRATEGY_STRATEGY'); + } +};