mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into fix/events-sorting
# Conflicts: # src/renderer/components/+events/events.tsx
This commit is contained in:
commit
100b0aa271
@ -15,7 +15,10 @@ export default class SurveyRendererExtension extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
async onActivate() {
|
async onActivate() {
|
||||||
await surveyPreferencesStore.loadExtension(this);
|
// Activate extension only on main renderer
|
||||||
survey.start();
|
if (window.location.hostname === "localhost") {
|
||||||
|
await surveyPreferencesStore.loadExtension(this);
|
||||||
|
survey.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "kontena-lens",
|
"name": "kontena-lens",
|
||||||
"productName": "Lens",
|
"productName": "Lens",
|
||||||
"description": "Lens - The Kubernetes IDE",
|
"description": "Lens - The Kubernetes IDE",
|
||||||
"version": "4.1.0-rc.1",
|
"version": "4.1.0-rc.2",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2020, Mirantis, Inc.",
|
"copyright": "© 2020, Mirantis, Inc.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -222,7 +222,7 @@
|
|||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-router": "^5.2.0",
|
"react-router": "^5.2.0",
|
||||||
"readable-web-to-node-stream": "^3.0.1",
|
"readable-stream": "^3.6.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"request-promise-native": "^1.0.8",
|
"request-promise-native": "^1.0.8",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
@ -277,6 +277,7 @@
|
|||||||
"@types/react-router-dom": "^5.1.6",
|
"@types/react-router-dom": "^5.1.6",
|
||||||
"@types/react-select": "^3.0.13",
|
"@types/react-select": "^3.0.13",
|
||||||
"@types/react-window": "^1.8.2",
|
"@types/react-window": "^1.8.2",
|
||||||
|
"@types/readable-stream": "^2.3.9",
|
||||||
"@types/request": "^2.48.5",
|
"@types/request": "^2.48.5",
|
||||||
"@types/request-promise-native": "^1.0.17",
|
"@types/request-promise-native": "^1.0.17",
|
||||||
"@types/semver": "^7.2.0",
|
"@types/semver": "^7.2.0",
|
||||||
|
|||||||
@ -400,7 +400,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
this.ready = false;
|
this.ready = false;
|
||||||
this.activated = false;
|
this.activated = false;
|
||||||
this.allowedNamespaces = [];
|
this.allowedNamespaces = [];
|
||||||
this.accessibleNamespaces = [];
|
|
||||||
this.resourceAccessStatuses.clear();
|
this.resourceAccessStatuses.clear();
|
||||||
this.pushState();
|
this.pushState();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,8 +10,8 @@ import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
|
|||||||
import { KubeJsonApi, KubeJsonApiData, KubeJsonApiDataList } from "./kube-json-api";
|
import { KubeJsonApi, KubeJsonApiData, KubeJsonApiDataList } from "./kube-json-api";
|
||||||
import { IKubeObjectConstructor, KubeObject, KubeStatus } from "./kube-object";
|
import { IKubeObjectConstructor, KubeObject, KubeStatus } from "./kube-object";
|
||||||
import byline from "byline";
|
import byline from "byline";
|
||||||
import { ReadableWebToNodeStream } from "readable-web-to-node-stream";
|
|
||||||
import { IKubeWatchEvent } from "./kube-watch-api";
|
import { IKubeWatchEvent } from "./kube-watch-api";
|
||||||
|
import { ReadableWebToNodeStream } from "../utils/readableStream";
|
||||||
|
|
||||||
export interface IKubeApiOptions<T extends KubeObject> {
|
export interface IKubeApiOptions<T extends KubeObject> {
|
||||||
/**
|
/**
|
||||||
@ -373,7 +373,13 @@ export class KubeApi<T extends KubeObject = any> {
|
|||||||
opts.abortController = new AbortController();
|
opts.abortController = new AbortController();
|
||||||
}
|
}
|
||||||
let errorReceived = false;
|
let errorReceived = false;
|
||||||
|
let timedRetry: NodeJS.Timeout;
|
||||||
const { abortController, namespace, callback } = opts;
|
const { abortController, namespace, callback } = opts;
|
||||||
|
|
||||||
|
abortController.signal.addEventListener("abort", () => {
|
||||||
|
clearTimeout(timedRetry);
|
||||||
|
});
|
||||||
|
|
||||||
const watchUrl = this.getWatchUrl(namespace);
|
const watchUrl = this.getWatchUrl(namespace);
|
||||||
const responsePromise = this.request.getResponse(watchUrl, null, {
|
const responsePromise = this.request.getResponse(watchUrl, null, {
|
||||||
signal: abortController.signal
|
signal: abortController.signal
|
||||||
@ -387,14 +393,17 @@ export class KubeApi<T extends KubeObject = any> {
|
|||||||
}
|
}
|
||||||
const nodeStream = new ReadableWebToNodeStream(response.body);
|
const nodeStream = new ReadableWebToNodeStream(response.body);
|
||||||
|
|
||||||
nodeStream.on("end", () => {
|
["end", "close", "error"].forEach((eventName) => {
|
||||||
if (errorReceived) return; // kubernetes errors should be handled in a callback
|
nodeStream.on(eventName, () => {
|
||||||
|
if (errorReceived) return; // kubernetes errors should be handled in a callback
|
||||||
|
|
||||||
setTimeout(() => { // we did not get any kubernetes errors so let's retry
|
clearTimeout(timedRetry);
|
||||||
if (abortController.signal.aborted) return;
|
timedRetry = setTimeout(() => { // we did not get any kubernetes errors so let's retry
|
||||||
|
if (abortController.signal.aborted) return;
|
||||||
|
|
||||||
this.watch({...opts, namespace, callback});
|
this.watch({...opts, namespace, callback});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const stream = byline(nodeStream);
|
const stream = byline(nodeStream);
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { TabLayout } from "../layout/tab-layout";
|
|||||||
import { EventStore, eventStore } from "./event.store";
|
import { EventStore, eventStore } from "./event.store";
|
||||||
import { getDetailsUrl, KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object";
|
import { getDetailsUrl, KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object";
|
||||||
import { KubeEvent } from "../../api/endpoints/events.api";
|
import { KubeEvent } from "../../api/endpoints/events.api";
|
||||||
import { TableSortCallbacks, TableSortParams } from "../table";
|
import { TableSortCallbacks, TableSortParams, TableProps } from "../table";
|
||||||
import { IHeaderPlaceholders } from "../item-object-list";
|
import { IHeaderPlaceholders } from "../item-object-list";
|
||||||
import { Tooltip } from "../tooltip";
|
import { Tooltip } from "../tooltip";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@ -41,6 +41,11 @@ const defaultProps: Partial<Props> = {
|
|||||||
export class Events extends React.Component<Props> {
|
export class Events extends React.Component<Props> {
|
||||||
static defaultProps = defaultProps as object;
|
static defaultProps = defaultProps as object;
|
||||||
|
|
||||||
|
@observable sorting: TableSortParams = {
|
||||||
|
sortBy: columnId.age,
|
||||||
|
orderBy: "asc",
|
||||||
|
};
|
||||||
|
|
||||||
private sortingCallbacks: TableSortCallbacks = {
|
private sortingCallbacks: TableSortCallbacks = {
|
||||||
[columnId.namespace]: (event: KubeEvent) => event.getNs(),
|
[columnId.namespace]: (event: KubeEvent) => event.getNs(),
|
||||||
[columnId.type]: (event: KubeEvent) => event.type,
|
[columnId.type]: (event: KubeEvent) => event.type,
|
||||||
@ -49,9 +54,10 @@ export class Events extends React.Component<Props> {
|
|||||||
[columnId.age]: (event: KubeEvent) => event.getTimeDiffFromNow(),
|
[columnId.age]: (event: KubeEvent) => event.getTimeDiffFromNow(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@observable sorting: TableSortParams = {
|
private tableConfiguration: TableProps = {
|
||||||
sortBy: columnId.age,
|
sortSyncWithUrl: false,
|
||||||
orderBy: "asc",
|
sortByDefault: this.sorting,
|
||||||
|
onSort: params => this.sorting = params,
|
||||||
};
|
};
|
||||||
|
|
||||||
get store(): EventStore {
|
get store(): EventStore {
|
||||||
@ -106,7 +112,7 @@ export class Events extends React.Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { store, visibleItems, sortingCallbacks, sorting } = this;
|
const { store, visibleItems } = this;
|
||||||
const { compact, compactLimit, className, ...layoutProps } = this.props;
|
const { compact, compactLimit, className, ...layoutProps } = this.props;
|
||||||
|
|
||||||
const events = (
|
const events = (
|
||||||
@ -121,12 +127,8 @@ export class Events extends React.Component<Props> {
|
|||||||
isSelectable={false}
|
isSelectable={false}
|
||||||
items={visibleItems}
|
items={visibleItems}
|
||||||
virtual={!compact}
|
virtual={!compact}
|
||||||
sortingCallbacks={sortingCallbacks}
|
tableProps={this.tableConfiguration}
|
||||||
tableProps={{
|
sortingCallbacks={this.sortingCallbacks}
|
||||||
sortSyncWithUrl: false,
|
|
||||||
sortByDefault: sorting,
|
|
||||||
onSort: params => this.sorting = params,
|
|
||||||
}}
|
|
||||||
searchFilters={[
|
searchFilters={[
|
||||||
(event: KubeEvent) => event.getSearchFields(),
|
(event: KubeEvent) => event.getSearchFields(),
|
||||||
(event: KubeEvent) => event.message,
|
(event: KubeEvent) => event.message,
|
||||||
@ -140,7 +142,7 @@ export class Events extends React.Component<Props> {
|
|||||||
{ title: "Involved Object", className: "object", sortBy: columnId.object, id: columnId.object },
|
{ title: "Involved Object", className: "object", sortBy: columnId.object, id: columnId.object },
|
||||||
{ title: "Source", className: "source", id: columnId.source },
|
{ title: "Source", className: "source", id: columnId.source },
|
||||||
{ title: "Count", className: "count", sortBy: columnId.count, id: columnId.count },
|
{ title: "Count", className: "count", sortBy: columnId.count, id: columnId.count },
|
||||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
{ title: "Last Seen", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||||
]}
|
]}
|
||||||
renderTableContents={(event: KubeEvent) => {
|
renderTableContents={(event: KubeEvent) => {
|
||||||
const { involvedObject, type, message } = event;
|
const { involvedObject, type, message } = event;
|
||||||
|
|||||||
@ -282,8 +282,8 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
const dialogCustomProps = customizeRemoveDialog ? customizeRemoveDialog(selectedItems) : {};
|
const dialogCustomProps = customizeRemoveDialog ? customizeRemoveDialog(selectedItems) : {};
|
||||||
const selectedCount = selectedItems.length;
|
const selectedCount = selectedItems.length;
|
||||||
const tailCount = selectedCount > visibleMaxNamesCount ? selectedCount - visibleMaxNamesCount : 0;
|
const tailCount = selectedCount > visibleMaxNamesCount ? selectedCount - visibleMaxNamesCount : 0;
|
||||||
const tail = tailCount > 0 ? "and <b>{tailCount}</b> more" : null;
|
const tail = tailCount > 0 ? <>, and <b>{tailCount}</b> more</> : null;
|
||||||
const message = selectedCount <= 1 ? <p>Remove item <b>{selectedNames}</b>?</p> : <p>Remove <b>{selectedCount}</b> items <b>{selectedNames}</b> {tail}?</p>;
|
const message = selectedCount <= 1 ? <p>Remove item <b>{selectedNames}</b>?</p> : <p>Remove <b>{selectedCount}</b> items <b>{selectedNames}</b>{tail}?</p>;
|
||||||
|
|
||||||
ConfirmDialog.open({
|
ConfirmDialog.open({
|
||||||
ok: removeSelectedItems,
|
ok: removeSelectedItems,
|
||||||
|
|||||||
@ -111,7 +111,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
|
|||||||
|
|
||||||
const isLoadingAll = this.context.allNamespaces.every(ns => namespaces.includes(ns));
|
const isLoadingAll = this.context.allNamespaces.every(ns => namespaces.includes(ns));
|
||||||
|
|
||||||
if (isLoadingAll) {
|
if (isLoadingAll && this.context.cluster.accessibleNamespaces.length === 0) {
|
||||||
this.loadedNamespaces = [];
|
this.loadedNamespaces = [];
|
||||||
|
|
||||||
return api.list({}, this.query);
|
return api.list({}, this.query);
|
||||||
|
|||||||
87
src/renderer/utils/readableStream.ts
Normal file
87
src/renderer/utils/readableStream.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { Readable } from "readable-stream";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ReadableWebToNodeStream
|
||||||
|
*
|
||||||
|
* Copied from https://github.com/Borewit/readable-web-to-node-stream
|
||||||
|
*
|
||||||
|
* Adds read error handler
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
export class ReadableWebToNodeStream extends Readable {
|
||||||
|
|
||||||
|
public bytesRead = 0;
|
||||||
|
public released = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default web API stream reader
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader
|
||||||
|
*/
|
||||||
|
private reader: ReadableStreamReader;
|
||||||
|
private pendingRead: Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param stream ReadableStream: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream
|
||||||
|
*/
|
||||||
|
constructor(stream: ReadableStream) {
|
||||||
|
super();
|
||||||
|
this.reader = stream.getReader();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of readable._read(size).
|
||||||
|
* When readable._read() is called, if data is available from the resource,
|
||||||
|
* the implementation should begin pushing that data into the read queue
|
||||||
|
* https://nodejs.org/api/stream.html#stream_readable_read_size_1
|
||||||
|
*/
|
||||||
|
public async _read() {
|
||||||
|
// Should start pushing data into the queue
|
||||||
|
// Read data from the underlying Web-API-readable-stream
|
||||||
|
if (this.released) {
|
||||||
|
this.push(null); // Signal EOF
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.pendingRead = this.reader.read();
|
||||||
|
const data = await this.pendingRead;
|
||||||
|
|
||||||
|
// clear the promise before pushing pushing new data to the queue and allow sequential calls to _read()
|
||||||
|
delete this.pendingRead;
|
||||||
|
|
||||||
|
if (data.done || this.released) {
|
||||||
|
this.push(null); // Signal EOF
|
||||||
|
} else {
|
||||||
|
this.bytesRead += data.value.length;
|
||||||
|
this.push(data.value); // Push new data to the queue
|
||||||
|
}
|
||||||
|
} catch(error) {
|
||||||
|
this.push(null); // Signal EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is no unresolved read call to Web-API ReadableStream immediately returns;
|
||||||
|
* otherwise will wait until the read is resolved.
|
||||||
|
*/
|
||||||
|
public async waitForReadToComplete() {
|
||||||
|
if (this.pendingRead) {
|
||||||
|
await this.pendingRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close wrapper
|
||||||
|
*/
|
||||||
|
public async close(): Promise<void> {
|
||||||
|
await this.syncAndRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncAndRelease() {
|
||||||
|
this.released = true;
|
||||||
|
await this.waitForReadToComplete();
|
||||||
|
await this.reader.releaseLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
||||||
|
|
||||||
## 4.1.0-rc.1 (current version)
|
## 4.1.0-rc.2 (current version)
|
||||||
|
|
||||||
- Change: list views default to a namespace (instead of listing resources from all namespaces)
|
- Change: list views default to a namespace (instead of listing resources from all namespaces)
|
||||||
- Command palette
|
- Command palette
|
||||||
|
|||||||
@ -11464,14 +11464,6 @@ readable-stream@~1.1.10:
|
|||||||
isarray "0.0.1"
|
isarray "0.0.1"
|
||||||
string_decoder "~0.10.x"
|
string_decoder "~0.10.x"
|
||||||
|
|
||||||
readable-web-to-node-stream@^3.0.1:
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.1.tgz#3f619b1bc5dd73a4cfe5c5f9b4f6faba55dff845"
|
|
||||||
integrity sha512-4zDC6CvjUyusN7V0QLsXVB7pJCD9+vtrM9bYDRv6uBQ+SKfx36rp5AFNPRgh9auKRul/a1iFZJYXcCbwRL+SaA==
|
|
||||||
dependencies:
|
|
||||||
"@types/readable-stream" "^2.3.9"
|
|
||||||
readable-stream "^3.6.0"
|
|
||||||
|
|
||||||
readdir-scoped-modules@^1.0.0, readdir-scoped-modules@^1.1.0:
|
readdir-scoped-modules@^1.0.0, readdir-scoped-modules@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
|
resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user