diff --git a/src/pkg/lib/journal.js b/src/pkg/lib/journal.js index 4fff73c..21ea706 100644 --- a/src/pkg/lib/journal.js +++ b/src/pkg/lib/journal.js @@ -119,6 +119,8 @@ cmd.push("--after=" + options.after); if (options.merge) cmd.push("-m"); + if (options.grep) + cmd.push("--grep=" + options.grep); /* journalctl doesn't allow reverse and follow together */ if (options.reverse) diff --git a/src/player.jsx b/src/player.jsx index 6e5444d..d5f91c0 100644 --- a/src/player.jsx +++ b/src/player.jsx @@ -20,12 +20,53 @@ import React from 'react'; let cockpit = require("cockpit"); let _ = cockpit.gettext; +let moment = require("moment"); let Term = require("term.js-cockpit"); let Journal = require("journal"); let $ = require("jquery"); require("console.css"); require("bootstrap-slider"); +let padInt = function (n, w) { + let i = Math.floor(n); + let a = Math.abs(i); + let s = a.toString(); + for (w -= s.length; w > 0; w--) { + s = '0' + s; + } + return ((i < 0) ? '-' : '') + s; +}; + +let formatDateTime = function (ms) { + return moment(ms).format("YYYY-MM-DD HH:mm:ss"); +}; + +/* + * Format a time interval from a number of milliseconds. + */ +let formatDuration = function (ms) { + let v = Math.floor(ms / 1000); + let s = Math.floor(v % 60); + v = Math.floor(v / 60); + let m = Math.floor(v % 60); + v = Math.floor(v / 60); + let h = Math.floor(v % 24); + let d = Math.floor(v / 24); + let str = ''; + + if (d > 0) { + str += d + ' ' + _("days") + ' '; + } + + if (h > 0 || str.length > 0) { + str += padInt(h, 2) + ':'; + } + + str += padInt(m, 2) + ':' + padInt(s, 2); + + return (ms < 0 ? '-' : '') + str; +}; + let scrollToBottom = function(id) { const el = document.getElementById(id); if (el) { @@ -564,6 +605,88 @@ class Slider extends React.Component { } } +function SearchEntry(props) { + return ( + props.fastForwardToTS(props.pos, e)}>{formatDuration(props.pos)} + ); +} + +class Search extends React.Component { + constructor(props) { + super(props); + this.handleInputChange = this.handleInputChange.bind(this); + this.handleStream = this.handleStream.bind(this); + this.handleError = this.handleError.bind(this); + this.handleSearchSubmit = this.handleSearchSubmit.bind(this); + this.clearSearchResults = this.clearSearchResults.bind(this); + this.state = { + search: cockpit.location.options.search_rec || cockpit.location.options.search || "", + }; + } + + handleInputChange(event) { + event.preventDefault(); + const name = event.target.name; + const value = event.target.value; + let state = {}; + state[name] = value; + this.setState(state); + cockpit.location.go(cockpit.location.path[0], $.extend(cockpit.location.options, {search_rec: value})); + } + + handleSearchSubmit() { + this.journalctl = Journal.journalctl( + this.props.matchList, + {count: "all", follow: false, merge: true, grep: this.state.search}); + this.journalctl.fail(this.handleError); + this.journalctl.stream(this.handleStream); + } + + handleStream(data) { + let items = data.map(item => { + return JSON.parse(item.MESSAGE); + }); + items = items.map(item => { + return ; + }); + this.setState({items: items}); + } + + handleError(data) { + this.props.errorService.addMessage(data); + } + + clearSearchResults() { + delete cockpit.location.options.search; + cockpit.location.go(cockpit.location.path[0], cockpit.location.options); + this.setState({search: ""}); + this.handleStream([]); + } + + componentDidMount() { + if (this.state.search) { + this.handleSearchSubmit(); + } + } + + render() { + return ( +
+
+ + + + + +
+
+ {this.state.items} +
+
+ ); + } +} + class InputPlayer extends React.Component { render() { const input = String(this.props.input).replace(/(?:\r\n|\r|\n)/g, " "); @@ -905,7 +1028,7 @@ export class Player extends React.Component { handleKeyDown(event) { let keyCodesFuncs = { - "p": this.playPauseToggle, + "P": this.playPauseToggle, "}": this.speedUp, "{": this.speedDown, "Backspace": this.speedReset, @@ -917,8 +1040,10 @@ export class Player extends React.Component { "-": this.zoomOut, "Z": this.fitIn, }; - if (keyCodesFuncs[event.key]) { - (keyCodesFuncs[event.key](event)); + if (event.target.nodeName.toLowerCase() !== 'input') { + if (keyCodesFuncs[event.key]) { + (keyCodesFuncs[event.key](event)); + } } } @@ -1064,6 +1189,8 @@ export class Player extends React.Component { } render() { + let r = this.props.recording; + let speedExp = this.state.speedExp; let speedFactor = Math.pow(2, Math.abs(speedExp)); let speedStr; @@ -1100,75 +1227,132 @@ export class Player extends React.Component { // ensure react never reuses this div by keying it with the terminal widget return ( -
-
-
-
- {this.state.title} -
-
-
-
+ +
+
+
+
+
+ {this.state.title} +
+
+
+
+
+
+
+ +