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

Merge pull request #1450 from lensapp/feature/enforce-semicolons

Enforce semicolons in eslint
This commit is contained in:
Panu Horsmalahti 2020-11-19 18:52:14 +02:00 committed by GitHub
commit c94c599cdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
588 changed files with 4909 additions and 4901 deletions

View File

@ -20,6 +20,7 @@ module.exports = {
rules: {
"indent": ["error", 2],
"no-unused-vars": "off",
"semi": ["error", "always"],
}
},
{
@ -47,7 +48,9 @@ module.exports = {
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-interface": "off",
"indent": ["error", 2]
"indent": ["error", 2],
"semi": "off",
"@typescript-eslint/semi": ["error"],
},
},
{
@ -75,7 +78,9 @@ module.exports = {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-empty-function": "off",
"indent": ["error", 2]
"indent": ["error", 2],
"semi": "off",
"@typescript-eslint/semi": ["error"],
},
}
]

View File

@ -5,7 +5,7 @@ module.exports = {
getVersion: jest.fn().mockReturnValue("3.0.0"),
getLocale: jest.fn().mockRejectedValue("en"),
getPath: jest.fn((name: string) => {
return "tmp"
return "tmp";
}),
},
remote: {

View File

@ -1,9 +1,9 @@
// Generate tray icons from SVG to PNG + different sizes and colors (B&W)
// Command: `yarn build:tray-icons`
import path from "path"
import path from "path";
import sharp from "sharp";
import jsdom from "jsdom"
import fs from "fs-extra"
import jsdom from "jsdom";
import fs from "fs-extra";
export async function generateTrayIcon(
{
@ -14,15 +14,15 @@ export async function generateTrayIcon(
pixelSize = 32,
shouldUseDarkColors = false, // managed by electron.nativeTheme.shouldUseDarkColors
} = {}) {
outputFilename += shouldUseDarkColors ? "_dark" : ""
dpiSuffix = dpiSuffix !== "1x" ? `@${dpiSuffix}` : ""
const pngIconDestPath = path.resolve(outputFolder, `${outputFilename}${dpiSuffix}.png`)
outputFilename += shouldUseDarkColors ? "_dark" : "";
dpiSuffix = dpiSuffix !== "1x" ? `@${dpiSuffix}` : "";
const pngIconDestPath = path.resolve(outputFolder, `${outputFilename}${dpiSuffix}.png`);
try {
// Modify .SVG colors
const trayIconColor = shouldUseDarkColors ? "white" : "black";
const svgDom = await jsdom.JSDOM.fromFile(svgIconPath);
const svgRoot = svgDom.window.document.body.getElementsByTagName("svg")[0];
svgRoot.innerHTML += `<style>* {fill: ${trayIconColor} !important;}</style>`
svgRoot.innerHTML += `<style>* {fill: ${trayIconColor} !important;}</style>`;
const svgIconBuffer = Buffer.from(svgRoot.outerHTML);
// Resize and convert to .PNG

View File

@ -1,3 +1,3 @@
import { helmCli } from "../src/main/helm/helm-cli"
import { helmCli } from "../src/main/helm/helm-cli";
helmCli.ensureBinary()
helmCli.ensureBinary();

View File

@ -1,20 +1,20 @@
import packageInfo from "../package.json"
import fs from "fs"
import request from "request"
import md5File from "md5-file"
import requestPromise from "request-promise-native"
import { ensureDir, pathExists } from "fs-extra"
import path from "path"
import packageInfo from "../package.json";
import fs from "fs";
import request from "request";
import md5File from "md5-file";
import requestPromise from "request-promise-native";
import { ensureDir, pathExists } from "fs-extra";
import path from "path";
class KubectlDownloader {
public kubectlVersion: string
protected url: string
public kubectlVersion: string;
protected url: string;
protected path: string;
protected dirname: string
protected dirname: string;
constructor(clusterVersion: string, platform: string, arch: string, target: string) {
this.kubectlVersion = clusterVersion;
const binaryName = platform === "windows" ? "kubectl.exe" : "kubectl"
const binaryName = platform === "windows" ? "kubectl.exe" : "kubectl";
this.url = `https://storage.googleapis.com/kubernetes-release/release/v${this.kubectlVersion}/bin/${platform}/${arch}/${binaryName}`;
this.dirname = path.dirname(target);
this.path = target;
@ -25,83 +25,85 @@ class KubectlDownloader {
method: "HEAD",
uri: this.url,
resolveWithFullResponse: true
}).catch((error) => { console.log(error) })
}).catch((error) => { console.log(error); });
if (response.headers["etag"]) {
return response.headers["etag"].replace(/"/g, "")
return response.headers["etag"].replace(/"/g, "");
}
return ""
return "";
}
public async checkBinary() {
const exists = await pathExists(this.path)
const exists = await pathExists(this.path);
if (exists) {
const hash = md5File.sync(this.path)
const etag = await this.urlEtag()
const hash = md5File.sync(this.path);
const etag = await this.urlEtag();
if(hash == etag) {
console.log("Kubectl md5sum matches the remote etag")
return true
console.log("Kubectl md5sum matches the remote etag");
return true;
}
console.log("Kubectl md5sum " + hash + " does not match the remote etag " + etag + ", unlinking and downloading again")
await fs.promises.unlink(this.path)
console.log("Kubectl md5sum " + hash + " does not match the remote etag " + etag + ", unlinking and downloading again");
await fs.promises.unlink(this.path);
}
return false
return false;
}
public async downloadKubectl() {
const exists = await this.checkBinary();
if(exists) {
console.log("Already exists and is valid")
return
console.log("Already exists and is valid");
return;
}
await ensureDir(path.dirname(this.path), 0o755)
await ensureDir(path.dirname(this.path), 0o755);
const file = fs.createWriteStream(this.path)
console.log(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`)
const file = fs.createWriteStream(this.path);
console.log(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
const requestOpts: request.UriOptions & request.CoreOptions = {
uri: this.url,
gzip: true
}
const stream = request(requestOpts)
};
const stream = request(requestOpts);
stream.on("complete", () => {
console.log("kubectl binary download finished")
file.end(() => {})
})
console.log("kubectl binary download finished");
// eslint-disable-next-line @typescript-eslint/no-empty-function
file.end(() => {});
});
stream.on("error", (error) => {
console.log(error)
fs.unlink(this.path, () => {})
throw(error)
})
console.log(error);
// eslint-disable-next-line @typescript-eslint/no-empty-function
fs.unlink(this.path, () => {});
throw(error);
});
return new Promise((resolve, reject) => {
file.on("close", () => {
console.log("kubectl binary download closed")
console.log("kubectl binary download closed");
fs.chmod(this.path, 0o755, (err) => {
if (err) reject(err);
})
resolve()
})
stream.pipe(file)
})
});
resolve();
});
stream.pipe(file);
});
}
}
const downloadVersion = packageInfo.config.bundledKubectlVersion;
const baseDir = path.join(process.env.INIT_CWD, 'binaries', 'client')
const baseDir = path.join(process.env.INIT_CWD, 'binaries', 'client');
const downloads = [
{ platform: 'linux', arch: 'amd64', target: path.join(baseDir, 'linux', 'x64', 'kubectl') },
{ platform: 'darwin', arch: 'amd64', target: path.join(baseDir, 'darwin', 'x64', 'kubectl') },
{ platform: 'windows', arch: 'amd64', target: path.join(baseDir, 'windows', 'x64', 'kubectl.exe') },
{ platform: 'windows', arch: '386', target: path.join(baseDir, 'windows', 'ia32', 'kubectl.exe') }
]
];
downloads.forEach((dlOpts) => {
console.log(dlOpts)
console.log(dlOpts);
const downloader = new KubectlDownloader(downloadVersion, dlOpts.platform, dlOpts.arch, dlOpts.target);
console.log("Downloading: " + JSON.stringify(dlOpts));
downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete")))
})
downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete")));
});

View File

@ -1,4 +1,4 @@
const { notarize } = require('electron-notarize')
const { notarize } = require('electron-notarize');
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context;

View File

@ -1,9 +1,9 @@
import * as fs from "fs"
import * as path from "path"
import packageInfo from "../src/extensions/npm/extensions/package.json"
import appInfo from "../package.json"
import * as fs from "fs";
import * as path from "path";
import packageInfo from "../src/extensions/npm/extensions/package.json";
import appInfo from "../package.json";
const packagePath = path.join(__dirname, "../src/extensions/npm/extensions/package.json")
const packagePath = path.join(__dirname, "../src/extensions/npm/extensions/package.json");
packageInfo.version = appInfo.version
fs.writeFileSync(packagePath, JSON.stringify(packageInfo, null, 2) + "\n")
packageInfo.version = appInfo.version;
fs.writeFileSync(packagePath, JSON.stringify(packageInfo, null, 2) + "\n");

View File

@ -1,10 +1,10 @@
import { LensRendererExtension, Component } from "@k8slens/extensions";
import { CoffeeDoodle } from "react-open-doodles";
import path from "path";
import React from "react"
import React from "react";
export function ExampleIcon(props: Component.IconProps) {
return <Component.Icon {...props} material="pages" tooltip={path.basename(__filename)}/>
return <Component.Icon {...props} material="pages" tooltip={path.basename(__filename)}/>;
}
export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> {
@ -16,7 +16,7 @@ export class ExamplePage extends React.Component<{ extension: LensRendererExtens
render() {
const doodleStyle = {
width: "200px"
}
};
return (
<div className="flex column gaps align-flex-start">
<div style={doodleStyle}><CoffeeDoodle accent="#3d90ce" /></div>
@ -24,6 +24,6 @@ export class ExamplePage extends React.Component<{ extension: LensRendererExtens
<p>File: <i>{__filename}</i></p>
<Component.Button accent label="Deactivate" onClick={this.deactivate}/>
</div>
)
);
}
}

View File

@ -1,6 +1,6 @@
import { LensRendererExtension } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page"
import React from "react"
import { ExampleIcon, ExamplePage } from "./page";
import React from "react";
export default class ExampleExtension extends LensRendererExtension {
clusterPages = [

View File

@ -1,5 +1,5 @@
import { LensRendererExtension, K8sApi } from "@k8slens/extensions";
import { resolveStatus, resolveStatusForCronJobs, resolveStatusForPods } from "./src/resolver"
import { resolveStatus, resolveStatusForCronJobs, resolveStatusForPods } from "./src/resolver";
export default class EventResourceStatusRendererExtension extends LensRendererExtension {
kubeObjectStatusTexts = [

View File

@ -1,9 +1,9 @@
import { K8sApi } from "@k8slens/extensions";
export function resolveStatus(object: K8sApi.KubeObject): K8sApi.KubeObjectStatus {
const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi)
const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi);
const events = (eventStore as K8sApi.EventStore).getEventsByObject(object);
let warnings = events.filter(evt => evt.isWarning());
const warnings = events.filter(evt => evt.isWarning());
if (!events.length || !warnings.length) {
return null;
}
@ -12,16 +12,16 @@ export function resolveStatus(object: K8sApi.KubeObject): K8sApi.KubeObjectStatu
level: K8sApi.KubeObjectStatusLevel.WARNING,
text: `${event.message}`,
timestamp: event.metadata.creationTimestamp
}
};
}
export function resolveStatusForPods(pod: K8sApi.Pod): K8sApi.KubeObjectStatus {
if (!pod.hasIssues()) {
return null
return null;
}
const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi)
const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi);
const events = (eventStore as K8sApi.EventStore).getEventsByObject(pod);
let warnings = events.filter(evt => evt.isWarning());
const warnings = events.filter(evt => evt.isWarning());
if (!events.length || !warnings.length) {
return null;
}
@ -30,13 +30,13 @@ export function resolveStatusForPods(pod: K8sApi.Pod): K8sApi.KubeObjectStatus {
level: K8sApi.KubeObjectStatusLevel.WARNING,
text: `${event.message}`,
timestamp: event.metadata.creationTimestamp
}
};
}
export function resolveStatusForCronJobs(cronJob: K8sApi.CronJob): K8sApi.KubeObjectStatus {
const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi)
const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi);
let events = (eventStore as K8sApi.EventStore).getEventsByObject(cronJob);
let warnings = events.filter(evt => evt.isWarning());
const warnings = events.filter(evt => evt.isWarning());
if (cronJob.isNeverRun()) {
events = events.filter(event => event.reason != "FailedNeedsStart");
}
@ -48,5 +48,5 @@ export function resolveStatusForCronJobs(cronJob: K8sApi.CronJob): K8sApi.KubeOb
level: K8sApi.KubeObjectStatusLevel.WARNING,
text: `${event.message}`,
timestamp: event.metadata.creationTimestamp
}
};
}

View File

@ -6,7 +6,7 @@ export default class LicenseLensMainExtension extends LensMainExtension {
parentId: "help",
label: "License",
async click() {
Util.openExternal("https://k8slens.dev/licenses/eula.md")
Util.openExternal("https://k8slens.dev/licenses/eula.md");
}
}
]

View File

@ -1,4 +1,4 @@
import path from "path"
import path from "path";
const outputPath = path.resolve(__dirname, 'dist');

View File

@ -1,6 +1,6 @@
import { LensRendererExtension } from "@k8slens/extensions"
import { MetricsFeature } from "./src/metrics-feature"
import React from "react"
import { LensRendererExtension } from "@k8slens/extensions";
import { MetricsFeature } from "./src/metrics-feature";
import React from "react";
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
clusterFeatures = [
@ -13,8 +13,8 @@ export default class ClusterMetricsFeatureExtension extends LensRendererExtensio
Enable timeseries data visualization (Prometheus stack) for your cluster.
Install this only if you don't have existing Prometheus stack installed.
You can see preview of manifests <a href="https://github.com/lensapp/lens/tree/master/extensions/lens-metrics/resources" target="_blank">here</a>.
</span>
)
</span>
);
}
},
feature: new MetricsFeature()

View File

@ -1,6 +1,6 @@
import { ClusterFeature, Store, K8sApi } from "@k8slens/extensions"
import semver from "semver"
import * as path from "path"
import { ClusterFeature, Store, K8sApi } from "@k8slens/extensions";
import semver from "semver";
import * as path from "path";
export interface MetricsConfiguration {
// Placeholder for Metrics config structure
@ -51,46 +51,46 @@ export class MetricsFeature extends ClusterFeature.Feature {
async install(cluster: Store.Cluster): Promise<void> {
// Check if there are storageclasses
const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass)
const scs = await storageClassApi.list()
const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass);
const scs = await storageClassApi.list();
this.config.persistence.enabled = scs.some(sc => (
sc.metadata?.annotations?.['storageclass.kubernetes.io/is-default-class'] === 'true' ||
sc.metadata?.annotations?.['storageclass.beta.kubernetes.io/is-default-class'] === 'true'
));
super.applyResources(cluster, super.renderTemplates(path.join(__dirname, "../resources/")))
super.applyResources(cluster, super.renderTemplates(path.join(__dirname, "../resources/")));
}
async upgrade(cluster: Store.Cluster): Promise<void> {
return this.install(cluster)
return this.install(cluster);
}
async updateStatus(cluster: Store.Cluster): Promise<ClusterFeature.FeatureStatus> {
try {
const statefulSet = K8sApi.forCluster(cluster, K8sApi.StatefulSet)
const prometheus = await statefulSet.get({name: "prometheus", namespace: "lens-metrics"})
const statefulSet = K8sApi.forCluster(cluster, K8sApi.StatefulSet);
const prometheus = await statefulSet.get({name: "prometheus", namespace: "lens-metrics"});
if (prometheus?.kind) {
this.status.installed = true;
this.status.currentVersion = prometheus.spec.template.spec.containers[0].image.split(":")[1];
this.status.canUpgrade = semver.lt(this.status.currentVersion, this.latestVersion, true);
} else {
this.status.installed = false
this.status.installed = false;
}
} catch(e) {
if (e?.error?.code === 404) {
this.status.installed = false
this.status.installed = false;
}
}
return this.status
return this.status;
}
async uninstall(cluster: Store.Cluster): Promise<void> {
const namespaceApi = K8sApi.forCluster(cluster, K8sApi.Namespace)
const clusterRoleBindingApi = K8sApi.forCluster(cluster, K8sApi.ClusterRoleBinding)
const clusterRoleApi = K8sApi.forCluster(cluster, K8sApi.ClusterRole)
const namespaceApi = K8sApi.forCluster(cluster, K8sApi.Namespace);
const clusterRoleBindingApi = K8sApi.forCluster(cluster, K8sApi.ClusterRoleBinding);
const clusterRoleApi = K8sApi.forCluster(cluster, K8sApi.ClusterRole);
await namespaceApi.delete({name: "lens-metrics"})
await clusterRoleBindingApi.delete({name: "lens-prometheus"})
await clusterRoleApi.delete({name: "lens-prometheus"}) }
await namespaceApi.delete({name: "lens-metrics"});
await clusterRoleBindingApi.delete({name: "lens-prometheus"});
await clusterRoleApi.delete({name: "lens-prometheus"}); }
}

View File

@ -1,6 +1,6 @@
import { LensRendererExtension } from "@k8slens/extensions";
import React from "react"
import { NodeMenu, NodeMenuProps } from "./src/node-menu"
import React from "react";
import { NodeMenu, NodeMenuProps } from "./src/node-menu";
export default class NodeMenuRendererExtension extends LensRendererExtension {
kubeObjectMenuItems = [

View File

@ -1,5 +1,5 @@
import React from "react";
import { Component, K8sApi, Navigation} from "@k8slens/extensions"
import { Component, K8sApi, Navigation} from "@k8slens/extensions";
export interface NodeMenuProps extends Component.KubeObjectMenuProps<K8sApi.Node> {
}
@ -15,7 +15,7 @@ export function NodeMenu(props: NodeMenuProps) {
newTab: true,
});
Navigation.hideDetails();
}
};
const shell = () => {
Component.createTerminalTab({
@ -23,15 +23,15 @@ export function NodeMenu(props: NodeMenuProps) {
node: nodeName,
});
Navigation.hideDetails();
}
};
const cordon = () => {
sendToTerminal(`kubectl cordon ${nodeName}`);
}
};
const unCordon = () => {
sendToTerminal(`kubectl uncordon ${nodeName}`)
}
sendToTerminal(`kubectl uncordon ${nodeName}`);
};
const drain = () => {
const command = `kubectl drain ${nodeName} --delete-local-data --ignore-daemonsets --force`;
@ -43,8 +43,8 @@ export function NodeMenu(props: NodeMenuProps) {
Are you sure you want to drain <b>{nodeName}</b>?
</p>
),
})
}
});
};
return (
<>

View File

@ -1,7 +1,7 @@
import { LensRendererExtension } from "@k8slens/extensions";
import { PodShellMenu, PodShellMenuProps } from "./src/shell-menu"
import { PodLogsMenu, PodLogsMenuProps } from "./src/logs-menu"
import React from "react"
import { PodShellMenu, PodShellMenuProps } from "./src/shell-menu";
import { PodLogsMenu, PodLogsMenuProps } from "./src/logs-menu";
import React from "react";
export default class PodMenuRendererExtension extends LensRendererExtension {
kubeObjectMenuItems = [

View File

@ -19,7 +19,7 @@ export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
}
render() {
const { object: pod, toolbar } = this.props
const { object: pod, toolbar } = this.props;
const containers = pod.getAllContainers();
const statuses = pod.getContainerStatuses();
if (!containers.length) return null;
@ -33,25 +33,25 @@ export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
<Component.SubMenu>
{
containers.map(container => {
const { name } = container
const { name } = container;
const status = statuses.find(status => status.name === name);
const brick = status ? (
<Component.StatusBrick
className={Util.cssNames(Object.keys(status.state)[0], { ready: status.ready })}
/>
) : null
) : null;
return (
<Component.MenuItem key={name} onClick={Util.prevDefault(() => this.showLogs(container))} className="flex align-center">
{brick}
{name}
</Component.MenuItem>
)
);
})
}
</Component.SubMenu>
</>
)}
</Component.MenuItem>
)
);
}
}

View File

@ -9,16 +9,16 @@ export interface PodShellMenuProps extends Component.KubeObjectMenuProps<K8sApi.
export class PodShellMenu extends React.Component<PodShellMenuProps> {
async execShell(container?: string) {
Navigation.hideDetails();
const { object: pod } = this.props
const containerParam = container ? `-c ${container}` : ""
let command = `kubectl exec -i -t -n ${pod.getNs()} ${pod.getName()} ${containerParam} "--"`
const { object: pod } = this.props;
const containerParam = container ? `-c ${container}` : "";
let command = `kubectl exec -i -t -n ${pod.getNs()} ${pod.getName()} ${containerParam} "--"`;
if (window.navigator.platform !== "Win32") {
command = `exec ${command}`
command = `exec ${command}`;
}
if (pod.getSelectedNodeOs() === "windows") {
command = `${command} powershell`
command = `${command} powershell`;
} else {
command = `${command} sh -c "clear; (bash || ash || sh)"`
command = `${command} sh -c "clear; (bash || ash || sh)"`;
}
const shell = Component.createTerminalTab({
@ -32,7 +32,7 @@ export class PodShellMenu extends React.Component<PodShellMenuProps> {
}
render() {
const { object, toolbar } = this.props
const { object, toolbar } = this.props;
const containers = object.getRunningContainers();
if (!containers.length) return null;
return (
@ -51,13 +51,13 @@ export class PodShellMenu extends React.Component<PodShellMenuProps> {
<Component.StatusBrick/>
{name}
</Component.MenuItem>
)
);
})
}
</Component.SubMenu>
</>
)}
</Component.MenuItem>
)
);
}
}

View File

@ -1,8 +1,8 @@
// TODO: support localization / figure out how to extract / consume i18n strings
import "./support.scss"
import React from "react"
import { observer } from "mobx-react"
import "./support.scss";
import React from "react";
import { observer } from "mobx-react";
import { App, Component } from "@k8slens/extensions";
@observer

View File

@ -1,4 +1,4 @@
import path from "path"
import path from "path";
const outputPath = path.resolve(__dirname, 'dist');

View File

@ -1,18 +1,18 @@
import { LensMainExtension } from "@k8slens/extensions";
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store"
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store";
import { tracker } from "./src/tracker";
export default class TelemetryMainExtension extends LensMainExtension {
async onActivate() {
console.log("telemetry main extension activated")
tracker.start()
tracker.reportPeriodically()
await telemetryPreferencesStore.loadExtension(this)
console.log("telemetry main extension activated");
tracker.start();
tracker.reportPeriodically();
await telemetryPreferencesStore.loadExtension(this);
}
onDeactivate() {
tracker.stop()
console.log("telemetry main extension deactivated")
tracker.stop();
console.log("telemetry main extension deactivated");
}
}

View File

@ -1,8 +1,8 @@
import { LensRendererExtension } from "@k8slens/extensions";
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store"
import { TelemetryPreferenceHint, TelemetryPreferenceInput } from "./src/telemetry-preference"
import { tracker } from "./src/tracker"
import React from "react"
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store";
import { TelemetryPreferenceHint, TelemetryPreferenceInput } from "./src/telemetry-preference";
import { tracker } from "./src/tracker";
import React from "react";
export default class TelemetryRendererExtension extends LensRendererExtension {
appPreferences = [
@ -16,8 +16,8 @@ export default class TelemetryRendererExtension extends LensRendererExtension {
];
async onActivate() {
console.log("telemetry extension activated")
tracker.start()
await telemetryPreferencesStore.loadExtension(this)
console.log("telemetry extension activated");
tracker.start();
await telemetryPreferencesStore.loadExtension(this);
}
}

View File

@ -1,19 +1,19 @@
import { Component } from "@k8slens/extensions"
import React from "react"
import { Component } from "@k8slens/extensions";
import React from "react";
import { observer } from "mobx-react";
import { TelemetryPreferencesStore } from "./telemetry-preferences-store"
import { TelemetryPreferencesStore } from "./telemetry-preferences-store";
@observer
export class TelemetryPreferenceInput extends React.Component<{telemetry: TelemetryPreferencesStore}, {}> {
render() {
const { telemetry } = this.props
const { telemetry } = this.props;
return (
<Component.Checkbox
label="Allow telemetry & usage tracking"
value={telemetry.enabled}
onChange={v => { telemetry.enabled = v; }}
/>
)
);
}
}
@ -21,6 +21,6 @@ export class TelemetryPreferenceHint extends React.Component {
render() {
return (
<span>Telemetry & usage data is collected to continuously improve the Lens experience.</span>
)
);
}
}

View File

@ -1,5 +1,5 @@
import { Store } from "@k8slens/extensions";
import { toJS } from "mobx"
import { toJS } from "mobx";
export type TelemetryPreferencesModel = {
enabled: boolean;
@ -14,11 +14,11 @@ export class TelemetryPreferencesStore extends Store.ExtensionStore<TelemetryPre
defaults: {
enabled: true
}
})
});
}
protected fromStore({ enabled }: TelemetryPreferencesModel): void {
this.enabled = enabled
this.enabled = enabled;
}
toJSON(): TelemetryPreferencesModel {
@ -26,8 +26,8 @@ export class TelemetryPreferencesStore extends Store.ExtensionStore<TelemetryPre
enabled: this.enabled
}, {
recurseEverything: true
})
});
}
}
export const telemetryPreferencesStore = TelemetryPreferencesStore.getInstance<TelemetryPreferencesStore>()
export const telemetryPreferencesStore = TelemetryPreferencesStore.getInstance<TelemetryPreferencesStore>();

View File

@ -1,8 +1,8 @@
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"
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"
@ -23,69 +23,69 @@ export class Tracker extends Util.Singleton {
private constructor() {
super();
this.anonymousId = machineIdSync()
this.os = this.resolveOS()
this.userAgent = `Lens ${App.version} (${this.os})`
this.anonymousId = machineIdSync();
this.os = this.resolveOS();
this.userAgent = `Lens ${App.version} (${this.os})`;
try {
this.visitor = ua(Tracker.GA_ID, this.anonymousId, { strictCidFormat: false })
this.visitor = ua(Tracker.GA_ID, this.anonymousId, { strictCidFormat: false });
} catch (error) {
this.visitor = ua(Tracker.GA_ID)
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", this.userAgent)
this.analytics = new Analytics(Tracker.SEGMENT_KEY, { flushAt: 1 });
this.visitor.set("dl", "https://telemetry.k8slens.dev");
this.visitor.set("ua", this.userAgent);
}
start() {
if (this.started === true) { return }
if (this.started === true) { return; }
this.started = true
this.started = true;
const handler = (ev: EventBus.AppEvent) => {
this.event(ev.name, ev.action, ev.params)
}
this.eventHandlers.push(handler)
EventBus.appEventBus.addListener(handler)
this.event(ev.name, ev.action, ev.params);
};
this.eventHandlers.push(handler);
EventBus.appEventBus.addListener(handler);
}
reportPeriodically() {
this.reportInterval = setInterval(() => {
this.reportData()
}, 60 * 60 * 1000) // report every 1h
this.reportData();
}, 60 * 60 * 1000); // report every 1h
}
stop() {
if (!this.started) { return }
if (!this.started) { return; }
this.started = false
this.started = false;
for (const handler of this.eventHandlers) {
EventBus.appEventBus.removeListener(handler)
EventBus.appEventBus.removeListener(handler);
}
if (this.reportInterval) {
clearInterval(this.reportInterval)
clearInterval(this.reportInterval);
}
}
protected async isTelemetryAllowed(): Promise<boolean> {
return telemetryPreferencesStore.enabled
return telemetryPreferencesStore.enabled;
}
protected reportData() {
const clustersList = Store.clusterStore.enabledClustersList
const clustersList = Store.clusterStore.enabledClustersList;
this.event("generic-data", "report", {
appVersion: App.version,
os: this.os,
clustersCount: clustersList.length,
workspacesCount: Store.workspaceStore.enabledWorkspacesList.length
})
});
clustersList.forEach((cluster) => {
if (!cluster?.metadata.lastSeen) { return }
this.reportClusterData(cluster)
})
if (!cluster?.metadata.lastSeen) { return; }
this.reportClusterData(cluster);
});
}
protected reportClusterData(cluster: Store.ClusterModel) {
@ -96,26 +96,26 @@ export class Tracker extends Util.Singleton {
distribution: cluster.metadata.distribution,
nodesCount: cluster.metadata.nodes,
lastSeen: cluster.metadata.lastSeen
})
});
}
protected resolveOS() {
let os = ""
let os = "";
if (App.isMac) {
os = "MacOS"
os = "MacOS";
} else if(App.isWindows) {
os = "Windows"
os = "Windows";
} else if (App.isLinux) {
os = "Linux"
os = "Linux";
if (App.isSnap) {
os += "; Snap"
os += "; Snap";
} else {
os += "; AppImage"
os += "; AppImage";
}
} else {
os = "Unknown"
os = "Unknown";
}
return os
return os;
}
protected async event(eventCategory: string, eventAction: string, otherParams = {}) {
@ -128,7 +128,7 @@ export class Tracker extends Util.Singleton {
ec: eventCategory,
ea: eventAction,
...otherParams,
}).send()
}).send();
this.analytics.track({
anonymousId: this.anonymousId,
@ -141,9 +141,9 @@ export class Tracker extends Util.Singleton {
...otherParams,
},
})
});
} catch (err) {
console.error(`Failed to track "${eventCategory}:${eventAction}"`, err)
console.error(`Failed to track "${eventCategory}:${eventAction}"`, err);
}
}
}

View File

@ -4,159 +4,159 @@
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
cluster and vice versa.
*/
import { Application } from "spectron"
import * as util from "../helpers/utils"
import { spawnSync } from "child_process"
import { Application } from "spectron";
import * as util from "../helpers/utils";
import { spawnSync } from "child_process";
const describeif = (condition: boolean) => condition ? describe : describe.skip
const itif = (condition: boolean) => condition ? it : it.skip
const describeif = (condition: boolean) => condition ? describe : describe.skip;
const itif = (condition: boolean) => condition ? it : it.skip;
jest.setTimeout(60000)
jest.setTimeout(60000);
// FIXME (!): improve / simplify all css-selectors + use [data-test-id="some-id"] (already used in some tests below)
describe("Lens integration tests", () => {
const TEST_NAMESPACE = "integration-tests"
const TEST_NAMESPACE = "integration-tests";
const BACKSPACE = "\uE003"
let app: Application
const BACKSPACE = "\uE003";
let app: Application;
const appStart = async () => {
app = util.setup()
await app.start()
app = util.setup();
await app.start();
// Wait for splash screen to be closed
while (await app.client.getWindowCount() > 1);
await app.client.windowByIndex(0)
await app.client.waitUntilWindowLoaded()
}
await app.client.windowByIndex(0);
await app.client.waitUntilWindowLoaded();
};
const clickWhatsNew = async (app: Application) => {
await app.client.waitUntilTextExists("h1", "What's new?")
await app.client.click("button.primary")
await app.client.waitUntilTextExists("h1", "Welcome")
}
await app.client.waitUntilTextExists("h1", "What's new?");
await app.client.click("button.primary");
await app.client.waitUntilTextExists("h1", "Welcome");
};
describe("app start", () => {
beforeAll(appStart, 20000)
beforeAll(appStart, 20000);
afterAll(async () => {
if (app && app.isRunning()) {
return util.tearDown(app)
return util.tearDown(app);
}
})
});
it('shows "whats new"', async () => {
await clickWhatsNew(app)
})
await clickWhatsNew(app);
});
it('shows "add cluster"', async () => {
await app.electron.ipcRenderer.send('test-menu-item-click', "File", "Add Cluster")
await app.client.waitUntilTextExists("h2", "Add Cluster")
})
await app.electron.ipcRenderer.send('test-menu-item-click', "File", "Add Cluster");
await app.client.waitUntilTextExists("h2", "Add Cluster");
});
describe("preferences page", () => {
it('shows "preferences"', async () => {
let appName: string = process.platform === "darwin" ? "Lens" : "File"
await app.electron.ipcRenderer.send('test-menu-item-click', appName, "Preferences")
await app.client.waitUntilTextExists("h2", "Preferences")
})
const appName: string = process.platform === "darwin" ? "Lens" : "File";
await app.electron.ipcRenderer.send('test-menu-item-click', appName, "Preferences");
await app.client.waitUntilTextExists("h2", "Preferences");
});
it('ensures helm repos', async () => {
await app.client.waitUntilTextExists("div.repos #message-stable", "stable") // wait for the helm-cli to fetch the stable repo
await app.client.click("#HelmRepoSelect") // click the repo select to activate the drop-down
await app.client.waitUntilTextExists("div.Select__option", "") // wait for at least one option to appear (any text)
})
})
await app.client.waitUntilTextExists("div.repos #message-stable", "stable"); // wait for the helm-cli to fetch the stable repo
await app.client.click("#HelmRepoSelect"); // click the repo select to activate the drop-down
await app.client.waitUntilTextExists("div.Select__option", ""); // wait for at least one option to appear (any text)
});
});
it.skip('quits Lens"', async () => {
await app.client.keys(['Meta', 'Q'])
await app.client.keys('Meta')
})
})
await app.client.keys(['Meta', 'Q']);
await app.client.keys('Meta');
});
});
const minikubeReady = (): boolean => {
// determine if minikube is running
let status = spawnSync("minikube status", { shell: true })
let status = spawnSync("minikube status", { shell: true });
if (status.status !== 0) {
console.warn("minikube not running")
return false
console.warn("minikube not running");
return false;
}
// Remove TEST_NAMESPACE if it already exists
status = spawnSync(`minikube kubectl -- get namespace ${TEST_NAMESPACE}`, { shell: true })
status = spawnSync(`minikube kubectl -- get namespace ${TEST_NAMESPACE}`, { shell: true });
if (status.status === 0) {
console.warn(`Removing existing ${TEST_NAMESPACE} namespace`)
status = spawnSync(`minikube kubectl -- delete namespace ${TEST_NAMESPACE}`, { shell: true })
console.warn(`Removing existing ${TEST_NAMESPACE} namespace`);
status = spawnSync(`minikube kubectl -- delete namespace ${TEST_NAMESPACE}`, { shell: true });
if (status.status !== 0) {
console.warn(`Error removing ${TEST_NAMESPACE} namespace: ${status.stderr.toString()}`)
return false
console.warn(`Error removing ${TEST_NAMESPACE} namespace: ${status.stderr.toString()}`);
return false;
}
console.log(status.stdout.toString())
console.log(status.stdout.toString());
}
return true
}
const ready = minikubeReady()
return true;
};
const ready = minikubeReady();
const addMinikubeCluster = async (app: Application) => {
await app.client.click("div.add-cluster")
await app.client.waitUntilTextExists("div", "Select kubeconfig file")
await app.client.click("div.Select__control") // show the context drop-down list
await app.client.waitUntilTextExists("div", "minikube")
await app.client.click("div.add-cluster");
await app.client.waitUntilTextExists("div", "Select kubeconfig file");
await app.client.click("div.Select__control"); // show the context drop-down list
await app.client.waitUntilTextExists("div", "minikube");
if (!await app.client.$("button.primary").isEnabled()) {
await app.client.click("div.minikube") // select minikube context
await app.client.click("div.minikube"); // select minikube context
} // else the only context, which must be 'minikube', is automatically selected
await app.client.click("div.Select__control") // hide the context drop-down list (it might be obscuring the Add cluster(s) button)
await app.client.click("button.primary") // add minikube cluster
}
await app.client.click("div.Select__control"); // hide the context drop-down list (it might be obscuring the Add cluster(s) button)
await app.client.click("button.primary"); // add minikube cluster
};
const waitForMinikubeDashboard = async (app: Application) => {
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started")
await app.client.waitForExist(`iframe[name="minikube"]`)
await app.client.frame("minikube")
await app.client.waitUntilTextExists("span.link-text", "Cluster")
}
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started");
await app.client.waitForExist(`iframe[name="minikube"]`);
await app.client.frame("minikube");
await app.client.waitUntilTextExists("span.link-text", "Cluster");
};
describeif(ready)("cluster tests", () => {
let clusterAdded = false
let clusterAdded = false;
const addCluster = async () => {
await clickWhatsNew(app)
await addMinikubeCluster(app)
await waitForMinikubeDashboard(app)
await app.client.click('a[href="/nodes"]')
await app.client.waitUntilTextExists("div.TableCell", "Ready")
}
await clickWhatsNew(app);
await addMinikubeCluster(app);
await waitForMinikubeDashboard(app);
await app.client.click('a[href="/nodes"]');
await app.client.waitUntilTextExists("div.TableCell", "Ready");
};
describe("cluster add", () => {
beforeAll(appStart, 20000)
beforeAll(appStart, 20000);
afterAll(async () => {
if (app && app.isRunning()) {
return util.tearDown(app)
return util.tearDown(app);
}
})
});
it('allows to add a cluster', async () => {
await addCluster()
clusterAdded = true
})
})
await addCluster();
clusterAdded = true;
});
});
const appStartAddCluster = async () => {
if (clusterAdded) {
await appStart()
await addCluster()
await appStart();
await addCluster();
}
}
};
describe("cluster pages", () => {
beforeAll(appStartAddCluster, 40000)
beforeAll(appStartAddCluster, 40000);
afterAll(async () => {
if (app && app.isRunning()) {
return util.tearDown(app)
return util.tearDown(app);
}
})
});
const tests: {
drawer?: string
@ -394,119 +394,119 @@ describe("Lens integration tests", () => {
tests.forEach(({ drawer = "", drawerId = "", pages }) => {
if (drawer !== "") {
it(`shows ${drawer} drawer`, async () => {
expect(clusterAdded).toBe(true)
await app.client.click(`.sidebar-nav [data-test-id="${drawerId}"] span.link-text`)
await app.client.waitUntilTextExists(`a[href^="/${pages[0].href}"]`, pages[0].name)
})
expect(clusterAdded).toBe(true);
await app.client.click(`.sidebar-nav [data-test-id="${drawerId}"] span.link-text`);
await app.client.waitUntilTextExists(`a[href^="/${pages[0].href}"]`, pages[0].name);
});
}
pages.forEach(({ name, href, expectedSelector, expectedText }) => {
it(`shows ${drawer}->${name} page`, async () => {
expect(clusterAdded).toBe(true)
await app.client.click(`a[href^="/${href}"]`)
await app.client.waitUntilTextExists(expectedSelector, expectedText)
})
})
expect(clusterAdded).toBe(true);
await app.client.click(`a[href^="/${href}"]`);
await app.client.waitUntilTextExists(expectedSelector, expectedText);
});
});
if (drawer !== "") {
// hide the drawer
it(`hides ${drawer} drawer`, async () => {
expect(clusterAdded).toBe(true)
await app.client.click(`.sidebar-nav [data-test-id="${drawerId}"] span.link-text`)
await expect(app.client.waitUntilTextExists(`a[href^="/${pages[0].href}"]`, pages[0].name, 100)).rejects.toThrow()
})
expect(clusterAdded).toBe(true);
await app.client.click(`.sidebar-nav [data-test-id="${drawerId}"] span.link-text`);
await expect(app.client.waitUntilTextExists(`a[href^="/${pages[0].href}"]`, pages[0].name, 100)).rejects.toThrow();
});
}
})
})
});
});
describe("viewing pod logs", () => {
beforeEach(appStartAddCluster, 40000)
beforeEach(appStartAddCluster, 40000);
afterEach(async () => {
if (app && app.isRunning()) {
return util.tearDown(app)
return util.tearDown(app);
}
})
});
it(`shows a logs for a pod`, async () => {
expect(clusterAdded).toBe(true)
expect(clusterAdded).toBe(true);
// Go to Pods page
await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text")
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods")
await app.client.click('a[href^="/pods"]')
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver")
await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text");
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods");
await app.client.click('a[href^="/pods"]');
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver");
// Open logs tab in dock
await app.client.click(".list .TableRow:first-child")
await app.client.waitForVisible(".Drawer")
await app.client.click(".drawer-title .Menu li:nth-child(2)")
await app.client.click(".list .TableRow:first-child");
await app.client.waitForVisible(".Drawer");
await app.client.click(".drawer-title .Menu li:nth-child(2)");
// Check if controls are available
await app.client.waitForVisible(".PodLogs .VirtualList")
await app.client.waitForVisible(".PodLogControls")
await app.client.waitForVisible(".PodLogControls .SearchInput")
await app.client.waitForVisible(".PodLogControls .SearchInput input")
await app.client.waitForVisible(".PodLogs .VirtualList");
await app.client.waitForVisible(".PodLogControls");
await app.client.waitForVisible(".PodLogControls .SearchInput");
await app.client.waitForVisible(".PodLogControls .SearchInput input");
// Search for semicolon
await app.client.keys(":")
await app.client.waitForVisible(".PodLogs .list span.active")
await app.client.keys(":");
await app.client.waitForVisible(".PodLogs .list span.active");
// Click through controls
await app.client.click(".PodLogControls .timestamps-icon")
await app.client.click(".PodLogControls .undo-icon")
})
})
await app.client.click(".PodLogControls .timestamps-icon");
await app.client.click(".PodLogControls .undo-icon");
});
});
describe("cluster operations", () => {
beforeEach(appStartAddCluster, 40000)
beforeEach(appStartAddCluster, 40000);
afterEach(async () => {
if (app && app.isRunning()) {
return util.tearDown(app)
return util.tearDown(app);
}
})
});
it('shows default namespace', async () => {
expect(clusterAdded).toBe(true)
await app.client.click('a[href="/namespaces"]')
await app.client.waitUntilTextExists("div.TableCell", "default")
await app.client.waitUntilTextExists("div.TableCell", "kube-system")
})
expect(clusterAdded).toBe(true);
await app.client.click('a[href="/namespaces"]');
await app.client.waitUntilTextExists("div.TableCell", "default");
await app.client.waitUntilTextExists("div.TableCell", "kube-system");
});
it(`creates ${TEST_NAMESPACE} namespace`, async () => {
expect(clusterAdded).toBe(true)
await app.client.click('a[href="/namespaces"]')
await app.client.waitUntilTextExists("div.TableCell", "default")
await app.client.waitUntilTextExists("div.TableCell", "kube-system")
await app.client.click("button.add-button")
await app.client.waitUntilTextExists("div.AddNamespaceDialog", "Create Namespace")
await app.client.keys(`${TEST_NAMESPACE}\n`)
await app.client.waitForExist(`.name=${TEST_NAMESPACE}`)
})
expect(clusterAdded).toBe(true);
await app.client.click('a[href="/namespaces"]');
await app.client.waitUntilTextExists("div.TableCell", "default");
await app.client.waitUntilTextExists("div.TableCell", "kube-system");
await app.client.click("button.add-button");
await app.client.waitUntilTextExists("div.AddNamespaceDialog", "Create Namespace");
await app.client.keys(`${TEST_NAMESPACE}\n`);
await app.client.waitForExist(`.name=${TEST_NAMESPACE}`);
});
it(`creates a pod in ${TEST_NAMESPACE} namespace`, async () => {
expect(clusterAdded).toBe(true)
await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text")
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods")
await app.client.click('a[href^="/pods"]')
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver")
await app.client.click('.Icon.new-dock-tab')
await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource")
await app.client.click("li.MenuItem.create-resource-tab")
await app.client.waitForVisible(".CreateResource div.ace_content")
expect(clusterAdded).toBe(true);
await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text");
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods");
await app.client.click('a[href^="/pods"]');
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver");
await app.client.click('.Icon.new-dock-tab');
await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource");
await app.client.click("li.MenuItem.create-resource-tab");
await app.client.waitForVisible(".CreateResource div.ace_content");
// Write pod manifest to editor
await app.client.keys("apiVersion: v1\n")
await app.client.keys("kind: Pod\n")
await app.client.keys("metadata:\n")
await app.client.keys(" name: nginx-create-pod-test\n")
await app.client.keys(`namespace: ${TEST_NAMESPACE}\n`)
await app.client.keys(BACKSPACE + "spec:\n")
await app.client.keys(" containers:\n")
await app.client.keys("- name: nginx-create-pod-test\n")
await app.client.keys(" image: nginx:alpine\n")
await app.client.keys("apiVersion: v1\n");
await app.client.keys("kind: Pod\n");
await app.client.keys("metadata:\n");
await app.client.keys(" name: nginx-create-pod-test\n");
await app.client.keys(`namespace: ${TEST_NAMESPACE}\n`);
await app.client.keys(BACKSPACE + "spec:\n");
await app.client.keys(" containers:\n");
await app.client.keys("- name: nginx-create-pod-test\n");
await app.client.keys(" image: nginx:alpine\n");
// Create deployment
await app.client.waitForEnabled("button.Button=Create & Close")
await app.client.click("button.Button=Create & Close")
await app.client.waitForEnabled("button.Button=Create & Close");
await app.client.click("button.Button=Create & Close");
// Wait until first bits of pod appears on dashboard
await app.client.waitForExist(".name=nginx-create-pod-test")
await app.client.waitForExist(".name=nginx-create-pod-test");
// Open pod details
await app.client.click(".name=nginx-create-pod-test")
await app.client.waitUntilTextExists("div.drawer-title-text", "Pod: nginx-create-pod-test")
})
})
})
})
await app.client.click(".name=nginx-create-pod-test");
await app.client.waitUntilTextExists("div.drawer-title-text", "Pod: nginx-create-pod-test");
});
});
});
});

View File

@ -4,7 +4,7 @@ const AppPaths: Partial<Record<NodeJS.Platform, string>> = {
"win32": "./dist/win-unpacked/Lens.exe",
"linux": "./dist/linux-unpacked/kontena-lens",
"darwin": "./dist/mac/Lens.app/Contents/MacOS/Lens",
}
};
export function setup(): Application {
return new Application({
@ -16,16 +16,16 @@ export function setup(): Application {
env: {
CICD: "true"
}
})
});
}
export async function tearDown(app: Application) {
let mpid: any = app.mainProcess.pid
let pid = await mpid()
await app.stop()
const mpid: any = app.mainProcess.pid;
const pid = await mpid();
await app.stop();
try {
process.kill(pid, "SIGKILL");
} catch (e) {
console.error(e)
console.error(e);
}
}

View File

@ -37,7 +37,8 @@
"download:kubectl": "yarn run ts-node build/download_kubectl.ts",
"download:helm": "yarn run ts-node build/download_helm.ts",
"build:tray-icons": "yarn run ts-node build/build_tray_icon.ts",
"lint": "yarn run eslint $@ --ext js,ts,tsx --max-warnings=0 src/",
"lint": "yarn run eslint $@ --ext js,ts,tsx --max-warnings=0 src/ integration/ __mocks__/ build/",
"lint:fix": "yarn run lint --fix",
"mkdocs-serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
"typedocs-extensions-api": "yarn run typedoc --ignoreCompilerErrors --readme docs/extensions/typedoc-readme.md.tpl --name @k8slens/extensions --out docs/extensions/api --mode library --excludePrivate --hideBreadcrumbs --includes src/ src/extensions/extension-api.ts"
},

View File

@ -5,9 +5,9 @@ import { Cluster } from "../../main/cluster";
import { ClusterStore } from "../cluster-store";
import { workspaceStore } from "../workspace-store";
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png")
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png");
console.log("") // fix bug
console.log(""); // fix bug
let clusterStore: ClusterStore;
@ -18,15 +18,15 @@ describe("empty config", () => {
'tmp': {
'lens-cluster-store.json': JSON.stringify({})
}
}
};
mockFs(mockOpts);
clusterStore = ClusterStore.getInstance<ClusterStore>();
return clusterStore.load();
})
});
afterEach(() => {
mockFs.restore();
})
});
describe("with foo cluster added", () => {
beforeEach(() => {
@ -43,30 +43,30 @@ describe("empty config", () => {
workspace: workspaceStore.currentWorkspaceId
})
);
})
});
it("adds new cluster to store", async () => {
const storedCluster = clusterStore.getById("foo");
expect(storedCluster.id).toBe("foo");
expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
expect(storedCluster.preferences.icon).toBe("data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5");
})
});
it("adds cluster to default workspace", () => {
const storedCluster = clusterStore.getById("foo");
expect(storedCluster.workspace).toBe("default");
})
});
it("removes cluster from store", async () => {
await clusterStore.removeById("foo");
expect(clusterStore.getById("foo")).toBeUndefined();
})
});
it("sets active cluster", () => {
clusterStore.setActive("foo");
expect(clusterStore.active.id).toBe("foo");
})
})
});
});
describe("with prod and dev clusters added", () => {
beforeEach(() => {
@ -89,8 +89,8 @@ describe("empty config", () => {
kubeConfigPath: ClusterStore.embedCustomKubeConfig("dev", "fancy config"),
workspace: "workstation"
})
)
})
);
});
it("check if store can contain multiple clusters", () => {
expect(clusterStore.hasClusters()).toBeTruthy();
@ -104,42 +104,42 @@ describe("empty config", () => {
expect(wsClusters.length).toBe(2);
expect(wsClusters[0].id).toBe("prod");
expect(wsClusters[1].id).toBe("dev");
})
});
it("check if cluster's kubeconfig file saved", () => {
const file = ClusterStore.embedCustomKubeConfig("boo", "kubeconfig");
expect(fs.readFileSync(file, "utf8")).toBe("kubeconfig");
})
});
it("check if reorderring works for same from and to", () => {
clusterStore.swapIconOrders("workstation", 1, 1)
clusterStore.swapIconOrders("workstation", 1, 1);
const clusters = clusterStore.getByWorkspaceId("workstation");
expect(clusters[0].id).toBe("prod")
expect(clusters[0].preferences.iconOrder).toBe(0)
expect(clusters[1].id).toBe("dev")
expect(clusters[1].preferences.iconOrder).toBe(1)
})
expect(clusters[0].id).toBe("prod");
expect(clusters[0].preferences.iconOrder).toBe(0);
expect(clusters[1].id).toBe("dev");
expect(clusters[1].preferences.iconOrder).toBe(1);
});
it("check if reorderring works for different from and to", () => {
clusterStore.swapIconOrders("workstation", 0, 1)
clusterStore.swapIconOrders("workstation", 0, 1);
const clusters = clusterStore.getByWorkspaceId("workstation");
expect(clusters[0].id).toBe("dev")
expect(clusters[0].preferences.iconOrder).toBe(0)
expect(clusters[1].id).toBe("prod")
expect(clusters[1].preferences.iconOrder).toBe(1)
})
expect(clusters[0].id).toBe("dev");
expect(clusters[0].preferences.iconOrder).toBe(0);
expect(clusters[1].id).toBe("prod");
expect(clusters[1].preferences.iconOrder).toBe(1);
});
it("check if after icon reordering, changing workspaces still works", () => {
clusterStore.swapIconOrders("workstation", 1, 1)
clusterStore.getById("prod").workspace = "default"
clusterStore.swapIconOrders("workstation", 1, 1);
clusterStore.getById("prod").workspace = "default";
expect(clusterStore.getByWorkspaceId("workstation").length).toBe(1);
expect(clusterStore.getByWorkspaceId("default").length).toBe(1);
})
})
})
});
});
});
describe("config with existing clusters", () => {
beforeEach(() => {
@ -176,21 +176,21 @@ describe("config with existing clusters", () => {
]
})
}
}
};
mockFs(mockOpts);
clusterStore = ClusterStore.getInstance<ClusterStore>();
return clusterStore.load();
})
});
afterEach(() => {
mockFs.restore();
})
});
it("allows to retrieve a cluster", () => {
const storedCluster = clusterStore.getById('cluster1');
expect(storedCluster.id).toBe('cluster1');
expect(storedCluster.preferences.terminalCWD).toBe('/foo');
})
});
it("allows to delete a cluster", () => {
clusterStore.removeById('cluster2');
@ -198,18 +198,18 @@ describe("config with existing clusters", () => {
expect(storedCluster).toBeTruthy();
const storedCluster2 = clusterStore.getById('cluster2');
expect(storedCluster2).toBeUndefined();
})
});
it("allows getting all of the clusters", async () => {
const storedClusters = clusterStore.clustersList;
expect(storedClusters.length).toBe(3)
expect(storedClusters[0].id).toBe('cluster1')
expect(storedClusters[0].preferences.terminalCWD).toBe('/foo')
expect(storedClusters[1].id).toBe('cluster2')
expect(storedClusters[1].preferences.terminalCWD).toBe('/foo2')
expect(storedClusters[2].id).toBe('cluster3')
})
})
expect(storedClusters.length).toBe(3);
expect(storedClusters[0].id).toBe('cluster1');
expect(storedClusters[0].preferences.terminalCWD).toBe('/foo');
expect(storedClusters[1].id).toBe('cluster2');
expect(storedClusters[1].preferences.terminalCWD).toBe('/foo2');
expect(storedClusters[2].id).toBe('cluster3');
});
});
describe("pre 2.0 config with an existing cluster", () => {
beforeEach(() => {
@ -229,17 +229,17 @@ describe("pre 2.0 config with an existing cluster", () => {
mockFs(mockOpts);
clusterStore = ClusterStore.getInstance<ClusterStore>();
return clusterStore.load();
})
});
afterEach(() => {
mockFs.restore();
})
});
it("migrates to modern format with kubeconfig in a file", async () => {
const config = clusterStore.clustersList[0].kubeConfigPath;
expect(fs.readFileSync(config, "utf8")).toBe("kubeconfig content");
})
})
});
});
describe("pre 2.6.0 config with a cluster that has arrays in auth config", () => {
beforeEach(() => {
@ -257,15 +257,15 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
},
})
}
}
};
mockFs(mockOpts);
clusterStore = ClusterStore.getInstance<ClusterStore>();
return clusterStore.load();
})
});
afterEach(() => {
mockFs.restore();
})
});
it("replaces array format access token and expiry into string", async () => {
const file = clusterStore.clustersList[0].kubeConfigPath;
@ -273,8 +273,8 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
const kc = yaml.safeLoad(config);
expect(kc.users[0].user['auth-provider'].config['access-token']).toBe("should be string");
expect(kc.users[0].user['auth-provider'].config['expiry']).toBe("should be string");
})
})
});
});
describe("pre 2.6.0 config with a cluster icon", () => {
beforeEach(() => {
@ -297,23 +297,23 @@ describe("pre 2.6.0 config with a cluster icon", () => {
}),
"icon_path": testDataIcon,
}
}
};
mockFs(mockOpts);
clusterStore = ClusterStore.getInstance<ClusterStore>();
return clusterStore.load();
})
});
afterEach(() => {
mockFs.restore();
})
});
it("moves the icon into preferences", async () => {
const storedClusterData = clusterStore.clustersList[0];
expect(storedClusterData.hasOwnProperty('icon')).toBe(false);
expect(storedClusterData.preferences.hasOwnProperty('icon')).toBe(true);
expect(storedClusterData.preferences.icon.startsWith("data:;base64,")).toBe(true);
})
})
});
});
describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
beforeEach(() => {
@ -334,21 +334,21 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
},
})
}
}
};
mockFs(mockOpts);
clusterStore = ClusterStore.getInstance<ClusterStore>();
return clusterStore.load();
})
});
afterEach(() => {
mockFs.restore();
})
});
it("adds cluster to default workspace", async () => {
const storedClusterData = clusterStore.clustersList[0];
expect(storedClusterData.workspace).toBe('default');
})
})
});
});
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
beforeEach(() => {
@ -378,19 +378,19 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
mockFs(mockOpts);
clusterStore = ClusterStore.getInstance<ClusterStore>();
return clusterStore.load();
})
});
afterEach(() => {
mockFs.restore();
})
});
it("migrates to modern format with kubeconfig in a file", async () => {
const config = clusterStore.clustersList[0].kubeConfigPath;
expect(fs.readFileSync(config, "utf8")).toBe("kubeconfig content");
})
});
it("migrates to modern format with icon not in file", async () => {
const { icon } = clusterStore.clustersList[0].preferences;
expect(icon.startsWith("data:;base64,")).toBe(true);
})
})
});
});

View File

@ -1,15 +1,15 @@
import { appEventBus, AppEvent } from "../event-bus"
import { appEventBus, AppEvent } from "../event-bus";
describe("event bus tests", () => {
describe("emit", () => {
it("emits an event", () => {
let event: AppEvent = null
let event: AppEvent = null;
appEventBus.addListener((data) => {
event = data
})
event = data;
});
appEventBus.emit({name: "foo", action: "bar"})
expect(event.name).toBe("foo")
})
})
})
appEventBus.emit({name: "foo", action: "bar"});
expect(event.name).toBe("foo");
});
});
});

View File

@ -2,7 +2,7 @@
* @jest-environment jsdom
*/
import { SearchStore } from "../search-store"
import { SearchStore } from "../search-store";
let searchStore: SearchStore = null;
@ -10,17 +10,17 @@ const logs = [
"1:M 30 Oct 2020 16:17:41.553 # Connection with replica 172.17.0.12:6379 lost",
"1:M 30 Oct 2020 16:17:41.623 * Replica 172.17.0.12:6379 asks for synchronization",
"1:M 30 Oct 2020 16:17:41.623 * Starting Partial resynchronization request from 172.17.0.12:6379 accepted. Sending 0 bytes of backlog starting from offset 14407."
]
];
describe("search store tests", () => {
beforeEach(async () => {
searchStore = new SearchStore();
})
});
it("does nothing with empty search query", () => {
searchStore.onSearch([], "");
expect(searchStore.occurrences).toEqual([]);
})
});
it("doesn't break if no text provided", () => {
searchStore.onSearch(null, "replica");
@ -28,53 +28,53 @@ describe("search store tests", () => {
searchStore.onSearch([], "replica");
expect(searchStore.occurrences).toEqual([]);
})
});
it("find 3 occurences across 3 lines", () => {
searchStore.onSearch(logs, "172");
expect(searchStore.occurrences).toEqual([0, 1, 2]);
})
});
it("find occurences within 1 line (case-insensitive)", () => {
searchStore.onSearch(logs, "Starting");
expect(searchStore.occurrences).toEqual([2, 2]);
})
});
it("sets overlay index equal to first occurence", () => {
searchStore.onSearch(logs, "Replica");
expect(searchStore.activeOverlayIndex).toBe(0);
})
});
it("set overlay index to next occurence", () => {
searchStore.onSearch(logs, "172");
searchStore.setNextOverlayActive();
expect(searchStore.activeOverlayIndex).toBe(1);
})
});
it("sets overlay to last occurence", () => {
searchStore.onSearch(logs, "172");
searchStore.setPrevOverlayActive();
expect(searchStore.activeOverlayIndex).toBe(2);
})
});
it("gets line index where overlay is located", () => {
searchStore.onSearch(logs, "synchronization");
expect(searchStore.activeOverlayLine).toBe(1);
})
});
it("escapes string for using in regex", () => {
const regex = searchStore.escapeRegex("some.interesting-query\\#?()[]");
expect(regex).toBe("some\\.interesting\\-query\\\\\\#\\?\\(\\)\\[\\]");
})
});
it("gets active find number", () => {
searchStore.onSearch(logs, "172");
searchStore.setNextOverlayActive();
expect(searchStore.activeFind).toBe(2);
})
});
it("gets total finds number", () => {
searchStore.onSearch(logs, "Starting");
expect(searchStore.totalFinds).toBe(2);
})
})
});
});

View File

@ -1,4 +1,4 @@
import mockFs from "mock-fs"
import mockFs from "mock-fs";
jest.mock("electron", () => {
return {
@ -7,55 +7,55 @@ jest.mock("electron", () => {
getPath: () => 'tmp',
getLocale: () => 'en'
}
}
})
};
});
import { UserStore } from "../user-store"
import { SemVer } from "semver"
import electron from "electron"
import { UserStore } from "../user-store";
import { SemVer } from "semver";
import electron from "electron";
describe("user store tests", () => {
describe("for an empty config", () => {
beforeEach(() => {
UserStore.resetInstance()
mockFs({ tmp: { 'config.json': "{}" } })
})
UserStore.resetInstance();
mockFs({ tmp: { 'config.json': "{}" } });
});
afterEach(() => {
mockFs.restore()
})
mockFs.restore();
});
it("allows setting and retrieving lastSeenAppVersion", () => {
const us = UserStore.getInstance<UserStore>();
us.lastSeenAppVersion = "1.2.3";
expect(us.lastSeenAppVersion).toBe("1.2.3");
})
});
it("allows adding and listing seen contexts", () => {
const us = UserStore.getInstance<UserStore>();
us.seenContexts.add('foo')
expect(us.seenContexts.size).toBe(1)
us.seenContexts.add('foo');
expect(us.seenContexts.size).toBe(1);
us.seenContexts.add('foo')
us.seenContexts.add('bar')
expect(us.seenContexts.size).toBe(2) // check 'foo' isn't added twice
expect(us.seenContexts.has('foo')).toBe(true)
expect(us.seenContexts.has('bar')).toBe(true)
})
us.seenContexts.add('foo');
us.seenContexts.add('bar');
expect(us.seenContexts.size).toBe(2); // check 'foo' isn't added twice
expect(us.seenContexts.has('foo')).toBe(true);
expect(us.seenContexts.has('bar')).toBe(true);
});
it("allows setting and getting preferences", () => {
const us = UserStore.getInstance<UserStore>();
us.preferences.httpsProxy = 'abcd://defg';
expect(us.preferences.httpsProxy).toBe('abcd://defg')
expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme)
expect(us.preferences.httpsProxy).toBe('abcd://defg');
expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme);
us.preferences.colorTheme = "light";
expect(us.preferences.colorTheme).toBe('light')
})
expect(us.preferences.colorTheme).toBe('light');
});
it("correctly resets theme to default value", async () => {
const us = UserStore.getInstance<UserStore>();
@ -64,7 +64,7 @@ describe("user store tests", () => {
us.preferences.colorTheme = "some other theme";
await us.resetTheme();
expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme);
})
});
it("correctly calculates if the last seen version is an old release", () => {
const us = UserStore.getInstance<UserStore>();
@ -73,12 +73,12 @@ describe("user store tests", () => {
us.lastSeenAppVersion = (new SemVer(electron.app.getVersion())).inc("major").format();
expect(us.isNewVersion).toBe(false);
})
})
});
});
describe("migrations", () => {
beforeEach(() => {
UserStore.resetInstance()
UserStore.resetInstance();
mockFs({
'tmp': {
'config.json': JSON.stringify({
@ -87,17 +87,17 @@ describe("user store tests", () => {
lastSeenAppVersion: '1.2.3'
})
}
})
})
});
});
afterEach(() => {
mockFs.restore()
})
mockFs.restore();
});
it("sets last seen app version to 0.0.0", () => {
const us = UserStore.getInstance<UserStore>();
expect(us.lastSeenAppVersion).toBe('0.0.0')
})
})
})
expect(us.lastSeenAppVersion).toBe('0.0.0');
});
});
});

View File

@ -1,4 +1,4 @@
import mockFs from "mock-fs"
import mockFs from "mock-fs";
jest.mock("electron", () => {
return {
@ -7,36 +7,36 @@ jest.mock("electron", () => {
getPath: () => 'tmp',
getLocale: () => 'en'
}
}
})
};
});
import { Workspace, WorkspaceStore } from "../workspace-store"
import { Workspace, WorkspaceStore } from "../workspace-store";
describe("workspace store tests", () => {
describe("for an empty config", () => {
beforeEach(async () => {
WorkspaceStore.resetInstance()
mockFs({ tmp: { 'lens-workspace-store.json': "{}" } })
WorkspaceStore.resetInstance();
mockFs({ tmp: { 'lens-workspace-store.json': "{}" } });
await WorkspaceStore.getInstance<WorkspaceStore>().load();
})
});
afterEach(() => {
mockFs.restore()
})
mockFs.restore();
});
it("default workspace should always exist", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
expect(ws.workspaces.size).toBe(1);
expect(ws.getById(WorkspaceStore.defaultId)).not.toBe(null);
})
});
it("cannot remove the default workspace", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
expect(() => ws.removeWorkspaceById(WorkspaceStore.defaultId)).toThrowError("Cannot remove");
})
});
it("can update workspace description", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
@ -50,7 +50,7 @@ describe("workspace store tests", () => {
ws.updateWorkspace(workspace);
expect(ws.getById("foobar").description).toBe("Foobar description");
})
});
it("can add workspaces", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
@ -61,13 +61,13 @@ describe("workspace store tests", () => {
}));
expect(ws.getById("123").name).toBe("foobar");
})
});
it("cannot set a non-existent workspace to be active", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
expect(() => ws.setActive("abc")).toThrow("doesn't exist");
})
});
it("can set a existent workspace to be active", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
@ -78,7 +78,7 @@ describe("workspace store tests", () => {
}));
expect(() => ws.setActive("abc")).not.toThrowError();
})
});
it("can remove a workspace", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
@ -94,7 +94,7 @@ describe("workspace store tests", () => {
ws.removeWorkspaceById("123");
expect(ws.workspaces.size).toBe(2);
})
});
it("cannot create workspace with existent name", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
@ -105,7 +105,7 @@ describe("workspace store tests", () => {
}));
expect(ws.workspacesList.length).toBe(1); // default workspace only
})
});
it("cannot create workspace with empty name", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
@ -116,7 +116,7 @@ describe("workspace store tests", () => {
}));
expect(ws.workspacesList.length).toBe(1); // default workspace only
})
});
it("cannot create workspace with ' ' name", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
@ -127,7 +127,7 @@ describe("workspace store tests", () => {
}));
expect(ws.workspacesList.length).toBe(1); // default workspace only
})
});
it("trim workspace name", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
@ -138,12 +138,12 @@ describe("workspace store tests", () => {
}));
expect(ws.workspacesList.length).toBe(1); // default workspace only
})
})
});
});
describe("for a non-empty config", () => {
beforeEach(async () => {
WorkspaceStore.resetInstance()
WorkspaceStore.resetInstance();
mockFs({
tmp: {
'lens-workspace-store.json': JSON.stringify({
@ -157,19 +157,19 @@ describe("workspace store tests", () => {
}]
})
}
})
});
await WorkspaceStore.getInstance<WorkspaceStore>().load();
})
});
afterEach(() => {
mockFs.restore()
})
mockFs.restore();
});
it("doesn't revert to default workspace", async () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
expect(ws.currentWorkspaceId).toBe("abc");
})
})
})
});
});
});

View File

@ -1,7 +1,7 @@
import path from "path"
import Config from "conf"
import { Options as ConfOptions } from "conf/dist/source/types"
import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron"
import path from "path";
import Config from "conf";
import { Options as ConfOptions } from "conf/dist/source/types";
import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron";
import { action, IReactionOptions, observable, reaction, runInAction, toJS, when } from "mobx";
import Singleton from "./utils/singleton";
import { getAppVersion } from "./utils/app-version";
@ -32,7 +32,7 @@ export abstract class BaseStore<T = any> extends Singleton {
autoLoad: false,
syncEnabled: true,
...params,
}
};
this.init();
}
@ -41,11 +41,11 @@ export abstract class BaseStore<T = any> extends Singleton {
}
protected get syncRendererChannel() {
return `store-sync-renderer:${this.path}`
return `store-sync-renderer:${this.path}`;
}
protected get syncMainChannel() {
return `store-sync-main:${this.path}`
return `store-sync-main:${this.path}`;
}
get path() {
@ -76,7 +76,7 @@ export abstract class BaseStore<T = any> extends Singleton {
}
protected cwd() {
return (app || remote.app).getPath("userData")
return (app || remote.app).getPath("userData");
}
protected async saveToFile(model: T) {
@ -96,7 +96,7 @@ export abstract class BaseStore<T = any> extends Singleton {
logger.silly(`[STORE]: SYNC ${this.name} from renderer`, { model });
this.onSync(model);
};
subscribeToBroadcast(this.syncMainChannel, callback)
subscribeToBroadcast(this.syncMainChannel, callback);
this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncMainChannel, callback));
}
if (ipcRenderer) {
@ -104,20 +104,20 @@ export abstract class BaseStore<T = any> extends Singleton {
logger.silly(`[STORE]: SYNC ${this.name} from main`, { model });
this.onSyncFromMain(model);
};
subscribeToBroadcast(this.syncRendererChannel, callback)
subscribeToBroadcast(this.syncRendererChannel, callback);
this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncRendererChannel, callback));
}
}
protected onSyncFromMain(model: T) {
this.applyWithoutSync(() => {
this.onSync(model)
})
this.onSync(model);
});
}
unregisterIpcListener() {
ipcRenderer.removeAllListeners(this.syncMainChannel)
ipcRenderer.removeAllListeners(this.syncRendererChannel)
ipcRenderer.removeAllListeners(this.syncMainChannel);
ipcRenderer.removeAllListeners(this.syncRendererChannel);
}
disableSync() {
@ -143,9 +143,9 @@ export abstract class BaseStore<T = any> extends Singleton {
protected async onModelChange(model: T) {
if (ipcMain) {
this.saveToFile(model); // save config file
broadcastMessage(this.syncRendererChannel, model)
broadcastMessage(this.syncRendererChannel, model);
} else {
broadcastMessage(this.syncMainChannel, model)
broadcastMessage(this.syncMainChannel, model);
}
}

View File

@ -1,3 +1,3 @@
import { observable } from "mobx"
import { observable } from "mobx";
export const clusterFrameMap = observable.map<string, number>();

View File

@ -1,15 +1,15 @@
import { handleRequest } from "./ipc";
import { ClusterId, clusterStore } from "./cluster-store";
import { appEventBus } from "./event-bus"
import { appEventBus } from "./event-bus";
import { ResourceApplier } from "../main/resource-applier";
import { ipcMain } from "electron";
import { clusterFrameMap } from "./cluster-frames"
import { clusterFrameMap } from "./cluster-frames";
export const clusterActivateHandler = "cluster:activate"
export const clusterSetFrameIdHandler = "cluster:set-frame-id"
export const clusterRefreshHandler = "cluster:refresh"
export const clusterDisconnectHandler = "cluster:disconnect"
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all"
export const clusterActivateHandler = "cluster:activate";
export const clusterSetFrameIdHandler = "cluster:set-frame-id";
export const clusterRefreshHandler = "cluster:refresh";
export const clusterDisconnectHandler = "cluster:disconnect";
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
if (ipcMain) {
@ -18,38 +18,38 @@ if (ipcMain) {
if (cluster) {
return cluster.activate(force);
}
})
});
handleRequest(clusterSetFrameIdHandler, (event, clusterId: ClusterId, frameId: number) => {
const cluster = clusterStore.getById(clusterId);
if (cluster) {
clusterFrameMap.set(cluster.id, frameId)
clusterFrameMap.set(cluster.id, frameId);
return cluster.pushState();
}
})
});
handleRequest(clusterRefreshHandler, (event, clusterId: ClusterId) => {
const cluster = clusterStore.getById(clusterId);
if (cluster) return cluster.refresh({ refreshMetadata: true })
})
if (cluster) return cluster.refresh({ refreshMetadata: true });
});
handleRequest(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
appEventBus.emit({name: "cluster", action: "stop"});
const cluster = clusterStore.getById(clusterId);
if (cluster) {
cluster.disconnect();
clusterFrameMap.delete(cluster.id)
clusterFrameMap.delete(cluster.id);
}
})
});
handleRequest(clusterKubectlApplyAllHandler, (event, clusterId: ClusterId, resources: string[]) => {
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"})
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"});
const cluster = clusterStore.getById(clusterId);
if (cluster) {
const applier = new ResourceApplier(cluster)
applier.kubectlApplyAll(resources)
const applier = new ResourceApplier(cluster);
applier.kubectlApplyAll(resources);
} else {
throw `${clusterId} is not a valid cluster id`;
}
})
});
}

View File

@ -5,9 +5,9 @@ import { unlink } from "fs-extra";
import { action, computed, observable, reaction, toJS } from "mobx";
import { BaseStore } from "./base-store";
import { Cluster, ClusterState } from "../main/cluster";
import migrations from "../migrations/cluster-store"
import migrations from "../migrations/cluster-store";
import logger from "../main/logger";
import { appEventBus } from "./event-bus"
import { appEventBus } from "./event-bus";
import { dumpConfigYaml } from "./kube-helpers";
import { saveToAppFiles } from "./utils/saveToAppFiles";
import { KubeConfig } from "@kubernetes/client-node";
@ -86,38 +86,38 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
migrations: migrations,
});
this.pushStateToViewsAutomatically()
this.pushStateToViewsAutomatically();
}
protected pushStateToViewsAutomatically() {
if (!ipcRenderer) {
reaction(() => this.connectedClustersList, () => {
this.pushState()
})
this.pushState();
});
}
}
registerIpcListener() {
logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`)
logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`);
subscribeToBroadcast("cluster:state", (event, clusterId: string, state: ClusterState) => {
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, clusterId, state);
this.getById(clusterId)?.setState(state)
})
this.getById(clusterId)?.setState(state);
});
}
unregisterIpcListener() {
super.unregisterIpcListener()
unsubscribeAllFromBroadcast("cluster:state")
super.unregisterIpcListener();
unsubscribeAllFromBroadcast("cluster:state");
}
pushState() {
this.clusters.forEach((c) => {
c.pushState()
})
c.pushState();
});
}
get activeClusterId() {
return this.activeCluster
return this.activeCluster;
}
@computed get clustersList(): Cluster[] {
@ -125,7 +125,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
}
@computed get enabledClustersList(): Cluster[] {
return this.clustersList.filter((c) => c.enabled)
return this.clustersList.filter((c) => c.enabled);
}
@computed get active(): Cluster | null {
@ -133,7 +133,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
}
@computed get connectedClustersList(): Cluster[] {
return this.clustersList.filter((c) => !c.disconnected)
return this.clustersList.filter((c) => !c.disconnected);
}
isActive(id: ClusterId) {
@ -149,7 +149,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
swapIconOrders(workspace: WorkspaceId, from: number, to: number) {
const clusters = this.getByWorkspaceId(workspace);
if (from < 0 || to < 0 || from >= clusters.length || to >= clusters.length || isNaN(from) || isNaN(to)) {
throw new Error(`invalid from<->to arguments`)
throw new Error(`invalid from<->to arguments`);
}
move.mutate(clusters, from, to);
@ -170,37 +170,37 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
getByWorkspaceId(workspaceId: string): Cluster[] {
const clusters = Array.from(this.clusters.values())
.filter(cluster => cluster.workspace === workspaceId);
return _.sortBy(clusters, cluster => cluster.preferences.iconOrder)
return _.sortBy(clusters, cluster => cluster.preferences.iconOrder);
}
@action
addClusters(...models: ClusterModel[]): Cluster[] {
const clusters: Cluster[] = []
const clusters: Cluster[] = [];
models.forEach(model => {
clusters.push(this.addCluster(model))
})
clusters.push(this.addCluster(model));
});
return clusters
return clusters;
}
@action
addCluster(model: ClusterModel | Cluster): Cluster {
appEventBus.emit({ name: "cluster", action: "add" })
appEventBus.emit({ name: "cluster", action: "add" });
let cluster = model as Cluster;
if (!(model instanceof Cluster)) {
cluster = new Cluster(model)
cluster = new Cluster(model);
}
this.clusters.set(model.id, cluster);
return cluster
return cluster;
}
async removeCluster(model: ClusterModel) {
await this.removeById(model.id)
await this.removeById(model.id);
}
@action
async removeById(clusterId: ClusterId) {
appEventBus.emit({ name: "cluster", action: "remove" })
appEventBus.emit({ name: "cluster", action: "remove" });
const cluster = this.getById(clusterId);
if (cluster) {
this.clusters.delete(clusterId);
@ -217,8 +217,8 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
@action
removeByWorkspaceId(workspaceId: string) {
this.getByWorkspaceId(workspaceId).forEach(cluster => {
this.removeById(cluster.id)
})
this.removeById(cluster.id);
});
}
@action
@ -235,7 +235,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
} else {
cluster = new Cluster(clusterModel);
if (!cluster.isManaged) {
cluster.enabled = true
cluster.enabled = true;
}
}
newClusters.set(clusterModel.id, cluster);
@ -259,7 +259,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
clusters: this.clustersList.map(cluster => cluster.toJSON()),
}, {
recurseEverything: true
})
});
}
}

View File

@ -1,9 +1,9 @@
import { EventEmitter } from "./event-emitter"
import { EventEmitter } from "./event-emitter";
export type AppEvent = {
name: string;
action: string;
params?: object;
}
};
export const appEventBus = new EventEmitter<[AppEvent]>()
export const appEventBus = new EventEmitter<[AppEvent]>();

View File

@ -35,6 +35,6 @@ export class EventEmitter<D extends [...any[]]> {
const result = callback(...data);
if (result === false) return; // break cycle
return true;
})
});
}
}

View File

@ -7,24 +7,24 @@ import logger from "../main/logger";
import { clusterFrameMap } from "./cluster-frames";
export function handleRequest(channel: string, listener: (...args: any[]) => any) {
ipcMain.handle(channel, listener)
ipcMain.handle(channel, listener);
}
export async function requestMain(channel: string, ...args: any[]) {
return ipcRenderer.invoke(channel, ...args)
return ipcRenderer.invoke(channel, ...args);
}
async function getSubFrames(): Promise<number[]> {
const subFrames: number[] = [];
clusterFrameMap.forEach((frameId, _) => {
subFrames.push(frameId)
subFrames.push(frameId);
});
return subFrames;
}
export function broadcastMessage(channel: string, ...args: any[]) {
const views = (webContents || remote?.webContents)?.getAllWebContents();
if (!views) return
if (!views) return;
views.forEach(webContent => {
const type = webContent.getType();
@ -32,39 +32,39 @@ export function broadcastMessage(channel: string, ...args: any[]) {
webContent.send(channel, ...args);
getSubFrames().then((frames) => {
frames.map((frameId) => {
webContent.sendToFrame(frameId, channel, ...args)
})
}).catch((e) => e)
})
webContent.sendToFrame(frameId, channel, ...args);
});
}).catch((e) => e);
});
if (ipcRenderer) {
ipcRenderer.send(channel, ...args)
ipcRenderer.send(channel, ...args);
} else {
ipcMain.emit(channel, ...args)
ipcMain.emit(channel, ...args);
}
}
export function subscribeToBroadcast(channel: string, listener: (...args: any[]) => any) {
if (ipcRenderer) {
ipcRenderer.on(channel, listener)
ipcRenderer.on(channel, listener);
} else {
ipcMain.on(channel, listener)
ipcMain.on(channel, listener);
}
return listener
return listener;
}
export function unsubscribeFromBroadcast(channel: string, listener: (...args: any[]) => any) {
if (ipcRenderer) {
ipcRenderer.off(channel, listener)
ipcRenderer.off(channel, listener);
} else {
ipcMain.off(channel, listener)
ipcMain.off(channel, listener);
}
}
export function unsubscribeAllFromBroadcast(channel: string) {
if (ipcRenderer) {
ipcRenderer.removeAllListeners(channel)
ipcRenderer.removeAllListeners(channel);
} else {
ipcMain.removeAllListeners(channel)
ipcMain.removeAllListeners(channel);
}
}

View File

@ -1,8 +1,8 @@
import { KubeConfig, V1Node, V1Pod } from "@kubernetes/client-node"
import { KubeConfig, V1Node, V1Pod } from "@kubernetes/client-node";
import fse from "fs-extra";
import path from "path"
import os from "os"
import yaml from "js-yaml"
import path from "path";
import os from "os";
import yaml from "js-yaml";
import logger from "../main/logger";
import commandExists from "command-exists";
import { ExecValidationNotFoundError } from "./custom-errors";
@ -25,7 +25,7 @@ export function loadConfig(pathOrContent?: string): KubeConfig {
kc.loadFromString(pathOrContent);
}
return kc
return kc;
}
/**
@ -39,33 +39,33 @@ export function validateConfig(config: KubeConfig | string): KubeConfig {
if (typeof config == "string") {
config = loadConfig(config);
}
logger.debug(`validating kube config: ${JSON.stringify(config)}`)
logger.debug(`validating kube config: ${JSON.stringify(config)}`);
if (!config.users || config.users.length == 0) {
throw new Error("No users provided in config")
throw new Error("No users provided in config");
}
if (!config.clusters || config.clusters.length == 0) {
throw new Error("No clusters provided in config")
throw new Error("No clusters provided in config");
}
if (!config.contexts || config.contexts.length == 0) {
throw new Error("No contexts provided in config")
throw new Error("No contexts provided in config");
}
return config
return config;
}
/**
* Breaks kube config into several configs. Each context as it own KubeConfig object
*/
export function splitConfig(kubeConfig: KubeConfig): KubeConfig[] {
const configs: KubeConfig[] = []
const configs: KubeConfig[] = [];
if (!kubeConfig.contexts) {
return configs;
}
kubeConfig.contexts.forEach(ctx => {
const kc = new KubeConfig();
kc.clusters = [kubeConfig.getCluster(ctx.cluster)].filter(n => n);
kc.users = [kubeConfig.getUser(ctx.user)].filter(n => n)
kc.contexts = [kubeConfig.getContextObject(ctx.name)].filter(n => n)
kc.users = [kubeConfig.getUser(ctx.user)].filter(n => n);
kc.contexts = [kubeConfig.getContextObject(ctx.name)].filter(n => n);
kc.setCurrentContext(ctx.name);
configs.push(kc);
@ -88,7 +88,7 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
server: cluster.server,
'insecure-skip-tls-verify': cluster.skipTLSVerify
}
}
};
}),
contexts: kubeConfig.contexts.map(context => {
return {
@ -98,7 +98,7 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
user: context.user,
namespace: context.namespace
}
}
};
}),
users: kubeConfig.users.map(user => {
return {
@ -114,9 +114,9 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
username: user.username,
password: user.password
}
}
};
})
}
};
logger.debug("Dumping KubeConfig:", config);
@ -127,20 +127,20 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
export function podHasIssues(pod: V1Pod) {
// Logic adapted from dashboard
const notReady = !!pod.status.conditions.find(condition => {
return condition.type == "Ready" && condition.status !== "True"
return condition.type == "Ready" && condition.status !== "True";
});
return (
notReady ||
pod.status.phase !== "Running" ||
pod.spec.priority > 500000 // We're interested in high prio pods events regardless of their running status
)
);
}
export function getNodeWarningConditions(node: V1Node) {
return node.status.conditions.filter(c =>
c.status.toLowerCase() === "true" && c.type !== "Ready" && c.type !== "HostUpgrades"
)
);
}
/**

View File

@ -5,8 +5,8 @@ import { PrometheusStacklight } from "../main/prometheus/stacklight";
import { PrometheusProviderRegistry } from "../main/prometheus/provider-registry";
[PrometheusLens, PrometheusHelm, PrometheusOperator, PrometheusStacklight].forEach(providerClass => {
const provider = new providerClass()
PrometheusProviderRegistry.registerProvider(provider.id, provider)
const provider = new providerClass();
PrometheusProviderRegistry.registerProvider(provider.id, provider);
});
export const prometheusProviders = PrometheusProviderRegistry.getProviders()
export const prometheusProviders = PrometheusProviderRegistry.getProviders();

View File

@ -4,7 +4,7 @@ export type KubeResource =
"namespaces" | "nodes" | "events" | "resourcequotas" |
"services" | "secrets" | "configmaps" | "ingresses" | "networkpolicies" | "persistentvolumes" | "storageclasses" |
"pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "jobs" | "cronjobs" |
"endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets"
"endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets";
export interface KubeApiResource {
resource: KubeResource; // valid resource name

View File

@ -1,6 +1,6 @@
// Register custom protocols
import { protocol } from "electron"
import { protocol } from "electron";
import path from "path";
export function registerFileProtocol(name: string, basePath: string) {
@ -8,5 +8,5 @@ export function registerFileProtocol(name: string, basePath: string) {
const filePath = request.url.replace(name + "://", "");
const absPath = path.resolve(basePath, filePath);
callback({ path: absPath });
})
});
}

View File

@ -1,28 +1,28 @@
import request from "request"
import requestPromise from "request-promise-native"
import { userStore } from "./user-store"
import request from "request";
import requestPromise from "request-promise-native";
import { userStore } from "./user-store";
// todo: get rid of "request" (deprecated)
// https://github.com/lensapp/lens/issues/459
function getDefaultRequestOpts(): Partial<request.Options> {
const { httpsProxy, allowUntrustedCAs } = userStore.preferences
const { httpsProxy, allowUntrustedCAs } = userStore.preferences;
return {
proxy: httpsProxy || undefined,
rejectUnauthorized: !allowUntrustedCAs,
}
};
}
/**
* @deprecated
*/
export function customRequest(opts: request.Options) {
return request.defaults(getDefaultRequestOpts())(opts)
return request.defaults(getDefaultRequestOpts())(opts);
}
/**
* @deprecated
*/
export function customRequestPromise(opts: requestPromise.Options) {
return requestPromise.defaults(getDefaultRequestOpts())(opts)
return requestPromise.defaults(getDefaultRequestOpts())(opts);
}

View File

@ -1,14 +1,14 @@
import { isMac, isWindows } from "./vars";
import winca from "win-ca"
import macca from "mac-ca"
import logger from "../main/logger"
import winca from "win-ca";
import macca from "mac-ca";
import logger from "../main/logger";
if (isMac) {
for (const crt of macca.all()) {
const attributes = crt.issuer?.attributes?.map((a: any) => `${a.name}=${a.value}`)
logger.debug("Using host CA: " + attributes.join(","))
const attributes = crt.issuer?.attributes?.map((a: any) => `${a.name}=${a.value}`);
logger.debug("Using host CA: " + attributes.join(","));
}
}
if (isWindows) {
winca.inject("+") // see: https://github.com/ukoloff/win-ca#caveats
winca.inject("+"); // see: https://github.com/ukoloff/win-ca#caveats
}

View File

@ -1,13 +1,13 @@
import type { ThemeId } from "../renderer/theme.store";
import { app, remote } from 'electron';
import semver from "semver"
import { readFile } from "fs-extra"
import semver from "semver";
import { readFile } from "fs-extra";
import { action, observable, reaction, toJS } from "mobx";
import { BaseStore } from "./base-store";
import migrations from "../migrations/user-store"
import migrations from "../migrations/user-store";
import { getAppVersion } from "./utils/app-version";
import { kubeConfigDefaultPath, loadConfig } from "./kube-helpers";
import { appEventBus } from "./event-bus"
import { appEventBus } from "./event-bus";
import logger from "../main/logger";
import path from 'path';
@ -31,7 +31,7 @@ export interface UserPreferences {
}
export class UserStore extends BaseStore<UserStoreModel> {
static readonly defaultTheme: ThemeId = "lens-dark"
static readonly defaultTheme: ThemeId = "lens-dark";
private constructor() {
super({
@ -42,7 +42,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
this.handleOnLoad();
}
@observable lastSeenAppVersion = "0.0.0"
@observable lastSeenAppVersion = "0.0.0";
@observable kubeConfigPath = kubeConfigDefaultPath; // used in add-cluster page for providing context
@observable seenContexts = observable.set<string>();
@observable newContexts = observable.set<string>();
@ -66,7 +66,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
if (app) {
// track telemetry availability
reaction(() => this.preferences.allowTelemetry, allowed => {
appEventBus.emit({name: "telemetry", action: allowed ? "enabled" : "disabled"})
appEventBus.emit({name: "telemetry", action: allowed ? "enabled" : "disabled"});
});
// open at system start-up
@ -95,7 +95,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
@action
saveLastSeenAppVersion() {
appEventBus.emit({name: "app", action: "whats-new-seen"})
appEventBus.emit({name: "app", action: "whats-new-seen"});
this.lastSeenAppVersion = getAppVersion();
}
@ -113,7 +113,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
logger.error(err);
this.resetKubeConfigPath();
}
}
};
@action
markNewContextsAsSeen() {
@ -127,12 +127,12 @@ export class UserStore extends BaseStore<UserStoreModel> {
* @returns string
*/
getDefaultKubectlPath(): string {
return path.join((app || remote.app).getPath("userData"), "binaries")
return path.join((app || remote.app).getPath("userData"), "binaries");
}
@action
protected async fromStore(data: Partial<UserStoreModel> = {}) {
const { lastSeenAppVersion, seenContexts = [], preferences, kubeConfigPath } = data
const { lastSeenAppVersion, seenContexts = [], preferences, kubeConfigPath } = data;
if (lastSeenAppVersion) {
this.lastSeenAppVersion = lastSeenAppVersion;
}
@ -149,10 +149,10 @@ export class UserStore extends BaseStore<UserStoreModel> {
lastSeenAppVersion: this.lastSeenAppVersion,
seenContexts: Array.from(this.seenContexts),
preferences: this.preferences,
}
};
return toJS(model, {
recurseEverything: true,
})
});
}
}

View File

@ -1,4 +1,4 @@
import packageInfo from "../../../package.json"
import packageInfo from "../../../package.json";
export function getAppVersion(): string {
return packageInfo.version;
@ -9,5 +9,5 @@ export function getBundledKubectlVersion(): string {
}
export function getBundledExtensions(): string[] {
return packageInfo.lens?.extensions || []
return packageInfo.lens?.extensions || [];
}

View File

@ -6,7 +6,7 @@ export function autobind() {
return function (target: Constructor | object, prop?: string, descriptor?: PropertyDescriptor) {
if (target instanceof Function) return bindClass(target);
else return bindMethod(target, prop, descriptor);
}
};
}
function bindClass<T extends Constructor>(constructor: T) {
@ -22,12 +22,12 @@ function bindClass<T extends Constructor>(constructor: T) {
if (skipMethod(prop)) return;
const boundDescriptor = bindMethod(proto, prop, descriptors[prop]);
Object.defineProperty(proto, prop, boundDescriptor);
})
});
}
function bindMethod(target: object, prop?: string, descriptor?: PropertyDescriptor) {
if (!descriptor || typeof descriptor.value !== "function") {
throw new Error(`@autobind() must be used on class or method only`)
throw new Error(`@autobind() must be used on class or method only`);
}
const { value: func, enumerable, configurable } = descriptor;
const boundFunc = new WeakMap<object, Function>();

View File

@ -1,4 +1,4 @@
import { compile } from "path-to-regexp"
import { compile } from "path-to-regexp";
export interface IURLParams<P extends object = {}, Q extends object = {}> {
params?: P;
@ -8,7 +8,7 @@ export interface IURLParams<P extends object = {}, Q extends object = {}> {
export function buildURL<P extends object = {}, Q extends object = {}>(path: string | any) {
const pathBuilder = compile(String(path));
return function ({ params, query }: IURLParams<P, Q> = {}) {
const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : ""
return pathBuilder(params) + (queryParams ? `?${queryParams}` : "")
}
const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : "";
return pathBuilder(params) + (queryParams ? `?${queryParams}` : "");
};
}

View File

@ -5,7 +5,7 @@
export function defineGlobal(propName: string, descriptor: PropertyDescriptor) {
const scope = typeof global !== "undefined" ? global : window;
if (scope.hasOwnProperty(propName)) {
console.info(`Global variable "${propName}" already exists. Skipping.`)
console.info(`Global variable "${propName}" already exists. Skipping.`);
return;
}
Object.defineProperty(scope, propName, descriptor);

View File

@ -1,14 +1,14 @@
// Common utils (main OR renderer)
export * from "./app-version"
export * from "./autobind"
export * from "./base64"
export * from "./camelCase"
export * from "./cloneJson"
export * from "./debouncePromise"
export * from "./defineGlobal"
export * from "./getRandId"
export * from "./splitArray"
export * from "./saveToAppFiles"
export * from "./singleton"
export * from "./openExternal"
export * from "./app-version";
export * from "./autobind";
export * from "./base64";
export * from "./camelCase";
export * from "./cloneJson";
export * from "./debouncePromise";
export * from "./defineGlobal";
export * from "./getRandId";
export * from "./splitArray";
export * from "./saveToAppFiles";
export * from "./singleton";
export * from "./openExternal";

View File

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

View File

@ -2,7 +2,7 @@
import path from "path";
import { app, remote } from "electron";
import { ensureDirSync, writeFileSync } from "fs-extra";
import { WriteFileOptions } from "fs"
import { WriteFileOptions } from "fs";
export function saveToAppFiles(filePath: string, contents: any, options?: WriteFileOptions): string {
const absPath = path.resolve((app || remote.app).getPath("userData"), filePath);

View File

@ -24,5 +24,5 @@ class Singleton {
}
}
export { Singleton }
export { Singleton };
export default Singleton;

View File

@ -15,5 +15,5 @@ export function splitArray<T>(array: T[], element: T): [T[], T[], boolean] {
if (index < 0) {
return [array, [], false];
}
return [array.slice(0, index), array.slice(index + 1, array.length), true]
return [array.slice(0, index), array.slice(index + 1, array.length), true];
}

View File

@ -1,19 +1,19 @@
// App's common configuration for any process (main, renderer, build pipeline, etc.)
import path from "path";
import packageInfo from "../../package.json"
import packageInfo from "../../package.json";
import { defineGlobal } from "./utils/defineGlobal";
export const isMac = process.platform === "darwin"
export const isWindows = process.platform === "win32"
export const isLinux = process.platform === "linux"
export const isMac = process.platform === "darwin";
export const isWindows = process.platform === "win32";
export const isLinux = process.platform === "linux";
export const isDebugging = process.env.DEBUG === "true";
export const isSnap = !!process.env["SNAP"]
export const isProduction = process.env.NODE_ENV === "production"
export const isSnap = !!process.env["SNAP"];
export const isProduction = process.env.NODE_ENV === "production";
export const isTestEnv = !!process.env.JEST_WORKER_ID;
export const isDevelopment = !isTestEnv && !isProduction;
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`
export const publicPath = "/build/"
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`;
export const publicPath = "/build/";
// Webpack build paths
export const contextDir = process.cwd();
@ -22,7 +22,7 @@ export const mainDir = path.join(contextDir, "src/main");
export const rendererDir = path.join(contextDir, "src/renderer");
export const htmlTemplate = path.resolve(rendererDir, "template.html");
export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");
export const webpackDevServerPort = 9009
export const webpackDevServerPort = 9009;
// Special runtime paths
defineGlobal("__static", {
@ -30,14 +30,14 @@ defineGlobal("__static", {
if (isDevelopment) {
return path.resolve(contextDir, "static");
}
return path.resolve(process.resourcesPath, "static")
return path.resolve(process.resourcesPath, "static");
}
})
});
// Apis
export const apiPrefix = "/api" // local router apis
export const apiKubePrefix = "/api-kube" // k8s cluster apis
export const apiPrefix = "/api"; // local router apis
export const apiKubePrefix = "/api-kube"; // k8s cluster apis
// Links
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues"
export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI"
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues";
export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI";

View File

@ -1,7 +1,7 @@
import { ipcRenderer } from "electron";
import { action, computed, observable, toJS, reaction } from "mobx";
import { BaseStore } from "./base-store";
import { clusterStore } from "./cluster-store"
import { clusterStore } from "./cluster-store";
import { appEventBus } from "./event-bus";
import { broadcastMessage } from "../common/ipc";
import logger from "../main/logger";
@ -25,40 +25,40 @@ export interface WorkspaceState {
}
export class Workspace implements WorkspaceModel, WorkspaceState {
@observable id: WorkspaceId
@observable name: string
@observable description?: string
@observable ownerRef?: string
@observable enabled: boolean
@observable id: WorkspaceId;
@observable name: string;
@observable description?: string;
@observable ownerRef?: string;
@observable enabled: boolean;
constructor(data: WorkspaceModel) {
Object.assign(this, data)
Object.assign(this, data);
if (!ipcRenderer) {
reaction(() => this.getState(), () => {
this.pushState()
})
this.pushState();
});
}
}
get isManaged(): boolean {
return !!this.ownerRef
return !!this.ownerRef;
}
getState(): WorkspaceState {
return {
enabled: this.enabled
}
};
}
pushState(state = this.getState()) {
logger.silly("[WORKSPACE] pushing state", {...state, id: this.id})
broadcastMessage("workspace:state", this.id, toJS(state))
logger.silly("[WORKSPACE] pushing state", {...state, id: this.id});
broadcastMessage("workspace:state", this.id, toJS(state));
}
@action
setState(state: WorkspaceState) {
Object.assign(this, state)
Object.assign(this, state);
}
toJSON(): WorkspaceModel {
@ -67,12 +67,12 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
name: this.name,
description: this.description,
ownerRef: this.ownerRef
})
});
}
}
export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
static readonly defaultId: WorkspaceId = "default"
static readonly defaultId: WorkspaceId = "default";
private constructor() {
super({
@ -81,21 +81,21 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
if (!ipcRenderer) {
setInterval(() => {
this.pushState()
}, 5000)
this.pushState();
}, 5000);
}
}
registerIpcListener() {
logger.info("[WORKSPACE-STORE] starting to listen state events")
logger.info("[WORKSPACE-STORE] starting to listen state events");
ipcRenderer.on("workspace:state", (event, workspaceId: string, state: WorkspaceState) => {
this.getById(workspaceId)?.setState(state)
})
this.getById(workspaceId)?.setState(state);
});
}
unregisterIpcListener() {
super.unregisterIpcListener()
ipcRenderer.removeAllListeners("workspace:state")
super.unregisterIpcListener();
ipcRenderer.removeAllListeners("workspace:state");
}
@observable currentWorkspaceId = WorkspaceStore.defaultId;
@ -121,8 +121,8 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
pushState() {
this.workspaces.forEach((w) => {
w.pushState()
})
w.pushState();
});
}
isDefault(id: WorkspaceId) {
@ -154,7 +154,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
return;
}
this.workspaces.set(id, workspace);
appEventBus.emit({name: "workspace", action: "add"})
appEventBus.emit({name: "workspace", action: "add"});
return workspace;
}
@ -166,7 +166,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
@action
removeWorkspace(workspace: Workspace) {
this.removeWorkspaceById(workspace.id)
this.removeWorkspaceById(workspace.id);
}
@action
@ -180,24 +180,24 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
this.currentWorkspaceId = WorkspaceStore.defaultId; // reset to default
}
this.workspaces.delete(id);
appEventBus.emit({name: "workspace", action: "remove"})
clusterStore.removeByWorkspaceId(id)
appEventBus.emit({name: "workspace", action: "remove"});
clusterStore.removeByWorkspaceId(id);
}
@action
protected fromStore({ currentWorkspace, workspaces = [] }: WorkspaceStoreModel) {
if (currentWorkspace) {
this.currentWorkspaceId = currentWorkspace
this.currentWorkspaceId = currentWorkspace;
}
if (workspaces.length) {
this.workspaces.clear();
workspaces.forEach(ws => {
const workspace = new Workspace(ws)
const workspace = new Workspace(ws);
if (!workspace.isManaged) {
workspace.enabled = true
workspace.enabled = true;
}
this.workspaces.set(workspace.id, workspace)
})
this.workspaces.set(workspace.id, workspace);
});
}
}
@ -207,8 +207,8 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
workspaces: this.workspacesList.map((w) => w.toJSON()),
}, {
recurseEverything: true
})
});
}
}
export const workspaceStore = WorkspaceStore.getInstance<WorkspaceStore>()
export const workspaceStore = WorkspaceStore.getInstance<WorkspaceStore>();

View File

@ -1,6 +1,6 @@
import { LensExtension } from "../lens-extension"
import { LensExtension } from "../lens-extension";
let ext: LensExtension = null
let ext: LensExtension = null;
describe("lens extension", () => {
beforeEach(async () => {
@ -12,12 +12,12 @@ describe("lens extension", () => {
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true
})
})
});
});
describe("name", () => {
it("returns name", () => {
expect(ext.name).toBe("foo-bar")
})
})
})
expect(ext.name).toBe("foo-bar");
});
});
});

View File

@ -1,11 +1,11 @@
import fs from "fs";
import path from "path"
import hb from "handlebars"
import { observable } from "mobx"
import { ResourceApplier } from "../main/resource-applier"
import path from "path";
import hb from "handlebars";
import { observable } from "mobx";
import { ResourceApplier } from "../main/resource-applier";
import { Cluster } from "../main/cluster";
import logger from "../main/logger";
import { app } from "electron"
import { app } from "electron";
import { requestMain } from "../common/ipc";
import { clusterKubectlApplyAllHandler } from "../common/cluster-ipc";
@ -26,7 +26,7 @@ export abstract class ClusterFeature {
installed: false,
latestVersion: null,
canUpgrade: false
}
};
abstract async install(cluster: Cluster): Promise<void>;
@ -38,9 +38,9 @@ export abstract class ClusterFeature {
protected async applyResources(cluster: Cluster, resources: string[]) {
if (app) {
await new ResourceApplier(cluster).kubectlApplyAll(resources)
await new ResourceApplier(cluster).kubectlApplyAll(resources);
} else {
await requestMain(clusterKubectlApplyAllHandler, cluster.id, resources)
await requestMain(clusterKubectlApplyAllHandler, cluster.id, resources);
}
}

View File

@ -1,4 +1,4 @@
import { getAppVersion } from "../../common/utils";
export const version = getAppVersion()
export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars"
export const version = getAppVersion();
export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars";

View File

@ -1,2 +1,2 @@
export { ClusterFeature as Feature } from "../cluster-feature"
export type { ClusterFeatureStatus as FeatureStatus } from "../cluster-feature"
export { ClusterFeature as Feature } from "../cluster-feature";
export type { ClusterFeatureStatus as FeatureStatus } from "../cluster-feature";

View File

@ -1,2 +1,2 @@
export { appEventBus } from "../../common/event-bus"
export type { AppEvent } from "../../common/event-bus"
export { appEventBus } from "../../common/event-bus";
export type { AppEvent } from "../../common/event-bus";

View File

@ -1,14 +1,14 @@
// Lens-extensions api developer's kit
export * from "../lens-main-extension"
export * from "../lens-renderer-extension"
export * from "../lens-main-extension";
export * from "../lens-renderer-extension";
// APIs
import * as App from "./app"
import * as EventBus from "./event-bus"
import * as Store from "./stores"
import * as Util from "./utils"
import * as ClusterFeature from "./cluster-feature"
import * as Interface from "../interfaces"
import * as App from "./app";
import * as EventBus from "./event-bus";
import * as Store from "./stores";
import * as Util from "./utils";
import * as ClusterFeature from "./cluster-feature";
import * as Interface from "../interfaces";
export {
App,
@ -17,4 +17,4 @@ export {
Interface,
Store,
Util,
}
};

View File

@ -1,6 +1,6 @@
export { ExtensionStore } from "../extension-store"
export { clusterStore } from "../../common/cluster-store"
export type { ClusterModel } from "../../common/cluster-store"
export { Cluster } from "../../main/cluster"
export { workspaceStore, Workspace } from "../../common/workspace-store"
export type { WorkspaceModel } from "../../common/workspace-store"
export { ExtensionStore } from "../extension-store";
export { clusterStore } from "../../common/cluster-store";
export type { ClusterModel } from "../../common/cluster-store";
export { Cluster } from "../../main/cluster";
export { workspaceStore, Workspace } from "../../common/workspace-store";
export type { WorkspaceModel } from "../../common/workspace-store";

View File

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

View File

@ -1,4 +1,4 @@
// Extension-api types generation bundle
export * from "./core-api"
export * from "./renderer-api"
export * from "./core-api";
export * from "./renderer-api";

View File

@ -1,24 +1,24 @@
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "./lens-extension"
import type { LensMainExtension } from "./lens-main-extension"
import type { LensRendererExtension } from "./lens-renderer-extension"
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "./lens-extension";
import type { LensMainExtension } from "./lens-main-extension";
import type { LensRendererExtension } from "./lens-renderer-extension";
import type { InstalledExtension } from "./extension-manager";
import path from "path"
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc"
import { action, computed, observable, reaction, toJS, when } from "mobx"
import logger from "../main/logger"
import { app, ipcRenderer, remote } from "electron"
import path from "path";
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
import { action, computed, observable, reaction, toJS, when } from "mobx";
import logger from "../main/logger";
import { app, ipcRenderer, remote } from "electron";
import * as registries from "./registries";
import { extensionsStore } from "./extensions-store";
// lazy load so that we get correct userData
export function extensionPackagesRoot() {
return path.join((app || remote.app).getPath("userData"))
return path.join((app || remote.app).getPath("userData"));
}
export class ExtensionLoader {
protected extensions = observable.map<LensExtensionId, InstalledExtension>();
protected instances = observable.map<LensExtensionId, LensExtension>();
protected readonly requestExtensionsChannel = "extensions:loaded"
protected readonly requestExtensionsChannel = "extensions:loaded";
@observable isLoaded = false;
whenLoaded = when(() => this.isLoaded);
@ -29,22 +29,22 @@ export class ExtensionLoader {
if (ext.isBundled) {
extensions.delete(extId);
}
})
});
return extensions;
}
@action
async init() {
if (ipcRenderer) {
this.initRenderer()
this.initRenderer();
} else {
this.initMain()
this.initMain();
}
extensionsStore.manageState(this);
}
initExtensions(extensions?: Map<LensExtensionId, InstalledExtension>) {
this.extensions.replace(extensions)
this.extensions.replace(extensions);
}
protected async initMain() {
@ -53,12 +53,12 @@ export class ExtensionLoader {
this.broadcastExtensions();
reaction(() => this.extensions.toJS(), () => {
this.broadcastExtensions()
})
this.broadcastExtensions();
});
handleRequest(this.requestExtensionsChannel, () => {
return Array.from(this.toJSON())
})
return Array.from(this.toJSON());
});
}
protected async initRenderer() {
@ -66,25 +66,25 @@ export class ExtensionLoader {
this.isLoaded = true;
extensions.forEach(([extId, ext]) => {
if (!this.extensions.has(extId)) {
this.extensions.set(extId, ext)
this.extensions.set(extId, ext);
}
})
}
requestMain(this.requestExtensionsChannel).then(extensionListHandler)
});
};
requestMain(this.requestExtensionsChannel).then(extensionListHandler);
subscribeToBroadcast(this.requestExtensionsChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => {
extensionListHandler(extensions)
extensionListHandler(extensions);
});
}
loadOnMain() {
logger.info('[EXTENSIONS-LOADER]: load on main')
logger.info('[EXTENSIONS-LOADER]: load on main');
this.autoInitExtensions((ext: LensMainExtension) => [
registries.menuRegistry.add(ext.appMenus)
]);
}
loadOnClusterManagerRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)');
this.autoInitExtensions((ext: LensRendererExtension) => [
registries.globalPageRegistry.add(ext.globalPages, ext),
registries.globalPageMenuRegistry.add(ext.globalPageMenus, ext),
@ -95,14 +95,14 @@ export class ExtensionLoader {
}
loadOnClusterRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)');
this.autoInitExtensions((ext: LensRendererExtension) => [
registries.clusterPageRegistry.add(ext.clusterPages, ext),
registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, ext),
registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems),
registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems),
registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts)
])
]);
}
protected autoInitExtensions(register: (ext: LensExtension) => Function[]) {
@ -111,43 +111,43 @@ export class ExtensionLoader {
let instance = this.instances.get(extId);
if (ext.isEnabled && !instance) {
try {
const LensExtensionClass: LensExtensionConstructor = this.requireExtension(ext)
const LensExtensionClass: LensExtensionConstructor = this.requireExtension(ext);
if (!LensExtensionClass) continue;
instance = new LensExtensionClass(ext);
instance.whenEnabled(() => register(instance));
instance.enable();
this.instances.set(extId, instance);
} catch (err) {
logger.error(`[EXTENSION-LOADER]: activation extension error`, { ext, err })
logger.error(`[EXTENSION-LOADER]: activation extension error`, { ext, err });
}
} else if (!ext.isEnabled && instance) {
try {
instance.disable();
this.instances.delete(extId);
} catch (err) {
logger.error(`[EXTENSION-LOADER]: deactivation extension error`, { ext, err })
logger.error(`[EXTENSION-LOADER]: deactivation extension error`, { ext, err });
}
}
}
}, {
fireImmediately: true,
})
});
}
protected requireExtension(extension: InstalledExtension) {
let extEntrypoint = ""
let extEntrypoint = "";
try {
if (ipcRenderer && extension.manifest.renderer) {
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.renderer))
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.renderer));
} else if (!ipcRenderer && extension.manifest.main) {
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main))
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main));
}
if (extEntrypoint !== "") {
return __non_webpack_require__(extEntrypoint).default;
}
} catch (err) {
console.error(`[EXTENSION-LOADER]: can't load extension main at ${extEntrypoint}: ${err}`, { extension });
console.trace(err)
console.trace(err);
}
}
@ -159,11 +159,11 @@ export class ExtensionLoader {
return toJS(this.extensions, {
exportMapsAsObjects: false,
recurseEverything: true,
})
});
}
broadcastExtensions() {
broadcastMessage(this.requestExtensionsChannel, Array.from(this.toJSON()))
broadcastMessage(this.requestExtensionsChannel, Array.from(this.toJSON()));
}
}

