1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge branch 'master' into fix/add-analytics-node-types

This commit is contained in:
Lauri Nevala 2020-11-02 20:58:48 +02:00 committed by GitHub
commit c1ab1423bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 3994 additions and 78 deletions

17
.github/labeler-config.yml vendored Normal file
View File

@ -0,0 +1,17 @@
---
area/ui:
- src/renderer/**/*
area/test:
- integration/**/*
- __mocks__/**/*
area/extension:
- extensions/**/*
- src/extensions/**/*
area/documentation:
- README.md
- docs/**/*
area/ci:
- .github/workflows/**/*
- .azure-pipelines.yml
dependencies:
- yarn.lock

14
.github/workflows/labeler.yml vendored Normal file
View File

@ -0,0 +1,14 @@
---
name: "Pull Request Labeler"
'on':
- pull_request
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v2
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
configuration-path: .github/labeler-config.yml

View File

@ -0,0 +1,5 @@
install-deps:
npm install
build: install-deps
npm run build

View File

@ -0,0 +1,13 @@
import { LensMainExtension, Util } from "@k8slens/extensions";
export default class LicenseLensMainExtension extends LensMainExtension {
appMenus = [
{
parentId: "help",
label: "License",
async click() {
Util.openExternal("https://k8slens.dev/licenses/eula.md")
}
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
{
"name": "lens-license",
"version": "0.1.0",
"description": "License menu item",
"main": "dist/main.js",
"scripts": {
"build": "webpack -p",
"dev": "webpack --watch"
},
"dependencies": {},
"devDependencies": {
"@types/webpack": "^4.41.17",
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"mobx": "^5.15.5",
"react": "^16.13.1",
"ts-loader": "^8.0.4",
"ts-node": "^9.0.0",
"typescript": "^4.0.3",
"webpack": "^4.44.2"
}
}

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"outDir": "dist",
"baseUrl": ".",
"module": "CommonJS",
"target": "ES2017",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"moduleResolution": "Node",
"sourceMap": false,
"declaration": false,
"strict": false,
"noImplicitAny": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"jsx": "react"
}
}

View File

@ -0,0 +1,34 @@
import path from "path"
const outputPath = path.resolve(__dirname, 'dist');
export default [
{
entry: './main.ts',
context: __dirname,
target: "electron-main",
mode: "production",
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
externals: {
"@k8slens/extensions": "var global.LensExtensions",
"mobx": "var global.Mobx",
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
libraryTarget: "commonjs2",
globalObject: "this",
filename: 'main.js',
path: outputPath,
},
},
];

View File

@ -22,8 +22,7 @@ export default class SupportPageRendererExtension extends LensRendererExtension
className="flex align-center gaps hover-highlight"
onClick={() => Navigation.navigate(supportPageURL())}
>
<Component.Icon material="help_outline" small/>
<span>Support</span>
<Component.Icon material="help" smallest />
</div>
)
}

View File

