diff --git a/gui/velociraptor/src/App.jsx b/gui/velociraptor/src/App.jsx index c58572e11cc..4197524050a 100644 --- a/gui/velociraptor/src/App.jsx +++ b/gui/velociraptor/src/App.jsx @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import './css/App.css'; import qs from "qs"; +import _ from 'lodash'; import PropTypes from 'prop-types'; import VeloNavigator from './components/sidebar/navigator.jsx'; @@ -96,6 +97,9 @@ class App extends Component { }; setClientSearch = (query) => { + if (!_.isString(query)) { + return; + } let now = new Date(); this.setState({query: query, query_version: now.getTime()}); this.props.history.push('/search/' + (query || "all")); @@ -139,10 +143,8 @@ class App extends Component { - + setSearch={this.setClientSearch} /> { + this.props.setSearch("label:" + label); + } + isSelected = c=>{ return _.includes(this.state.selected, c.client_id); } @@ -693,14 +697,18 @@ class VeloClientList extends Component { {c && c.os_info && c.os_info.fqdn} {c && c.os_info && c.os_info.release} {_.map(c.labels, (label, idx)=>{ - return + ; + + ; })} ); })} diff --git a/gui/velociraptor/src/components/clients/search.jsx b/gui/velociraptor/src/components/clients/search.jsx index ed65a6fd2d1..d01725eb369 100644 --- a/gui/velociraptor/src/components/clients/search.jsx +++ b/gui/velociraptor/src/components/clients/search.jsx @@ -1,4 +1,5 @@ import "./search.css"; +import _ from 'lodash'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; @@ -8,7 +9,6 @@ import Button from 'react-bootstrap/Button'; import ButtonGroup from 'react-bootstrap/ButtonGroup'; import FormGroup from 'react-bootstrap/FormGroup'; import Form from 'react-bootstrap/Form'; -import { withRouter } from "react-router-dom"; import Autosuggest from 'react-autosuggest'; import Dropdown from 'react-bootstrap/Dropdown'; import UserConfig from '../core/user.jsx'; @@ -18,26 +18,28 @@ import {CancelToken} from 'axios'; import T from '../i8n/i8n.jsx'; -class VeloClientSearch extends Component { +export default class VeloClientSearch extends Component { static contextType = UserConfig; static propTypes = { // Update the applications's search parameter. setSearch: PropTypes.func.isRequired, - - // React router props. - match: PropTypes.object, - history: PropTypes.object, }; componentDidMount = () => { this.source = CancelToken.source(); - let query = this.props.match && this.props.match.params && - this.props.match.params.query; + let query = this.getQueryFromLocation(); if (query && query !== this.state.query) { - this.this.setState({query: query}); + this.setState({query: query}); }; }; + componentDidUpdate = (prevProps, prevState, rootNode) => { + let query = this.getQueryFromLocation(); + if (query && query !== this.state.query) { + this.setQuery(query); + }; + } + componentWillUnmount() { this.source.cancel("unmounted"); } @@ -45,16 +47,35 @@ class VeloClientSearch extends Component { state = { // query used to update suggestions. query: "", + + // When the user begins editing the query string we set the + // new query here. When the user submits the query, we clear + // this and set the query in the upstream component. + + // When the edit box is editing, we block automatic updates of + // the query content from the URL. + pending_query: null, options: [], } + getQueryFromLocation = ()=>{ + let hash = window.location.hash || ""; + if(hash.startsWith("#/search/")) { + return hash.substring(9); + }; + return ""; + } + showAll = () => { this.setState({query: "all"}); this.props.setSearch("all"); } setQuery = (query) => { - this.setState({query: query}); + if(_.isString(this.state.pending_query)) { + query = this.state.pending_query; + } + this.setState({query: query, pending_query: null}); this.props.setSearch(query); } @@ -75,6 +96,17 @@ class VeloClientSearch extends Component { }); } + // Ensure the query string is a string. + getQuery = ()=>{ + if(_.isString(this.state.pending_query)) { + return this.state.pending_query; + } + if(_.isString(this.state.query)) { + return this.state.query; + } + return ""; + }; + render() { return (
{ @@ -88,20 +120,22 @@ class VeloClientSearch extends Component { onSuggestionsFetchRequested={(x) => this.showSuggestions(x.value)} onSuggestionsClearRequested={() => this.setState({options: []})} onSuggestionSelected={(e, x) => { - this.setQuery(x.suggestionValue); + this.setState({pending_query: null}); + this.props.setSearch(x.suggestionValue); }} getSuggestionValue={x=>x} renderSuggestion={(x) =>
{x}
} inputProps={{ placeholder: T("SEARCH_CLIENTS"), spellCheck: "false", - value: this.state.query, + value: this.getQuery(), id: "client-search-bar", onChange: (e, {newValue, method}) => { - this.setState({query: newValue}); + this.setState({pending_query: newValue}); e.preventDefault(); return false; }, + onBlur: ()=>this.setQuery(this.state.pending_query), }} /> @@ -149,6 +183,3 @@ class VeloClientSearch extends Component { ); } }; - - -export default withRouter(VeloClientSearch);