View File

@ -1,11 +1,11 @@
import type { LensExtensionId, LensExtensionManifest } from "./lens-extension"
import path from "path"
import os from "os"
import fs from "fs-extra"
import type { LensExtensionId, LensExtensionManifest } from "./lens-extension";
import path from "path";
import os from "os";
import fs from "fs-extra";
import child_process from "child_process";
import logger from "../main/logger"
import { extensionPackagesRoot } from "./extension-loader"
import { getBundledExtensions } from "../common/utils/app-version"
import logger from "../main/logger";
import { extensionPackagesRoot } from "./extension-loader";
import { getBundledExtensions } from "../common/utils/app-version";
export interface InstalledExtension {
readonly manifest: LensExtensionManifest;
@ -16,26 +16,26 @@ export interface InstalledExtension {
type Dependencies = {
[name: string]: string;
}
};
type PackageJson = {
dependencies: Dependencies;
}
};
export class ExtensionManager {
protected bundledFolderPath: string
protected bundledFolderPath: string;
protected packagesJson: PackageJson = {
dependencies: {}
}
};
get extensionPackagesRoot() {
return extensionPackagesRoot()
return extensionPackagesRoot();
}
get inTreeTargetPath() {
return path.join(this.extensionPackagesRoot, "extensions")
return path.join(this.extensionPackagesRoot, "extensions");
}
get inTreeFolderPath(): string {
@ -43,7 +43,7 @@ export class ExtensionManager {
}
get nodeModulesPath(): string {
return path.join(this.extensionPackagesRoot, "node_modules")
return path.join(this.extensionPackagesRoot, "node_modules");
}
get localFolderPath(): string {
@ -51,30 +51,30 @@ export class ExtensionManager {
}
get npmPath() {
return __non_webpack_require__.resolve('npm/bin/npm-cli')
return __non_webpack_require__.resolve('npm/bin/npm-cli');
}
get packageJsonPath() {
return path.join(this.extensionPackagesRoot, "package.json")
return path.join(this.extensionPackagesRoot, "package.json");
}
async load(): Promise<Map<LensExtensionId, InstalledExtension>> {
logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot)
logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot);
if (fs.existsSync(path.join(this.extensionPackagesRoot, "package-lock.json"))) {
await fs.remove(path.join(this.extensionPackagesRoot, "package-lock.json"))
await fs.remove(path.join(this.extensionPackagesRoot, "package-lock.json"));
}
try {
await fs.access(this.inTreeFolderPath, fs.constants.W_OK)
this.bundledFolderPath = this.inTreeFolderPath
await fs.access(this.inTreeFolderPath, fs.constants.W_OK);
this.bundledFolderPath = this.inTreeFolderPath;
} catch {
// we need to copy in-tree extensions so that we can symlink them properly on "npm install"
await fs.remove(this.inTreeTargetPath)
await fs.ensureDir(this.inTreeTargetPath)
await fs.copy(this.inTreeFolderPath, this.inTreeTargetPath)
this.bundledFolderPath = this.inTreeTargetPath
await fs.remove(this.inTreeTargetPath);
await fs.ensureDir(this.inTreeTargetPath);
await fs.copy(this.inTreeFolderPath, this.inTreeTargetPath);
this.bundledFolderPath = this.inTreeTargetPath;
}
await fs.ensureDir(this.nodeModulesPath)
await fs.ensureDir(this.localFolderPath)
await fs.ensureDir(this.nodeModulesPath);
await fs.ensureDir(this.localFolderPath);
return await this.loadExtensions();
}
@ -82,16 +82,16 @@ export class ExtensionManager {
let manifestJson: LensExtensionManifest;
try {
fs.accessSync(manifestPath, fs.constants.F_OK); // check manifest file for existence
manifestJson = __non_webpack_require__(manifestPath)
this.packagesJson.dependencies[manifestJson.name] = path.dirname(manifestPath)
manifestJson = __non_webpack_require__(manifestPath);
this.packagesJson.dependencies[manifestJson.name] = path.dirname(manifestPath);
logger.info("[EXTENSION-MANAGER] installed extension " + manifestJson.name)
logger.info("[EXTENSION-MANAGER] installed extension " + manifestJson.name);
return {
manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"),
manifest: manifestJson,
isBundled: isBundled,
isEnabled: isBundled,
}
};
} catch (err) {
logger.error(`[EXTENSION-MANAGER]: can't install extension at ${manifestPath}: ${err}`, { manifestJson });
}
@ -102,65 +102,65 @@ export class ExtensionManager {
const child = child_process.fork(this.npmPath, ["install", "--silent", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock"], {
cwd: extensionPackagesRoot(),
silent: true
})
});
child.on("close", () => {
resolve()
})
resolve();
});
child.on("error", (err) => {
reject(err)
})
})
reject(err);
});
});
}
async loadExtensions() {
const bundledExtensions = await this.loadBundledExtensions()
const localExtensions = await this.loadFromFolder(this.localFolderPath)
await fs.writeFile(path.join(this.packageJsonPath), JSON.stringify(this.packagesJson, null, 2), { mode: 0o600 })
await this.installPackages()
const extensions = bundledExtensions.concat(localExtensions)
const bundledExtensions = await this.loadBundledExtensions();
const localExtensions = await this.loadFromFolder(this.localFolderPath);
await fs.writeFile(path.join(this.packageJsonPath), JSON.stringify(this.packagesJson, null, 2), { mode: 0o600 });
await this.installPackages();
const extensions = bundledExtensions.concat(localExtensions);
return new Map(extensions.map(ext => [ext.manifestPath, ext]));
}
async loadBundledExtensions() {
const extensions: InstalledExtension[] = []
const folderPath = this.bundledFolderPath
const bundledExtensions = getBundledExtensions()
const extensions: InstalledExtension[] = [];
const folderPath = this.bundledFolderPath;
const bundledExtensions = getBundledExtensions();
const paths = await fs.readdir(folderPath);
for (const fileName of paths) {
if (!bundledExtensions.includes(fileName)) {
continue
continue;
}
const absPath = path.resolve(folderPath, fileName);
const manifestPath = path.resolve(absPath, "package.json");
const ext = await this.getByManifest(manifestPath, { isBundled: true }).catch(() => null)
const ext = await this.getByManifest(manifestPath, { isBundled: true }).catch(() => null);
if (ext) {
extensions.push(ext)
extensions.push(ext);
}
}
logger.debug(`[EXTENSION-MANAGER]: ${extensions.length} extensions loaded`, { folderPath, extensions });
return extensions
return extensions;
}
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
const bundledExtensions = getBundledExtensions()
const extensions: InstalledExtension[] = []
const bundledExtensions = getBundledExtensions();
const extensions: InstalledExtension[] = [];
const paths = await fs.readdir(folderPath);
for (const fileName of paths) {
if (bundledExtensions.includes(fileName)) { // do no allow to override bundled extensions
continue
continue;
}
const absPath = path.resolve(folderPath, fileName);
if (!fs.existsSync(absPath)) {
continue
continue;
}
const lstat = await fs.lstat(absPath)
const lstat = await fs.lstat(absPath);
if (!lstat.isDirectory() && !lstat.isSymbolicLink()) { // skip non-directories
continue
continue;
}
const manifestPath = path.resolve(absPath, "package.json");
const ext = await this.getByManifest(manifestPath).catch(() => null)
const ext = await this.getByManifest(manifestPath).catch(() => null);
if (ext) {
extensions.push(ext)
extensions.push(ext);
}
}
@ -169,4 +169,4 @@ export class ExtensionManager {
}
}
export const extensionManager = new ExtensionManager()
export const extensionManager = new ExtensionManager();

