mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add a few missing folders to be linted.
Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>
This commit is contained in:
parent
ca67caea60
commit
c7b24c2922
@ -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: {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
import { helmCli } from "../src/main/helm/helm-cli"
|
||||
import { helmCli } from "../src/main/helm/helm-cli";
|
||||
|
||||
helmCli.ensureBinary()
|
||||
helmCli.ensureBinary();
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
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
|
||||
@ -14,7 +14,7 @@ class KubectlDownloader {
|
||||
|
||||
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")));
|
||||
});
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const { notarize } = require('electron-notarize')
|
||||
const { notarize } = require('electron-notarize');
|
||||
|
||||
exports.default = async function notarizing(context) {
|
||||
const { electronPlatformName, appOutDir } = context;
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import path from "path"
|
||||
import path from "path";
|
||||
|
||||
const outputPath = path.resolve(__dirname, 'dist');
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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"}); }
|
||||
}
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import path from "path"
|
||||
import path from "path";
|
||||
|
||||
const outputPath = path.resolve(__dirname, 'dist');
|
||||
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,194 +4,194 @@
|
||||
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 () => {
|
||||
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")
|
||||
})
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe("workspaces", () => {
|
||||
beforeAll(appStart, 20000)
|
||||
beforeAll(appStart, 20000);
|
||||
|
||||
afterAll(async () => {
|
||||
if (app && app.isRunning()) {
|
||||
return util.tearDown(app)
|
||||
return util.tearDown(app);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it('switches between workspaces', async () => {
|
||||
await clickWhatsNew(app)
|
||||
await app.client.click('#current-workspace .Icon')
|
||||
await app.client.click('a[href="/workspaces"]')
|
||||
await app.client.click('.Workspaces button.Button')
|
||||
await app.client.keys("test-workspace")
|
||||
await app.client.click('.Workspaces .Input.description input')
|
||||
await app.client.keys("test description")
|
||||
await app.client.click('.Workspaces .workspace.editing .Icon')
|
||||
await app.client.waitUntilTextExists(".workspace .name a", "test-workspace")
|
||||
await addMinikubeCluster(app)
|
||||
await app.client.waitForExist(`iframe[name="minikube"]`)
|
||||
await app.client.waitForVisible(".ClustersMenu .ClusterIcon.active")
|
||||
await clickWhatsNew(app);
|
||||
await app.client.click('#current-workspace .Icon');
|
||||
await app.client.click('a[href="/workspaces"]');
|
||||
await app.client.click('.Workspaces button.Button');
|
||||
await app.client.keys("test-workspace");
|
||||
await app.client.click('.Workspaces .Input.description input');
|
||||
await app.client.keys("test description");
|
||||
await app.client.click('.Workspaces .workspace.editing .Icon');
|
||||
await app.client.waitUntilTextExists(".workspace .name a", "test-workspace");
|
||||
await addMinikubeCluster(app);
|
||||
await app.client.waitForExist(`iframe[name="minikube"]`);
|
||||
await app.client.waitForVisible(".ClustersMenu .ClusterIcon.active");
|
||||
|
||||
// Go to test-workspace
|
||||
await app.client.click('#current-workspace .Icon')
|
||||
await app.client.click('.WorkspaceMenu li[title="test description"]')
|
||||
await addMinikubeCluster(app)
|
||||
await app.client.click('#current-workspace .Icon');
|
||||
await app.client.click('.WorkspaceMenu li[title="test description"]');
|
||||
await addMinikubeCluster(app);
|
||||
|
||||
// Back to default one
|
||||
await app.client.click('#current-workspace .Icon')
|
||||
await app.client.click('.WorkspaceMenu > li:first-of-type')
|
||||
await app.client.waitForVisible(".ClustersMenu .ClusterIcon.active")
|
||||
})
|
||||
})
|
||||
await app.client.click('#current-workspace .Icon');
|
||||
await app.client.click('.WorkspaceMenu > li:first-of-type');
|
||||
await app.client.waitForVisible(".ClustersMenu .ClusterIcon.active");
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
@ -429,119 +429,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");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
"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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user