mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-28 17:51:21 +00:00
@@ -580,16 +580,17 @@ export function receiveControlNodeRemoved(nodeId) {
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveControlPipeFromParams(pipeId, rawTty) {
|
||||
export function receiveControlPipeFromParams(pipeId, rawTty, resizeTtyControl) {
|
||||
// TODO add nodeId
|
||||
return {
|
||||
type: ActionTypes.RECEIVE_CONTROL_PIPE,
|
||||
pipeId,
|
||||
rawTty
|
||||
rawTty,
|
||||
resizeTtyControl
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveControlPipe(pipeId, nodeId, rawTty) {
|
||||
export function receiveControlPipe(pipeId, nodeId, rawTty, resizeTtyControl) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
if (state.get('nodeDetails').last()
|
||||
@@ -608,7 +609,8 @@ export function receiveControlPipe(pipeId, nodeId, rawTty) {
|
||||
type: ActionTypes.RECEIVE_CONTROL_PIPE,
|
||||
nodeId,
|
||||
pipeId,
|
||||
rawTty
|
||||
rawTty,
|
||||
resizeTtyControl
|
||||
});
|
||||
|
||||
updateRoute(getState);
|
||||
|
||||
@@ -204,46 +204,47 @@ export default class NodeDetailsTable extends React.Component {
|
||||
}
|
||||
|
||||
renderHeaders(sortBy, sortedDesc) {
|
||||
if (this.props.nodes && this.props.nodes.length > 0) {
|
||||
const headers = this.getColumnHeaders();
|
||||
const colStyles = getColumnsStyles(headers);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
{headers.map((header, i) => {
|
||||
const headerClasses = ['node-details-table-header', 'truncate'];
|
||||
const onHeaderClick = ev => {
|
||||
this.handleHeaderClick(ev, header.id, sortBy, sortedDesc);
|
||||
};
|
||||
// sort by first metric by default
|
||||
const isSorted = header.id === sortBy;
|
||||
const isSortedDesc = isSorted && sortedDesc;
|
||||
const isSortedAsc = isSorted && !isSortedDesc;
|
||||
|
||||
if (isSorted) {
|
||||
headerClasses.push('node-details-table-header-sorted');
|
||||
}
|
||||
|
||||
const style = colStyles[i];
|
||||
const label = (style.width === CW.XS && XS_LABEL[header.id]) ?
|
||||
XS_LABEL[header.id] :
|
||||
header.label;
|
||||
|
||||
return (
|
||||
<td className={headerClasses.join(' ')} style={style} onClick={onHeaderClick}
|
||||
title={header.label} key={header.id}>
|
||||
{isSortedAsc
|
||||
&& <span className="node-details-table-header-sorter fa fa-caret-up" />}
|
||||
{isSortedDesc
|
||||
&& <span className="node-details-table-header-sorter fa fa-caret-down" />}
|
||||
{label}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
if (!this.props.nodes || this.props.nodes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return '';
|
||||
|
||||
const headers = this.getColumnHeaders();
|
||||
const colStyles = getColumnsStyles(headers);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
{headers.map((header, i) => {
|
||||
const headerClasses = ['node-details-table-header', 'truncate'];
|
||||
const onHeaderClick = ev => {
|
||||
this.handleHeaderClick(ev, header.id, sortBy, sortedDesc);
|
||||
};
|
||||
// sort by first metric by default
|
||||
const isSorted = header.id === sortBy;
|
||||
const isSortedDesc = isSorted && sortedDesc;
|
||||
const isSortedAsc = isSorted && !isSortedDesc;
|
||||
|
||||
if (isSorted) {
|
||||
headerClasses.push('node-details-table-header-sorted');
|
||||
}
|
||||
|
||||
const style = colStyles[i];
|
||||
const label = (style.width === CW.XS && XS_LABEL[header.id]) ?
|
||||
XS_LABEL[header.id] :
|
||||
header.label;
|
||||
|
||||
return (
|
||||
<td className={headerClasses.join(' ')} style={style} onClick={onHeaderClick}
|
||||
title={header.label} key={header.id}>
|
||||
{isSortedAsc
|
||||
&& <span className="node-details-table-header-sorter fa fa-caret-up" />}
|
||||
{isSortedDesc
|
||||
&& <span className="node-details-table-header-sorter fa fa-caret-down" />}
|
||||
{label}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -13,7 +13,8 @@ class TerminalApp extends React.Component {
|
||||
|
||||
const paramString = window.location.hash.split('/').pop();
|
||||
const params = JSON.parse(decodeURIComponent(paramString));
|
||||
this.props.receiveControlPipeFromParams(params.pipe.id, null, params.pipe.raw, false);
|
||||
this.props.receiveControlPipeFromParams(params.pipe.id, params.pipe.raw,
|
||||
params.pipe.resizeTtyControl);
|
||||
|
||||
this.state = {
|
||||
title: params.title,
|
||||
|
||||
@@ -8,7 +8,7 @@ import classNames from 'classnames';
|
||||
import { clickCloseTerminal } from '../actions/app-actions';
|
||||
import { getNeutralColor } from '../utils/color-utils';
|
||||
import { setDocumentTitle } from '../utils/title-utils';
|
||||
import { getPipeStatus, basePath } from '../utils/web-api-utils';
|
||||
import { getPipeStatus, basePath, doResizeTty } from '../utils/web-api-utils';
|
||||
import Term from 'xterm';
|
||||
|
||||
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
@@ -32,27 +32,29 @@ function ab2str(buf) {
|
||||
return decodedString;
|
||||
}
|
||||
|
||||
function terminalCellSize(wrapperNode, rows, cols) {
|
||||
const height = wrapperNode.clientHeight;
|
||||
function terminalCellSize(wrapperNode) {
|
||||
// Badly guess the width/height of the row.
|
||||
let characterWidth = 20;
|
||||
let characterHeight = 20;
|
||||
|
||||
// Guess the width of the row.
|
||||
let width = wrapperNode.clientWidth;
|
||||
// Now try and measure the first row we find.
|
||||
const firstRow = wrapperNode.querySelector('.terminal div');
|
||||
if (!firstRow) {
|
||||
const subjectRow = wrapperNode.querySelector('.terminal .xterm-rows div');
|
||||
if (!subjectRow) {
|
||||
log("ERROR: Couldn't find first row, resizing might not work very well.");
|
||||
} else {
|
||||
const rowDisplay = firstRow.style.display;
|
||||
firstRow.style.display = 'inline';
|
||||
width = firstRow.offsetWidth;
|
||||
firstRow.style.display = rowDisplay;
|
||||
const rowDisplay = subjectRow.style.display;
|
||||
const contentBuffer = subjectRow.innerHTML;
|
||||
|
||||
subjectRow.innerHTML = 'W';
|
||||
subjectRow.style.display = 'inline';
|
||||
characterWidth = subjectRow.getBoundingClientRect().width;
|
||||
subjectRow.style.display = rowDisplay;
|
||||
characterHeight = parseInt(subjectRow.offsetHeight, 10);
|
||||
subjectRow.innerHTML = contentBuffer;
|
||||
}
|
||||
|
||||
const pixelPerCol = width / cols;
|
||||
const pixelPerRow = height / rows;
|
||||
|
||||
log('Caculated (col, row) sizes in px: ', pixelPerCol, pixelPerRow);
|
||||
return {pixelPerCol, pixelPerRow};
|
||||
log('Caculated (charWidth, charHeight) sizes in px: ', characterWidth, characterHeight);
|
||||
return {characterWidth, characterHeight};
|
||||
}
|
||||
|
||||
|
||||
@@ -88,14 +90,13 @@ class Terminal extends React.Component {
|
||||
connected: false,
|
||||
rows: DEFAULT_ROWS,
|
||||
cols: DEFAULT_COLS,
|
||||
pixelPerCol: 0,
|
||||
pixelPerRow: 0
|
||||
characterWidth: 0,
|
||||
characterHeight: 0
|
||||
};
|
||||
|
||||
this.handleCloseClick = this.handleCloseClick.bind(this);
|
||||
this.handlePopoutTerminal = this.handlePopoutTerminal.bind(this);
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.focusTerminal = this.focusTerminal.bind(this);
|
||||
}
|
||||
|
||||
createWebsocket(term) {
|
||||
@@ -200,15 +201,14 @@ class Terminal extends React.Component {
|
||||
|
||||
this.createWebsocket(this.term);
|
||||
|
||||
const {pixelPerCol, pixelPerRow} = terminalCellSize(
|
||||
this.term.element, this.state.rows, this.state.cols);
|
||||
const {characterWidth, characterHeight} = terminalCellSize(this.term.element);
|
||||
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
|
||||
this.resizeTimeout = setTimeout(() => {
|
||||
this.setState({
|
||||
pixelPerCol,
|
||||
pixelPerRow
|
||||
characterWidth,
|
||||
characterHeight
|
||||
});
|
||||
this.handleResize();
|
||||
}, 10);
|
||||
@@ -256,29 +256,29 @@ class Terminal extends React.Component {
|
||||
this.props.dispatch(clickCloseTerminal(this.getPipeId(), true));
|
||||
}
|
||||
|
||||
focusTerminal() {
|
||||
if (this.term) {
|
||||
this.term.focus();
|
||||
}
|
||||
}
|
||||
|
||||
handlePopoutTerminal(ev) {
|
||||
ev.preventDefault();
|
||||
const paramString = JSON.stringify(this.props);
|
||||
this.props.dispatch(clickCloseTerminal(this.getPipeId()));
|
||||
|
||||
const bcr = ReactDOM.findDOMNode(this).getBoundingClientRect();
|
||||
const minWidth = this.state.pixelPerCol * 80 + (8 * 2);
|
||||
const minWidth = this.state.characterWidth * 80 + (8 * 2);
|
||||
openNewWindow(`terminal.html#!/state/${paramString}`, bcr, minWidth);
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
const innerNode = ReactDOM.findDOMNode(this.innerFlex);
|
||||
// scrollbar === 16px
|
||||
const width = innerNode.clientWidth - (2 * 8) - 16;
|
||||
const height = innerNode.clientHeight - (2 * 8);
|
||||
const cols = DEFAULT_COLS;
|
||||
const rows = Math.floor(height / this.state.pixelPerRow);
|
||||
const cols = Math.floor(width / this.state.characterWidth);
|
||||
const rows = Math.floor(height / this.state.characterHeight);
|
||||
|
||||
this.setState({cols, rows});
|
||||
const resizeTtyControl = this.props.pipe.get('resizeTtyControl');
|
||||
if (resizeTtyControl) {
|
||||
doResizeTty(this.getPipeId(), resizeTtyControl, cols, rows)
|
||||
.then(() => this.setState({cols, rows}));
|
||||
}
|
||||
}
|
||||
|
||||
isEmbedded() {
|
||||
@@ -359,7 +359,7 @@ class Terminal extends React.Component {
|
||||
overflow: 'hidden',
|
||||
};
|
||||
const innerStyle = {
|
||||
width: (this.state.cols + 2) * this.state.pixelPerCol
|
||||
width: (this.state.cols + 2) * this.state.characterWidth
|
||||
};
|
||||
const innerClassName = classNames('terminal-inner hideable', {
|
||||
'terminal-inactive': !this.state.connected
|
||||
@@ -369,7 +369,6 @@ class Terminal extends React.Component {
|
||||
<div className="terminal-wrapper">
|
||||
{this.isEmbedded() && this.getTerminalHeader()}
|
||||
<div
|
||||
onClick={this.focusTerminal}
|
||||
ref={(ref) => this.innerFlex = ref}
|
||||
className={innerClassName}
|
||||
style={innerFlexStyle} >
|
||||
|
||||
@@ -466,7 +466,8 @@ export function rootReducer(state = initialState, action) {
|
||||
return state.setIn(['controlPipes', action.pipeId], makeOrderedMap({
|
||||
id: action.pipeId,
|
||||
nodeId: action.nodeId,
|
||||
raw: action.rawTty
|
||||
raw: action.rawTty,
|
||||
resizeTtyControl: action.resizeTtyControl,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -232,7 +232,11 @@ export function doControlRequest(nodeId, control, dispatch) {
|
||||
if (res) {
|
||||
if (res.pipe) {
|
||||
dispatch(blurSearch());
|
||||
dispatch(receiveControlPipe(res.pipe, nodeId, res.raw_tty, true));
|
||||
dispatch(receiveControlPipe(
|
||||
res.pipe,
|
||||
nodeId,
|
||||
res.raw_tty,
|
||||
{id: res.resize_tty_control, probeId: control.probeId, nodeId: control.nodeId}));
|
||||
}
|
||||
if (res.removedNode) {
|
||||
dispatch(receiveControlNodeRemoved(nodeId));
|
||||
@@ -248,6 +252,22 @@ export function doControlRequest(nodeId, control, dispatch) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function doResizeTty(pipeId, control, cols, rows) {
|
||||
const url = `api/control/${encodeURIComponent(control.probeId)}/`
|
||||
+ `${encodeURIComponent(control.nodeId)}/${control.id}`;
|
||||
|
||||
return reqwest({
|
||||
method: 'POST',
|
||||
url,
|
||||
data: JSON.stringify({pipeID: pipeId, width: cols.toString(), height: rows.toString()}),
|
||||
})
|
||||
.fail((err) => {
|
||||
log(`Error resizing pipe: ${err}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function deletePipe(pipeId, dispatch) {
|
||||
const url = `api/pipe/${encodeURIComponent(pipeId)}`;
|
||||
reqwest({
|
||||
@@ -263,6 +283,7 @@ export function deletePipe(pipeId, dispatch) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function getPipeStatus(pipeId, dispatch) {
|
||||
const url = `api/pipe/${encodeURIComponent(pipeId)}/check`;
|
||||
reqwest({
|
||||
|
||||
Reference in New Issue
Block a user