View File

@ -1,21 +1,21 @@
import { BaseStore } from "../common/base-store"
import * as path from "path"
import { LensExtension } from "./lens-extension"
import { BaseStore } from "../common/base-store";
import * as path from "path";
import { LensExtension } from "./lens-extension";
export abstract class ExtensionStore<T> extends BaseStore<T> {
protected extension: LensExtension
protected extension: LensExtension;
async loadExtension(extension: LensExtension) {
this.extension = extension
return super.load()
this.extension = extension;
return super.load();
}
async load() {
if (!this.extension) { return }
return super.load()
if (!this.extension) { return; }
return super.load();
}
protected cwd() {
return path.join(super.cwd(), "extension-store", this.extension.name)
return path.join(super.cwd(), "extension-store", this.extension.name);
}
}

View File

@ -1,6 +1,6 @@
import type { LensExtensionId } from "./lens-extension";
import type { ExtensionLoader } from "./extension-loader";
import { BaseStore } from "../common/base-store"
import { BaseStore } from "../common/base-store";
import { action, observable, reaction, toJS } from "mobx";
export interface LensExtensionsStoreModel {
@ -25,9 +25,9 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
return Array.from(extensionLoader.userExtensions).reduce((state, [extId, ext]) => {
state[extId] = {
enabled: ext.isEnabled,
}
};
return state;
}, state)
}, state);
}
async manageState(extensionLoader: ExtensionLoader) {
@ -46,13 +46,13 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
if (ext && !ext.isBundled) {
ext.isEnabled = state.enabled;
}
})
})
});
});
// save state on change `extension.isEnabled`
reaction(() => this.getState(extensionLoader), extensionsState => {
this.state.merge(extensionsState)
})
this.state.merge(extensionsState);
});
}
isEnabled(extId: LensExtensionId) {
@ -70,7 +70,7 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
extensions: this.state.toJSON(),
}, {
recurseEverything: true
})
});
}
}

