mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Allow extensions to define cluster features (#1125)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
parent
f3a0059355
commit
62ae7771df
5
extensions/metrics-cluster-feature/Makefile
Normal file
5
extensions/metrics-cluster-feature/Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
install-deps:
|
||||||
|
npm install
|
||||||
|
|
||||||
|
build: install-deps
|
||||||
|
npm run build
|
||||||
3570
extensions/metrics-cluster-feature/package-lock.json
generated
Normal file
3570
extensions/metrics-cluster-feature/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
extensions/metrics-cluster-feature/package.json
Normal file
24
extensions/metrics-cluster-feature/package.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "lens-metrics-cluster-feature",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Lens metrics cluster feature",
|
||||||
|
"renderer": "dist/renderer.js",
|
||||||
|
"lens": {
|
||||||
|
"metadata": {},
|
||||||
|
"styles": []
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --config webpack.config.js",
|
||||||
|
"dev": "npm run build --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^7.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"ts-loader": "^8.0.4",
|
||||||
|
"typescript": "^4.0.3",
|
||||||
|
"webpack": "^4.44.2",
|
||||||
|
"mobx": "^5.15.5",
|
||||||
|
"react": "^16.13.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
25
extensions/metrics-cluster-feature/renderer.tsx
Normal file
25
extensions/metrics-cluster-feature/renderer.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Registry, LensRendererExtension } from "@k8slens/extensions"
|
||||||
|
import { MetricsFeature } from "./src/metrics-feature"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
|
||||||
|
registerClusterFeatures(registry: Registry.ClusterFeatureRegistry) {
|
||||||
|
this.disposers.push(
|
||||||
|
registry.add({
|
||||||
|
title: "Metrics Stack",
|
||||||
|
components: {
|
||||||
|
Description: () => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
Enable timeseries data visualization (Prometheus stack) for your cluster.
|
||||||
|
Install this only if you don't have existing Prometheus stack installed.
|
||||||
|
You can see preview of manifests <a href="https://github.com/lensapp/lens/tree/master/extensions/lens-metrics/resources" target="_blank">here</a>.
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
feature: new MetricsFeature()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
96
extensions/metrics-cluster-feature/src/metrics-feature.ts
Normal file
96
extensions/metrics-cluster-feature/src/metrics-feature.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { ClusterFeature, Store, K8sApi } from "@k8slens/extensions"
|
||||||
|
import semver from "semver"
|
||||||
|
import * as path from "path"
|
||||||
|
|
||||||
|
export interface MetricsConfiguration {
|
||||||
|
// Placeholder for Metrics config structure
|
||||||
|
persistence: {
|
||||||
|
enabled: boolean;
|
||||||
|
storageClass: string;
|
||||||
|
size: string;
|
||||||
|
};
|
||||||
|
nodeExporter: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
kubeStateMetrics: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
retention: {
|
||||||
|
time: string;
|
||||||
|
size: string;
|
||||||
|
};
|
||||||
|
alertManagers: string[];
|
||||||
|
replicas: number;
|
||||||
|
storageClass: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MetricsFeature extends ClusterFeature.Feature {
|
||||||
|
name = "metrics"
|
||||||
|
latestVersion = "v2.17.2-lens1"
|
||||||
|
|
||||||
|
config: MetricsConfiguration = {
|
||||||
|
persistence: {
|
||||||
|
enabled: false,
|
||||||
|
storageClass: null,
|
||||||
|
size: "20G",
|
||||||
|
},
|
||||||
|
nodeExporter: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
retention: {
|
||||||
|
time: "2d",
|
||||||
|
size: "5GB",
|
||||||
|
},
|
||||||
|
kubeStateMetrics: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
alertManagers: null,
|
||||||
|
replicas: 1,
|
||||||
|
storageClass: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
async install(cluster: Store.Cluster): Promise<void> {
|
||||||
|
// Check if there are storageclasses
|
||||||
|
const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass)
|
||||||
|
const scs = await storageClassApi.list()
|
||||||
|
this.config.persistence.enabled = scs.some(sc => (
|
||||||
|
sc.metadata?.annotations?.['storageclass.kubernetes.io/is-default-class'] === 'true' ||
|
||||||
|
sc.metadata?.annotations?.['storageclass.beta.kubernetes.io/is-default-class'] === 'true'
|
||||||
|
));
|
||||||
|
|
||||||
|
super.applyResources(cluster, super.renderTemplates(path.join(__dirname, "../resources/")))
|
||||||
|
}
|
||||||
|
|
||||||
|
async upgrade(cluster: Store.Cluster): Promise<void> {
|
||||||
|
return this.install(cluster)
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateStatus(cluster: Store.Cluster): Promise<ClusterFeature.FeatureStatus> {
|
||||||
|
try {
|
||||||
|
const statefulSet = K8sApi.forCluster(cluster, K8sApi.StatefulSet)
|
||||||
|
const prometheus = await statefulSet.get({name: "prometheus", namespace: "lens-metrics"})
|
||||||
|
if (prometheus?.kind) {
|
||||||
|
this.status.installed = true;
|
||||||
|
this.status.currentVersion = prometheus.spec.template.spec.containers[0].image.split(":")[1];
|
||||||
|
this.status.canUpgrade = semver.lt(this.status.currentVersion, this.latestVersion, true);
|
||||||
|
} else {
|
||||||
|
this.status.installed = false
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
if (e?.error?.code === 404) {
|
||||||
|
this.status.installed = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.status
|
||||||
|
}
|
||||||
|
|
||||||
|
async uninstall(cluster: Store.Cluster): Promise<void> {
|
||||||
|
const namespaceApi = K8sApi.forCluster(cluster, K8sApi.Namespace)
|
||||||
|
const clusterRoleBindingApi = K8sApi.forCluster(cluster, K8sApi.ClusterRoleBinding)
|
||||||
|
const clusterRoleApi = K8sApi.forCluster(cluster, K8sApi.ClusterRole)
|
||||||
|
|
||||||
|
await namespaceApi.delete({name: "lens-metrics"})
|
||||||
|
await clusterRoleBindingApi.delete({name: "lens-prometheus"})
|
||||||
|
await clusterRoleApi.delete({name: "lens-prometheus"}) }
|
||||||
|
}
|
||||||
27
extensions/metrics-cluster-feature/tsconfig.json
Normal file
27
extensions/metrics-cluster-feature/tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"module": "CommonJS",
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"sourceMap": false,
|
||||||
|
"declaration": false,
|
||||||
|
"strict": false,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"jsx": "react"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"../../src/extensions/npm/**/*.d.ts",
|
||||||
|
"./*.ts",
|
||||||
|
"./*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"*.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
38
extensions/metrics-cluster-feature/webpack.config.js
Normal file
38
extensions/metrics-cluster-feature/webpack.config.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
entry: './renderer.tsx',
|
||||||
|
context: __dirname,
|
||||||
|
target: "electron-renderer",
|
||||||
|
mode: "production",
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
externals: [
|
||||||
|
{
|
||||||
|
"@k8slens/extensions": "var global.LensExtensions",
|
||||||
|
"react": "var global.React",
|
||||||
|
"mobx": "var global.Mobx"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
extensions: [ '.tsx', '.ts', '.js' ],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
libraryTarget: "commonjs2",
|
||||||
|
globalObject: "this",
|
||||||
|
filename: 'renderer.js',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
__dirname: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
5
extensions/support-page/Makefile
Normal file
5
extensions/support-page/Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
install-deps:
|
||||||
|
npm install
|
||||||
|
|
||||||
|
build: install-deps
|
||||||
|
npm run build
|
||||||
@ -23,7 +23,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"renderer.ts",
|
"renderer.tsx",
|
||||||
"../../src/extensions/npm/**/*.d.ts",
|
"../../src/extensions/npm/**/*.d.ts",
|
||||||
"src/**/*"
|
"src/**/*"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -177,6 +177,7 @@
|
|||||||
"telemetry",
|
"telemetry",
|
||||||
"pod-menu",
|
"pod-menu",
|
||||||
"node-menu",
|
"node-menu",
|
||||||
|
"metrics-cluster-feature",
|
||||||
"support-page"
|
"support-page"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { createIpcChannel } from "./ipc";
|
|||||||
import { ClusterId, clusterStore } from "./cluster-store";
|
import { ClusterId, clusterStore } from "./cluster-store";
|
||||||
import { extensionLoader } from "../extensions/extension-loader"
|
import { extensionLoader } from "../extensions/extension-loader"
|
||||||
import { appEventBus } from "./event-bus"
|
import { appEventBus } from "./event-bus"
|
||||||
|
import { ResourceApplier } from "../main/resource-applier";
|
||||||
|
|
||||||
export const clusterIpc = {
|
export const clusterIpc = {
|
||||||
activate: createIpcChannel({
|
activate: createIpcChannel({
|
||||||
@ -42,32 +43,17 @@ export const clusterIpc = {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
installFeature: createIpcChannel({
|
kubectlApplyAll: createIpcChannel({
|
||||||
channel: "cluster:install-feature",
|
channel: "cluster:kubectl-apply-all",
|
||||||
handle: async (clusterId: ClusterId, feature: string, config?: any) => {
|
handle: (clusterId: ClusterId, resources: string[]) => {
|
||||||
appEventBus.emit({name: "cluster", action: "install", params: { feature: feature}})
|
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"})
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = clusterStore.getById(clusterId);
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
await cluster.installFeature(feature, config)
|
const applier = new ResourceApplier(cluster)
|
||||||
|
applier.kubectlApplyAll(resources)
|
||||||
} else {
|
} else {
|
||||||
throw `${clusterId} is not a valid cluster id`;
|
throw `${clusterId} is not a valid cluster id`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
uninstallFeature: createIpcChannel({
|
|
||||||
channel: "cluster:uninstall-feature",
|
|
||||||
handle: (clusterId: ClusterId, feature: string) => {
|
|
||||||
appEventBus.emit({name: "cluster", action: "uninstall", params: { feature: feature}})
|
|
||||||
return clusterStore.getById(clusterId)?.uninstallFeature(feature)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
upgradeFeature: createIpcChannel({
|
|
||||||
channel: "cluster:upgrade-feature",
|
|
||||||
handle: (clusterId: ClusterId, feature: string, config?: any) => {
|
|
||||||
appEventBus.emit({name: "cluster", action: "upgrade", params: { feature: feature}})
|
|
||||||
return clusterStore.getById(clusterId)?.upgradeFeature(feature, config)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/extensions/cluster-feature.ts
Normal file
62
src/extensions/cluster-feature.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import path from "path"
|
||||||
|
import hb from "handlebars"
|
||||||
|
import { observable } from "mobx"
|
||||||
|
import { ResourceApplier } from "../main/resource-applier"
|
||||||
|
import { Cluster } from "../main/cluster";
|
||||||
|
import logger from "../main/logger";
|
||||||
|
import { app } from "electron"
|
||||||
|
import { clusterIpc } from "../common/cluster-ipc"
|
||||||
|
|
||||||
|
export interface ClusterFeatureStatus {
|
||||||
|
currentVersion: string;
|
||||||
|
installed: boolean;
|
||||||
|
latestVersion: string;
|
||||||
|
canUpgrade: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class ClusterFeature {
|
||||||
|
name: string;
|
||||||
|
latestVersion: string;
|
||||||
|
config: any;
|
||||||
|
|
||||||
|
@observable status: ClusterFeatureStatus = {
|
||||||
|
currentVersion: null,
|
||||||
|
installed: false,
|
||||||
|
latestVersion: null,
|
||||||
|
canUpgrade: false
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract async install(cluster: Cluster): Promise<void>;
|
||||||
|
|
||||||
|
abstract async upgrade(cluster: Cluster): Promise<void>;
|
||||||
|
|
||||||
|
abstract async uninstall(cluster: Cluster): Promise<void>;
|
||||||
|
|
||||||
|
abstract async updateStatus(cluster: Cluster): Promise<ClusterFeatureStatus>;
|
||||||
|
|
||||||
|
protected async applyResources(cluster: Cluster, resources: string[]) {
|
||||||
|
if (app) {
|
||||||
|
await new ResourceApplier(cluster).kubectlApplyAll(resources)
|
||||||
|
} else {
|
||||||
|
await clusterIpc.kubectlApplyAll.invokeFromRenderer(cluster.id, resources)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderTemplates(folderPath: string): string[] {
|
||||||
|
const resources: string[] = [];
|
||||||
|
logger.info(`[FEATURE]: render templates from ${folderPath}`);
|
||||||
|
fs.readdirSync(folderPath).forEach(filename => {
|
||||||
|
const file = path.join(folderPath, filename);
|
||||||
|
const raw = fs.readFileSync(file);
|
||||||
|
if (filename.endsWith('.hb')) {
|
||||||
|
const template = hb.compile(raw.toString());
|
||||||
|
resources.push(template(this.config));
|
||||||
|
} else {
|
||||||
|
resources.push(raw.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return resources;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/extensions/core-api/cluster-feature.ts
Normal file
1
src/extensions/core-api/cluster-feature.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { ClusterFeature as Feature, ClusterFeatureStatus as FeatureStatus } from "../cluster-feature"
|
||||||
@ -10,11 +10,13 @@ import * as Store from "./stores"
|
|||||||
import * as Util from "./utils"
|
import * as Util from "./utils"
|
||||||
import * as Registry from "../registries"
|
import * as Registry from "../registries"
|
||||||
import * as CommonVars from "../../common/vars";
|
import * as CommonVars from "../../common/vars";
|
||||||
|
import * as ClusterFeature from "./cluster-feature"
|
||||||
|
|
||||||
export let windowManager: WindowManager;
|
export let windowManager: WindowManager;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
EventBus,
|
EventBus,
|
||||||
|
ClusterFeature,
|
||||||
Store,
|
Store,
|
||||||
Util,
|
Util,
|
||||||
Registry,
|
Registry,
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export { ExtensionStore } from "../extension-store"
|
export { ExtensionStore } from "../extension-store"
|
||||||
|
export type { Cluster } from "../../main/cluster"
|
||||||
|
|||||||
19
src/extensions/core-extension-api.ts
Normal file
19
src/extensions/core-extension-api.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Lens-extensions api developer's kit
|
||||||
|
export type { LensExtensionRuntimeEnv } from "./lens-runtime";
|
||||||
|
export * from "./lens-main-extension"
|
||||||
|
export * from "./lens-renderer-extension"
|
||||||
|
|
||||||
|
// APIs
|
||||||
|
import * as EventBus from "./core-api/event-bus"
|
||||||
|
import * as Store from "./core-api/stores"
|
||||||
|
import * as Util from "./core-api/utils"
|
||||||
|
import * as Registry from "./core-api/registries"
|
||||||
|
import * as ClusterFeature from "./core-api/cluster-feature"
|
||||||
|
|
||||||
|
export {
|
||||||
|
ClusterFeature,
|
||||||
|
EventBus,
|
||||||
|
Registry,
|
||||||
|
Store,
|
||||||
|
Util
|
||||||
|
}
|
||||||
@ -6,7 +6,7 @@ import { broadcastIpc } from "../common/ipc"
|
|||||||
import { observable, reaction, toJS, } from "mobx"
|
import { observable, reaction, toJS, } 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 { appPreferenceRegistry, kubeObjectMenuRegistry, menuRegistry, pageRegistry, statusBarRegistry } from "./registries";
|
import { appPreferenceRegistry, kubeObjectMenuRegistry, menuRegistry, pageRegistry, statusBarRegistry, clusterFeatureRegistry } from "./registries";
|
||||||
|
|
||||||
export interface InstalledExtension extends ExtensionModel {
|
export interface InstalledExtension extends ExtensionModel {
|
||||||
manifestPath: string;
|
manifestPath: string;
|
||||||
@ -46,6 +46,7 @@ export class ExtensionLoader {
|
|||||||
this.autoloadExtensions((instance: LensRendererExtension) => {
|
this.autoloadExtensions((instance: LensRendererExtension) => {
|
||||||
instance.registerPages(pageRegistry)
|
instance.registerPages(pageRegistry)
|
||||||
instance.registerAppPreferences(appPreferenceRegistry)
|
instance.registerAppPreferences(appPreferenceRegistry)
|
||||||
|
instance.registerClusterFeatures(clusterFeatureRegistry)
|
||||||
instance.registerStatusBarIcon(statusBarRegistry)
|
instance.registerStatusBarIcon(statusBarRegistry)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -60,8 +61,8 @@ export class ExtensionLoader {
|
|||||||
|
|
||||||
protected autoloadExtensions(callback: (instance: LensExtension) => void) {
|
protected autoloadExtensions(callback: (instance: LensExtension) => void) {
|
||||||
return reaction(() => this.extensions.toJS(), (installedExtensions) => {
|
return reaction(() => this.extensions.toJS(), (installedExtensions) => {
|
||||||
for (const [id, ext] of installedExtensions) {
|
for(const [id, ext] of installedExtensions) {
|
||||||
let instance = this.instances.get(ext.name)
|
let instance = this.instances.get(ext.id)
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
const extensionModule = this.requireExtension(ext)
|
const extensionModule = this.requireExtension(ext)
|
||||||
if (!extensionModule) {
|
if (!extensionModule) {
|
||||||
@ -69,9 +70,12 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
const LensExtensionClass = extensionModule.default;
|
const LensExtensionClass = extensionModule.default;
|
||||||
instance = new LensExtensionClass({ ...ext.manifest, manifestPath: ext.manifestPath, id: ext.manifestPath }, ext.manifest);
|
instance = new LensExtensionClass({ ...ext.manifest, manifestPath: ext.manifestPath, id: ext.manifestPath }, ext.manifest);
|
||||||
instance.enable();
|
try {
|
||||||
|
instance.enable()
|
||||||
callback(instance)
|
callback(instance)
|
||||||
this.instances.set(ext.name, instance)
|
} finally {
|
||||||
|
this.instances.set(ext.id, instance)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { LensExtension } from "./lens-extension"
|
import { LensExtension } from "./lens-extension"
|
||||||
import type { PageRegistry, AppPreferenceRegistry, StatusBarRegistry, KubeObjectMenuRegistry } from "./registries"
|
import type { PageRegistry, AppPreferenceRegistry, StatusBarRegistry, KubeObjectMenuRegistry, ClusterFeatureRegistry } from "./registries"
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension {
|
export class LensRendererExtension extends LensExtension {
|
||||||
registerPages(registry: PageRegistry) {
|
registerPages(registry: PageRegistry) {
|
||||||
@ -10,6 +10,10 @@ export class LensRendererExtension extends LensExtension {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerClusterFeatures(registry: ClusterFeatureRegistry) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
registerStatusBarIcon(registry: StatusBarRegistry) {
|
registerStatusBarIcon(registry: StatusBarRegistry) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/extensions/registries/cluster-feature-registry.ts
Normal file
16
src/extensions/registries/cluster-feature-registry.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
import { ClusterFeature } from "../cluster-feature";
|
||||||
|
|
||||||
|
export interface ClusterFeatureComponents {
|
||||||
|
Description: React.ComponentType<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterFeatureRegistration {
|
||||||
|
title: string;
|
||||||
|
components: ClusterFeatureComponents
|
||||||
|
feature: ClusterFeature
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ClusterFeatureRegistry extends BaseRegistry<ClusterFeatureRegistration> {}
|
||||||
|
|
||||||
|
export const clusterFeatureRegistry = new ClusterFeatureRegistry()
|
||||||
@ -5,3 +5,4 @@ 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-menu-registry";
|
export * from "./kube-object-menu-registry";
|
||||||
|
export * from "./cluster-feature-registry"
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
export { apiManager } from "../../renderer/api/api-manager";
|
export { apiManager } from "../../renderer/api/api-manager";
|
||||||
export { KubeApi } 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, IPodContainer, IPodContainerStatus } from "../../renderer/api/endpoints";
|
export { Pod, podsApi, IPodContainer, IPodContainerStatus } from "../../renderer/api/endpoints";
|
||||||
export { Node, nodesApi } from "../../renderer/api/endpoints";
|
export { Node, nodesApi } from "../../renderer/api/endpoints";
|
||||||
|
|||||||
@ -1,100 +0,0 @@
|
|||||||
import { Feature, FeatureStatus } from "../main/feature"
|
|
||||||
import {KubeConfig, AppsV1Api, RbacAuthorizationV1Api} from "@kubernetes/client-node"
|
|
||||||
import semver from "semver"
|
|
||||||
import { Cluster } from "../main/cluster";
|
|
||||||
import * as k8s from "@kubernetes/client-node"
|
|
||||||
|
|
||||||
export interface MetricsConfiguration {
|
|
||||||
// Placeholder for Metrics config structure
|
|
||||||
persistence: {
|
|
||||||
enabled: boolean;
|
|
||||||
storageClass: string;
|
|
||||||
size: string;
|
|
||||||
};
|
|
||||||
nodeExporter: {
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
kubeStateMetrics: {
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
retention: {
|
|
||||||
time: string;
|
|
||||||
size: string;
|
|
||||||
};
|
|
||||||
alertManagers: string[];
|
|
||||||
replicas: number;
|
|
||||||
storageClass: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MetricsFeature extends Feature {
|
|
||||||
static id = 'metrics'
|
|
||||||
name = MetricsFeature.id;
|
|
||||||
latestVersion = "v2.17.2-lens1"
|
|
||||||
|
|
||||||
config: MetricsConfiguration = {
|
|
||||||
persistence: {
|
|
||||||
enabled: false,
|
|
||||||
storageClass: null,
|
|
||||||
size: "20G",
|
|
||||||
},
|
|
||||||
nodeExporter: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
retention: {
|
|
||||||
time: "2d",
|
|
||||||
size: "5GB",
|
|
||||||
},
|
|
||||||
kubeStateMetrics: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
alertManagers: null,
|
|
||||||
replicas: 1,
|
|
||||||
storageClass: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
async install(cluster: Cluster): Promise<void> {
|
|
||||||
// Check if there are storageclasses
|
|
||||||
const storageClient = cluster.getProxyKubeconfig().makeApiClient(k8s.StorageV1Api)
|
|
||||||
const scs = await storageClient.listStorageClass();
|
|
||||||
|
|
||||||
this.config.persistence.enabled = scs.body.items.some(sc => (
|
|
||||||
sc.metadata?.annotations?.['storageclass.kubernetes.io/is-default-class'] === 'true' ||
|
|
||||||
sc.metadata?.annotations?.['storageclass.beta.kubernetes.io/is-default-class'] === 'true'
|
|
||||||
));
|
|
||||||
|
|
||||||
return super.install(cluster)
|
|
||||||
}
|
|
||||||
|
|
||||||
async upgrade(cluster: Cluster): Promise<void> {
|
|
||||||
return this.install(cluster)
|
|
||||||
}
|
|
||||||
|
|
||||||
async featureStatus(kc: KubeConfig): Promise<FeatureStatus> {
|
|
||||||
const client = kc.makeApiClient(AppsV1Api)
|
|
||||||
const status: FeatureStatus = {
|
|
||||||
currentVersion: null,
|
|
||||||
installed: false,
|
|
||||||
latestVersion: this.latestVersion,
|
|
||||||
canUpgrade: false, // Dunno yet
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const prometheus = (await client.readNamespacedStatefulSet('prometheus', 'lens-metrics')).body;
|
|
||||||
status.installed = true;
|
|
||||||
status.currentVersion = prometheus.spec.template.spec.containers[0].image.split(":")[1];
|
|
||||||
status.canUpgrade = semver.lt(status.currentVersion, this.latestVersion, true);
|
|
||||||
} catch {
|
|
||||||
// ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
async uninstall(cluster: Cluster): Promise<void> {
|
|
||||||
const rbacClient = cluster.getProxyKubeconfig().makeApiClient(RbacAuthorizationV1Api)
|
|
||||||
|
|
||||||
await this.deleteNamespace(cluster.getProxyKubeconfig(), "lens-metrics")
|
|
||||||
await rbacClient.deleteClusterRole("lens-prometheus");
|
|
||||||
await rbacClient.deleteClusterRoleBinding("lens-prometheus");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
import { Feature, FeatureStatus } from "../main/feature"
|
|
||||||
import {KubeConfig, RbacAuthorizationV1Api} from "@kubernetes/client-node"
|
|
||||||
import { Cluster } from "../main/cluster"
|
|
||||||
|
|
||||||
export class UserModeFeature extends Feature {
|
|
||||||
static id = 'user-mode'
|
|
||||||
name = UserModeFeature.id;
|
|
||||||
latestVersion = "v2.0.0"
|
|
||||||
|
|
||||||
async install(cluster: Cluster): Promise<void> {
|
|
||||||
return super.install(cluster)
|
|
||||||
}
|
|
||||||
|
|
||||||
async upgrade(cluster: Cluster): Promise<void> {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
async featureStatus(kc: KubeConfig): Promise<FeatureStatus> {
|
|
||||||
const client = kc.makeApiClient(RbacAuthorizationV1Api)
|
|
||||||
const status: FeatureStatus = {
|
|
||||||
currentVersion: null,
|
|
||||||
installed: false,
|
|
||||||
latestVersion: this.latestVersion,
|
|
||||||
canUpgrade: false, // Dunno yet
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.readClusterRoleBinding("lens-user")
|
|
||||||
status.installed = true;
|
|
||||||
status.currentVersion = this.latestVersion;
|
|
||||||
status.canUpgrade = false;
|
|
||||||
} catch {
|
|
||||||
// ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
async uninstall(cluster: Cluster): Promise<void> {
|
|
||||||
const rbacClient = cluster.getProxyKubeconfig().makeApiClient(RbacAuthorizationV1Api)
|
|
||||||
await rbacClient.deleteClusterRole("lens-user");
|
|
||||||
await rbacClient.deleteClusterRoleBinding("lens-user");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRole
|
|
||||||
metadata:
|
|
||||||
name: lens-user
|
|
||||||
rules:
|
|
||||||
- verbs:
|
|
||||||
- list
|
|
||||||
apiGroups:
|
|
||||||
- ''
|
|
||||||
resources:
|
|
||||||
- namespaces
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRoleBinding
|
|
||||||
metadata:
|
|
||||||
name: lens-user
|
|
||||||
subjects:
|
|
||||||
- kind: Group
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
name: 'system:authenticated'
|
|
||||||
roleRef:
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
kind: ClusterRole
|
|
||||||
name: lens-user
|
|
||||||
@ -49,6 +49,8 @@ export class ClusterManager {
|
|||||||
// we need to swap path prefix so that request is proxied to kube api
|
// we need to swap path prefix so that request is proxied to kube api
|
||||||
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix)
|
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix)
|
||||||
}
|
}
|
||||||
|
} else if (req.headers["x-cluster-id"]) {
|
||||||
|
cluster = clusterStore.getById(req.headers["x-cluster-id"].toString())
|
||||||
} else {
|
} else {
|
||||||
const clusterId = getClusterIdFromHost(req.headers.host);
|
const clusterId = getClusterIdFromHost(req.headers.host);
|
||||||
cluster = clusterStore.getById(clusterId)
|
cluster = clusterStore.getById(clusterId)
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences } from "../common/cluster-store"
|
import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences } from "../common/cluster-store"
|
||||||
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
|
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
|
||||||
import type { WorkspaceId } from "../common/workspace-store";
|
import type { WorkspaceId } from "../common/workspace-store";
|
||||||
import type { FeatureStatusMap } from "./feature"
|
|
||||||
import { action, computed, observable, reaction, toJS, when } from "mobx";
|
import { action, computed, observable, reaction, toJS, when } from "mobx";
|
||||||
import { apiKubePrefix } from "../common/vars";
|
import { apiKubePrefix } from "../common/vars";
|
||||||
import { broadcastIpc } from "../common/ipc";
|
import { broadcastIpc } from "../common/ipc";
|
||||||
@ -10,7 +9,6 @@ import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from
|
|||||||
import { Kubectl } from "./kubectl";
|
import { Kubectl } from "./kubectl";
|
||||||
import { KubeconfigManager } from "./kubeconfig-manager"
|
import { KubeconfigManager } from "./kubeconfig-manager"
|
||||||
import { getNodeWarningConditions, loadConfig, podHasIssues } from "../common/kube-helpers"
|
import { getNodeWarningConditions, loadConfig, podHasIssues } from "../common/kube-helpers"
|
||||||
import { getFeatures, installFeature, uninstallFeature, upgradeFeature } from "./feature-manager";
|
|
||||||
import request, { RequestPromiseOptions } from "request-promise-native"
|
import request, { RequestPromiseOptions } from "request-promise-native"
|
||||||
import { apiResources } from "../common/rbac";
|
import { apiResources } from "../common/rbac";
|
||||||
import logger from "./logger"
|
import logger from "./logger"
|
||||||
@ -47,7 +45,6 @@ export interface ClusterState extends ClusterModel {
|
|||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
allowedNamespaces: string[]
|
allowedNamespaces: string[]
|
||||||
allowedResources: string[]
|
allowedResources: string[]
|
||||||
features: FeatureStatusMap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Cluster implements ClusterModel {
|
export class Cluster implements ClusterModel {
|
||||||
@ -78,7 +75,6 @@ export class Cluster implements ClusterModel {
|
|||||||
@observable eventCount = 0;
|
@observable eventCount = 0;
|
||||||
@observable preferences: ClusterPreferences = {};
|
@observable preferences: ClusterPreferences = {};
|
||||||
@observable metadata: ClusterMetadata = {};
|
@observable metadata: ClusterMetadata = {};
|
||||||
@observable features: FeatureStatusMap = {};
|
|
||||||
@observable allowedNamespaces: string[] = [];
|
@observable allowedNamespaces: string[] = [];
|
||||||
@observable allowedResources: string[] = [];
|
@observable allowedResources: string[] = [];
|
||||||
|
|
||||||
@ -194,12 +190,7 @@ export class Cluster implements ClusterModel {
|
|||||||
await this.whenInitialized;
|
await this.whenInitialized;
|
||||||
await this.refreshConnectionStatus();
|
await this.refreshConnectionStatus();
|
||||||
if (this.accessible) {
|
if (this.accessible) {
|
||||||
const [features, isAdmin] = await Promise.all([
|
this.isAdmin = await this.isClusterAdmin();
|
||||||
getFeatures(this),
|
|
||||||
this.isClusterAdmin(),
|
|
||||||
]);
|
|
||||||
this.features = features;
|
|
||||||
this.isAdmin = isAdmin;
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.refreshEvents(),
|
this.refreshEvents(),
|
||||||
this.refreshAllowedResources(),
|
this.refreshAllowedResources(),
|
||||||
@ -250,18 +241,6 @@ export class Cluster implements ClusterModel {
|
|||||||
return this.kubeconfigManager.getPath()
|
return this.kubeconfigManager.getPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
async installFeature(name: string, config: any) {
|
|
||||||
return installFeature(name, this, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
async upgradeFeature(name: string, config: any) {
|
|
||||||
return upgradeFeature(name, this, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
async uninstallFeature(name: string) {
|
|
||||||
return uninstallFeature(name, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
|
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
|
||||||
const apiUrl = this.kubeProxyUrl + path;
|
const apiUrl = this.kubeProxyUrl + path;
|
||||||
return request(apiUrl, {
|
return request(apiUrl, {
|
||||||
@ -400,7 +379,6 @@ export class Cluster implements ClusterModel {
|
|||||||
accessible: this.accessible,
|
accessible: this.accessible,
|
||||||
failureReason: this.failureReason,
|
failureReason: this.failureReason,
|
||||||
isAdmin: this.isAdmin,
|
isAdmin: this.isAdmin,
|
||||||
features: this.features,
|
|
||||||
eventCount: this.eventCount,
|
eventCount: this.eventCount,
|
||||||
allowedNamespaces: this.allowedNamespaces,
|
allowedNamespaces: this.allowedNamespaces,
|
||||||
allowedResources: this.allowedResources,
|
allowedResources: this.allowedResources,
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
import { KubeConfig } from "@kubernetes/client-node"
|
|
||||||
import logger from "./logger";
|
|
||||||
import { Cluster } from "./cluster";
|
|
||||||
import { Feature, FeatureStatusMap, FeatureMap } from "./feature"
|
|
||||||
import { MetricsFeature } from "../features/metrics"
|
|
||||||
import { UserModeFeature } from "../features/user-mode"
|
|
||||||
|
|
||||||
const ALL_FEATURES: Map<string, Feature> = new Map([
|
|
||||||
[MetricsFeature.id, new MetricsFeature(null)],
|
|
||||||
[UserModeFeature.id, new UserModeFeature(null)],
|
|
||||||
]);
|
|
||||||
|
|
||||||
export async function getFeatures(cluster: Cluster): Promise<FeatureStatusMap> {
|
|
||||||
const result: FeatureStatusMap = {};
|
|
||||||
logger.debug(`features for ${cluster.contextName}`);
|
|
||||||
|
|
||||||
for (const [key, feature] of ALL_FEATURES) {
|
|
||||||
logger.debug(`feature ${key}`);
|
|
||||||
logger.debug("getting feature status...");
|
|
||||||
|
|
||||||
const kc = new KubeConfig();
|
|
||||||
kc.loadFromFile(cluster.getProxyKubeconfigPath());
|
|
||||||
|
|
||||||
result[feature.name] = await feature.featureStatus(kc);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`getFeatures resolving with features: ${JSON.stringify(result)}`);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function installFeature(name: string, cluster: Cluster, config: any): Promise<void> {
|
|
||||||
// TODO Figure out how to handle config stuff
|
|
||||||
return ALL_FEATURES.get(name).install(cluster)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function upgradeFeature(name: string, cluster: Cluster, config: any): Promise<void> {
|
|
||||||
// TODO Figure out how to handle config stuff
|
|
||||||
return ALL_FEATURES.get(name).upgrade(cluster)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function uninstallFeature(name: string, cluster: Cluster): Promise<void> {
|
|
||||||
return ALL_FEATURES.get(name).uninstall(cluster)
|
|
||||||
}
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import path from "path"
|
|
||||||
import hb from "handlebars"
|
|
||||||
import { ResourceApplier } from "./resource-applier"
|
|
||||||
import { CoreV1Api, KubeConfig, Watch } from "@kubernetes/client-node"
|
|
||||||
import { Cluster } from "./cluster";
|
|
||||||
import logger from "./logger";
|
|
||||||
import { isDevelopment } from "../common/vars";
|
|
||||||
|
|
||||||
export type FeatureStatusMap = Record<string, FeatureStatus>
|
|
||||||
export type FeatureMap = Record<string, Feature>
|
|
||||||
|
|
||||||
export interface FeatureInstallRequest {
|
|
||||||
clusterId: string;
|
|
||||||
name: string;
|
|
||||||
config?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FeatureStatus {
|
|
||||||
currentVersion: string;
|
|
||||||
installed: boolean;
|
|
||||||
latestVersion: string;
|
|
||||||
canUpgrade: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class Feature {
|
|
||||||
public name: string;
|
|
||||||
public latestVersion: string;
|
|
||||||
|
|
||||||
abstract async upgrade(cluster: Cluster): Promise<void>;
|
|
||||||
|
|
||||||
abstract async uninstall(cluster: Cluster): Promise<void>;
|
|
||||||
|
|
||||||
abstract async featureStatus(kc: KubeConfig): Promise<FeatureStatus>;
|
|
||||||
|
|
||||||
constructor(public config: any) {
|
|
||||||
}
|
|
||||||
|
|
||||||
get folderPath() {
|
|
||||||
if (isDevelopment) {
|
|
||||||
return path.resolve(__static, "../src/features", this.name);
|
|
||||||
}
|
|
||||||
return path.resolve(__static, "../features", this.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
async install(cluster: Cluster): Promise<void> {
|
|
||||||
const resources = this.renderTemplates();
|
|
||||||
try {
|
|
||||||
await new ResourceApplier(cluster).kubectlApplyAll(resources);
|
|
||||||
} catch (err) {
|
|
||||||
logger.error("Installing feature error", { err, cluster });
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async deleteNamespace(kc: KubeConfig, name: string) {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
const client = kc.makeApiClient(CoreV1Api)
|
|
||||||
const result = await client.deleteNamespace("lens-metrics", 'false', undefined, undefined, undefined, "Foreground");
|
|
||||||
const nsVersion = result.body.metadata.resourceVersion;
|
|
||||||
const nsWatch = new Watch(kc);
|
|
||||||
const query: Record<string, string> = {
|
|
||||||
resourceVersion: nsVersion,
|
|
||||||
fieldSelector: "metadata.name=lens-metrics",
|
|
||||||
}
|
|
||||||
const req = await nsWatch.watch('/api/v1/namespaces', query,
|
|
||||||
(phase, obj) => {
|
|
||||||
if (phase === 'DELETED') {
|
|
||||||
logger.debug(`namespace ${name} finally gone`)
|
|
||||||
req.abort();
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err?: any) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderTemplates(): string[] {
|
|
||||||
const folderPath = this.folderPath;
|
|
||||||
const resources: string[] = [];
|
|
||||||
logger.info(`[FEATURE]: render templates from ${folderPath}`);
|
|
||||||
fs.readdirSync(folderPath).forEach(filename => {
|
|
||||||
const file = path.join(folderPath, filename);
|
|
||||||
const raw = fs.readFileSync(file);
|
|
||||||
if (filename.endsWith('.hb')) {
|
|
||||||
const template = hb.compile(raw.toString());
|
|
||||||
resources.push(template(this.config));
|
|
||||||
} else {
|
|
||||||
resources.push(raw.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return resources;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,11 +3,10 @@ import { KubeApi } from "../kube-api";
|
|||||||
|
|
||||||
export class ClusterRoleBinding extends RoleBinding {
|
export class ClusterRoleBinding extends RoleBinding {
|
||||||
static kind = "ClusterRoleBinding"
|
static kind = "ClusterRoleBinding"
|
||||||
|
static namespaced = false
|
||||||
|
static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clusterRoleBindingApi = new KubeApi({
|
export const clusterRoleBindingApi = new KubeApi({
|
||||||
kind: ClusterRoleBinding.kind,
|
|
||||||
apiBase: "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings",
|
|
||||||
isNamespaced: false,
|
|
||||||
objectConstructor: ClusterRoleBinding,
|
objectConstructor: ClusterRoleBinding,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,11 +5,10 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class ClusterRole extends Role {
|
export class ClusterRole extends Role {
|
||||||
static kind = "ClusterRole"
|
static kind = "ClusterRole"
|
||||||
|
static namespaced = false
|
||||||
|
static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterroles"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clusterRoleApi = new KubeApi({
|
export const clusterRoleApi = new KubeApi({
|
||||||
kind: ClusterRole.kind,
|
|
||||||
apiBase: "/apis/rbac.authorization.k8s.io/v1/clusterroles",
|
|
||||||
isNamespaced: false,
|
|
||||||
objectConstructor: ClusterRole,
|
objectConstructor: ClusterRole,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,6 +3,9 @@ import { KubeObject } from "../kube-object";
|
|||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi } from "../kube-api";
|
||||||
|
|
||||||
export class ClusterApi extends KubeApi<Cluster> {
|
export class ClusterApi extends KubeApi<Cluster> {
|
||||||
|
static kind = "Cluster"
|
||||||
|
static namespaced = true
|
||||||
|
|
||||||
async getMetrics(nodeNames: string[], params?: IMetricsReqParams): Promise<IClusterMetrics> {
|
async getMetrics(nodeNames: string[], params?: IMetricsReqParams): Promise<IClusterMetrics> {
|
||||||
const nodes = nodeNames.join("|");
|
const nodes = nodeNames.join("|");
|
||||||
const opts = { category: "cluster", nodes: nodes }
|
const opts = { category: "cluster", nodes: nodes }
|
||||||
@ -49,6 +52,7 @@ export interface IClusterMetrics<T = IMetrics> {
|
|||||||
|
|
||||||
export class Cluster extends KubeObject {
|
export class Cluster extends KubeObject {
|
||||||
static kind = "Cluster";
|
static kind = "Cluster";
|
||||||
|
static apiBase = "/apis/cluster.k8s.io/v1alpha1/clusters"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
clusterNetwork?: {
|
clusterNetwork?: {
|
||||||
@ -91,8 +95,5 @@ export class Cluster extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const clusterApi = new ClusterApi({
|
export const clusterApi = new ClusterApi({
|
||||||
kind: Cluster.kind,
|
|
||||||
apiBase: "/apis/cluster.k8s.io/v1alpha1/clusters",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: Cluster,
|
objectConstructor: Cluster,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,6 +9,8 @@ export interface IComponentStatusCondition {
|
|||||||
|
|
||||||
export class ComponentStatus extends KubeObject {
|
export class ComponentStatus extends KubeObject {
|
||||||
static kind = "ComponentStatus"
|
static kind = "ComponentStatus"
|
||||||
|
static namespaced = false
|
||||||
|
static apiBase = "/api/v1/componentstatuses"
|
||||||
|
|
||||||
conditions: IComponentStatusCondition[]
|
conditions: IComponentStatusCondition[]
|
||||||
|
|
||||||
@ -18,8 +20,5 @@ export class ComponentStatus extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const componentStatusApi = new KubeApi({
|
export const componentStatusApi = new KubeApi({
|
||||||
kind: ComponentStatus.kind,
|
|
||||||
apiBase: "/api/v1/componentstatuses",
|
|
||||||
isNamespaced: false,
|
|
||||||
objectConstructor: ComponentStatus,
|
objectConstructor: ComponentStatus,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class ConfigMap extends KubeObject {
|
export class ConfigMap extends KubeObject {
|
||||||
static kind = "ConfigMap";
|
static kind = "ConfigMap";
|
||||||
|
static namespaced = true;
|
||||||
|
static apiBase = "/api/v1/configmaps"
|
||||||
|
|
||||||
constructor(data: KubeJsonApiData) {
|
constructor(data: KubeJsonApiData) {
|
||||||
super(data);
|
super(data);
|
||||||
@ -22,8 +24,5 @@ export class ConfigMap extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const configMapApi = new KubeApi({
|
export const configMapApi = new KubeApi({
|
||||||
kind: ConfigMap.kind,
|
|
||||||
apiBase: "/api/v1/configmaps",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: ConfigMap,
|
objectConstructor: ConfigMap,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,6 +19,8 @@ type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & {
|
|||||||
|
|
||||||
export class CustomResourceDefinition extends KubeObject {
|
export class CustomResourceDefinition extends KubeObject {
|
||||||
static kind = "CustomResourceDefinition";
|
static kind = "CustomResourceDefinition";
|
||||||
|
static namespaced = false;
|
||||||
|
static apiBase = "/apis/apiextensions.k8s.io/v1/customresourcedefinitions"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
group: string;
|
group: string;
|
||||||
@ -145,9 +147,6 @@ export class CustomResourceDefinition extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const crdApi = new VersionedKubeApi<CustomResourceDefinition>({
|
export const crdApi = new VersionedKubeApi<CustomResourceDefinition>({
|
||||||
kind: CustomResourceDefinition.kind,
|
|
||||||
apiBase: "/apis/apiextensions.k8s.io/v1/customresourcedefinitions",
|
|
||||||
isNamespaced: false,
|
|
||||||
objectConstructor: CustomResourceDefinition
|
objectConstructor: CustomResourceDefinition
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class CronJob extends KubeObject {
|
export class CronJob extends KubeObject {
|
||||||
static kind = "CronJob"
|
static kind = "CronJob"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/apis/batch/v1beta1/cronjobs"
|
||||||
|
|
||||||
kind: string
|
kind: string
|
||||||
apiVersion: string
|
apiVersion: string
|
||||||
@ -88,8 +90,5 @@ export class CronJob extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const cronJobApi = new KubeApi({
|
export const cronJobApi = new KubeApi({
|
||||||
kind: CronJob.kind,
|
|
||||||
apiBase: "/apis/batch/v1beta1/cronjobs",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: CronJob,
|
objectConstructor: CronJob,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class DaemonSet extends WorkloadKubeObject {
|
export class DaemonSet extends WorkloadKubeObject {
|
||||||
static kind = "DaemonSet"
|
static kind = "DaemonSet"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/apis/apps/v1/daemonsets"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
selector: {
|
selector: {
|
||||||
@ -69,8 +71,5 @@ export class DaemonSet extends WorkloadKubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const daemonSetApi = new KubeApi({
|
export const daemonSetApi = new KubeApi({
|
||||||
kind: DaemonSet.kind,
|
|
||||||
apiBase: "/apis/apps/v1/daemonsets",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: DaemonSet,
|
objectConstructor: DaemonSet,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -28,6 +28,8 @@ export class DeploymentApi extends KubeApi<Deployment> {
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class Deployment extends WorkloadKubeObject {
|
export class Deployment extends WorkloadKubeObject {
|
||||||
static kind = "Deployment"
|
static kind = "Deployment"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/apis/apps/v1/deployments"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
replicas: number;
|
replicas: number;
|
||||||
@ -164,8 +166,5 @@ export class Deployment extends WorkloadKubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const deploymentApi = new DeploymentApi({
|
export const deploymentApi = new DeploymentApi({
|
||||||
kind: Deployment.kind,
|
|
||||||
apiBase: "/apis/apps/v1/deployments",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: Deployment,
|
objectConstructor: Deployment,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -99,6 +99,8 @@ export class EndpointSubset implements IEndpointSubset {
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class Endpoint extends KubeObject {
|
export class Endpoint extends KubeObject {
|
||||||
static kind = "Endpoints"
|
static kind = "Endpoints"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/api/v1/endpoints"
|
||||||
|
|
||||||
subsets: IEndpointSubset[]
|
subsets: IEndpointSubset[]
|
||||||
|
|
||||||
@ -118,8 +120,5 @@ export class Endpoint extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const endpointApi = new KubeApi({
|
export const endpointApi = new KubeApi({
|
||||||
kind: Endpoint.kind,
|
|
||||||
apiBase: "/api/v1/endpoints",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: Endpoint,
|
objectConstructor: Endpoint,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class KubeEvent extends KubeObject {
|
export class KubeEvent extends KubeObject {
|
||||||
static kind = "Event"
|
static kind = "Event"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/api/v1/events"
|
||||||
|
|
||||||
involvedObject: {
|
involvedObject: {
|
||||||
kind: string;
|
kind: string;
|
||||||
@ -52,8 +54,5 @@ export class KubeEvent extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const eventApi = new KubeApi({
|
export const eventApi = new KubeApi({
|
||||||
kind: KubeEvent.kind,
|
|
||||||
apiBase: "/api/v1/events",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: KubeEvent,
|
objectConstructor: KubeEvent,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -40,6 +40,8 @@ export interface IHpaMetric {
|
|||||||
|
|
||||||
export class HorizontalPodAutoscaler extends KubeObject {
|
export class HorizontalPodAutoscaler extends KubeObject {
|
||||||
static kind = "HorizontalPodAutoscaler";
|
static kind = "HorizontalPodAutoscaler";
|
||||||
|
static namespaced = true;
|
||||||
|
static apiBase = "/apis/autoscaling/v2beta1/horizontalpodautoscalers"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
scaleTargetRef: {
|
scaleTargetRef: {
|
||||||
@ -133,8 +135,5 @@ export class HorizontalPodAutoscaler extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const hpaApi = new KubeApi({
|
export const hpaApi = new KubeApi({
|
||||||
kind: HorizontalPodAutoscaler.kind,
|
|
||||||
apiBase: "/apis/autoscaling/v2beta1/horizontalpodautoscalers",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: HorizontalPodAutoscaler,
|
objectConstructor: HorizontalPodAutoscaler,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -32,6 +32,8 @@ export interface ILoadBalancerIngress {
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class Ingress extends KubeObject {
|
export class Ingress extends KubeObject {
|
||||||
static kind = "Ingress"
|
static kind = "Ingress"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/apis/extensions/v1beta1/ingresses"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
tls: {
|
tls: {
|
||||||
@ -110,8 +112,5 @@ export class Ingress extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ingressApi = new IngressApi({
|
export const ingressApi = new IngressApi({
|
||||||
kind: Ingress.kind,
|
|
||||||
apiBase: "/apis/extensions/v1beta1/ingresses",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: Ingress,
|
objectConstructor: Ingress,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import { JsonApiParams } from "../json-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class Job extends WorkloadKubeObject {
|
export class Job extends WorkloadKubeObject {
|
||||||
static kind = "Job"
|
static kind = "Job"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/apis/batch/v1/jobs"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
parallelism?: number;
|
parallelism?: number;
|
||||||
@ -102,8 +104,5 @@ export class Job extends WorkloadKubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const jobApi = new KubeApi({
|
export const jobApi = new KubeApi({
|
||||||
kind: Job.kind,
|
|
||||||
apiBase: "/apis/batch/v1/jobs",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: Job,
|
objectConstructor: Job,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,6 +10,8 @@ export enum NamespaceStatus {
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class Namespace extends KubeObject {
|
export class Namespace extends KubeObject {
|
||||||
static kind = "Namespace";
|
static kind = "Namespace";
|
||||||
|
static namespaced = false;
|
||||||
|
static apiBase = "/api/v1/namespaces";
|
||||||
|
|
||||||
status?: {
|
status?: {
|
||||||
phase: string;
|
phase: string;
|
||||||
@ -21,8 +23,5 @@ export class Namespace extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const namespacesApi = new KubeApi({
|
export const namespacesApi = new KubeApi({
|
||||||
kind: Namespace.kind,
|
|
||||||
apiBase: "/api/v1/namespaces",
|
|
||||||
isNamespaced: false,
|
|
||||||
objectConstructor: Namespace,
|
objectConstructor: Namespace,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -38,6 +38,8 @@ export interface IPolicyEgress {
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class NetworkPolicy extends KubeObject {
|
export class NetworkPolicy extends KubeObject {
|
||||||
static kind = "NetworkPolicy"
|
static kind = "NetworkPolicy"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/apis/networking.k8s.io/v1/networkpolicies"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
podSelector: {
|
podSelector: {
|
||||||
@ -65,8 +67,5 @@ export class NetworkPolicy extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const networkPolicyApi = new KubeApi({
|
export const networkPolicyApi = new KubeApi({
|
||||||
kind: NetworkPolicy.kind,
|
|
||||||
apiBase: "/apis/networking.k8s.io/v1/networkpolicies",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: NetworkPolicy,
|
objectConstructor: NetworkPolicy,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -31,6 +31,8 @@ export interface INodeMetrics<T = IMetrics> {
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class Node extends KubeObject {
|
export class Node extends KubeObject {
|
||||||
static kind = "Node"
|
static kind = "Node"
|
||||||
|
static namespaced = false
|
||||||
|
static apiBase = "/api/v1/nodes"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
podCIDR: string;
|
podCIDR: string;
|
||||||
@ -156,8 +158,5 @@ export class Node extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const nodesApi = new NodesApi({
|
export const nodesApi = new NodesApi({
|
||||||
kind: Node.kind,
|
|
||||||
apiBase: "/api/v1/nodes",
|
|
||||||
isNamespaced: false,
|
|
||||||
objectConstructor: Node,
|
objectConstructor: Node,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -24,6 +24,8 @@ export interface IPvcMetrics<T = IMetrics> {
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class PersistentVolumeClaim extends KubeObject {
|
export class PersistentVolumeClaim extends KubeObject {
|
||||||
static kind = "PersistentVolumeClaim"
|
static kind = "PersistentVolumeClaim"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/api/v1/persistentvolumeclaims"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
accessModes: string[];
|
accessModes: string[];
|
||||||
@ -81,8 +83,5 @@ export class PersistentVolumeClaim extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const pvcApi = new PersistentVolumeClaimsApi({
|
export const pvcApi = new PersistentVolumeClaimsApi({
|
||||||
kind: PersistentVolumeClaim.kind,
|
|
||||||
apiBase: "/api/v1/persistentvolumeclaims",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: PersistentVolumeClaim,
|
objectConstructor: PersistentVolumeClaim,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class PersistentVolume extends KubeObject {
|
export class PersistentVolume extends KubeObject {
|
||||||
static kind = "PersistentVolume"
|
static kind = "PersistentVolume"
|
||||||
|
static namespaced = false
|
||||||
|
static apiBase = "/api/v1/persistentvolumes"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
capacity: {
|
capacity: {
|
||||||
@ -64,8 +66,5 @@ export class PersistentVolume extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const persistentVolumeApi = new KubeApi({
|
export const persistentVolumeApi = new KubeApi({
|
||||||
kind: PersistentVolume.kind,
|
|
||||||
apiBase: "/api/v1/persistentvolumes",
|
|
||||||
isNamespaced: false,
|
|
||||||
objectConstructor: PersistentVolume,
|
objectConstructor: PersistentVolume,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,6 +2,10 @@ import { KubeObject } from "../kube-object";
|
|||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi } from "../kube-api";
|
||||||
|
|
||||||
export class PodMetrics extends KubeObject {
|
export class PodMetrics extends KubeObject {
|
||||||
|
static kind = "Pod"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/apis/metrics.k8s.io/v1beta1/pods"
|
||||||
|
|
||||||
timestamp: string
|
timestamp: string
|
||||||
window: string
|
window: string
|
||||||
containers: {
|
containers: {
|
||||||
@ -14,8 +18,5 @@ export class PodMetrics extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const podMetricsApi = new KubeApi({
|
export const podMetricsApi = new KubeApi({
|
||||||
kind: PodMetrics.kind,
|
|
||||||
apiBase: "/apis/metrics.k8s.io/v1beta1/pods",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: PodMetrics,
|
objectConstructor: PodMetrics,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class PodDisruptionBudget extends KubeObject {
|
export class PodDisruptionBudget extends KubeObject {
|
||||||
static kind = "PodDisruptionBudget";
|
static kind = "PodDisruptionBudget";
|
||||||
|
static namespaced = true;
|
||||||
|
static apiBase = "/apis/policy/v1beta1/poddisruptionbudgets";
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
minAvailable: string;
|
minAvailable: string;
|
||||||
@ -42,8 +44,5 @@ export class PodDisruptionBudget extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const pdbApi = new KubeApi({
|
export const pdbApi = new KubeApi({
|
||||||
kind: PodDisruptionBudget.kind,
|
|
||||||
apiBase: "/apis/policy/v1beta1/poddisruptionbudgets",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: PodDisruptionBudget,
|
objectConstructor: PodDisruptionBudget,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -163,6 +163,8 @@ export interface IPodContainerStatus {
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class Pod extends WorkloadKubeObject {
|
export class Pod extends WorkloadKubeObject {
|
||||||
static kind = "Pod"
|
static kind = "Pod"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/api/v1/pods"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
volumes?: {
|
volumes?: {
|
||||||
@ -419,8 +421,5 @@ export class Pod extends WorkloadKubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const podsApi = new PodsApi({
|
export const podsApi = new PodsApi({
|
||||||
kind: Pod.kind,
|
|
||||||
apiBase: "/api/v1/pods",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: Pod,
|
objectConstructor: Pod,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class PodSecurityPolicy extends KubeObject {
|
export class PodSecurityPolicy extends KubeObject {
|
||||||
static kind = "PodSecurityPolicy"
|
static kind = "PodSecurityPolicy"
|
||||||
|
static namespaced = false
|
||||||
|
static apiBase = "/apis/policy/v1beta1/podsecuritypolicies"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
allowPrivilegeEscalation?: boolean;
|
allowPrivilegeEscalation?: boolean;
|
||||||
@ -87,8 +89,5 @@ export class PodSecurityPolicy extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const pspApi = new KubeApi({
|
export const pspApi = new KubeApi({
|
||||||
kind: PodSecurityPolicy.kind,
|
|
||||||
apiBase: "/apis/policy/v1beta1/podsecuritypolicies",
|
|
||||||
isNamespaced: false,
|
|
||||||
objectConstructor: PodSecurityPolicy,
|
objectConstructor: PodSecurityPolicy,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class ReplicaSet extends WorkloadKubeObject {
|
export class ReplicaSet extends WorkloadKubeObject {
|
||||||
static kind = "ReplicaSet"
|
static kind = "ReplicaSet"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/apis/apps/v1/replicasets"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
replicas?: number;
|
replicas?: number;
|
||||||
@ -51,8 +53,5 @@ export class ReplicaSet extends WorkloadKubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const replicaSetApi = new KubeApi({
|
export const replicaSetApi = new KubeApi({
|
||||||
kind: ReplicaSet.kind,
|
|
||||||
apiBase: "/apis/apps/v1/replicasets",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: ReplicaSet,
|
objectConstructor: ReplicaSet,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -32,6 +32,8 @@ export interface IResourceQuotaValues {
|
|||||||
|
|
||||||
export class ResourceQuota extends KubeObject {
|
export class ResourceQuota extends KubeObject {
|
||||||
static kind = "ResourceQuota"
|
static kind = "ResourceQuota"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/api/v1/resourcequotas"
|
||||||
|
|
||||||
constructor(data: KubeJsonApiData) {
|
constructor(data: KubeJsonApiData) {
|
||||||
super(data);
|
super(data);
|
||||||
@ -61,8 +63,5 @@ export class ResourceQuota extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const resourceQuotaApi = new KubeApi({
|
export const resourceQuotaApi = new KubeApi({
|
||||||
kind: ResourceQuota.kind,
|
|
||||||
apiBase: "/api/v1/resourcequotas",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: ResourceQuota,
|
objectConstructor: ResourceQuota,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,6 +12,8 @@ export interface IRoleBindingSubject {
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class RoleBinding extends KubeObject {
|
export class RoleBinding extends KubeObject {
|
||||||
static kind = "RoleBinding"
|
static kind = "RoleBinding"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/apis/rbac.authorization.k8s.io/v1/rolebindings"
|
||||||
|
|
||||||
subjects?: IRoleBindingSubject[]
|
subjects?: IRoleBindingSubject[]
|
||||||
roleRef: {
|
roleRef: {
|
||||||
@ -30,8 +32,5 @@ export class RoleBinding extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const roleBindingApi = new KubeApi({
|
export const roleBindingApi = new KubeApi({
|
||||||
kind: RoleBinding.kind,
|
|
||||||
apiBase: "/apis/rbac.authorization.k8s.io/v1/rolebindings",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: RoleBinding,
|
objectConstructor: RoleBinding,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
|
|
||||||
export class Role extends KubeObject {
|
export class Role extends KubeObject {
|
||||||
static kind = "Role"
|
static kind = "Role"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/apis/rbac.authorization.k8s.io/v1/roles"
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
verbs: string[];
|
verbs: string[];
|
||||||
@ -17,8 +19,5 @@ export class Role extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const roleApi = new KubeApi({
|
export const roleApi = new KubeApi({
|
||||||
kind: Role.kind,
|
|
||||||
apiBase: "/apis/rbac.authorization.k8s.io/v1/roles",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: Role,
|
objectConstructor: Role,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -22,6 +22,8 @@ export interface ISecretRef {
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class Secret extends KubeObject {
|
export class Secret extends KubeObject {
|
||||||
static kind = "Secret"
|
static kind = "Secret"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/api/v1/secrets"
|
||||||
|
|
||||||
type: SecretType;
|
type: SecretType;
|
||||||
data: {
|
data: {
|
||||||
@ -44,8 +46,5 @@ export class Secret extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const secretsApi = new KubeApi({
|
export const secretsApi = new KubeApi({
|
||||||
kind: Secret.kind,
|
|
||||||
apiBase: "/api/v1/secrets",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: Secret,
|
objectConstructor: Secret,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -22,6 +22,8 @@ export interface ISelfSubjectReviewRule {
|
|||||||
|
|
||||||
export class SelfSubjectRulesReview extends KubeObject {
|
export class SelfSubjectRulesReview extends KubeObject {
|
||||||
static kind = "SelfSubjectRulesReview"
|
static kind = "SelfSubjectRulesReview"
|
||||||
|
static namespaced = false
|
||||||
|
static apiBase = "/apis/authorization.k8s.io/v1/selfsubjectrulesreviews"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
// todo: add more types from api docs
|
// todo: add more types from api docs
|
||||||
@ -61,8 +63,5 @@ export class SelfSubjectRulesReview extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const selfSubjectRulesReviewApi = new SelfSubjectRulesReviewApi({
|
export const selfSubjectRulesReviewApi = new SelfSubjectRulesReviewApi({
|
||||||
kind: SelfSubjectRulesReview.kind,
|
|
||||||
apiBase: "/apis/authorization.k8s.io/v1/selfsubjectrulesreviews",
|
|
||||||
isNamespaced: false,
|
|
||||||
objectConstructor: SelfSubjectRulesReview,
|
objectConstructor: SelfSubjectRulesReview,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class ServiceAccount extends KubeObject {
|
export class ServiceAccount extends KubeObject {
|
||||||
static kind = "ServiceAccount";
|
static kind = "ServiceAccount";
|
||||||
|
static namespaced = true;
|
||||||
|
static apiBase = "/api/v1/serviceaccounts"
|
||||||
|
|
||||||
secrets?: {
|
secrets?: {
|
||||||
name: string;
|
name: string;
|
||||||
@ -23,8 +25,5 @@ export class ServiceAccount extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const serviceAccountsApi = new KubeApi<ServiceAccount>({
|
export const serviceAccountsApi = new KubeApi<ServiceAccount>({
|
||||||
kind: ServiceAccount.kind,
|
|
||||||
apiBase: "/api/v1/serviceaccounts",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: ServiceAccount,
|
objectConstructor: ServiceAccount,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -32,6 +32,8 @@ export class ServicePort implements IServicePort {
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class Service extends KubeObject {
|
export class Service extends KubeObject {
|
||||||
static kind = "Service"
|
static kind = "Service"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/api/v1/services"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
type: string;
|
type: string;
|
||||||
@ -93,8 +95,5 @@ export class Service extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const serviceApi = new KubeApi({
|
export const serviceApi = new KubeApi({
|
||||||
kind: Service.kind,
|
|
||||||
apiBase: "/api/v1/services",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: Service,
|
objectConstructor: Service,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class StatefulSet extends WorkloadKubeObject {
|
export class StatefulSet extends WorkloadKubeObject {
|
||||||
static kind = "StatefulSet"
|
static kind = "StatefulSet"
|
||||||
|
static namespaced = true
|
||||||
|
static apiBase = "/apis/apps/v1/statefulsets"
|
||||||
|
|
||||||
spec: {
|
spec: {
|
||||||
serviceName: string;
|
serviceName: string;
|
||||||
@ -77,8 +79,5 @@ export class StatefulSet extends WorkloadKubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const statefulSetApi = new KubeApi({
|
export const statefulSetApi = new KubeApi({
|
||||||
kind: StatefulSet.kind,
|
|
||||||
apiBase: "/apis/apps/v1/statefulsets",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: StatefulSet,
|
objectConstructor: StatefulSet,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { KubeApi } from "../kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class StorageClass extends KubeObject {
|
export class StorageClass extends KubeObject {
|
||||||
static kind = "StorageClass"
|
static kind = "StorageClass"
|
||||||
|
static namespaced = false
|
||||||
|
static apiBase = "/apis/storage.k8s.io/v1/storageclasses"
|
||||||
|
|
||||||
provisioner: string; // e.g. "storage.k8s.io/v1"
|
provisioner: string; // e.g. "storage.k8s.io/v1"
|
||||||
mountOptions?: string[];
|
mountOptions?: string[];
|
||||||
@ -32,8 +34,5 @@ export class StorageClass extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const storageClassApi = new KubeApi({
|
export const storageClassApi = new KubeApi({
|
||||||
kind: StorageClass.kind,
|
|
||||||
apiBase: "/apis/storage.k8s.io/v1/storageclasses",
|
|
||||||
isNamespaced: false,
|
|
||||||
objectConstructor: StorageClass,
|
objectConstructor: StorageClass,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,13 +8,15 @@ import { apiKube } from "./index";
|
|||||||
import { kubeWatchApi } from "./kube-watch-api";
|
import { kubeWatchApi } from "./kube-watch-api";
|
||||||
import { apiManager } from "./api-manager";
|
import { apiManager } from "./api-manager";
|
||||||
import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
|
import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
|
||||||
|
import { apiKubePrefix, isDevelopment } from "../../common/vars";
|
||||||
|
import * as URL from "url"
|
||||||
|
|
||||||
export interface IKubeApiOptions<T extends KubeObject> {
|
export interface IKubeApiOptions<T extends KubeObject> {
|
||||||
kind: string; // resource type within api-group, e.g. "Namespace"
|
apiBase?: string; // base api-path for listing all resources, e.g. "/api/v1/pods"
|
||||||
apiBase: string; // base api-path for listing all resources, e.g. "/api/v1/pods"
|
|
||||||
isNamespaced: boolean;
|
|
||||||
objectConstructor?: IKubeObjectConstructor<T>;
|
objectConstructor?: IKubeObjectConstructor<T>;
|
||||||
request?: KubeJsonApi;
|
request?: KubeJsonApi;
|
||||||
|
isNamespaced?: boolean;
|
||||||
|
kind?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IKubeApiQueryParams {
|
export interface IKubeApiQueryParams {
|
||||||
@ -25,6 +27,25 @@ export interface IKubeApiQueryParams {
|
|||||||
continue?: string; // might be used with ?limit from second request
|
continue?: string; // might be used with ?limit from second request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IKubeApiCluster {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function forCluster<T extends KubeObject>(cluster: IKubeApiCluster, kubeClass: IKubeObjectConstructor<T>): KubeApi<T> {
|
||||||
|
const request = new KubeJsonApi({
|
||||||
|
apiBase: apiKubePrefix,
|
||||||
|
debug: isDevelopment,
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
"X-Cluster-ID": cluster.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new KubeApi({
|
||||||
|
objectConstructor: kubeClass,
|
||||||
|
request: request
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export class KubeApi<T extends KubeObject = any> {
|
export class KubeApi<T extends KubeObject = any> {
|
||||||
static parseApi = parseKubeApi;
|
static parseApi = parseKubeApi;
|
||||||
|
|
||||||
@ -48,11 +69,14 @@ export class KubeApi<T extends KubeObject = any> {
|
|||||||
|
|
||||||
constructor(protected options: IKubeApiOptions<T>) {
|
constructor(protected options: IKubeApiOptions<T>) {
|
||||||
const {
|
const {
|
||||||
kind,
|
|
||||||
isNamespaced = false,
|
|
||||||
objectConstructor = KubeObject as IKubeObjectConstructor,
|
objectConstructor = KubeObject as IKubeObjectConstructor,
|
||||||
request = apiKube
|
request = apiKube,
|
||||||
|
kind = options.objectConstructor?.kind,
|
||||||
|
isNamespaced = options.objectConstructor?.namespaced
|
||||||
} = options || {};
|
} = options || {};
|
||||||
|
if (!options.apiBase) {
|
||||||
|
options.apiBase = objectConstructor.apiBase
|
||||||
|
}
|
||||||
const { apiBase, apiPrefix, apiGroup, apiVersion, apiVersionWithGroup, resource } = KubeApi.parseApi(options.apiBase);
|
const { apiBase, apiPrefix, apiGroup, apiVersion, apiVersionWithGroup, resource } = KubeApi.parseApi(options.apiBase);
|
||||||
|
|
||||||
this.kind = kind;
|
this.kind = kind;
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import { resourceApplierApi } from "./endpoints/resource-applier.api";
|
|||||||
|
|
||||||
export type IKubeObjectConstructor<T extends KubeObject = any> = (new (data: KubeJsonApiData | any) => T) & {
|
export type IKubeObjectConstructor<T extends KubeObject = any> = (new (data: KubeJsonApiData | any) => T) & {
|
||||||
kind?: string;
|
kind?: string;
|
||||||
|
namespaced?: boolean;
|
||||||
|
apiBase?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IKubeObjectMetadata {
|
export interface IKubeObjectMetadata {
|
||||||
@ -43,6 +45,7 @@ export type IKubeMetaField = keyof IKubeObjectMetadata;
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class KubeObject implements ItemObject {
|
export class KubeObject implements ItemObject {
|
||||||
static readonly kind: string;
|
static readonly kind: string;
|
||||||
|
static readonly namespaced: boolean;
|
||||||
|
|
||||||
static create(data: any) {
|
static create(data: any) {
|
||||||
return new KubeObject(data);
|
return new KubeObject(data);
|
||||||
|
|||||||
@ -1,72 +1,87 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observable, reaction, comparer } from "mobx";
|
import { observable, reaction, comparer } from "mobx";
|
||||||
import { observer, disposeOnUnmount } from "mobx-react";
|
import { observer, disposeOnUnmount } from "mobx-react";
|
||||||
import { clusterIpc } from "../../../../common/cluster-ipc";
|
|
||||||
import { Cluster } from "../../../../main/cluster";
|
import { Cluster } from "../../../../main/cluster";
|
||||||
import { Button } from "../../button";
|
import { Button } from "../../button";
|
||||||
import { Notifications } from "../../notifications";
|
import { Notifications } from "../../notifications";
|
||||||
import { Spinner } from "../../spinner";
|
import { Spinner } from "../../spinner";
|
||||||
|
import { ClusterFeature } from "../../../../extensions/cluster-feature";
|
||||||
|
import { interval } from "../../../utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
cluster: Cluster
|
cluster: Cluster
|
||||||
feature: string
|
feature: ClusterFeature
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class InstallFeature extends React.Component<Props> {
|
export class InstallFeature extends React.Component<Props> {
|
||||||
@observable loading = false;
|
@observable loading = false;
|
||||||
|
@observable message = "";
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
const feature = this.props.feature
|
||||||
|
const cluster = this.props.cluster
|
||||||
|
const statusUpdate = interval(20, () => {
|
||||||
|
feature.updateStatus(cluster)
|
||||||
|
})
|
||||||
|
statusUpdate.start(true)
|
||||||
|
|
||||||
|
disposeOnUnmount(this, () => {
|
||||||
|
statusUpdate.stop()
|
||||||
|
})
|
||||||
|
|
||||||
disposeOnUnmount(this,
|
disposeOnUnmount(this,
|
||||||
reaction(() => this.props.cluster.features[this.props.feature], () => {
|
reaction(() => feature.status.installed, () => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
this.message = ""
|
||||||
}, { equals: comparer.structural })
|
}, { equals: comparer.structural })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getActionButtons() {
|
getActionButtons() {
|
||||||
const { cluster, feature } = this.props;
|
const { cluster, feature } = this.props;
|
||||||
const features = cluster.features[feature];
|
|
||||||
const disabled = !cluster.isAdmin || this.loading;
|
const disabled = !cluster.isAdmin || this.loading;
|
||||||
const loadingIcon = this.loading ? <Spinner/> : null;
|
const loadingIcon = this.loading ? <Spinner/> : null;
|
||||||
if (!features) return null;
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gaps align-center">
|
<div className="flex gaps align-center">
|
||||||
{features.canUpgrade &&
|
{feature.status.canUpgrade &&
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={this.runAction(() =>
|
onClick={this.runAction(() =>
|
||||||
clusterIpc.upgradeFeature.invokeFromRenderer(cluster.id, feature))
|
feature.upgrade(cluster)
|
||||||
}
|
)}
|
||||||
>
|
>
|
||||||
Upgrade
|
Upgrade
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
{features.installed &&
|
{feature.status.installed &&
|
||||||
<Button
|
<Button
|
||||||
accent
|
accent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={this.runAction(() =>
|
onClick={this.runAction(async () => {
|
||||||
clusterIpc.uninstallFeature.invokeFromRenderer(cluster.id, feature))
|
this.message = "Uninstalling feature ..."
|
||||||
}
|
feature.uninstall(cluster)
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
Uninstall
|
Uninstall
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
{!features.installed && !features.canUpgrade &&
|
{!feature.status.installed && !feature.status.canUpgrade &&
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={this.runAction(() =>
|
onClick={this.runAction(async () =>{
|
||||||
clusterIpc.installFeature.invokeFromRenderer(cluster.id, feature))
|
this.message = "Installing feature ..."
|
||||||
}
|
feature.install(cluster)
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
Install
|
Install
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
{loadingIcon}
|
{loadingIcon}
|
||||||
{!cluster.isAdmin && <span className='admin-note'>Actions can only be performed by admins.</span>}
|
{!cluster.isAdmin && <span className='admin-note'>Actions can only be performed by admins.</span>}
|
||||||
|
{cluster.isAdmin && this.loading && this.message !== "" && <span className='admin-note'>{this.message}</span>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,7 @@ import React from "react";
|
|||||||
import { Cluster } from "../../../main/cluster";
|
import { Cluster } from "../../../main/cluster";
|
||||||
import { InstallFeature } from "./components/install-feature";
|
import { InstallFeature } from "./components/install-feature";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import { MetricsFeature } from "../../../features/metrics";
|
import { clusterFeatureRegistry } from "../../../extensions/registries/cluster-feature-registry";
|
||||||
import { UserModeFeature } from "../../../features/user-mode";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
cluster: Cluster;
|
cluster: Cluster;
|
||||||
@ -12,30 +11,19 @@ interface Props {
|
|||||||
export class Features extends React.Component<Props> {
|
export class Features extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { cluster } = this.props;
|
const { cluster } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>Features</h2>
|
<h2>Features</h2>
|
||||||
<InstallFeature cluster={cluster} feature={MetricsFeature.id}>
|
{ clusterFeatureRegistry.getItems().map((f) => {
|
||||||
|
return (
|
||||||
|
<InstallFeature cluster={cluster} feature={f.feature}>
|
||||||
<>
|
<>
|
||||||
<SubTitle title="Metrics"/>
|
<SubTitle title={f.title}/>
|
||||||
<p>
|
<p><f.components.Description /></p>
|
||||||
Enable timeseries data visualization (Prometheus stack) for your cluster.
|
|
||||||
Install this only if you don't have existing Prometheus stack installed.
|
|
||||||
You can see preview of manifests{" "}
|
|
||||||
<a href="https://github.com/lensapp/lens/tree/master/src/features/metrics" target="_blank">here</a>.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
</InstallFeature>
|
|
||||||
<InstallFeature cluster={cluster} feature={UserModeFeature.id}>
|
|
||||||
<>
|
|
||||||
<SubTitle title="User Mode"/>
|
|
||||||
<p>
|
|
||||||
User Mode feature enables non-admin users to see namespaces they have access to.{" "}
|
|
||||||
This is achieved by configuring RBAC rules so that every authenticated user is granted to list namespaces.
|
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
</InstallFeature>
|
</InstallFeature>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user