mirror of
https://github.com/skooner-k8s/skooner.git
synced 2026-08-24 04:16:18 +00:00
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:
Generated
+306
-827
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user