View File

@ -1 +1 @@
export * from "./registrations"
export * from "./registrations";

View File

@ -1,8 +1,8 @@
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../registries/app-preference-registry"
export type { ClusterFeatureRegistration, ClusterFeatureComponents } from "../registries/cluster-feature-registry"
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry"
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry"
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry"
export type { PageRegistration, PageComponents } from "../registries/page-registry"
export type { PageMenuRegistration, PageMenuComponents } from "../registries/page-menu-registry"
export type { StatusBarRegistration } from "../registries/status-bar-registry"
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../registries/app-preference-registry";
export type { ClusterFeatureRegistration, ClusterFeatureComponents } from "../registries/cluster-feature-registry";
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry";
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry";
export type { PageRegistration, PageComponents } from "../registries/page-registry";
export type { PageMenuRegistration, PageMenuComponents } from "../registries/page-menu-registry";
export type { StatusBarRegistration } from "../registries/status-bar-registry";

View File

@ -21,9 +21,9 @@ export class LensExtension {
@observable private isEnabled = false;
constructor({ manifest, manifestPath, isBundled }: InstalledExtension) {
this.manifest = manifest
this.manifestPath = manifestPath
this.isBundled = !!isBundled
this.manifest = manifest;
this.manifestPath = manifestPath;
this.isBundled = !!isBundled;
}
get id(): LensExtensionId {
@ -31,15 +31,15 @@ export class LensExtension {
}
get name() {
return this.manifest.name
return this.manifest.name;
}
get version() {
return this.manifest.version
return this.manifest.version;
}
get description() {
return this.manifest.description
return this.manifest.description;
}
@action
@ -60,18 +60,18 @@ export class LensExtension {
toggle(enable?: boolean) {
if (typeof enable === "boolean") {
enable ? this.enable() : this.disable()
enable ? this.enable() : this.disable();
} else {
this.isEnabled ? this.disable() : this.enable()
this.isEnabled ? this.disable() : this.enable();
}
}
async whenEnabled(handlers: () => Function[]) {
const disposers: Function[] = [];
const unregisterHandlers = () => {
disposers.forEach(unregister => unregister())
disposers.forEach(unregister => unregister());
disposers.length = 0;
}
};
const cancelReaction = reaction(() => this.isEnabled, isEnabled => {
if (isEnabled) {
disposers.push(...handlers());
@ -80,11 +80,11 @@ export class LensExtension {
}
}, {
fireImmediately: true
})
});
return () => {
unregisterHandlers();
cancelReaction();
}
};
}
protected onActivate() {

View File

@ -1,11 +1,11 @@
import type { MenuRegistration } from "./registries/menu-registry";
import { observable } from "mobx";
import { LensExtension } from "./lens-extension"
import { LensExtension } from "./lens-extension";
import { WindowManager } from "../main/window-manager";
import { getExtensionPageUrl } from "./registries/page-registry"
import { getExtensionPageUrl } from "./registries/page-registry";
export class LensMainExtension extends LensExtension {
@observable.shallow appMenus: MenuRegistration[] = []
@observable.shallow appMenus: MenuRegistration[] = [];
async navigate<P extends object>(pageId?: string, params?: P, frameId?: number) {
const windowManager = WindowManager.getInstance<WindowManager>();

View File

@ -1,19 +1,19 @@
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries"
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries";
import { observable } from "mobx";
import { LensExtension } from "./lens-extension"
import { getExtensionPageUrl } from "./registries/page-registry"
import { LensExtension } from "./lens-extension";
import { getExtensionPageUrl } from "./registries/page-registry";
export class LensRendererExtension extends LensExtension {
@observable.shallow globalPages: PageRegistration[] = []
@observable.shallow clusterPages: PageRegistration[] = []
@observable.shallow globalPageMenus: PageMenuRegistration[] = []
@observable.shallow clusterPageMenus: PageMenuRegistration[] = []
@observable.shallow kubeObjectStatusTexts: KubeObjectStatusRegistration[] = []
@observable.shallow appPreferences: AppPreferenceRegistration[] = []
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = []
@observable.shallow statusBarItems: StatusBarRegistration[] = []
@observable.shallow kubeObjectDetailItems: KubeObjectDetailRegistration[] = []
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = []
@observable.shallow globalPages: PageRegistration[] = [];
@observable.shallow clusterPages: PageRegistration[] = [];
@observable.shallow globalPageMenus: PageMenuRegistration[] = [];
@observable.shallow clusterPageMenus: PageMenuRegistration[] = [];
@observable.shallow kubeObjectStatusTexts: KubeObjectStatusRegistration[] = [];
@observable.shallow appPreferences: AppPreferenceRegistration[] = [];
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = [];
@observable.shallow statusBarItems: StatusBarRegistration[] = [];
@observable.shallow kubeObjectDetailItems: KubeObjectDetailRegistration[] = [];
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = [];
async navigate<P extends object>(pageId?: string, params?: P) {
const { navigate } = await import("../renderer/navigation");

View File

@ -1,8 +1,8 @@
import { getExtensionPageUrl, globalPageRegistry, PageRegistration } from "../page-registry"
import { LensExtension } from "../../lens-extension"
import { getExtensionPageUrl, globalPageRegistry, PageRegistration } from "../page-registry";
import { LensExtension } from "../../lens-extension";
import React from "react";
let ext: LensExtension = null
let ext: LensExtension = null;
describe("getPageUrl", () => {
beforeEach(async () => {
@ -14,25 +14,25 @@ describe("getPageUrl", () => {
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true
})
})
});
});
it("returns a page url for extension", () => {
expect(getExtensionPageUrl({ extensionId: ext.name })).toBe("/extension/foo-bar")
})
expect(getExtensionPageUrl({ extensionId: ext.name })).toBe("/extension/foo-bar");
});
it("allows to pass base url as parameter", () => {
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "/test" })).toBe("/extension/foo-bar/test")
})
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "/test" })).toBe("/extension/foo-bar/test");
});
it("removes @", () => {
expect(getExtensionPageUrl({ extensionId: "@foo/bar" })).toBe("/extension/foo-bar")
})
expect(getExtensionPageUrl({ extensionId: "@foo/bar" })).toBe("/extension/foo-bar");
});
it("adds / prefix", () => {
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "test" })).toBe("/extension/foo-bar/test")
})
})
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "test" })).toBe("/extension/foo-bar/test");
});
});
describe("globalPageRegistry", () => {
beforeEach(async () => {
@ -44,7 +44,7 @@ describe("globalPageRegistry", () => {
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true
})
});
globalPageRegistry.add([
{
id: "test-page",
@ -63,12 +63,12 @@ describe("globalPageRegistry", () => {
Page: () => React.createElement('Default')
}
},
], ext)
})
], ext);
});
describe("getByPageMenuTarget", () => {
it("matching to first registered page without id", () => {
const page = globalPageRegistry.getByPageMenuTarget({ extensionId: ext.name })
const page = globalPageRegistry.getByPageMenuTarget({ extensionId: ext.name });
expect(page.id).toEqual(undefined);
expect(page.extensionId).toEqual(ext.name);
expect(page.routePath).toEqual(getExtensionPageUrl({ extensionId: ext.name }));
@ -78,16 +78,16 @@ describe("globalPageRegistry", () => {
const page = globalPageRegistry.getByPageMenuTarget({
pageId: "test-page",
extensionId: ext.name
})
expect(page.id).toEqual("test-page")
})
});
expect(page.id).toEqual("test-page");
});
it("returns null if target not found", () => {
const page = globalPageRegistry.getByPageMenuTarget({
pageId: "wrong-page",
extensionId: ext.name
})
expect(page).toBeNull()
})
})
})
});
expect(page).toBeNull();
});
});
});

