transfer the argument suggestion into popup, show available commands, highlight currently typing argument

This commit is contained in:
zokutyou2@gmail.com 2021-07-13 20:02:31 +09:00
parent 5b229c2dcd
commit fc7455b914
4 changed files with 87 additions and 43 deletions

View File

@ -27,10 +27,8 @@ from mitmproxy.utils.strutils import always_str
def flow_to_json(flow: mitmproxy.flow.Flow) -> dict:
"""
Remove flow message content and cert to save transmission space.
Args:
flow: The original flow.
Sync with web/src/flow.ts.
"""
f = {
@ -454,12 +452,20 @@ class FlowContentView(RequestHandler):
class Commands(RequestHandler):
def get(self):
commands = {}
for (name, command) in self.master.commands.commands.items():
commands[name] = []
for parameter in command.parameters:
commands[name].append({"name": parameter.name})
self.write({"commands": commands})
def post(self):
result = self.master.commands.execute(self.json["command"])
if result is None:
self.write({"result": ""})
return
self.write({"result": str(result)})
self.write({ "result": result, "type": type(result).__name__ })
class Events(RequestHandler):
@ -479,16 +485,6 @@ class Options(RequestHandler):
raise APIError(400, f"{err}")
class CommandArguments(RequestHandler):
def get(self):
arguments = {}
for (name, command) in self.master.commands.commands.items():
arguments[name] = []
for parameter in command.parameters:
arguments[name].append(parameter.name)
self.write(arguments)
class SaveOptions(RequestHandler):
def post(self):
# try:
@ -541,7 +537,7 @@ class Application(tornado.web.Application):
(r"/", IndexHandler),
(r"/filter-help(?:\.json)?", FilterHelp),
(r"/updates", ClientConnection),
(r"/commands", Commands),
(r"/commands(?:\.json)?", Commands),
(r"/events(?:\.json)?", Events),
(r"/flows(?:\.json)?", Flows),
(r"/flows/dump", DumpFlows),
@ -559,7 +555,6 @@ class Application(tornado.web.Application):
r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content/(?P<content_view>[0-9a-zA-Z\-\_]+)(?:\.json)?",
FlowContentView),
(r"/clear", ClearAll),
(r"/arguments(?:\.json)?", CommandArguments),
(r"/options(?:\.json)?", Options),
(r"/options/save", SaveOptions),
(r"/conf\.js", Conf),

View File

@ -1,4 +1,4 @@
.command {
.command-title {
background-color: #F2F2F2;
border: 1px solid #aaa;
}
@ -14,4 +14,18 @@
.command-suggestion {
background-color: #9c9c9c;
}
.argument-suggestion {
background-color: hsla(209, 52%, 84%, 0.5) !important;
}
.command > .popover {
display: block;
position: relative;
max-width: none;
}
.available-commands {
overflow: auto;
}

View File

@ -1,13 +1,10 @@
import React from 'react'
import renderer from 'react-test-renderer'
import CommandBar from '../../../components/CommandBar'
import { render } from "../../test-utils"
describe('CommandBar Component', () => {
let commandBar = renderer.create(
<CommandBar />),
tree = commandBar.toJSON()
it('should render correctly', () => {
expect(tree).toMatchSnapshot()
})
test('CommandBar Component', async () => {
const {asFragment, store} = render(
<CommandBar/>
);
expect(asFragment()).toMatchSnapshot();
})

View File

@ -3,39 +3,76 @@ import classnames from 'classnames'
import { Key, fetchApi } from '../utils'
import Filt from '../filt/command'
export function AvailableCommands({input, commands}) {
if (!commands) return null
let availableCommands = []
for (const [command, args] of Object.entries(commands)) {
if (command.startsWith(input)) {
availableCommands.push(command)
}
}
return <div className="available-commands popover bottom">Available Commands: {JSON.stringify(availableCommands)}</div>
}
export function ArgumentSuggestion({nextArgs, currentArg}){
let results = []
for (let i = 0; i < nextArgs.length; i++) {
if (i==currentArg) {
results.push(<mark>{nextArgs[i]}</mark>)
continue
}
results.push(<span>{nextArgs[i]} </span>)
}
return (<div className="argument-suggestion popover top">
<div className="arrow"/>
<div className="popover-content">
Argument suggestion: {results}
</div>
</div>)
}
export default function CommandBar() {
const [input, setInput] = useState("")
const [command, setCommand] = useState("")
const [results, setResults] = useState([])
const [history, setHistory] = useState([])
const [currentPos, setCurrentPos] = useState(0)
const [args, setArgs] = useState({})
const [allCommands, setAllCommands] = useState({})
const [nextArgs, setNextArgs] = useState([])
const [currentArg, setCurrentArg] = useState(0)
const [commandHelp, setCommandHelp] = useState("")
useEffect(() => {
fetchApi('/arguments')
fetchApi('/commands', { method: 'GET' })
.then(response => response.json())
.then(data => setArgs(data))
.then(data => setAllCommands(data))
}, [])
const parseCommand = (input) => {
const parts = Filt.parse(input)
if (allCommands["commands"].hasOwnProperty(parts[0])){
setCommand(parts[0])
} else {
setCommand("")
}
const nextArgs = args[parts[0]]
const nextArgs = allCommands["commands"][parts[0]]?.map(arg => arg.name)
if (nextArgs) {
setNextArgs([parts[0], ...nextArgs])
setCurrentArg(parts.length-1)
}
}
const onChange = (e) => {
setCommand(e.target.value)
setInput(e.target.value)
}
const onKeyDown = (e) => {
if (e.keyCode === Key.ENTER) {
const body = {"command": command}
const body = {"command": input}
const newHistory = Object.assign([], history)
newHistory.splice(currentPos, 0, command)
newHistory.splice(currentPos, 0, input)
fetchApi(`/commands`, {
method: 'POST',
@ -48,21 +85,21 @@ export default function CommandBar() {
.then(data => {
setHistory(newHistory)
setCurrentPos(currentPos + 1)
setNextArgs([])
if (data.result == "") return
setResults([...results, {"id": results.length, "result": data.result}])
})
setCommand("")
setInput("")
}
if (e.keyCode === Key.UP) {
if (currentPos > 0) {
setCommand(history[currentPos - 1])
setInput(history[currentPos - 1])
setCurrentPos(currentPos - 1)
}
}
if (e.keyCode === Key.DOWN) {
setCommand(history[currentPos])
setInput(history[currentPos])
if (currentPos < history.length -1) {
setCurrentPos(currentPos + 1)
}
@ -71,14 +108,14 @@ export default function CommandBar() {
}
const onKeyUp = (e) => {
if (command == "") return
parseCommand(command)
if (input == "") return
parseCommand(input)
e.stopPropagation()
}
return (
<>
<div className="command">
<div className="command">
<div className="command-title">
Command Result
</div>
<div className="command-result">
@ -88,7 +125,7 @@ export default function CommandBar() {
</div>
))}
</div>
{ nextArgs ? <div className="command-suggestion">Argument suggestion: {nextArgs.join(" ")}</div> : null }
{ nextArgs.length > 0 && <ArgumentSuggestion nextArgs={nextArgs} currentArg={currentArg} /> }
<div className={classnames('command-input input-group')}>
<span className="input-group-addon">
<i className={'fa fa-fw fa-terminal'}/>
@ -97,12 +134,13 @@ export default function CommandBar() {
type="text"
placeholder="Enter command"
className="form-control"
value={command}
value={input}
onChange={onChange}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
/>
</div>
</>
{ !command && <AvailableCommands input={input} commands={allCommands["commands"]} /> }
</div>
)
}