Files
weave-scope/client/app/scripts/utils/web-api-utils.js
Simon Howe 3b2af2ae6c Terminal UI for pipes
- term.js
- Add eslintignore
- Fix color and es2015 after rebase
- Fix JS test, probably deleted during conflict resolution
- Moves terminal close button to top-right of window
- Consitent w/ details window.
- Changes padding of details window close button so both close buttons
  are horizonally aligned.
- Terminal resizes w/ browser window.
- No longer can drag window around.
- Add tiny big of padding between term and node-details.
- Playing w/ terminal placement.  This one's more drawer-like.
- Send DELETE when we close a terminal window.
- Dont lint or bable JS vendor files
- Ignore ctags 'tags' files.
- Adds popping out terminal out into a new browser window.
- Simplify code as now we've just a single terminal window.
- ESC is back to close the terminal, then the details panel.
- Fixes bug w/ slow response to closing the details panel.
- Moving away from "drawer-style" for terminal size and position to a
  simple window.
- Just gotta handle the case for refreshing a popped out terminal.
- Stop terminal text being auto-deselected.
- window resizes will still deselect.
- Adds state.connected to react.scu check.
- Don't delete pipe when browser closes
- To allow for nicer refresh flows
- scope-app will time out pipe after a while.
- Keep terminal-open/closed state in the url.
- shouldComponentUpdate fix to prevent deselection of text has been
  rolled back so gotta come up w/ another way to handle that...
- Fixes terminal text-selection again.
- Make pipes work for non-raw terminals too.
- Move document.title updating somewhere more sensible.
- Pass rawTty prop along to all terminals.
- Don't render react root into doc.body
- Reconnect the websocket if we lose it.
- First, slightly rough, attempt at displaying if pipe has been deleted
- Refactor controlPipe structure in the AppStore/hash.
- Merge controlPipeId, controlPipeRaw, controlPipeStatus into a single object.
- Adds a status bar to the terminal window.
- Error handling in popout working again.
- Don't show terminal cursor when not connected.
- Simplify controlPipe status and error handling.
- Don't keep the status in the hash.
- Use special new action receiveControlPipeFromParams rather than
  adding lots of branching to receiveControlPipe.
- You can reload a terminal but it doesn't exist in history stack.
- Pull out terminal into its own entry point!
- Fixes prod webpack build
- Fixes terminal-app websocket path when running on prod.
- Fixes old terminals appearing when closing a terminal.
- History hacking wasn't working, this is a little simpler.
2015-12-10 12:52:44 +00:00

218 lines
5.5 KiB
JavaScript