View File

@ -1,4 +1,4 @@
import type React from "react"
import type React from "react";
import { BaseRegistry } from "./base-registry";
export interface AppPreferenceComponents {
@ -14,4 +14,4 @@ export interface AppPreferenceRegistration {
export class AppPreferenceRegistry extends BaseRegistry<AppPreferenceRegistration> {
}
export const appPreferenceRegistry = new AppPreferenceRegistry()
export const appPreferenceRegistry = new AppPreferenceRegistry();

View File

@ -12,7 +12,7 @@ export class BaseRegistry<T = object, I extends T = T> {
add(items: T | T[], ext?: LensExtension): () => void; // allow method overloading with required "ext"
@action
add(items: T | T[]) {
const normalizedItems = (Array.isArray(items) ? items : [items])
const normalizedItems = (Array.isArray(items) ? items : [items]);
this.items.push(...normalizedItems);
return () => this.remove(...normalizedItems);
}
@ -21,6 +21,6 @@ export class BaseRegistry<T = object, I extends T = T> {
remove(...items: T[]) {
items.forEach(item => {
this.items.remove(item); // works because of {deep: false};
})
});
}
}

View File

@ -1,4 +1,4 @@
import type React from "react"
import type React from "react";
import { BaseRegistry } from "./base-registry";
import { ClusterFeature } from "../cluster-feature";
@ -15,4 +15,4 @@ export interface ClusterFeatureRegistration {
export class ClusterFeatureRegistry extends BaseRegistry<ClusterFeatureRegistration> {
}
export const clusterFeatureRegistry = new ClusterFeatureRegistry()
export const clusterFeatureRegistry = new ClusterFeatureRegistry();

View File

@ -1,11 +1,11 @@
// All registries managed by extensions api
export * from "./page-registry"
export * from "./page-menu-registry"
export * from "./menu-registry"
export * from "./app-preference-registry"
export * from "./status-bar-registry"
export * from "./page-registry";
export * from "./page-menu-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"
export * from "./kube-object-status-registry"
export * from "./cluster-feature-registry";
export * from "./kube-object-status-registry";

View File

@ -1,4 +1,4 @@
import React from "react"
import React from "react";
import { BaseRegistry } from "./base-registry";
export interface KubeObjectDetailComponents {
@ -15,15 +15,15 @@ export interface KubeObjectDetailRegistration {
export class KubeObjectDetailRegistry extends BaseRegistry<KubeObjectDetailRegistration> {
getItemsForKind(kind: string, apiVersion: string) {
const items = this.getItems().filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion)
return item.kind === kind && item.apiVersions.includes(apiVersion);
}).map((item) => {
if (item.priority === null) {
item.priority = 50
item.priority = 50;
}
return item
})
return items.sort((a, b) => b.priority - a.priority)
return item;
});
return items.sort((a, b) => b.priority - a.priority);
}
}
export const kubeObjectDetailRegistry = new KubeObjectDetailRegistry()
export const kubeObjectDetailRegistry = new KubeObjectDetailRegistry();

