Improvements to the logging ui. Correctly decoding unicode chars. Initial support for ansi color codes. Fixing bug where logs could be repeated multiple times. Performance improvements.

This commit is contained in:
Eric Herbrandson
2019-06-18 08:16:25 -05:00
parent 97afc1f96b
commit 412d6aa02a
5 changed files with 335 additions and 854 deletions
+306 -827
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -4,6 +4,8 @@
"private": true,
"homepage": ".",
"dependencies": {
"ansi-to-react": "^5.0.0",
"js-base64": "^2.5.1",
"lodash": "^4.17.11",
"moment": "^2.24.0",
"node-sass": "^4.11.0",
+14 -7
View File
@@ -1,4 +1,5 @@
import _ from 'lodash';
import {Base64} from 'js-base64';
import {request, stream, streamResult, streamResults} from './apiProxy';
import log from '../utils/log';
@@ -152,20 +153,26 @@ function swagger() {
function exec(namespace, name, container, cb) {
const url = `/api/v1/namespaces/${namespace}/pods/${name}/exec?container=${container}&command=sh&stdin=1&stderr=1&stdout=1&tty=1`;
const protocols = ['v4.channel.k8s.io', 'v3.channel.k8s.io', 'v2.channel.k8s.io', 'channel.k8s.io'];
return stream(url, cb, false, protocols);
const additionalProtocols = ['v4.channel.k8s.io', 'v3.channel.k8s.io', 'v2.channel.k8s.io', 'channel.k8s.io'];
return stream(url, cb, {additionalProtocols, isJson: false});
}
function logs(namespace, name, container, showPrevious, cb) {
const url = `/api/v1/namespaces/${namespace}/pods/${name}/log?container=${container}&previous=${showPrevious}&tailLines=2000&follow=true`;
const {cancel} = stream(url, transformer, false);
function logs(namespace, name, container, tailLines, showPrevious, cb) {
const items = [];
const url = `/api/v1/namespaces/${namespace}/pods/${name}/log?container=${container}&previous=${showPrevious}&tailLines=${tailLines}&follow=true`;
const {cancel} = stream(url, transformer, {isJson: false, connectCb});
return cancel;
function connectCb() {
items.length = 0;
}
function transformer(item) {
if (!item) return; // For some reason, this api returns a lot of empty strings
const message = atob(item);
cb(message);
const message = Base64.decode(item);
items.push(message);
cb(items);
}
}
+5 -3
View File
@@ -81,7 +81,7 @@ export async function streamResult(url, name, cb, errCb) {
const fieldSelector = encodeURIComponent(`metadata.name=${name}`);
const watchUrl = `${url}?watch=1&fieldSelector=${fieldSelector}`;
socket = stream(watchUrl, x => cb(x.object));
socket = stream(watchUrl, x => cb(x.object), {isJson: true});
} catch (err) {
log.error('Error in api request', {err, url});
if (errCb) errCb(err);
@@ -112,7 +112,7 @@ export async function streamResults(url, cb, errCb) {
add(items, kind);
const watchUrl = `${url}?watch=1&resourceVersion=${metadata.resourceVersion}`;
socket = stream(watchUrl, update);
socket = stream(watchUrl, update, {isJson: true});
} catch (err) {
log.error('Error in api request', {err, url});
if (errCb) errCb(err);
@@ -177,9 +177,10 @@ export async function streamResults(url, cb, errCb) {
}
}
export function stream(url, cb, isJson = true, additionalProtocols) {
export function stream(url, cb, args) {
let connection;
let isCancelled;
const {isJson, additionalProtocols, connectCb} = args;
connect();
@@ -195,6 +196,7 @@ export function stream(url, cb, isJson = true, additionalProtocols) {
}
function connect() {
if (connectCb) connectCb();
connection = connectStream(url, cb, onFail, isJson, additionalProtocols);
}
+8 -17
View File
@@ -3,6 +3,7 @@ import _ from 'lodash';
import React from 'react';
import Switch from 'react-switch';
import Select from 'react-select';
import Ansi from 'ansi-to-react';
import Base from '../components/base';
import InputFilter from '../components/inputFilter';
import Loading from '../components/loading';
@@ -21,7 +22,7 @@ export default class Logs extends Base {
// on the trailing edge of the timeout only if the debounced function is invoked more
// than once during the wait timeout."
const options = {leading: true, trailing: true, maxWait: 1000};
this.debouncedRefreshLogs = _.debounce(this.refreshLogs.bind(this), 1000, options);
this.debouncedSetState = _.debounce(this.setState.bind(this), 100, options);
}
componentDidMount() {
@@ -51,28 +52,17 @@ export default class Logs extends Base {
startLogsStream(container, showPrevious) {
if (!container) return;
const {namespace, name} = this.props;
this.setState({container, showPrevious, items: []});
const {namespace, name} = this.props;
this.registerApi({
items: api.logs(namespace, name, container, showPrevious, items => this.onLogs(items)),
items: api.logs(namespace, name, container, 1000, showPrevious, items => this.debouncedSetState({items})), // eslint-disable-line max-len
});
}
onLogs(log) {
const {items} = this.state;
items.push(log);
this.debouncedRefreshLogs(items);
}
refreshLogs(logs) {
const items = logs.slice(-1000);
this.setState({items});
}
render() {
const {namespace, name} = this.props;
const {items, container, containers = [], filter = '', showPrevious = false} = this.state || {};
const {items, container, containers = [], filter = '', showPrevious = false} = this.state;
const lowercaseFilter = filter.toLowerCase();
const filteredLogs = items.filter(x => x.toLowerCase().includes(lowercaseFilter));
@@ -111,13 +101,14 @@ export default class Logs extends Base {
filter={filter}
onChange={x => this.setState({filter: x})}
/>
</div>
<div className='contentPanel'>
{!items ? <Loading /> : (
<pre>
{filteredLogs.join('')}
{filteredLogs.map((x, i) => (
<Ansi key={i}>{x}</Ansi>
))}
</pre>
)}
</div>