import debug from 'debug';
import reqwest from 'reqwest';
import { clearControlError, closeWebsocket, openWebsocket, receiveError,
receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError,
receiveControlPipe, receiveControlPipeStatus, receiveControlSuccess,
receiveTopologies } from '../actions/app-actions';
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
const wsUrl = __WS_URL__ || wsProto + '://' + location.host + location.pathname.replace(/\/$/, '');
const log = debug('scope:web-api-utils');
const apiTimerInterval = 10000;
const reconnectTimerInterval = 5000;
const topologyTimerInterval = apiTimerInterval;
const updateFrequency = '5s';
let socket;
let reconnectTimer = 0;
let currentUrl = null;
let currentOptions = null;
let topologyTimer = 0;
let apiDetailsTimer = 0;
let controlErrorTimer = 0;
function buildOptionsQuery(options) {
if (options) {
return options.reduce(function(query, value, param) {
return `${query}&${param}=${value}`;
}, '');
}
return '';
}
export function basePath(urlPath) {
//
// "/scope/terminal.html" -> "/scope"
// "/scope/" -> "/scope"
// "/scope" -> "/scope"
// "/" -> ""
//
const parts = urlPath.split('/');
// if the last item has a "." in it, e.g. foo.html...
if (parts[parts.length - 1].indexOf('.') !== -1) {
return parts.slice(0, -1).join('/');
}
return parts.join('/').replace(/\/$/, '');
}
function createWebsocket(topologyUrl, optionsQuery) {
if (socket) {
socket.onclose = null;
socket.onerror = null;
socket.close();
}
socket = new WebSocket(wsUrl + topologyUrl
+ '/ws?t=' + updateFrequency + '&' + optionsQuery);
socket.onopen = function() {
openWebsocket();
};
socket.onclose = function() {
clearTimeout(reconnectTimer);
socket = null;
closeWebsocket();
log('Closed websocket to ' + topologyUrl);
reconnectTimer = setTimeout(function() {
createWebsocket(topologyUrl, optionsQuery);
}, reconnectTimerInterval);
};
socket.onerror = function() {
log('Error in websocket to ' + topologyUrl);
receiveError(currentUrl);
};
socket.onmessage = function(event) {
const msg = JSON.parse(event.data);
receiveNodesDelta(msg);
};
}
/* keep URLs relative */
export function getTopologies(options) {
clearTimeout(topologyTimer);
const optionsQuery = buildOptionsQuery(options);
const url = `api/topology?${optionsQuery}`;
reqwest({
url: url,
success: function(res) {
receiveTopologies(res);
topologyTimer = setTimeout(function() {
getTopologies(options);
}, topologyTimerInterval / 2);
},
error: function(err) {
log('Error in topology request: ' + err);
receiveError(url);
topologyTimer = setTimeout(function() {
getTopologies(options);
}, topologyTimerInterval / 2);
}
});
}
export function getNodesDelta(topologyUrl, options) {
const optionsQuery = buildOptionsQuery(options);
// only recreate websocket if url changed
if (topologyUrl && (topologyUrl !== currentUrl || currentOptions !== optionsQuery)) {
createWebsocket(topologyUrl, optionsQuery);
currentUrl = topologyUrl;
currentOptions = optionsQuery;
}
}
export function getNodeDetails(topologyUrl, nodeId) {
if (topologyUrl && nodeId) {
const url = [topologyUrl, '/', encodeURIComponent(nodeId)]
.join('').substr(1);
reqwest({
url: url,
success: function(res) {
receiveNodeDetails(res.node);
},
error: function(err) {
log('Error in node details request: ' + err.responseText);
// dont treat missing node as error
if (err.status !== 404) {
receiveError(topologyUrl);
}
}
});
}
}
export function getApiDetails() {
clearTimeout(apiDetailsTimer);
const url = 'api';
reqwest({
url: url,
success: function(res) {
receiveApiDetails(res);
apiDetailsTimer = setTimeout(getApiDetails, apiTimerInterval);
},
error: function(err) {
log('Error in api details request: ' + err);
receiveError(url);
apiDetailsTimer = setTimeout(getApiDetails, apiTimerInterval / 2);
}
});
}
export function doControl(probeId, nodeId, control) {
clearTimeout(controlErrorTimer);
const url = `api/control/${encodeURIComponent(probeId)}/`
+ `${encodeURIComponent(nodeId)}/${control}`;
reqwest({
method: 'POST',
url: url,
success: function(res) {
receiveControlSuccess();
if (res && res.pipe) {
receiveControlPipe(res.pipe, nodeId, res.raw_tty, true);
}
},
error: function(err) {
receiveControlError(err.response);
controlErrorTimer = setTimeout(function() {
clearControlError();
}, 10000);
}
});
}
export function deletePipe(pipeId) {
const url = `api/pipe/${encodeURIComponent(pipeId)}`;
reqwest({
method: 'DELETE',
url: url,
success: function() {
log('Closed the pipe!');
},
error: function(err) {
log('Error closing pipe:' + err);
receiveError(url);
}
});
}
export function getPipeStatus(pipeId) {
const url = `/api/pipe/${encodeURIComponent(pipeId)}`;
reqwest({
method: 'GET',
url: url,
success: function(res) {
log('ERROR: expected responses: [400, 404]. Got:', res);
},
error: function(err) {
const status = {
400: 'PIPE_ALIVE',
404: 'PIPE_DELETED'
}[err.status];
if (!status) {
log('Unexpected pipe status:', err.status);
return;
}
receiveControlPipeStatus(pipeId, status);
}
});
}