Skip to content

Commit

Permalink
Redesigned date/time selector. (Velocidex#3795)
Browse files Browse the repository at this point in the history
* Reflect user preference timezone
* Shows times in RFC3339 format
* Enter time in RFC3339 format
* Dropdown calendar
* Quick settings - next week, prev week etc.
  • Loading branch information
scudette authored Oct 3, 2024
1 parent a84906e commit 84a4314
Show file tree
Hide file tree
Showing 35 changed files with 923 additions and 1,062 deletions.
426 changes: 4 additions & 422 deletions gui/velociraptor/package-lock.json

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions gui/velociraptor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@
"react-bootstrap-typeahead": "^5.2.2",
"react-calendar-timeline": "^0.28.0",
"react-contexify": "5.0.0",
"react-datepicker": "^3.8.0",
"react-datetime-picker": "^4.2.1",
"react-dom": "^16.14.0",
"react-hotkeys": "^2.0.0",
"react-overlays": "5.2.1",
Expand Down
2 changes: 0 additions & 2 deletions gui/velociraptor/src/components/events/events.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import ButtonGroup from 'react-bootstrap/ButtonGroup';
import Button from 'react-bootstrap/Button';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Dropdown from 'react-bootstrap/Dropdown';
import "react-datepicker/dist/react-datepicker.css";

import { EventTableWizard, ServerEventTableWizard } from './event-table.jsx';
import Container from 'react-bootstrap/Container';
import Modal from 'react-bootstrap/Modal';
Expand Down
69 changes: 16 additions & 53 deletions gui/velociraptor/src/components/forms/form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import api from '../core/api-service.jsx';
import _ from 'lodash';
import {CancelToken} from 'axios';
import DateTimePicker from 'react-datetime-picker';
import DateTimePicker from '../widgets/datetime.jsx';
import Form from 'react-bootstrap/Form';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
Expand Down Expand Up @@ -69,7 +69,6 @@ export default class VeloForm extends React.Component {
};

state = {
isUTC: true,

// A Date() object that is parsed from value in local time.
timestamp: null,
Expand Down Expand Up @@ -318,15 +317,7 @@ export default class VeloForm extends React.Component {

case "timestamp": {
// value prop is always a string in ISO format in UTC timezone.
let date = convertToDate(this.props.value);

// Internally the date selector always uses local browser
// time. If the form is configured to use utc mode, then
// we convert the UTC time to the equivalent time in local
// just for the form.
if(_.isDate(date) && this.state.isUTC) {
date = localTimeFromUTCTime(date);
}
let date = this.props.value;

return (
<Form.Group as={Row} className="velo-form">
Expand All @@ -338,48 +329,20 @@ export default class VeloForm extends React.Component {
</ToolTip>
</Form.Label>
<Col sm="8">
<ButtonGroup className="velo-datetime-picker">
<DateTimePicker
className="btn-group"
showLeadingZeros={true}
onChange={(value) => {
// Clear the prop value
if (!_.isDate(value)) {
this.props.setValue(undefined);

// If the form is in UTC we take the
// date the form gives us (which is in
// local timezone) and force the same
// date into a serialized ISO in Z time.
} else if(this.state.isUTC) {
let local_time = convertToDate(value);
let utc_time = utcTimeFromLocalTime(local_time);
this.props.setValue(utc_time.toISOString());

} else {
// When in local time we just set the
// time as it is.
let local_time = convertToDate(value);
this.props.setValue(local_time.toISOString());
}
}}
value={date}
/>
{this.state.isUTC ?
<Button variant="default-outline"
onClick={() => this.setState({isUTC: false})}
size="sm">UTC</Button>:
<Button variant="default-outline"
onClick={() => this.setState({isUTC: true})}
size="sm">{T("Local")}</Button>
}
<Button variant="default-outline"
onClick={() => {
let now = new Date();
this.props.setValue(now.toISOString());
}}
size="sm">{T("Now")}</Button>
</ButtonGroup>
<DateTimePicker
onChange={(value) => {
// Clear the prop value if the value is not
// a real date.
if (_.isDate(value)) {
this.props.setValue(value.toISOString());
} else if (_.isString(value)) {
this.props.setValue(value);
} else {
this.props.setValue(undefined);
}
}}
value={date}
/>
</Col>
</Form.Group>
);
Expand Down
35 changes: 0 additions & 35 deletions gui/velociraptor/src/components/forms/forms.css
Original file line number Diff line number Diff line change
@@ -1,28 +1,3 @@
/* react calendar */
.react-calendar {
background: var(--color-calendar-background);
}

.react-calendar .react-calendar__navigation button,
.react-calendar .react-calendar__tile {
color: var(--color-foreground-dimmed);
}

.react-calendar .react-calendar__tile--now {
background: var(--background-calendar-tile-now);
color: var(--color-calendar-tile);
}

.react-calendar .react-calendar__tile--active,
.react-calendar .react-calendar__tile--active:hover {
background: var(--background-calendar-tile-active);
color: var(--color-calendar-tile);
}

.react-calendar .react-calendar__month-view__days__day--weekend {
color: var(--accent-color);
}

.regex_array_item {
display: flex;
}
Expand All @@ -36,16 +11,6 @@
width: 100%;
}


.react-datetime-picker.btn-group {
height: 38px;
}

.react-datetime-picker.btn-group .react-datetime-picker__wrapper {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}

.velo-form .form-switch {
margin-top: 7px;
margin-left: 20px;
Expand Down
33 changes: 25 additions & 8 deletions gui/velociraptor/src/components/hunts/new-hunt.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Form from 'react-bootstrap/Form';
import Col from 'react-bootstrap/Col';
import Row from 'react-bootstrap/Row';
import { HotKeys, ObserveKeys } from "react-hotkeys";
import DateTimePicker from 'react-datetime-picker';
import DateTimePicker from '../widgets/datetime.jsx';
import EstimateHunt from './estimate.jsx';
import LabelForm from '../utils/labels.jsx';
import api from '../core/api-service.jsx';
Expand All @@ -28,8 +28,13 @@ import {
PaginationBuilder
} from '../flows/new-collection.jsx';

import { FormatRFC3339, ToStandardTime } from '../utils/time.jsx';

import UserConfig from '../core/user.jsx';

const hour = 60 * 60 * 1000;


// The hunt wizard is built upon the new collection wizard with some extra steps.
class HuntPaginator extends PaginationBuilder {
PaginationSteps = ["Configure Hunt",
Expand Down Expand Up @@ -130,7 +135,15 @@ class NewHuntConfigureHunt extends React.Component {
<Form.Label column sm="3">{T("Expiry")}</Form.Label>
<Col sm="8">
<DateTimePicker value={this.props.parameters.expires}
onChange={(value) => this.setParam("expires", value)}
onChange={(value)=>{
let ts = ToStandardTime(value);
let now = new Date();
if (ts < now) {
api.error(T("Expiry time is in the past"));
return;
}
this.setParam("expires", value);
}}
/>
</Col>
</Form.Group>
Expand Down Expand Up @@ -306,12 +319,15 @@ export default class NewHuntWizard extends React.Component {
setStateFromBase = (hunt) => {
let request = hunt && hunt.start_request;
let expiry = new Date();

let timezone = this.context.traits.timezone || "UTC";
let hunt_expiry_hours = this.context.traits && this.context.traits.customizations &&
this.context.traits.customizations.hunt_expiry_hours;
if (!hunt_expiry_hours) {hunt_expiry_hours = 7 * 24;};
if (!hunt_expiry_hours) {
hunt_expiry_hours = 7 * 24;
};

expiry.setTime(expiry.getTime() + hunt_expiry_hours * 60 * 60 * 1000);
// Calcuate the default expiry to 1 week from now.
expiry.setTime(expiry.getTime() + hunt_expiry_hours * hour);

if (request) {
let state = {
Expand Down Expand Up @@ -347,7 +363,7 @@ export default class NewHuntWizard extends React.Component {
}
state.hunt_parameters.description = hunt.hunt_description;
state.hunt_parameters.tags = hunt.tags || [];
state.hunt_parameters.expires = expiry;
state.hunt_parameters.expires = FormatRFC3339(expiry, timezone);
state.hunt_parameters.org_ids = hunt.org_ids || [];

if (_.isEmpty(request.artifacts)) {
Expand Down Expand Up @@ -399,7 +415,7 @@ export default class NewHuntWizard extends React.Component {
};

let state = this.state;
state.hunt_parameters.expires = expiry;
state.hunt_parameters.expires = FormatRFC3339(expiry, timezone);
return state;
}

Expand Down Expand Up @@ -455,7 +471,8 @@ export default class NewHuntWizard extends React.Component {

let hunt_parameters = this.state.hunt_parameters;
if (hunt_parameters.expires) {
result.expires = hunt_parameters.expires.getTime() * 1000;
// hunt_parameters.expires is already an RFC3339 string.
result.expires = hunt_parameters.expires;
}

if (hunt_parameters.include_condition === "labels") {
Expand Down
29 changes: 28 additions & 1 deletion gui/velociraptor/src/components/i8n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -325,5 +325,32 @@
"56616c756520666f726d206e6577206d65746164617461": "Wert aus neuen Metadaten",
"596f75206172652061626f757420746f2072656d6f766520746869732074696d656c696e65": "Sie sind dabei, diese Zeitleiste zu entfernen",
"5a6970": "Zip",
"4d65737361676520436f6c756d6e": "Nachrichtenspalte"
"4d65737361676520436f6c756d6e": "Nachrichtenspalte",
"41206461792061676f": "Vor einem Tag",
"41206d6f6e74682061676f": "Vor einem Monat",
"41207765656b2061676f": "Vor einer Woche",
"41207765656b2066726f6d206e6f77": "In einer Woche",
"417072696c": "April",
"417567757374": "August",
"436f6c6c6170736520616c6c20636f6c756d6e73": "Alle Spalten ausblenden",
"436f6d7061637420436f6c756d6e": "Kompakt Spalte",
"446563656d626572": "Dezember",
"457870616e6420436f6c756d6e": "Spalte erweitern",
"457870616e6420616c6c20636f6c756d6e73": "Alle Spalten erweitern",
"4578706972792074696d6520697320696e207468652070617374": "Ablaufzeit liegt in der Vergangenheit",
"4665627275617279": "Februar",
"4a616e75617279": "Januar",
"4a756c79": "Juli",
"4a756e65": "Juni",
"4d61726368": "März",
"4d6179": "Mai",
"4e6f76656d626572": "November",
"4f63746f626572": "Oktober",
"52656765782046696c746572": "Regex-Filter",
"53656c6563742054696d65": "Uhrzeit auswählen",
"53656c6563742074696d6520616e64206461746520696e205246433333333920666f726d6174": "Uhrzeit und Datum im RFC3339-Format auswählen",
"53657074656d626572": "September",
"536f7274": "Sortieren",
"5374616e6461726420446f6373": "Standarddokumente",
"59656172": "Jahr"
}
Loading

0 comments on commit 84a4314

Please sign in to comment.