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",
|
||||
"sizzle": "^2.3.3",
|
||||
"stdio": "^0.2.7",
|
||||
"webpack": "^4.17.1",
|
||||
"webpack": "^4.19.0",
|
||||
"webpack-cli": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.0.0",
|
||||
"node-sass": "^4.9.0",
|
||||
"react": "^16.4.2",
|
||||
"react-dom": "^16.4.2"
|
||||
"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",
|
||||
"comment-json": "^1.1.3",
|
||||
"term.js-cockpit": "0.0.10",
|
||||
"fs.extra": "^1.3.2",
|
||||
"fs.realpath": "^1.0.0",
|
||||
"ini": "^1.3.5",
|
||||
"jquery": "3.3.1",
|
||||
"moment": "2.22.2",
|
||||
"mustache": "2.3.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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
137
src/config.jsx
137
src/config.jsx
|
|
@ -22,10 +22,11 @@
|
|||
|
||||
let cockpit = require("cockpit");
|
||||
let React = require("react");
|
||||
let ReactDOM = require("react-dom");
|
||||
let json = require('comment-json');
|
||||
let ini = require('ini');
|
||||
|
||||
let Config = class extends React.Component {
|
||||
class Config extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
|
|
@ -38,7 +39,7 @@
|
|||
config: null,
|
||||
file_error: null,
|
||||
submitting: "none",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleInputChange(e) {
|
||||
|
|
@ -59,10 +60,8 @@
|
|||
|
||||
handleSubmit(event) {
|
||||
this.setState({submitting:"block"});
|
||||
console.log(event);
|
||||
this.prepareConfig();
|
||||
this.file.replace(this.state.config).done(() => {
|
||||
console.log('updated');
|
||||
this.setState({submitting:"none"});
|
||||
})
|
||||
.fail((error) => {
|
||||
|
|
@ -72,20 +71,16 @@
|
|||
}
|
||||
|
||||
setConfig(data) {
|
||||
console.log(data);
|
||||
this.setState({config: data});
|
||||
}
|
||||
|
||||
fileReadFailed(reason) {
|
||||
console.log(reason);
|
||||
this.setState({file_error: reason});
|
||||
console.log('failed to read file');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let parseFunc = function(data) {
|
||||
console.log(data);
|
||||
// return data;
|
||||
return json.parse(data, null, true);
|
||||
};
|
||||
|
||||
|
|
@ -106,8 +101,6 @@
|
|||
// host: string
|
||||
});
|
||||
|
||||
console.log(this.file);
|
||||
|
||||
let promise = this.file.read();
|
||||
|
||||
promise.done((data) => {
|
||||
|
|
@ -253,10 +246,9 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td className="top" />
|
||||
<div className="spinner spinner-sm" style={{display: this.state.submitting}} />
|
||||
<td>
|
||||
<button className="btn btn-default" type="submit">Save</button>
|
||||
|
||||
<div className="spinner spinner-sm" style={{display: this.state.submitting}} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
@ -274,9 +266,9 @@
|
|||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let SssdConfig = class extends React.Component {
|
||||
class SssdConfig extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
|
|
@ -284,27 +276,24 @@
|
|||
this.setConfig = this.setConfig.bind(this);
|
||||
this.file = null;
|
||||
this.state = {
|
||||
config: {
|
||||
session_recording: {
|
||||
scope: null,
|
||||
users: null,
|
||||
groups: null,
|
||||
},
|
||||
},
|
||||
scope: "",
|
||||
users: "",
|
||||
groups: "",
|
||||
submitting: "none",
|
||||
};
|
||||
}
|
||||
|
||||
handleInputChange(e){
|
||||
handleInputChange(e) {
|
||||
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
|
||||
const name = e.target.name;
|
||||
const config = this.state.config;
|
||||
config.session_recording[name] = value;
|
||||
|
||||
this.forceUpdate();
|
||||
const state = {};
|
||||
state[name] = value;
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
setConfig(data) {
|
||||
this.setState({config: data});
|
||||
const config = {...data['session_recording']};
|
||||
this.setState(config);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -327,14 +316,21 @@
|
|||
});
|
||||
}
|
||||
|
||||
handleSubmit() {
|
||||
this.file.replace(this.state.config).done( function() {
|
||||
console.log('updated');
|
||||
handleSubmit(e) {
|
||||
this.setState({submitting:"block"});
|
||||
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) {
|
||||
console.log(error);
|
||||
});
|
||||
event.preventDefault();
|
||||
.fail(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -342,49 +338,48 @@
|
|||
<form onSubmit={this.handleSubmit}>
|
||||
<table className="info-table-ct col-md-12">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label htmlFor="scope">Scope</label></td>
|
||||
<td>
|
||||
<select name="scope" id="scope" className="form-control"
|
||||
value={this.state.config.session_recording.scope}
|
||||
<tr>
|
||||
<td><label htmlFor="scope">Scope</label></td>
|
||||
<td>
|
||||
<select name="scope" id="scope" className="form-control"
|
||||
value={this.state.scope}
|
||||
onChange={this.handleInputChange} >
|
||||
<option value="none">None</option>
|
||||
<option value="some">Some</option>
|
||||
<option value="all">All</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label htmlFor="users">Users</label></td>
|
||||
<td>
|
||||
<input type="text" id="users" name="users"
|
||||
value={this.state.config.session_recording.users}
|
||||
className="form-control" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label htmlFor="groups">Groups</label></td>
|
||||
<td>
|
||||
<input type="text" id="groups" name="groups"
|
||||
value={this.state.config.session_recording.groups}
|
||||
<option value="none">None</option>
|
||||
<option value="some">Some</option>
|
||||
<option value="all">All</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label htmlFor="users">Users</label></td>
|
||||
<td>
|
||||
<input type="text" id="users" name="users"
|
||||
value={this.state.users}
|
||||
className="form-control" onChange={this.handleInputChange} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<button className="btn btn-default" type="submit">Save</button>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label htmlFor="groups">Groups</label></td>
|
||||
<td>
|
||||
<input type="text" id="groups" name="groups"
|
||||
value={this.state.groups}
|
||||
className="form-control" onChange={this.handleInputChange} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td />
|
||||
<td>
|
||||
<button className="btn btn-default" type="submit">Save</button>
|
||||
<span className="spinner spinner-sm" style={{display: this.state.submitting}} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
React.render(<Config />, document.getElementById('sr_config'));
|
||||
React.render(<SssdConfig />, document.getElementById('sssd_config'));
|
||||
ReactDOM.render(<Config />, document.getElementById('sr_config'));
|
||||
ReactDOM.render(<SssdConfig />, document.getElementById('sssd_config'));
|
||||
}());
|
||||
|
|
|
|||
|
|
@ -17,11 +17,9 @@
|
|||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var React = require('react');
|
||||
|
||||
require('./listing.less');
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import './listing.less';
|
||||
|
||||
/* 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>
|
||||
|
|
@ -47,55 +45,43 @@ require('./listing.less');
|
|||
* 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
|
||||
*/
|
||||
var ListingRow = React.createClass({
|
||||
propTypes: {
|
||||
rowId: React.PropTypes.string,
|
||||
columns: React.PropTypes.array.isRequired,
|
||||
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 {
|
||||
export class ListingRow extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
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
|
||||
loadedTabs: {}, // which tabs were already loaded - this is important for 'loadOnDemand' setting
|
||||
// contains tab indices
|
||||
selected: this.props.selected, // whether the current row is selected
|
||||
};
|
||||
},
|
||||
handleNavigateClick: function(e) {
|
||||
this.handleNavigateClick = this.handleNavigateClick.bind(this);
|
||||
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
|
||||
if (!e || e.button !== 0)
|
||||
return;
|
||||
this.props.navigateToItem();
|
||||
},
|
||||
handleExpandClick: function(e) {
|
||||
}
|
||||
|
||||
handleExpandClick(e) {
|
||||
// only consider primary mouse button
|
||||
if (!e || e.button !== 0)
|
||||
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 });
|
||||
|
||||
var loadedTabs = {};
|
||||
let loadedTabs = {};
|
||||
// unload all tabs if not expanded
|
||||
if (willBeExpanded) {
|
||||
// see if we should preload some tabs
|
||||
var tabIdx;
|
||||
var tabPresence;
|
||||
let tabIdx;
|
||||
let tabPresence;
|
||||
for (tabIdx = 0; tabIdx < this.props.tabRenderers.length; tabIdx++) {
|
||||
if ('presence' in this.props.tabRenderers[tabIdx])
|
||||
tabPresence = this.props.tabRenderers[tabIdx].presence;
|
||||
|
|
@ -115,13 +101,14 @@ var ListingRow = React.createClass({
|
|||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
},
|
||||
handleSelectClick: function(e) {
|
||||
}
|
||||
|
||||
handleSelectClick(e) {
|
||||
// only consider primary mouse button
|
||||
if (!e || e.button !== 0)
|
||||
return;
|
||||
|
||||
var selected = !this.state.selected;
|
||||
let selected = !this.state.selected;
|
||||
this.setState({ selected: selected });
|
||||
|
||||
if (this.props.selectChanged)
|
||||
|
|
@ -129,14 +116,15 @@ var ListingRow = React.createClass({
|
|||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
},
|
||||
handleTabClick: function(tabIdx, e) {
|
||||
}
|
||||
|
||||
handleTabClick(tabIdx, e) {
|
||||
// only consider primary mouse button
|
||||
if (!e || e.button !== 0)
|
||||
return;
|
||||
var prevTab = this.state.activeTab;
|
||||
var prevTabPresence = 'default';
|
||||
var loadedTabs = this.state.loadedTabs;
|
||||
let prevTab = this.state.activeTab;
|
||||
let prevTabPresence = 'default';
|
||||
let loadedTabs = this.state.loadedTabs;
|
||||
if (prevTab !== tabIdx) {
|
||||
// see if we need to unload the previous tab
|
||||
if ('presence' in this.props.tabRenderers[prevTab])
|
||||
|
|
@ -151,41 +139,42 @@ var ListingRow = React.createClass({
|
|||
}
|
||||
e.stopPropagation();
|
||||
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))
|
||||
return (<td>{itm}</td>);
|
||||
return (<td key={index}>{itm}</td>);
|
||||
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)
|
||||
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
|
||||
return (<td>{itm.name}</td>);
|
||||
return (<td key={index}>{itm.name}</td>);
|
||||
});
|
||||
|
||||
var allowExpand = (this.props.tabRenderers.length > 0);
|
||||
var expandToggle;
|
||||
let allowExpand = (this.props.tabRenderers.length > 0);
|
||||
let expandToggle;
|
||||
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" />
|
||||
</td>;
|
||||
} 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)
|
||||
listingItemClasses.push("listing-ct-nonavigate");
|
||||
if (!allowExpand)
|
||||
listingItemClasses.push("listing-ct-noexpand");
|
||||
|
||||
var allowSelect = !(allowNavigate || allowExpand) && (this.state.selected !== undefined);
|
||||
var clickHandler;
|
||||
let allowSelect = !(allowNavigate || allowExpand) && (this.state.selected !== undefined);
|
||||
let clickHandler;
|
||||
if (allowSelect) {
|
||||
clickHandler = this.handleSelectClick;
|
||||
if (this.state.selected)
|
||||
|
|
@ -197,7 +186,7 @@ var ListingRow = React.createClass({
|
|||
clickHandler = this.handleExpandClick;
|
||||
}
|
||||
|
||||
var listingItem = (
|
||||
let listingItem = (
|
||||
<tr data-row-id={ this.props.rowId }
|
||||
className={ listingItemClasses.join(' ') }
|
||||
onClick={clickHandler}>
|
||||
|
|
@ -207,18 +196,18 @@ var ListingRow = React.createClass({
|
|||
);
|
||||
|
||||
if (this.state.expanded) {
|
||||
var links = this.props.tabRenderers.map(function(itm, idx) {
|
||||
let links = this.props.tabRenderers.map((itm, idx) => {
|
||||
return (
|
||||
<li key={idx} className={ (idx === self.state.activeTab) ? "active" : ""} >
|
||||
<a href="#" tabIndex="0" onClick={ self.handleTabClick.bind(self, idx) }>{itm.name}</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
var tabs = [];
|
||||
var tabIdx;
|
||||
var Renderer;
|
||||
var rendererData;
|
||||
var row;
|
||||
let tabs = [];
|
||||
let tabIdx;
|
||||
let Renderer;
|
||||
let rendererData;
|
||||
let row;
|
||||
for (tabIdx = 0; tabIdx < this.props.tabRenderers.length; tabIdx++) {
|
||||
Renderer = this.props.tabRenderers[tabIdx].renderer;
|
||||
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>);
|
||||
}
|
||||
|
||||
var listingDetail;
|
||||
let listingDetail;
|
||||
if ('listingDetail' in this.props) {
|
||||
listingDetail = (
|
||||
<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
|
||||
* https://www.patternfly.org/list-view/
|
||||
* Properties:
|
||||
|
|
@ -281,80 +288,77 @@ var ListingRow = React.createClass({
|
|||
* receives the column index as argument
|
||||
* - actions: additional listing-wide actions (displayed next to the list's title)
|
||||
*/
|
||||
var Listing = React.createClass({
|
||||
propTypes: {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
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");
|
||||
var headerClasses;
|
||||
var headerRow;
|
||||
var selectableRows;
|
||||
if (!this.props.children || this.props.children.length === 0) {
|
||||
headerClasses = "listing-ct-empty";
|
||||
headerRow = <tr><td>{this.props.emptyCaption}</td></tr>;
|
||||
} else if (this.props.columnTitles.length) {
|
||||
// check if any of the children are selectable
|
||||
selectableRows = false;
|
||||
this.props.children.forEach(function(r) {
|
||||
if (r.props.selected !== undefined)
|
||||
selectableRows = true;
|
||||
export const Listing = (props) => {
|
||||
let bodyClasses = ["listing", "listing-ct"];
|
||||
if (props.fullWidth)
|
||||
bodyClasses.push("listing-ct-wide");
|
||||
let headerClasses;
|
||||
let headerRow;
|
||||
let selectableRows;
|
||||
if (!props.children || props.children.length === 0) {
|
||||
headerClasses = "listing-ct-empty";
|
||||
headerRow = <tr><td>{props.emptyCaption}</td></tr>;
|
||||
} else if (props.columnTitles.length) {
|
||||
// check if any of the children are selectable
|
||||
selectableRows = false;
|
||||
props.children.forEach(function(r) {
|
||||
if (r.props.selected !== undefined)
|
||||
selectableRows = true;
|
||||
});
|
||||
|
||||
if (selectableRows) {
|
||||
// now make sure that if one is set, it's available on all items
|
||||
props.children.forEach(function(r) {
|
||||
if (r.props.selected === undefined)
|
||||
r.props.selected = false;
|
||||
});
|
||||
|
||||
if (selectableRows) {
|
||||
// now make sure that if one is set, it's available on all items
|
||||
this.props.children.forEach(function(r) {
|
||||
if (r.props.selected === undefined)
|
||||
r.props.selected = false;
|
||||
});
|
||||
}
|
||||
|
||||
headerRow = (
|
||||
<tr>
|
||||
<th className="listing-ct-toggle" />
|
||||
{ this.props.columnTitles.map(function (title, index) {
|
||||
var clickHandler = null;
|
||||
if (self.props.columnTitleClick)
|
||||
clickHandler = function() { self.props.columnTitleClick(index) };
|
||||
return <th onClick={clickHandler}>{title}</th>;
|
||||
}) }
|
||||
</tr>
|
||||
);
|
||||
} else {
|
||||
headerRow = <tr />
|
||||
}
|
||||
var caption;
|
||||
if (this.props.title || (this.props.actions && this.props.actions.length > 0))
|
||||
caption = <caption className="cockpit-caption">{this.props.title}{this.props.actions}</caption>;
|
||||
|
||||
return (
|
||||
<table className={ bodyClasses.join(" ") }>
|
||||
{caption}
|
||||
<thead className={headerClasses}>
|
||||
{headerRow}
|
||||
</thead>
|
||||
{this.props.children}
|
||||
</table>
|
||||
headerRow = (
|
||||
<tr>
|
||||
<th key="empty" className="listing-ct-toggle" />
|
||||
{ props.columnTitles.map((title, index) => {
|
||||
let clickHandler = null;
|
||||
if (props.columnTitleClick)
|
||||
clickHandler = function() { props.columnTitleClick(index) };
|
||||
return <th key={index} onClick={clickHandler}>{title}</th>;
|
||||
}) }
|
||||
</tr>
|
||||
);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
headerRow = <tr />;
|
||||
}
|
||||
let caption;
|
||||
if (props.title || (props.actions && props.actions.length > 0))
|
||||
caption = <caption className="cockpit-caption">{props.title}{props.actions}</caption>;
|
||||
|
||||
module.exports = {
|
||||
ListingRow: ListingRow,
|
||||
Listing: Listing,
|
||||
return (
|
||||
<table className={ bodyClasses.join(" ") }>
|
||||
{caption}
|
||||
<thead className={headerClasses}>
|
||||
{headerRow}
|
||||
</thead>
|
||||
{props.children}
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
Listing.defaultProps = {
|
||||
title: '',
|
||||
fullWidth: true,
|
||||
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)
|
||||
};
|
||||
|
|
|
|||
2051
src/player.jsx
2051
src/player.jsx
File diff suppressed because it is too large
Load diff
1696
src/recordings.jsx
1696
src/recordings.jsx
File diff suppressed because it is too large
Load diff
330
src/terminal.jsx
330
src/terminal.jsx
|
|
@ -17,175 +17,177 @@
|
|||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
import Player from "./player";
|
||||
|
||||
var React = require("react");
|
||||
var Term = require("term");
|
||||
let $ = require("jquery");
|
||||
|
||||
require("console.css");
|
||||
require("jquery-resizable");
|
||||
require("jquery-resizable/resizable.css");
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* A terminal component that communicates over a cockpit channel.
|
||||
*
|
||||
* The only required property is 'channel', which must point to a cockpit
|
||||
* stream channel.
|
||||
*
|
||||
* The size of the terminal can be set with the 'rows' and 'cols'
|
||||
* properties. If those properties are not given, the terminal will fill
|
||||
* its container.
|
||||
*
|
||||
* If the 'onTitleChanged' callback property is set, it will be called whenever
|
||||
* the title of the terminal changes.
|
||||
*
|
||||
* Call focus() to set the input focus on the terminal.
|
||||
*/
|
||||
var Terminal = React.createClass({
|
||||
propTypes: {
|
||||
cols: React.PropTypes.number,
|
||||
rows: React.PropTypes.number,
|
||||
channel: React.PropTypes.object.isRequired,
|
||||
onTitleChanged: React.PropTypes.func
|
||||
},
|
||||
var React = require("react");
|
||||
var Term = require("term");
|
||||
let $ = require("jquery");
|
||||
|
||||
componentWillMount: function () {
|
||||
var term = new Term({
|
||||
cols: this.state.cols || 80,
|
||||
rows: this.state.rows || 25,
|
||||
screenKeys: true,
|
||||
useStyle: true
|
||||
require("console.css");
|
||||
require("jquery-resizable");
|
||||
require("jquery-resizable/resizable.css");
|
||||
|
||||
/*
|
||||
* A terminal component that communicates over a cockpit channel.
|
||||
*
|
||||
* The only required property is 'channel', which must point to a cockpit
|
||||
* stream channel.
|
||||
*
|
||||
* The size of the terminal can be set with the 'rows' and 'cols'
|
||||
* properties. If those properties are not given, the terminal will fill
|
||||
* its container.
|
||||
*
|
||||
* If the 'onTitleChanged' callback property is set, it will be called whenever
|
||||
* the title of the terminal changes.
|
||||
*
|
||||
* Call focus() to set the input focus on the terminal.
|
||||
*/
|
||||
var Terminal = React.createClass({
|
||||
propTypes: {
|
||||
// cols: React.PropTypes.number,
|
||||
rows: React.PropTypes.number,
|
||||
channel: React.PropTypes.object.isRequired,
|
||||
onTitleChanged: React.PropTypes.func
|
||||
},
|
||||
|
||||
componentWillMount: function () {
|
||||
var term = new Term({
|
||||
cols: this.state.cols || 80,
|
||||
rows: this.state.rows || 25,
|
||||
screenKeys: true,
|
||||
useStyle: true
|
||||
});
|
||||
|
||||
term.on('data', function(data) {
|
||||
if (this.props.channel.valid)
|
||||
this.props.channel.send(data);
|
||||
}.bind(this));
|
||||
|
||||
if (this.props.onTitleChanged)
|
||||
term.on('title', this.props.onTitleChanged);
|
||||
|
||||
this.setState({ terminal: term });
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.state.terminal.open(this.refs.terminal);
|
||||
this.connectChannel();
|
||||
|
||||
let term = this.refs.terminal;
|
||||
let onWindowResize = this.onWindowResize;
|
||||
|
||||
$(function() {
|
||||
$(term).resizable({
|
||||
direction: ['right', 'bottom'],
|
||||
stop: function() {
|
||||
onWindowResize();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
term.on('data', function(data) {
|
||||
if (this.props.channel.valid)
|
||||
this.props.channel.send(data);
|
||||
}.bind(this));
|
||||
|
||||
if (this.props.onTitleChanged)
|
||||
term.on('title', this.props.onTitleChanged);
|
||||
|
||||
this.setState({ terminal: term });
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.state.terminal.open(this.refs.terminal);
|
||||
this.connectChannel();
|
||||
|
||||
let term = this.refs.terminal;
|
||||
let onWindowResize = this.onWindowResize;
|
||||
|
||||
$(function() {
|
||||
$(term).resizable({
|
||||
direction: ['right', 'bottom'],
|
||||
stop: function() {
|
||||
onWindowResize();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (!this.props.rows) {
|
||||
window.addEventListener('resize', this.onWindowResize);
|
||||
this.onWindowResize();
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUpdate: function (nextProps, nextState) {
|
||||
if (nextState.cols !== this.state.cols || nextState.rows !== this.state.rows) {
|
||||
this.state.terminal.resize(nextState.cols, nextState.rows);
|
||||
this.props.channel.control({
|
||||
window: {
|
||||
rows: nextState.rows,
|
||||
cols: nextState.cols
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (nextProps.channel !== this.props.channel) {
|
||||
this.state.terminal.reset();
|
||||
this.disconnectChannel();
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate: function (prevProps) {
|
||||
if (prevProps.channel !== this.props.channel)
|
||||
this.connectChannel();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let style = {
|
||||
'min-width': '300px',
|
||||
'min-height': '100px',
|
||||
}
|
||||
// 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} />;
|
||||
},
|
||||
|
||||
componentWillUnmount: function () {
|
||||
this.disconnectChannel();
|
||||
this.state.terminal.destroy();
|
||||
},
|
||||
|
||||
onChannelMessage: function (event, data) {
|
||||
if (this.state.terminal) {
|
||||
this.state.terminal.write(data);
|
||||
}
|
||||
},
|
||||
|
||||
onChannelClose: function (event, options) {
|
||||
var term = this.state.terminal;
|
||||
term.write('\x1b[31m' + (options.problem || 'disconnected') + '\x1b[m\r\n');
|
||||
term.cursorHidden = true;
|
||||
term.refresh(term.y, term.y);
|
||||
},
|
||||
|
||||
connectChannel: function () {
|
||||
var channel = this.props.channel;
|
||||
if (channel && channel.valid) {
|
||||
channel.addEventListener('message', this.onChannelMessage.bind(this));
|
||||
channel.addEventListener('close', this.onChannelClose.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
disconnectChannel: function () {
|
||||
if (this.props.channel) {
|
||||
this.props.channel.removeEventListener('message', this.onChannelMessage);
|
||||
this.props.channel.removeEventListener('close', this.onChannelClose);
|
||||
}
|
||||
},
|
||||
|
||||
focus: function () {
|
||||
if (this.state.terminal)
|
||||
this.state.terminal.focus();
|
||||
},
|
||||
|
||||
onWindowResize: function () {
|
||||
if (this.refs) {
|
||||
var padding = 2 * 11;
|
||||
var node = this.getDOMNode();
|
||||
var terminal = this.refs.terminal.querySelector('.terminal');
|
||||
|
||||
var ch = document.createElement('div');
|
||||
ch.textContent = 'M';
|
||||
terminal.appendChild(ch);
|
||||
var height = ch.offsetHeight; // offsetHeight is only correct for block elements
|
||||
ch.style.display = 'inline';
|
||||
var width = ch.offsetWidth;
|
||||
terminal.removeChild(ch);
|
||||
|
||||
this.setState({
|
||||
rows: Math.floor((node.parentElement.clientHeight - padding) / height),
|
||||
cols: Math.floor((node.parentElement.clientWidth - padding) / width)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
send: function(value) {
|
||||
this.state.terminal.send(value);
|
||||
if (!this.props.rows) {
|
||||
window.addEventListener('resize', this.onWindowResize);
|
||||
this.onWindowResize();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
module.exports = { Terminal: Terminal };
|
||||
}());
|
||||
componentWillUpdate: function (nextProps, nextState) {
|
||||
if (nextState.cols !== this.state.cols || nextState.rows !== this.state.rows) {
|
||||
this.state.terminal.resize(nextState.cols, nextState.rows);
|
||||
this.props.channel.control({
|
||||
window: {
|
||||
rows: nextState.rows,
|
||||
cols: nextState.cols
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (nextProps.channel !== this.props.channel) {
|
||||
this.state.terminal.reset();
|
||||
this.disconnectChannel();
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate: function (prevProps) {
|
||||
if (prevProps.channel !== this.props.channel)
|
||||
this.connectChannel();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let style = {
|
||||
'min-width': '300px',
|
||||
'min-height': '100px',
|
||||
};
|
||||
// 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} />;
|
||||
},
|
||||
|
||||
componentWillUnmount: function () {
|
||||
this.disconnectChannel();
|
||||
this.state.terminal.destroy();
|
||||
},
|
||||
|
||||
onChannelMessage: function (event, data) {
|
||||
if (this.state.terminal) {
|
||||
this.state.terminal.write(data);
|
||||
}
|
||||
},
|
||||
|
||||
onChannelClose: function (event, options) {
|
||||
var term = this.state.terminal;
|
||||
term.write('\x1b[31m' + (options.problem || 'disconnected') + '\x1b[m\r\n');
|
||||
term.cursorHidden = true;
|
||||
term.refresh(term.y, term.y);
|
||||
},
|
||||
|
||||
connectChannel: function () {
|
||||
var channel = this.props.channel;
|
||||
if (channel && channel.valid) {
|
||||
channel.addEventListener('message', this.onChannelMessage.bind(this));
|
||||
channel.addEventListener('close', this.onChannelClose.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
disconnectChannel: function () {
|
||||
if (this.props.channel) {
|
||||
this.props.channel.removeEventListener('message', this.onChannelMessage);
|
||||
this.props.channel.removeEventListener('close', this.onChannelClose);
|
||||
}
|
||||
},
|
||||
|
||||
focus: function () {
|
||||
if (this.state.terminal)
|
||||
this.state.terminal.focus();
|
||||
},
|
||||
|
||||
onWindowResize: function () {
|
||||
if (this.refs) {
|
||||
var padding = 2 * 11;
|
||||
var node = this.getDOMNode();
|
||||
var terminal = this.refs.terminal.querySelector('.terminal');
|
||||
|
||||
var ch = document.createElement('div');
|
||||
ch.textContent = 'M';
|
||||
terminal.appendChild(ch);
|
||||
var height = ch.offsetHeight; // offsetHeight is only correct for block elements
|
||||
ch.style.display = 'inline';
|
||||
var width = ch.offsetWidth;
|
||||
terminal.removeChild(ch);
|
||||
|
||||
this.setState({
|
||||
rows: Math.floor((node.parentElement.clientHeight - padding) / height),
|
||||
cols: Math.floor((node.parentElement.clientWidth - padding) / width)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
send: function(value) {
|
||||
this.state.terminal.send(value);
|
||||
}
|
||||
});
|
||||
|
||||
// module.exports = { Terminal: Terminal };
|
||||
export class Terminal;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ var info = {
|
|||
"config": [
|
||||
"./config.jsx",
|
||||
"./recordings.css",
|
||||
"./table.css",
|
||||
]
|
||||
},
|
||||
files: [
|
||||
|
|
@ -38,6 +39,7 @@ var info = {
|
|||
"player.jsx",
|
||||
"recordings.jsx",
|
||||
"recordings.css",
|
||||
"table.css",
|
||||
"terminal.jsx",
|
||||
"manifest.json",
|
||||
"timer.css",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue