mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge pull request #1450 from lensapp/feature/enforce-semicolons
Enforce semicolons in eslint
This commit is contained in:
commit
c94c599cdd
@ -20,6 +20,7 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
"indent": ["error", 2],
|
"indent": ["error", 2],
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
|
"semi": ["error", "always"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -47,7 +48,9 @@ module.exports = {
|
|||||||
"@typescript-eslint/ban-types": "off",
|
"@typescript-eslint/ban-types": "off",
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/no-empty-interface": "off",
|
"@typescript-eslint/no-empty-interface": "off",
|
||||||
"indent": ["error", 2]
|
"indent": ["error", 2],
|
||||||
|
"semi": "off",
|
||||||
|
"@typescript-eslint/semi": ["error"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -75,7 +78,9 @@ module.exports = {
|
|||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
"@typescript-eslint/ban-types": "off",
|
"@typescript-eslint/ban-types": "off",
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"indent": ["error", 2]
|
"indent": ["error", 2],
|
||||||
|
"semi": "off",
|
||||||
|
"@typescript-eslint/semi": ["error"],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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,20 +1,20 @@
|
|||||||
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;
|
||||||
protected url: string
|
protected url: string;
|
||||||
protected path: string;
|
protected path: string;
|
||||||
protected dirname: string
|
protected dirname: string;
|
||||||
|
|
||||||
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 = [
|
||||||
@ -13,8 +13,8 @@ export default class ClusterMetricsFeatureExtension extends LensRendererExtensio
|
|||||||
Enable timeseries data visualization (Prometheus stack) for your cluster.
|
Enable timeseries data visualization (Prometheus stack) for your cluster.
|
||||||
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,8 @@
|
|||||||
"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",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import { Cluster } from "../../main/cluster";
|
|||||||
import { ClusterStore } from "../cluster-store";
|
import { ClusterStore } from "../cluster-store";
|
||||||
import { workspaceStore } from "../workspace-store";
|
import { workspaceStore } from "../workspace-store";
|
||||||
|
|
||||||
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png")
|
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png");
|
||||||
|
|
||||||
console.log("") // fix bug
|
console.log(""); // fix bug
|
||||||
|
|
||||||
let clusterStore: ClusterStore;
|
let clusterStore: ClusterStore;
|
||||||
|
|
||||||
@ -18,15 +18,15 @@ describe("empty config", () => {
|
|||||||
'tmp': {
|
'tmp': {
|
||||||
'lens-cluster-store.json': JSON.stringify({})
|
'lens-cluster-store.json': JSON.stringify({})
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
||||||
return clusterStore.load();
|
return clusterStore.load();
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("with foo cluster added", () => {
|
describe("with foo cluster added", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -43,30 +43,30 @@ describe("empty config", () => {
|
|||||||
workspace: workspaceStore.currentWorkspaceId
|
workspace: workspaceStore.currentWorkspaceId
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("adds new cluster to store", async () => {
|
it("adds new cluster to store", async () => {
|
||||||
const storedCluster = clusterStore.getById("foo");
|
const storedCluster = clusterStore.getById("foo");
|
||||||
expect(storedCluster.id).toBe("foo");
|
expect(storedCluster.id).toBe("foo");
|
||||||
expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
|
expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
|
||||||
expect(storedCluster.preferences.icon).toBe("data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5");
|
expect(storedCluster.preferences.icon).toBe("data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("adds cluster to default workspace", () => {
|
it("adds cluster to default workspace", () => {
|
||||||
const storedCluster = clusterStore.getById("foo");
|
const storedCluster = clusterStore.getById("foo");
|
||||||
expect(storedCluster.workspace).toBe("default");
|
expect(storedCluster.workspace).toBe("default");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("removes cluster from store", async () => {
|
it("removes cluster from store", async () => {
|
||||||
await clusterStore.removeById("foo");
|
await clusterStore.removeById("foo");
|
||||||
expect(clusterStore.getById("foo")).toBeUndefined();
|
expect(clusterStore.getById("foo")).toBeUndefined();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("sets active cluster", () => {
|
it("sets active cluster", () => {
|
||||||
clusterStore.setActive("foo");
|
clusterStore.setActive("foo");
|
||||||
expect(clusterStore.active.id).toBe("foo");
|
expect(clusterStore.active.id).toBe("foo");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("with prod and dev clusters added", () => {
|
describe("with prod and dev clusters added", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -89,8 +89,8 @@ describe("empty config", () => {
|
|||||||
kubeConfigPath: ClusterStore.embedCustomKubeConfig("dev", "fancy config"),
|
kubeConfigPath: ClusterStore.embedCustomKubeConfig("dev", "fancy config"),
|
||||||
workspace: "workstation"
|
workspace: "workstation"
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("check if store can contain multiple clusters", () => {
|
it("check if store can contain multiple clusters", () => {
|
||||||
expect(clusterStore.hasClusters()).toBeTruthy();
|
expect(clusterStore.hasClusters()).toBeTruthy();
|
||||||
@ -104,42 +104,42 @@ describe("empty config", () => {
|
|||||||
expect(wsClusters.length).toBe(2);
|
expect(wsClusters.length).toBe(2);
|
||||||
expect(wsClusters[0].id).toBe("prod");
|
expect(wsClusters[0].id).toBe("prod");
|
||||||
expect(wsClusters[1].id).toBe("dev");
|
expect(wsClusters[1].id).toBe("dev");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("check if cluster's kubeconfig file saved", () => {
|
it("check if cluster's kubeconfig file saved", () => {
|
||||||
const file = ClusterStore.embedCustomKubeConfig("boo", "kubeconfig");
|
const file = ClusterStore.embedCustomKubeConfig("boo", "kubeconfig");
|
||||||
expect(fs.readFileSync(file, "utf8")).toBe("kubeconfig");
|
expect(fs.readFileSync(file, "utf8")).toBe("kubeconfig");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("check if reorderring works for same from and to", () => {
|
it("check if reorderring works for same from and to", () => {
|
||||||
clusterStore.swapIconOrders("workstation", 1, 1)
|
clusterStore.swapIconOrders("workstation", 1, 1);
|
||||||
|
|
||||||
const clusters = clusterStore.getByWorkspaceId("workstation");
|
const clusters = clusterStore.getByWorkspaceId("workstation");
|
||||||
expect(clusters[0].id).toBe("prod")
|
expect(clusters[0].id).toBe("prod");
|
||||||
expect(clusters[0].preferences.iconOrder).toBe(0)
|
expect(clusters[0].preferences.iconOrder).toBe(0);
|
||||||
expect(clusters[1].id).toBe("dev")
|
expect(clusters[1].id).toBe("dev");
|
||||||
expect(clusters[1].preferences.iconOrder).toBe(1)
|
expect(clusters[1].preferences.iconOrder).toBe(1);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("check if reorderring works for different from and to", () => {
|
it("check if reorderring works for different from and to", () => {
|
||||||
clusterStore.swapIconOrders("workstation", 0, 1)
|
clusterStore.swapIconOrders("workstation", 0, 1);
|
||||||
|
|
||||||
const clusters = clusterStore.getByWorkspaceId("workstation");
|
const clusters = clusterStore.getByWorkspaceId("workstation");
|
||||||
expect(clusters[0].id).toBe("dev")
|
expect(clusters[0].id).toBe("dev");
|
||||||
expect(clusters[0].preferences.iconOrder).toBe(0)
|
expect(clusters[0].preferences.iconOrder).toBe(0);
|
||||||
expect(clusters[1].id).toBe("prod")
|
expect(clusters[1].id).toBe("prod");
|
||||||
expect(clusters[1].preferences.iconOrder).toBe(1)
|
expect(clusters[1].preferences.iconOrder).toBe(1);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("check if after icon reordering, changing workspaces still works", () => {
|
it("check if after icon reordering, changing workspaces still works", () => {
|
||||||
clusterStore.swapIconOrders("workstation", 1, 1)
|
clusterStore.swapIconOrders("workstation", 1, 1);
|
||||||
clusterStore.getById("prod").workspace = "default"
|
clusterStore.getById("prod").workspace = "default";
|
||||||
|
|
||||||
expect(clusterStore.getByWorkspaceId("workstation").length).toBe(1);
|
expect(clusterStore.getByWorkspaceId("workstation").length).toBe(1);
|
||||||
expect(clusterStore.getByWorkspaceId("default").length).toBe(1);
|
expect(clusterStore.getByWorkspaceId("default").length).toBe(1);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("config with existing clusters", () => {
|
describe("config with existing clusters", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -176,21 +176,21 @@ describe("config with existing clusters", () => {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
||||||
return clusterStore.load();
|
return clusterStore.load();
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("allows to retrieve a cluster", () => {
|
it("allows to retrieve a cluster", () => {
|
||||||
const storedCluster = clusterStore.getById('cluster1');
|
const storedCluster = clusterStore.getById('cluster1');
|
||||||
expect(storedCluster.id).toBe('cluster1');
|
expect(storedCluster.id).toBe('cluster1');
|
||||||
expect(storedCluster.preferences.terminalCWD).toBe('/foo');
|
expect(storedCluster.preferences.terminalCWD).toBe('/foo');
|
||||||
})
|
});
|
||||||
|
|
||||||
it("allows to delete a cluster", () => {
|
it("allows to delete a cluster", () => {
|
||||||
clusterStore.removeById('cluster2');
|
clusterStore.removeById('cluster2');
|
||||||
@ -198,18 +198,18 @@ describe("config with existing clusters", () => {
|
|||||||
expect(storedCluster).toBeTruthy();
|
expect(storedCluster).toBeTruthy();
|
||||||
const storedCluster2 = clusterStore.getById('cluster2');
|
const storedCluster2 = clusterStore.getById('cluster2');
|
||||||
expect(storedCluster2).toBeUndefined();
|
expect(storedCluster2).toBeUndefined();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("allows getting all of the clusters", async () => {
|
it("allows getting all of the clusters", async () => {
|
||||||
const storedClusters = clusterStore.clustersList;
|
const storedClusters = clusterStore.clustersList;
|
||||||
expect(storedClusters.length).toBe(3)
|
expect(storedClusters.length).toBe(3);
|
||||||
expect(storedClusters[0].id).toBe('cluster1')
|
expect(storedClusters[0].id).toBe('cluster1');
|
||||||
expect(storedClusters[0].preferences.terminalCWD).toBe('/foo')
|
expect(storedClusters[0].preferences.terminalCWD).toBe('/foo');
|
||||||
expect(storedClusters[1].id).toBe('cluster2')
|
expect(storedClusters[1].id).toBe('cluster2');
|
||||||
expect(storedClusters[1].preferences.terminalCWD).toBe('/foo2')
|
expect(storedClusters[1].preferences.terminalCWD).toBe('/foo2');
|
||||||
expect(storedClusters[2].id).toBe('cluster3')
|
expect(storedClusters[2].id).toBe('cluster3');
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("pre 2.0 config with an existing cluster", () => {
|
describe("pre 2.0 config with an existing cluster", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -229,17 +229,17 @@ describe("pre 2.0 config with an existing cluster", () => {
|
|||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
||||||
return clusterStore.load();
|
return clusterStore.load();
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("migrates to modern format with kubeconfig in a file", async () => {
|
it("migrates to modern format with kubeconfig in a file", async () => {
|
||||||
const config = clusterStore.clustersList[0].kubeConfigPath;
|
const config = clusterStore.clustersList[0].kubeConfigPath;
|
||||||
expect(fs.readFileSync(config, "utf8")).toBe("kubeconfig content");
|
expect(fs.readFileSync(config, "utf8")).toBe("kubeconfig content");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("pre 2.6.0 config with a cluster that has arrays in auth config", () => {
|
describe("pre 2.6.0 config with a cluster that has arrays in auth config", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -257,15 +257,15 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
||||||
return clusterStore.load();
|
return clusterStore.load();
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("replaces array format access token and expiry into string", async () => {
|
it("replaces array format access token and expiry into string", async () => {
|
||||||
const file = clusterStore.clustersList[0].kubeConfigPath;
|
const file = clusterStore.clustersList[0].kubeConfigPath;
|
||||||
@ -273,8 +273,8 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
|
|||||||
const kc = yaml.safeLoad(config);
|
const kc = yaml.safeLoad(config);
|
||||||
expect(kc.users[0].user['auth-provider'].config['access-token']).toBe("should be string");
|
expect(kc.users[0].user['auth-provider'].config['access-token']).toBe("should be string");
|
||||||
expect(kc.users[0].user['auth-provider'].config['expiry']).toBe("should be string");
|
expect(kc.users[0].user['auth-provider'].config['expiry']).toBe("should be string");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("pre 2.6.0 config with a cluster icon", () => {
|
describe("pre 2.6.0 config with a cluster icon", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -297,23 +297,23 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
|||||||
}),
|
}),
|
||||||
"icon_path": testDataIcon,
|
"icon_path": testDataIcon,
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
||||||
return clusterStore.load();
|
return clusterStore.load();
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("moves the icon into preferences", async () => {
|
it("moves the icon into preferences", async () => {
|
||||||
const storedClusterData = clusterStore.clustersList[0];
|
const storedClusterData = clusterStore.clustersList[0];
|
||||||
expect(storedClusterData.hasOwnProperty('icon')).toBe(false);
|
expect(storedClusterData.hasOwnProperty('icon')).toBe(false);
|
||||||
expect(storedClusterData.preferences.hasOwnProperty('icon')).toBe(true);
|
expect(storedClusterData.preferences.hasOwnProperty('icon')).toBe(true);
|
||||||
expect(storedClusterData.preferences.icon.startsWith("data:;base64,")).toBe(true);
|
expect(storedClusterData.preferences.icon.startsWith("data:;base64,")).toBe(true);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
|
describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -334,21 +334,21 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
||||||
return clusterStore.load();
|
return clusterStore.load();
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("adds cluster to default workspace", async () => {
|
it("adds cluster to default workspace", async () => {
|
||||||
const storedClusterData = clusterStore.clustersList[0];
|
const storedClusterData = clusterStore.clustersList[0];
|
||||||
expect(storedClusterData.workspace).toBe('default');
|
expect(storedClusterData.workspace).toBe('default');
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -378,19 +378,19 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
|||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
||||||
return clusterStore.load();
|
return clusterStore.load();
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("migrates to modern format with kubeconfig in a file", async () => {
|
it("migrates to modern format with kubeconfig in a file", async () => {
|
||||||
const config = clusterStore.clustersList[0].kubeConfigPath;
|
const config = clusterStore.clustersList[0].kubeConfigPath;
|
||||||
expect(fs.readFileSync(config, "utf8")).toBe("kubeconfig content");
|
expect(fs.readFileSync(config, "utf8")).toBe("kubeconfig content");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("migrates to modern format with icon not in file", async () => {
|
it("migrates to modern format with icon not in file", async () => {
|
||||||
const { icon } = clusterStore.clustersList[0].preferences;
|
const { icon } = clusterStore.clustersList[0].preferences;
|
||||||
expect(icon.startsWith("data:;base64,")).toBe(true);
|
expect(icon.startsWith("data:;base64,")).toBe(true);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import { appEventBus, AppEvent } from "../event-bus"
|
import { appEventBus, AppEvent } from "../event-bus";
|
||||||
|
|
||||||
describe("event bus tests", () => {
|
describe("event bus tests", () => {
|
||||||
describe("emit", () => {
|
describe("emit", () => {
|
||||||
it("emits an event", () => {
|
it("emits an event", () => {
|
||||||
let event: AppEvent = null
|
let event: AppEvent = null;
|
||||||
appEventBus.addListener((data) => {
|
appEventBus.addListener((data) => {
|
||||||
event = data
|
event = data;
|
||||||
})
|
});
|
||||||
|
|
||||||
appEventBus.emit({name: "foo", action: "bar"})
|
appEventBus.emit({name: "foo", action: "bar"});
|
||||||
expect(event.name).toBe("foo")
|
expect(event.name).toBe("foo");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
* @jest-environment jsdom
|
* @jest-environment jsdom
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SearchStore } from "../search-store"
|
import { SearchStore } from "../search-store";
|
||||||
|
|
||||||
let searchStore: SearchStore = null;
|
let searchStore: SearchStore = null;
|
||||||
|
|
||||||
@ -10,17 +10,17 @@ const logs = [
|
|||||||
"1:M 30 Oct 2020 16:17:41.553 # Connection with replica 172.17.0.12:6379 lost",
|
"1:M 30 Oct 2020 16:17:41.553 # Connection with replica 172.17.0.12:6379 lost",
|
||||||
"1:M 30 Oct 2020 16:17:41.623 * Replica 172.17.0.12:6379 asks for synchronization",
|
"1:M 30 Oct 2020 16:17:41.623 * Replica 172.17.0.12:6379 asks for synchronization",
|
||||||
"1:M 30 Oct 2020 16:17:41.623 * Starting Partial resynchronization request from 172.17.0.12:6379 accepted. Sending 0 bytes of backlog starting from offset 14407."
|
"1:M 30 Oct 2020 16:17:41.623 * Starting Partial resynchronization request from 172.17.0.12:6379 accepted. Sending 0 bytes of backlog starting from offset 14407."
|
||||||
]
|
];
|
||||||
|
|
||||||
describe("search store tests", () => {
|
describe("search store tests", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
searchStore = new SearchStore();
|
searchStore = new SearchStore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("does nothing with empty search query", () => {
|
it("does nothing with empty search query", () => {
|
||||||
searchStore.onSearch([], "");
|
searchStore.onSearch([], "");
|
||||||
expect(searchStore.occurrences).toEqual([]);
|
expect(searchStore.occurrences).toEqual([]);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("doesn't break if no text provided", () => {
|
it("doesn't break if no text provided", () => {
|
||||||
searchStore.onSearch(null, "replica");
|
searchStore.onSearch(null, "replica");
|
||||||
@ -28,53 +28,53 @@ describe("search store tests", () => {
|
|||||||
|
|
||||||
searchStore.onSearch([], "replica");
|
searchStore.onSearch([], "replica");
|
||||||
expect(searchStore.occurrences).toEqual([]);
|
expect(searchStore.occurrences).toEqual([]);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("find 3 occurences across 3 lines", () => {
|
it("find 3 occurences across 3 lines", () => {
|
||||||
searchStore.onSearch(logs, "172");
|
searchStore.onSearch(logs, "172");
|
||||||
expect(searchStore.occurrences).toEqual([0, 1, 2]);
|
expect(searchStore.occurrences).toEqual([0, 1, 2]);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("find occurences within 1 line (case-insensitive)", () => {
|
it("find occurences within 1 line (case-insensitive)", () => {
|
||||||
searchStore.onSearch(logs, "Starting");
|
searchStore.onSearch(logs, "Starting");
|
||||||
expect(searchStore.occurrences).toEqual([2, 2]);
|
expect(searchStore.occurrences).toEqual([2, 2]);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("sets overlay index equal to first occurence", () => {
|
it("sets overlay index equal to first occurence", () => {
|
||||||
searchStore.onSearch(logs, "Replica");
|
searchStore.onSearch(logs, "Replica");
|
||||||
expect(searchStore.activeOverlayIndex).toBe(0);
|
expect(searchStore.activeOverlayIndex).toBe(0);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("set overlay index to next occurence", () => {
|
it("set overlay index to next occurence", () => {
|
||||||
searchStore.onSearch(logs, "172");
|
searchStore.onSearch(logs, "172");
|
||||||
searchStore.setNextOverlayActive();
|
searchStore.setNextOverlayActive();
|
||||||
expect(searchStore.activeOverlayIndex).toBe(1);
|
expect(searchStore.activeOverlayIndex).toBe(1);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("sets overlay to last occurence", () => {
|
it("sets overlay to last occurence", () => {
|
||||||
searchStore.onSearch(logs, "172");
|
searchStore.onSearch(logs, "172");
|
||||||
searchStore.setPrevOverlayActive();
|
searchStore.setPrevOverlayActive();
|
||||||
expect(searchStore.activeOverlayIndex).toBe(2);
|
expect(searchStore.activeOverlayIndex).toBe(2);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("gets line index where overlay is located", () => {
|
it("gets line index where overlay is located", () => {
|
||||||
searchStore.onSearch(logs, "synchronization");
|
searchStore.onSearch(logs, "synchronization");
|
||||||
expect(searchStore.activeOverlayLine).toBe(1);
|
expect(searchStore.activeOverlayLine).toBe(1);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("escapes string for using in regex", () => {
|
it("escapes string for using in regex", () => {
|
||||||
const regex = searchStore.escapeRegex("some.interesting-query\\#?()[]");
|
const regex = searchStore.escapeRegex("some.interesting-query\\#?()[]");
|
||||||
expect(regex).toBe("some\\.interesting\\-query\\\\\\#\\?\\(\\)\\[\\]");
|
expect(regex).toBe("some\\.interesting\\-query\\\\\\#\\?\\(\\)\\[\\]");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("gets active find number", () => {
|
it("gets active find number", () => {
|
||||||
searchStore.onSearch(logs, "172");
|
searchStore.onSearch(logs, "172");
|
||||||
searchStore.setNextOverlayActive();
|
searchStore.setNextOverlayActive();
|
||||||
expect(searchStore.activeFind).toBe(2);
|
expect(searchStore.activeFind).toBe(2);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("gets total finds number", () => {
|
it("gets total finds number", () => {
|
||||||
searchStore.onSearch(logs, "Starting");
|
searchStore.onSearch(logs, "Starting");
|
||||||
expect(searchStore.totalFinds).toBe(2);
|
expect(searchStore.totalFinds).toBe(2);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import mockFs from "mock-fs"
|
import mockFs from "mock-fs";
|
||||||
|
|
||||||
jest.mock("electron", () => {
|
jest.mock("electron", () => {
|
||||||
return {
|
return {
|
||||||
@ -7,55 +7,55 @@ jest.mock("electron", () => {
|
|||||||
getPath: () => 'tmp',
|
getPath: () => 'tmp',
|
||||||
getLocale: () => 'en'
|
getLocale: () => 'en'
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
import { UserStore } from "../user-store"
|
import { UserStore } from "../user-store";
|
||||||
import { SemVer } from "semver"
|
import { SemVer } from "semver";
|
||||||
import electron from "electron"
|
import electron from "electron";
|
||||||
|
|
||||||
describe("user store tests", () => {
|
describe("user store tests", () => {
|
||||||
describe("for an empty config", () => {
|
describe("for an empty config", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
UserStore.resetInstance()
|
UserStore.resetInstance();
|
||||||
mockFs({ tmp: { 'config.json': "{}" } })
|
mockFs({ tmp: { 'config.json': "{}" } });
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore()
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("allows setting and retrieving lastSeenAppVersion", () => {
|
it("allows setting and retrieving lastSeenAppVersion", () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance<UserStore>();
|
||||||
|
|
||||||
us.lastSeenAppVersion = "1.2.3";
|
us.lastSeenAppVersion = "1.2.3";
|
||||||
expect(us.lastSeenAppVersion).toBe("1.2.3");
|
expect(us.lastSeenAppVersion).toBe("1.2.3");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("allows adding and listing seen contexts", () => {
|
it("allows adding and listing seen contexts", () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance<UserStore>();
|
||||||
|
|
||||||
us.seenContexts.add('foo')
|
us.seenContexts.add('foo');
|
||||||
expect(us.seenContexts.size).toBe(1)
|
expect(us.seenContexts.size).toBe(1);
|
||||||
|
|
||||||
us.seenContexts.add('foo')
|
us.seenContexts.add('foo');
|
||||||
us.seenContexts.add('bar')
|
us.seenContexts.add('bar');
|
||||||
expect(us.seenContexts.size).toBe(2) // check 'foo' isn't added twice
|
expect(us.seenContexts.size).toBe(2); // check 'foo' isn't added twice
|
||||||
expect(us.seenContexts.has('foo')).toBe(true)
|
expect(us.seenContexts.has('foo')).toBe(true);
|
||||||
expect(us.seenContexts.has('bar')).toBe(true)
|
expect(us.seenContexts.has('bar')).toBe(true);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("allows setting and getting preferences", () => {
|
it("allows setting and getting preferences", () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance<UserStore>();
|
||||||
|
|
||||||
us.preferences.httpsProxy = 'abcd://defg';
|
us.preferences.httpsProxy = 'abcd://defg';
|
||||||
|
|
||||||
expect(us.preferences.httpsProxy).toBe('abcd://defg')
|
expect(us.preferences.httpsProxy).toBe('abcd://defg');
|
||||||
expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme)
|
expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme);
|
||||||
|
|
||||||
us.preferences.colorTheme = "light";
|
us.preferences.colorTheme = "light";
|
||||||
expect(us.preferences.colorTheme).toBe('light')
|
expect(us.preferences.colorTheme).toBe('light');
|
||||||
})
|
});
|
||||||
|
|
||||||
it("correctly resets theme to default value", async () => {
|
it("correctly resets theme to default value", async () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance<UserStore>();
|
||||||
@ -64,7 +64,7 @@ describe("user store tests", () => {
|
|||||||
us.preferences.colorTheme = "some other theme";
|
us.preferences.colorTheme = "some other theme";
|
||||||
await us.resetTheme();
|
await us.resetTheme();
|
||||||
expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme);
|
expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("correctly calculates if the last seen version is an old release", () => {
|
it("correctly calculates if the last seen version is an old release", () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance<UserStore>();
|
||||||
@ -73,12 +73,12 @@ describe("user store tests", () => {
|
|||||||
|
|
||||||
us.lastSeenAppVersion = (new SemVer(electron.app.getVersion())).inc("major").format();
|
us.lastSeenAppVersion = (new SemVer(electron.app.getVersion())).inc("major").format();
|
||||||
expect(us.isNewVersion).toBe(false);
|
expect(us.isNewVersion).toBe(false);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("migrations", () => {
|
describe("migrations", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
UserStore.resetInstance()
|
UserStore.resetInstance();
|
||||||
mockFs({
|
mockFs({
|
||||||
'tmp': {
|
'tmp': {
|
||||||
'config.json': JSON.stringify({
|
'config.json': JSON.stringify({
|
||||||
@ -87,17 +87,17 @@ describe("user store tests", () => {
|
|||||||
lastSeenAppVersion: '1.2.3'
|
lastSeenAppVersion: '1.2.3'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore()
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("sets last seen app version to 0.0.0", () => {
|
it("sets last seen app version to 0.0.0", () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance<UserStore>();
|
||||||
|
|
||||||
expect(us.lastSeenAppVersion).toBe('0.0.0')
|
expect(us.lastSeenAppVersion).toBe('0.0.0');
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import mockFs from "mock-fs"
|
import mockFs from "mock-fs";
|
||||||
|
|
||||||
jest.mock("electron", () => {
|
jest.mock("electron", () => {
|
||||||
return {
|
return {
|
||||||
@ -7,36 +7,36 @@ jest.mock("electron", () => {
|
|||||||
getPath: () => 'tmp',
|
getPath: () => 'tmp',
|
||||||
getLocale: () => 'en'
|
getLocale: () => 'en'
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
import { Workspace, WorkspaceStore } from "../workspace-store"
|
import { Workspace, WorkspaceStore } from "../workspace-store";
|
||||||
|
|
||||||
describe("workspace store tests", () => {
|
describe("workspace store tests", () => {
|
||||||
describe("for an empty config", () => {
|
describe("for an empty config", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
WorkspaceStore.resetInstance()
|
WorkspaceStore.resetInstance();
|
||||||
mockFs({ tmp: { 'lens-workspace-store.json': "{}" } })
|
mockFs({ tmp: { 'lens-workspace-store.json': "{}" } });
|
||||||
|
|
||||||
await WorkspaceStore.getInstance<WorkspaceStore>().load();
|
await WorkspaceStore.getInstance<WorkspaceStore>().load();
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore()
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("default workspace should always exist", () => {
|
it("default workspace should always exist", () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
|
|
||||||
expect(ws.workspaces.size).toBe(1);
|
expect(ws.workspaces.size).toBe(1);
|
||||||
expect(ws.getById(WorkspaceStore.defaultId)).not.toBe(null);
|
expect(ws.getById(WorkspaceStore.defaultId)).not.toBe(null);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("cannot remove the default workspace", () => {
|
it("cannot remove the default workspace", () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
|
|
||||||
expect(() => ws.removeWorkspaceById(WorkspaceStore.defaultId)).toThrowError("Cannot remove");
|
expect(() => ws.removeWorkspaceById(WorkspaceStore.defaultId)).toThrowError("Cannot remove");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("can update workspace description", () => {
|
it("can update workspace description", () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
@ -50,7 +50,7 @@ describe("workspace store tests", () => {
|
|||||||
ws.updateWorkspace(workspace);
|
ws.updateWorkspace(workspace);
|
||||||
|
|
||||||
expect(ws.getById("foobar").description).toBe("Foobar description");
|
expect(ws.getById("foobar").description).toBe("Foobar description");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("can add workspaces", () => {
|
it("can add workspaces", () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
@ -61,13 +61,13 @@ describe("workspace store tests", () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
expect(ws.getById("123").name).toBe("foobar");
|
expect(ws.getById("123").name).toBe("foobar");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("cannot set a non-existent workspace to be active", () => {
|
it("cannot set a non-existent workspace to be active", () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
|
|
||||||
expect(() => ws.setActive("abc")).toThrow("doesn't exist");
|
expect(() => ws.setActive("abc")).toThrow("doesn't exist");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("can set a existent workspace to be active", () => {
|
it("can set a existent workspace to be active", () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
@ -78,7 +78,7 @@ describe("workspace store tests", () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
expect(() => ws.setActive("abc")).not.toThrowError();
|
expect(() => ws.setActive("abc")).not.toThrowError();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("can remove a workspace", () => {
|
it("can remove a workspace", () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
@ -94,7 +94,7 @@ describe("workspace store tests", () => {
|
|||||||
ws.removeWorkspaceById("123");
|
ws.removeWorkspaceById("123");
|
||||||
|
|
||||||
expect(ws.workspaces.size).toBe(2);
|
expect(ws.workspaces.size).toBe(2);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("cannot create workspace with existent name", () => {
|
it("cannot create workspace with existent name", () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
@ -105,7 +105,7 @@ describe("workspace store tests", () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||||
})
|
});
|
||||||
|
|
||||||
it("cannot create workspace with empty name", () => {
|
it("cannot create workspace with empty name", () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
@ -116,7 +116,7 @@ describe("workspace store tests", () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||||
})
|
});
|
||||||
|
|
||||||
it("cannot create workspace with ' ' name", () => {
|
it("cannot create workspace with ' ' name", () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
@ -127,7 +127,7 @@ describe("workspace store tests", () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||||
})
|
});
|
||||||
|
|
||||||
it("trim workspace name", () => {
|
it("trim workspace name", () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
@ -138,12 +138,12 @@ describe("workspace store tests", () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("for a non-empty config", () => {
|
describe("for a non-empty config", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
WorkspaceStore.resetInstance()
|
WorkspaceStore.resetInstance();
|
||||||
mockFs({
|
mockFs({
|
||||||
tmp: {
|
tmp: {
|
||||||
'lens-workspace-store.json': JSON.stringify({
|
'lens-workspace-store.json': JSON.stringify({
|
||||||
@ -157,19 +157,19 @@ describe("workspace store tests", () => {
|
|||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
await WorkspaceStore.getInstance<WorkspaceStore>().load();
|
await WorkspaceStore.getInstance<WorkspaceStore>().load();
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore()
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("doesn't revert to default workspace", async () => {
|
it("doesn't revert to default workspace", async () => {
|
||||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
|
|
||||||
expect(ws.currentWorkspaceId).toBe("abc");
|
expect(ws.currentWorkspaceId).toBe("abc");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import path from "path"
|
import path from "path";
|
||||||
import Config from "conf"
|
import Config from "conf";
|
||||||
import { Options as ConfOptions } from "conf/dist/source/types"
|
import { Options as ConfOptions } from "conf/dist/source/types";
|
||||||
import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron"
|
import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron";
|
||||||
import { action, IReactionOptions, observable, reaction, runInAction, toJS, when } from "mobx";
|
import { action, IReactionOptions, observable, reaction, runInAction, toJS, when } from "mobx";
|
||||||
import Singleton from "./utils/singleton";
|
import Singleton from "./utils/singleton";
|
||||||
import { getAppVersion } from "./utils/app-version";
|
import { getAppVersion } from "./utils/app-version";
|
||||||
@ -32,7 +32,7 @@ export abstract class BaseStore<T = any> extends Singleton {
|
|||||||
autoLoad: false,
|
autoLoad: false,
|
||||||
syncEnabled: true,
|
syncEnabled: true,
|
||||||
...params,
|
...params,
|
||||||
}
|
};
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,11 +41,11 @@ export abstract class BaseStore<T = any> extends Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected get syncRendererChannel() {
|
protected get syncRendererChannel() {
|
||||||
return `store-sync-renderer:${this.path}`
|
return `store-sync-renderer:${this.path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get syncMainChannel() {
|
protected get syncMainChannel() {
|
||||||
return `store-sync-main:${this.path}`
|
return `store-sync-main:${this.path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get path() {
|
get path() {
|
||||||
@ -76,7 +76,7 @@ export abstract class BaseStore<T = any> extends Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected cwd() {
|
protected cwd() {
|
||||||
return (app || remote.app).getPath("userData")
|
return (app || remote.app).getPath("userData");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async saveToFile(model: T) {
|
protected async saveToFile(model: T) {
|
||||||
@ -96,7 +96,7 @@ export abstract class BaseStore<T = any> extends Singleton {
|
|||||||
logger.silly(`[STORE]: SYNC ${this.name} from renderer`, { model });
|
logger.silly(`[STORE]: SYNC ${this.name} from renderer`, { model });
|
||||||
this.onSync(model);
|
this.onSync(model);
|
||||||
};
|
};
|
||||||
subscribeToBroadcast(this.syncMainChannel, callback)
|
subscribeToBroadcast(this.syncMainChannel, callback);
|
||||||
this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncMainChannel, callback));
|
this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncMainChannel, callback));
|
||||||
}
|
}
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
@ -104,20 +104,20 @@ export abstract class BaseStore<T = any> extends Singleton {
|
|||||||
logger.silly(`[STORE]: SYNC ${this.name} from main`, { model });
|
logger.silly(`[STORE]: SYNC ${this.name} from main`, { model });
|
||||||
this.onSyncFromMain(model);
|
this.onSyncFromMain(model);
|
||||||
};
|
};
|
||||||
subscribeToBroadcast(this.syncRendererChannel, callback)
|
subscribeToBroadcast(this.syncRendererChannel, callback);
|
||||||
this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncRendererChannel, callback));
|
this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncRendererChannel, callback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onSyncFromMain(model: T) {
|
protected onSyncFromMain(model: T) {
|
||||||
this.applyWithoutSync(() => {
|
this.applyWithoutSync(() => {
|
||||||
this.onSync(model)
|
this.onSync(model);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterIpcListener() {
|
unregisterIpcListener() {
|
||||||
ipcRenderer.removeAllListeners(this.syncMainChannel)
|
ipcRenderer.removeAllListeners(this.syncMainChannel);
|
||||||
ipcRenderer.removeAllListeners(this.syncRendererChannel)
|
ipcRenderer.removeAllListeners(this.syncRendererChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableSync() {
|
disableSync() {
|
||||||
@ -143,9 +143,9 @@ export abstract class BaseStore<T = any> extends Singleton {
|
|||||||
protected async onModelChange(model: T) {
|
protected async onModelChange(model: T) {
|
||||||
if (ipcMain) {
|
if (ipcMain) {
|
||||||
this.saveToFile(model); // save config file
|
this.saveToFile(model); // save config file
|
||||||
broadcastMessage(this.syncRendererChannel, model)
|
broadcastMessage(this.syncRendererChannel, model);
|
||||||
} else {
|
} else {
|
||||||
broadcastMessage(this.syncMainChannel, model)
|
broadcastMessage(this.syncMainChannel, model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
import { observable } from "mobx"
|
import { observable } from "mobx";
|
||||||
|
|
||||||
export const clusterFrameMap = observable.map<string, number>();
|
export const clusterFrameMap = observable.map<string, number>();
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import { handleRequest } from "./ipc";
|
import { handleRequest } from "./ipc";
|
||||||
import { ClusterId, clusterStore } from "./cluster-store";
|
import { ClusterId, clusterStore } from "./cluster-store";
|
||||||
import { appEventBus } from "./event-bus"
|
import { appEventBus } from "./event-bus";
|
||||||
import { ResourceApplier } from "../main/resource-applier";
|
import { ResourceApplier } from "../main/resource-applier";
|
||||||
import { ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
import { clusterFrameMap } from "./cluster-frames"
|
import { clusterFrameMap } from "./cluster-frames";
|
||||||
|
|
||||||
export const clusterActivateHandler = "cluster:activate"
|
export const clusterActivateHandler = "cluster:activate";
|
||||||
export const clusterSetFrameIdHandler = "cluster:set-frame-id"
|
export const clusterSetFrameIdHandler = "cluster:set-frame-id";
|
||||||
export const clusterRefreshHandler = "cluster:refresh"
|
export const clusterRefreshHandler = "cluster:refresh";
|
||||||
export const clusterDisconnectHandler = "cluster:disconnect"
|
export const clusterDisconnectHandler = "cluster:disconnect";
|
||||||
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all"
|
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
|
||||||
|
|
||||||
|
|
||||||
if (ipcMain) {
|
if (ipcMain) {
|
||||||
@ -18,38 +18,38 @@ if (ipcMain) {
|
|||||||
if (cluster) {
|
if (cluster) {
|
||||||
return cluster.activate(force);
|
return cluster.activate(force);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
handleRequest(clusterSetFrameIdHandler, (event, clusterId: ClusterId, frameId: number) => {
|
handleRequest(clusterSetFrameIdHandler, (event, clusterId: ClusterId, frameId: number) => {
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = clusterStore.getById(clusterId);
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
clusterFrameMap.set(cluster.id, frameId)
|
clusterFrameMap.set(cluster.id, frameId);
|
||||||
return cluster.pushState();
|
return cluster.pushState();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
handleRequest(clusterRefreshHandler, (event, clusterId: ClusterId) => {
|
handleRequest(clusterRefreshHandler, (event, clusterId: ClusterId) => {
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = clusterStore.getById(clusterId);
|
||||||
if (cluster) return cluster.refresh({ refreshMetadata: true })
|
if (cluster) return cluster.refresh({ refreshMetadata: true });
|
||||||
})
|
});
|
||||||
|
|
||||||
handleRequest(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
|
handleRequest(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
|
||||||
appEventBus.emit({name: "cluster", action: "stop"});
|
appEventBus.emit({name: "cluster", action: "stop"});
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = clusterStore.getById(clusterId);
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
cluster.disconnect();
|
cluster.disconnect();
|
||||||
clusterFrameMap.delete(cluster.id)
|
clusterFrameMap.delete(cluster.id);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
handleRequest(clusterKubectlApplyAllHandler, (event, clusterId: ClusterId, resources: string[]) => {
|
handleRequest(clusterKubectlApplyAllHandler, (event, clusterId: ClusterId, resources: string[]) => {
|
||||||
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"})
|
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"});
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = clusterStore.getById(clusterId);
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
const applier = new ResourceApplier(cluster)
|
const applier = new ResourceApplier(cluster);
|
||||||
applier.kubectlApplyAll(resources)
|
applier.kubectlApplyAll(resources);
|
||||||
} else {
|
} else {
|
||||||
throw `${clusterId} is not a valid cluster id`;
|
throw `${clusterId} is not a valid cluster id`;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import { unlink } from "fs-extra";
|
|||||||
import { action, computed, observable, reaction, toJS } from "mobx";
|
import { action, computed, observable, reaction, toJS } from "mobx";
|
||||||
import { BaseStore } from "./base-store";
|
import { BaseStore } from "./base-store";
|
||||||
import { Cluster, ClusterState } from "../main/cluster";
|
import { Cluster, ClusterState } from "../main/cluster";
|
||||||
import migrations from "../migrations/cluster-store"
|
import migrations from "../migrations/cluster-store";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { appEventBus } from "./event-bus"
|
import { appEventBus } from "./event-bus";
|
||||||
import { dumpConfigYaml } from "./kube-helpers";
|
import { dumpConfigYaml } from "./kube-helpers";
|
||||||
import { saveToAppFiles } from "./utils/saveToAppFiles";
|
import { saveToAppFiles } from "./utils/saveToAppFiles";
|
||||||
import { KubeConfig } from "@kubernetes/client-node";
|
import { KubeConfig } from "@kubernetes/client-node";
|
||||||
@ -86,38 +86,38 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
migrations: migrations,
|
migrations: migrations,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pushStateToViewsAutomatically()
|
this.pushStateToViewsAutomatically();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected pushStateToViewsAutomatically() {
|
protected pushStateToViewsAutomatically() {
|
||||||
if (!ipcRenderer) {
|
if (!ipcRenderer) {
|
||||||
reaction(() => this.connectedClustersList, () => {
|
reaction(() => this.connectedClustersList, () => {
|
||||||
this.pushState()
|
this.pushState();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerIpcListener() {
|
registerIpcListener() {
|
||||||
logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`)
|
logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`);
|
||||||
subscribeToBroadcast("cluster:state", (event, clusterId: string, state: ClusterState) => {
|
subscribeToBroadcast("cluster:state", (event, clusterId: string, state: ClusterState) => {
|
||||||
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, clusterId, state);
|
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, clusterId, state);
|
||||||
this.getById(clusterId)?.setState(state)
|
this.getById(clusterId)?.setState(state);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterIpcListener() {
|
unregisterIpcListener() {
|
||||||
super.unregisterIpcListener()
|
super.unregisterIpcListener();
|
||||||
unsubscribeAllFromBroadcast("cluster:state")
|
unsubscribeAllFromBroadcast("cluster:state");
|
||||||
}
|
}
|
||||||
|
|
||||||
pushState() {
|
pushState() {
|
||||||
this.clusters.forEach((c) => {
|
this.clusters.forEach((c) => {
|
||||||
c.pushState()
|
c.pushState();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeClusterId() {
|
get activeClusterId() {
|
||||||
return this.activeCluster
|
return this.activeCluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get clustersList(): Cluster[] {
|
@computed get clustersList(): Cluster[] {
|
||||||
@ -125,7 +125,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get enabledClustersList(): Cluster[] {
|
@computed get enabledClustersList(): Cluster[] {
|
||||||
return this.clustersList.filter((c) => c.enabled)
|
return this.clustersList.filter((c) => c.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get active(): Cluster | null {
|
@computed get active(): Cluster | null {
|
||||||
@ -133,7 +133,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get connectedClustersList(): Cluster[] {
|
@computed get connectedClustersList(): Cluster[] {
|
||||||
return this.clustersList.filter((c) => !c.disconnected)
|
return this.clustersList.filter((c) => !c.disconnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
isActive(id: ClusterId) {
|
isActive(id: ClusterId) {
|
||||||
@ -149,7 +149,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
swapIconOrders(workspace: WorkspaceId, from: number, to: number) {
|
swapIconOrders(workspace: WorkspaceId, from: number, to: number) {
|
||||||
const clusters = this.getByWorkspaceId(workspace);
|
const clusters = this.getByWorkspaceId(workspace);
|
||||||
if (from < 0 || to < 0 || from >= clusters.length || to >= clusters.length || isNaN(from) || isNaN(to)) {
|
if (from < 0 || to < 0 || from >= clusters.length || to >= clusters.length || isNaN(from) || isNaN(to)) {
|
||||||
throw new Error(`invalid from<->to arguments`)
|
throw new Error(`invalid from<->to arguments`);
|
||||||
}
|
}
|
||||||
|
|
||||||
move.mutate(clusters, from, to);
|
move.mutate(clusters, from, to);
|
||||||
@ -170,37 +170,37 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
getByWorkspaceId(workspaceId: string): Cluster[] {
|
getByWorkspaceId(workspaceId: string): Cluster[] {
|
||||||
const clusters = Array.from(this.clusters.values())
|
const clusters = Array.from(this.clusters.values())
|
||||||
.filter(cluster => cluster.workspace === workspaceId);
|
.filter(cluster => cluster.workspace === workspaceId);
|
||||||
return _.sortBy(clusters, cluster => cluster.preferences.iconOrder)
|
return _.sortBy(clusters, cluster => cluster.preferences.iconOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
addClusters(...models: ClusterModel[]): Cluster[] {
|
addClusters(...models: ClusterModel[]): Cluster[] {
|
||||||
const clusters: Cluster[] = []
|
const clusters: Cluster[] = [];
|
||||||
models.forEach(model => {
|
models.forEach(model => {
|
||||||
clusters.push(this.addCluster(model))
|
clusters.push(this.addCluster(model));
|
||||||
})
|
});
|
||||||
|
|
||||||
return clusters
|
return clusters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
addCluster(model: ClusterModel | Cluster): Cluster {
|
addCluster(model: ClusterModel | Cluster): Cluster {
|
||||||
appEventBus.emit({ name: "cluster", action: "add" })
|
appEventBus.emit({ name: "cluster", action: "add" });
|
||||||
let cluster = model as Cluster;
|
let cluster = model as Cluster;
|
||||||
if (!(model instanceof Cluster)) {
|
if (!(model instanceof Cluster)) {
|
||||||
cluster = new Cluster(model)
|
cluster = new Cluster(model);
|
||||||
}
|
}
|
||||||
this.clusters.set(model.id, cluster);
|
this.clusters.set(model.id, cluster);
|
||||||
return cluster
|
return cluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeCluster(model: ClusterModel) {
|
async removeCluster(model: ClusterModel) {
|
||||||
await this.removeById(model.id)
|
await this.removeById(model.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async removeById(clusterId: ClusterId) {
|
async removeById(clusterId: ClusterId) {
|
||||||
appEventBus.emit({ name: "cluster", action: "remove" })
|
appEventBus.emit({ name: "cluster", action: "remove" });
|
||||||
const cluster = this.getById(clusterId);
|
const cluster = this.getById(clusterId);
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
this.clusters.delete(clusterId);
|
this.clusters.delete(clusterId);
|
||||||
@ -217,8 +217,8 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
@action
|
@action
|
||||||
removeByWorkspaceId(workspaceId: string) {
|
removeByWorkspaceId(workspaceId: string) {
|
||||||
this.getByWorkspaceId(workspaceId).forEach(cluster => {
|
this.getByWorkspaceId(workspaceId).forEach(cluster => {
|
||||||
this.removeById(cluster.id)
|
this.removeById(cluster.id);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -235,7 +235,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
} else {
|
} else {
|
||||||
cluster = new Cluster(clusterModel);
|
cluster = new Cluster(clusterModel);
|
||||||
if (!cluster.isManaged) {
|
if (!cluster.isManaged) {
|
||||||
cluster.enabled = true
|
cluster.enabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newClusters.set(clusterModel.id, cluster);
|
newClusters.set(clusterModel.id, cluster);
|
||||||
@ -259,7 +259,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
clusters: this.clustersList.map(cluster => cluster.toJSON()),
|
clusters: this.clustersList.map(cluster => cluster.toJSON()),
|
||||||
}, {
|
}, {
|
||||||
recurseEverything: true
|
recurseEverything: true
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { EventEmitter } from "./event-emitter"
|
import { EventEmitter } from "./event-emitter";
|
||||||
|
|
||||||
export type AppEvent = {
|
export type AppEvent = {
|
||||||
name: string;
|
name: string;
|
||||||
action: string;
|
action: string;
|
||||||
params?: object;
|
params?: object;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const appEventBus = new EventEmitter<[AppEvent]>()
|
export const appEventBus = new EventEmitter<[AppEvent]>();
|
||||||
|
|||||||
@ -35,6 +35,6 @@ export class EventEmitter<D extends [...any[]]> {
|
|||||||
const result = callback(...data);
|
const result = callback(...data);
|
||||||
if (result === false) return; // break cycle
|
if (result === false) return; // break cycle
|
||||||
return true;
|
return true;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,24 +7,24 @@ import logger from "../main/logger";
|
|||||||
import { clusterFrameMap } from "./cluster-frames";
|
import { clusterFrameMap } from "./cluster-frames";
|
||||||
|
|
||||||
export function handleRequest(channel: string, listener: (...args: any[]) => any) {
|
export function handleRequest(channel: string, listener: (...args: any[]) => any) {
|
||||||
ipcMain.handle(channel, listener)
|
ipcMain.handle(channel, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requestMain(channel: string, ...args: any[]) {
|
export async function requestMain(channel: string, ...args: any[]) {
|
||||||
return ipcRenderer.invoke(channel, ...args)
|
return ipcRenderer.invoke(channel, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSubFrames(): Promise<number[]> {
|
async function getSubFrames(): Promise<number[]> {
|
||||||
const subFrames: number[] = [];
|
const subFrames: number[] = [];
|
||||||
clusterFrameMap.forEach((frameId, _) => {
|
clusterFrameMap.forEach((frameId, _) => {
|
||||||
subFrames.push(frameId)
|
subFrames.push(frameId);
|
||||||
});
|
});
|
||||||
return subFrames;
|
return subFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function broadcastMessage(channel: string, ...args: any[]) {
|
export function broadcastMessage(channel: string, ...args: any[]) {
|
||||||
const views = (webContents || remote?.webContents)?.getAllWebContents();
|
const views = (webContents || remote?.webContents)?.getAllWebContents();
|
||||||
if (!views) return
|
if (!views) return;
|
||||||
|
|
||||||
views.forEach(webContent => {
|
views.forEach(webContent => {
|
||||||
const type = webContent.getType();
|
const type = webContent.getType();
|
||||||
@ -32,39 +32,39 @@ export function broadcastMessage(channel: string, ...args: any[]) {
|
|||||||
webContent.send(channel, ...args);
|
webContent.send(channel, ...args);
|
||||||
getSubFrames().then((frames) => {
|
getSubFrames().then((frames) => {
|
||||||
frames.map((frameId) => {
|
frames.map((frameId) => {
|
||||||
webContent.sendToFrame(frameId, channel, ...args)
|
webContent.sendToFrame(frameId, channel, ...args);
|
||||||
})
|
});
|
||||||
}).catch((e) => e)
|
}).catch((e) => e);
|
||||||
})
|
});
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
ipcRenderer.send(channel, ...args)
|
ipcRenderer.send(channel, ...args);
|
||||||
} else {
|
} else {
|
||||||
ipcMain.emit(channel, ...args)
|
ipcMain.emit(channel, ...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function subscribeToBroadcast(channel: string, listener: (...args: any[]) => any) {
|
export function subscribeToBroadcast(channel: string, listener: (...args: any[]) => any) {
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
ipcRenderer.on(channel, listener)
|
ipcRenderer.on(channel, listener);
|
||||||
} else {
|
} else {
|
||||||
ipcMain.on(channel, listener)
|
ipcMain.on(channel, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
return listener
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unsubscribeFromBroadcast(channel: string, listener: (...args: any[]) => any) {
|
export function unsubscribeFromBroadcast(channel: string, listener: (...args: any[]) => any) {
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
ipcRenderer.off(channel, listener)
|
ipcRenderer.off(channel, listener);
|
||||||
} else {
|
} else {
|
||||||
ipcMain.off(channel, listener)
|
ipcMain.off(channel, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unsubscribeAllFromBroadcast(channel: string) {
|
export function unsubscribeAllFromBroadcast(channel: string) {
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
ipcRenderer.removeAllListeners(channel)
|
ipcRenderer.removeAllListeners(channel);
|
||||||
} else {
|
} else {
|
||||||
ipcMain.removeAllListeners(channel)
|
ipcMain.removeAllListeners(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { KubeConfig, V1Node, V1Pod } from "@kubernetes/client-node"
|
import { KubeConfig, V1Node, V1Pod } from "@kubernetes/client-node";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import path from "path"
|
import path from "path";
|
||||||
import os from "os"
|
import os from "os";
|
||||||
import yaml from "js-yaml"
|
import yaml from "js-yaml";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import commandExists from "command-exists";
|
import commandExists from "command-exists";
|
||||||
import { ExecValidationNotFoundError } from "./custom-errors";
|
import { ExecValidationNotFoundError } from "./custom-errors";
|
||||||
@ -25,7 +25,7 @@ export function loadConfig(pathOrContent?: string): KubeConfig {
|
|||||||
kc.loadFromString(pathOrContent);
|
kc.loadFromString(pathOrContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return kc
|
return kc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,33 +39,33 @@ export function validateConfig(config: KubeConfig | string): KubeConfig {
|
|||||||
if (typeof config == "string") {
|
if (typeof config == "string") {
|
||||||
config = loadConfig(config);
|
config = loadConfig(config);
|
||||||
}
|
}
|
||||||
logger.debug(`validating kube config: ${JSON.stringify(config)}`)
|
logger.debug(`validating kube config: ${JSON.stringify(config)}`);
|
||||||
if (!config.users || config.users.length == 0) {
|
if (!config.users || config.users.length == 0) {
|
||||||
throw new Error("No users provided in config")
|
throw new Error("No users provided in config");
|
||||||
}
|
}
|
||||||
if (!config.clusters || config.clusters.length == 0) {
|
if (!config.clusters || config.clusters.length == 0) {
|
||||||
throw new Error("No clusters provided in config")
|
throw new Error("No clusters provided in config");
|
||||||
}
|
}
|
||||||
if (!config.contexts || config.contexts.length == 0) {
|
if (!config.contexts || config.contexts.length == 0) {
|
||||||
throw new Error("No contexts provided in config")
|
throw new Error("No contexts provided in config");
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Breaks kube config into several configs. Each context as it own KubeConfig object
|
* Breaks kube config into several configs. Each context as it own KubeConfig object
|
||||||
*/
|
*/
|
||||||
export function splitConfig(kubeConfig: KubeConfig): KubeConfig[] {
|
export function splitConfig(kubeConfig: KubeConfig): KubeConfig[] {
|
||||||
const configs: KubeConfig[] = []
|
const configs: KubeConfig[] = [];
|
||||||
if (!kubeConfig.contexts) {
|
if (!kubeConfig.contexts) {
|
||||||
return configs;
|
return configs;
|
||||||
}
|
}
|
||||||
kubeConfig.contexts.forEach(ctx => {
|
kubeConfig.contexts.forEach(ctx => {
|
||||||
const kc = new KubeConfig();
|
const kc = new KubeConfig();
|
||||||
kc.clusters = [kubeConfig.getCluster(ctx.cluster)].filter(n => n);
|
kc.clusters = [kubeConfig.getCluster(ctx.cluster)].filter(n => n);
|
||||||
kc.users = [kubeConfig.getUser(ctx.user)].filter(n => n)
|
kc.users = [kubeConfig.getUser(ctx.user)].filter(n => n);
|
||||||
kc.contexts = [kubeConfig.getContextObject(ctx.name)].filter(n => n)
|
kc.contexts = [kubeConfig.getContextObject(ctx.name)].filter(n => n);
|
||||||
kc.setCurrentContext(ctx.name);
|
kc.setCurrentContext(ctx.name);
|
||||||
|
|
||||||
configs.push(kc);
|
configs.push(kc);
|
||||||
@ -88,7 +88,7 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
|
|||||||
server: cluster.server,
|
server: cluster.server,
|
||||||
'insecure-skip-tls-verify': cluster.skipTLSVerify
|
'insecure-skip-tls-verify': cluster.skipTLSVerify
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}),
|
}),
|
||||||
contexts: kubeConfig.contexts.map(context => {
|
contexts: kubeConfig.contexts.map(context => {
|
||||||
return {
|
return {
|
||||||
@ -98,7 +98,7 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
|
|||||||
user: context.user,
|
user: context.user,
|
||||||
namespace: context.namespace
|
namespace: context.namespace
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}),
|
}),
|
||||||
users: kubeConfig.users.map(user => {
|
users: kubeConfig.users.map(user => {
|
||||||
return {
|
return {
|
||||||
@ -114,9 +114,9 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
|
|||||||
username: user.username,
|
username: user.username,
|
||||||
password: user.password
|
password: user.password
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
|
|
||||||
logger.debug("Dumping KubeConfig:", config);
|
logger.debug("Dumping KubeConfig:", config);
|
||||||
|
|
||||||
@ -127,20 +127,20 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
|
|||||||
export function podHasIssues(pod: V1Pod) {
|
export function podHasIssues(pod: V1Pod) {
|
||||||
// Logic adapted from dashboard
|
// Logic adapted from dashboard
|
||||||
const notReady = !!pod.status.conditions.find(condition => {
|
const notReady = !!pod.status.conditions.find(condition => {
|
||||||
return condition.type == "Ready" && condition.status !== "True"
|
return condition.type == "Ready" && condition.status !== "True";
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
notReady ||
|
notReady ||
|
||||||
pod.status.phase !== "Running" ||
|
pod.status.phase !== "Running" ||
|
||||||
pod.spec.priority > 500000 // We're interested in high prio pods events regardless of their running status
|
pod.spec.priority > 500000 // We're interested in high prio pods events regardless of their running status
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNodeWarningConditions(node: V1Node) {
|
export function getNodeWarningConditions(node: V1Node) {
|
||||||
return node.status.conditions.filter(c =>
|
return node.status.conditions.filter(c =>
|
||||||
c.status.toLowerCase() === "true" && c.type !== "Ready" && c.type !== "HostUpgrades"
|
c.status.toLowerCase() === "true" && c.type !== "Ready" && c.type !== "HostUpgrades"
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import { PrometheusStacklight } from "../main/prometheus/stacklight";
|
|||||||
import { PrometheusProviderRegistry } from "../main/prometheus/provider-registry";
|
import { PrometheusProviderRegistry } from "../main/prometheus/provider-registry";
|
||||||
|
|
||||||
[PrometheusLens, PrometheusHelm, PrometheusOperator, PrometheusStacklight].forEach(providerClass => {
|
[PrometheusLens, PrometheusHelm, PrometheusOperator, PrometheusStacklight].forEach(providerClass => {
|
||||||
const provider = new providerClass()
|
const provider = new providerClass();
|
||||||
PrometheusProviderRegistry.registerProvider(provider.id, provider)
|
PrometheusProviderRegistry.registerProvider(provider.id, provider);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const prometheusProviders = PrometheusProviderRegistry.getProviders()
|
export const prometheusProviders = PrometheusProviderRegistry.getProviders();
|
||||||
@ -4,7 +4,7 @@ export type KubeResource =
|
|||||||
"namespaces" | "nodes" | "events" | "resourcequotas" |
|
"namespaces" | "nodes" | "events" | "resourcequotas" |
|
||||||
"services" | "secrets" | "configmaps" | "ingresses" | "networkpolicies" | "persistentvolumes" | "storageclasses" |
|
"services" | "secrets" | "configmaps" | "ingresses" | "networkpolicies" | "persistentvolumes" | "storageclasses" |
|
||||||
"pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "jobs" | "cronjobs" |
|
"pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "jobs" | "cronjobs" |
|
||||||
"endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets"
|
"endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets";
|
||||||
|
|
||||||
export interface KubeApiResource {
|
export interface KubeApiResource {
|
||||||
resource: KubeResource; // valid resource name
|
resource: KubeResource; // valid resource name
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Register custom protocols
|
// Register custom protocols
|
||||||
|
|
||||||
import { protocol } from "electron"
|
import { protocol } from "electron";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
export function registerFileProtocol(name: string, basePath: string) {
|
export function registerFileProtocol(name: string, basePath: string) {
|
||||||
@ -8,5 +8,5 @@ export function registerFileProtocol(name: string, basePath: string) {
|
|||||||
const filePath = request.url.replace(name + "://", "");
|
const filePath = request.url.replace(name + "://", "");
|
||||||
const absPath = path.resolve(basePath, filePath);
|
const absPath = path.resolve(basePath, filePath);
|
||||||
callback({ path: absPath });
|
callback({ path: absPath });
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,28 @@
|
|||||||
import request from "request"
|
import request from "request";
|
||||||
import requestPromise from "request-promise-native"
|
import requestPromise from "request-promise-native";
|
||||||
import { userStore } from "./user-store"
|
import { userStore } from "./user-store";
|
||||||
|
|
||||||
// todo: get rid of "request" (deprecated)
|
// todo: get rid of "request" (deprecated)
|
||||||
// https://github.com/lensapp/lens/issues/459
|
// https://github.com/lensapp/lens/issues/459
|
||||||
|
|
||||||
function getDefaultRequestOpts(): Partial<request.Options> {
|
function getDefaultRequestOpts(): Partial<request.Options> {
|
||||||
const { httpsProxy, allowUntrustedCAs } = userStore.preferences
|
const { httpsProxy, allowUntrustedCAs } = userStore.preferences;
|
||||||
return {
|
return {
|
||||||
proxy: httpsProxy || undefined,
|
proxy: httpsProxy || undefined,
|
||||||
rejectUnauthorized: !allowUntrustedCAs,
|
rejectUnauthorized: !allowUntrustedCAs,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
export function customRequest(opts: request.Options) {
|
export function customRequest(opts: request.Options) {
|
||||||
return request.defaults(getDefaultRequestOpts())(opts)
|
return request.defaults(getDefaultRequestOpts())(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
export function customRequestPromise(opts: requestPromise.Options) {
|
export function customRequestPromise(opts: requestPromise.Options) {
|
||||||
return requestPromise.defaults(getDefaultRequestOpts())(opts)
|
return requestPromise.defaults(getDefaultRequestOpts())(opts);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { isMac, isWindows } from "./vars";
|
import { isMac, isWindows } from "./vars";
|
||||||
import winca from "win-ca"
|
import winca from "win-ca";
|
||||||
import macca from "mac-ca"
|
import macca from "mac-ca";
|
||||||
import logger from "../main/logger"
|
import logger from "../main/logger";
|
||||||
|
|
||||||
if (isMac) {
|
if (isMac) {
|
||||||
for (const crt of macca.all()) {
|
for (const crt of macca.all()) {
|
||||||
const attributes = crt.issuer?.attributes?.map((a: any) => `${a.name}=${a.value}`)
|
const attributes = crt.issuer?.attributes?.map((a: any) => `${a.name}=${a.value}`);
|
||||||
logger.debug("Using host CA: " + attributes.join(","))
|
logger.debug("Using host CA: " + attributes.join(","));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isWindows) {
|
if (isWindows) {
|
||||||
winca.inject("+") // see: https://github.com/ukoloff/win-ca#caveats
|
winca.inject("+"); // see: https://github.com/ukoloff/win-ca#caveats
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import type { ThemeId } from "../renderer/theme.store";
|
import type { ThemeId } from "../renderer/theme.store";
|
||||||
import { app, remote } from 'electron';
|
import { app, remote } from 'electron';
|
||||||
import semver from "semver"
|
import semver from "semver";
|
||||||
import { readFile } from "fs-extra"
|
import { readFile } from "fs-extra";
|
||||||
import { action, observable, reaction, toJS } from "mobx";
|
import { action, observable, reaction, toJS } from "mobx";
|
||||||
import { BaseStore } from "./base-store";
|
import { BaseStore } from "./base-store";
|
||||||
import migrations from "../migrations/user-store"
|
import migrations from "../migrations/user-store";
|
||||||
import { getAppVersion } from "./utils/app-version";
|
import { getAppVersion } from "./utils/app-version";
|
||||||
import { kubeConfigDefaultPath, loadConfig } from "./kube-helpers";
|
import { kubeConfigDefaultPath, loadConfig } from "./kube-helpers";
|
||||||
import { appEventBus } from "./event-bus"
|
import { appEventBus } from "./event-bus";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ export interface UserPreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UserStore extends BaseStore<UserStoreModel> {
|
export class UserStore extends BaseStore<UserStoreModel> {
|
||||||
static readonly defaultTheme: ThemeId = "lens-dark"
|
static readonly defaultTheme: ThemeId = "lens-dark";
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
super({
|
super({
|
||||||
@ -42,7 +42,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
|||||||
this.handleOnLoad();
|
this.handleOnLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable lastSeenAppVersion = "0.0.0"
|
@observable lastSeenAppVersion = "0.0.0";
|
||||||
@observable kubeConfigPath = kubeConfigDefaultPath; // used in add-cluster page for providing context
|
@observable kubeConfigPath = kubeConfigDefaultPath; // used in add-cluster page for providing context
|
||||||
@observable seenContexts = observable.set<string>();
|
@observable seenContexts = observable.set<string>();
|
||||||
@observable newContexts = observable.set<string>();
|
@observable newContexts = observable.set<string>();
|
||||||
@ -66,7 +66,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
|||||||
if (app) {
|
if (app) {
|
||||||
// track telemetry availability
|
// track telemetry availability
|
||||||
reaction(() => this.preferences.allowTelemetry, allowed => {
|
reaction(() => this.preferences.allowTelemetry, allowed => {
|
||||||
appEventBus.emit({name: "telemetry", action: allowed ? "enabled" : "disabled"})
|
appEventBus.emit({name: "telemetry", action: allowed ? "enabled" : "disabled"});
|
||||||
});
|
});
|
||||||
|
|
||||||
// open at system start-up
|
// open at system start-up
|
||||||
@ -95,7 +95,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
saveLastSeenAppVersion() {
|
saveLastSeenAppVersion() {
|
||||||
appEventBus.emit({name: "app", action: "whats-new-seen"})
|
appEventBus.emit({name: "app", action: "whats-new-seen"});
|
||||||
this.lastSeenAppVersion = getAppVersion();
|
this.lastSeenAppVersion = getAppVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
|||||||
logger.error(err);
|
logger.error(err);
|
||||||
this.resetKubeConfigPath();
|
this.resetKubeConfigPath();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
markNewContextsAsSeen() {
|
markNewContextsAsSeen() {
|
||||||
@ -127,12 +127,12 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
|||||||
* @returns string
|
* @returns string
|
||||||
*/
|
*/
|
||||||
getDefaultKubectlPath(): string {
|
getDefaultKubectlPath(): string {
|
||||||
return path.join((app || remote.app).getPath("userData"), "binaries")
|
return path.join((app || remote.app).getPath("userData"), "binaries");
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected async fromStore(data: Partial<UserStoreModel> = {}) {
|
protected async fromStore(data: Partial<UserStoreModel> = {}) {
|
||||||
const { lastSeenAppVersion, seenContexts = [], preferences, kubeConfigPath } = data
|
const { lastSeenAppVersion, seenContexts = [], preferences, kubeConfigPath } = data;
|
||||||
if (lastSeenAppVersion) {
|
if (lastSeenAppVersion) {
|
||||||
this.lastSeenAppVersion = lastSeenAppVersion;
|
this.lastSeenAppVersion = lastSeenAppVersion;
|
||||||
}
|
}
|
||||||
@ -149,10 +149,10 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
|||||||
lastSeenAppVersion: this.lastSeenAppVersion,
|
lastSeenAppVersion: this.lastSeenAppVersion,
|
||||||
seenContexts: Array.from(this.seenContexts),
|
seenContexts: Array.from(this.seenContexts),
|
||||||
preferences: this.preferences,
|
preferences: this.preferences,
|
||||||
}
|
};
|
||||||
return toJS(model, {
|
return toJS(model, {
|
||||||
recurseEverything: true,
|
recurseEverything: true,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import packageInfo from "../../../package.json"
|
import packageInfo from "../../../package.json";
|
||||||
|
|
||||||
export function getAppVersion(): string {
|
export function getAppVersion(): string {
|
||||||
return packageInfo.version;
|
return packageInfo.version;
|
||||||
@ -9,5 +9,5 @@ export function getBundledKubectlVersion(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getBundledExtensions(): string[] {
|
export function getBundledExtensions(): string[] {
|
||||||
return packageInfo.lens?.extensions || []
|
return packageInfo.lens?.extensions || [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export function autobind() {
|
|||||||
return function (target: Constructor | object, prop?: string, descriptor?: PropertyDescriptor) {
|
return function (target: Constructor | object, prop?: string, descriptor?: PropertyDescriptor) {
|
||||||
if (target instanceof Function) return bindClass(target);
|
if (target instanceof Function) return bindClass(target);
|
||||||
else return bindMethod(target, prop, descriptor);
|
else return bindMethod(target, prop, descriptor);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindClass<T extends Constructor>(constructor: T) {
|
function bindClass<T extends Constructor>(constructor: T) {
|
||||||
@ -22,12 +22,12 @@ function bindClass<T extends Constructor>(constructor: T) {
|
|||||||
if (skipMethod(prop)) return;
|
if (skipMethod(prop)) return;
|
||||||
const boundDescriptor = bindMethod(proto, prop, descriptors[prop]);
|
const boundDescriptor = bindMethod(proto, prop, descriptors[prop]);
|
||||||
Object.defineProperty(proto, prop, boundDescriptor);
|
Object.defineProperty(proto, prop, boundDescriptor);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindMethod(target: object, prop?: string, descriptor?: PropertyDescriptor) {
|
function bindMethod(target: object, prop?: string, descriptor?: PropertyDescriptor) {
|
||||||
if (!descriptor || typeof descriptor.value !== "function") {
|
if (!descriptor || typeof descriptor.value !== "function") {
|
||||||
throw new Error(`@autobind() must be used on class or method only`)
|
throw new Error(`@autobind() must be used on class or method only`);
|
||||||
}
|
}
|
||||||
const { value: func, enumerable, configurable } = descriptor;
|
const { value: func, enumerable, configurable } = descriptor;
|
||||||
const boundFunc = new WeakMap<object, Function>();
|
const boundFunc = new WeakMap<object, Function>();
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { compile } from "path-to-regexp"
|
import { compile } from "path-to-regexp";
|
||||||
|
|
||||||
export interface IURLParams<P extends object = {}, Q extends object = {}> {
|
export interface IURLParams<P extends object = {}, Q extends object = {}> {
|
||||||
params?: P;
|
params?: P;
|
||||||
@ -8,7 +8,7 @@ export interface IURLParams<P extends object = {}, Q extends object = {}> {
|
|||||||
export function buildURL<P extends object = {}, Q extends object = {}>(path: string | any) {
|
export function buildURL<P extends object = {}, Q extends object = {}>(path: string | any) {
|
||||||
const pathBuilder = compile(String(path));
|
const pathBuilder = compile(String(path));
|
||||||
return function ({ params, query }: IURLParams<P, Q> = {}) {
|
return function ({ params, query }: IURLParams<P, Q> = {}) {
|
||||||
const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : ""
|
const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : "";
|
||||||
return pathBuilder(params) + (queryParams ? `?${queryParams}` : "")
|
return pathBuilder(params) + (queryParams ? `?${queryParams}` : "");
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
export function defineGlobal(propName: string, descriptor: PropertyDescriptor) {
|
export function defineGlobal(propName: string, descriptor: PropertyDescriptor) {
|
||||||
const scope = typeof global !== "undefined" ? global : window;
|
const scope = typeof global !== "undefined" ? global : window;
|
||||||
if (scope.hasOwnProperty(propName)) {
|
if (scope.hasOwnProperty(propName)) {
|
||||||
console.info(`Global variable "${propName}" already exists. Skipping.`)
|
console.info(`Global variable "${propName}" already exists. Skipping.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Object.defineProperty(scope, propName, descriptor);
|
Object.defineProperty(scope, propName, descriptor);
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
// Common utils (main OR renderer)
|
// Common utils (main OR renderer)
|
||||||
|
|
||||||
export * from "./app-version"
|
export * from "./app-version";
|
||||||
export * from "./autobind"
|
export * from "./autobind";
|
||||||
export * from "./base64"
|
export * from "./base64";
|
||||||
export * from "./camelCase"
|
export * from "./camelCase";
|
||||||
export * from "./cloneJson"
|
export * from "./cloneJson";
|
||||||
export * from "./debouncePromise"
|
export * from "./debouncePromise";
|
||||||
export * from "./defineGlobal"
|
export * from "./defineGlobal";
|
||||||
export * from "./getRandId"
|
export * from "./getRandId";
|
||||||
export * from "./splitArray"
|
export * from "./splitArray";
|
||||||
export * from "./saveToAppFiles"
|
export * from "./saveToAppFiles";
|
||||||
export * from "./singleton"
|
export * from "./singleton";
|
||||||
export * from "./openExternal"
|
export * from "./openExternal";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Opens a link in external browser
|
// Opens a link in external browser
|
||||||
import { shell } from "electron"
|
import { shell } from "electron";
|
||||||
|
|
||||||
export function openExternal(url: string) {
|
export function openExternal(url: string) {
|
||||||
return shell.openExternal(url);
|
return shell.openExternal(url);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import { app, remote } from "electron";
|
import { app, remote } from "electron";
|
||||||
import { ensureDirSync, writeFileSync } from "fs-extra";
|
import { ensureDirSync, writeFileSync } from "fs-extra";
|
||||||
import { WriteFileOptions } from "fs"
|
import { WriteFileOptions } from "fs";
|
||||||
|
|
||||||
export function saveToAppFiles(filePath: string, contents: any, options?: WriteFileOptions): string {
|
export function saveToAppFiles(filePath: string, contents: any, options?: WriteFileOptions): string {
|
||||||
const absPath = path.resolve((app || remote.app).getPath("userData"), filePath);
|
const absPath = path.resolve((app || remote.app).getPath("userData"), filePath);
|
||||||
|
|||||||
@ -24,5 +24,5 @@ class Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Singleton }
|
export { Singleton };
|
||||||
export default Singleton;
|
export default Singleton;
|
||||||
@ -15,5 +15,5 @@ export function splitArray<T>(array: T[], element: T): [T[], T[], boolean] {
|
|||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
return [array, [], false];
|
return [array, [], false];
|
||||||
}
|
}
|
||||||
return [array.slice(0, index), array.slice(index + 1, array.length), true]
|
return [array.slice(0, index), array.slice(index + 1, array.length), true];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
// App's common configuration for any process (main, renderer, build pipeline, etc.)
|
// App's common configuration for any process (main, renderer, build pipeline, etc.)
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import packageInfo from "../../package.json"
|
import packageInfo from "../../package.json";
|
||||||
import { defineGlobal } from "./utils/defineGlobal";
|
import { defineGlobal } from "./utils/defineGlobal";
|
||||||
|
|
||||||
export const isMac = process.platform === "darwin"
|
export const isMac = process.platform === "darwin";
|
||||||
export const isWindows = process.platform === "win32"
|
export const isWindows = process.platform === "win32";
|
||||||
export const isLinux = process.platform === "linux"
|
export const isLinux = process.platform === "linux";
|
||||||
export const isDebugging = process.env.DEBUG === "true";
|
export const isDebugging = process.env.DEBUG === "true";
|
||||||
export const isSnap = !!process.env["SNAP"]
|
export const isSnap = !!process.env["SNAP"];
|
||||||
export const isProduction = process.env.NODE_ENV === "production"
|
export const isProduction = process.env.NODE_ENV === "production";
|
||||||
export const isTestEnv = !!process.env.JEST_WORKER_ID;
|
export const isTestEnv = !!process.env.JEST_WORKER_ID;
|
||||||
export const isDevelopment = !isTestEnv && !isProduction;
|
export const isDevelopment = !isTestEnv && !isProduction;
|
||||||
|
|
||||||
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`
|
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`;
|
||||||
export const publicPath = "/build/"
|
export const publicPath = "/build/";
|
||||||
|
|
||||||
// Webpack build paths
|
// Webpack build paths
|
||||||
export const contextDir = process.cwd();
|
export const contextDir = process.cwd();
|
||||||
@ -22,7 +22,7 @@ export const mainDir = path.join(contextDir, "src/main");
|
|||||||
export const rendererDir = path.join(contextDir, "src/renderer");
|
export const rendererDir = path.join(contextDir, "src/renderer");
|
||||||
export const htmlTemplate = path.resolve(rendererDir, "template.html");
|
export const htmlTemplate = path.resolve(rendererDir, "template.html");
|
||||||
export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");
|
export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");
|
||||||
export const webpackDevServerPort = 9009
|
export const webpackDevServerPort = 9009;
|
||||||
|
|
||||||
// Special runtime paths
|
// Special runtime paths
|
||||||
defineGlobal("__static", {
|
defineGlobal("__static", {
|
||||||
@ -30,14 +30,14 @@ defineGlobal("__static", {
|
|||||||
if (isDevelopment) {
|
if (isDevelopment) {
|
||||||
return path.resolve(contextDir, "static");
|
return path.resolve(contextDir, "static");
|
||||||
}
|
}
|
||||||
return path.resolve(process.resourcesPath, "static")
|
return path.resolve(process.resourcesPath, "static");
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// Apis
|
// Apis
|
||||||
export const apiPrefix = "/api" // local router apis
|
export const apiPrefix = "/api"; // local router apis
|
||||||
export const apiKubePrefix = "/api-kube" // k8s cluster apis
|
export const apiKubePrefix = "/api-kube"; // k8s cluster apis
|
||||||
|
|
||||||
// Links
|
// Links
|
||||||
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues"
|
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues";
|
||||||
export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI"
|
export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI";
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { action, computed, observable, toJS, reaction } from "mobx";
|
import { action, computed, observable, toJS, reaction } from "mobx";
|
||||||
import { BaseStore } from "./base-store";
|
import { BaseStore } from "./base-store";
|
||||||
import { clusterStore } from "./cluster-store"
|
import { clusterStore } from "./cluster-store";
|
||||||
import { appEventBus } from "./event-bus";
|
import { appEventBus } from "./event-bus";
|
||||||
import { broadcastMessage } from "../common/ipc";
|
import { broadcastMessage } from "../common/ipc";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
@ -25,40 +25,40 @@ export interface WorkspaceState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Workspace implements WorkspaceModel, WorkspaceState {
|
export class Workspace implements WorkspaceModel, WorkspaceState {
|
||||||
@observable id: WorkspaceId
|
@observable id: WorkspaceId;
|
||||||
@observable name: string
|
@observable name: string;
|
||||||
@observable description?: string
|
@observable description?: string;
|
||||||
@observable ownerRef?: string
|
@observable ownerRef?: string;
|
||||||
@observable enabled: boolean
|
@observable enabled: boolean;
|
||||||
|
|
||||||
constructor(data: WorkspaceModel) {
|
constructor(data: WorkspaceModel) {
|
||||||
Object.assign(this, data)
|
Object.assign(this, data);
|
||||||
|
|
||||||
if (!ipcRenderer) {
|
if (!ipcRenderer) {
|
||||||
reaction(() => this.getState(), () => {
|
reaction(() => this.getState(), () => {
|
||||||
this.pushState()
|
this.pushState();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isManaged(): boolean {
|
get isManaged(): boolean {
|
||||||
return !!this.ownerRef
|
return !!this.ownerRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
getState(): WorkspaceState {
|
getState(): WorkspaceState {
|
||||||
return {
|
return {
|
||||||
enabled: this.enabled
|
enabled: this.enabled
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pushState(state = this.getState()) {
|
pushState(state = this.getState()) {
|
||||||
logger.silly("[WORKSPACE] pushing state", {...state, id: this.id})
|
logger.silly("[WORKSPACE] pushing state", {...state, id: this.id});
|
||||||
broadcastMessage("workspace:state", this.id, toJS(state))
|
broadcastMessage("workspace:state", this.id, toJS(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
setState(state: WorkspaceState) {
|
setState(state: WorkspaceState) {
|
||||||
Object.assign(this, state)
|
Object.assign(this, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): WorkspaceModel {
|
toJSON(): WorkspaceModel {
|
||||||
@ -67,12 +67,12 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
|
|||||||
name: this.name,
|
name: this.name,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
ownerRef: this.ownerRef
|
ownerRef: this.ownerRef
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||||
static readonly defaultId: WorkspaceId = "default"
|
static readonly defaultId: WorkspaceId = "default";
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
super({
|
super({
|
||||||
@ -81,21 +81,21 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
|
|
||||||
if (!ipcRenderer) {
|
if (!ipcRenderer) {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.pushState()
|
this.pushState();
|
||||||
}, 5000)
|
}, 5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerIpcListener() {
|
registerIpcListener() {
|
||||||
logger.info("[WORKSPACE-STORE] starting to listen state events")
|
logger.info("[WORKSPACE-STORE] starting to listen state events");
|
||||||
ipcRenderer.on("workspace:state", (event, workspaceId: string, state: WorkspaceState) => {
|
ipcRenderer.on("workspace:state", (event, workspaceId: string, state: WorkspaceState) => {
|
||||||
this.getById(workspaceId)?.setState(state)
|
this.getById(workspaceId)?.setState(state);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterIpcListener() {
|
unregisterIpcListener() {
|
||||||
super.unregisterIpcListener()
|
super.unregisterIpcListener();
|
||||||
ipcRenderer.removeAllListeners("workspace:state")
|
ipcRenderer.removeAllListeners("workspace:state");
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable currentWorkspaceId = WorkspaceStore.defaultId;
|
@observable currentWorkspaceId = WorkspaceStore.defaultId;
|
||||||
@ -121,8 +121,8 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
|
|
||||||
pushState() {
|
pushState() {
|
||||||
this.workspaces.forEach((w) => {
|
this.workspaces.forEach((w) => {
|
||||||
w.pushState()
|
w.pushState();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isDefault(id: WorkspaceId) {
|
isDefault(id: WorkspaceId) {
|
||||||
@ -154,7 +154,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.workspaces.set(id, workspace);
|
this.workspaces.set(id, workspace);
|
||||||
appEventBus.emit({name: "workspace", action: "add"})
|
appEventBus.emit({name: "workspace", action: "add"});
|
||||||
return workspace;
|
return workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
removeWorkspace(workspace: Workspace) {
|
removeWorkspace(workspace: Workspace) {
|
||||||
this.removeWorkspaceById(workspace.id)
|
this.removeWorkspaceById(workspace.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -180,24 +180,24 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
this.currentWorkspaceId = WorkspaceStore.defaultId; // reset to default
|
this.currentWorkspaceId = WorkspaceStore.defaultId; // reset to default
|
||||||
}
|
}
|
||||||
this.workspaces.delete(id);
|
this.workspaces.delete(id);
|
||||||
appEventBus.emit({name: "workspace", action: "remove"})
|
appEventBus.emit({name: "workspace", action: "remove"});
|
||||||
clusterStore.removeByWorkspaceId(id)
|
clusterStore.removeByWorkspaceId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected fromStore({ currentWorkspace, workspaces = [] }: WorkspaceStoreModel) {
|
protected fromStore({ currentWorkspace, workspaces = [] }: WorkspaceStoreModel) {
|
||||||
if (currentWorkspace) {
|
if (currentWorkspace) {
|
||||||
this.currentWorkspaceId = currentWorkspace
|
this.currentWorkspaceId = currentWorkspace;
|
||||||
}
|
}
|
||||||
if (workspaces.length) {
|
if (workspaces.length) {
|
||||||
this.workspaces.clear();
|
this.workspaces.clear();
|
||||||
workspaces.forEach(ws => {
|
workspaces.forEach(ws => {
|
||||||
const workspace = new Workspace(ws)
|
const workspace = new Workspace(ws);
|
||||||
if (!workspace.isManaged) {
|
if (!workspace.isManaged) {
|
||||||
workspace.enabled = true
|
workspace.enabled = true;
|
||||||
}
|
}
|
||||||
this.workspaces.set(workspace.id, workspace)
|
this.workspaces.set(workspace.id, workspace);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,8 +207,8 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
workspaces: this.workspacesList.map((w) => w.toJSON()),
|
workspaces: this.workspacesList.map((w) => w.toJSON()),
|
||||||
}, {
|
}, {
|
||||||
recurseEverything: true
|
recurseEverything: true
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const workspaceStore = WorkspaceStore.getInstance<WorkspaceStore>()
|
export const workspaceStore = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { LensExtension } from "../lens-extension"
|
import { LensExtension } from "../lens-extension";
|
||||||
|
|
||||||
let ext: LensExtension = null
|
let ext: LensExtension = null;
|
||||||
|
|
||||||
describe("lens extension", () => {
|
describe("lens extension", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -12,12 +12,12 @@ describe("lens extension", () => {
|
|||||||
manifestPath: "/this/is/fake/package.json",
|
manifestPath: "/this/is/fake/package.json",
|
||||||
isBundled: false,
|
isBundled: false,
|
||||||
isEnabled: true
|
isEnabled: true
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("name", () => {
|
describe("name", () => {
|
||||||
it("returns name", () => {
|
it("returns name", () => {
|
||||||
expect(ext.name).toBe("foo-bar")
|
expect(ext.name).toBe("foo-bar");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path"
|
import path from "path";
|
||||||
import hb from "handlebars"
|
import hb from "handlebars";
|
||||||
import { observable } from "mobx"
|
import { observable } from "mobx";
|
||||||
import { ResourceApplier } from "../main/resource-applier"
|
import { ResourceApplier } from "../main/resource-applier";
|
||||||
import { Cluster } from "../main/cluster";
|
import { Cluster } from "../main/cluster";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { app } from "electron"
|
import { app } from "electron";
|
||||||
import { requestMain } from "../common/ipc";
|
import { requestMain } from "../common/ipc";
|
||||||
import { clusterKubectlApplyAllHandler } from "../common/cluster-ipc";
|
import { clusterKubectlApplyAllHandler } from "../common/cluster-ipc";
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ export abstract class ClusterFeature {
|
|||||||
installed: false,
|
installed: false,
|
||||||
latestVersion: null,
|
latestVersion: null,
|
||||||
canUpgrade: false
|
canUpgrade: false
|
||||||
}
|
};
|
||||||
|
|
||||||
abstract async install(cluster: Cluster): Promise<void>;
|
abstract async install(cluster: Cluster): Promise<void>;
|
||||||
|
|
||||||
@ -38,9 +38,9 @@ export abstract class ClusterFeature {
|
|||||||
|
|
||||||
protected async applyResources(cluster: Cluster, resources: string[]) {
|
protected async applyResources(cluster: Cluster, resources: string[]) {
|
||||||
if (app) {
|
if (app) {
|
||||||
await new ResourceApplier(cluster).kubectlApplyAll(resources)
|
await new ResourceApplier(cluster).kubectlApplyAll(resources);
|
||||||
} else {
|
} else {
|
||||||
await requestMain(clusterKubectlApplyAllHandler, cluster.id, resources)
|
await requestMain(clusterKubectlApplyAllHandler, cluster.id, resources);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { getAppVersion } from "../../common/utils";
|
import { getAppVersion } from "../../common/utils";
|
||||||
|
|
||||||
export const version = getAppVersion()
|
export const version = getAppVersion();
|
||||||
export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars"
|
export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars";
|
||||||
@ -1,2 +1,2 @@
|
|||||||
export { ClusterFeature as Feature } from "../cluster-feature"
|
export { ClusterFeature as Feature } from "../cluster-feature";
|
||||||
export type { ClusterFeatureStatus as FeatureStatus } from "../cluster-feature"
|
export type { ClusterFeatureStatus as FeatureStatus } from "../cluster-feature";
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export { appEventBus } from "../../common/event-bus"
|
export { appEventBus } from "../../common/event-bus";
|
||||||
export type { AppEvent } from "../../common/event-bus"
|
export type { AppEvent } from "../../common/event-bus";
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
// Lens-extensions api developer's kit
|
// Lens-extensions api developer's kit
|
||||||
export * from "../lens-main-extension"
|
export * from "../lens-main-extension";
|
||||||
export * from "../lens-renderer-extension"
|
export * from "../lens-renderer-extension";
|
||||||
|
|
||||||
// APIs
|
// APIs
|
||||||
import * as App from "./app"
|
import * as App from "./app";
|
||||||
import * as EventBus from "./event-bus"
|
import * as EventBus from "./event-bus";
|
||||||
import * as Store from "./stores"
|
import * as Store from "./stores";
|
||||||
import * as Util from "./utils"
|
import * as Util from "./utils";
|
||||||
import * as ClusterFeature from "./cluster-feature"
|
import * as ClusterFeature from "./cluster-feature";
|
||||||
import * as Interface from "../interfaces"
|
import * as Interface from "../interfaces";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
App,
|
App,
|
||||||
@ -17,4 +17,4 @@ export {
|
|||||||
Interface,
|
Interface,
|
||||||
Store,
|
Store,
|
||||||
Util,
|
Util,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
export { ExtensionStore } from "../extension-store"
|
export { ExtensionStore } from "../extension-store";
|
||||||
export { clusterStore } from "../../common/cluster-store"
|
export { clusterStore } from "../../common/cluster-store";
|
||||||
export type { ClusterModel } from "../../common/cluster-store"
|
export type { ClusterModel } from "../../common/cluster-store";
|
||||||
export { Cluster } from "../../main/cluster"
|
export { Cluster } from "../../main/cluster";
|
||||||
export { workspaceStore, Workspace } from "../../common/workspace-store"
|
export { workspaceStore, Workspace } from "../../common/workspace-store";
|
||||||
export type { WorkspaceModel } from "../../common/workspace-store"
|
export type { WorkspaceModel } from "../../common/workspace-store";
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
export { Singleton, openExternal } from "../../common/utils"
|
export { Singleton, openExternal } from "../../common/utils";
|
||||||
export { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault"
|
export { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault";
|
||||||
export { cssNames } from "../../renderer/utils/cssNames"
|
export { cssNames } from "../../renderer/utils/cssNames";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Extension-api types generation bundle
|
// Extension-api types generation bundle
|
||||||
|
|
||||||
export * from "./core-api"
|
export * from "./core-api";
|
||||||
export * from "./renderer-api"
|
export * from "./renderer-api";
|
||||||
|
|||||||
@ -1,24 +1,24 @@
|
|||||||
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "./lens-extension"
|
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "./lens-extension";
|
||||||
import type { LensMainExtension } from "./lens-main-extension"
|
import type { LensMainExtension } from "./lens-main-extension";
|
||||||
import type { LensRendererExtension } from "./lens-renderer-extension"
|
import type { LensRendererExtension } from "./lens-renderer-extension";
|
||||||
import type { InstalledExtension } from "./extension-manager";
|
import type { InstalledExtension } from "./extension-manager";
|
||||||
import path from "path"
|
import path from "path";
|
||||||
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc"
|
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
|
||||||
import { action, computed, observable, reaction, toJS, when } from "mobx"
|
import { action, computed, observable, reaction, toJS, when } from "mobx";
|
||||||
import logger from "../main/logger"
|
import logger from "../main/logger";
|
||||||
import { app, ipcRenderer, remote } from "electron"
|
import { app, ipcRenderer, remote } from "electron";
|
||||||
import * as registries from "./registries";
|
import * as registries from "./registries";
|
||||||
import { extensionsStore } from "./extensions-store";
|
import { extensionsStore } from "./extensions-store";
|
||||||
|
|
||||||
// lazy load so that we get correct userData
|
// lazy load so that we get correct userData
|
||||||
export function extensionPackagesRoot() {
|
export function extensionPackagesRoot() {
|
||||||
return path.join((app || remote.app).getPath("userData"))
|
return path.join((app || remote.app).getPath("userData"));
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExtensionLoader {
|
export class ExtensionLoader {
|
||||||
protected extensions = observable.map<LensExtensionId, InstalledExtension>();
|
protected extensions = observable.map<LensExtensionId, InstalledExtension>();
|
||||||
protected instances = observable.map<LensExtensionId, LensExtension>();
|
protected instances = observable.map<LensExtensionId, LensExtension>();
|
||||||
protected readonly requestExtensionsChannel = "extensions:loaded"
|
protected readonly requestExtensionsChannel = "extensions:loaded";
|
||||||
|
|
||||||
@observable isLoaded = false;
|
@observable isLoaded = false;
|
||||||
whenLoaded = when(() => this.isLoaded);
|
whenLoaded = when(() => this.isLoaded);
|
||||||
@ -29,22 +29,22 @@ export class ExtensionLoader {
|
|||||||
if (ext.isBundled) {
|
if (ext.isBundled) {
|
||||||
extensions.delete(extId);
|
extensions.delete(extId);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return extensions;
|
return extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async init() {
|
async init() {
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
this.initRenderer()
|
this.initRenderer();
|
||||||
} else {
|
} else {
|
||||||
this.initMain()
|
this.initMain();
|
||||||
}
|
}
|
||||||
extensionsStore.manageState(this);
|
extensionsStore.manageState(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
initExtensions(extensions?: Map<LensExtensionId, InstalledExtension>) {
|
initExtensions(extensions?: Map<LensExtensionId, InstalledExtension>) {
|
||||||
this.extensions.replace(extensions)
|
this.extensions.replace(extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initMain() {
|
protected async initMain() {
|
||||||
@ -53,12 +53,12 @@ export class ExtensionLoader {
|
|||||||
this.broadcastExtensions();
|
this.broadcastExtensions();
|
||||||
|
|
||||||
reaction(() => this.extensions.toJS(), () => {
|
reaction(() => this.extensions.toJS(), () => {
|
||||||
this.broadcastExtensions()
|
this.broadcastExtensions();
|
||||||
})
|
});
|
||||||
|
|
||||||
handleRequest(this.requestExtensionsChannel, () => {
|
handleRequest(this.requestExtensionsChannel, () => {
|
||||||
return Array.from(this.toJSON())
|
return Array.from(this.toJSON());
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initRenderer() {
|
protected async initRenderer() {
|
||||||
@ -66,25 +66,25 @@ export class ExtensionLoader {
|
|||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
extensions.forEach(([extId, ext]) => {
|
extensions.forEach(([extId, ext]) => {
|
||||||
if (!this.extensions.has(extId)) {
|
if (!this.extensions.has(extId)) {
|
||||||
this.extensions.set(extId, ext)
|
this.extensions.set(extId, ext);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
requestMain(this.requestExtensionsChannel).then(extensionListHandler)
|
requestMain(this.requestExtensionsChannel).then(extensionListHandler);
|
||||||
subscribeToBroadcast(this.requestExtensionsChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => {
|
subscribeToBroadcast(this.requestExtensionsChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => {
|
||||||
extensionListHandler(extensions)
|
extensionListHandler(extensions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnMain() {
|
loadOnMain() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main')
|
logger.info('[EXTENSIONS-LOADER]: load on main');
|
||||||
this.autoInitExtensions((ext: LensMainExtension) => [
|
this.autoInitExtensions((ext: LensMainExtension) => [
|
||||||
registries.menuRegistry.add(ext.appMenus)
|
registries.menuRegistry.add(ext.appMenus)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterManagerRenderer() {
|
loadOnClusterManagerRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)');
|
||||||
this.autoInitExtensions((ext: LensRendererExtension) => [
|
this.autoInitExtensions((ext: LensRendererExtension) => [
|
||||||
registries.globalPageRegistry.add(ext.globalPages, ext),
|
registries.globalPageRegistry.add(ext.globalPages, ext),
|
||||||
registries.globalPageMenuRegistry.add(ext.globalPageMenus, ext),
|
registries.globalPageMenuRegistry.add(ext.globalPageMenus, ext),
|
||||||
@ -95,14 +95,14 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterRenderer() {
|
loadOnClusterRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)');
|
||||||
this.autoInitExtensions((ext: LensRendererExtension) => [
|
this.autoInitExtensions((ext: LensRendererExtension) => [
|
||||||
registries.clusterPageRegistry.add(ext.clusterPages, ext),
|
registries.clusterPageRegistry.add(ext.clusterPages, ext),
|
||||||
registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, ext),
|
registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, ext),
|
||||||
registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems),
|
registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems),
|
||||||
registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems),
|
registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems),
|
||||||
registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts)
|
registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts)
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected autoInitExtensions(register: (ext: LensExtension) => Function[]) {
|
protected autoInitExtensions(register: (ext: LensExtension) => Function[]) {
|
||||||
@ -111,43 +111,43 @@ export class ExtensionLoader {
|
|||||||
let instance = this.instances.get(extId);
|
let instance = this.instances.get(extId);
|
||||||
if (ext.isEnabled && !instance) {
|
if (ext.isEnabled && !instance) {
|
||||||
try {
|
try {
|
||||||
const LensExtensionClass: LensExtensionConstructor = this.requireExtension(ext)
|
const LensExtensionClass: LensExtensionConstructor = this.requireExtension(ext);
|
||||||
if (!LensExtensionClass) continue;
|
if (!LensExtensionClass) continue;
|
||||||
instance = new LensExtensionClass(ext);
|
instance = new LensExtensionClass(ext);
|
||||||
instance.whenEnabled(() => register(instance));
|
instance.whenEnabled(() => register(instance));
|
||||||
instance.enable();
|
instance.enable();
|
||||||
this.instances.set(extId, instance);
|
this.instances.set(extId, instance);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`[EXTENSION-LOADER]: activation extension error`, { ext, err })
|
logger.error(`[EXTENSION-LOADER]: activation extension error`, { ext, err });
|
||||||
}
|
}
|
||||||
} else if (!ext.isEnabled && instance) {
|
} else if (!ext.isEnabled && instance) {
|
||||||
try {
|
try {
|
||||||
instance.disable();
|
instance.disable();
|
||||||
this.instances.delete(extId);
|
this.instances.delete(extId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`[EXTENSION-LOADER]: deactivation extension error`, { ext, err })
|
logger.error(`[EXTENSION-LOADER]: deactivation extension error`, { ext, err });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected requireExtension(extension: InstalledExtension) {
|
protected requireExtension(extension: InstalledExtension) {
|
||||||
let extEntrypoint = ""
|
let extEntrypoint = "";
|
||||||
try {
|
try {
|
||||||
if (ipcRenderer && extension.manifest.renderer) {
|
if (ipcRenderer && extension.manifest.renderer) {
|
||||||
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.renderer))
|
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.renderer));
|
||||||
} else if (!ipcRenderer && extension.manifest.main) {
|
} else if (!ipcRenderer && extension.manifest.main) {
|
||||||
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main))
|
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main));
|
||||||
}
|
}
|
||||||
if (extEntrypoint !== "") {
|
if (extEntrypoint !== "") {
|
||||||
return __non_webpack_require__(extEntrypoint).default;
|
return __non_webpack_require__(extEntrypoint).default;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[EXTENSION-LOADER]: can't load extension main at ${extEntrypoint}: ${err}`, { extension });
|
console.error(`[EXTENSION-LOADER]: can't load extension main at ${extEntrypoint}: ${err}`, { extension });
|
||||||
console.trace(err)
|
console.trace(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,11 +159,11 @@ export class ExtensionLoader {
|
|||||||
return toJS(this.extensions, {
|
return toJS(this.extensions, {
|
||||||
exportMapsAsObjects: false,
|
exportMapsAsObjects: false,
|
||||||
recurseEverything: true,
|
recurseEverything: true,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcastExtensions() {
|
broadcastExtensions() {
|
||||||
broadcastMessage(this.requestExtensionsChannel, Array.from(this.toJSON()))
|
broadcastMessage(this.requestExtensionsChannel, Array.from(this.toJSON()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import type { LensExtensionId, LensExtensionManifest } from "./lens-extension"
|
import type { LensExtensionId, LensExtensionManifest } from "./lens-extension";
|
||||||
import path from "path"
|
import path from "path";
|
||||||
import os from "os"
|
import os from "os";
|
||||||
import fs from "fs-extra"
|
import fs from "fs-extra";
|
||||||
import child_process from "child_process";
|
import child_process from "child_process";
|
||||||
import logger from "../main/logger"
|
import logger from "../main/logger";
|
||||||
import { extensionPackagesRoot } from "./extension-loader"
|
import { extensionPackagesRoot } from "./extension-loader";
|
||||||
import { getBundledExtensions } from "../common/utils/app-version"
|
import { getBundledExtensions } from "../common/utils/app-version";
|
||||||
|
|
||||||
export interface InstalledExtension {
|
export interface InstalledExtension {
|
||||||
readonly manifest: LensExtensionManifest;
|
readonly manifest: LensExtensionManifest;
|
||||||
@ -16,26 +16,26 @@ export interface InstalledExtension {
|
|||||||
|
|
||||||
type Dependencies = {
|
type Dependencies = {
|
||||||
[name: string]: string;
|
[name: string]: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
type PackageJson = {
|
type PackageJson = {
|
||||||
dependencies: Dependencies;
|
dependencies: Dependencies;
|
||||||
}
|
};
|
||||||
|
|
||||||
export class ExtensionManager {
|
export class ExtensionManager {
|
||||||
|
|
||||||
protected bundledFolderPath: string
|
protected bundledFolderPath: string;
|
||||||
|
|
||||||
protected packagesJson: PackageJson = {
|
protected packagesJson: PackageJson = {
|
||||||
dependencies: {}
|
dependencies: {}
|
||||||
}
|
};
|
||||||
|
|
||||||
get extensionPackagesRoot() {
|
get extensionPackagesRoot() {
|
||||||
return extensionPackagesRoot()
|
return extensionPackagesRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
get inTreeTargetPath() {
|
get inTreeTargetPath() {
|
||||||
return path.join(this.extensionPackagesRoot, "extensions")
|
return path.join(this.extensionPackagesRoot, "extensions");
|
||||||
}
|
}
|
||||||
|
|
||||||
get inTreeFolderPath(): string {
|
get inTreeFolderPath(): string {
|
||||||
@ -43,7 +43,7 @@ export class ExtensionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get nodeModulesPath(): string {
|
get nodeModulesPath(): string {
|
||||||
return path.join(this.extensionPackagesRoot, "node_modules")
|
return path.join(this.extensionPackagesRoot, "node_modules");
|
||||||
}
|
}
|
||||||
|
|
||||||
get localFolderPath(): string {
|
get localFolderPath(): string {
|
||||||
@ -51,30 +51,30 @@ export class ExtensionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get npmPath() {
|
get npmPath() {
|
||||||
return __non_webpack_require__.resolve('npm/bin/npm-cli')
|
return __non_webpack_require__.resolve('npm/bin/npm-cli');
|
||||||
}
|
}
|
||||||
|
|
||||||
get packageJsonPath() {
|
get packageJsonPath() {
|
||||||
return path.join(this.extensionPackagesRoot, "package.json")
|
return path.join(this.extensionPackagesRoot, "package.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
async load(): Promise<Map<LensExtensionId, InstalledExtension>> {
|
async load(): Promise<Map<LensExtensionId, InstalledExtension>> {
|
||||||
logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot)
|
logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot);
|
||||||
if (fs.existsSync(path.join(this.extensionPackagesRoot, "package-lock.json"))) {
|
if (fs.existsSync(path.join(this.extensionPackagesRoot, "package-lock.json"))) {
|
||||||
await fs.remove(path.join(this.extensionPackagesRoot, "package-lock.json"))
|
await fs.remove(path.join(this.extensionPackagesRoot, "package-lock.json"));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await fs.access(this.inTreeFolderPath, fs.constants.W_OK)
|
await fs.access(this.inTreeFolderPath, fs.constants.W_OK);
|
||||||
this.bundledFolderPath = this.inTreeFolderPath
|
this.bundledFolderPath = this.inTreeFolderPath;
|
||||||
} catch {
|
} catch {
|
||||||
// we need to copy in-tree extensions so that we can symlink them properly on "npm install"
|
// we need to copy in-tree extensions so that we can symlink them properly on "npm install"
|
||||||
await fs.remove(this.inTreeTargetPath)
|
await fs.remove(this.inTreeTargetPath);
|
||||||
await fs.ensureDir(this.inTreeTargetPath)
|
await fs.ensureDir(this.inTreeTargetPath);
|
||||||
await fs.copy(this.inTreeFolderPath, this.inTreeTargetPath)
|
await fs.copy(this.inTreeFolderPath, this.inTreeTargetPath);
|
||||||
this.bundledFolderPath = this.inTreeTargetPath
|
this.bundledFolderPath = this.inTreeTargetPath;
|
||||||
}
|
}
|
||||||
await fs.ensureDir(this.nodeModulesPath)
|
await fs.ensureDir(this.nodeModulesPath);
|
||||||
await fs.ensureDir(this.localFolderPath)
|
await fs.ensureDir(this.localFolderPath);
|
||||||
return await this.loadExtensions();
|
return await this.loadExtensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,16 +82,16 @@ export class ExtensionManager {
|
|||||||
let manifestJson: LensExtensionManifest;
|
let manifestJson: LensExtensionManifest;
|
||||||
try {
|
try {
|
||||||
fs.accessSync(manifestPath, fs.constants.F_OK); // check manifest file for existence
|
fs.accessSync(manifestPath, fs.constants.F_OK); // check manifest file for existence
|
||||||
manifestJson = __non_webpack_require__(manifestPath)
|
manifestJson = __non_webpack_require__(manifestPath);
|
||||||
this.packagesJson.dependencies[manifestJson.name] = path.dirname(manifestPath)
|
this.packagesJson.dependencies[manifestJson.name] = path.dirname(manifestPath);
|
||||||
|
|
||||||
logger.info("[EXTENSION-MANAGER] installed extension " + manifestJson.name)
|
logger.info("[EXTENSION-MANAGER] installed extension " + manifestJson.name);
|
||||||
return {
|
return {
|
||||||
manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"),
|
manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"),
|
||||||
manifest: manifestJson,
|
manifest: manifestJson,
|
||||||
isBundled: isBundled,
|
isBundled: isBundled,
|
||||||
isEnabled: isBundled,
|
isEnabled: isBundled,
|
||||||
}
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`[EXTENSION-MANAGER]: can't install extension at ${manifestPath}: ${err}`, { manifestJson });
|
logger.error(`[EXTENSION-MANAGER]: can't install extension at ${manifestPath}: ${err}`, { manifestJson });
|
||||||
}
|
}
|
||||||
@ -102,65 +102,65 @@ export class ExtensionManager {
|
|||||||
const child = child_process.fork(this.npmPath, ["install", "--silent", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock"], {
|
const child = child_process.fork(this.npmPath, ["install", "--silent", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock"], {
|
||||||
cwd: extensionPackagesRoot(),
|
cwd: extensionPackagesRoot(),
|
||||||
silent: true
|
silent: true
|
||||||
})
|
});
|
||||||
child.on("close", () => {
|
child.on("close", () => {
|
||||||
resolve()
|
resolve();
|
||||||
})
|
});
|
||||||
child.on("error", (err) => {
|
child.on("error", (err) => {
|
||||||
reject(err)
|
reject(err);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadExtensions() {
|
async loadExtensions() {
|
||||||
const bundledExtensions = await this.loadBundledExtensions()
|
const bundledExtensions = await this.loadBundledExtensions();
|
||||||
const localExtensions = await this.loadFromFolder(this.localFolderPath)
|
const localExtensions = await this.loadFromFolder(this.localFolderPath);
|
||||||
await fs.writeFile(path.join(this.packageJsonPath), JSON.stringify(this.packagesJson, null, 2), { mode: 0o600 })
|
await fs.writeFile(path.join(this.packageJsonPath), JSON.stringify(this.packagesJson, null, 2), { mode: 0o600 });
|
||||||
await this.installPackages()
|
await this.installPackages();
|
||||||
const extensions = bundledExtensions.concat(localExtensions)
|
const extensions = bundledExtensions.concat(localExtensions);
|
||||||
return new Map(extensions.map(ext => [ext.manifestPath, ext]));
|
return new Map(extensions.map(ext => [ext.manifestPath, ext]));
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadBundledExtensions() {
|
async loadBundledExtensions() {
|
||||||
const extensions: InstalledExtension[] = []
|
const extensions: InstalledExtension[] = [];
|
||||||
const folderPath = this.bundledFolderPath
|
const folderPath = this.bundledFolderPath;
|
||||||
const bundledExtensions = getBundledExtensions()
|
const bundledExtensions = getBundledExtensions();
|
||||||
const paths = await fs.readdir(folderPath);
|
const paths = await fs.readdir(folderPath);
|
||||||
for (const fileName of paths) {
|
for (const fileName of paths) {
|
||||||
if (!bundledExtensions.includes(fileName)) {
|
if (!bundledExtensions.includes(fileName)) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
const absPath = path.resolve(folderPath, fileName);
|
const absPath = path.resolve(folderPath, fileName);
|
||||||
const manifestPath = path.resolve(absPath, "package.json");
|
const manifestPath = path.resolve(absPath, "package.json");
|
||||||
const ext = await this.getByManifest(manifestPath, { isBundled: true }).catch(() => null)
|
const ext = await this.getByManifest(manifestPath, { isBundled: true }).catch(() => null);
|
||||||
if (ext) {
|
if (ext) {
|
||||||
extensions.push(ext)
|
extensions.push(ext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.debug(`[EXTENSION-MANAGER]: ${extensions.length} extensions loaded`, { folderPath, extensions });
|
logger.debug(`[EXTENSION-MANAGER]: ${extensions.length} extensions loaded`, { folderPath, extensions });
|
||||||
return extensions
|
return extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
|
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
|
||||||
const bundledExtensions = getBundledExtensions()
|
const bundledExtensions = getBundledExtensions();
|
||||||
const extensions: InstalledExtension[] = []
|
const extensions: InstalledExtension[] = [];
|
||||||
const paths = await fs.readdir(folderPath);
|
const paths = await fs.readdir(folderPath);
|
||||||
for (const fileName of paths) {
|
for (const fileName of paths) {
|
||||||
if (bundledExtensions.includes(fileName)) { // do no allow to override bundled extensions
|
if (bundledExtensions.includes(fileName)) { // do no allow to override bundled extensions
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
const absPath = path.resolve(folderPath, fileName);
|
const absPath = path.resolve(folderPath, fileName);
|
||||||
if (!fs.existsSync(absPath)) {
|
if (!fs.existsSync(absPath)) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
const lstat = await fs.lstat(absPath)
|
const lstat = await fs.lstat(absPath);
|
||||||
if (!lstat.isDirectory() && !lstat.isSymbolicLink()) { // skip non-directories
|
if (!lstat.isDirectory() && !lstat.isSymbolicLink()) { // skip non-directories
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
const manifestPath = path.resolve(absPath, "package.json");
|
const manifestPath = path.resolve(absPath, "package.json");
|
||||||
const ext = await this.getByManifest(manifestPath).catch(() => null)
|
const ext = await this.getByManifest(manifestPath).catch(() => null);
|
||||||
if (ext) {
|
if (ext) {
|
||||||
extensions.push(ext)
|
extensions.push(ext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,4 +169,4 @@ export class ExtensionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extensionManager = new ExtensionManager()
|
export const extensionManager = new ExtensionManager();
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
import { BaseStore } from "../common/base-store"
|
import { BaseStore } from "../common/base-store";
|
||||||
import * as path from "path"
|
import * as path from "path";
|
||||||
import { LensExtension } from "./lens-extension"
|
import { LensExtension } from "./lens-extension";
|
||||||
|
|
||||||
export abstract class ExtensionStore<T> extends BaseStore<T> {
|
export abstract class ExtensionStore<T> extends BaseStore<T> {
|
||||||
protected extension: LensExtension
|
protected extension: LensExtension;
|
||||||
|
|
||||||
async loadExtension(extension: LensExtension) {
|
async loadExtension(extension: LensExtension) {
|
||||||
this.extension = extension
|
this.extension = extension;
|
||||||
return super.load()
|
return super.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
if (!this.extension) { return }
|
if (!this.extension) { return; }
|
||||||
return super.load()
|
return super.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected cwd() {
|
protected cwd() {
|
||||||
return path.join(super.cwd(), "extension-store", this.extension.name)
|
return path.join(super.cwd(), "extension-store", this.extension.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { LensExtensionId } from "./lens-extension";
|
import type { LensExtensionId } from "./lens-extension";
|
||||||
import type { ExtensionLoader } from "./extension-loader";
|
import type { ExtensionLoader } from "./extension-loader";
|
||||||
import { BaseStore } from "../common/base-store"
|
import { BaseStore } from "../common/base-store";
|
||||||
import { action, observable, reaction, toJS } from "mobx";
|
import { action, observable, reaction, toJS } from "mobx";
|
||||||
|
|
||||||
export interface LensExtensionsStoreModel {
|
export interface LensExtensionsStoreModel {
|
||||||
@ -25,9 +25,9 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
|||||||
return Array.from(extensionLoader.userExtensions).reduce((state, [extId, ext]) => {
|
return Array.from(extensionLoader.userExtensions).reduce((state, [extId, ext]) => {
|
||||||
state[extId] = {
|
state[extId] = {
|
||||||
enabled: ext.isEnabled,
|
enabled: ext.isEnabled,
|
||||||
}
|
};
|
||||||
return state;
|
return state;
|
||||||
}, state)
|
}, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
async manageState(extensionLoader: ExtensionLoader) {
|
async manageState(extensionLoader: ExtensionLoader) {
|
||||||
@ -46,13 +46,13 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
|||||||
if (ext && !ext.isBundled) {
|
if (ext && !ext.isBundled) {
|
||||||
ext.isEnabled = state.enabled;
|
ext.isEnabled = state.enabled;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
// save state on change `extension.isEnabled`
|
// save state on change `extension.isEnabled`
|
||||||
reaction(() => this.getState(extensionLoader), extensionsState => {
|
reaction(() => this.getState(extensionLoader), extensionsState => {
|
||||||
this.state.merge(extensionsState)
|
this.state.merge(extensionsState);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled(extId: LensExtensionId) {
|
isEnabled(extId: LensExtensionId) {
|
||||||
@ -70,7 +70,7 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
|||||||
extensions: this.state.toJSON(),
|
extensions: this.state.toJSON(),
|
||||||
}, {
|
}, {
|
||||||
recurseEverything: true
|
recurseEverything: true
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export * from "./registrations"
|
export * from "./registrations";
|
||||||
@ -1,8 +1,8 @@
|
|||||||
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../registries/app-preference-registry"
|
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../registries/app-preference-registry";
|
||||||
export type { ClusterFeatureRegistration, ClusterFeatureComponents } from "../registries/cluster-feature-registry"
|
export type { ClusterFeatureRegistration, ClusterFeatureComponents } from "../registries/cluster-feature-registry";
|
||||||
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry"
|
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
|
||||||
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry"
|
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry";
|
||||||
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry"
|
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry";
|
||||||
export type { PageRegistration, PageComponents } from "../registries/page-registry"
|
export type { PageRegistration, PageComponents } from "../registries/page-registry";
|
||||||
export type { PageMenuRegistration, PageMenuComponents } from "../registries/page-menu-registry"
|
export type { PageMenuRegistration, PageMenuComponents } from "../registries/page-menu-registry";
|
||||||
export type { StatusBarRegistration } from "../registries/status-bar-registry"
|
export type { StatusBarRegistration } from "../registries/status-bar-registry";
|
||||||
@ -21,9 +21,9 @@ export class LensExtension {
|
|||||||
@observable private isEnabled = false;
|
@observable private isEnabled = false;
|
||||||
|
|
||||||
constructor({ manifest, manifestPath, isBundled }: InstalledExtension) {
|
constructor({ manifest, manifestPath, isBundled }: InstalledExtension) {
|
||||||
this.manifest = manifest
|
this.manifest = manifest;
|
||||||
this.manifestPath = manifestPath
|
this.manifestPath = manifestPath;
|
||||||
this.isBundled = !!isBundled
|
this.isBundled = !!isBundled;
|
||||||
}
|
}
|
||||||
|
|
||||||
get id(): LensExtensionId {
|
get id(): LensExtensionId {
|
||||||
@ -31,15 +31,15 @@ export class LensExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
return this.manifest.name
|
return this.manifest.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
get version() {
|
get version() {
|
||||||
return this.manifest.version
|
return this.manifest.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
get description() {
|
get description() {
|
||||||
return this.manifest.description
|
return this.manifest.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -60,18 +60,18 @@ export class LensExtension {
|
|||||||
|
|
||||||
toggle(enable?: boolean) {
|
toggle(enable?: boolean) {
|
||||||
if (typeof enable === "boolean") {
|
if (typeof enable === "boolean") {
|
||||||
enable ? this.enable() : this.disable()
|
enable ? this.enable() : this.disable();
|
||||||
} else {
|
} else {
|
||||||
this.isEnabled ? this.disable() : this.enable()
|
this.isEnabled ? this.disable() : this.enable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async whenEnabled(handlers: () => Function[]) {
|
async whenEnabled(handlers: () => Function[]) {
|
||||||
const disposers: Function[] = [];
|
const disposers: Function[] = [];
|
||||||
const unregisterHandlers = () => {
|
const unregisterHandlers = () => {
|
||||||
disposers.forEach(unregister => unregister())
|
disposers.forEach(unregister => unregister());
|
||||||
disposers.length = 0;
|
disposers.length = 0;
|
||||||
}
|
};
|
||||||
const cancelReaction = reaction(() => this.isEnabled, isEnabled => {
|
const cancelReaction = reaction(() => this.isEnabled, isEnabled => {
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
disposers.push(...handlers());
|
disposers.push(...handlers());
|
||||||
@ -80,11 +80,11 @@ export class LensExtension {
|
|||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
fireImmediately: true
|
fireImmediately: true
|
||||||
})
|
});
|
||||||
return () => {
|
return () => {
|
||||||
unregisterHandlers();
|
unregisterHandlers();
|
||||||
cancelReaction();
|
cancelReaction();
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onActivate() {
|
protected onActivate() {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import type { MenuRegistration } from "./registries/menu-registry";
|
import type { MenuRegistration } from "./registries/menu-registry";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { LensExtension } from "./lens-extension"
|
import { LensExtension } from "./lens-extension";
|
||||||
import { WindowManager } from "../main/window-manager";
|
import { WindowManager } from "../main/window-manager";
|
||||||
import { getExtensionPageUrl } from "./registries/page-registry"
|
import { getExtensionPageUrl } from "./registries/page-registry";
|
||||||
|
|
||||||
export class LensMainExtension extends LensExtension {
|
export class LensMainExtension extends LensExtension {
|
||||||
@observable.shallow appMenus: MenuRegistration[] = []
|
@observable.shallow appMenus: MenuRegistration[] = [];
|
||||||
|
|
||||||
async navigate<P extends object>(pageId?: string, params?: P, frameId?: number) {
|
async navigate<P extends object>(pageId?: string, params?: P, frameId?: number) {
|
||||||
const windowManager = WindowManager.getInstance<WindowManager>();
|
const windowManager = WindowManager.getInstance<WindowManager>();
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries"
|
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { LensExtension } from "./lens-extension"
|
import { LensExtension } from "./lens-extension";
|
||||||
import { getExtensionPageUrl } from "./registries/page-registry"
|
import { getExtensionPageUrl } from "./registries/page-registry";
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension {
|
export class LensRendererExtension extends LensExtension {
|
||||||
@observable.shallow globalPages: PageRegistration[] = []
|
@observable.shallow globalPages: PageRegistration[] = [];
|
||||||
@observable.shallow clusterPages: PageRegistration[] = []
|
@observable.shallow clusterPages: PageRegistration[] = [];
|
||||||
@observable.shallow globalPageMenus: PageMenuRegistration[] = []
|
@observable.shallow globalPageMenus: PageMenuRegistration[] = [];
|
||||||
@observable.shallow clusterPageMenus: PageMenuRegistration[] = []
|
@observable.shallow clusterPageMenus: PageMenuRegistration[] = [];
|
||||||
@observable.shallow kubeObjectStatusTexts: KubeObjectStatusRegistration[] = []
|
@observable.shallow kubeObjectStatusTexts: KubeObjectStatusRegistration[] = [];
|
||||||
@observable.shallow appPreferences: AppPreferenceRegistration[] = []
|
@observable.shallow appPreferences: AppPreferenceRegistration[] = [];
|
||||||
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = []
|
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = [];
|
||||||
@observable.shallow statusBarItems: StatusBarRegistration[] = []
|
@observable.shallow statusBarItems: StatusBarRegistration[] = [];
|
||||||
@observable.shallow kubeObjectDetailItems: KubeObjectDetailRegistration[] = []
|
@observable.shallow kubeObjectDetailItems: KubeObjectDetailRegistration[] = [];
|
||||||
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = []
|
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = [];
|
||||||
|
|
||||||
async navigate<P extends object>(pageId?: string, params?: P) {
|
async navigate<P extends object>(pageId?: string, params?: P) {
|
||||||
const { navigate } = await import("../renderer/navigation");
|
const { navigate } = await import("../renderer/navigation");
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { getExtensionPageUrl, globalPageRegistry, PageRegistration } from "../page-registry"
|
import { getExtensionPageUrl, globalPageRegistry, PageRegistration } from "../page-registry";
|
||||||
import { LensExtension } from "../../lens-extension"
|
import { LensExtension } from "../../lens-extension";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
let ext: LensExtension = null
|
let ext: LensExtension = null;
|
||||||
|
|
||||||
describe("getPageUrl", () => {
|
describe("getPageUrl", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -14,25 +14,25 @@ describe("getPageUrl", () => {
|
|||||||
manifestPath: "/this/is/fake/package.json",
|
manifestPath: "/this/is/fake/package.json",
|
||||||
isBundled: false,
|
isBundled: false,
|
||||||
isEnabled: true
|
isEnabled: true
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
it("returns a page url for extension", () => {
|
it("returns a page url for extension", () => {
|
||||||
expect(getExtensionPageUrl({ extensionId: ext.name })).toBe("/extension/foo-bar")
|
expect(getExtensionPageUrl({ extensionId: ext.name })).toBe("/extension/foo-bar");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("allows to pass base url as parameter", () => {
|
it("allows to pass base url as parameter", () => {
|
||||||
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "/test" })).toBe("/extension/foo-bar/test")
|
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "/test" })).toBe("/extension/foo-bar/test");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("removes @", () => {
|
it("removes @", () => {
|
||||||
expect(getExtensionPageUrl({ extensionId: "@foo/bar" })).toBe("/extension/foo-bar")
|
expect(getExtensionPageUrl({ extensionId: "@foo/bar" })).toBe("/extension/foo-bar");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("adds / prefix", () => {
|
it("adds / prefix", () => {
|
||||||
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "test" })).toBe("/extension/foo-bar/test")
|
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "test" })).toBe("/extension/foo-bar/test");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("globalPageRegistry", () => {
|
describe("globalPageRegistry", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -44,7 +44,7 @@ describe("globalPageRegistry", () => {
|
|||||||
manifestPath: "/this/is/fake/package.json",
|
manifestPath: "/this/is/fake/package.json",
|
||||||
isBundled: false,
|
isBundled: false,
|
||||||
isEnabled: true
|
isEnabled: true
|
||||||
})
|
});
|
||||||
globalPageRegistry.add([
|
globalPageRegistry.add([
|
||||||
{
|
{
|
||||||
id: "test-page",
|
id: "test-page",
|
||||||
@ -63,12 +63,12 @@ describe("globalPageRegistry", () => {
|
|||||||
Page: () => React.createElement('Default')
|
Page: () => React.createElement('Default')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
], ext)
|
], ext);
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("getByPageMenuTarget", () => {
|
describe("getByPageMenuTarget", () => {
|
||||||
it("matching to first registered page without id", () => {
|
it("matching to first registered page without id", () => {
|
||||||
const page = globalPageRegistry.getByPageMenuTarget({ extensionId: ext.name })
|
const page = globalPageRegistry.getByPageMenuTarget({ extensionId: ext.name });
|
||||||
expect(page.id).toEqual(undefined);
|
expect(page.id).toEqual(undefined);
|
||||||
expect(page.extensionId).toEqual(ext.name);
|
expect(page.extensionId).toEqual(ext.name);
|
||||||
expect(page.routePath).toEqual(getExtensionPageUrl({ extensionId: ext.name }));
|
expect(page.routePath).toEqual(getExtensionPageUrl({ extensionId: ext.name }));
|
||||||
@ -78,16 +78,16 @@ describe("globalPageRegistry", () => {
|
|||||||
const page = globalPageRegistry.getByPageMenuTarget({
|
const page = globalPageRegistry.getByPageMenuTarget({
|
||||||
pageId: "test-page",
|
pageId: "test-page",
|
||||||
extensionId: ext.name
|
extensionId: ext.name
|
||||||
})
|
});
|
||||||
expect(page.id).toEqual("test-page")
|
expect(page.id).toEqual("test-page");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("returns null if target not found", () => {
|
it("returns null if target not found", () => {
|
||||||
const page = globalPageRegistry.getByPageMenuTarget({
|
const page = globalPageRegistry.getByPageMenuTarget({
|
||||||
pageId: "wrong-page",
|
pageId: "wrong-page",
|
||||||
extensionId: ext.name
|
extensionId: ext.name
|
||||||
})
|
});
|
||||||
expect(page).toBeNull()
|
expect(page).toBeNull();
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type React from "react"
|
import type React from "react";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
|
||||||
export interface AppPreferenceComponents {
|
export interface AppPreferenceComponents {
|
||||||
@ -14,4 +14,4 @@ export interface AppPreferenceRegistration {
|
|||||||
export class AppPreferenceRegistry extends BaseRegistry<AppPreferenceRegistration> {
|
export class AppPreferenceRegistry extends BaseRegistry<AppPreferenceRegistration> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appPreferenceRegistry = new AppPreferenceRegistry()
|
export const appPreferenceRegistry = new AppPreferenceRegistry();
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export class BaseRegistry<T = object, I extends T = T> {
|
|||||||
add(items: T | T[], ext?: LensExtension): () => void; // allow method overloading with required "ext"
|
add(items: T | T[], ext?: LensExtension): () => void; // allow method overloading with required "ext"
|
||||||
@action
|
@action
|
||||||
add(items: T | T[]) {
|
add(items: T | T[]) {
|
||||||
const normalizedItems = (Array.isArray(items) ? items : [items])
|
const normalizedItems = (Array.isArray(items) ? items : [items]);
|
||||||
this.items.push(...normalizedItems);
|
this.items.push(...normalizedItems);
|
||||||
return () => this.remove(...normalizedItems);
|
return () => this.remove(...normalizedItems);
|
||||||
}
|
}
|
||||||
@ -21,6 +21,6 @@ export class BaseRegistry<T = object, I extends T = T> {
|
|||||||
remove(...items: T[]) {
|
remove(...items: T[]) {
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
this.items.remove(item); // works because of {deep: false};
|
this.items.remove(item); // works because of {deep: false};
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type React from "react"
|
import type React from "react";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
import { ClusterFeature } from "../cluster-feature";
|
import { ClusterFeature } from "../cluster-feature";
|
||||||
|
|
||||||
@ -15,4 +15,4 @@ export interface ClusterFeatureRegistration {
|
|||||||
export class ClusterFeatureRegistry extends BaseRegistry<ClusterFeatureRegistration> {
|
export class ClusterFeatureRegistry extends BaseRegistry<ClusterFeatureRegistration> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clusterFeatureRegistry = new ClusterFeatureRegistry()
|
export const clusterFeatureRegistry = new ClusterFeatureRegistry();
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
// All registries managed by extensions api
|
// All registries managed by extensions api
|
||||||
|
|
||||||
export * from "./page-registry"
|
export * from "./page-registry";
|
||||||
export * from "./page-menu-registry"
|
export * from "./page-menu-registry";
|
||||||
export * from "./menu-registry"
|
export * from "./menu-registry";
|
||||||
export * from "./app-preference-registry"
|
export * from "./app-preference-registry";
|
||||||
export * from "./status-bar-registry"
|
export * from "./status-bar-registry";
|
||||||
export * from "./kube-object-detail-registry";
|
export * from "./kube-object-detail-registry";
|
||||||
export * from "./kube-object-menu-registry";
|
export * from "./kube-object-menu-registry";
|
||||||
export * from "./cluster-feature-registry"
|
export * from "./cluster-feature-registry";
|
||||||
export * from "./kube-object-status-registry"
|
export * from "./kube-object-status-registry";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
|
||||||
export interface KubeObjectDetailComponents {
|
export interface KubeObjectDetailComponents {
|
||||||
@ -15,15 +15,15 @@ export interface KubeObjectDetailRegistration {
|
|||||||
export class KubeObjectDetailRegistry extends BaseRegistry<KubeObjectDetailRegistration> {
|
export class KubeObjectDetailRegistry extends BaseRegistry<KubeObjectDetailRegistration> {
|
||||||
getItemsForKind(kind: string, apiVersion: string) {
|
getItemsForKind(kind: string, apiVersion: string) {
|
||||||
const items = this.getItems().filter((item) => {
|
const items = this.getItems().filter((item) => {
|
||||||
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
return item.kind === kind && item.apiVersions.includes(apiVersion);
|
||||||
}).map((item) => {
|
}).map((item) => {
|
||||||
if (item.priority === null) {
|
if (item.priority === null) {
|
||||||
item.priority = 50
|
item.priority = 50;
|
||||||
}
|
}
|
||||||
return item
|
return item;
|
||||||
})
|
});
|
||||||
return items.sort((a, b) => b.priority - a.priority)
|
return items.sort((a, b) => b.priority - a.priority);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const kubeObjectDetailRegistry = new KubeObjectDetailRegistry()
|
export const kubeObjectDetailRegistry = new KubeObjectDetailRegistry();
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
|
||||||
export interface KubeObjectMenuComponents {
|
export interface KubeObjectMenuComponents {
|
||||||
@ -14,9 +14,9 @@ export interface KubeObjectMenuRegistration {
|
|||||||
export class KubeObjectMenuRegistry extends BaseRegistry<KubeObjectMenuRegistration> {
|
export class KubeObjectMenuRegistry extends BaseRegistry<KubeObjectMenuRegistration> {
|
||||||
getItemsForKind(kind: string, apiVersion: string) {
|
getItemsForKind(kind: string, apiVersion: string) {
|
||||||
return this.getItems().filter((item) => {
|
return this.getItems().filter((item) => {
|
||||||
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
return item.kind === kind && item.apiVersions.includes(apiVersion);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const kubeObjectMenuRegistry = new KubeObjectMenuRegistry()
|
export const kubeObjectMenuRegistry = new KubeObjectMenuRegistry();
|
||||||
|
|||||||
@ -10,8 +10,8 @@ export interface KubeObjectStatusRegistration {
|
|||||||
export class KubeObjectStatusRegistry extends BaseRegistry<KubeObjectStatusRegistration> {
|
export class KubeObjectStatusRegistry extends BaseRegistry<KubeObjectStatusRegistration> {
|
||||||
getItemsForKind(kind: string, apiVersion: string) {
|
getItemsForKind(kind: string, apiVersion: string) {
|
||||||
return this.getItems().filter((item) => {
|
return this.getItems().filter((item) => {
|
||||||
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
return item.kind === kind && item.apiVersions.includes(apiVersion);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,8 +29,8 @@ export class PageMenuRegistry extends BaseRegistry<PageMenuRegistration, Require
|
|||||||
extensionId: ext.name,
|
extensionId: ext.name,
|
||||||
...(menuItem.target || {}),
|
...(menuItem.target || {}),
|
||||||
};
|
};
|
||||||
return menuItem
|
return menuItem;
|
||||||
})
|
});
|
||||||
return super.add(normalizedItems);
|
return super.add(normalizedItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export interface PageComponents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeExtensionName(name: string) {
|
export function sanitizeExtensionName(name: string) {
|
||||||
return name.replace("@", "").replace("/", "-")
|
return name.replace("@", "").replace("/", "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExtensionPageUrl<P extends object>({ extensionId, pageId = "", params }: PageMenuTarget<P>): string {
|
export function getExtensionPageUrl<P extends object>({ extensionId, pageId = "", params }: PageMenuTarget<P>): string {
|
||||||
@ -68,13 +68,13 @@ export class PageRegistry extends BaseRegistry<PageRegistration, RegisteredPage>
|
|||||||
...page,
|
...page,
|
||||||
extensionId: ext.name,
|
extensionId: ext.name,
|
||||||
routePath: getExtensionPageUrl({ extensionId: ext.name, pageId: page.id ?? page.routePath }),
|
routePath: getExtensionPageUrl({ extensionId: ext.name, pageId: page.id ?? page.routePath }),
|
||||||
}))
|
}));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`[EXTENSION]: page-registration failed`, {
|
logger.error(`[EXTENSION]: page-registration failed`, {
|
||||||
items,
|
items,
|
||||||
extension: ext,
|
extension: ext,
|
||||||
error: String(err),
|
error: String(err),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
return super.add(registeredPages);
|
return super.add(registeredPages);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,36 +1,36 @@
|
|||||||
// Common UI components
|
// Common UI components
|
||||||
|
|
||||||
// layouts
|
// layouts
|
||||||
export * from "../../renderer/components/layout/page-layout"
|
export * from "../../renderer/components/layout/page-layout";
|
||||||
export * from "../../renderer/components/layout/wizard-layout"
|
export * from "../../renderer/components/layout/wizard-layout";
|
||||||
export * from "../../renderer/components/layout/tab-layout"
|
export * from "../../renderer/components/layout/tab-layout";
|
||||||
|
|
||||||
// form-controls
|
// form-controls
|
||||||
export * from "../../renderer/components/button"
|
export * from "../../renderer/components/button";
|
||||||
export * from "../../renderer/components/checkbox"
|
export * from "../../renderer/components/checkbox";
|
||||||
export * from "../../renderer/components/radio"
|
export * from "../../renderer/components/radio";
|
||||||
export * from "../../renderer/components/select"
|
export * from "../../renderer/components/select";
|
||||||
export * from "../../renderer/components/slider"
|
export * from "../../renderer/components/slider";
|
||||||
export * from "../../renderer/components/input/input"
|
export * from "../../renderer/components/input/input";
|
||||||
|
|
||||||
// other components
|
// other components
|
||||||
export * from "../../renderer/components/icon"
|
export * from "../../renderer/components/icon";
|
||||||
export * from "../../renderer/components/tooltip"
|
export * from "../../renderer/components/tooltip";
|
||||||
export * from "../../renderer/components/tabs"
|
export * from "../../renderer/components/tabs";
|
||||||
export * from "../../renderer/components/table"
|
export * from "../../renderer/components/table";
|
||||||
export * from "../../renderer/components/badge"
|
export * from "../../renderer/components/badge";
|
||||||
export * from "../../renderer/components/drawer"
|
export * from "../../renderer/components/drawer";
|
||||||
export * from "../../renderer/components/dialog"
|
export * from "../../renderer/components/dialog";
|
||||||
export * from "../../renderer/components/confirm-dialog";
|
export * from "../../renderer/components/confirm-dialog";
|
||||||
export * from "../../renderer/components/line-progress"
|
export * from "../../renderer/components/line-progress";
|
||||||
export * from "../../renderer/components/menu"
|
export * from "../../renderer/components/menu";
|
||||||
export * from "../../renderer/components/notifications"
|
export * from "../../renderer/components/notifications";
|
||||||
export * from "../../renderer/components/spinner"
|
export * from "../../renderer/components/spinner";
|
||||||
export * from "../../renderer/components/stepper"
|
export * from "../../renderer/components/stepper";
|
||||||
|
|
||||||
// kube helpers
|
// kube helpers
|
||||||
export * from "../../renderer/components/kube-object"
|
export * from "../../renderer/components/kube-object";
|
||||||
export * from "../../renderer/components/+events/kube-event-details"
|
export * from "../../renderer/components/+events/kube-event-details";
|
||||||
|
|
||||||
// specific exports
|
// specific exports
|
||||||
export * from "../../renderer/components/status-brick";
|
export * from "../../renderer/components/status-brick";
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
// Lens-extensions apis, required in renderer process runtime
|
// Lens-extensions apis, required in renderer process runtime
|
||||||
|
|
||||||
// APIs
|
// APIs
|
||||||
import * as Component from "./components"
|
import * as Component from "./components";
|
||||||
import * as K8sApi from "./k8s-api"
|
import * as K8sApi from "./k8s-api";
|
||||||
import * as Navigation from "./navigation"
|
import * as Navigation from "./navigation";
|
||||||
import * as Theme from "./theming"
|
import * as Theme from "./theming";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Component,
|
Component,
|
||||||
K8sApi,
|
K8sApi,
|
||||||
Navigation,
|
Navigation,
|
||||||
Theme,
|
Theme,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
export { isAllowedResource } from "../../common/rbac"
|
export { isAllowedResource } from "../../common/rbac";
|
||||||
export { apiManager } from "../../renderer/api/api-manager";
|
export { apiManager } from "../../renderer/api/api-manager";
|
||||||
export { KubeObjectStore } from "../../renderer/kube-object.store"
|
export { KubeObjectStore } from "../../renderer/kube-object.store";
|
||||||
export { KubeApi, forCluster, IKubeApiCluster } from "../../renderer/api/kube-api";
|
export { KubeApi, forCluster, IKubeApiCluster } from "../../renderer/api/kube-api";
|
||||||
export { KubeObject } from "../../renderer/api/kube-object";
|
export { KubeObject } from "../../renderer/api/kube-object";
|
||||||
export { Pod, podsApi, PodsApi, IPodContainer, IPodContainerStatus } from "../../renderer/api/endpoints";
|
export { Pod, podsApi, PodsApi, IPodContainer, IPodContainerStatus } from "../../renderer/api/endpoints";
|
||||||
@ -31,33 +31,33 @@ export { RoleBinding, roleBindingApi } from "../../renderer/api/endpoints";
|
|||||||
export { ClusterRole, clusterRoleApi } from "../../renderer/api/endpoints";
|
export { ClusterRole, clusterRoleApi } from "../../renderer/api/endpoints";
|
||||||
export { ClusterRoleBinding, clusterRoleBindingApi } from "../../renderer/api/endpoints";
|
export { ClusterRoleBinding, clusterRoleBindingApi } from "../../renderer/api/endpoints";
|
||||||
export { CustomResourceDefinition, crdApi } from "../../renderer/api/endpoints";
|
export { CustomResourceDefinition, crdApi } from "../../renderer/api/endpoints";
|
||||||
export { KubeObjectStatus, KubeObjectStatusLevel } from "./kube-object-status"
|
export { KubeObjectStatus, KubeObjectStatusLevel } from "./kube-object-status";
|
||||||
|
|
||||||
// stores
|
// stores
|
||||||
export type { EventStore } from "../../renderer/components/+events/event.store"
|
export type { EventStore } from "../../renderer/components/+events/event.store";
|
||||||
export type { PodsStore } from "../../renderer/components/+workloads-pods/pods.store"
|
export type { PodsStore } from "../../renderer/components/+workloads-pods/pods.store";
|
||||||
export type { NodesStore } from "../../renderer/components/+nodes/nodes.store"
|
export type { NodesStore } from "../../renderer/components/+nodes/nodes.store";
|
||||||
export type { DeploymentStore } from "../../renderer/components/+workloads-deployments/deployments.store"
|
export type { DeploymentStore } from "../../renderer/components/+workloads-deployments/deployments.store";
|
||||||
export type { DaemonSetStore } from "../../renderer/components/+workloads-daemonsets/daemonsets.store"
|
export type { DaemonSetStore } from "../../renderer/components/+workloads-daemonsets/daemonsets.store";
|
||||||
export type { StatefulSetStore } from "../../renderer/components/+workloads-statefulsets/statefulset.store"
|
export type { StatefulSetStore } from "../../renderer/components/+workloads-statefulsets/statefulset.store";
|
||||||
export type { JobStore } from "../../renderer/components/+workloads-jobs/job.store"
|
export type { JobStore } from "../../renderer/components/+workloads-jobs/job.store";
|
||||||
export type { CronJobStore } from "../../renderer/components/+workloads-cronjobs/cronjob.store"
|
export type { CronJobStore } from "../../renderer/components/+workloads-cronjobs/cronjob.store";
|
||||||
export type { ConfigMapsStore } from "../../renderer/components/+config-maps/config-maps.store"
|
export type { ConfigMapsStore } from "../../renderer/components/+config-maps/config-maps.store";
|
||||||
export type { SecretsStore } from "../../renderer/components/+config-secrets/secrets.store"
|
export type { SecretsStore } from "../../renderer/components/+config-secrets/secrets.store";
|
||||||
export type { ReplicaSetStore } from "../../renderer/components/+workloads-replicasets/replicasets.store"
|
export type { ReplicaSetStore } from "../../renderer/components/+workloads-replicasets/replicasets.store";
|
||||||
export type { ResourceQuotasStore } from "../../renderer/components/+config-resource-quotas/resource-quotas.store"
|
export type { ResourceQuotasStore } from "../../renderer/components/+config-resource-quotas/resource-quotas.store";
|
||||||
export type { HPAStore } from "../../renderer/components/+config-autoscalers/hpa.store"
|
export type { HPAStore } from "../../renderer/components/+config-autoscalers/hpa.store";
|
||||||
export type { PodDisruptionBudgetsStore } from "../../renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store"
|
export type { PodDisruptionBudgetsStore } from "../../renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store";
|
||||||
export type { ServiceStore } from "../../renderer/components/+network-services/services.store"
|
export type { ServiceStore } from "../../renderer/components/+network-services/services.store";
|
||||||
export type { EndpointStore } from "../../renderer/components/+network-endpoints/endpoints.store"
|
export type { EndpointStore } from "../../renderer/components/+network-endpoints/endpoints.store";
|
||||||
export type { IngressStore } from "../../renderer/components/+network-ingresses/ingress.store"
|
export type { IngressStore } from "../../renderer/components/+network-ingresses/ingress.store";
|
||||||
export type { NetworkPolicyStore } from "../../renderer/components/+network-policies/network-policy.store"
|
export type { NetworkPolicyStore } from "../../renderer/components/+network-policies/network-policy.store";
|
||||||
export type { PersistentVolumesStore } from "../../renderer/components/+storage-volumes/volumes.store"
|
export type { PersistentVolumesStore } from "../../renderer/components/+storage-volumes/volumes.store";
|
||||||
export type { VolumeClaimStore } from "../../renderer/components/+storage-volume-claims/volume-claim.store"
|
export type { VolumeClaimStore } from "../../renderer/components/+storage-volume-claims/volume-claim.store";
|
||||||
export type { StorageClassStore } from "../../renderer/components/+storage-classes/storage-class.store"
|
export type { StorageClassStore } from "../../renderer/components/+storage-classes/storage-class.store";
|
||||||
export type { NamespaceStore } from "../../renderer/components/+namespaces/namespace.store"
|
export type { NamespaceStore } from "../../renderer/components/+namespaces/namespace.store";
|
||||||
export type { ServiceAccountsStore } from "../../renderer/components/+user-management-service-accounts/service-accounts.store"
|
export type { ServiceAccountsStore } from "../../renderer/components/+user-management-service-accounts/service-accounts.store";
|
||||||
export type { RolesStore } from "../../renderer/components/+user-management-roles/roles.store"
|
export type { RolesStore } from "../../renderer/components/+user-management-roles/roles.store";
|
||||||
export type { RoleBindingsStore } from "../../renderer/components/+user-management-roles-bindings/role-bindings.store"
|
export type { RoleBindingsStore } from "../../renderer/components/+user-management-roles-bindings/role-bindings.store";
|
||||||
export type { CRDStore } from "../../renderer/components/+custom-resources/crd.store"
|
export type { CRDStore } from "../../renderer/components/+custom-resources/crd.store";
|
||||||
export type { CRDResourceStore } from "../../renderer/components/+custom-resources/crd-resource.store"
|
export type { CRDResourceStore } from "../../renderer/components/+custom-resources/crd-resource.store";
|
||||||
|
|||||||
@ -2,7 +2,7 @@ export type KubeObjectStatus = {
|
|||||||
level: KubeObjectStatusLevel;
|
level: KubeObjectStatusLevel;
|
||||||
text: string;
|
text: string;
|
||||||
timestamp?: string;
|
timestamp?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export enum KubeObjectStatusLevel {
|
export enum KubeObjectStatusLevel {
|
||||||
INFO = 1,
|
INFO = 1,
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
export { navigate } from "../../renderer/navigation";
|
export { navigate } from "../../renderer/navigation";
|
||||||
export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/navigation"
|
export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/navigation";
|
||||||
export { IURLParams } from "../../common/utils/buildUrl";
|
export { IURLParams } from "../../common/utils/buildUrl";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
import fetchMock from "jest-fetch-mock"
|
import fetchMock from "jest-fetch-mock";
|
||||||
// rewire global.fetch to call 'fetchMock'
|
// rewire global.fetch to call 'fetchMock'
|
||||||
fetchMock.enableMocks();
|
fetchMock.enableMocks();
|
||||||
|
|||||||
@ -21,35 +21,35 @@ jest.mock("winston", () => ({
|
|||||||
Console: jest.fn(),
|
Console: jest.fn(),
|
||||||
File: jest.fn(),
|
File: jest.fn(),
|
||||||
}
|
}
|
||||||
}))
|
}));
|
||||||
|
|
||||||
|
|
||||||
jest.mock("../../common/ipc")
|
jest.mock("../../common/ipc");
|
||||||
jest.mock("../context-handler")
|
jest.mock("../context-handler");
|
||||||
jest.mock("request")
|
jest.mock("request");
|
||||||
jest.mock("request-promise-native")
|
jest.mock("request-promise-native");
|
||||||
|
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import { workspaceStore } from "../../common/workspace-store";
|
import { workspaceStore } from "../../common/workspace-store";
|
||||||
import { Cluster } from "../cluster"
|
import { Cluster } from "../cluster";
|
||||||
import { ContextHandler } from "../context-handler";
|
import { ContextHandler } from "../context-handler";
|
||||||
import { getFreePort } from "../port";
|
import { getFreePort } from "../port";
|
||||||
import { V1ResourceAttributes } from "@kubernetes/client-node";
|
import { V1ResourceAttributes } from "@kubernetes/client-node";
|
||||||
import { apiResources } from "../../common/rbac";
|
import { apiResources } from "../../common/rbac";
|
||||||
import request from "request-promise-native"
|
import request from "request-promise-native";
|
||||||
import { Kubectl } from "../kubectl";
|
import { Kubectl } from "../kubectl";
|
||||||
|
|
||||||
const mockedRequest = request as jest.MockedFunction<typeof request>
|
const mockedRequest = request as jest.MockedFunction<typeof request>;
|
||||||
|
|
||||||
console = new Console(process.stdout, process.stderr) // fix mockFS
|
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||||
|
|
||||||
describe("create clusters", () => {
|
describe("create clusters", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks();
|
||||||
})
|
});
|
||||||
|
|
||||||
let c: Cluster
|
let c: Cluster;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
@ -74,68 +74,68 @@ describe("create clusters", () => {
|
|||||||
kind: "Config",
|
kind: "Config",
|
||||||
preferences: {},
|
preferences: {},
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
mockFs(mockOpts)
|
mockFs(mockOpts);
|
||||||
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true))
|
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true));
|
||||||
c = new Cluster({
|
c = new Cluster({
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "minikube-config.yml",
|
||||||
workspace: workspaceStore.currentWorkspaceId
|
workspace: workspaceStore.currentWorkspaceId
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore()
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("should be able to create a cluster from a cluster model and apiURL should be decoded", () => {
|
it("should be able to create a cluster from a cluster model and apiURL should be decoded", () => {
|
||||||
expect(c.apiUrl).toBe("https://192.168.64.3:8443")
|
expect(c.apiUrl).toBe("https://192.168.64.3:8443");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("reconnect should not throw if contextHandler is missing", () => {
|
it("reconnect should not throw if contextHandler is missing", () => {
|
||||||
expect(() => c.reconnect()).not.toThrowError()
|
expect(() => c.reconnect()).not.toThrowError();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("disconnect should not throw if contextHandler is missing", () => {
|
it("disconnect should not throw if contextHandler is missing", () => {
|
||||||
expect(() => c.disconnect()).not.toThrowError()
|
expect(() => c.disconnect()).not.toThrowError();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("init should not throw if everything is in order", async () => {
|
it("init should not throw if everything is in order", async () => {
|
||||||
await c.init(await getFreePort())
|
await c.init(await getFreePort());
|
||||||
expect(logger.info).toBeCalledWith(expect.stringContaining("init success"), {
|
expect(logger.info).toBeCalledWith(expect.stringContaining("init success"), {
|
||||||
id: "foo",
|
id: "foo",
|
||||||
apiUrl: "https://192.168.64.3:8443",
|
apiUrl: "https://192.168.64.3:8443",
|
||||||
context: "minikube",
|
context: "minikube",
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
it("activating cluster should try to connect to cluster and do a refresh", async () => {
|
it("activating cluster should try to connect to cluster and do a refresh", async () => {
|
||||||
const port = await getFreePort()
|
const port = await getFreePort();
|
||||||
jest.spyOn(ContextHandler.prototype, "ensureServer");
|
jest.spyOn(ContextHandler.prototype, "ensureServer");
|
||||||
|
|
||||||
const mockListNSs = jest.fn()
|
const mockListNSs = jest.fn();
|
||||||
const mockKC = {
|
const mockKC = {
|
||||||
makeApiClient() {
|
makeApiClient() {
|
||||||
return {
|
return {
|
||||||
listNamespace: mockListNSs,
|
listNamespace: mockListNSs,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
jest.spyOn(Cluster.prototype, "isClusterAdmin").mockReturnValue(Promise.resolve(true))
|
jest.spyOn(Cluster.prototype, "isClusterAdmin").mockReturnValue(Promise.resolve(true));
|
||||||
jest.spyOn(Cluster.prototype, "canI")
|
jest.spyOn(Cluster.prototype, "canI")
|
||||||
.mockImplementationOnce((attr: V1ResourceAttributes): Promise<boolean> => {
|
.mockImplementationOnce((attr: V1ResourceAttributes): Promise<boolean> => {
|
||||||
expect(attr.namespace).toBe("default")
|
expect(attr.namespace).toBe("default");
|
||||||
expect(attr.resource).toBe("pods")
|
expect(attr.resource).toBe("pods");
|
||||||
expect(attr.verb).toBe("list")
|
expect(attr.verb).toBe("list");
|
||||||
return Promise.resolve(true)
|
return Promise.resolve(true);
|
||||||
})
|
})
|
||||||
.mockImplementation((attr: V1ResourceAttributes): Promise<boolean> => {
|
.mockImplementation((attr: V1ResourceAttributes): Promise<boolean> => {
|
||||||
expect(attr.namespace).toBe("default")
|
expect(attr.namespace).toBe("default");
|
||||||
expect(attr.verb).toBe("list")
|
expect(attr.verb).toBe("list");
|
||||||
return Promise.resolve(true)
|
return Promise.resolve(true);
|
||||||
})
|
});
|
||||||
jest.spyOn(Cluster.prototype, "getProxyKubeconfig").mockReturnValue(mockKC as any)
|
jest.spyOn(Cluster.prototype, "getProxyKubeconfig").mockReturnValue(mockKC as any);
|
||||||
mockListNSs.mockImplementationOnce(() => ({
|
mockListNSs.mockImplementationOnce(() => ({
|
||||||
body: {
|
body: {
|
||||||
items: [{
|
items: [{
|
||||||
@ -144,36 +144,36 @@ describe("create clusters", () => {
|
|||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}))
|
}));
|
||||||
|
|
||||||
mockedRequest.mockImplementationOnce(((uri: any, _options: any) => {
|
mockedRequest.mockImplementationOnce(((uri: any, _options: any) => {
|
||||||
expect(uri).toBe(`http://localhost:${port}/api-kube/version`)
|
expect(uri).toBe(`http://localhost:${port}/api-kube/version`);
|
||||||
return Promise.resolve({ gitVersion: "1.2.3" })
|
return Promise.resolve({ gitVersion: "1.2.3" });
|
||||||
}) as any)
|
}) as any);
|
||||||
|
|
||||||
const c = new class extends Cluster {
|
const c = new class extends Cluster {
|
||||||
// only way to mock protected methods, without these we leak promises
|
// only way to mock protected methods, without these we leak promises
|
||||||
protected bindEvents() {
|
protected bindEvents() {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
protected async ensureKubectl() {
|
protected async ensureKubectl() {
|
||||||
return Promise.resolve(true)
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
}({
|
}({
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "minikube-config.yml",
|
||||||
workspace: workspaceStore.currentWorkspaceId
|
workspace: workspaceStore.currentWorkspaceId
|
||||||
})
|
});
|
||||||
await c.init(port)
|
await c.init(port);
|
||||||
await c.activate()
|
await c.activate();
|
||||||
|
|
||||||
expect(ContextHandler.prototype.ensureServer).toBeCalled()
|
expect(ContextHandler.prototype.ensureServer).toBeCalled();
|
||||||
expect(mockedRequest).toBeCalled()
|
expect(mockedRequest).toBeCalled();
|
||||||
expect(c.accessible).toBe(true)
|
expect(c.accessible).toBe(true);
|
||||||
expect(c.allowedNamespaces.length).toBe(1)
|
expect(c.allowedNamespaces.length).toBe(1);
|
||||||
expect(c.allowedResources.length).toBe(apiResources.length)
|
expect(c.allowedResources.length).toBe(apiResources.length);
|
||||||
c.disconnect()
|
c.disconnect();
|
||||||
jest.resetAllMocks()
|
jest.resetAllMocks();
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -21,109 +21,109 @@ jest.mock("winston", () => ({
|
|||||||
Console: jest.fn(),
|
Console: jest.fn(),
|
||||||
File: jest.fn(),
|
File: jest.fn(),
|
||||||
}
|
}
|
||||||
}))
|
}));
|
||||||
|
|
||||||
jest.mock("../../common/ipc")
|
jest.mock("../../common/ipc");
|
||||||
jest.mock("child_process")
|
jest.mock("child_process");
|
||||||
jest.mock("tcp-port-used")
|
jest.mock("tcp-port-used");
|
||||||
|
|
||||||
import { Cluster } from "../cluster"
|
import { Cluster } from "../cluster";
|
||||||
import { KubeAuthProxy } from "../kube-auth-proxy"
|
import { KubeAuthProxy } from "../kube-auth-proxy";
|
||||||
import { getFreePort } from "../port"
|
import { getFreePort } from "../port";
|
||||||
import { broadcastMessage } from "../../common/ipc"
|
import { broadcastMessage } from "../../common/ipc";
|
||||||
import { ChildProcess, spawn, SpawnOptions } from "child_process"
|
import { ChildProcess, spawn, SpawnOptions } from "child_process";
|
||||||
import { bundledKubectlPath, Kubectl } from "../kubectl"
|
import { bundledKubectlPath, Kubectl } from "../kubectl";
|
||||||
import { mock, MockProxy } from 'jest-mock-extended';
|
import { mock, MockProxy } from 'jest-mock-extended';
|
||||||
import { waitUntilUsed } from 'tcp-port-used';
|
import { waitUntilUsed } from 'tcp-port-used';
|
||||||
import { Readable } from "stream"
|
import { Readable } from "stream";
|
||||||
|
|
||||||
const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcastMessage>
|
const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcastMessage>;
|
||||||
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>
|
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
|
||||||
const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction<typeof waitUntilUsed>
|
const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction<typeof waitUntilUsed>;
|
||||||
|
|
||||||
describe("kube auth proxy tests", () => {
|
describe("kube auth proxy tests", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("calling exit multiple times shouldn't throw", async () => {
|
it("calling exit multiple times shouldn't throw", async () => {
|
||||||
const port = await getFreePort()
|
const port = await getFreePort();
|
||||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {});
|
||||||
kap.exit()
|
kap.exit();
|
||||||
kap.exit()
|
kap.exit();
|
||||||
kap.exit()
|
kap.exit();
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("spawn tests", () => {
|
describe("spawn tests", () => {
|
||||||
let port: number
|
let port: number;
|
||||||
let mockedCP: MockProxy<ChildProcess>
|
let mockedCP: MockProxy<ChildProcess>;
|
||||||
let listeners: Record<string, (...args: any[]) => void>
|
let listeners: Record<string, (...args: any[]) => void>;
|
||||||
let proxy: KubeAuthProxy
|
let proxy: KubeAuthProxy;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
port = await getFreePort()
|
port = await getFreePort();
|
||||||
mockedCP = mock<ChildProcess>()
|
mockedCP = mock<ChildProcess>();
|
||||||
listeners = {}
|
listeners = {};
|
||||||
|
|
||||||
jest.spyOn(Kubectl.prototype, "checkBinary").mockReturnValueOnce(Promise.resolve(true))
|
jest.spyOn(Kubectl.prototype, "checkBinary").mockReturnValueOnce(Promise.resolve(true));
|
||||||
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValueOnce(Promise.resolve(false))
|
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValueOnce(Promise.resolve(false));
|
||||||
mockedCP.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): ChildProcess => {
|
mockedCP.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): ChildProcess => {
|
||||||
listeners[event] = listener
|
listeners[event] = listener;
|
||||||
return mockedCP
|
return mockedCP;
|
||||||
})
|
});
|
||||||
mockedCP.stderr = mock<Readable>()
|
mockedCP.stderr = mock<Readable>();
|
||||||
mockedCP.stderr.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): Readable => {
|
mockedCP.stderr.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): Readable => {
|
||||||
listeners[`stderr/${event}`] = listener
|
listeners[`stderr/${event}`] = listener;
|
||||||
return mockedCP.stderr
|
return mockedCP.stderr;
|
||||||
})
|
});
|
||||||
mockedCP.stdout = mock<Readable>()
|
mockedCP.stdout = mock<Readable>();
|
||||||
mockedCP.stdout.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): Readable => {
|
mockedCP.stdout.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): Readable => {
|
||||||
listeners[`stdout/${event}`] = listener
|
listeners[`stdout/${event}`] = listener;
|
||||||
return mockedCP.stdout
|
return mockedCP.stdout;
|
||||||
})
|
});
|
||||||
mockSpawn.mockImplementationOnce((command: string, args: readonly string[], options: SpawnOptions): ChildProcess => {
|
mockSpawn.mockImplementationOnce((command: string, args: readonly string[], options: SpawnOptions): ChildProcess => {
|
||||||
expect(command).toBe(bundledKubectlPath())
|
expect(command).toBe(bundledKubectlPath());
|
||||||
return mockedCP
|
return mockedCP;
|
||||||
})
|
});
|
||||||
mockWaitUntilUsed.mockReturnValueOnce(Promise.resolve())
|
mockWaitUntilUsed.mockReturnValueOnce(Promise.resolve());
|
||||||
const cluster = new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" })
|
const cluster = new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" });
|
||||||
jest.spyOn(cluster, "apiUrl", "get").mockReturnValue("https://fake.k8s.internal")
|
jest.spyOn(cluster, "apiUrl", "get").mockReturnValue("https://fake.k8s.internal");
|
||||||
proxy = new KubeAuthProxy(cluster, port, {})
|
proxy = new KubeAuthProxy(cluster, port, {});
|
||||||
})
|
});
|
||||||
|
|
||||||
it("should call spawn and broadcast errors", async () => {
|
it("should call spawn and broadcast errors", async () => {
|
||||||
await proxy.run()
|
await proxy.run();
|
||||||
listeners["error"]({ message: "foobarbat" })
|
listeners["error"]({ message: "foobarbat" });
|
||||||
|
|
||||||
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "foobarbat", error: true })
|
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "foobarbat", error: true });
|
||||||
})
|
});
|
||||||
|
|
||||||
it("should call spawn and broadcast exit", async () => {
|
it("should call spawn and broadcast exit", async () => {
|
||||||
await proxy.run()
|
await proxy.run();
|
||||||
listeners["exit"](0)
|
listeners["exit"](0);
|
||||||
|
|
||||||
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "proxy exited with code: 0", error: false })
|
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "proxy exited with code: 0", error: false });
|
||||||
})
|
});
|
||||||
|
|
||||||
it("should call spawn and broadcast errors from stderr", async () => {
|
it("should call spawn and broadcast errors from stderr", async () => {
|
||||||
await proxy.run()
|
await proxy.run();
|
||||||
listeners["stderr/data"]("an error")
|
listeners["stderr/data"]("an error");
|
||||||
|
|
||||||
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "an error", error: true })
|
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "an error", error: true });
|
||||||
})
|
});
|
||||||
|
|
||||||
it("should call spawn and broadcast stdout serving info", async () => {
|
it("should call spawn and broadcast stdout serving info", async () => {
|
||||||
await proxy.run()
|
await proxy.run();
|
||||||
listeners["stdout/data"]("Starting to serve on")
|
listeners["stdout/data"]("Starting to serve on");
|
||||||
|
|
||||||
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "Authentication proxy started\n" })
|
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "Authentication proxy started\n" });
|
||||||
})
|
});
|
||||||
|
|
||||||
it("should call spawn and broadcast stdout other info", async () => {
|
it("should call spawn and broadcast stdout other info", async () => {
|
||||||
await proxy.run()
|
await proxy.run();
|
||||||
listeners["stdout/data"]("some info")
|
listeners["stdout/data"]("some info");
|
||||||
|
|
||||||
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "some info" })
|
expect(mockBroadcastIpc).toBeCalledWith("kube-auth:foobar", { data: "some info" });
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -21,24 +21,24 @@ jest.mock("winston", () => ({
|
|||||||
Console: jest.fn(),
|
Console: jest.fn(),
|
||||||
File: jest.fn(),
|
File: jest.fn(),
|
||||||
}
|
}
|
||||||
}))
|
}));
|
||||||
|
|
||||||
import { KubeconfigManager } from "../kubeconfig-manager"
|
import { KubeconfigManager } from "../kubeconfig-manager";
|
||||||
import mockFs from "mock-fs"
|
import mockFs from "mock-fs";
|
||||||
import { Cluster } from "../cluster";
|
import { Cluster } from "../cluster";
|
||||||
import { workspaceStore } from "../../common/workspace-store";
|
import { workspaceStore } from "../../common/workspace-store";
|
||||||
import { ContextHandler } from "../context-handler";
|
import { ContextHandler } from "../context-handler";
|
||||||
import { getFreePort } from "../port";
|
import { getFreePort } from "../port";
|
||||||
import fse from "fs-extra"
|
import fse from "fs-extra";
|
||||||
import { loadYaml } from "@kubernetes/client-node";
|
import { loadYaml } from "@kubernetes/client-node";
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
|
|
||||||
console = new Console(process.stdout, process.stderr) // fix mockFS
|
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||||
|
|
||||||
describe("kubeconfig manager tests", () => {
|
describe("kubeconfig manager tests", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks();
|
||||||
})
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
@ -63,13 +63,13 @@ describe("kubeconfig manager tests", () => {
|
|||||||
kind: "Config",
|
kind: "Config",
|
||||||
preferences: {},
|
preferences: {},
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
mockFs(mockOpts)
|
mockFs(mockOpts);
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore()
|
mockFs.restore();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("should create 'temp' kube config with proxy", async () => {
|
it("should create 'temp' kube config with proxy", async () => {
|
||||||
const cluster = new Cluster({
|
const cluster = new Cluster({
|
||||||
@ -77,19 +77,19 @@ describe("kubeconfig manager tests", () => {
|
|||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "minikube-config.yml",
|
||||||
workspace: workspaceStore.currentWorkspaceId
|
workspace: workspaceStore.currentWorkspaceId
|
||||||
})
|
});
|
||||||
const contextHandler = new ContextHandler(cluster)
|
const contextHandler = new ContextHandler(cluster);
|
||||||
const port = await getFreePort()
|
const port = await getFreePort();
|
||||||
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port)
|
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port);
|
||||||
|
|
||||||
expect(logger.error).not.toBeCalled()
|
expect(logger.error).not.toBeCalled();
|
||||||
expect(kubeConfManager.getPath()).toBe("tmp/kubeconfig-foo")
|
expect(kubeConfManager.getPath()).toBe("tmp/kubeconfig-foo");
|
||||||
const file = await fse.readFile(kubeConfManager.getPath())
|
const file = await fse.readFile(kubeConfManager.getPath());
|
||||||
const yml = loadYaml<any>(file.toString())
|
const yml = loadYaml<any>(file.toString());
|
||||||
expect(yml["current-context"]).toBe("minikube")
|
expect(yml["current-context"]).toBe("minikube");
|
||||||
expect(yml["clusters"][0]["cluster"]["server"]).toBe(`http://127.0.0.1:${port}/foo`)
|
expect(yml["clusters"][0]["cluster"]["server"]).toBe(`http://127.0.0.1:${port}/foo`);
|
||||||
expect(yml["users"][0]["name"]).toBe("proxy")
|
expect(yml["users"][0]["name"]).toBe("proxy");
|
||||||
})
|
});
|
||||||
|
|
||||||
it("should remove 'temp' kube config on unlink and remove reference from inside class", async () => {
|
it("should remove 'temp' kube config on unlink and remove reference from inside class", async () => {
|
||||||
const cluster = new Cluster({
|
const cluster = new Cluster({
|
||||||
@ -97,16 +97,16 @@ describe("kubeconfig manager tests", () => {
|
|||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "minikube-config.yml",
|
||||||
workspace: workspaceStore.currentWorkspaceId
|
workspace: workspaceStore.currentWorkspaceId
|
||||||
})
|
});
|
||||||
const contextHandler = new ContextHandler(cluster)
|
const contextHandler = new ContextHandler(cluster);
|
||||||
const port = await getFreePort()
|
const port = await getFreePort();
|
||||||
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port)
|
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port);
|
||||||
|
|
||||||
const configPath = kubeConfManager.getPath()
|
const configPath = kubeConfManager.getPath();
|
||||||
expect(await fse.pathExists(configPath)).toBe(true)
|
expect(await fse.pathExists(configPath)).toBe(true);
|
||||||
await kubeConfManager.unlink()
|
await kubeConfManager.unlink();
|
||||||
expect(await fse.pathExists(configPath)).toBe(false)
|
expect(await fse.pathExists(configPath)).toBe(false);
|
||||||
await kubeConfManager.unlink() // doesn't throw
|
await kubeConfManager.unlink(); // doesn't throw
|
||||||
expect(kubeConfManager.getPath()).toBeUndefined()
|
expect(kubeConfManager.getPath()).toBeUndefined();
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
import { autoUpdater } from "electron-updater"
|
import { autoUpdater } from "electron-updater";
|
||||||
import logger from "./logger"
|
import logger from "./logger";
|
||||||
|
|
||||||
export class AppUpdater {
|
export class AppUpdater {
|
||||||
static readonly defaultUpdateIntervalMs = 1000 * 60 * 60 * 24 // once a day
|
static readonly defaultUpdateIntervalMs = 1000 * 60 * 60 * 24; // once a day
|
||||||
|
|
||||||
static checkForUpdates() {
|
static checkForUpdates() {
|
||||||
return autoUpdater.checkForUpdatesAndNotify()
|
return autoUpdater.checkForUpdatesAndNotify();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(protected updateInterval = AppUpdater.defaultUpdateIntervalMs) {
|
constructor(protected updateInterval = AppUpdater.defaultUpdateIntervalMs) {
|
||||||
autoUpdater.logger = logger
|
autoUpdater.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public start() {
|
public start() {
|
||||||
setInterval(AppUpdater.checkForUpdates, this.updateInterval)
|
setInterval(AppUpdater.checkForUpdates, this.updateInterval);
|
||||||
return AppUpdater.checkForUpdates();
|
return AppUpdater.checkForUpdates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
import request, { RequestPromiseOptions } from "request-promise-native"
|
import request, { RequestPromiseOptions } from "request-promise-native";
|
||||||
import { Cluster } from "../cluster";
|
import { Cluster } from "../cluster";
|
||||||
|
|
||||||
export type ClusterDetectionResult = {
|
export type ClusterDetectionResult = {
|
||||||
value: string | number | boolean
|
value: string | number | boolean
|
||||||
accuracy: number
|
accuracy: number
|
||||||
}
|
};
|
||||||
|
|
||||||
export class BaseClusterDetector {
|
export class BaseClusterDetector {
|
||||||
cluster: Cluster
|
cluster: Cluster;
|
||||||
key: string
|
key: string;
|
||||||
|
|
||||||
constructor(cluster: Cluster) {
|
constructor(cluster: Cluster) {
|
||||||
this.cluster = cluster
|
this.cluster = cluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
detect(): Promise<ClusterDetectionResult> {
|
detect(): Promise<ClusterDetectionResult> {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
|
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
|
||||||
@ -28,6 +28,6 @@ export class BaseClusterDetector {
|
|||||||
Host: `${this.cluster.id}.${new URL(this.cluster.kubeProxyUrl).host}`, // required in ClusterManager.getClusterForRequest()
|
Host: `${this.cluster.id}.${new URL(this.cluster.kubeProxyUrl).host}`, // required in ClusterManager.getClusterForRequest()
|
||||||
...(options.headers || {}),
|
...(options.headers || {}),
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,23 +1,23 @@
|
|||||||
import { BaseClusterDetector } from "./base-cluster-detector";
|
import { BaseClusterDetector } from "./base-cluster-detector";
|
||||||
import { createHash } from "crypto"
|
import { createHash } from "crypto";
|
||||||
import { ClusterMetadataKey } from "../cluster";
|
import { ClusterMetadataKey } from "../cluster";
|
||||||
|
|
||||||
export class ClusterIdDetector extends BaseClusterDetector {
|
export class ClusterIdDetector extends BaseClusterDetector {
|
||||||
key = ClusterMetadataKey.CLUSTER_ID
|
key = ClusterMetadataKey.CLUSTER_ID;
|
||||||
|
|
||||||
public async detect() {
|
public async detect() {
|
||||||
let id: string
|
let id: string;
|
||||||
try {
|
try {
|
||||||
id = await this.getDefaultNamespaceId()
|
id = await this.getDefaultNamespaceId();
|
||||||
} catch(_) {
|
} catch(_) {
|
||||||
id = this.cluster.apiUrl
|
id = this.cluster.apiUrl;
|
||||||
}
|
}
|
||||||
const value = createHash("sha256").update(id).digest("hex")
|
const value = createHash("sha256").update(id).digest("hex");
|
||||||
return { value: value, accuracy: 100 }
|
return { value: value, accuracy: 100 };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getDefaultNamespaceId() {
|
protected async getDefaultNamespaceId() {
|
||||||
const response = await this.k8sRequest("/api/v1/namespaces/default")
|
const response = await this.k8sRequest("/api/v1/namespaces/default");
|
||||||
return response.metadata.uid
|
return response.metadata.uid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user