View File

@ -1,4 +1,4 @@
import React from "react"
import React from "react";
import { BaseRegistry } from "./base-registry";
export interface KubeObjectMenuComponents {
@ -14,9 +14,9 @@ export interface KubeObjectMenuRegistration {
export class KubeObjectMenuRegistry extends BaseRegistry<KubeObjectMenuRegistration> {
getItemsForKind(kind: string, apiVersion: string) {
return this.getItems().filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion)
})
return item.kind === kind && item.apiVersions.includes(apiVersion);
});
}
}
export const kubeObjectMenuRegistry = new KubeObjectMenuRegistry()
export const kubeObjectMenuRegistry = new KubeObjectMenuRegistry();

View File

@ -10,8 +10,8 @@ export interface KubeObjectStatusRegistration {
export class KubeObjectStatusRegistry extends BaseRegistry<KubeObjectStatusRegistration> {
getItemsForKind(kind: string, apiVersion: string) {
return this.getItems().filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion)
})
return item.kind === kind && item.apiVersions.includes(apiVersion);
});
}
}

View File

@ -29,8 +29,8 @@ export class PageMenuRegistry extends BaseRegistry<PageMenuRegistration, Require
extensionId: ext.name,
...(menuItem.target || {}),
};
return menuItem
})
return menuItem;
});
return super.add(normalizedItems);
}
}