@ -7,6 +7,7 @@ export default class TelemetryMainExtension extends LensMainExtension {
async onActivate() {
console.log("telemetry main extension activated")
tracker.start()
tracker.reportPeriodically()
await telemetryPreferencesStore.loadExtension(this)
}

View File

@ -13,6 +13,16 @@
"resolved": "https://registry.npmjs.org/@types/analytics-node/-/analytics-node-3.1.3.tgz",
"integrity": "sha512-Yk299LUqnyJ6fNYQkLFd0yTfUwIvgfxH3f5WEX3ib0PC5T+mZgqcOPMDhNZ4AOD/A9tXKJQeBIb6KvgzuXflaQ==",
"dev": true
}
"@segment/loosely-validate-event": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz",
"integrity": "sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==",
"dev": true,
"requires": {
"component-type": "^1.2.1",
"join-component": "^1.1.0"
}
},
"@webassemblyjs/ast": {
"version": "1.9.0",
@ -231,6 +241,22 @@
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
},
"analytics-node": {
"version": "3.4.0-beta.3",
"resolved": "https://registry.npmjs.org/analytics-node/-/analytics-node-3.4.0-beta.3.tgz",
"integrity": "sha512-NIdpxiwlZ4cKgs9MDlDe89b5bg/pMq2W7XTA+cjzCM66IwW3ujZhVE49vk+zG6Yrxk0s/DXmennJ+cCQIsTKMA==",
"dev": true,
"requires": {
"@segment/loosely-validate-event": "^2.0.0",
"axios": "^0.19.2",
"axios-retry": "^3.0.2",
"lodash.isstring": "^4.0.1",
"md5": "^2.2.1",
"ms": "^2.0.0",
"remove-trailing-slash": "^0.1.0",
"uuid": "^3.2.1"
}
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@ -380,6 +406,24 @@
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==",
"dev": true
},
"axios": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
"dev": true,
"requires": {
"follow-redirects": "1.5.10"
}
},
"axios-retry": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.1.9.tgz",
"integrity": "sha512-NFCoNIHq8lYkJa6ku4m+V1837TP6lCa7n79Iuf8/AqATAHYB0ISaAS1eyIenDOfHOLtym34W65Sjke2xjg2fsA==",
"dev": true,
"requires": {
"is-retry-allowed": "^1.1.0"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -702,6 +746,12 @@
"supports-color": "^5.3.0"
}
},
"charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=",
"dev": true
},
"chokidar": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
@ -819,6 +869,12 @@
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
"dev": true
},
"component-type": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-type/-/component-type-1.2.1.tgz",
"integrity": "sha1-ikeQFwAjjk/DIml3EjAibyS0Fak=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -920,6 +976,12 @@
"sha.js": "^2.4.8"
}
},
"crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=",
"dev": true
},
"crypto-browserify": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
@ -1383,6 +1445,26 @@
"readable-stream": "^2.3.6"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"dev": true,
"requires": {
"debug": "=3.1.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
}
}
},
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@ -1790,6 +1872,12 @@
"isobject": "^3.0.1"
}
},
"is-retry-allowed": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
"integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==",
"dev": true
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@ -1826,6 +1914,12 @@
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
"dev": true
},
"join-component": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/join-component/-/join-component-1.1.0.tgz",
"integrity": "sha1-uEF7dQZho5K+4sJTfGiyqdSXfNU=",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -1916,6 +2010,12 @@
"path-exists": "^3.0.0"
}
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=",
"dev": true
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -1967,6 +2067,17 @@
"object-visit": "^1.0.0"
}
},
"md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"dev": true,
"requires": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
}
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -2621,6 +2732,12 @@
"dev": true,
"optional": true
},
"remove-trailing-slash": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz",
"integrity": "sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==",
"dev": true
},
"repeat-element": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",

View File

@ -22,6 +22,7 @@
"mobx": "^5.15.5",
"react": "^16.13.1",
"node-machine-id": "^1.1.12",
"universal-analytics": "^0.4.23"
"universal-analytics": "^0.4.23",
"analytics-node": "^3.4.0-beta.3"
}
}

View File

@ -1,31 +1,40 @@
import { EventBus, Util, Store, App } from "@k8slens/extensions"
import ua from "universal-analytics"
import Analytics from "analytics-node"
import { machineIdSync } from "node-machine-id"
import { telemetryPreferencesStore } from "./telemetry-preferences-store"
export class Tracker extends Util.Singleton {
static readonly GA_ID = "UA-159377374-1"
static readonly SEGMENT_KEY = "YENwswyhlOgz8P7EFKUtIZ2MfON7Yxqb"
protected eventHandlers: Array<(ev: EventBus.AppEvent ) => void> = []
protected started = false
protected visitor: ua.Visitor
protected analytics: Analytics
protected machineId: string = null;
protected ip: string = null;
protected appVersion: string;
protected locale: string;
protected electronUA: string;
protected userAgent: string;
protected anonymousId: string;
protected os: string
protected reportInterval: NodeJS.Timeout
private constructor() {
super();
this.anonymousId = machineIdSync()
this.os = this.resolveOS()
this.userAgent = `Lens ${App.version} (${this.os})`
try {
this.visitor = ua(Tracker.GA_ID, machineIdSync(), { strictCidFormat: false })
this.visitor = ua(Tracker.GA_ID, this.anonymousId, { strictCidFormat: false })
} catch (error) {
this.visitor = ua(Tracker.GA_ID)
}
this.analytics = new Analytics(Tracker.SEGMENT_KEY, { flushAt: 1 })
this.visitor.set("dl", "https://telemetry.k8slens.dev")
this.visitor.set("ua", `Lens ${App.version} (${this.getOS()})`)
this.visitor.set("ua", this.userAgent)
}
start() {
@ -38,6 +47,9 @@ export class Tracker extends Util.Singleton {
}
this.eventHandlers.push(handler)
EventBus.appEventBus.addListener(handler)
}
reportPeriodically() {
this.reportInterval = setInterval(() => {
this.reportData()
}, 60 * 60 * 1000) // report every 1h
@ -61,12 +73,13 @@ export class Tracker extends Util.Singleton {
}
protected reportData() {
const clustersList = Store.clusterStore.clustersList
const clustersList = Store.clusterStore.enabledClustersList
this.event("generic-data", "report", {
appVersion: App.version,
os: this.os,
clustersCount: clustersList.length,
workspacesCount: Store.workspaceStore.workspacesList.length
workspacesCount: Store.workspaceStore.enabledWorkspacesList.length
})
clustersList.forEach((cluster) => {
@ -78,6 +91,7 @@ export class Tracker extends Util.Singleton {
protected reportClusterData(cluster: Store.ClusterModel) {
this.event("cluster-data", "report", {
id: cluster.metadata.id,
managed: !!cluster.ownerRef,
kubernetesVersion: cluster.metadata.version,
distribution: cluster.metadata.distribution,
nodesCount: cluster.metadata.nodes,
@ -85,7 +99,7 @@ export class Tracker extends Util.Singleton {
})
}
protected getOS() {
protected resolveOS() {
let os = ""
if (App.isMac) {
os = "MacOS"
@ -115,6 +129,19 @@ export class Tracker extends Util.Singleton {
ea: eventAction,
...otherParams,
}).send()
this.analytics.track({
anonymousId: this.anonymousId,
event: `${eventCategory} ${eventAction}`,
context: {
userAgent: this.userAgent,
},
properties: {
category: eventCategory,
...otherParams,
},
})
} catch (err) {
console.error(`Failed to track "${eventCategory}:${eventAction}"`, err)
}

View File

@ -185,6 +185,7 @@
"pod-menu",
"node-menu",
"metrics-cluster-feature",
"license-menu-item",
"support-page"
]
},

View File

@ -11,4 +11,4 @@ export * from "./getRandId"
export * from "./splitArray"
export * from "./saveToAppFiles"
export * from "./singleton"
export * from "./cloneJson"
export * from "./openExternal"

View File

@ -0,0 +1,6 @@
// Opens a link in external browser
import { shell } from "electron"
export function openExternal(url: string) {
return shell.openExternal(url);
}

View File

@ -1,3 +1,3 @@
export { Singleton } from "../../common/utils"
export { Singleton, openExternal } from "../../common/utils"
export { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault"
export { cssNames } from "../../renderer/utils/cssNames"

View File

@ -6,7 +6,10 @@ import { broadcastIpc } from "../common/ipc"
import { observable, reaction, toJS, } from "mobx"
import logger from "../main/logger"
import { app, ipcRenderer, remote } from "electron"
import { appPreferenceRegistry, clusterFeatureRegistry, clusterPageRegistry, globalPageRegistry, kubeObjectMenuRegistry, menuRegistry, statusBarRegistry } from "./registries";
import {
appPreferenceRegistry, clusterFeatureRegistry, clusterPageRegistry, globalPageRegistry,
kubeObjectDetailRegistry, kubeObjectMenuRegistry, menuRegistry, statusBarRegistry
} from "./registries";
export interface InstalledExtension extends ExtensionModel {
manifestPath: string;
@ -56,6 +59,7 @@ export class ExtensionLoader {
this.autoloadExtensions((extension: LensRendererExtension) => {
extension.registerTo(clusterPageRegistry, extension.clusterPages)
extension.registerTo(kubeObjectMenuRegistry, extension.kubeObjectMenuItems)
extension.registerTo(kubeObjectDetailRegistry, extension.kubeObjectDetailItems)
})
}

View File

@ -1,4 +1,8 @@
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectMenuRegistration, PageRegistration, StatusBarRegistration } from "./registries"
import type {
AppPreferenceRegistration, ClusterFeatureRegistration,
KubeObjectMenuRegistration, KubeObjectDetailRegistration,
PageRegistration, StatusBarRegistration
} from "./registries"
import { observable } from "mobx";
import { LensExtension } from "./lens-extension"
@ -8,5 +12,6 @@ export class LensRendererExtension extends LensExtension {
@observable.shallow appPreferences: AppPreferenceRegistration[] = []
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = []
@observable.shallow statusBarItems: StatusBarRegistration[] = []
@observable.shallow kubeObjectDetailItems: KubeObjectDetailRegistration[] = []
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = []
}

View File

@ -4,5 +4,6 @@ export * from "./page-registry"
export * from "./menu-registry"
export * from "./app-preference-registry"
export * from "./status-bar-registry"
export * from "./kube-object-detail-registry";
export * from "./kube-object-menu-registry";
export * from "./cluster-feature-registry"

View File

@ -0,0 +1,22 @@
import React from "react"
import { BaseRegistry } from "./base-registry";
export interface KubeObjectDetailComponents {
Details: React.ComponentType<any>;
}
export interface KubeObjectDetailRegistration {
kind: string;
apiVersions: string[];
components: KubeObjectDetailComponents;
}
export class KubeObjectDetailRegistry extends BaseRegistry<KubeObjectDetailRegistration> {
getItemsForKind(kind: string, apiVersion: string) {
return this.items.filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion)
})
}
}
export const kubeObjectDetailRegistry = new KubeObjectDetailRegistry()

View File

@ -11,7 +11,8 @@ export * from "../../renderer/components/drawer"
// kube helpers
export { KubeObjectDetailsProps, KubeObjectMenuProps } from "../../renderer/components/kube-object"
export { KubeObjectMeta } from "../../renderer/components/kube-object/kube-object-meta";
export { KubeObjectMeta } from "../../renderer/components/kube-object/kube-object-meta"
export { KubeObjectListLayout, KubeObjectListLayoutProps } from "../../renderer/components/kube-object/kube-object-list-layout";
export { KubeEventDetails } from "../../renderer/components/+events/kube-event-details"
// specific exports

View File

@ -1,4 +1,6 @@
export { isAllowedResource } from "../../common/rbac"
export { apiManager } from "../../renderer/api/api-manager";
export { KubeObjectStore } from "../../renderer/kube-object.store"
export { KubeApi, forCluster, IKubeApiCluster } from "../../renderer/api/kube-api";
export { KubeObject } from "../../renderer/api/kube-object";
export { Pod, podsApi, IPodContainer, IPodContainerStatus } from "../../renderer/api/endpoints";

View File

@ -1 +1,3 @@
export { navigate, hideDetails, showDetails } from "../../renderer/navigation"
export { navigate, hideDetails, showDetails, getDetailsUrl } from "../../renderer/navigation"
export { RouteProps } from "react-router"
export { IURLParams } from "../../common/utils/buildUrl";

View File

@ -31,7 +31,7 @@ export class DistributionDetector extends BaseClusterDetector {
if (this.isCustom()) {
return { value: "custom", accuracy: 10}
}
return { value: "vanilla", accuracy: 10}
return { value: "unknown", accuracy: 10}
}
public async getKubernetesVersion() {

View File

@ -5,13 +5,12 @@ export class NodesCountDetector extends BaseClusterDetector {
key = ClusterMetadataKey.NODES_COUNT
public async detect() {
if (!this.cluster.accessible) return null;
const nodeCount = await this.getNodeCount()
return { value: nodeCount, accuracy: 100}
}
protected async getNodeCount(): Promise<number> {
if (!this.cluster.accessible) return null;
const response = await this.k8sRequest("/api/v1/nodes")
return response.items.length
}

View File

@ -1,5 +1,6 @@
import "../common/cluster-ipc";
import type http from "http"
import { ipcMain } from "electron"
import { autorun } from "mobx";
import { clusterStore, getClusterIdFromHost } from "../common/cluster-store"
import { Cluster } from "./cluster"
@ -30,6 +31,29 @@ export class ClusterManager {
}, {
delay: 250
});
ipcMain.on("network:offline", () => { this.onNetworkOffline() })
ipcMain.on("network:online", () => { this.onNetworkOnline() })
}
protected onNetworkOffline() {
logger.info("[CLUSTER-MANAGER]: network is offline")
clusterStore.enabledClustersList.forEach((cluster) => {
if (!cluster.disconnected) {
cluster.online = false
cluster.accessible = false
cluster.refreshConnectionStatus().catch((e) => e)
}
})
}
protected onNetworkOnline() {
logger.info("[CLUSTER-MANAGER]: network is online")
clusterStore.enabledClustersList.forEach((cluster) => {
if (!cluster.disconnected) {
cluster.refreshConnectionStatus().catch((e) => e)
}
})
}
stop() {

View File

@ -67,12 +67,12 @@ export class Cluster implements ClusterModel, ClusterState {
@observable kubeConfigPath: string;
@observable apiUrl: string; // cluster server url
@observable kubeProxyUrl: string; // lens-proxy to kube-api url
@observable enabled = false;
@observable online = false;
@observable accessible = false;
@observable ready = false;
@observable enabled = false; // only enabled clusters are visible to users
@observable online = false; // describes if we can detect that cluster is online
@observable accessible = false; // if user is able to access cluster resources
@observable ready = false; // cluster is in usable state
@observable reconnecting = false;
@observable disconnected = true;
@observable disconnected = true; // false if user has selected to connect
@observable failureReason: string;
@observable isAdmin = false;
@observable eventCount = 0;
@ -127,16 +127,16 @@ export class Cluster implements ClusterModel, ClusterState {
}
protected bindEvents() {
logger.info(`[CLUSTER]: bind events`, this.getMeta());
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000); // every 15 minutes
logger.info(`[CLUSTER]: bind events`, this.getMeta())
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000) // every 30s
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000) // every 15 minutes
if (ipcMain) {
this.eventDisposers.push(
reaction(() => this.getState(), () => this.pushState()),
() => {
clearInterval(refreshTimer);
clearInterval(refreshMetadataTimer);
clearInterval(refreshTimer)
clearInterval(refreshMetadataTimer)
},
);
}

View File

@ -82,6 +82,12 @@ export class LensProxy {
proxySocket.write("\r\n")
proxySocket.write(head)
})
proxySocket.setKeepAlive(true)
socket.setKeepAlive(true)
proxySocket.setTimeout(0)
socket.setTimeout(0)
proxySocket.on('data', function (chunk) {
socket.write(chunk)
})

View File

@ -1,4 +1,4 @@
import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, shell, webContents } from "electron"
import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, webContents } from "electron"
import { autorun } from "mobx";
import { WindowManager } from "./window-manager";
import { appName, isMac, isWindows } from "../common/vars";
@ -185,12 +185,6 @@ export function buildMenu(windowManager: WindowManager) {
navigate(whatsNewURL())
},
},
{
label: "License",
click: async () => {
shell.openExternal('https://k8slens.dev/licenses/eula.md');
},
},
...ignoreOnMac([
{
label: "About Lens",

View File

@ -1,33 +1 @@
import { observable } from "mobx"
import React from "react"
export interface KubeObjectDetailComponents {
Details: React.ComponentType<any>;
}
export interface KubeObjectDetailRegistration {
kind: string;
apiVersions: string[];
components: KubeObjectDetailComponents;
}
export class KubeObjectDetailRegistry {
items = observable.array<KubeObjectDetailRegistration>([], { deep: false });
add(item: KubeObjectDetailRegistration) {
this.items.push(item)
return () => {
this.items.replace(
this.items.filter(c => c !== item)
)
};
}
getItemsForKind(kind: string, apiVersion: string) {
return this.items.filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion)
})
}
}
export const kubeObjectDetailRegistry = new KubeObjectDetailRegistry()
export { kubeObjectDetailRegistry } from "../../extensions/registries/kube-object-detail-registry"

View File

@ -109,17 +109,22 @@ export class KubeWatchApi {
}
}
protected async onRouteEvent({ type, url }: IKubeWatchRouteEvent) {
if (type === "STREAM_END") {
protected async onRouteEvent(event: IKubeWatchRouteEvent) {
if (event.type === "STREAM_END") {
this.disconnect();
const { apiBase, namespace } = KubeApi.parseApi(url);
const { apiBase, namespace } = KubeApi.parseApi(event.url);
const api = apiManager.getApi(apiBase);
if (api) {
try {
await api.refreshResourceVersion({ namespace });
this.reconnect();
} catch (error) {
console.debug("failed to refresh resource version", error)
console.error("failed to refresh resource version", error)
if (this.subscribers.size > 0) {
setTimeout(() => {
this.onRouteEvent(event)
}, 1000)
}
}
}
}

View File

@ -15,7 +15,7 @@ export interface AnimateProps {
@observer
export class Animate extends React.Component<AnimateProps> {
static VISIBILITY_DELAY_MS = 100;
static VISIBILITY_DELAY_MS = 0;
static defaultProps: AnimateProps = {
name: "opacity",

View File

@ -54,6 +54,9 @@ export class App extends React.Component {
appEventBus.emit({name: "cluster", action: "open", params: {
clusterId: clusterId
}})
window.addEventListener("online", () => {
window.location.reload()
})
}
get startURL() {

View File

@ -4,8 +4,9 @@
font-size: $font-size-small;
background-color: #3d90ce;
padding: 0 $padding;
padding: 0 2px;
color: white;
height: 22px;
#current-workspace {
padding: $padding / 4 $padding / 2;

View File

@ -14,7 +14,7 @@ export class BottomBar extends React.Component {
return (
<div className="BottomBar flex gaps">
<div id="current-workspace" className="flex gaps align-center hover-highlight">
<Icon small material="layers"/>
<Icon smallest material="layers"/>
<span className="workspace-name">{currentWorkspace.name}</span>
</div>
<WorkspaceMenu

View File

@ -1,6 +1,7 @@
.Icon {
--size: 21px;
--small-size: 18px;
--smallest-size: 16px;
--big-size: 32px;
--color-active: #{$iconActiveColor};
--bgc-active: #{$iconActiveBackground};
@ -21,6 +22,12 @@
width: var(--size);
height: var(--size);
&.smallest {
font-size: var(--smallest-size);
width: var(--smallest-size);
height: var(--smallest-size);
}
&.small {
font-size: var(--small-size);
width: var(--small-size);

View File

@ -15,6 +15,7 @@ export interface IconProps extends React.HTMLAttributes<any>, TooltipDecoratorPr
href?: string; // render icon as hyperlink
size?: string | number; // icon-size
small?: boolean; // pre-defined icon-size
smallest?: boolean; // pre-defined icon-size
big?: boolean; // pre-defined icon-size
active?: boolean; // apply active-state styles
interactive?: boolean; // indicates that icon is interactive and highlight it on focus/hover
@ -63,7 +64,7 @@ export class Icon extends React.PureComponent<IconProps> {
const { isInteractive } = this;
const {
// skip passing props to icon's html element
className, href, link, material, svg, size, small, big,
className, href, link, material, svg, size, smallest, small, big,
disabled, sticker, active, focusable, children,
interactive: _interactive,
onClick: _onClick,
@ -75,7 +76,7 @@ export class Icon extends React.PureComponent<IconProps> {
const iconProps: Partial<IconProps> = {
className: cssNames("Icon", className,
{ svg, material, interactive: isInteractive, disabled, sticker, active, focusable },
!size ? { small, big } : {}
!size ? { smallest, small, big } : {}
),
onClick: isInteractive ? this.onClick : undefined,
onKeyDown: isInteractive ? this.onKeyDown : undefined,

View File

@ -1,5 +1,6 @@
import "../common/system-ca"
import React from "react";
import { ipcRenderer } from "electron";
import { Route, Router, Switch } from "react-router";
import { observer } from "mobx-react";
import { userStore } from "../common/user-store";
@ -17,6 +18,12 @@ import { extensionLoader } from "../extensions/extension-loader";
export class LensApp extends React.Component {
static async init() {
extensionLoader.loadOnClusterManagerRenderer();
window.addEventListener("offline", () => {
ipcRenderer.send("network:offline")
})
window.addEventListener("online", () => {
ipcRenderer.send("network:online")
})
}
render() {