Skip to content

Commit

Permalink
Added ACE editor for VQL shell input. (Velocidex#1071)
Browse files Browse the repository at this point in the history
Also added delete flow buttons to shell cells.
  • Loading branch information
scudette authored May 14, 2021
1 parent 6f54dea commit e284c5f
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 17 deletions.
1 change: 1 addition & 0 deletions artifacts/assets/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package assets
1 change: 1 addition & 0 deletions gui/velociraptor/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package velociraptor
6 changes: 6 additions & 0 deletions gui/velociraptor/src/components/clients/shell-viewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,9 @@ div.dt-buttons {
max-height: calc(100vh - 50px - 20px - 47px - 66px - 40px);
overflow-y: auto;
}

.shell-command .velo-ace-editor {
flex: auto;
width: 1%;
min-width: 0;
}
105 changes: 89 additions & 16 deletions gui/velociraptor/src/components/clients/shell-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import axios from 'axios';
import VeloTimestamp from "../utils/time.js";
import VeloPagedTable from "../core/paged-table.js";
import VeloAce from '../core/ace.js';
import Completer from '../artifacts/syntax.js';
import { DeleteFlowDialog } from "../flows/flows-list.js";


// Refresh every 5 seconds
const SHELL_POLL_TIME = 5000;
Expand All @@ -30,6 +33,7 @@ class VeloShellCell extends Component {
collapsed: false,
output: [],
loaded: false,
showDeleteWizard: false,
}

getInput = () => {
Expand Down Expand Up @@ -151,9 +155,9 @@ class VeloShellCell extends Component {
);
}

let flow_status;
let flow_status = [];
if (this.props.flow.state === 'RUNNING') {
flow_status = (
flow_status.push(
<button className="btn btn-outline-info"
disabled>
<i><FontAwesomeIcon icon="spinner" spin /></i>
Expand All @@ -162,7 +166,7 @@ class VeloShellCell extends Component {
</button>
);
} else if (this.props.flow.state === 'FINISHED') {
flow_status = (
flow_status.push(
<button className="btn btn-outline-info"
disabled>
<VeloTimestamp usec={this.props.flow.active_time/1000} />
Expand All @@ -171,7 +175,7 @@ class VeloShellCell extends Component {
);

} else if (this.props.flow.state === 'ERROR') {
flow_status = (
flow_status.push(
<button className="btn btn-outline-info"
disabled>
<i><FontAwesomeIcon icon="exclamation"/></i>
Expand All @@ -181,6 +185,15 @@ class VeloShellCell extends Component {
);
}

flow_status.push(
<button className="btn btn-default" key={5}
title="Delete"
onClick={()=>this.setState({showDeleteWizard: true})}>
<i><FontAwesomeIcon icon="trash"/></i>
</button>
);


let output = "";
if (this.state.loaded) {
output = this.state.output.map((item, index) => {
Expand All @@ -192,6 +205,15 @@ class VeloShellCell extends Component {

return (
<>
{ this.state.showDeleteWizard &&
<DeleteFlowDialog
client={this.props.client}
flow={this.props.flow}
onClose={e=>{
this.setState({showDeleteWizard: false});
}}
/>
}
<div className={classNames({
collapsed: this.state.collapsed,
expanded: !this.state.collapsed,
Expand Down Expand Up @@ -228,6 +250,7 @@ class VeloVQLCell extends Component {

state = {
loaded: false,
showDeleteWizard: false,
}

getInput = () => {
Expand Down Expand Up @@ -325,9 +348,9 @@ class VeloVQLCell extends Component {
);
}

let flow_status;
let flow_status = [];
if (this.props.flow.state === 'RUNNING') {
flow_status = (
flow_status.push(
<button className="btn btn-outline-info"
disabled>
<i><FontAwesomeIcon icon="spinner" spin /></i>
Expand All @@ -336,7 +359,7 @@ class VeloVQLCell extends Component {
</button>
);
} else if (this.props.flow.state === 'FINISHED') {
flow_status = (
flow_status.push(
<button className="btn btn-outline-info"
disabled>
<VeloTimestamp usec={this.props.flow.active_time/1000} />
Expand All @@ -345,7 +368,7 @@ class VeloVQLCell extends Component {
);

} else if (this.props.flow.state === 'ERROR') {
flow_status = (
flow_status.push(
<button className="btn btn-outline-info"
disabled>
<i><FontAwesomeIcon icon="exclamation"/></i>
Expand All @@ -355,6 +378,14 @@ class VeloVQLCell extends Component {
);
}

flow_status.push(
<button className="btn btn-default" key={5}
title="Delete"
onClick={()=>this.setState({showDeleteWizard: true})}>
<i><FontAwesomeIcon icon="trash"/></i>
</button>
);

let output = <div></div>;
if (this.state.loaded) {
let artifact = this.props.flow && this.props.flow.request &&
Expand All @@ -369,6 +400,15 @@ class VeloVQLCell extends Component {

return (
<>
{ this.state.showDeleteWizard &&
<DeleteFlowDialog
client={this.props.client}
flow={this.props.flow}
onClose={e=>{
this.setState({showDeleteWizard: false});
}}
/>
}
<div className={classNames({
collapsed: this.state.collapsed,
expanded: !this.state.collapsed,
Expand Down Expand Up @@ -438,13 +478,11 @@ class ShellViewer extends Component {
setType = (shell) => {
this.setState({
shell_type: shell,
command: this.state.command,
});
};

setText = (e) => {
this.setState({
shell_type: this.state.shell_type,
command: e.target.value,
});
};
Expand All @@ -463,11 +501,11 @@ class ShellViewer extends Component {
if (response.cancel) return;
let new_state = Object.assign({}, this.state);
new_state.flows = [];
if (!response.data || !response.data.items) {
if (!response.data) {
return;
}

let items = response.data.items;
let items = response.data.items || [];

for(var i=0; i<items.length; i++) {
var artifacts = items[i].request.artifacts;
Expand Down Expand Up @@ -540,7 +578,26 @@ class ShellViewer extends Component {
});
};

aceConfig = (ace) => {
// Attach a completer to ACE.
let completer = new Completer();
completer.initializeAceEditor(ace, {});

ace.setOptions({
autoScrollEditorIntoView: true,
maxLines: 25
});

this.setState({ace: ace});
};

render() {
let simple_textarea = true;
if (this.state.shell_type == "VQL") {
simple_textarea = false;
}


return (
<>
<div className="shell-command">
Expand All @@ -556,10 +613,26 @@ class ShellViewer extends Component {
<Dropdown.Item eventKey="VQL">VQL</Dropdown.Item>
</DropdownButton>
</InputGroup.Prepend>
<textarea focus-me="controller.focus" rows="1"
className="form-control"
onChange={(e) => this.setText(e)}>
</textarea>
{ simple_textarea ?
<textarea focus-me="controller.focus" rows="1"
className="form-control"
onChange={(e) => this.setText(e)}>
{this.state.command}
</textarea> :
<VeloAce
mode="VQL"
aceConfig={this.aceConfig}
text={this.state.command}
onChange={(value) => {this.setState({command: value});}}
commands={[{
name: 'saveAndExit',
bindKey: {win: 'Ctrl-Enter', mac: 'Command-Enter'},
exec: (editor) => {
this.launchCommand();
},
}]}
/>
}

<InputGroup.Append className="input-group-append">
<button className="btn btn-danger" type="button"
Expand Down
2 changes: 1 addition & 1 deletion gui/velociraptor/src/components/flows/flows-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import Modal from 'react-bootstrap/Modal';
import axios from 'axios';


class DeleteFlowDialog extends React.PureComponent {
export class DeleteFlowDialog extends React.PureComponent {
static propTypes = {
client: PropTypes.object,
flow: PropTypes.object,
Expand Down

0 comments on commit e284c5f

Please sign in to comment.