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:
commit
c1ab1423bb
17
.github/labeler-config.yml
vendored
Normal file
17
.github/labeler-config.yml
vendored
Normal 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
14
.github/workflows/labeler.yml
vendored
Normal 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
|
||||
5
extensions/license-menu-item/Makefile
Normal file
5
extensions/license-menu-item/Makefile
Normal file
@ -0,0 +1,5 @@
|
||||
install-deps:
|
||||
npm install
|
||||
|
||||
build: install-deps
|
||||
npm run build
|
||||
13
extensions/license-menu-item/main.ts
Normal file
13
extensions/license-menu-item/main.ts
Normal 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")
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
3589
extensions/license-menu-item/package-lock.json
generated
Normal file
3589
extensions/license-menu-item/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
extensions/license-menu-item/package.json
Normal file
21
extensions/license-menu-item/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
19
extensions/license-menu-item/tsconfig.json
Normal file
19
extensions/license-menu-item/tsconfig.json
Normal 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"
|
||||
}
|
||||
}
|
||||
34
extensions/license-menu-item/webpack.config.ts
Normal file
34
extensions/license-menu-item/webpack.config.ts
Normal 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,
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
117
extensions/telemetry/package-lock.json
generated
117
extensions/telemetry/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -185,6 +185,7 @@
|
||||
"pod-menu",
|
||||
"node-menu",
|
||||
"metrics-cluster-feature",
|
||||
"license-menu-item",
|
||||
"support-page"
|
||||
]
|
||||
},
|
||||
|
||||
@ -11,4 +11,4 @@ export * from "./getRandId"
|
||||
export * from "./splitArray"
|
||||
export * from "./saveToAppFiles"
|
||||
export * from "./singleton"
|
||||
export * from "./cloneJson"
|
||||
export * from "./openExternal"
|
||||
|
||||
6
src/common/utils/openExternal.ts
Normal file
6
src/common/utils/openExternal.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// Opens a link in external browser
|
||||
import { shell } from "electron"
|
||||
|
||||
export function openExternal(url: string) {
|
||||
return shell.openExternal(url);
|
||||
}
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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[] = []
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
22
src/extensions/registries/kube-object-detail-registry.ts
Normal file
22
src/extensions/registries/kube-object-detail-registry.ts
Normal 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()
|
||||
@ -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
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user