Rebase and migration to full React instead of react-lite
This commit is contained in:
parent
4ca9b76b23
commit
1abe64fe0c
7 changed files with 2275 additions and 2246 deletions
21
package.json
21
package.json
|
|
@ -42,27 +42,26 @@
|
||||||
"sass-loader": "^7.0.3",
|
"sass-loader": "^7.0.3",
|
||||||
"sizzle": "^2.3.3",
|
"sizzle": "^2.3.3",
|
||||||
"stdio": "^0.2.7",
|
"stdio": "^0.2.7",
|
||||||
"webpack": "^4.17.1",
|
"webpack": "^4.19.0",
|
||||||
"webpack-cli": "^3.1.0"
|
"webpack-cli": "^3.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/polyfill": "^7.0.0",
|
"@babel/polyfill": "^7.0.0",
|
||||||
"node-sass": "^4.9.0",
|
|
||||||
"react": "^16.4.2",
|
|
||||||
"react-dom": "^16.4.2"
|
|
||||||
"bootstrap": "3.3.7",
|
"bootstrap": "3.3.7",
|
||||||
"patternfly": "3.35.1",
|
|
||||||
"webpack": "^2.6.1",
|
|
||||||
"jquery": "3.3.1",
|
|
||||||
"moment": "2.22.2",
|
|
||||||
"mustache": "2.3.0",
|
|
||||||
"bootstrap-datetime-picker": "2.4.4",
|
"bootstrap-datetime-picker": "2.4.4",
|
||||||
"comment-json": "^1.1.3",
|
"comment-json": "^1.1.3",
|
||||||
"term.js-cockpit": "0.0.10",
|
|
||||||
"fs.extra": "^1.3.2",
|
"fs.extra": "^1.3.2",
|
||||||
"fs.realpath": "^1.0.0",
|
"fs.realpath": "^1.0.0",
|
||||||
"ini": "^1.3.5",
|
"ini": "^1.3.5",
|
||||||
|
"jquery": "3.3.1",
|
||||||
|
"moment": "2.22.2",
|
||||||
|
"mustache": "2.3.0",
|
||||||
"node-sass": "^4.9.0",
|
"node-sass": "^4.9.0",
|
||||||
"raw-loader": "^0.5.1"
|
"patternfly": "3.35.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"raw-loader": "^0.5.1",
|
||||||
|
"react": "^16.4.2",
|
||||||
|
"react-dom": "^16.4.2",
|
||||||
|
"term.js-cockpit": "0.0.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,11 @@
|
||||||
|
|
||||||
let cockpit = require("cockpit");
|
let cockpit = require("cockpit");
|
||||||
let React = require("react");
|
let React = require("react");
|
||||||
|
let ReactDOM = require("react-dom");
|
||||||
let json = require('comment-json');
|
let json = require('comment-json');
|
||||||
let ini = require('ini');
|
let ini = require('ini');
|
||||||
|
|
||||||
let Config = class extends React.Component {
|
class Config extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleInputChange = this.handleInputChange.bind(this);
|
this.handleInputChange = this.handleInputChange.bind(this);
|
||||||
|
|
@ -38,7 +39,7 @@
|
||||||
config: null,
|
config: null,
|
||||||
file_error: null,
|
file_error: null,
|
||||||
submitting: "none",
|
submitting: "none",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputChange(e) {
|
handleInputChange(e) {
|
||||||
|
|
@ -59,10 +60,8 @@
|
||||||
|
|
||||||
handleSubmit(event) {
|
handleSubmit(event) {
|
||||||
this.setState({submitting:"block"});
|
this.setState({submitting:"block"});
|
||||||
console.log(event);
|
|
||||||
this.prepareConfig();
|
this.prepareConfig();
|
||||||
this.file.replace(this.state.config).done(() => {
|
this.file.replace(this.state.config).done(() => {
|
||||||
console.log('updated');
|
|
||||||
this.setState({submitting:"none"});
|
this.setState({submitting:"none"});
|
||||||
})
|
})
|
||||||
.fail((error) => {
|
.fail((error) => {
|
||||||
|
|
@ -72,20 +71,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfig(data) {
|
setConfig(data) {
|
||||||
console.log(data);
|
|
||||||
this.setState({config: data});
|
this.setState({config: data});
|
||||||
}
|
}
|
||||||
|
|
||||||
fileReadFailed(reason) {
|
fileReadFailed(reason) {
|
||||||
console.log(reason);
|
console.log(reason);
|
||||||
this.setState({file_error: reason});
|
this.setState({file_error: reason});
|
||||||
console.log('failed to read file');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
let parseFunc = function(data) {
|
let parseFunc = function(data) {
|
||||||
console.log(data);
|
|
||||||
// return data;
|
|
||||||
return json.parse(data, null, true);
|
return json.parse(data, null, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -106,8 +101,6 @@
|
||||||
// host: string
|
// host: string
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(this.file);
|
|
||||||
|
|
||||||
let promise = this.file.read();
|
let promise = this.file.read();
|
||||||
|
|
||||||
promise.done((data) => {
|
promise.done((data) => {
|
||||||
|
|
@ -253,10 +246,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="top" />
|
<td className="top" />
|
||||||
<div className="spinner spinner-sm" style={{display: this.state.submitting}} />
|
|
||||||
<td>
|
<td>
|
||||||
<button className="btn btn-default" type="submit">Save</button>
|
<button className="btn btn-default" type="submit">Save</button>
|
||||||
|
<div className="spinner spinner-sm" style={{display: this.state.submitting}} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
@ -274,9 +266,9 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let SssdConfig = class extends React.Component {
|
class SssdConfig extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
|
@ -284,27 +276,24 @@
|
||||||
this.setConfig = this.setConfig.bind(this);
|
this.setConfig = this.setConfig.bind(this);
|
||||||
this.file = null;
|
this.file = null;
|
||||||
this.state = {
|
this.state = {
|
||||||
config: {
|
scope: "",
|
||||||
session_recording: {
|
users: "",
|
||||||
scope: null,
|
groups: "",
|
||||||
users: null,
|
submitting: "none",
|
||||||
groups: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputChange(e) {
|
handleInputChange(e) {
|
||||||
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
|
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
|
||||||
const name = e.target.name;
|
const name = e.target.name;
|
||||||
const config = this.state.config;
|
const state = {};
|
||||||
config.session_recording[name] = value;
|
state[name] = value;
|
||||||
|
this.setState(state);
|
||||||
this.forceUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfig(data) {
|
setConfig(data) {
|
||||||
this.setState({config: data});
|
const config = {...data['session_recording']};
|
||||||
|
this.setState(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
|
@ -327,14 +316,21 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit() {
|
handleSubmit(e) {
|
||||||
this.file.replace(this.state.config).done( function() {
|
this.setState({submitting:"block"});
|
||||||
console.log('updated');
|
const obj = {};
|
||||||
|
obj.users = this.state.users;
|
||||||
|
obj.groups = this.state.groups;
|
||||||
|
obj.scope = this.state.scope;
|
||||||
|
obj['session_recording'] = this.state;
|
||||||
|
let _this = this;
|
||||||
|
this.file.replace(obj).done(function() {
|
||||||
|
_this.setState({submitting:"none"});
|
||||||
})
|
})
|
||||||
.fail(function(error) {
|
.fail(function(error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
event.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
@ -346,7 +342,7 @@
|
||||||
<td><label htmlFor="scope">Scope</label></td>
|
<td><label htmlFor="scope">Scope</label></td>
|
||||||
<td>
|
<td>
|
||||||
<select name="scope" id="scope" className="form-control"
|
<select name="scope" id="scope" className="form-control"
|
||||||
value={this.state.config.session_recording.scope}
|
value={this.state.scope}
|
||||||
onChange={this.handleInputChange} >
|
onChange={this.handleInputChange} >
|
||||||
<option value="none">None</option>
|
<option value="none">None</option>
|
||||||
<option value="some">Some</option>
|
<option value="some">Some</option>
|
||||||
|
|
@ -358,24 +354,23 @@
|
||||||
<td><label htmlFor="users">Users</label></td>
|
<td><label htmlFor="users">Users</label></td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" id="users" name="users"
|
<input type="text" id="users" name="users"
|
||||||
value={this.state.config.session_recording.users}
|
value={this.state.users}
|
||||||
className="form-control" />
|
className="form-control" onChange={this.handleInputChange} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><label htmlFor="groups">Groups</label></td>
|
<td><label htmlFor="groups">Groups</label></td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" id="groups" name="groups"
|
<input type="text" id="groups" name="groups"
|
||||||
value={this.state.config.session_recording.groups}
|
value={this.state.groups}
|
||||||
className="form-control" onChange={this.handleInputChange} />
|
className="form-control" onChange={this.handleInputChange} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td />
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<button className="btn btn-default" type="submit">Save</button>
|
<button className="btn btn-default" type="submit">Save</button>
|
||||||
|
<span className="spinner spinner-sm" style={{display: this.state.submitting}} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
@ -383,8 +378,8 @@
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
React.render(<Config />, document.getElementById('sr_config'));
|
ReactDOM.render(<Config />, document.getElementById('sr_config'));
|
||||||
React.render(<SssdConfig />, document.getElementById('sssd_config'));
|
ReactDOM.render(<SssdConfig />, document.getElementById('sssd_config'));
|
||||||
}());
|
}());
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,9 @@
|
||||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use strict";
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
var React = require('react');
|
import './listing.less';
|
||||||
|
|
||||||
require('./listing.less');
|
|
||||||
|
|
||||||
/* entry for an alert in the listing, can be expanded (with details) or standard
|
/* entry for an alert in the listing, can be expanded (with details) or standard
|
||||||
* rowId optional: an identifier for the row which will be set as "data-row-id" attribute on the <tr>
|
* rowId optional: an identifier for the row which will be set as "data-row-id" attribute on the <tr>
|
||||||
|
|
@ -47,55 +45,43 @@ require('./listing.less');
|
||||||
* initiallyExpanded optional: the entry will be initially rendered as expanded, but then behaves normally
|
* initiallyExpanded optional: the entry will be initially rendered as expanded, but then behaves normally
|
||||||
* expandChanged optional: callback will be used if the row is either expanded or collapsed passing single `isExpanded` boolean argument
|
* expandChanged optional: callback will be used if the row is either expanded or collapsed passing single `isExpanded` boolean argument
|
||||||
*/
|
*/
|
||||||
var ListingRow = React.createClass({
|
export class ListingRow extends React.Component {
|
||||||
propTypes: {
|
constructor(props) {
|
||||||
rowId: React.PropTypes.string,
|
super(props);
|
||||||
columns: React.PropTypes.array.isRequired,
|
this.state = {
|
||||||
tabRenderers: React.PropTypes.array,
|
|
||||||
navigateToItem: React.PropTypes.func,
|
|
||||||
listingDetail: React.PropTypes.node,
|
|
||||||
listingActions: React.PropTypes.arrayOf(React.PropTypes.node),
|
|
||||||
selectChanged: React.PropTypes.func,
|
|
||||||
selected: React.PropTypes.bool,
|
|
||||||
initiallyExpanded: React.PropTypes.bool,
|
|
||||||
expandChanged: React.PropTypes.func,
|
|
||||||
initiallyActiveTab: React.PropTypes.bool,
|
|
||||||
},
|
|
||||||
getDefaultProps: function () {
|
|
||||||
return {
|
|
||||||
tabRenderers: [],
|
|
||||||
navigateToItem: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
expanded: this.props.initiallyExpanded, // show expanded view if true, otherwise one line compact
|
expanded: this.props.initiallyExpanded, // show expanded view if true, otherwise one line compact
|
||||||
activeTab: this.props.initiallyActiveTab ? this.props.initiallyActiveTab : 0, // currently active tab in expanded mode, defaults to first tab
|
activeTab: this.props.initiallyActiveTab ? this.props.initiallyActiveTab : 0, // currently active tab in expanded mode, defaults to first tab
|
||||||
loadedTabs: {}, // which tabs were already loaded - this is important for 'loadOnDemand' setting
|
loadedTabs: {}, // which tabs were already loaded - this is important for 'loadOnDemand' setting
|
||||||
// contains tab indices
|
// contains tab indices
|
||||||
selected: this.props.selected, // whether the current row is selected
|
selected: this.props.selected, // whether the current row is selected
|
||||||
};
|
};
|
||||||
},
|
this.handleNavigateClick = this.handleNavigateClick.bind(this);
|
||||||
handleNavigateClick: function(e) {
|
this.handleExpandClick = this.handleExpandClick.bind(this);
|
||||||
|
this.handleSelectClick = this.handleSelectClick.bind(this);
|
||||||
|
this.handleTabClick = this.handleTabClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNavigateClick(e) {
|
||||||
// only consider primary mouse button
|
// only consider primary mouse button
|
||||||
if (!e || e.button !== 0)
|
if (!e || e.button !== 0)
|
||||||
return;
|
return;
|
||||||
this.props.navigateToItem();
|
this.props.navigateToItem();
|
||||||
},
|
}
|
||||||
handleExpandClick: function(e) {
|
|
||||||
|
handleExpandClick(e) {
|
||||||
// only consider primary mouse button
|
// only consider primary mouse button
|
||||||
if (!e || e.button !== 0)
|
if (!e || e.button !== 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var willBeExpanded = !this.state.expanded && this.props.tabRenderers.length > 0;
|
let willBeExpanded = !this.state.expanded && this.props.tabRenderers.length > 0;
|
||||||
this.setState({ expanded: willBeExpanded });
|
this.setState({ expanded: willBeExpanded });
|
||||||
|
|
||||||
var loadedTabs = {};
|
let loadedTabs = {};
|
||||||
// unload all tabs if not expanded
|
// unload all tabs if not expanded
|
||||||
if (willBeExpanded) {
|
if (willBeExpanded) {
|
||||||
// see if we should preload some tabs
|
// see if we should preload some tabs
|
||||||
var tabIdx;
|
let tabIdx;
|
||||||
var tabPresence;
|
let tabPresence;
|
||||||
for (tabIdx = 0; tabIdx < this.props.tabRenderers.length; tabIdx++) {
|
for (tabIdx = 0; tabIdx < this.props.tabRenderers.length; tabIdx++) {
|
||||||
if ('presence' in this.props.tabRenderers[tabIdx])
|
if ('presence' in this.props.tabRenderers[tabIdx])
|
||||||
tabPresence = this.props.tabRenderers[tabIdx].presence;
|
tabPresence = this.props.tabRenderers[tabIdx].presence;
|
||||||
|
|
@ -115,13 +101,14 @@ var ListingRow = React.createClass({
|
||||||
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
},
|
}
|
||||||
handleSelectClick: function(e) {
|
|
||||||
|
handleSelectClick(e) {
|
||||||
// only consider primary mouse button
|
// only consider primary mouse button
|
||||||
if (!e || e.button !== 0)
|
if (!e || e.button !== 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var selected = !this.state.selected;
|
let selected = !this.state.selected;
|
||||||
this.setState({ selected: selected });
|
this.setState({ selected: selected });
|
||||||
|
|
||||||
if (this.props.selectChanged)
|
if (this.props.selectChanged)
|
||||||
|
|
@ -129,14 +116,15 @@ var ListingRow = React.createClass({
|
||||||
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
},
|
}
|
||||||
handleTabClick: function(tabIdx, e) {
|
|
||||||
|
handleTabClick(tabIdx, e) {
|
||||||
// only consider primary mouse button
|
// only consider primary mouse button
|
||||||
if (!e || e.button !== 0)
|
if (!e || e.button !== 0)
|
||||||
return;
|
return;
|
||||||
var prevTab = this.state.activeTab;
|
let prevTab = this.state.activeTab;
|
||||||
var prevTabPresence = 'default';
|
let prevTabPresence = 'default';
|
||||||
var loadedTabs = this.state.loadedTabs;
|
let loadedTabs = this.state.loadedTabs;
|
||||||
if (prevTab !== tabIdx) {
|
if (prevTab !== tabIdx) {
|
||||||
// see if we need to unload the previous tab
|
// see if we need to unload the previous tab
|
||||||
if ('presence' in this.props.tabRenderers[prevTab])
|
if ('presence' in this.props.tabRenderers[prevTab])
|
||||||
|
|
@ -151,41 +139,42 @@ var ListingRow = React.createClass({
|
||||||
}
|
}
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
},
|
}
|
||||||
render: function() {
|
|
||||||
var self = this;
|
|
||||||
// only enable navigation if a function is provided and the row isn't expanded (prevent accidental navigation)
|
|
||||||
var allowNavigate = !!this.props.navigateToItem && !this.state.expanded;
|
|
||||||
|
|
||||||
var headerEntries = this.props.columns.map(function(itm) {
|
render() {
|
||||||
|
let self = this;
|
||||||
|
// only enable navigation if a function is provided and the row isn't expanded (prevent accidental navigation)
|
||||||
|
let allowNavigate = !!this.props.navigateToItem && !this.state.expanded;
|
||||||
|
|
||||||
|
let headerEntries = this.props.columns.map((itm, index) => {
|
||||||
if (typeof itm === 'string' || typeof itm === 'number' || itm === null || itm === undefined || itm instanceof String || React.isValidElement(itm))
|
if (typeof itm === 'string' || typeof itm === 'number' || itm === null || itm === undefined || itm instanceof String || React.isValidElement(itm))
|
||||||
return (<td>{itm}</td>);
|
return (<td key={index}>{itm}</td>);
|
||||||
else if ('header' in itm && itm.header)
|
else if ('header' in itm && itm.header)
|
||||||
return (<th>{itm.name}</th>);
|
return (<th key={index}>{itm.name}</th>);
|
||||||
else if ('tight' in itm && itm.tight)
|
else if ('tight' in itm && itm.tight)
|
||||||
return (<td className="listing-ct-actions">{itm.name || itm.element}</td>);
|
return (<td key={index} className="listing-ct-actions">{itm.name || itm.element}</td>);
|
||||||
else
|
else
|
||||||
return (<td>{itm.name}</td>);
|
return (<td key={index}>{itm.name}</td>);
|
||||||
});
|
});
|
||||||
|
|
||||||
var allowExpand = (this.props.tabRenderers.length > 0);
|
let allowExpand = (this.props.tabRenderers.length > 0);
|
||||||
var expandToggle;
|
let expandToggle;
|
||||||
if (allowExpand) {
|
if (allowExpand) {
|
||||||
expandToggle = <td className="listing-ct-toggle" onClick={ allowNavigate ? this.handleExpandClick : undefined }>
|
expandToggle = <td key="expandToggle" className="listing-ct-toggle" onClick={ allowNavigate ? this.handleExpandClick : undefined }>
|
||||||
<i className="fa fa-fw" />
|
<i className="fa fa-fw" />
|
||||||
</td>;
|
</td>;
|
||||||
} else {
|
} else {
|
||||||
expandToggle = <td className="listing-ct-toggle" />;
|
expandToggle = <td key="expandToggle-empty" className="listing-ct-toggle" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
var listingItemClasses = ["listing-ct-item"];
|
let listingItemClasses = ["listing-ct-item"];
|
||||||
if (!allowNavigate)
|
if (!allowNavigate)
|
||||||
listingItemClasses.push("listing-ct-nonavigate");
|
listingItemClasses.push("listing-ct-nonavigate");
|
||||||
if (!allowExpand)
|
if (!allowExpand)
|
||||||
listingItemClasses.push("listing-ct-noexpand");
|
listingItemClasses.push("listing-ct-noexpand");
|
||||||
|
|
||||||
var allowSelect = !(allowNavigate || allowExpand) && (this.state.selected !== undefined);
|
let allowSelect = !(allowNavigate || allowExpand) && (this.state.selected !== undefined);
|
||||||
var clickHandler;
|
let clickHandler;
|
||||||
if (allowSelect) {
|
if (allowSelect) {
|
||||||
clickHandler = this.handleSelectClick;
|
clickHandler = this.handleSelectClick;
|
||||||
if (this.state.selected)
|
if (this.state.selected)
|
||||||
|
|
@ -197,7 +186,7 @@ var ListingRow = React.createClass({
|
||||||
clickHandler = this.handleExpandClick;
|
clickHandler = this.handleExpandClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
var listingItem = (
|
let listingItem = (
|
||||||
<tr data-row-id={ this.props.rowId }
|
<tr data-row-id={ this.props.rowId }
|
||||||
className={ listingItemClasses.join(' ') }
|
className={ listingItemClasses.join(' ') }
|
||||||
onClick={clickHandler}>
|
onClick={clickHandler}>
|
||||||
|
|
@ -207,18 +196,18 @@ var ListingRow = React.createClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.state.expanded) {
|
if (this.state.expanded) {
|
||||||
var links = this.props.tabRenderers.map(function(itm, idx) {
|
let links = this.props.tabRenderers.map((itm, idx) => {
|
||||||
return (
|
return (
|
||||||
<li key={idx} className={ (idx === self.state.activeTab) ? "active" : ""} >
|
<li key={idx} className={ (idx === self.state.activeTab) ? "active" : ""} >
|
||||||
<a href="#" tabIndex="0" onClick={ self.handleTabClick.bind(self, idx) }>{itm.name}</a>
|
<a href="#" tabIndex="0" onClick={ self.handleTabClick.bind(self, idx) }>{itm.name}</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
var tabs = [];
|
let tabs = [];
|
||||||
var tabIdx;
|
let tabIdx;
|
||||||
var Renderer;
|
let Renderer;
|
||||||
var rendererData;
|
let rendererData;
|
||||||
var row;
|
let row;
|
||||||
for (tabIdx = 0; tabIdx < this.props.tabRenderers.length; tabIdx++) {
|
for (tabIdx = 0; tabIdx < this.props.tabRenderers.length; tabIdx++) {
|
||||||
Renderer = this.props.tabRenderers[tabIdx].renderer;
|
Renderer = this.props.tabRenderers[tabIdx].renderer;
|
||||||
rendererData = this.props.tabRenderers[tabIdx].data;
|
rendererData = this.props.tabRenderers[tabIdx].data;
|
||||||
|
|
@ -231,7 +220,7 @@ var ListingRow = React.createClass({
|
||||||
tabs.push(<div className="listing-ct-body" key={tabIdx} hidden>{row}</div>);
|
tabs.push(<div className="listing-ct-body" key={tabIdx} hidden>{row}</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
var listingDetail;
|
let listingDetail;
|
||||||
if ('listingDetail' in this.props) {
|
if ('listingDetail' in this.props) {
|
||||||
listingDetail = (
|
listingDetail = (
|
||||||
<span className="listing-ct-caption">
|
<span className="listing-ct-caption">
|
||||||
|
|
@ -268,8 +257,26 @@ var ListingRow = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
ListingRow.defaultProps = {
|
||||||
|
tabRenderers: [],
|
||||||
|
navigateToItem: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
ListingRow.propTypes = {
|
||||||
|
rowId: PropTypes.string,
|
||||||
|
columns: PropTypes.array.isRequired,
|
||||||
|
tabRenderers: PropTypes.array,
|
||||||
|
navigateToItem: PropTypes.func,
|
||||||
|
listingDetail: PropTypes.node,
|
||||||
|
listingActions: PropTypes.arrayOf(PropTypes.node),
|
||||||
|
selectChanged: PropTypes.func,
|
||||||
|
selected: PropTypes.bool,
|
||||||
|
initiallyExpanded: PropTypes.bool,
|
||||||
|
expandChanged: PropTypes.func,
|
||||||
|
initiallyActiveTab: PropTypes.bool
|
||||||
|
};
|
||||||
/* Implements a PatternFly 'List View' pattern
|
/* Implements a PatternFly 'List View' pattern
|
||||||
* https://www.patternfly.org/list-view/
|
* https://www.patternfly.org/list-view/
|
||||||
* Properties:
|
* Properties:
|
||||||
|
|
@ -281,44 +288,27 @@ var ListingRow = React.createClass({
|
||||||
* receives the column index as argument
|
* receives the column index as argument
|
||||||
* - actions: additional listing-wide actions (displayed next to the list's title)
|
* - actions: additional listing-wide actions (displayed next to the list's title)
|
||||||
*/
|
*/
|
||||||
var Listing = React.createClass({
|
export const Listing = (props) => {
|
||||||
propTypes: {
|
let bodyClasses = ["listing", "listing-ct"];
|
||||||
title: React.PropTypes.string.isRequired,
|
if (props.fullWidth)
|
||||||
fullWidth: React.PropTypes.bool,
|
|
||||||
emptyCaption: React.PropTypes.string.isRequired,
|
|
||||||
columnTitles: React.PropTypes.arrayOf(React.PropTypes.string),
|
|
||||||
columnTitleClick: React.PropTypes.func,
|
|
||||||
actions: React.PropTypes.arrayOf(React.PropTypes.node)
|
|
||||||
},
|
|
||||||
getDefaultProps: function () {
|
|
||||||
return {
|
|
||||||
fullWidth: true,
|
|
||||||
columnTitles: [],
|
|
||||||
actions: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var self = this;
|
|
||||||
var bodyClasses = ["listing", "listing-ct"];
|
|
||||||
if (this.props.fullWidth)
|
|
||||||
bodyClasses.push("listing-ct-wide");
|
bodyClasses.push("listing-ct-wide");
|
||||||
var headerClasses;
|
let headerClasses;
|
||||||
var headerRow;
|
let headerRow;
|
||||||
var selectableRows;
|
let selectableRows;
|
||||||
if (!this.props.children || this.props.children.length === 0) {
|
if (!props.children || props.children.length === 0) {
|
||||||
headerClasses = "listing-ct-empty";
|
headerClasses = "listing-ct-empty";
|
||||||
headerRow = <tr><td>{this.props.emptyCaption}</td></tr>;
|
headerRow = <tr><td>{props.emptyCaption}</td></tr>;
|
||||||
} else if (this.props.columnTitles.length) {
|
} else if (props.columnTitles.length) {
|
||||||
// check if any of the children are selectable
|
// check if any of the children are selectable
|
||||||
selectableRows = false;
|
selectableRows = false;
|
||||||
this.props.children.forEach(function(r) {
|
props.children.forEach(function(r) {
|
||||||
if (r.props.selected !== undefined)
|
if (r.props.selected !== undefined)
|
||||||
selectableRows = true;
|
selectableRows = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selectableRows) {
|
if (selectableRows) {
|
||||||
// now make sure that if one is set, it's available on all items
|
// now make sure that if one is set, it's available on all items
|
||||||
this.props.children.forEach(function(r) {
|
props.children.forEach(function(r) {
|
||||||
if (r.props.selected === undefined)
|
if (r.props.selected === undefined)
|
||||||
r.props.selected = false;
|
r.props.selected = false;
|
||||||
});
|
});
|
||||||
|
|
@ -326,21 +316,21 @@ var Listing = React.createClass({
|
||||||
|
|
||||||
headerRow = (
|
headerRow = (
|
||||||
<tr>
|
<tr>
|
||||||
<th className="listing-ct-toggle" />
|
<th key="empty" className="listing-ct-toggle" />
|
||||||
{ this.props.columnTitles.map(function (title, index) {
|
{ props.columnTitles.map((title, index) => {
|
||||||
var clickHandler = null;
|
let clickHandler = null;
|
||||||
if (self.props.columnTitleClick)
|
if (props.columnTitleClick)
|
||||||
clickHandler = function() { self.props.columnTitleClick(index) };
|
clickHandler = function() { props.columnTitleClick(index) };
|
||||||
return <th onClick={clickHandler}>{title}</th>;
|
return <th key={index} onClick={clickHandler}>{title}</th>;
|
||||||
}) }
|
}) }
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
headerRow = <tr />
|
headerRow = <tr />;
|
||||||
}
|
}
|
||||||
var caption;
|
let caption;
|
||||||
if (this.props.title || (this.props.actions && this.props.actions.length > 0))
|
if (props.title || (props.actions && props.actions.length > 0))
|
||||||
caption = <caption className="cockpit-caption">{this.props.title}{this.props.actions}</caption>;
|
caption = <caption className="cockpit-caption">{props.title}{props.actions}</caption>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className={ bodyClasses.join(" ") }>
|
<table className={ bodyClasses.join(" ") }>
|
||||||
|
|
@ -348,13 +338,27 @@ var Listing = React.createClass({
|
||||||
<thead className={headerClasses}>
|
<thead className={headerClasses}>
|
||||||
{headerRow}
|
{headerRow}
|
||||||
</thead>
|
</thead>
|
||||||
{this.props.children}
|
{props.children}
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
});
|
|
||||||
|
Listing.defaultProps = {
|
||||||
module.exports = {
|
title: '',
|
||||||
ListingRow: ListingRow,
|
fullWidth: true,
|
||||||
Listing: Listing,
|
columnTitles: [],
|
||||||
|
actions: []
|
||||||
|
};
|
||||||
|
|
||||||
|
Listing.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
fullWidth: PropTypes.bool,
|
||||||
|
emptyCaption: PropTypes.string.isRequired,
|
||||||
|
columnTitles: PropTypes.arrayOf(
|
||||||
|
PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.element,
|
||||||
|
])),
|
||||||
|
columnTitleClick: PropTypes.func,
|
||||||
|
actions: PropTypes.arrayOf(PropTypes.node)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,13 @@
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
import React from 'react';
|
||||||
let cockpit = require("cockpit");
|
let cockpit = require("cockpit");
|
||||||
let _ = cockpit.gettext;
|
let _ = cockpit.gettext;
|
||||||
let React = require("react");
|
|
||||||
let Term = require("term.js-cockpit");
|
let Term = require("term.js-cockpit");
|
||||||
let Journal = require("journal");
|
let Journal = require("journal");
|
||||||
let $ = require("jquery");
|
let $ = require("jquery");
|
||||||
|
|
||||||
require("console.css");
|
require("console.css");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -42,14 +38,14 @@
|
||||||
throw Error("invalid \"" + field + "\" field type: " + typeof (value));
|
throw Error("invalid \"" + field + "\" field type: " + typeof (value));
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
};
|
||||||
|
|
||||||
let scrollToBottom = function(id) {
|
let scrollToBottom = function(id) {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (el) {
|
if (el) {
|
||||||
el.scrollTop = el.scrollHeight;
|
el.scrollTop = el.scrollHeight;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* An auto-loading buffer of recording's packets.
|
* An auto-loading buffer of recording's packets.
|
||||||
|
|
@ -209,7 +205,7 @@
|
||||||
addPacket(pkt) {
|
addPacket(pkt) {
|
||||||
/* TODO Validate the packet */
|
/* TODO Validate the packet */
|
||||||
/* Add the packet */
|
/* Add the packet */
|
||||||
this.pktList.push(pkt)
|
this.pktList.push(pkt);
|
||||||
/* Notify any matching listeners */
|
/* Notify any matching listeners */
|
||||||
while (this.idxDfdList.length > 0) {
|
while (this.idxDfdList.length > 0) {
|
||||||
let idxDfd = this.idxDfdList[0];
|
let idxDfd = this.idxDfdList[0];
|
||||||
|
|
@ -469,7 +465,7 @@
|
||||||
|
|
||||||
jumpTo(e) {
|
jumpTo(e) {
|
||||||
if (this.props.fastForwardFunc) {
|
if (this.props.fastForwardFunc) {
|
||||||
let percent = parseInt((e.offsetX * 100) / e.currentTarget.clientWidth);
|
let percent = parseInt((e.nativeEvent.offsetX * 100) / e.currentTarget.clientWidth);
|
||||||
let ts = parseInt((this.props.length * percent) / 100);
|
let ts = parseInt((this.props.length * percent) / 100);
|
||||||
this.props.fastForwardFunc(ts);
|
this.props.fastForwardFunc(ts);
|
||||||
}
|
}
|
||||||
|
|
@ -489,10 +485,6 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
let InputPlayer = class extends React.Component {
|
let InputPlayer = class extends React.Component {
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div id="input-player" className="panel panel-default">
|
<div id="input-player" className="panel panel-default">
|
||||||
|
|
@ -500,15 +492,14 @@
|
||||||
<span>Input</span>
|
<span>Input</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-body">
|
<div className="panel-body">
|
||||||
<textarea name="input" id="input-textarea" cols="30" rows="10" readonly disabled>{this.props.input}</textarea>
|
<textarea name="input" id="input-textarea" cols="30" rows="10" value={this.props.input} readOnly disabled />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let Player = class extends React.Component {
|
export class Player extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
|
@ -558,7 +549,8 @@
|
||||||
currentTsPost: 0,
|
currentTsPost: 0,
|
||||||
scale: 1,
|
scale: 1,
|
||||||
error: null,
|
error: null,
|
||||||
input: ""
|
input: "",
|
||||||
|
mark: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.containerHeight = 290;
|
this.containerHeight = 290;
|
||||||
|
|
@ -608,6 +600,7 @@
|
||||||
|
|
||||||
/* Move to beginning of recording */
|
/* Move to beginning of recording */
|
||||||
this.recTS = 0;
|
this.recTS = 0;
|
||||||
|
this.setState({currentTsPost: parseInt(this.recTS)});
|
||||||
/* Start the playback time */
|
/* Start the playback time */
|
||||||
this.locTS = performance.now();
|
this.locTS = performance.now();
|
||||||
|
|
||||||
|
|
@ -782,6 +775,7 @@
|
||||||
|
|
||||||
/* Send packet ts to the top */
|
/* Send packet ts to the top */
|
||||||
this.props.onTsChange(this.pkt.pos);
|
this.props.onTsChange(this.pkt.pos);
|
||||||
|
this.setState({currentTsPost: parseInt(this.pkt.pos)});
|
||||||
|
|
||||||
/* Output the packet */
|
/* Output the packet */
|
||||||
if (this.pkt.is_io && !this.pkt.is_output) {
|
if (this.pkt.is_io && !this.pkt.is_output) {
|
||||||
|
|
@ -823,7 +817,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
clearInputPlayer() {
|
clearInputPlayer() {
|
||||||
this.setState({input: null});
|
this.setState({input: ""});
|
||||||
}
|
}
|
||||||
|
|
||||||
rewindToStart() {
|
rewindToStart() {
|
||||||
|
|
@ -978,6 +972,9 @@
|
||||||
if (this.state.input != prevState.input) {
|
if (this.state.input != prevState.input) {
|
||||||
scrollToBottom("input-textarea");
|
scrollToBottom("input-textarea");
|
||||||
}
|
}
|
||||||
|
if (prevProps.logsTs != this.props.logsTs) {
|
||||||
|
this.fastForwardToTS(this.props.logsTs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
@ -995,7 +992,7 @@
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
"transform": "scale(" + this.state.scale + ") translate(" + this.state.term_translate + ")",
|
"transform": "scale(" + this.state.scale + ") translate(" + this.state.term_translate + ")",
|
||||||
"transform-origin": "top left",
|
"transformOrigin": "top left",
|
||||||
"display": "inline-block",
|
"display": "inline-block",
|
||||||
"margin": "0 auto",
|
"margin": "0 auto",
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
|
|
@ -1004,9 +1001,9 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollwrap = {
|
const scrollwrap = {
|
||||||
"min-width": "630px",
|
"minWidth": "630px",
|
||||||
"height": this.containerHeight + "px",
|
"height": this.containerHeight + "px",
|
||||||
"background-color": "#f5f5f5",
|
"backgroundColor": "#f5f5f5",
|
||||||
"overflow": this.state.term_scroll,
|
"overflow": this.state.term_scroll,
|
||||||
"position": "relative",
|
"position": "relative",
|
||||||
};
|
};
|
||||||
|
|
@ -1016,7 +1013,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const progressbar_style = {
|
const progressbar_style = {
|
||||||
'margin-top': '10px',
|
'marginTop': '10px',
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentTsPost = function(currentTS, bufLength) {
|
const currentTsPost = function(currentTS, bufLength) {
|
||||||
|
|
@ -1121,12 +1118,4 @@
|
||||||
window.removeEventListener("keydown", this.handleKeyDown, false);
|
window.removeEventListener("keydown", this.handleKeyDown, false);
|
||||||
this.state.term.destroy();
|
this.state.term.destroy();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
Player.propTypes = {
|
|
||||||
matchList: React.PropTypes.array,
|
|
||||||
// onTitleChanged: React.PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = { Player: Player };
|
|
||||||
}());
|
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,16 @@
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
|
||||||
let $ = require("jquery");
|
let $ = require("jquery");
|
||||||
let cockpit = require("cockpit");
|
let cockpit = require("cockpit");
|
||||||
let _ = cockpit.gettext;
|
let _ = cockpit.gettext;
|
||||||
let moment = require("moment");
|
let moment = require("moment");
|
||||||
let Journal = require("journal");
|
let Journal = require("journal");
|
||||||
let React = require("react");
|
|
||||||
let Listing = require("cockpit-components-listing.jsx");
|
let Listing = require("cockpit-components-listing.jsx");
|
||||||
let Player = require("./player.jsx");
|
let Player = require("./player.jsx");
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
s = '0' + s;
|
s = '0' + s;
|
||||||
}
|
}
|
||||||
return ((i < 0) ? '-' : '') + s;
|
return ((i < 0) ? '-' : '') + s;
|
||||||
}
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Format date and time for a number of milliseconds since Epoch.
|
* Format date and time for a number of milliseconds since Epoch.
|
||||||
|
|
@ -107,7 +107,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A component representing a date & time picker based on bootstrap-datetime-picker.
|
* A component representing a date & time picker based on bootstrap-datetime-picker.
|
||||||
|
|
@ -116,7 +116,7 @@
|
||||||
* - onDateChange: function to call on date change event of datepicker.
|
* - onDateChange: function to call on date change event of datepicker.
|
||||||
* - date: variable to pass which will be used as initial value.
|
* - date: variable to pass which will be used as initial value.
|
||||||
*/
|
*/
|
||||||
let Datetimepicker = class extends React.Component {
|
class Datetimepicker extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleDateChange = this.handleDateChange.bind(this);
|
this.handleDateChange = this.handleDateChange.bind(this);
|
||||||
|
|
@ -203,20 +203,25 @@
|
||||||
* A component representing a username input text field.
|
* A component representing a username input text field.
|
||||||
* TODO make as a select / drop-down with list of exisiting users.
|
* TODO make as a select / drop-down with list of exisiting users.
|
||||||
*/
|
*/
|
||||||
let UserPicker = class extends React.Component {
|
class UserPicker extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleUsernameChange = this.handleUsernameChange.bind(this);
|
this.handleUsernameChange = this.handleUsernameChange.bind(this);
|
||||||
|
this.state = {
|
||||||
|
username: cockpit.location.options.username || "",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUsernameChange(e) {
|
handleUsernameChange(e) {
|
||||||
this.props.onUsernameChange(e.target.value);
|
const value = e.target.value;
|
||||||
|
this.setState({username: value});
|
||||||
|
this.props.onUsernameChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<input type="text" className="form-control" value={this.props.username}
|
<input type="text" className="form-control" value={this.state.username}
|
||||||
onChange={this.handleUsernameChange} />
|
onChange={this.handleUsernameChange} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -227,38 +232,60 @@
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleHostnameChange = this.handleHostnameChange.bind(this);
|
this.handleHostnameChange = this.handleHostnameChange.bind(this);
|
||||||
|
this.state = {
|
||||||
|
hostname: cockpit.location.options.hostname || "",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHostnameChange(e) {
|
handleHostnameChange(e) {
|
||||||
this.props.onHostnameChange(e.target.value);
|
const value = e.target.value;
|
||||||
|
this.setState({hostname: value});
|
||||||
|
this.props.onHostnameChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<input type="text" className="form-control" value={this.props.hostname}
|
<input type="text" className="form-control" value={this.state.hostname}
|
||||||
onChange={this.handleHostnameChange} />
|
onChange={this.handleHostnameChange} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function LogElement(props) {
|
function LogElement(props) {
|
||||||
const entry = props.entry;
|
const entry = props.entry;
|
||||||
const start = props.start;
|
const start = props.start;
|
||||||
const end = props.end;
|
const end = props.end;
|
||||||
const entry_timestamp = entry.__REALTIME_TIMESTAMP / 1000;
|
const cursor = entry.__CURSOR;
|
||||||
|
const entry_timestamp = parseInt(entry.__REALTIME_TIMESTAMP / 1000);
|
||||||
|
|
||||||
|
const timeClick = function(e) {
|
||||||
|
const ts = entry_timestamp - start;
|
||||||
|
if (ts > 0) {
|
||||||
|
props.jumpToTs(ts);
|
||||||
|
} else {
|
||||||
|
props.jumpToTs(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const messageClick = () => {
|
||||||
|
const url = '/system/logs#/' + cursor + '?parent_options={}';
|
||||||
|
const win = window.open(url, '_blank');
|
||||||
|
win.focus();
|
||||||
|
};
|
||||||
|
|
||||||
let className = 'cockpit-logline';
|
let className = 'cockpit-logline';
|
||||||
if (start < entry_timestamp && end > entry_timestamp) {
|
if (start < entry_timestamp && end > entry_timestamp) {
|
||||||
className = 'cockpit-logline highlighted';
|
className = 'cockpit-logline highlighted';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} data-cursor={entry.__CURSOR}>
|
<div className={className} data-cursor={cursor} key={cursor}>
|
||||||
<div className="cockpit-log-warning">
|
<div className="cockpit-log-warning">
|
||||||
<i className="fa fa-exclamation-triangle" />
|
<i className="fa fa-exclamation-triangle" />
|
||||||
</div>
|
</div>
|
||||||
<div className="logs-view-log-time">{formatDateTime(parseInt(entry.__REALTIME_TIMESTAMP / 1000))}</div>
|
<div className="logs-view-log-time" onClick={timeClick}>{formatDateTime(entry_timestamp)}</div>
|
||||||
<span className="cockpit-log-message">{entry.MESSAGE}</span>
|
<span className="cockpit-log-message" onClick={messageClick}>{entry.MESSAGE}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -268,7 +295,7 @@
|
||||||
const start = props.start;
|
const start = props.start;
|
||||||
const end = props.end;
|
const end = props.end;
|
||||||
const rows = entries.map((entry) =>
|
const rows = entries.map((entry) =>
|
||||||
<LogElement entry={entry} start={start} end={end} />
|
<LogElement key={entry.__CURSOR} entry={entry} start={start} end={end} jumpToTs={props.jumpToTs} />
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="panel panel-default cockpit-log-panel" id="logs-view">
|
<div className="panel panel-default cockpit-log-panel" id="logs-view">
|
||||||
|
|
@ -277,7 +304,7 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Logs = class extends React.Component {
|
class Logs extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.journalctlError = this.journalctlError.bind(this);
|
this.journalctlError = this.journalctlError.bind(this);
|
||||||
|
|
@ -409,7 +436,7 @@
|
||||||
<button className="btn btn-default" style={{"float":"right"}} onClick={this.loadEarlier}>Load earlier entries</button>
|
<button className="btn btn-default" style={{"float":"right"}} onClick={this.loadEarlier}>Load earlier entries</button>
|
||||||
</div>
|
</div>
|
||||||
<LogsView entries={this.state.entries} start={this.props.recording.start}
|
<LogsView entries={this.state.entries} start={this.props.recording.start}
|
||||||
end={this.props.recording.end} />
|
end={this.props.recording.end} jumpToTs={this.props.jumpToTs} />
|
||||||
<div className="panel-heading">
|
<div className="panel-heading">
|
||||||
<button className="btn btn-default" onClick={this.loadLater}>Load later entries</button>
|
<button className="btn btn-default" onClick={this.loadLater}>Load later entries</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -427,7 +454,7 @@
|
||||||
* - recording: either null for no recording data available yet, or a
|
* - recording: either null for no recording data available yet, or a
|
||||||
* recording object, as created by the View below.
|
* recording object, as created by the View below.
|
||||||
*/
|
*/
|
||||||
let Recording = class extends React.Component {
|
class Recording extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.goBackToList = this.goBackToList.bind(this);
|
this.goBackToList = this.goBackToList.bind(this);
|
||||||
|
|
@ -482,6 +509,7 @@
|
||||||
(<Player.Player
|
(<Player.Player
|
||||||
ref="player"
|
ref="player"
|
||||||
matchList={this.props.recording.matchList}
|
matchList={this.props.recording.matchList}
|
||||||
|
logsTs={this.props.logsTs}
|
||||||
onTsChange={this.props.onTsChange} />);
|
onTsChange={this.props.onTsChange} />);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -502,6 +530,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-body">
|
<div className="panel-body">
|
||||||
<table className="form-table-ct">
|
<table className="form-table-ct">
|
||||||
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_("ID")}</td>
|
<td>{_("ID")}</td>
|
||||||
<td>{r.id}</td>
|
<td>{r.id}</td>
|
||||||
|
|
@ -535,6 +564,7 @@
|
||||||
<td>{_("User")}</td>
|
<td>{_("User")}</td>
|
||||||
<td>{r.user}</td>
|
<td>{r.user}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -545,14 +575,14 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A component representing a list of recordings.
|
* A component representing a list of recordings.
|
||||||
* Properties:
|
* Properties:
|
||||||
* - list: an array with recording objects, as created by the View below
|
* - list: an array with recording objects, as created by the View below
|
||||||
*/
|
*/
|
||||||
let RecordingList = class extends React.Component {
|
class RecordingList extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleColumnClick = this.handleColumnClick.bind(this);
|
this.handleColumnClick = this.handleColumnClick.bind(this);
|
||||||
|
|
@ -618,17 +648,17 @@
|
||||||
getColumnTitles() {
|
getColumnTitles() {
|
||||||
let columnTitles = [
|
let columnTitles = [
|
||||||
(<div id="user" className="sort" onClick={this.handleColumnClick}><span>{_("User")}</span> <div
|
(<div id="user" className="sort" onClick={this.handleColumnClick}><span>{_("User")}</span> <div
|
||||||
ref="user" className="sort-icon"></div></div>),
|
ref="user" className="sort-icon" /></div>),
|
||||||
(<div id="start" className="sort" onClick={this.handleColumnClick}><span>{_("Start")}</span> <div
|
(<div id="start" className="sort" onClick={this.handleColumnClick}><span>{_("Start")}</span> <div
|
||||||
ref="start" className="sort-icon"></div></div>),
|
ref="start" className="sort-icon" /></div>),
|
||||||
(<div id="end" className="sort" onClick={this.handleColumnClick}><span>{_("End")}</span> <div
|
(<div id="end" className="sort" onClick={this.handleColumnClick}><span>{_("End")}</span> <div
|
||||||
ref="end" className="sort-icon"></div></div>),
|
ref="end" className="sort-icon" /></div>),
|
||||||
(<div id="duration" className="sort" onClick={this.handleColumnClick}><span>{_("Duration")}</span> <div
|
(<div id="duration" className="sort" onClick={this.handleColumnClick}><span>{_("Duration")}</span> <div
|
||||||
ref="duration" className="sort-icon"></div></div>),
|
ref="duration" className="sort-icon" /></div>),
|
||||||
];
|
];
|
||||||
if (this.props.diff_hosts === true) {
|
if (this.props.diff_hosts === true) {
|
||||||
columnTitles.push((<div id="hostname" className="sort" onClick={this.handleColumnClick}>
|
columnTitles.push((<div id="hostname" className="sort" onClick={this.handleColumnClick}>
|
||||||
<span>{_("Hostname")}</span> <div ref="hostname" className="sort-icon"></div></div>));
|
<span>{_("Hostname")}</span> <div ref="hostname" className="sort-icon" /></div>));
|
||||||
}
|
}
|
||||||
return columnTitles;
|
return columnTitles;
|
||||||
}
|
}
|
||||||
|
|
@ -637,7 +667,7 @@
|
||||||
let columns = [r.user,
|
let columns = [r.user,
|
||||||
formatDateTime(r.start),
|
formatDateTime(r.start),
|
||||||
formatDateTime(r.end),
|
formatDateTime(r.end),
|
||||||
formatDuration(r.end - r.start)]
|
formatDuration(r.end - r.start)];
|
||||||
if (this.props.diff_hosts === true) {
|
if (this.props.diff_hosts === true) {
|
||||||
columns.push(r.hostname);
|
columns.push(r.hostname);
|
||||||
}
|
}
|
||||||
|
|
@ -653,6 +683,7 @@
|
||||||
let r = list[i];
|
let r = list[i];
|
||||||
let columns = this.getColumns(r);
|
let columns = this.getColumns(r);
|
||||||
rows.push(<Listing.ListingRow
|
rows.push(<Listing.ListingRow
|
||||||
|
key={r.id}
|
||||||
rowId={r.id}
|
rowId={r.id}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
navigateToItem={this.navigateToRecording.bind(this, r)} />);
|
navigateToItem={this.navigateToRecording.bind(this, r)} />);
|
||||||
|
|
@ -661,7 +692,8 @@
|
||||||
<div>
|
<div>
|
||||||
<div className="content-header-extra">
|
<div className="content-header-extra">
|
||||||
<table className="form-table-ct">
|
<table className="form-table-ct">
|
||||||
<th>
|
<thead>
|
||||||
|
<tr>
|
||||||
<td className="top">
|
<td className="top">
|
||||||
<label className="control-label" htmlFor="dateSince">Since</label>
|
<label className="control-label" htmlFor="dateSince">Since</label>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -680,8 +712,7 @@
|
||||||
<label className="control-label" htmlFor="username">Username</label>
|
<label className="control-label" htmlFor="username">Username</label>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<UserPicker onUsernameChange={this.props.onUsernameChange}
|
<UserPicker onUsernameChange={this.props.onUsernameChange} />
|
||||||
username={this.props.username} />
|
|
||||||
</td>
|
</td>
|
||||||
<td className="top">
|
<td className="top">
|
||||||
<label className="control-label" htmlFor="hostname">Hostname</label>
|
<label className="control-label" htmlFor="hostname">Hostname</label>
|
||||||
|
|
@ -697,7 +728,8 @@
|
||||||
<a href="/cockpit/@localhost/session-recording/config.html" className="btn btn-default" data-toggle="modal">
|
<a href="/cockpit/@localhost/session-recording/config.html" className="btn btn-default" data-toggle="modal">
|
||||||
<i className="fa fa-cog" aria-hidden="true" /></a>
|
<i className="fa fa-cog" aria-hidden="true" /></a>
|
||||||
</td>
|
</td>
|
||||||
</th>
|
</tr>
|
||||||
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<Listing.Listing title={_("Sessions")}
|
<Listing.Listing title={_("Sessions")}
|
||||||
|
|
@ -709,14 +741,14 @@
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A component representing the view upon a list of recordings, or a
|
* A component representing the view upon a list of recordings, or a
|
||||||
* single recording. Extracts the ID of the recording to display from
|
* single recording. Extracts the ID of the recording to display from
|
||||||
* cockpit.location.path[0]. If it's zero, displays the list.
|
* cockpit.location.path[0]. If it's zero, displays the list.
|
||||||
*/
|
*/
|
||||||
let View = class extends React.Component {
|
class View extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.onLocationChanged = this.onLocationChanged.bind(this);
|
this.onLocationChanged = this.onLocationChanged.bind(this);
|
||||||
|
|
@ -726,6 +758,7 @@
|
||||||
this.handleUsernameChange = this.handleUsernameChange.bind(this);
|
this.handleUsernameChange = this.handleUsernameChange.bind(this);
|
||||||
this.handleHostnameChange = this.handleHostnameChange.bind(this);
|
this.handleHostnameChange = this.handleHostnameChange.bind(this);
|
||||||
this.handleTsChange = this.handleTsChange.bind(this);
|
this.handleTsChange = this.handleTsChange.bind(this);
|
||||||
|
this.handleLogTsChange = this.handleLogTsChange.bind(this);
|
||||||
/* Journalctl instance */
|
/* Journalctl instance */
|
||||||
this.journalctl = null;
|
this.journalctl = null;
|
||||||
/* Recording ID journalctl instance is invoked with */
|
/* Recording ID journalctl instance is invoked with */
|
||||||
|
|
@ -744,12 +777,13 @@
|
||||||
dateUntil: cockpit.location.options.dateUntil || null,
|
dateUntil: cockpit.location.options.dateUntil || null,
|
||||||
dateUntilLastValid: null,
|
dateUntilLastValid: null,
|
||||||
/* value to filter recordings by username */
|
/* value to filter recordings by username */
|
||||||
username: cockpit.location.options.username || null,
|
username: cockpit.location.options.username || "",
|
||||||
hostname: cockpit.location.options.hostname || null,
|
hostname: cockpit.location.options.hostname || "",
|
||||||
error_tlog_uid: false,
|
error_tlog_uid: false,
|
||||||
diff_hosts: false,
|
diff_hosts: false,
|
||||||
curTs: null,
|
curTs: null,
|
||||||
}
|
logsTs: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -949,6 +983,10 @@
|
||||||
this.setState({curTs: ts});
|
this.setState({curTs: ts});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleLogTsChange(ts) {
|
||||||
|
this.setState({logsTs: ts});
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
let proc = cockpit.spawn(["getent", "passwd", "tlog"]);
|
let proc = cockpit.spawn(["getent", "passwd", "tlog"]);
|
||||||
|
|
||||||
|
|
@ -1026,11 +1064,12 @@
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Recording recording={this.recordingMap[this.state.recordingID]} onTsChange={this.handleTsChange} />
|
<Recording recording={this.recordingMap[this.state.recordingID]} onTsChange={this.handleTsChange} logsTs={this.state.logsTs} />
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
<Logs recording={this.recordingMap[this.state.recordingID]} curTs={this.state.curTs} />
|
<Logs recording={this.recordingMap[this.state.recordingID]} curTs={this.state.curTs}
|
||||||
|
jumpToTs={this.handleLogTsChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1038,7 +1077,6 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
React.render(<View />, document.getElementById('view'));
|
ReactDOM.render(<View />, document.getElementById('view'));
|
||||||
}());
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@
|
||||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
import Player from "./player";
|
||||||
|
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var React = require("react");
|
var React = require("react");
|
||||||
|
|
@ -45,7 +47,7 @@
|
||||||
*/
|
*/
|
||||||
var Terminal = React.createClass({
|
var Terminal = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
cols: React.PropTypes.number,
|
// cols: React.PropTypes.number,
|
||||||
rows: React.PropTypes.number,
|
rows: React.PropTypes.number,
|
||||||
channel: React.PropTypes.object.isRequired,
|
channel: React.PropTypes.object.isRequired,
|
||||||
onTitleChanged: React.PropTypes.func
|
onTitleChanged: React.PropTypes.func
|
||||||
|
|
@ -118,7 +120,7 @@
|
||||||
let style = {
|
let style = {
|
||||||
'min-width': '300px',
|
'min-width': '300px',
|
||||||
'min-height': '100px',
|
'min-height': '100px',
|
||||||
}
|
};
|
||||||
// ensure react never reuses this div by keying it with the terminal widget
|
// ensure react never reuses this div by keying it with the terminal widget
|
||||||
return <div ref="terminal" style={style} className="console-ct" key={this.state.terminal} />;
|
return <div ref="terminal" style={style} className="console-ct" key={this.state.terminal} />;
|
||||||
},
|
},
|
||||||
|
|
@ -187,5 +189,5 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { Terminal: Terminal };
|
// module.exports = { Terminal: Terminal };
|
||||||
}());
|
export class Terminal;
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ var info = {
|
||||||
"config": [
|
"config": [
|
||||||
"./config.jsx",
|
"./config.jsx",
|
||||||
"./recordings.css",
|
"./recordings.css",
|
||||||
|
"./table.css",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
files: [
|
files: [
|
||||||
|
|
@ -38,6 +39,7 @@ var info = {
|
||||||
"player.jsx",
|
"player.jsx",
|
||||||
"recordings.jsx",
|
"recordings.jsx",
|
||||||
"recordings.css",
|
"recordings.css",
|
||||||
|
"table.css",
|
||||||
"terminal.jsx",
|
"terminal.jsx",
|
||||||
"manifest.json",
|
"manifest.json",
|
||||||
"timer.css",
|
"timer.css",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue