mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into fix-k0s-distribution-detection
This commit is contained in:
commit
6c93cb8c24
@ -10,7 +10,7 @@ jest.mock("chokidar", () => ({
|
|||||||
jest.mock("../extension-installer", () => ({
|
jest.mock("../extension-installer", () => ({
|
||||||
extensionInstaller: {
|
extensionInstaller: {
|
||||||
extensionPackagesRoot: "",
|
extensionPackagesRoot: "",
|
||||||
installPackages: jest.fn()
|
installPackage: jest.fn()
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ describe("ExtensionDiscovery", () => {
|
|||||||
// Need to force isLoaded to be true so that the file watching is started
|
// Need to force isLoaded to be true so that the file watching is started
|
||||||
extensionDiscovery.isLoaded = true;
|
extensionDiscovery.isLoaded = true;
|
||||||
|
|
||||||
await extensionDiscovery.initMain();
|
await extensionDiscovery.watchExtensions();
|
||||||
|
|
||||||
extensionDiscovery.events.on("add", (extension: InstalledExtension) => {
|
extensionDiscovery.events.on("add", (extension: InstalledExtension) => {
|
||||||
expect(extension).toEqual({
|
expect(extension).toEqual({
|
||||||
@ -81,7 +81,7 @@ describe("ExtensionDiscovery", () => {
|
|||||||
// Need to force isLoaded to be true so that the file watching is started
|
// Need to force isLoaded to be true so that the file watching is started
|
||||||
extensionDiscovery.isLoaded = true;
|
extensionDiscovery.isLoaded = true;
|
||||||
|
|
||||||
await extensionDiscovery.initMain();
|
await extensionDiscovery.watchExtensions();
|
||||||
|
|
||||||
const onAdd = jest.fn();
|
const onAdd = jest.fn();
|
||||||
|
|
||||||
|
|||||||
@ -55,6 +55,7 @@ export class ExtensionDiscovery {
|
|||||||
protected bundledFolderPath: string;
|
protected bundledFolderPath: string;
|
||||||
|
|
||||||
private loadStarted = false;
|
private loadStarted = false;
|
||||||
|
private extensions: Map<string, InstalledExtension> = new Map();
|
||||||
|
|
||||||
// True if extensions have been loaded from the disk after app startup
|
// True if extensions have been loaded from the disk after app startup
|
||||||
@observable isLoaded = false;
|
@observable isLoaded = false;
|
||||||
@ -69,13 +70,6 @@ export class ExtensionDiscovery {
|
|||||||
this.events = new EventEmitter();
|
this.events = new EventEmitter();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each extension is added as a single dependency to this object, which is written as package.json.
|
|
||||||
// Each dependency key is the name of the dependency, and
|
|
||||||
// each dependency value is the non-symlinked path to the dependency (folder).
|
|
||||||
protected packagesJson: PackageJson = {
|
|
||||||
dependencies: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
get localFolderPath(): string {
|
get localFolderPath(): string {
|
||||||
return path.join(os.homedir(), ".k8slens", "extensions");
|
return path.join(os.homedir(), ".k8slens", "extensions");
|
||||||
}
|
}
|
||||||
@ -119,7 +113,6 @@ export class ExtensionDiscovery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initMain() {
|
async initMain() {
|
||||||
this.watchExtensions();
|
|
||||||
handleRequest(ExtensionDiscovery.extensionDiscoveryChannel, () => this.toJSON());
|
handleRequest(ExtensionDiscovery.extensionDiscoveryChannel, () => this.toJSON());
|
||||||
|
|
||||||
reaction(() => this.toJSON(), () => {
|
reaction(() => this.toJSON(), () => {
|
||||||
@ -141,6 +134,7 @@ export class ExtensionDiscovery {
|
|||||||
watch(this.localFolderPath, {
|
watch(this.localFolderPath, {
|
||||||
// For adding and removing symlinks to work, the depth has to be 1.
|
// For adding and removing symlinks to work, the depth has to be 1.
|
||||||
depth: 1,
|
depth: 1,
|
||||||
|
ignoreInitial: true,
|
||||||
// Try to wait until the file has been completely copied.
|
// Try to wait until the file has been completely copied.
|
||||||
// The OS might emit an event for added file even it's not completely written to the filesysten.
|
// The OS might emit an event for added file even it's not completely written to the filesysten.
|
||||||
awaitWriteFinish: {
|
awaitWriteFinish: {
|
||||||
@ -176,8 +170,9 @@ export class ExtensionDiscovery {
|
|||||||
await this.removeSymlinkByManifestPath(manifestPath);
|
await this.removeSymlinkByManifestPath(manifestPath);
|
||||||
|
|
||||||
// Install dependencies for the new extension
|
// Install dependencies for the new extension
|
||||||
await this.installPackages();
|
await this.installPackage(extension.absolutePath);
|
||||||
|
|
||||||
|
this.extensions.set(extension.id, extension);
|
||||||
logger.info(`${logModule} Added extension ${extension.manifest.name}`);
|
logger.info(`${logModule} Added extension ${extension.manifest.name}`);
|
||||||
this.events.emit("add", extension);
|
this.events.emit("add", extension);
|
||||||
}
|
}
|
||||||
@ -197,23 +192,19 @@ export class ExtensionDiscovery {
|
|||||||
const extensionFolderName = path.basename(filePath);
|
const extensionFolderName = path.basename(filePath);
|
||||||
|
|
||||||
if (path.relative(this.localFolderPath, filePath) === extensionFolderName) {
|
if (path.relative(this.localFolderPath, filePath) === extensionFolderName) {
|
||||||
const extensionName: string | undefined = Object
|
const extension = Array.from(this.extensions.values()).find((extension) => extension.absolutePath === filePath);
|
||||||
.entries(this.packagesJson.dependencies)
|
|
||||||
.find(([, extensionFolder]) => filePath === extensionFolder)?.[0];
|
if (extension) {
|
||||||
|
const extensionName = extension.manifest.name;
|
||||||
|
|
||||||
if (extensionName !== undefined) {
|
|
||||||
// If the extension is deleted manually while the application is running, also remove the symlink
|
// If the extension is deleted manually while the application is running, also remove the symlink
|
||||||
await this.removeSymlinkByPackageName(extensionName);
|
await this.removeSymlinkByPackageName(extensionName);
|
||||||
|
|
||||||
delete this.packagesJson.dependencies[extensionName];
|
|
||||||
|
|
||||||
// Reinstall dependencies to remove the extension from package.json
|
|
||||||
await this.installPackages();
|
|
||||||
|
|
||||||
// The path to the manifest file is the lens extension id
|
// The path to the manifest file is the lens extension id
|
||||||
// Note that we need to use the symlinked path
|
// Note that we need to use the symlinked path
|
||||||
const lensExtensionId = path.join(this.nodeModulesPath, extensionName, manifestFilename);
|
const lensExtensionId = extension.manifestPath;
|
||||||
|
|
||||||
|
this.extensions.delete(extension.id);
|
||||||
logger.info(`${logModule} removed extension ${extensionName}`);
|
logger.info(`${logModule} removed extension ${extensionName}`);
|
||||||
this.events.emit("remove", lensExtensionId as LensExtensionId);
|
this.events.emit("remove", lensExtensionId as LensExtensionId);
|
||||||
} else {
|
} else {
|
||||||
@ -296,7 +287,7 @@ export class ExtensionDiscovery {
|
|||||||
await fs.ensureDir(this.nodeModulesPath);
|
await fs.ensureDir(this.nodeModulesPath);
|
||||||
await fs.ensureDir(this.localFolderPath);
|
await fs.ensureDir(this.localFolderPath);
|
||||||
|
|
||||||
const extensions = await this.loadExtensions();
|
const extensions = await this.ensureExtensions();
|
||||||
|
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
|
|
||||||
@ -335,7 +326,6 @@ export class ExtensionDiscovery {
|
|||||||
manifestJson = __non_webpack_require__(manifestPath);
|
manifestJson = __non_webpack_require__(manifestPath);
|
||||||
const installedManifestPath = this.getInstalledManifestPath(manifestJson.name);
|
const installedManifestPath = this.getInstalledManifestPath(manifestJson.name);
|
||||||
|
|
||||||
this.packagesJson.dependencies[manifestJson.name] = path.dirname(manifestPath);
|
|
||||||
const isEnabled = isBundled || extensionsStore.isEnabled(installedManifestPath);
|
const isEnabled = isBundled || extensionsStore.isEnabled(installedManifestPath);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -347,29 +337,46 @@ export class ExtensionDiscovery {
|
|||||||
isEnabled
|
isEnabled
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`${logModule}: can't install extension at ${manifestPath}: ${error}`, { manifestJson });
|
logger.error(`${logModule}: can't load extension manifest at ${manifestPath}: ${error}`, { manifestJson });
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadExtensions(): Promise<Map<LensExtensionId, InstalledExtension>> {
|
async ensureExtensions(): Promise<Map<LensExtensionId, InstalledExtension>> {
|
||||||
const bundledExtensions = await this.loadBundledExtensions();
|
const bundledExtensions = await this.loadBundledExtensions();
|
||||||
|
|
||||||
await this.installPackages(); // install in-tree as a separate step
|
await this.installBundledPackages(this.packageJsonPath, bundledExtensions);
|
||||||
const localExtensions = await this.loadFromFolder(this.localFolderPath);
|
|
||||||
|
|
||||||
await this.installPackages();
|
const userExtensions = await this.loadFromFolder(this.localFolderPath);
|
||||||
const extensions = bundledExtensions.concat(localExtensions);
|
|
||||||
|
|
||||||
return new Map(extensions.map(extension => [extension.id, extension]));
|
for (const extension of userExtensions) {
|
||||||
|
if (await fs.pathExists(extension.manifestPath) === false) {
|
||||||
|
await this.installPackage(extension.absolutePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const extensions = bundledExtensions.concat(userExtensions);
|
||||||
|
|
||||||
|
return this.extensions = new Map(extensions.map(extension => [extension.id, extension]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write package.json to file system and install dependencies.
|
* Write package.json to file system and install dependencies.
|
||||||
*/
|
*/
|
||||||
installPackages() {
|
async installBundledPackages(packageJsonPath: string, extensions: InstalledExtension[]) {
|
||||||
return extensionInstaller.installPackages(this.packageJsonPath, this.packagesJson);
|
const packagesJson: PackageJson = {
|
||||||
|
dependencies: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
extensions.forEach((extension) => {
|
||||||
|
packagesJson.dependencies[extension.manifest.name] = extension.absolutePath;
|
||||||
|
});
|
||||||
|
|
||||||
|
return await extensionInstaller.installPackages(packageJsonPath, packagesJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
async installPackage(name: string) {
|
||||||
|
return extensionInstaller.installPackage(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadBundledExtensions() {
|
async loadBundledExtensions() {
|
||||||
|
|||||||
@ -30,12 +30,49 @@ export class ExtensionInstaller {
|
|||||||
return __non_webpack_require__.resolve("npm/bin/npm-cli");
|
return __non_webpack_require__.resolve("npm/bin/npm-cli");
|
||||||
}
|
}
|
||||||
|
|
||||||
installDependencies(): Promise<void> {
|
/**
|
||||||
return new Promise((resolve, reject) => {
|
* Write package.json to the file system and execute npm install for it.
|
||||||
|
*/
|
||||||
|
async installPackages(packageJsonPath: string, packagesJson: PackageJson): Promise<void> {
|
||||||
|
// Mutual exclusion to install packages in sequence
|
||||||
|
await this.installLock.acquireAsync();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Write the package.json which will be installed in .installDependencies()
|
||||||
|
await fs.writeFile(path.join(packageJsonPath), JSON.stringify(packagesJson, null, 2), {
|
||||||
|
mode: 0o600
|
||||||
|
});
|
||||||
|
|
||||||
logger.info(`${logModule} installing dependencies at ${extensionPackagesRoot()}`);
|
logger.info(`${logModule} installing dependencies at ${extensionPackagesRoot()}`);
|
||||||
const child = child_process.fork(this.npmPath, ["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock"], {
|
await this.npm(["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock"]);
|
||||||
|
logger.info(`${logModule} dependencies installed at ${extensionPackagesRoot()}`);
|
||||||
|
} finally {
|
||||||
|
this.installLock.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install single package using npm
|
||||||
|
*/
|
||||||
|
async installPackage(name: string): Promise<void> {
|
||||||
|
// Mutual exclusion to install packages in sequence
|
||||||
|
await this.installLock.acquireAsync();
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info(`${logModule} installing package from ${name} to ${extensionPackagesRoot()}`);
|
||||||
|
await this.npm(["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock", "--no-save", name]);
|
||||||
|
logger.info(`${logModule} package ${name} installed to ${extensionPackagesRoot()}`);
|
||||||
|
} finally {
|
||||||
|
this.installLock.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private npm(args: string[]): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = child_process.fork(this.npmPath, args, {
|
||||||
cwd: extensionPackagesRoot(),
|
cwd: extensionPackagesRoot(),
|
||||||
silent: true
|
silent: true,
|
||||||
|
env: {}
|
||||||
});
|
});
|
||||||
let stderr = "";
|
let stderr = "";
|
||||||
|
|
||||||
@ -56,25 +93,6 @@ export class ExtensionInstaller {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Write package.json to the file system and execute npm install for it.
|
|
||||||
*/
|
|
||||||
async installPackages(packageJsonPath: string, packagesJson: PackageJson): Promise<void> {
|
|
||||||
// Mutual exclusion to install packages in sequence
|
|
||||||
await this.installLock.acquireAsync();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Write the package.json which will be installed in .installDependencies()
|
|
||||||
await fs.writeFile(path.join(packageJsonPath), JSON.stringify(packagesJson, null, 2), {
|
|
||||||
mode: 0o600
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.installDependencies();
|
|
||||||
} finally {
|
|
||||||
this.installLock.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extensionInstaller = new ExtensionInstaller();
|
export const extensionInstaller = new ExtensionInstaller();
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import type { LensExtension, LensExtensionConstructor, LensExtensionId } from ".
|
|||||||
import type { LensMainExtension } from "./lens-main-extension";
|
import type { LensMainExtension } from "./lens-main-extension";
|
||||||
import type { LensRendererExtension } from "./lens-renderer-extension";
|
import type { LensRendererExtension } from "./lens-renderer-extension";
|
||||||
import * as registries from "./registries";
|
import * as registries from "./registries";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
// lazy load so that we get correct userData
|
// lazy load so that we get correct userData
|
||||||
export function extensionPackagesRoot() {
|
export function extensionPackagesRoot() {
|
||||||
@ -71,7 +72,7 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([this.whenLoaded, extensionsStore.whenLoaded]);
|
await Promise.all([this.whenLoaded, extensionsStore.whenLoaded]);
|
||||||
|
|
||||||
// save state on change `extension.isEnabled`
|
// save state on change `extension.isEnabled`
|
||||||
reaction(() => this.storeState, extensionsState => {
|
reaction(() => this.storeState, extensionsState => {
|
||||||
extensionsStore.mergeState(extensionsState);
|
extensionsStore.mergeState(extensionsState);
|
||||||
@ -115,7 +116,6 @@ export class ExtensionLoader {
|
|||||||
protected async initMain() {
|
protected async initMain() {
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
this.loadOnMain();
|
this.loadOnMain();
|
||||||
this.broadcastExtensions();
|
|
||||||
|
|
||||||
reaction(() => this.toJSON(), () => {
|
reaction(() => this.toJSON(), () => {
|
||||||
this.broadcastExtensions();
|
this.broadcastExtensions();
|
||||||
@ -136,7 +136,7 @@ export class ExtensionLoader {
|
|||||||
this.syncExtensions(extensions);
|
this.syncExtensions(extensions);
|
||||||
|
|
||||||
const receivedExtensionIds = extensions.map(([lensExtensionId]) => lensExtensionId);
|
const receivedExtensionIds = extensions.map(([lensExtensionId]) => lensExtensionId);
|
||||||
|
|
||||||
// Remove deleted extensions in renderer side only
|
// Remove deleted extensions in renderer side only
|
||||||
this.extensions.forEach((_, lensExtensionId) => {
|
this.extensions.forEach((_, lensExtensionId) => {
|
||||||
if (!receivedExtensionIds.includes(lensExtensionId)) {
|
if (!receivedExtensionIds.includes(lensExtensionId)) {
|
||||||
@ -276,6 +276,12 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (extEntrypoint !== "") {
|
if (extEntrypoint !== "") {
|
||||||
|
if (!fs.existsSync(extEntrypoint)) {
|
||||||
|
console.log(`${logModule}: entrypoint ${extEntrypoint} not found, skipping ...`);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return __non_webpack_require__(extEntrypoint).default;
|
return __non_webpack_require__(extEntrypoint).default;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -39,15 +39,27 @@ export class DistributionDetector extends BaseClusterDetector {
|
|||||||
if (this.isK0s()) {
|
if (this.isK0s()) {
|
||||||
return { value: "k0s", accuracy: 80};
|
return { value: "k0s", accuracy: 80};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isVMWare()) {
|
||||||
|
return { value: "vmware", accuracy: 90};
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isMirantis()) {
|
if (this.isMirantis()) {
|
||||||
return { value: "mirantis", accuracy: 90};
|
return { value: "mirantis", accuracy: 90};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isAlibaba()) {
|
||||||
|
return { value: "alibaba", accuracy: 90};
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isHuawei()) {
|
if (this.isHuawei()) {
|
||||||
return { value: "huawei", accuracy: 90};
|
return { value: "huawei", accuracy: 90};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isTke()) {
|
||||||
|
return { value: "tencent", accuracy: 90};
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isMinikube()) {
|
if (this.isMinikube()) {
|
||||||
return { value: "minikube", accuracy: 80};
|
return { value: "minikube", accuracy: 80};
|
||||||
}
|
}
|
||||||
@ -123,10 +135,18 @@ export class DistributionDetector extends BaseClusterDetector {
|
|||||||
return this.cluster.contextName === "docker-desktop";
|
return this.cluster.contextName === "docker-desktop";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected isTke() {
|
||||||
|
return this.version.includes("-tke.");
|
||||||
|
}
|
||||||
|
|
||||||
protected isCustom() {
|
protected isCustom() {
|
||||||
return this.version.includes("+");
|
return this.version.includes("+");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected isVMWare() {
|
||||||
|
return this.version.includes("+vmware");
|
||||||
|
}
|
||||||
|
|
||||||
protected isRke() {
|
protected isRke() {
|
||||||
return this.version.includes("-rancher");
|
return this.version.includes("-rancher");
|
||||||
}
|
}
|
||||||
@ -138,6 +158,10 @@ export class DistributionDetector extends BaseClusterDetector {
|
|||||||
protected isK0s() {
|
protected isK0s() {
|
||||||
return this.version.includes("-k0s");
|
return this.version.includes("-k0s");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected isAlibaba() {
|
||||||
|
return this.version.includes("-aliyun");
|
||||||
|
}
|
||||||
|
|
||||||
protected isHuawei() {
|
protected isHuawei() {
|
||||||
return this.version.includes("-CCE");
|
return this.version.includes("-CCE");
|
||||||
|
|||||||
@ -103,7 +103,6 @@ app.on("ready", async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extensionLoader.init();
|
extensionLoader.init();
|
||||||
|
|
||||||
extensionDiscovery.init();
|
extensionDiscovery.init();
|
||||||
windowManager = WindowManager.getInstance<WindowManager>(proxyPort);
|
windowManager = WindowManager.getInstance<WindowManager>(proxyPort);
|
||||||
|
|
||||||
@ -111,6 +110,9 @@ app.on("ready", async () => {
|
|||||||
try {
|
try {
|
||||||
const extensions = await extensionDiscovery.load();
|
const extensions = await extensionDiscovery.load();
|
||||||
|
|
||||||
|
// Start watching after bundled extensions are loaded
|
||||||
|
extensionDiscovery.watchExtensions();
|
||||||
|
|
||||||
// Subscribe to extensions that are copied or deleted to/from the extensions folder
|
// Subscribe to extensions that are copied or deleted to/from the extensions folder
|
||||||
extensionDiscovery.events.on("add", (extension: InstalledExtension) => {
|
extensionDiscovery.events.on("add", (extension: InstalledExtension) => {
|
||||||
extensionLoader.addExtension(extension);
|
extensionLoader.addExtension(extension);
|
||||||
@ -122,6 +124,8 @@ app.on("ready", async () => {
|
|||||||
extensionLoader.initExtensions(extensions);
|
extensionLoader.initExtensions(extensions);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dialog.showErrorBox("Lens Error", `Could not load extensions${error?.message ? `: ${error.message}` : ""}`);
|
dialog.showErrorBox("Lens Error", `Could not load extensions${error?.message ? `: ${error.message}` : ""}`);
|
||||||
|
console.error(error);
|
||||||
|
console.trace();
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ const kubectlMap: Map<string, string> = new Map([
|
|||||||
["1.15", "1.15.11"],
|
["1.15", "1.15.11"],
|
||||||
["1.16", "1.16.15"],
|
["1.16", "1.16.15"],
|
||||||
["1.17", bundledVersion],
|
["1.17", bundledVersion],
|
||||||
["1.18", "1.18.15"],
|
["1.18", "1.18.14"],
|
||||||
["1.19", "1.19.5"],
|
["1.19", "1.19.5"],
|
||||||
["1.20", "1.20.0"]
|
["1.20", "1.20.0"]
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -120,6 +120,7 @@ export class ShellSession extends EventEmitter {
|
|||||||
if(path.basename(env["PTYSHELL"]) === "zsh") {
|
if(path.basename(env["PTYSHELL"]) === "zsh") {
|
||||||
env["OLD_ZDOTDIR"] = env.ZDOTDIR || env.HOME;
|
env["OLD_ZDOTDIR"] = env.ZDOTDIR || env.HOME;
|
||||||
env["ZDOTDIR"] = this.kubectlBinDir;
|
env["ZDOTDIR"] = this.kubectlBinDir;
|
||||||
|
env["DISABLE_AUTO_UPDATE"] = "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
env["PTYPID"] = process.pid.toString();
|
env["PTYPID"] = process.pid.toString();
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store";
|
|||||||
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
|
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
|
||||||
import { jobStore } from "../+workloads-jobs/job.store";
|
import { jobStore } from "../+workloads-jobs/job.store";
|
||||||
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
|
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
|
||||||
import { Spinner } from "../spinner";
|
|
||||||
import { Events } from "../+events";
|
import { Events } from "../+events";
|
||||||
import { KubeObjectStore } from "../../kube-object.store";
|
import { KubeObjectStore } from "../../kube-object.store";
|
||||||
import { isAllowedResource } from "../../../common/rbac";
|
import { isAllowedResource } from "../../../common/rbac";
|
||||||
@ -24,7 +23,6 @@ interface Props extends RouteComponentProps<IWorkloadsOverviewRouteParams> {
|
|||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class WorkloadsOverview extends React.Component<Props> {
|
export class WorkloadsOverview extends React.Component<Props> {
|
||||||
@observable isReady = false;
|
|
||||||
@observable isUnmounting = false;
|
@observable isUnmounting = false;
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
@ -61,10 +59,13 @@ export class WorkloadsOverview extends React.Component<Props> {
|
|||||||
if (isAllowedResource("events")) {
|
if (isAllowedResource("events")) {
|
||||||
stores.push(eventStore);
|
stores.push(eventStore);
|
||||||
}
|
}
|
||||||
this.isReady = stores.every(store => store.isLoaded);
|
|
||||||
await Promise.all(stores.map(store => store.loadAll()));
|
const unsubscribeList: Array<() => void> = [];
|
||||||
this.isReady = true;
|
|
||||||
const unsubscribeList = stores.map(store => store.subscribe());
|
for (const store of stores) {
|
||||||
|
await store.loadAll();
|
||||||
|
unsubscribeList.push(store.subscribe());
|
||||||
|
}
|
||||||
|
|
||||||
await when(() => this.isUnmounting);
|
await when(() => this.isUnmounting);
|
||||||
unsubscribeList.forEach(dispose => dispose());
|
unsubscribeList.forEach(dispose => dispose());
|
||||||
@ -74,11 +75,7 @@ export class WorkloadsOverview extends React.Component<Props> {
|
|||||||
this.isUnmounting = true;
|
this.isUnmounting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContents() {
|
get contents() {
|
||||||
if (!this.isReady) {
|
|
||||||
return <Spinner center/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<OverviewStatuses/>
|
<OverviewStatuses/>
|
||||||
@ -94,7 +91,7 @@ export class WorkloadsOverview extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="WorkloadsOverview flex column gaps">
|
<div className="WorkloadsOverview flex column gaps">
|
||||||
{this.renderContents()}
|
{this.contents}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ interface OptionalProps {
|
|||||||
showSubmitClose?: boolean;
|
showSubmitClose?: boolean;
|
||||||
showInlineInfo?: boolean;
|
showInlineInfo?: boolean;
|
||||||
showNotifications?: boolean;
|
showNotifications?: boolean;
|
||||||
|
showStatusPanel?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -38,6 +39,7 @@ export class InfoPanel extends Component<Props> {
|
|||||||
showSubmitClose: true,
|
showSubmitClose: true,
|
||||||
showInlineInfo: true,
|
showInlineInfo: true,
|
||||||
showNotifications: true,
|
showNotifications: true,
|
||||||
|
showStatusPanel: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@observable error = "";
|
@observable error = "";
|
||||||
@ -93,7 +95,7 @@ export class InfoPanel extends Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, controls, submitLabel, disableSubmit, error, submittingMessage, showButtons, showSubmitClose } = this.props;
|
const { className, controls, submitLabel, disableSubmit, error, submittingMessage, showButtons, showSubmitClose, showStatusPanel } = this.props;
|
||||||
const { submit, close, submitAndClose, waiting } = this;
|
const { submit, close, submitAndClose, waiting } = this;
|
||||||
const isDisabled = !!(disableSubmit || waiting || error);
|
const isDisabled = !!(disableSubmit || waiting || error);
|
||||||
|
|
||||||
@ -102,9 +104,11 @@ export class InfoPanel extends Component<Props> {
|
|||||||
<div className="controls">
|
<div className="controls">
|
||||||
{controls}
|
{controls}
|
||||||
</div>
|
</div>
|
||||||
<div className="info flex gaps align-center">
|
{showStatusPanel && (
|
||||||
{waiting ? <><Spinner /> {submittingMessage}</> : this.renderErrorIcon()}
|
<div className="flex gaps align-center">
|
||||||
</div>
|
{waiting ? <><Spinner /> {submittingMessage}</> : this.renderErrorIcon()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{showButtons && (
|
{showButtons && (
|
||||||
<>
|
<>
|
||||||
<Button plain label={<Trans>Cancel</Trans>} onClick={close} />
|
<Button plain label={<Trans>Cancel</Trans>} onClick={close} />
|
||||||
|
|||||||
@ -22,10 +22,9 @@ interface Props extends PodLogSearchProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const PodLogControls = observer((props: Props) => {
|
export const PodLogControls = observer((props: Props) => {
|
||||||
const { tabData, save, reload, tabId, logs } = props;
|
const { tabData, save, reload, logs } = props;
|
||||||
const { selectedContainer, showTimestamps, previous } = tabData;
|
const { selectedContainer, showTimestamps, previous } = tabData;
|
||||||
const rawLogs = podLogsStore.logs.get(tabId) || [];
|
const since = logs.length ? podLogsStore.getTimestamps(logs[0]) : null;
|
||||||
const since = rawLogs.length ? podLogsStore.getTimestamps(rawLogs[0]) : null;
|
|
||||||
const pod = new Pod(tabData.pod);
|
const pod = new Pod(tabData.pod);
|
||||||
|
|
||||||
const toggleTimestamps = () => {
|
const toggleTimestamps = () => {
|
||||||
@ -39,8 +38,9 @@ export const PodLogControls = observer((props: Props) => {
|
|||||||
|
|
||||||
const downloadLogs = () => {
|
const downloadLogs = () => {
|
||||||
const fileName = selectedContainer ? selectedContainer.name : pod.getName();
|
const fileName = selectedContainer ? selectedContainer.name : pod.getName();
|
||||||
|
const logsToDownload = showTimestamps ? logs : podLogsStore.logsWithoutTimestamps;
|
||||||
|
|
||||||
saveFileDialog(`${fileName}.log`, logs.join("\n"), "text/plain");
|
saveFileDialog(`${fileName}.log`, logsToDownload.join("\n"), "text/plain");
|
||||||
};
|
};
|
||||||
|
|
||||||
const onContainerChange = (option: SelectOption) => {
|
const onContainerChange = (option: SelectOption) => {
|
||||||
@ -118,7 +118,10 @@ export const PodLogControls = observer((props: Props) => {
|
|||||||
tooltip={_i18n._(t`Save`)}
|
tooltip={_i18n._(t`Save`)}
|
||||||
className="download-icon"
|
className="download-icon"
|
||||||
/>
|
/>
|
||||||
<PodLogSearch {...props} />
|
<PodLogSearch
|
||||||
|
{...props}
|
||||||
|
logs={showTimestamps ? logs : podLogsStore.logsWithoutTimestamps}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import AnsiUp from "ansi_up";
|
|||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import debounce from "lodash/debounce";
|
import debounce from "lodash/debounce";
|
||||||
import { Trans } from "@lingui/macro";
|
import { Trans } from "@lingui/macro";
|
||||||
import { action, observable } from "mobx";
|
import { action, computed, observable } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Align, ListOnScrollProps } from "react-window";
|
import { Align, ListOnScrollProps } from "react-window";
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ import { Button } from "../button";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import { VirtualList } from "../virtual-list";
|
import { VirtualList } from "../virtual-list";
|
||||||
import { logRange } from "./pod-logs.store";
|
import { podLogsStore } from "./pod-logs.store";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
logs: string[]
|
logs: string[]
|
||||||
@ -47,23 +47,25 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logs == prevProps.logs || !this.virtualListDiv.current) return;
|
if (logs == prevProps.logs || !this.virtualListDiv.current) return;
|
||||||
|
|
||||||
const newLogsLoaded = prevProps.logs.length < logs.length;
|
const newLogsLoaded = prevProps.logs.length < logs.length;
|
||||||
const scrolledToBeginning = this.virtualListDiv.current.scrollTop === 0;
|
const scrolledToBeginning = this.virtualListDiv.current.scrollTop === 0;
|
||||||
const fewLogsLoaded = logs.length < logRange;
|
|
||||||
|
|
||||||
if (this.isLastLineVisible) {
|
if (this.isLastLineVisible || prevProps.logs.length == 0) {
|
||||||
this.scrollToBottom(); // Scroll down to keep user watching/reading experience
|
this.scrollToBottom(); // Scroll down to keep user watching/reading experience
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scrolledToBeginning && newLogsLoaded) {
|
if (scrolledToBeginning && newLogsLoaded) {
|
||||||
this.virtualListDiv.current.scrollTop = (logs.length - prevProps.logs.length) * this.lineHeight;
|
const firstLineContents = prevProps.logs[0];
|
||||||
}
|
const lineToScroll = this.props.logs.findIndex((value) => value == firstLineContents);
|
||||||
|
|
||||||
if (fewLogsLoaded) {
|
if (lineToScroll !== -1) {
|
||||||
this.isJumpButtonVisible = false;
|
this.scrollToItem(lineToScroll, "start");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!logs.length) {
|
if (!logs.length) {
|
||||||
@ -71,6 +73,20 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns logs with or without timestamps regarding to showTimestamps prop
|
||||||
|
*/
|
||||||
|
@computed
|
||||||
|
get logs() {
|
||||||
|
const showTimestamps = podLogsStore.getData(this.props.id).showTimestamps;
|
||||||
|
|
||||||
|
if (!showTimestamps) {
|
||||||
|
return podLogsStore.logsWithoutTimestamps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.logs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if JumpToBottom button should be visible and sets its observable
|
* Checks if JumpToBottom button should be visible and sets its observable
|
||||||
* @param props Scrolling props from virtual list core
|
* @param props Scrolling props from virtual list core
|
||||||
@ -115,7 +131,6 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
@action
|
@action
|
||||||
scrollToBottom = () => {
|
scrollToBottom = () => {
|
||||||
if (!this.virtualListDiv.current) return;
|
if (!this.virtualListDiv.current) return;
|
||||||
this.isJumpButtonVisible = false;
|
|
||||||
this.virtualListDiv.current.scrollTop = this.virtualListDiv.current.scrollHeight;
|
this.virtualListDiv.current.scrollTop = this.virtualListDiv.current.scrollHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -123,7 +138,13 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
this.virtualListRef.current.scrollToItem(index, align);
|
this.virtualListRef.current.scrollToItem(index, align);
|
||||||
};
|
};
|
||||||
|
|
||||||
onScroll = debounce((props: ListOnScrollProps) => {
|
onScroll = (props: ListOnScrollProps) => {
|
||||||
|
if (!this.virtualListDiv.current) return;
|
||||||
|
this.isLastLineVisible = false;
|
||||||
|
this.onScrollDebounced(props);
|
||||||
|
};
|
||||||
|
|
||||||
|
onScrollDebounced = debounce((props: ListOnScrollProps) => {
|
||||||
if (!this.virtualListDiv.current) return;
|
if (!this.virtualListDiv.current) return;
|
||||||
this.setButtonVisibility(props);
|
this.setButtonVisibility(props);
|
||||||
this.setLastLineVisibility(props);
|
this.setLastLineVisibility(props);
|
||||||
@ -137,7 +158,7 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
*/
|
*/
|
||||||
getLogRow = (rowIndex: number) => {
|
getLogRow = (rowIndex: number) => {
|
||||||
const { searchQuery, isActiveOverlay } = searchStore;
|
const { searchQuery, isActiveOverlay } = searchStore;
|
||||||
const item = this.props.logs[rowIndex];
|
const item = this.logs[rowIndex];
|
||||||
const contents: React.ReactElement[] = [];
|
const contents: React.ReactElement[] = [];
|
||||||
const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi));
|
const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi));
|
||||||
|
|
||||||
@ -174,20 +195,22 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
{contents.length > 1 ? contents : (
|
{contents.length > 1 ? contents : (
|
||||||
<span dangerouslySetInnerHTML={{ __html: ansiToHtml(item) }} />
|
<span dangerouslySetInnerHTML={{ __html: ansiToHtml(item) }} />
|
||||||
)}
|
)}
|
||||||
|
{/* For preserving copy-paste experience and keeping line breaks */}
|
||||||
|
<br />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { logs, isLoading } = this.props;
|
const { isLoading } = this.props;
|
||||||
const isInitLoading = isLoading && !logs.length;
|
const isInitLoading = isLoading && !this.logs.length;
|
||||||
const rowHeights = new Array(logs.length).fill(this.lineHeight);
|
const rowHeights = new Array(this.logs.length).fill(this.lineHeight);
|
||||||
|
|
||||||
if (isInitLoading) {
|
if (isInitLoading) {
|
||||||
return <Spinner center/>;
|
return <Spinner center/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!logs.length) {
|
if (!this.logs.length) {
|
||||||
return (
|
return (
|
||||||
<div className="PodLogList flex box grow align-center justify-center">
|
<div className="PodLogList flex box grow align-center justify-center">
|
||||||
<Trans>There are no logs available for container</Trans>
|
<Trans>There are no logs available for container</Trans>
|
||||||
@ -198,7 +221,7 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<div className={cssNames("PodLogList flex", { isLoading })}>
|
<div className={cssNames("PodLogList flex", { isLoading })}>
|
||||||
<VirtualList
|
<VirtualList
|
||||||
items={logs}
|
items={this.logs}
|
||||||
rowHeights={rowHeights}
|
rowHeights={rowHeights}
|
||||||
getRow={this.getLogRow}
|
getRow={this.getLogRow}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
|
|||||||
@ -12,10 +12,13 @@ export interface PodLogSearchProps {
|
|||||||
onSearch: (query: string) => void
|
onSearch: (query: string) => void
|
||||||
toPrevOverlay: () => void
|
toPrevOverlay: () => void
|
||||||
toNextOverlay: () => void
|
toNextOverlay: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props extends PodLogSearchProps {
|
||||||
logs: string[]
|
logs: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PodLogSearch = observer((props: PodLogSearchProps) => {
|
export const PodLogSearch = observer((props: Props) => {
|
||||||
const { logs, onSearch, toPrevOverlay, toNextOverlay } = props;
|
const { logs, onSearch, toPrevOverlay, toNextOverlay } = props;
|
||||||
const { setNextOverlayActive, setPrevOverlayActive, searchQuery, occurrences, activeFind, totalFinds } = searchStore;
|
const { setNextOverlayActive, setPrevOverlayActive, searchQuery, occurrences, activeFind, totalFinds } = searchStore;
|
||||||
const jumpDisabled = !searchQuery || !occurrences.length;
|
const jumpDisabled = !searchQuery || !occurrences.length;
|
||||||
|
|||||||
@ -27,11 +27,11 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
private refresher = interval(10, () => {
|
private refresher = interval(10, () => {
|
||||||
const id = dockStore.selectedTabId;
|
const id = dockStore.selectedTabId;
|
||||||
|
|
||||||
if (!this.logs.get(id)) return;
|
if (!this.podLogs.get(id)) return;
|
||||||
this.loadMore(id);
|
this.loadMore(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
@observable logs = observable.map<TabId, PodLogLine[]>();
|
@observable podLogs = observable.map<TabId, PodLogLine[]>();
|
||||||
@observable newLogSince = observable.map<TabId, string>(); // Timestamp after which all logs are considered to be new
|
@observable newLogSince = observable.map<TabId, string>(); // Timestamp after which all logs are considered to be new
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -48,7 +48,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
}
|
}
|
||||||
}, { delay: 500 });
|
}, { delay: 500 });
|
||||||
|
|
||||||
reaction(() => this.logs.get(dockStore.selectedTabId), () => {
|
reaction(() => this.podLogs.get(dockStore.selectedTabId), () => {
|
||||||
this.setNewLogSince(dockStore.selectedTabId);
|
this.setNewLogSince(dockStore.selectedTabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.refresher.start();
|
this.refresher.start();
|
||||||
this.logs.set(tabId, logs);
|
this.podLogs.set(tabId, logs);
|
||||||
} catch ({error}) {
|
} catch ({error}) {
|
||||||
const message = [
|
const message = [
|
||||||
_i18n._(t`Failed to load logs: ${error.message}`),
|
_i18n._(t`Failed to load logs: ${error.message}`),
|
||||||
@ -80,7 +80,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
this.refresher.stop();
|
this.refresher.stop();
|
||||||
this.logs.set(tabId, message);
|
this.podLogs.set(tabId, message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -91,14 +91,14 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
* @param tabId
|
* @param tabId
|
||||||
*/
|
*/
|
||||||
loadMore = async (tabId: TabId) => {
|
loadMore = async (tabId: TabId) => {
|
||||||
if (!this.logs.get(tabId).length) return;
|
if (!this.podLogs.get(tabId).length) return;
|
||||||
const oldLogs = this.logs.get(tabId);
|
const oldLogs = this.podLogs.get(tabId);
|
||||||
const logs = await this.loadLogs(tabId, {
|
const logs = await this.loadLogs(tabId, {
|
||||||
sinceTime: this.getLastSinceTime(tabId)
|
sinceTime: this.getLastSinceTime(tabId)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add newly received logs to bottom
|
// Add newly received logs to bottom
|
||||||
this.logs.set(tabId, [...oldLogs, ...logs]);
|
this.podLogs.set(tabId, [...oldLogs, ...logs]);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,7 +134,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
* @param tabId
|
* @param tabId
|
||||||
*/
|
*/
|
||||||
setNewLogSince(tabId: TabId) {
|
setNewLogSince(tabId: TabId) {
|
||||||
if (!this.logs.has(tabId) || !this.logs.get(tabId).length || this.newLogSince.has(tabId)) return;
|
if (!this.podLogs.has(tabId) || !this.podLogs.get(tabId).length || this.newLogSince.has(tabId)) return;
|
||||||
const timestamp = this.getLastSinceTime(tabId);
|
const timestamp = this.getLastSinceTime(tabId);
|
||||||
|
|
||||||
this.newLogSince.set(tabId, timestamp.split(".")[0]); // Removing milliseconds from string
|
this.newLogSince.set(tabId, timestamp.split(".")[0]); // Removing milliseconds from string
|
||||||
@ -147,18 +147,38 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
@computed
|
@computed
|
||||||
get lines() {
|
get lines() {
|
||||||
const id = dockStore.selectedTabId;
|
const id = dockStore.selectedTabId;
|
||||||
const logs = this.logs.get(id);
|
const logs = this.podLogs.get(id);
|
||||||
|
|
||||||
return logs ? logs.length : 0;
|
return logs ? logs.length : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns logs with timestamps for selected tab
|
||||||
|
*/
|
||||||
|
get logs() {
|
||||||
|
const id = dockStore.selectedTabId;
|
||||||
|
|
||||||
|
if (!this.podLogs.has(id)) return [];
|
||||||
|
|
||||||
|
return this.podLogs.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes timestamps from each log line and returns changed logs
|
||||||
|
* @returns Logs without timestamps
|
||||||
|
*/
|
||||||
|
get logsWithoutTimestamps() {
|
||||||
|
return this.logs.map(item => this.removeTimestamps(item));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It gets timestamps from all logs then returns last one + 1 second
|
* It gets timestamps from all logs then returns last one + 1 second
|
||||||
* (this allows to avoid getting the last stamp in the selection)
|
* (this allows to avoid getting the last stamp in the selection)
|
||||||
* @param tabId
|
* @param tabId
|
||||||
*/
|
*/
|
||||||
getLastSinceTime(tabId: TabId) {
|
getLastSinceTime(tabId: TabId) {
|
||||||
const logs = this.logs.get(tabId);
|
const logs = this.podLogs.get(tabId);
|
||||||
const timestamps = this.getTimestamps(logs[logs.length - 1]);
|
const timestamps = this.getTimestamps(logs[logs.length - 1]);
|
||||||
const stamp = new Date(timestamps ? timestamps[0] : null);
|
const stamp = new Date(timestamps ? timestamps[0] : null);
|
||||||
|
|
||||||
@ -176,7 +196,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearLogs(tabId: TabId) {
|
clearLogs(tabId: TabId) {
|
||||||
this.logs.delete(tabId);
|
this.podLogs.delete(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearData(tabId: TabId) {
|
clearData(tabId: TabId) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { computed, observable, reaction } from "mobx";
|
import { observable, reaction } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
|
|
||||||
import { searchStore } from "../../../common/search-store";
|
import { searchStore } from "../../../common/search-store";
|
||||||
@ -79,31 +79,15 @@ export class PodLogs extends React.Component<Props> {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Computed prop which returns logs with or without timestamps added to each line
|
|
||||||
* @returns {Array} An array log items
|
|
||||||
*/
|
|
||||||
@computed
|
|
||||||
get logs(): string[] {
|
|
||||||
if (!podLogsStore.logs.has(this.tabId)) return [];
|
|
||||||
const logs = podLogsStore.logs.get(this.tabId);
|
|
||||||
const { getData, removeTimestamps } = podLogsStore;
|
|
||||||
const { showTimestamps } = getData(this.tabId);
|
|
||||||
|
|
||||||
if (!showTimestamps) {
|
|
||||||
return logs.map(item => removeTimestamps(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
return logs;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const logs = podLogsStore.logs;
|
||||||
|
|
||||||
const controls = (
|
const controls = (
|
||||||
<PodLogControls
|
<PodLogControls
|
||||||
ready={!this.isLoading}
|
ready={!this.isLoading}
|
||||||
tabId={this.tabId}
|
tabId={this.tabId}
|
||||||
tabData={this.tabData}
|
tabData={this.tabData}
|
||||||
logs={this.logs}
|
logs={logs}
|
||||||
save={this.save}
|
save={this.save}
|
||||||
reload={this.reload}
|
reload={this.reload}
|
||||||
onSearch={this.onSearch}
|
onSearch={this.onSearch}
|
||||||
@ -119,11 +103,12 @@ export class PodLogs extends React.Component<Props> {
|
|||||||
controls={controls}
|
controls={controls}
|
||||||
showSubmitClose={false}
|
showSubmitClose={false}
|
||||||
showButtons={false}
|
showButtons={false}
|
||||||
|
showStatusPanel={false}
|
||||||
/>
|
/>
|
||||||
<PodLogList
|
<PodLogList
|
||||||
|
logs={logs}
|
||||||
id={this.tabId}
|
id={this.tabId}
|
||||||
isLoading={this.isLoading}
|
isLoading={this.isLoading}
|
||||||
logs={this.logs}
|
|
||||||
load={this.load}
|
load={this.load}
|
||||||
ref={this.logListElement}
|
ref={this.logListElement}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user