View File

@ -45,7 +45,7 @@ export interface PageComponents {
}
export function sanitizeExtensionName(name: string) {
return name.replace("@", "").replace("/", "-")
return name.replace("@", "").replace("/", "-");
}
export function getExtensionPageUrl<P extends object>({ extensionId, pageId = "", params }: PageMenuTarget<P>): string {
@ -68,13 +68,13 @@ export class PageRegistry extends BaseRegistry<PageRegistration, RegisteredPage>
...page,
extensionId: ext.name,
routePath: getExtensionPageUrl({ extensionId: ext.name, pageId: page.id ?? page.routePath }),
}))
}));
} catch (err) {
logger.error(`[EXTENSION]: page-registration failed`, {
items,
extension: ext,
error: String(err),
})
});
}
return super.add(registeredPages);
}

View File

@ -1,36 +1,36 @@
// Common UI components
// layouts
export * from "../../renderer/components/layout/page-layout"
export * from "../../renderer/components/layout/wizard-layout"
export * from "../../renderer/components/layout/tab-layout"
export * from "../../renderer/components/layout/page-layout";
export * from "../../renderer/components/layout/wizard-layout";
export * from "../../renderer/components/layout/tab-layout";
// form-controls
export * from "../../renderer/components/button"
export * from "../../renderer/components/checkbox"
export * from "../../renderer/components/radio"
export * from "../../renderer/components/select"
export * from "../../renderer/components/slider"
export * from "../../renderer/components/input/input"
export * from "../../renderer/components/button";
export * from "../../renderer/components/checkbox";
export * from "../../renderer/components/radio";
export * from "../../renderer/components/select";
export * from "../../renderer/components/slider";
export * from "../../renderer/components/input/input";
// other components
export * from "../../renderer/components/icon"
export * from "../../renderer/components/tooltip"
export * from "../../renderer/components/tabs"
export * from "../../renderer/components/table"
export * from "../../renderer/components/badge"
export * from "../../renderer/components/drawer"
export * from "../../renderer/components/dialog"
export * from "../../renderer/components/icon";
export * from "../../renderer/components/tooltip";
export * from "../../renderer/components/tabs";
export * from "../../renderer/components/table";
export * from "../../renderer/components/badge";
export * from "../../renderer/components/drawer";
export * from "../../renderer/components/dialog";
export * from "../../renderer/components/confirm-dialog";
export * from "../../renderer/components/line-progress"
export * from "../../renderer/components/menu"
export * from "../../renderer/components/notifications"
export * from "../../renderer/components/spinner"
export * from "../../renderer/components/stepper"
export * from "../../renderer/components/line-progress";
export * from "../../renderer/components/menu";
export * from "../../renderer/components/notifications";
export * from "../../renderer/components/spinner";
export * from "../../renderer/components/stepper";
// kube helpers
export * from "../../renderer/components/kube-object"
export * from "../../renderer/components/+events/kube-event-details"
export * from "../../renderer/components/kube-object";
export * from "../../renderer/components/+events/kube-event-details";
// specific exports
export * from "../../renderer/components/status-brick";

View File

@ -1,14 +1,14 @@
// Lens-extensions apis, required in renderer process runtime
// APIs
import * as Component from "./components"
import * as K8sApi from "./k8s-api"
import * as Navigation from "./navigation"
import * as Theme from "./theming"
import * as Component from "./components";
import * as K8sApi from "./k8s-api";
import * as Navigation from "./navigation";
import * as Theme from "./theming";
export {
Component,
K8sApi,
Navigation,
Theme,
}
};

View File

@ -1,6 +1,6 @@
export { isAllowedResource } from "../../common/rbac"
export { isAllowedResource } from "../../common/rbac";
export { apiManager } from "../../renderer/api/api-manager";
export { KubeObjectStore } from "../../renderer/kube-object.store"
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, PodsApi, IPodContainer, IPodContainerStatus } from "../../renderer/api/endpoints";
@ -31,33 +31,33 @@ export { RoleBinding, roleBindingApi } from "../../renderer/api/endpoints";
export { ClusterRole, clusterRoleApi } from "../../renderer/api/endpoints";
export { ClusterRoleBinding, clusterRoleBindingApi } from "../../renderer/api/endpoints";
export { CustomResourceDefinition, crdApi } from "../../renderer/api/endpoints";
export { KubeObjectStatus, KubeObjectStatusLevel } from "./kube-object-status"
export { KubeObjectStatus, KubeObjectStatusLevel } from "./kube-object-status";
// stores
export type { EventStore } from "../../renderer/components/+events/event.store"
export type { PodsStore } from "../../renderer/components/+workloads-pods/pods.store"
export type { NodesStore } from "../../renderer/components/+nodes/nodes.store"
export type { DeploymentStore } from "../../renderer/components/+workloads-deployments/deployments.store"
export type { DaemonSetStore } from "../../renderer/components/+workloads-daemonsets/daemonsets.store"
export type { StatefulSetStore } from "../../renderer/components/+workloads-statefulsets/statefulset.store"
export type { JobStore } from "../../renderer/components/+workloads-jobs/job.store"
export type { CronJobStore } from "../../renderer/components/+workloads-cronjobs/cronjob.store"
export type { ConfigMapsStore } from "../../renderer/components/+config-maps/config-maps.store"
export type { SecretsStore } from "../../renderer/components/+config-secrets/secrets.store"
export type { ReplicaSetStore } from "../../renderer/components/+workloads-replicasets/replicasets.store"
export type { ResourceQuotasStore } from "../../renderer/components/+config-resource-quotas/resource-quotas.store"
export type { HPAStore } from "../../renderer/components/+config-autoscalers/hpa.store"
export type { PodDisruptionBudgetsStore } from "../../renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store"
export type { ServiceStore } from "../../renderer/components/+network-services/services.store"
export type { EndpointStore } from "../../renderer/components/+network-endpoints/endpoints.store"
export type { IngressStore } from "../../renderer/components/+network-ingresses/ingress.store"
export type { NetworkPolicyStore } from "../../renderer/components/+network-policies/network-policy.store"
export type { PersistentVolumesStore } from "../../renderer/components/+storage-volumes/volumes.store"
export type { VolumeClaimStore } from "../../renderer/components/+storage-volume-claims/volume-claim.store"
export type { StorageClassStore } from "../../renderer/components/+storage-classes/storage-class.store"
export type { NamespaceStore } from "../../renderer/components/+namespaces/namespace.store"
export type { ServiceAccountsStore } from "../../renderer/components/+user-management-service-accounts/service-accounts.store"
export type { RolesStore } from "../../renderer/components/+user-management-roles/roles.store"
export type { RoleBindingsStore } from "../../renderer/components/+user-management-roles-bindings/role-bindings.store"
export type { CRDStore } from "../../renderer/components/+custom-resources/crd.store"
export type { CRDResourceStore } from "../../renderer/components/+custom-resources/crd-resource.store"
export type { EventStore } from "../../renderer/components/+events/event.store";
export type { PodsStore } from "../../renderer/components/+workloads-pods/pods.store";
export type { NodesStore } from "../../renderer/components/+nodes/nodes.store";
export type { DeploymentStore } from "../../renderer/components/+workloads-deployments/deployments.store";
export type { DaemonSetStore } from "../../renderer/components/+workloads-daemonsets/daemonsets.store";
export type { StatefulSetStore } from "../../renderer/components/+workloads-statefulsets/statefulset.store";
export type { JobStore } from "../../renderer/components/+workloads-jobs/job.store";
export type { CronJobStore } from "../../renderer/components/+workloads-cronjobs/cronjob.store";
export type { ConfigMapsStore } from "../../renderer/components/+config-maps/config-maps.store";
export type { SecretsStore } from "../../renderer/components/+config-secrets/secrets.store";
export type { ReplicaSetStore } from "../../renderer/components/+workloads-replicasets/replicasets.store";
export type { ResourceQuotasStore } from "../../renderer/components/+config-resource-quotas/resource-quotas.store";
export type { HPAStore } from "../../renderer/components/+config-autoscalers/hpa.store";
export type { PodDisruptionBudgetsStore } from "../../renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store";
export type { ServiceStore } from "../../renderer/components/+network-services/services.store";
export type { EndpointStore } from "../../renderer/components/+network-endpoints/endpoints.store";
export type { IngressStore } from "../../renderer/components/+network-ingresses/ingress.store";
export type { NetworkPolicyStore } from "../../renderer/components/+network-policies/network-policy.store";
export type { PersistentVolumesStore } from "../../renderer/components/+storage-volumes/volumes.store";
export type { VolumeClaimStore } from "../../renderer/components/+storage-volume-claims/volume-claim.store";
export type { StorageClassStore } from "../../renderer/components/+storage-classes/storage-class.store";
export type { NamespaceStore } from "../../renderer/components/+namespaces/namespace.store";
export type { ServiceAccountsStore } from "../../renderer/components/+user-management-service-accounts/service-accounts.store";
export type { RolesStore } from "../../renderer/components/+user-management-roles/roles.store";
export type { RoleBindingsStore } from "../../renderer/components/+user-management-roles-bindings/role-bindings.store";
export type { CRDStore } from "../../renderer/components/+custom-resources/crd.store";
export type { CRDResourceStore } from "../../renderer/components/+custom-resources/crd-resource.store";

