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
1477bb8274
commit
0b182ccf6f
@ -5,7 +5,7 @@ module.exports = {
|
|||||||
getVersion: jest.fn().mockReturnValue("3.0.0"),
|
getVersion: jest.fn().mockReturnValue("3.0.0"),
|
||||||
getLocale: jest.fn().mockRejectedValue("en"),
|
getLocale: jest.fn().mockRejectedValue("en"),
|
||||||
getPath: jest.fn((name: string) => {
|
getPath: jest.fn((name: string) => {
|
||||||
return "tmp"
|
return "tmp";
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
remote: {
|
remote: {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
// Generate tray icons from SVG to PNG + different sizes and colors (B&W)
|
// Generate tray icons from SVG to PNG + different sizes and colors (B&W)
|
||||||
// Command: `yarn build:tray-icons`
|
// Command: `yarn build:tray-icons`
|
||||||
import path from "path"
|
import path from "path";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import jsdom from "jsdom"
|
import jsdom from "jsdom";
|
||||||
import fs from "fs-extra"
|
import fs from "fs-extra";
|
||||||
|
|
||||||
export async function generateTrayIcon(
|
export async function generateTrayIcon(
|
||||||
{
|
{
|
||||||
@ -14,15 +14,15 @@ export async function generateTrayIcon(
|
|||||||
pixelSize = 32,
|
pixelSize = 32,
|
||||||
shouldUseDarkColors = false, // managed by electron.nativeTheme.shouldUseDarkColors
|
shouldUseDarkColors = false, // managed by electron.nativeTheme.shouldUseDarkColors
|
||||||
} = {}) {
|
} = {}) {
|
||||||
outputFilename += shouldUseDarkColors ? "_dark" : ""
|
outputFilename += shouldUseDarkColors ? "_dark" : "";
|
||||||
dpiSuffix = dpiSuffix !== "1x" ? `@${dpiSuffix}` : ""
|
dpiSuffix = dpiSuffix !== "1x" ? `@${dpiSuffix}` : "";
|
||||||
const pngIconDestPath = path.resolve(outputFolder, `${outputFilename}${dpiSuffix}.png`)
|
const pngIconDestPath = path.resolve(outputFolder, `${outputFilename}${dpiSuffix}.png`);
|
||||||
try {
|
try {
|
||||||
// Modify .SVG colors
|
// Modify .SVG colors
|
||||||
const trayIconColor = shouldUseDarkColors ? "white" : "black";
|
const trayIconColor = shouldUseDarkColors ? "white" : "black";
|
||||||
const svgDom = await jsdom.JSDOM.fromFile(svgIconPath);
|
const svgDom = await jsdom.JSDOM.fromFile(svgIconPath);
|
||||||
const svgRoot = svgDom.window.document.body.getElementsByTagName("svg")[0];
|
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);
|
const svgIconBuffer = Buffer.from(svgRoot.outerHTML);
|
||||||
|
|
||||||
// Resize and convert to .PNG
|
// 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 packageInfo from "../package.json";
|
||||||
import fs from "fs"
|
import fs from "fs";
|
||||||
import request from "request"
|
import request from "request";
|
||||||
import md5File from "md5-file"
|
import md5File from "md5-file";
|
||||||
import requestPromise from "request-promise-native"
|
import requestPromise from "request-promise-native";
|
||||||
import { ensureDir, pathExists } from "fs-extra"
|
import { ensureDir, pathExists } from "fs-extra";
|
||||||
import path from "path"
|
import path from "path";
|
||||||
|
|
||||||
class KubectlDownloader {
|
class KubectlDownloader {
|
||||||
public kubectlVersion: string
|
public kubectlVersion: string
|
||||||
@ -14,7 +14,7 @@ class KubectlDownloader {
|
|||||||
|
|
||||||
constructor(clusterVersion: string, platform: string, arch: string, target: string) {
|
constructor(clusterVersion: string, platform: string, arch: string, target: string) {
|
||||||
this.kubectlVersion = clusterVersion;
|
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.url = `https://storage.googleapis.com/kubernetes-release/release/v${this.kubectlVersion}/bin/${platform}/${arch}/${binaryName}`;
|
||||||
this.dirname = path.dirname(target);
|
this.dirname = path.dirname(target);
|
||||||
this.path = target;
|
this.path = target;
|
||||||
@ -25,83 +25,85 @@ class KubectlDownloader {
|
|||||||
method: "HEAD",
|
method: "HEAD",
|
||||||
uri: this.url,
|
uri: this.url,
|
||||||
resolveWithFullResponse: true
|
resolveWithFullResponse: true
|
||||||
}).catch((error) => { console.log(error) })
|
}).catch((error) => { console.log(error); });
|
||||||
|
|
||||||
if (response.headers["etag"]) {
|
if (response.headers["etag"]) {
|
||||||
return response.headers["etag"].replace(/"/g, "")
|
return response.headers["etag"].replace(/"/g, "");
|
||||||
}
|
}
|
||||||
return ""
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public async checkBinary() {
|
public async checkBinary() {
|
||||||
const exists = await pathExists(this.path)
|
const exists = await pathExists(this.path);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
const hash = md5File.sync(this.path)
|
const hash = md5File.sync(this.path);
|
||||||
const etag = await this.urlEtag()
|
const etag = await this.urlEtag();
|
||||||
if(hash == etag) {
|
if(hash == etag) {
|
||||||
console.log("Kubectl md5sum matches the remote etag")
|
console.log("Kubectl md5sum matches the remote etag");
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Kubectl md5sum " + hash + " does not match the remote etag " + etag + ", unlinking and downloading again")
|
console.log("Kubectl md5sum " + hash + " does not match the remote etag " + etag + ", unlinking and downloading again");
|
||||||
await fs.promises.unlink(this.path)
|
await fs.promises.unlink(this.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async downloadKubectl() {
|
public async downloadKubectl() {
|
||||||
const exists = await this.checkBinary();
|
const exists = await this.checkBinary();
|
||||||
if(exists) {
|
if(exists) {
|
||||||
console.log("Already exists and is valid")
|
console.log("Already exists and is valid");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
await ensureDir(path.dirname(this.path), 0o755)
|
await ensureDir(path.dirname(this.path), 0o755);
|
||||||
|
|
||||||
const file = fs.createWriteStream(this.path)
|
const file = fs.createWriteStream(this.path);
|
||||||
console.log(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`)
|
console.log(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
|
||||||
const requestOpts: request.UriOptions & request.CoreOptions = {
|
const requestOpts: request.UriOptions & request.CoreOptions = {
|
||||||
uri: this.url,
|
uri: this.url,
|
||||||
gzip: true
|
gzip: true
|
||||||
}
|
};
|
||||||
const stream = request(requestOpts)
|
const stream = request(requestOpts);
|
||||||
|
|
||||||
stream.on("complete", () => {
|
stream.on("complete", () => {
|
||||||
console.log("kubectl binary download finished")
|
console.log("kubectl binary download finished");
|
||||||
file.end(() => {})
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
})
|
file.end(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
stream.on("error", (error) => {
|
stream.on("error", (error) => {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
fs.unlink(this.path, () => {})
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
throw(error)
|
fs.unlink(this.path, () => {});
|
||||||
})
|
throw(error);
|
||||||
|
});
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
file.on("close", () => {
|
file.on("close", () => {
|
||||||
console.log("kubectl binary download closed")
|
console.log("kubectl binary download closed");
|
||||||
fs.chmod(this.path, 0o755, (err) => {
|
fs.chmod(this.path, 0o755, (err) => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
})
|
});
|
||||||
resolve()
|
resolve();
|
||||||
})
|
});
|
||||||
stream.pipe(file)
|
stream.pipe(file);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadVersion = packageInfo.config.bundledKubectlVersion;
|
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 = [
|
const downloads = [
|
||||||
{ platform: 'linux', arch: 'amd64', target: path.join(baseDir, 'linux', 'x64', 'kubectl') },
|
{ platform: 'linux', arch: 'amd64', target: path.join(baseDir, 'linux', 'x64', 'kubectl') },
|
||||||
{ platform: 'darwin', arch: 'amd64', target: path.join(baseDir, 'darwin', '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: 'amd64', target: path.join(baseDir, 'windows', 'x64', 'kubectl.exe') },
|
||||||
{ platform: 'windows', arch: '386', target: path.join(baseDir, 'windows', 'ia32', 'kubectl.exe') }
|
{ platform: 'windows', arch: '386', target: path.join(baseDir, 'windows', 'ia32', 'kubectl.exe') }
|
||||||
]
|
];
|
||||||
|
|
||||||
downloads.forEach((dlOpts) => {
|
downloads.forEach((dlOpts) => {
|
||||||
console.log(dlOpts)
|
console.log(dlOpts);
|
||||||
const downloader = new KubectlDownloader(downloadVersion, dlOpts.platform, dlOpts.arch, dlOpts.target);
|
const downloader = new KubectlDownloader(downloadVersion, dlOpts.platform, dlOpts.arch, dlOpts.target);
|
||||||
console.log("Downloading: " + JSON.stringify(dlOpts));
|
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) {
|
exports.default = async function notarizing(context) {
|
||||||
const { electronPlatformName, appOutDir } = context;
|
const { electronPlatformName, appOutDir } = context;
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import * as fs from "fs"
|
import * as fs from "fs";
|
||||||
import * as path from "path"
|
import * as path from "path";
|
||||||
import packageInfo from "../src/extensions/npm/extensions/package.json"
|
import packageInfo from "../src/extensions/npm/extensions/package.json";
|
||||||
import appInfo from "../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
|
packageInfo.version = appInfo.version;
|
||||||
fs.writeFileSync(packagePath, JSON.stringify(packageInfo, null, 2) + "\n")
|
fs.writeFileSync(packagePath, JSON.stringify(packageInfo, null, 2) + "\n");
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { LensRendererExtension, Component } from "@k8slens/extensions";
|
import { LensRendererExtension, Component } from "@k8slens/extensions";
|
||||||
import { CoffeeDoodle } from "react-open-doodles";
|
import { CoffeeDoodle } from "react-open-doodles";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export function ExampleIcon(props: Component.IconProps) {
|
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 }> {
|
export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> {
|
||||||
@ -16,7 +16,7 @@ export class ExamplePage extends React.Component<{ extension: LensRendererExtens
|
|||||||
render() {
|
render() {
|
||||||
const doodleStyle = {
|
const doodleStyle = {
|
||||||
width: "200px"
|
width: "200px"
|
||||||
}
|
};
|
||||||
return (
|
return (
|
||||||
<div className="flex column gaps align-flex-start">
|
<div className="flex column gaps align-flex-start">
|
||||||
<div style={doodleStyle}><CoffeeDoodle accent="#3d90ce" /></div>
|
<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>
|
<p>File: <i>{__filename}</i></p>
|
||||||
<Component.Button accent label="Deactivate" onClick={this.deactivate}/>
|
<Component.Button accent label="Deactivate" onClick={this.deactivate}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { LensRendererExtension } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import { ExampleIcon, ExamplePage } from "./page"
|
import { ExampleIcon, ExamplePage } from "./page";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleExtension extends LensRendererExtension {
|
export default class ExampleExtension extends LensRendererExtension {
|
||||||
clusterPages = [
|
clusterPages = [
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { LensRendererExtension, K8sApi } from "@k8slens/extensions";
|
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 {
|
export default class EventResourceStatusRendererExtension extends LensRendererExtension {
|
||||||
kubeObjectStatusTexts = [
|
kubeObjectStatusTexts = [
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { K8sApi } from "@k8slens/extensions";
|
import { K8sApi } from "@k8slens/extensions";
|
||||||
|
|
||||||
export function resolveStatus(object: K8sApi.KubeObject): K8sApi.KubeObjectStatus {
|
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);
|
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) {
|
if (!events.length || !warnings.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -12,16 +12,16 @@ export function resolveStatus(object: K8sApi.KubeObject): K8sApi.KubeObjectStatu
|
|||||||
level: K8sApi.KubeObjectStatusLevel.WARNING,
|
level: K8sApi.KubeObjectStatusLevel.WARNING,
|
||||||
text: `${event.message}`,
|
text: `${event.message}`,
|
||||||
timestamp: event.metadata.creationTimestamp
|
timestamp: event.metadata.creationTimestamp
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveStatusForPods(pod: K8sApi.Pod): K8sApi.KubeObjectStatus {
|
export function resolveStatusForPods(pod: K8sApi.Pod): K8sApi.KubeObjectStatus {
|
||||||
if (!pod.hasIssues()) {
|
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);
|
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) {
|
if (!events.length || !warnings.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -30,13 +30,13 @@ export function resolveStatusForPods(pod: K8sApi.Pod): K8sApi.KubeObjectStatus {
|
|||||||
level: K8sApi.KubeObjectStatusLevel.WARNING,
|
level: K8sApi.KubeObjectStatusLevel.WARNING,
|
||||||
text: `${event.message}`,
|
text: `${event.message}`,
|
||||||
timestamp: event.metadata.creationTimestamp
|
timestamp: event.metadata.creationTimestamp
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveStatusForCronJobs(cronJob: K8sApi.CronJob): K8sApi.KubeObjectStatus {
|
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 events = (eventStore as K8sApi.EventStore).getEventsByObject(cronJob);
|
||||||
let warnings = events.filter(evt => evt.isWarning());
|
const warnings = events.filter(evt => evt.isWarning());
|
||||||
if (cronJob.isNeverRun()) {
|
if (cronJob.isNeverRun()) {
|
||||||
events = events.filter(event => event.reason != "FailedNeedsStart");
|
events = events.filter(event => event.reason != "FailedNeedsStart");
|
||||||
}
|
}
|
||||||
@ -48,5 +48,5 @@ export function resolveStatusForCronJobs(cronJob: K8sApi.CronJob): K8sApi.KubeOb
|
|||||||
level: K8sApi.KubeObjectStatusLevel.WARNING,
|
level: K8sApi.KubeObjectStatusLevel.WARNING,
|
||||||
text: `${event.message}`,
|
text: `${event.message}`,
|
||||||
timestamp: event.metadata.creationTimestamp
|
timestamp: event.metadata.creationTimestamp
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
@ -6,7 +6,7 @@ export default class LicenseLensMainExtension extends LensMainExtension {
|
|||||||
parentId: "help",
|
parentId: "help",
|
||||||
label: "License",
|
label: "License",
|
||||||
async click() {
|
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');
|
const outputPath = path.resolve(__dirname, 'dist');
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { LensRendererExtension } from "@k8slens/extensions"
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import { MetricsFeature } from "./src/metrics-feature"
|
import { MetricsFeature } from "./src/metrics-feature";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
|
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
|
||||||
clusterFeatures = [
|
clusterFeatures = [
|
||||||
@ -14,7 +14,7 @@ export default class ClusterMetricsFeatureExtension extends LensRendererExtensio
|
|||||||
Install this only if you don't have existing Prometheus stack installed.
|
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>.
|
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()
|
feature: new MetricsFeature()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { ClusterFeature, Store, K8sApi } from "@k8slens/extensions"
|
import { ClusterFeature, Store, K8sApi } from "@k8slens/extensions";
|
||||||
import semver from "semver"
|
import semver from "semver";
|
||||||
import * as path from "path"
|
import * as path from "path";
|
||||||
|
|
||||||
export interface MetricsConfiguration {
|
export interface MetricsConfiguration {
|
||||||
// Placeholder for Metrics config structure
|
// Placeholder for Metrics config structure
|
||||||
@ -51,46 +51,46 @@ export class MetricsFeature extends ClusterFeature.Feature {
|
|||||||
|
|
||||||
async install(cluster: Store.Cluster): Promise<void> {
|
async install(cluster: Store.Cluster): Promise<void> {
|
||||||
// Check if there are storageclasses
|
// Check if there are storageclasses
|
||||||
const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass)
|
const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass);
|
||||||
const scs = await storageClassApi.list()
|
const scs = await storageClassApi.list();
|
||||||
this.config.persistence.enabled = scs.some(sc => (
|
this.config.persistence.enabled = scs.some(sc => (
|
||||||
sc.metadata?.annotations?.['storageclass.kubernetes.io/is-default-class'] === 'true' ||
|
sc.metadata?.annotations?.['storageclass.kubernetes.io/is-default-class'] === 'true' ||
|
||||||
sc.metadata?.annotations?.['storageclass.beta.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> {
|
async upgrade(cluster: Store.Cluster): Promise<void> {
|
||||||
return this.install(cluster)
|
return this.install(cluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateStatus(cluster: Store.Cluster): Promise<ClusterFeature.FeatureStatus> {
|
async updateStatus(cluster: Store.Cluster): Promise<ClusterFeature.FeatureStatus> {
|
||||||
try {
|
try {
|
||||||
const statefulSet = K8sApi.forCluster(cluster, K8sApi.StatefulSet)
|
const statefulSet = K8sApi.forCluster(cluster, K8sApi.StatefulSet);
|
||||||
const prometheus = await statefulSet.get({name: "prometheus", namespace: "lens-metrics"})
|
const prometheus = await statefulSet.get({name: "prometheus", namespace: "lens-metrics"});
|
||||||
if (prometheus?.kind) {
|
if (prometheus?.kind) {
|
||||||
this.status.installed = true;
|
this.status.installed = true;
|
||||||
this.status.currentVersion = prometheus.spec.template.spec.containers[0].image.split(":")[1];
|
this.status.currentVersion = prometheus.spec.template.spec.containers[0].image.split(":")[1];
|
||||||
this.status.canUpgrade = semver.lt(this.status.currentVersion, this.latestVersion, true);
|
this.status.canUpgrade = semver.lt(this.status.currentVersion, this.latestVersion, true);
|
||||||
} else {
|
} else {
|
||||||
this.status.installed = false
|
this.status.installed = false;
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if (e?.error?.code === 404) {
|
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> {
|
async uninstall(cluster: Store.Cluster): Promise<void> {
|
||||||
const namespaceApi = K8sApi.forCluster(cluster, K8sApi.Namespace)
|
const namespaceApi = K8sApi.forCluster(cluster, K8sApi.Namespace);
|
||||||
const clusterRoleBindingApi = K8sApi.forCluster(cluster, K8sApi.ClusterRoleBinding)
|
const clusterRoleBindingApi = K8sApi.forCluster(cluster, K8sApi.ClusterRoleBinding);
|
||||||
const clusterRoleApi = K8sApi.forCluster(cluster, K8sApi.ClusterRole)
|
const clusterRoleApi = K8sApi.forCluster(cluster, K8sApi.ClusterRole);
|
||||||
|
|
||||||
await namespaceApi.delete({name: "lens-metrics"})
|
await namespaceApi.delete({name: "lens-metrics"});
|
||||||
await clusterRoleBindingApi.delete({name: "lens-prometheus"})
|
await clusterRoleBindingApi.delete({name: "lens-prometheus"});
|
||||||
await clusterRoleApi.delete({name: "lens-prometheus"}) }
|
await clusterRoleApi.delete({name: "lens-prometheus"}); }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { LensRendererExtension } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
import { NodeMenu, NodeMenuProps } from "./src/node-menu"
|
import { NodeMenu, NodeMenuProps } from "./src/node-menu";
|
||||||
|
|
||||||
export default class NodeMenuRendererExtension extends LensRendererExtension {
|
export default class NodeMenuRendererExtension extends LensRendererExtension {
|
||||||
kubeObjectMenuItems = [
|
kubeObjectMenuItems = [
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
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> {
|
export interface NodeMenuProps extends Component.KubeObjectMenuProps<K8sApi.Node> {
|
||||||
}
|
}
|
||||||
@ -15,7 +15,7 @@ export function NodeMenu(props: NodeMenuProps) {
|
|||||||
newTab: true,
|
newTab: true,
|
||||||
});
|
});
|
||||||
Navigation.hideDetails();
|
Navigation.hideDetails();
|
||||||
}
|
};
|
||||||
|
|
||||||
const shell = () => {
|
const shell = () => {
|
||||||
Component.createTerminalTab({
|
Component.createTerminalTab({
|
||||||
@ -23,15 +23,15 @@ export function NodeMenu(props: NodeMenuProps) {
|
|||||||
node: nodeName,
|
node: nodeName,
|
||||||
});
|
});
|
||||||
Navigation.hideDetails();
|
Navigation.hideDetails();
|
||||||
}
|
};
|
||||||
|
|
||||||
const cordon = () => {
|
const cordon = () => {
|
||||||
sendToTerminal(`kubectl cordon ${nodeName}`);
|
sendToTerminal(`kubectl cordon ${nodeName}`);
|
||||||
}
|
};
|
||||||
|
|
||||||
const unCordon = () => {
|
const unCordon = () => {
|
||||||
sendToTerminal(`kubectl uncordon ${nodeName}`)
|
sendToTerminal(`kubectl uncordon ${nodeName}`);
|
||||||
}
|
};
|
||||||
|
|
||||||
const drain = () => {
|
const drain = () => {
|
||||||
const command = `kubectl drain ${nodeName} --delete-local-data --ignore-daemonsets --force`;
|
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>?
|
Are you sure you want to drain <b>{nodeName}</b>?
|
||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { LensRendererExtension } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import { PodShellMenu, PodShellMenuProps } from "./src/shell-menu"
|
import { PodShellMenu, PodShellMenuProps } from "./src/shell-menu";
|
||||||
import { PodLogsMenu, PodLogsMenuProps } from "./src/logs-menu"
|
import { PodLogsMenu, PodLogsMenuProps } from "./src/logs-menu";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export default class PodMenuRendererExtension extends LensRendererExtension {
|
export default class PodMenuRendererExtension extends LensRendererExtension {
|
||||||
kubeObjectMenuItems = [
|
kubeObjectMenuItems = [
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { object: pod, toolbar } = this.props
|
const { object: pod, toolbar } = this.props;
|
||||||
const containers = pod.getAllContainers();
|
const containers = pod.getAllContainers();
|
||||||
const statuses = pod.getContainerStatuses();
|
const statuses = pod.getContainerStatuses();
|
||||||
if (!containers.length) return null;
|
if (!containers.length) return null;
|
||||||
@ -33,25 +33,25 @@ export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
|
|||||||
<Component.SubMenu>
|
<Component.SubMenu>
|
||||||
{
|
{
|
||||||
containers.map(container => {
|
containers.map(container => {
|
||||||
const { name } = container
|
const { name } = container;
|
||||||
const status = statuses.find(status => status.name === name);
|
const status = statuses.find(status => status.name === name);
|
||||||
const brick = status ? (
|
const brick = status ? (
|
||||||
<Component.StatusBrick
|
<Component.StatusBrick
|
||||||
className={Util.cssNames(Object.keys(status.state)[0], { ready: status.ready })}
|
className={Util.cssNames(Object.keys(status.state)[0], { ready: status.ready })}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null;
|
||||||
return (
|
return (
|
||||||
<Component.MenuItem key={name} onClick={Util.prevDefault(() => this.showLogs(container))} className="flex align-center">
|
<Component.MenuItem key={name} onClick={Util.prevDefault(() => this.showLogs(container))} className="flex align-center">
|
||||||
{brick}
|
{brick}
|
||||||
{name}
|
{name}
|
||||||
</Component.MenuItem>
|
</Component.MenuItem>
|
||||||
)
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</Component.SubMenu>
|
</Component.SubMenu>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Component.MenuItem>
|
</Component.MenuItem>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,16 +9,16 @@ export interface PodShellMenuProps extends Component.KubeObjectMenuProps<K8sApi.
|
|||||||
export class PodShellMenu extends React.Component<PodShellMenuProps> {
|
export class PodShellMenu extends React.Component<PodShellMenuProps> {
|
||||||
async execShell(container?: string) {
|
async execShell(container?: string) {
|
||||||
Navigation.hideDetails();
|
Navigation.hideDetails();
|
||||||
const { object: pod } = this.props
|
const { object: pod } = this.props;
|
||||||
const containerParam = container ? `-c ${container}` : ""
|
const containerParam = container ? `-c ${container}` : "";
|
||||||
let command = `kubectl exec -i -t -n ${pod.getNs()} ${pod.getName()} ${containerParam} "--"`
|
let command = `kubectl exec -i -t -n ${pod.getNs()} ${pod.getName()} ${containerParam} "--"`;
|
||||||
if (window.navigator.platform !== "Win32") {
|
if (window.navigator.platform !== "Win32") {
|
||||||
command = `exec ${command}`
|
command = `exec ${command}`;
|
||||||
}
|
}
|
||||||
if (pod.getSelectedNodeOs() === "windows") {
|
if (pod.getSelectedNodeOs() === "windows") {
|
||||||
command = `${command} powershell`
|
command = `${command} powershell`;
|
||||||
} else {
|
} else {
|
||||||
command = `${command} sh -c "clear; (bash || ash || sh)"`
|
command = `${command} sh -c "clear; (bash || ash || sh)"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shell = Component.createTerminalTab({
|
const shell = Component.createTerminalTab({
|
||||||
@ -32,7 +32,7 @@ export class PodShellMenu extends React.Component<PodShellMenuProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { object, toolbar } = this.props
|
const { object, toolbar } = this.props;
|
||||||
const containers = object.getRunningContainers();
|
const containers = object.getRunningContainers();
|
||||||
if (!containers.length) return null;
|
if (!containers.length) return null;
|
||||||
return (
|
return (
|
||||||
@ -51,13 +51,13 @@ export class PodShellMenu extends React.Component<PodShellMenuProps> {
|
|||||||
<Component.StatusBrick/>
|
<Component.StatusBrick/>
|
||||||
{name}
|
{name}
|
||||||
</Component.MenuItem>
|
</Component.MenuItem>
|
||||||
)
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</Component.SubMenu>
|
</Component.SubMenu>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Component.MenuItem>
|
</Component.MenuItem>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
// TODO: support localization / figure out how to extract / consume i18n strings
|
// TODO: support localization / figure out how to extract / consume i18n strings
|
||||||
|
|
||||||
import "./support.scss"
|
import "./support.scss";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
import { observer } from "mobx-react"
|
import { observer } from "mobx-react";
|
||||||
import { App, Component } from "@k8slens/extensions";
|
import { App, Component } from "@k8slens/extensions";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import path from "path"
|
import path from "path";
|
||||||
|
|
||||||
const outputPath = path.resolve(__dirname, 'dist');
|
const outputPath = path.resolve(__dirname, 'dist');
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
import { LensMainExtension } from "@k8slens/extensions";
|
import { LensMainExtension } from "@k8slens/extensions";
|
||||||
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store"
|
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store";
|
||||||
import { tracker } from "./src/tracker";
|
import { tracker } from "./src/tracker";
|
||||||
|
|
||||||
export default class TelemetryMainExtension extends LensMainExtension {
|
export default class TelemetryMainExtension extends LensMainExtension {
|
||||||
|
|
||||||
async onActivate() {
|
async onActivate() {
|
||||||
console.log("telemetry main extension activated")
|
console.log("telemetry main extension activated");
|
||||||
tracker.start()
|
tracker.start();
|
||||||
tracker.reportPeriodically()
|
tracker.reportPeriodically();
|
||||||
await telemetryPreferencesStore.loadExtension(this)
|
await telemetryPreferencesStore.loadExtension(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeactivate() {
|
onDeactivate() {
|
||||||
tracker.stop()
|
tracker.stop();
|
||||||
console.log("telemetry main extension deactivated")
|
console.log("telemetry main extension deactivated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { LensRendererExtension } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store"
|
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store";
|
||||||
import { TelemetryPreferenceHint, TelemetryPreferenceInput } from "./src/telemetry-preference"
|
import { TelemetryPreferenceHint, TelemetryPreferenceInput } from "./src/telemetry-preference";
|
||||||
import { tracker } from "./src/tracker"
|
import { tracker } from "./src/tracker";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export default class TelemetryRendererExtension extends LensRendererExtension {
|
export default class TelemetryRendererExtension extends LensRendererExtension {
|
||||||
appPreferences = [
|
appPreferences = [
|
||||||
@ -16,8 +16,8 @@ export default class TelemetryRendererExtension extends LensRendererExtension {
|
|||||||
];
|
];
|
||||||
|
|
||||||
async onActivate() {
|
async onActivate() {
|
||||||
console.log("telemetry extension activated")
|
console.log("telemetry extension activated");
|
||||||
tracker.start()
|
tracker.start();
|
||||||
await telemetryPreferencesStore.loadExtension(this)
|
await telemetryPreferencesStore.loadExtension(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
import { Component } from "@k8slens/extensions"
|
import { Component } from "@k8slens/extensions";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { TelemetryPreferencesStore } from "./telemetry-preferences-store"
|
import { TelemetryPreferencesStore } from "./telemetry-preferences-store";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class TelemetryPreferenceInput extends React.Component<{telemetry: TelemetryPreferencesStore}, {}> {
|
export class TelemetryPreferenceInput extends React.Component<{telemetry: TelemetryPreferencesStore}, {}> {
|
||||||
render() {
|
render() {
|
||||||
const { telemetry } = this.props
|
const { telemetry } = this.props;
|
||||||
return (
|
return (
|
||||||
<Component.Checkbox
|
<Component.Checkbox
|
||||||
label="Allow telemetry & usage tracking"
|
label="Allow telemetry & usage tracking"
|
||||||
value={telemetry.enabled}
|
value={telemetry.enabled}
|
||||||
onChange={v => { telemetry.enabled = v; }}
|
onChange={v => { telemetry.enabled = v; }}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,6 +21,6 @@ export class TelemetryPreferenceHint extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<span>Telemetry & usage data is collected to continuously improve the Lens experience.</span>
|
<span>Telemetry & usage data is collected to continuously improve the Lens experience.</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Store } from "@k8slens/extensions";
|
import { Store } from "@k8slens/extensions";
|
||||||
import { toJS } from "mobx"
|
import { toJS } from "mobx";
|
||||||
|
|
||||||
export type TelemetryPreferencesModel = {
|
export type TelemetryPreferencesModel = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -14,11 +14,11 @@ export class TelemetryPreferencesStore extends Store.ExtensionStore<TelemetryPre
|
|||||||
defaults: {
|
defaults: {
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fromStore({ enabled }: TelemetryPreferencesModel): void {
|
protected fromStore({ enabled }: TelemetryPreferencesModel): void {
|
||||||
this.enabled = enabled
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): TelemetryPreferencesModel {
|
toJSON(): TelemetryPreferencesModel {
|
||||||
@ -26,8 +26,8 @@ export class TelemetryPreferencesStore extends Store.ExtensionStore<TelemetryPre
|
|||||||
enabled: this.enabled
|
enabled: this.enabled
|
||||||
}, {
|
}, {
|
||||||
recurseEverything: true
|
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 { EventBus, Util, Store, App } from "@k8slens/extensions";
|
||||||
import ua from "universal-analytics"
|
import ua from "universal-analytics";
|
||||||
import Analytics from "analytics-node"
|
import Analytics from "analytics-node";
|
||||||
import { machineIdSync } from "node-machine-id"
|
import { machineIdSync } from "node-machine-id";
|
||||||
import { telemetryPreferencesStore } from "./telemetry-preferences-store"
|
import { telemetryPreferencesStore } from "./telemetry-preferences-store";
|
||||||
|
|
||||||
export class Tracker extends Util.Singleton {
|
export class Tracker extends Util.Singleton {
|
||||||
static readonly GA_ID = "UA-159377374-1"
|
static readonly GA_ID = "UA-159377374-1"
|
||||||
@ -23,69 +23,69 @@ export class Tracker extends Util.Singleton {
|
|||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
super();
|
super();
|
||||||
this.anonymousId = machineIdSync()
|
this.anonymousId = machineIdSync();
|
||||||
this.os = this.resolveOS()
|
this.os = this.resolveOS();
|
||||||
this.userAgent = `Lens ${App.version} (${this.os})`
|
this.userAgent = `Lens ${App.version} (${this.os})`;
|
||||||
try {
|
try {
|
||||||
this.visitor = ua(Tracker.GA_ID, this.anonymousId, { strictCidFormat: false })
|
this.visitor = ua(Tracker.GA_ID, this.anonymousId, { strictCidFormat: false });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.visitor = ua(Tracker.GA_ID)
|
this.visitor = ua(Tracker.GA_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.analytics = new Analytics(Tracker.SEGMENT_KEY, { flushAt: 1 })
|
this.analytics = new Analytics(Tracker.SEGMENT_KEY, { flushAt: 1 });
|
||||||
this.visitor.set("dl", "https://telemetry.k8slens.dev")
|
this.visitor.set("dl", "https://telemetry.k8slens.dev");
|
||||||
this.visitor.set("ua", this.userAgent)
|
this.visitor.set("ua", this.userAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if (this.started === true) { return }
|
if (this.started === true) { return; }
|
||||||
|
|
||||||
this.started = true
|
this.started = true;
|
||||||
|
|
||||||
const handler = (ev: EventBus.AppEvent) => {
|
const handler = (ev: EventBus.AppEvent) => {
|
||||||
this.event(ev.name, ev.action, ev.params)
|
this.event(ev.name, ev.action, ev.params);
|
||||||
}
|
};
|
||||||
this.eventHandlers.push(handler)
|
this.eventHandlers.push(handler);
|
||||||
EventBus.appEventBus.addListener(handler)
|
EventBus.appEventBus.addListener(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
reportPeriodically() {
|
reportPeriodically() {
|
||||||
this.reportInterval = setInterval(() => {
|
this.reportInterval = setInterval(() => {
|
||||||
this.reportData()
|
this.reportData();
|
||||||
}, 60 * 60 * 1000) // report every 1h
|
}, 60 * 60 * 1000); // report every 1h
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
if (!this.started) { return }
|
if (!this.started) { return; }
|
||||||
|
|
||||||
this.started = false
|
this.started = false;
|
||||||
|
|
||||||
for (const handler of this.eventHandlers) {
|
for (const handler of this.eventHandlers) {
|
||||||
EventBus.appEventBus.removeListener(handler)
|
EventBus.appEventBus.removeListener(handler);
|
||||||
}
|
}
|
||||||
if (this.reportInterval) {
|
if (this.reportInterval) {
|
||||||
clearInterval(this.reportInterval)
|
clearInterval(this.reportInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async isTelemetryAllowed(): Promise<boolean> {
|
protected async isTelemetryAllowed(): Promise<boolean> {
|
||||||
return telemetryPreferencesStore.enabled
|
return telemetryPreferencesStore.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected reportData() {
|
protected reportData() {
|
||||||
const clustersList = Store.clusterStore.enabledClustersList
|
const clustersList = Store.clusterStore.enabledClustersList;
|
||||||
|
|
||||||
this.event("generic-data", "report", {
|
this.event("generic-data", "report", {
|
||||||
appVersion: App.version,
|
appVersion: App.version,
|
||||||
os: this.os,
|
os: this.os,
|
||||||
clustersCount: clustersList.length,
|
clustersCount: clustersList.length,
|
||||||
workspacesCount: Store.workspaceStore.enabledWorkspacesList.length
|
workspacesCount: Store.workspaceStore.enabledWorkspacesList.length
|
||||||
})
|
});
|
||||||
|
|
||||||
clustersList.forEach((cluster) => {
|
clustersList.forEach((cluster) => {
|
||||||
if (!cluster?.metadata.lastSeen) { return }
|
if (!cluster?.metadata.lastSeen) { return; }
|
||||||
this.reportClusterData(cluster)
|
this.reportClusterData(cluster);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected reportClusterData(cluster: Store.ClusterModel) {
|
protected reportClusterData(cluster: Store.ClusterModel) {
|
||||||
@ -96,26 +96,26 @@ export class Tracker extends Util.Singleton {
|
|||||||
distribution: cluster.metadata.distribution,
|
distribution: cluster.metadata.distribution,
|
||||||
nodesCount: cluster.metadata.nodes,
|
nodesCount: cluster.metadata.nodes,
|
||||||
lastSeen: cluster.metadata.lastSeen
|
lastSeen: cluster.metadata.lastSeen
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected resolveOS() {
|
protected resolveOS() {
|
||||||
let os = ""
|
let os = "";
|
||||||
if (App.isMac) {
|
if (App.isMac) {
|
||||||
os = "MacOS"
|
os = "MacOS";
|
||||||
} else if(App.isWindows) {
|
} else if(App.isWindows) {
|
||||||
os = "Windows"
|
os = "Windows";
|
||||||
} else if (App.isLinux) {
|
} else if (App.isLinux) {
|
||||||
os = "Linux"
|
os = "Linux";
|
||||||
if (App.isSnap) {
|
if (App.isSnap) {
|
||||||
os += "; Snap"
|
os += "; Snap";
|
||||||
} else {
|
} else {
|
||||||
os += "; AppImage"
|
os += "; AppImage";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
os = "Unknown"
|
os = "Unknown";
|
||||||
}
|
}
|
||||||
return os
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async event(eventCategory: string, eventAction: string, otherParams = {}) {
|
protected async event(eventCategory: string, eventAction: string, otherParams = {}) {
|
||||||
@ -128,7 +128,7 @@ export class Tracker extends Util.Singleton {
|
|||||||
ec: eventCategory,
|
ec: eventCategory,
|
||||||
ea: eventAction,
|
ea: eventAction,
|
||||||
...otherParams,
|
...otherParams,
|
||||||
}).send()
|
}).send();
|
||||||
|
|
||||||
this.analytics.track({
|
this.analytics.track({
|
||||||
anonymousId: this.anonymousId,
|
anonymousId: this.anonymousId,
|
||||||
@ -141,9 +141,9 @@ export class Tracker extends Util.Singleton {
|
|||||||
...otherParams,
|
...otherParams,
|
||||||
},
|
},
|
||||||
|
|
||||||
})
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to track "${eventCategory}:${eventAction}"`, err)
|
console.error(`Failed to track "${eventCategory}:${eventAction}"`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,159 +4,159 @@
|
|||||||
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
|
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
|
||||||
cluster and vice versa.
|
cluster and vice versa.
|
||||||
*/
|
*/
|
||||||
import { Application } from "spectron"
|
import { Application } from "spectron";
|
||||||
import * as util from "../helpers/utils"
|
import * as util from "../helpers/utils";
|
||||||
import { spawnSync } from "child_process"
|
import { spawnSync } from "child_process";
|
||||||
|
|
||||||
const describeif = (condition: boolean) => condition ? describe : describe.skip
|
const describeif = (condition: boolean) => condition ? describe : describe.skip;
|
||||||
const itif = (condition: boolean) => condition ? it : it.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)
|
// FIXME (!): improve / simplify all css-selectors + use [data-test-id="some-id"] (already used in some tests below)
|
||||||
describe("Lens integration tests", () => {
|
describe("Lens integration tests", () => {
|
||||||
const TEST_NAMESPACE = "integration-tests"
|
const TEST_NAMESPACE = "integration-tests";
|
||||||
|
|
||||||
const BACKSPACE = "\uE003"
|
const BACKSPACE = "\uE003";
|
||||||
let app: Application
|
let app: Application;
|
||||||
|
|
||||||
const appStart = async () => {
|
const appStart = async () => {
|
||||||
app = util.setup()
|
app = util.setup();
|
||||||
await app.start()
|
await app.start();
|
||||||
// Wait for splash screen to be closed
|
// Wait for splash screen to be closed
|
||||||
while (await app.client.getWindowCount() > 1);
|
while (await app.client.getWindowCount() > 1);
|
||||||
await app.client.windowByIndex(0)
|
await app.client.windowByIndex(0);
|
||||||
await app.client.waitUntilWindowLoaded()
|
await app.client.waitUntilWindowLoaded();
|
||||||
}
|
};
|
||||||
|
|
||||||
const clickWhatsNew = async (app: Application) => {
|
const clickWhatsNew = async (app: Application) => {
|
||||||
await app.client.waitUntilTextExists("h1", "What's new?")
|
await app.client.waitUntilTextExists("h1", "What's new?");
|
||||||
await app.client.click("button.primary")
|
await app.client.click("button.primary");
|
||||||
await app.client.waitUntilTextExists("h1", "Welcome")
|
await app.client.waitUntilTextExists("h1", "Welcome");
|
||||||
}
|
};
|
||||||
|
|
||||||
describe("app start", () => {
|
describe("app start", () => {
|
||||||
beforeAll(appStart, 20000)
|
beforeAll(appStart, 20000);
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
if (app && app.isRunning()) {
|
if (app && app.isRunning()) {
|
||||||
return util.tearDown(app)
|
return util.tearDown(app);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
it('shows "whats new"', async () => {
|
it('shows "whats new"', async () => {
|
||||||
await clickWhatsNew(app)
|
await clickWhatsNew(app);
|
||||||
})
|
});
|
||||||
|
|
||||||
it('shows "add cluster"', async () => {
|
it('shows "add cluster"', async () => {
|
||||||
await app.electron.ipcRenderer.send('test-menu-item-click', "File", "Add Cluster")
|
await app.electron.ipcRenderer.send('test-menu-item-click', "File", "Add Cluster");
|
||||||
await app.client.waitUntilTextExists("h2", "Add Cluster")
|
await app.client.waitUntilTextExists("h2", "Add Cluster");
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("preferences page", () => {
|
describe("preferences page", () => {
|
||||||
it('shows "preferences"', async () => {
|
it('shows "preferences"', async () => {
|
||||||
let appName: string = process.platform === "darwin" ? "Lens" : "File"
|
const appName: string = process.platform === "darwin" ? "Lens" : "File";
|
||||||
await app.electron.ipcRenderer.send('test-menu-item-click', appName, "Preferences")
|
await app.electron.ipcRenderer.send('test-menu-item-click', appName, "Preferences");
|
||||||
await app.client.waitUntilTextExists("h2", "Preferences")
|
await app.client.waitUntilTextExists("h2", "Preferences");
|
||||||
})
|
});
|
||||||
|
|
||||||
it('ensures helm repos', async () => {
|
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.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.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.Select__option", ""); // wait for at least one option to appear (any text)
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
it.skip('quits Lens"', async () => {
|
it.skip('quits Lens"', async () => {
|
||||||
await app.client.keys(['Meta', 'Q'])
|
await app.client.keys(['Meta', 'Q']);
|
||||||
await app.client.keys('Meta')
|
await app.client.keys('Meta');
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
const minikubeReady = (): boolean => {
|
const minikubeReady = (): boolean => {
|
||||||
// determine if minikube is running
|
// determine if minikube is running
|
||||||
let status = spawnSync("minikube status", { shell: true })
|
let status = spawnSync("minikube status", { shell: true });
|
||||||
if (status.status !== 0) {
|
if (status.status !== 0) {
|
||||||
console.warn("minikube not running")
|
console.warn("minikube not running");
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove TEST_NAMESPACE if it already exists
|
// 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) {
|
if (status.status === 0) {
|
||||||
console.warn(`Removing existing ${TEST_NAMESPACE} namespace`)
|
console.warn(`Removing existing ${TEST_NAMESPACE} namespace`);
|
||||||
status = spawnSync(`minikube kubectl -- delete namespace ${TEST_NAMESPACE}`, { shell: true })
|
status = spawnSync(`minikube kubectl -- delete namespace ${TEST_NAMESPACE}`, { shell: true });
|
||||||
if (status.status !== 0) {
|
if (status.status !== 0) {
|
||||||
console.warn(`Error removing ${TEST_NAMESPACE} namespace: ${status.stderr.toString()}`)
|
console.warn(`Error removing ${TEST_NAMESPACE} namespace: ${status.stderr.toString()}`);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
console.log(status.stdout.toString())
|
console.log(status.stdout.toString());
|
||||||
}
|
}
|
||||||
return true
|
return true;
|
||||||
}
|
};
|
||||||
const ready = minikubeReady()
|
const ready = minikubeReady();
|
||||||
|
|
||||||
const addMinikubeCluster = async (app: Application) => {
|
const addMinikubeCluster = async (app: Application) => {
|
||||||
await app.client.click("div.add-cluster")
|
await app.client.click("div.add-cluster");
|
||||||
await app.client.waitUntilTextExists("div", "Select kubeconfig file")
|
await app.client.waitUntilTextExists("div", "Select kubeconfig file");
|
||||||
await app.client.click("div.Select__control") // show the context drop-down list
|
await app.client.click("div.Select__control"); // show the context drop-down list
|
||||||
await app.client.waitUntilTextExists("div", "minikube")
|
await app.client.waitUntilTextExists("div", "minikube");
|
||||||
if (!await app.client.$("button.primary").isEnabled()) {
|
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
|
} // 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("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("button.primary"); // add minikube cluster
|
||||||
}
|
};
|
||||||
|
|
||||||
const waitForMinikubeDashboard = async (app: Application) => {
|
const waitForMinikubeDashboard = async (app: Application) => {
|
||||||
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started")
|
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started");
|
||||||
await app.client.waitForExist(`iframe[name="minikube"]`)
|
await app.client.waitForExist(`iframe[name="minikube"]`);
|
||||||
await app.client.frame("minikube")
|
await app.client.frame("minikube");
|
||||||
await app.client.waitUntilTextExists("span.link-text", "Cluster")
|
await app.client.waitUntilTextExists("span.link-text", "Cluster");
|
||||||
}
|
};
|
||||||
|
|
||||||
describeif(ready)("cluster tests", () => {
|
describeif(ready)("cluster tests", () => {
|
||||||
let clusterAdded = false
|
let clusterAdded = false;
|
||||||
|
|
||||||
const addCluster = async () => {
|
const addCluster = async () => {
|
||||||
await clickWhatsNew(app)
|
await clickWhatsNew(app);
|
||||||
await addMinikubeCluster(app)
|
await addMinikubeCluster(app);
|
||||||
await waitForMinikubeDashboard(app)
|
await waitForMinikubeDashboard(app);
|
||||||
await app.client.click('a[href="/nodes"]')
|
await app.client.click('a[href="/nodes"]');
|
||||||
await app.client.waitUntilTextExists("div.TableCell", "Ready")
|
await app.client.waitUntilTextExists("div.TableCell", "Ready");
|
||||||
}
|
};
|
||||||
|
|
||||||
describe("cluster add", () => {
|
describe("cluster add", () => {
|
||||||
beforeAll(appStart, 20000)
|
beforeAll(appStart, 20000);
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
if (app && app.isRunning()) {
|
if (app && app.isRunning()) {
|
||||||
return util.tearDown(app)
|
return util.tearDown(app);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
it('allows to add a cluster', async () => {
|
it('allows to add a cluster', async () => {
|
||||||
await addCluster()
|
await addCluster();
|
||||||
clusterAdded = true
|
clusterAdded = true;
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
const appStartAddCluster = async () => {
|
const appStartAddCluster = async () => {
|
||||||
if (clusterAdded) {
|
if (clusterAdded) {
|
||||||
await appStart()
|
await appStart();
|
||||||
await addCluster()
|
await addCluster();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
describe("cluster pages", () => {
|
describe("cluster pages", () => {
|
||||||
|
|
||||||
beforeAll(appStartAddCluster, 40000)
|
beforeAll(appStartAddCluster, 40000);
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
if (app && app.isRunning()) {
|
if (app && app.isRunning()) {
|
||||||
return util.tearDown(app)
|
return util.tearDown(app);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const tests: {
|
const tests: {
|
||||||
drawer?: string
|
drawer?: string
|
||||||
@ -394,119 +394,119 @@ describe("Lens integration tests", () => {
|
|||||||
tests.forEach(({ drawer = "", drawerId = "", pages }) => {
|
tests.forEach(({ drawer = "", drawerId = "", pages }) => {
|
||||||
if (drawer !== "") {
|
if (drawer !== "") {
|
||||||
it(`shows ${drawer} drawer`, async () => {
|
it(`shows ${drawer} drawer`, async () => {
|
||||||
expect(clusterAdded).toBe(true)
|
expect(clusterAdded).toBe(true);
|
||||||
await app.client.click(`.sidebar-nav [data-test-id="${drawerId}"] span.link-text`)
|
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)
|
await app.client.waitUntilTextExists(`a[href^="/${pages[0].href}"]`, pages[0].name);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
pages.forEach(({ name, href, expectedSelector, expectedText }) => {
|
pages.forEach(({ name, href, expectedSelector, expectedText }) => {
|
||||||
it(`shows ${drawer}->${name} page`, async () => {
|
it(`shows ${drawer}->${name} page`, async () => {
|
||||||
expect(clusterAdded).toBe(true)
|
expect(clusterAdded).toBe(true);
|
||||||
await app.client.click(`a[href^="/${href}"]`)
|
await app.client.click(`a[href^="/${href}"]`);
|
||||||
await app.client.waitUntilTextExists(expectedSelector, expectedText)
|
await app.client.waitUntilTextExists(expectedSelector, expectedText);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
if (drawer !== "") {
|
if (drawer !== "") {
|
||||||
// hide the drawer
|
// hide the drawer
|
||||||
it(`hides ${drawer} drawer`, async () => {
|
it(`hides ${drawer} drawer`, async () => {
|
||||||
expect(clusterAdded).toBe(true)
|
expect(clusterAdded).toBe(true);
|
||||||
await app.client.click(`.sidebar-nav [data-test-id="${drawerId}"] span.link-text`)
|
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()
|
await expect(app.client.waitUntilTextExists(`a[href^="/${pages[0].href}"]`, pages[0].name, 100)).rejects.toThrow();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("viewing pod logs", () => {
|
describe("viewing pod logs", () => {
|
||||||
beforeEach(appStartAddCluster, 40000)
|
beforeEach(appStartAddCluster, 40000);
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (app && app.isRunning()) {
|
if (app && app.isRunning()) {
|
||||||
return util.tearDown(app)
|
return util.tearDown(app);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
it(`shows a logs for a pod`, async () => {
|
it(`shows a logs for a pod`, async () => {
|
||||||
expect(clusterAdded).toBe(true)
|
expect(clusterAdded).toBe(true);
|
||||||
// Go to Pods page
|
// Go to Pods page
|
||||||
await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text")
|
await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text");
|
||||||
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods")
|
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods");
|
||||||
await app.client.click('a[href^="/pods"]')
|
await app.client.click('a[href^="/pods"]');
|
||||||
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver")
|
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver");
|
||||||
// Open logs tab in dock
|
// Open logs tab in dock
|
||||||
await app.client.click(".list .TableRow:first-child")
|
await app.client.click(".list .TableRow:first-child");
|
||||||
await app.client.waitForVisible(".Drawer")
|
await app.client.waitForVisible(".Drawer");
|
||||||
await app.client.click(".drawer-title .Menu li:nth-child(2)")
|
await app.client.click(".drawer-title .Menu li:nth-child(2)");
|
||||||
// Check if controls are available
|
// Check if controls are available
|
||||||
await app.client.waitForVisible(".PodLogs .VirtualList")
|
await app.client.waitForVisible(".PodLogs .VirtualList");
|
||||||
await app.client.waitForVisible(".PodLogControls")
|
await app.client.waitForVisible(".PodLogControls");
|
||||||
await app.client.waitForVisible(".PodLogControls .SearchInput")
|
await app.client.waitForVisible(".PodLogControls .SearchInput");
|
||||||
await app.client.waitForVisible(".PodLogControls .SearchInput input")
|
await app.client.waitForVisible(".PodLogControls .SearchInput input");
|
||||||
// Search for semicolon
|
// Search for semicolon
|
||||||
await app.client.keys(":")
|
await app.client.keys(":");
|
||||||
await app.client.waitForVisible(".PodLogs .list span.active")
|
await app.client.waitForVisible(".PodLogs .list span.active");
|
||||||
// Click through controls
|
// Click through controls
|
||||||
await app.client.click(".PodLogControls .timestamps-icon")
|
await app.client.click(".PodLogControls .timestamps-icon");
|
||||||
await app.client.click(".PodLogControls .undo-icon")
|
await app.client.click(".PodLogControls .undo-icon");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("cluster operations", () => {
|
describe("cluster operations", () => {
|
||||||
beforeEach(appStartAddCluster, 40000)
|
beforeEach(appStartAddCluster, 40000);
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (app && app.isRunning()) {
|
if (app && app.isRunning()) {
|
||||||
return util.tearDown(app)
|
return util.tearDown(app);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
it('shows default namespace', async () => {
|
it('shows default namespace', async () => {
|
||||||
expect(clusterAdded).toBe(true)
|
expect(clusterAdded).toBe(true);
|
||||||
await app.client.click('a[href="/namespaces"]')
|
await app.client.click('a[href="/namespaces"]');
|
||||||
await app.client.waitUntilTextExists("div.TableCell", "default")
|
await app.client.waitUntilTextExists("div.TableCell", "default");
|
||||||
await app.client.waitUntilTextExists("div.TableCell", "kube-system")
|
await app.client.waitUntilTextExists("div.TableCell", "kube-system");
|
||||||
})
|
});
|
||||||
|
|
||||||
it(`creates ${TEST_NAMESPACE} namespace`, async () => {
|
it(`creates ${TEST_NAMESPACE} namespace`, async () => {
|
||||||
expect(clusterAdded).toBe(true)
|
expect(clusterAdded).toBe(true);
|
||||||
await app.client.click('a[href="/namespaces"]')
|
await app.client.click('a[href="/namespaces"]');
|
||||||
await app.client.waitUntilTextExists("div.TableCell", "default")
|
await app.client.waitUntilTextExists("div.TableCell", "default");
|
||||||
await app.client.waitUntilTextExists("div.TableCell", "kube-system")
|
await app.client.waitUntilTextExists("div.TableCell", "kube-system");
|
||||||
await app.client.click("button.add-button")
|
await app.client.click("button.add-button");
|
||||||
await app.client.waitUntilTextExists("div.AddNamespaceDialog", "Create Namespace")
|
await app.client.waitUntilTextExists("div.AddNamespaceDialog", "Create Namespace");
|
||||||
await app.client.keys(`${TEST_NAMESPACE}\n`)
|
await app.client.keys(`${TEST_NAMESPACE}\n`);
|
||||||
await app.client.waitForExist(`.name=${TEST_NAMESPACE}`)
|
await app.client.waitForExist(`.name=${TEST_NAMESPACE}`);
|
||||||
})
|
});
|
||||||
|
|
||||||
it(`creates a pod in ${TEST_NAMESPACE} namespace`, async () => {
|
it(`creates a pod in ${TEST_NAMESPACE} namespace`, async () => {
|
||||||
expect(clusterAdded).toBe(true)
|
expect(clusterAdded).toBe(true);
|
||||||
await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text")
|
await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text");
|
||||||
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods")
|
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods");
|
||||||
await app.client.click('a[href^="/pods"]')
|
await app.client.click('a[href^="/pods"]');
|
||||||
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver")
|
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver");
|
||||||
await app.client.click('.Icon.new-dock-tab')
|
await app.client.click('.Icon.new-dock-tab');
|
||||||
await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource")
|
await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource");
|
||||||
await app.client.click("li.MenuItem.create-resource-tab")
|
await app.client.click("li.MenuItem.create-resource-tab");
|
||||||
await app.client.waitForVisible(".CreateResource div.ace_content")
|
await app.client.waitForVisible(".CreateResource div.ace_content");
|
||||||
// Write pod manifest to editor
|
// Write pod manifest to editor
|
||||||
await app.client.keys("apiVersion: v1\n")
|
await app.client.keys("apiVersion: v1\n");
|
||||||
await app.client.keys("kind: Pod\n")
|
await app.client.keys("kind: Pod\n");
|
||||||
await app.client.keys("metadata:\n")
|
await app.client.keys("metadata:\n");
|
||||||
await app.client.keys(" name: nginx-create-pod-test\n")
|
await app.client.keys(" name: nginx-create-pod-test\n");
|
||||||
await app.client.keys(`namespace: ${TEST_NAMESPACE}\n`)
|
await app.client.keys(`namespace: ${TEST_NAMESPACE}\n`);
|
||||||
await app.client.keys(BACKSPACE + "spec:\n")
|
await app.client.keys(BACKSPACE + "spec:\n");
|
||||||
await app.client.keys(" containers:\n")
|
await app.client.keys(" containers:\n");
|
||||||
await app.client.keys("- name: nginx-create-pod-test\n")
|
await app.client.keys("- name: nginx-create-pod-test\n");
|
||||||
await app.client.keys(" image: nginx:alpine\n")
|
await app.client.keys(" image: nginx:alpine\n");
|
||||||
// Create deployment
|
// Create deployment
|
||||||
await app.client.waitForEnabled("button.Button=Create & Close")
|
await app.client.waitForEnabled("button.Button=Create & Close");
|
||||||
await app.client.click("button.Button=Create & Close")
|
await app.client.click("button.Button=Create & Close");
|
||||||
// Wait until first bits of pod appears on dashboard
|
// 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
|
// Open pod details
|
||||||
await app.client.click(".name=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")
|
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",
|
"win32": "./dist/win-unpacked/Lens.exe",
|
||||||
"linux": "./dist/linux-unpacked/kontena-lens",
|
"linux": "./dist/linux-unpacked/kontena-lens",
|
||||||
"darwin": "./dist/mac/Lens.app/Contents/MacOS/Lens",
|
"darwin": "./dist/mac/Lens.app/Contents/MacOS/Lens",
|
||||||
}
|
};
|
||||||
|
|
||||||
export function setup(): Application {
|
export function setup(): Application {
|
||||||
return new Application({
|
return new Application({
|
||||||
@ -16,16 +16,16 @@ export function setup(): Application {
|
|||||||
env: {
|
env: {
|
||||||
CICD: "true"
|
CICD: "true"
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function tearDown(app: Application) {
|
export async function tearDown(app: Application) {
|
||||||
let mpid: any = app.mainProcess.pid
|
const mpid: any = app.mainProcess.pid;
|
||||||
let pid = await mpid()
|
const pid = await mpid();
|
||||||
await app.stop()
|
await app.stop();
|
||||||
try {
|
try {
|
||||||
process.kill(pid, "SIGKILL");
|
process.kill(pid, "SIGKILL");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
"download:kubectl": "yarn run ts-node build/download_kubectl.ts",
|
"download:kubectl": "yarn run ts-node build/download_kubectl.ts",
|
||||||
"download:helm": "yarn run ts-node build/download_helm.ts",
|
"download:helm": "yarn run ts-node build/download_helm.ts",
|
||||||
"build:tray-icons": "yarn run ts-node build/build_tray_icon.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",
|
"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",
|
"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"
|
"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