View File

@ -2,7 +2,7 @@ export type KubeObjectStatus = {
level: KubeObjectStatusLevel;
text: string;
timestamp?: string;
}
};
export enum KubeObjectStatusLevel {
INFO = 1,

View File

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

View File

@ -1,4 +1,4 @@
import fetchMock from "jest-fetch-mock"
import fetchMock from "jest-fetch-mock";
// rewire global.fetch to call 'fetchMock'
fetchMock.enableMocks();

View File

@ -21,35 +21,35 @@ jest.mock("winston", () => ({
Console: jest.fn(),
File: jest.fn(),
}
}))
}));
jest.mock("../../common/ipc")
jest.mock("../context-handler")
jest.mock("request")
jest.mock("request-promise-native")
jest.mock("../../common/ipc");
jest.mock("../context-handler");
jest.mock("request");
jest.mock("request-promise-native");
import { Console } from "console";
import mockFs from "mock-fs";
import { workspaceStore } from "../../common/workspace-store";
import { Cluster } from "../cluster"
import { Cluster } from "../cluster";
import { ContextHandler } from "../context-handler";
import { getFreePort } from "../port";
import { V1ResourceAttributes } from "@kubernetes/client-node";
import { apiResources } from "../../common/rbac";
import request from "request-promise-native"
import request from "request-promise-native";
import { Kubectl } from "../kubectl";
const mockedRequest = request as jest.MockedFunction<typeof request>
const mockedRequest = request as jest.MockedFunction<typeof request>;
console = new Console(process.stdout, process.stderr) // fix mockFS
console = new Console(process.stdout, process.stderr); // fix mockFS
describe("create clusters", () => {
beforeEach(() => {
jest.clearAllMocks()
})
jest.clearAllMocks();
});
let c: Cluster
let c: Cluster;
beforeEach(() => {
const mockOpts = {
@ -74,68 +74,68 @@ describe("create clusters", () => {
kind: "Config",
preferences: {},
})
}
mockFs(mockOpts)
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true))
};
mockFs(mockOpts);
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true));
c = new Cluster({
id: "foo",
contextName: "minikube",
kubeConfigPath: "minikube-config.yml",
workspace: workspaceStore.currentWorkspaceId
})
})
});
});
afterEach(() => {
mockFs.restore()
})
mockFs.restore();
});
it("should be able to create a cluster from a cluster model and apiURL should be decoded", () => {
expect(c.apiUrl).toBe("https://192.168.64.3:8443")
})
expect(c.apiUrl).toBe("https://192.168.64.3:8443");
});
it("reconnect should not throw if contextHandler is missing", () => {
expect(() => c.reconnect()).not.toThrowError()
})
expect(() => c.reconnect()).not.toThrowError();
});
it("disconnect should not throw if contextHandler is missing", () => {
expect(() => c.disconnect()).not.toThrowError()
})
expect(() => c.disconnect()).not.toThrowError();
});
it("init should not throw if everything is in order", async () => {
await c.init(await getFreePort())
await c.init(await getFreePort());
expect(logger.info).toBeCalledWith(expect.stringContaining("init success"), {
id: "foo",
apiUrl: "https://192.168.64.3:8443",
context: "minikube",
})
})
});
});
it("activating cluster should try to connect to cluster and do a refresh", async () => {
const port = await getFreePort()
const port = await getFreePort();
jest.spyOn(ContextHandler.prototype, "ensureServer");
const mockListNSs = jest.fn()
const mockListNSs = jest.fn();
const mockKC = {
makeApiClient() {
return {
listNamespace: mockListNSs,
}
};
}
}
jest.spyOn(Cluster.prototype, "isClusterAdmin").mockReturnValue(Promise.resolve(true))
};
jest.spyOn(Cluster.prototype, "isClusterAdmin").mockReturnValue(Promise.resolve(true));
jest.spyOn(Cluster.prototype, "canI")
.mockImplementationOnce((attr: V1ResourceAttributes): Promise<boolean> => {
expect(attr.namespace).toBe("default")
expect(attr.resource).toBe("pods")
expect(attr.verb).toBe("list")
return Promise.resolve(true)
expect(attr.namespace).toBe("default");
expect(attr.resource).toBe("pods");
expect(attr.verb).toBe("list");
return Promise.resolve(true);
})
.mockImplementation((attr: V1ResourceAttributes): Promise<boolean> => {
expect(attr.namespace).toBe("default")
expect(attr.verb).toBe("list")
return Promise.resolve(true)
})
jest.spyOn(Cluster.prototype, "getProxyKubeconfig").mockReturnValue(mockKC as any)
expect(attr.namespace).toBe("default");
expect(attr.verb).toBe("list");
return Promise.resolve(true);
});
jest.spyOn(Cluster.prototype, "getProxyKubeconfig").mockReturnValue(mockKC as any);
mockListNSs.mockImplementationOnce(() => ({
body: {
items: [{
@ -144,36 +144,36 @@ describe("create clusters", () => {
}
}]
}
}))
}));
mockedRequest.mockImplementationOnce(((uri: any, _options: any) => {
expect(uri).toBe(`http://localhost:${port}/api-kube/version`)
return Promise.resolve({ gitVersion: "1.2.3" })
}) as any)
expect(uri).toBe(`http://localhost:${port}/api-kube/version`);
return Promise.resolve({ gitVersion: "1.2.3" });
}) as any);
const c = new class extends Cluster {
// only way to mock protected methods, without these we leak promises
protected bindEvents() {
return
return;
}
protected async ensureKubectl() {
return Promise.resolve(true)
return Promise.resolve(true);
}
}({
id: "foo",
contextName: "minikube",
kubeConfigPath: "minikube-config.yml",
workspace: workspaceStore.currentWorkspaceId
})
await c.init(port)
await c.activate()
});
await c.init(port);
await c.activate();
expect(ContextHandler.prototype.ensureServer).toBeCalled()
expect(mockedRequest).toBeCalled()
expect(c.accessible).toBe(true)
expect(c.allowedNamespaces.length).toBe(1)
expect(c.allowedResources.length).toBe(apiResources.length)
c.disconnect()
jest.resetAllMocks()
})
})
expect(ContextHandler.prototype.ensureServer).toBeCalled();
expect(mockedRequest).toBeCalled();
expect(c.accessible).toBe(true);
expect(c.allowedNamespaces.length).toBe(1);
expect(c.allowedResources.length).toBe(apiResources.length);
c.disconnect();
jest.resetAllMocks();
});
});

View File

@ -21,109 +21,109 @@ jest.mock("winston", () => ({
Console: jest.fn(),
File: jest.fn(),
}
}))
}));
jest.mock("../../common/ipc")
jest.mock("child_process")
jest.mock("tcp-port-used")
jest.mock("../../common/ipc");
jest.mock("child_process");
jest.mock("tcp-port-used");
import { Cluster } from "../cluster"
import { KubeAuthProxy } from "../kube-auth-proxy"
import { getFreePort } from "../port"
import { broadcastMessage } from "../../common/ipc"
import { ChildProcess, spawn, SpawnOptions } from "child_process"
import { bundledKubectlPath, Kubectl } from "../kubectl"
import { Cluster } from "../cluster";
import { KubeAuthProxy } from "../kube-auth-proxy";
import { getFreePort } from "../port";
import { broadcastMessage } from "../../common/ipc";
import { ChildProcess, spawn, SpawnOptions } from "child_process";
import { bundledKubectlPath, Kubectl } from "../kubectl";
import { mock, MockProxy } from 'jest-mock-extended';
import { waitUntilUsed } from 'tcp-port-used';
import { Readable } from "stream"
import { Readable } from "stream";
const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcastMessage>
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>
const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction<typeof waitUntilUsed>
const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcastMessage>;
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction<typeof waitUntilUsed>;
describe("kube auth proxy tests", () => {
beforeEach(() => {
jest.clearAllMocks()
})
jest.clearAllMocks();
});
it("calling exit multiple times shouldn't throw", async () => {
const port = await getFreePort()
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
kap.exit()
kap.exit()
kap.exit()
})
const port = await getFreePort();
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {});
kap.exit();
kap.exit();
kap.exit();
});
describe("spawn tests", () => {
let port: number
let mockedCP: MockProxy<ChildProcess>
let listeners: Record<string, (...args: any[]) => void>
let proxy: KubeAuthProxy
let port: number;
let mockedCP: MockProxy<ChildProcess>;
let listeners: Record<string, (...args: any[]) => void>;
let proxy: KubeAuthProxy;
beforeEach(async () => {
port = await getFreePort()
mockedCP = mock<ChildProcess>()
listeners = {}
port = await getFreePort();
mockedCP = mock<ChildProcess>();
listeners = {};
jest.spyOn(Kubectl.prototype, "checkBinary").mockReturnValueOnce(Promise.resolve(true))
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValueOnce(Promise.resolve(false))
jest.spyOn(Kubectl.prototype, "checkBinary").mockReturnValueOnce(Promise.resolve(true));
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValueOnce(Promise.resolve(false));
mockedCP.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): ChildProcess => {
listeners[event] = listener
return mockedCP
})
mockedCP.stderr = mock<Readable>()
listeners[event] = listener;
return mockedCP;
});
mockedCP.stderr = mock<Readable>();
mockedCP.stderr.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): Readable => {
listeners[`stderr/${event}`] = listener
return mockedCP.stderr
})
mockedCP.stdout = mock<Readable>()
listeners[`stderr/${event}`] = listener;
return mockedCP.stderr;
});
mockedCP.stdout = mock<Readable>();
mockedCP.stdout.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): Readable => {
listeners[`stdout/${event}`] = listener
return mockedCP.stdout
})
listeners[`stdout/${event}`] = listener;
return mockedCP.stdout;
});
mockSpawn.mockImplementationOnce((command: string, args: readonly string[], options: SpawnOptions): ChildProcess => {
expect(command).toBe(bundledKubectlPath())
return mockedCP
})
mockWaitUntilUsed.mockReturnValueOnce(Promise.resolve())
const cluster = new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" })
jest.spyOn(cluster, "apiUrl", "get").mockReturnValue("https://fake.k8s.internal")
proxy = new KubeAuthProxy(cluster, port, {})
})
expect(command).toBe(bundledKubectlPath());
return mockedCP;
});
mockWaitUntilUsed.mockReturnValueOnce(Promise.resolve());
const cluster = new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" });
jest.spyOn(cluster, "apiUrl", "get").mockReturnValue("https://fake.k8s.internal");
proxy = new KubeAuthProxy(cluster, port, {});
});
it("should call spawn and broadcast errors", async () => {
await proxy.run()
listeners["error"]({ message: "foobarbat" })
await proxy.run();
listeners["error"]({ message: "foobarbat" });
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "foobarbat", error: true })
})
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "foobarbat", error: true });
});
it("should call spawn and broadcast exit", async () => {
await proxy.run()
listeners["exit"](0)
await proxy.run();
listeners["exit"](0);
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "proxy exited with code: 0", error: false })
})
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "proxy exited with code: 0", error: false });
});
it("should call spawn and broadcast errors from stderr", async () => {
await proxy.run()
listeners["stderr/data"]("an error")
await proxy.run();
listeners["stderr/data"]("an error");
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "an error", error: true })
})
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "an error", error: true });
});
it("should call spawn and broadcast stdout serving info", async () => {
await proxy.run()
listeners["stdout/data"]("Starting to serve on")
await proxy.run();
listeners["stdout/data"]("Starting to serve on");
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "Authentication proxy started\n" })
})
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "Authentication proxy started\n" });
});
it("should call spawn and broadcast stdout other info", async () => {
await proxy.run()
listeners["stdout/data"]("some info")
await proxy.run();
listeners["stdout/data"]("some info");
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "some info" })
})
})
})
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "some info" });
});
});
});

View File

@ -21,24 +21,24 @@ jest.mock("winston", () => ({
Console: jest.fn(),
File: jest.fn(),
}
}))
}));
import { KubeconfigManager } from "../kubeconfig-manager"
import mockFs from "mock-fs"
import { KubeconfigManager } from "../kubeconfig-manager";
import mockFs from "mock-fs";
import { Cluster } from "../cluster";
import { workspaceStore } from "../../common/workspace-store";
import { ContextHandler } from "../context-handler";
import { getFreePort } from "../port";
import fse from "fs-extra"
import fse from "fs-extra";
import { loadYaml } from "@kubernetes/client-node";
import { Console } from "console";
console = new Console(process.stdout, process.stderr) // fix mockFS
console = new Console(process.stdout, process.stderr); // fix mockFS
describe("kubeconfig manager tests", () => {
beforeEach(() => {
jest.clearAllMocks()
})
jest.clearAllMocks();
});
beforeEach(() => {
const mockOpts = {
@ -63,13 +63,13 @@ describe("kubeconfig manager tests", () => {
kind: "Config",
preferences: {},
})
}
mockFs(mockOpts)
})
};
mockFs(mockOpts);
});
afterEach(() => {
mockFs.restore()
})
mockFs.restore();
});
it("should create 'temp' kube config with proxy", async () => {
const cluster = new Cluster({
@ -77,19 +77,19 @@ describe("kubeconfig manager tests", () => {
contextName: "minikube",
kubeConfigPath: "minikube-config.yml",
workspace: workspaceStore.currentWorkspaceId
})
const contextHandler = new ContextHandler(cluster)
const port = await getFreePort()
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port)
});
const contextHandler = new ContextHandler(cluster);
const port = await getFreePort();
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port);
expect(logger.error).not.toBeCalled()
expect(kubeConfManager.getPath()).toBe("tmp/kubeconfig-foo")
const file = await fse.readFile(kubeConfManager.getPath())
const yml = loadYaml<any>(file.toString())
expect(yml["current-context"]).toBe("minikube")
expect(yml["clusters"][0]["cluster"]["server"]).toBe(`http://127.0.0.1:${port}/foo`)
expect(yml["users"][0]["name"]).toBe("proxy")
})
expect(logger.error).not.toBeCalled();
expect(kubeConfManager.getPath()).toBe("tmp/kubeconfig-foo");
const file = await fse.readFile(kubeConfManager.getPath());
const yml = loadYaml<any>(file.toString());
expect(yml["current-context"]).toBe("minikube");
expect(yml["clusters"][0]["cluster"]["server"]).toBe(`http://127.0.0.1:${port}/foo`);
expect(yml["users"][0]["name"]).toBe("proxy");
});
it("should remove 'temp' kube config on unlink and remove reference from inside class", async () => {
const cluster = new Cluster({
@ -97,16 +97,16 @@ describe("kubeconfig manager tests", () => {
contextName: "minikube",
kubeConfigPath: "minikube-config.yml",
workspace: workspaceStore.currentWorkspaceId
})
const contextHandler = new ContextHandler(cluster)
const port = await getFreePort()
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port)
});
const contextHandler = new ContextHandler(cluster);
const port = await getFreePort();
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port);
const configPath = kubeConfManager.getPath()
expect(await fse.pathExists(configPath)).toBe(true)
await kubeConfManager.unlink()
expect(await fse.pathExists(configPath)).toBe(false)
await kubeConfManager.unlink() // doesn't throw
expect(kubeConfManager.getPath()).toBeUndefined()
})
})
const configPath = kubeConfManager.getPath();
expect(await fse.pathExists(configPath)).toBe(true);
await kubeConfManager.unlink();
expect(await fse.pathExists(configPath)).toBe(false);
await kubeConfManager.unlink(); // doesn't throw
expect(kubeConfManager.getPath()).toBeUndefined();
});
});

View File

@ -1,19 +1,19 @@
import { autoUpdater } from "electron-updater"
import logger from "./logger"
import { autoUpdater } from "electron-updater";
import logger from "./logger";
export class AppUpdater {
static readonly defaultUpdateIntervalMs = 1000 * 60 * 60 * 24 // once a day
static readonly defaultUpdateIntervalMs = 1000 * 60 * 60 * 24; // once a day
static checkForUpdates() {
return autoUpdater.checkForUpdatesAndNotify()
return autoUpdater.checkForUpdatesAndNotify();
}
constructor(protected updateInterval = AppUpdater.defaultUpdateIntervalMs) {
autoUpdater.logger = logger
autoUpdater.logger = logger;
}
public start() {
setInterval(AppUpdater.checkForUpdates, this.updateInterval)
setInterval(AppUpdater.checkForUpdates, this.updateInterval);
return AppUpdater.checkForUpdates();
}
}

View File

@ -1,21 +1,21 @@
import request, { RequestPromiseOptions } from "request-promise-native"
import request, { RequestPromiseOptions } from "request-promise-native";
import { Cluster } from "../cluster";
export type ClusterDetectionResult = {
value: string | number | boolean
accuracy: number
}
};
export class BaseClusterDetector {
cluster: Cluster
key: string
cluster: Cluster;
key: string;
constructor(cluster: Cluster) {
this.cluster = cluster
this.cluster = cluster;
}
detect(): Promise<ClusterDetectionResult> {
return null
return null;
}
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
@ -28,6 +28,6 @@ export class BaseClusterDetector {
Host: `${this.cluster.id}.${new URL(this.cluster.kubeProxyUrl).host}`, // required in ClusterManager.getClusterForRequest()
...(options.headers || {}),
},
})
});
}
}

View File

@ -1,23 +1,23 @@
import { BaseClusterDetector } from "./base-cluster-detector";
import { createHash } from "crypto"
import { createHash } from "crypto";
import { ClusterMetadataKey } from "../cluster";
export class ClusterIdDetector extends BaseClusterDetector {
key = ClusterMetadataKey.CLUSTER_ID
key = ClusterMetadataKey.CLUSTER_ID;
public async detect() {
let id: string
let id: string;
try {
id = await this.getDefaultNamespaceId()
id = await this.getDefaultNamespaceId();
} catch(_) {
id = this.cluster.apiUrl
id = this.cluster.apiUrl;
}
const value = createHash("sha256").update(id).digest("hex")
return { value: value, accuracy: 100 }
const value = createHash("sha256").update(id).digest("hex");
return { value: value, accuracy: 100 };
}
protected async getDefaultNamespaceId() {
const response = await this.k8sRequest("/api/v1/namespaces/default")
return response.metadata.uid
const response = await this.k8sRequest("/api/v1/namespaces/default");
return response.metadata.uid;
}
}

Some files were not shown because too many files have changed in this diff Show More