mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge remote-tracking branch 'origin/extensions-api' into extension_support_page
# Conflicts: # package.json # src/extensions/core-api/registries.ts # src/extensions/extension-loader.ts # src/extensions/lens-renderer-extension.ts # src/extensions/renderer-api/components.ts
This commit is contained in:
commit
2f408ff0bc
@ -4,6 +4,7 @@ module.exports = {
|
|||||||
files: [
|
files: [
|
||||||
"src/renderer/**/*.js",
|
"src/renderer/**/*.js",
|
||||||
"build/**/*.js",
|
"build/**/*.js",
|
||||||
|
"extensions/**/*.js"
|
||||||
],
|
],
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
|
|||||||
5
extensions/node-menu/Makefile
Normal file
5
extensions/node-menu/Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
install-deps:
|
||||||
|
npm install
|
||||||
|
|
||||||
|
build: install-deps
|
||||||
|
npm run build
|
||||||
3508
extensions/node-menu/package-lock.json
generated
Normal file
3508
extensions/node-menu/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
extensions/node-menu/package.json
Normal file
22
extensions/node-menu/package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "lens-node-menu",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Lens node menu",
|
||||||
|
"renderer": "dist/renderer.js",
|
||||||
|
"lens": {
|
||||||
|
"metadata": {},
|
||||||
|
"styles": []
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --config webpack.config.js",
|
||||||
|
"dev": "npm run build --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"ts-loader": "^8.0.4",
|
||||||
|
"typescript": "^4.0.3",
|
||||||
|
"webpack": "^4.44.2",
|
||||||
|
"mobx": "^5.15.5",
|
||||||
|
"react": "^16.13.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
extensions/node-menu/renderer.tsx
Normal file
21
extensions/node-menu/renderer.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Registry, LensRendererExtension } from "@k8slens/extensions";
|
||||||
|
import React from "react"
|
||||||
|
import { NodeMenu } from "./src/node-menu"
|
||||||
|
|
||||||
|
export default class NodeMenuRendererExtension extends LensRendererExtension {
|
||||||
|
async onActivate() {
|
||||||
|
console.log("node-menu extension activated")
|
||||||
|
}
|
||||||
|
|
||||||
|
registerKubeObjectMenus(registry: Registry.KubeObjectMenuRegistry) {
|
||||||
|
this.disposers.push(
|
||||||
|
registry.add({
|
||||||
|
kind: "Node",
|
||||||
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
MenuItem: (props) => <NodeMenu {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
70
extensions/node-menu/src/node-menu.tsx
Normal file
70
extensions/node-menu/src/node-menu.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Component, K8sApi, Navigation} from "@k8slens/extensions"
|
||||||
|
|
||||||
|
export function NodeMenu(props: Component.KubeObjectMenuProps<K8sApi.Node>) {
|
||||||
|
const { object: node, toolbar } = props;
|
||||||
|
if (!node) return null;
|
||||||
|
const nodeName = node.getName();
|
||||||
|
|
||||||
|
const sendToTerminal = (command: string) => {
|
||||||
|
Component.terminalStore.sendCommand(command, {
|
||||||
|
enter: true,
|
||||||
|
newTab: true,
|
||||||
|
});
|
||||||
|
Navigation.hideDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
const shell = () => {
|
||||||
|
Component.createTerminalTab({
|
||||||
|
title: `Node: ${nodeName}`,
|
||||||
|
node: nodeName,
|
||||||
|
});
|
||||||
|
Navigation.hideDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
const cordon = () => {
|
||||||
|
sendToTerminal(`kubectl cordon ${nodeName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const unCordon = () => {
|
||||||
|
sendToTerminal(`kubectl uncordon ${nodeName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const drain = () => {
|
||||||
|
const command = `kubectl drain ${nodeName} --delete-local-data --ignore-daemonsets --force`;
|
||||||
|
Component.ConfirmDialog.open({
|
||||||
|
ok: () => sendToTerminal(command),
|
||||||
|
labelOk: `Drain Node`,
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
Are you sure you want to drain <b>{nodeName}</b>?
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Component.MenuItem onClick={shell}>
|
||||||
|
<Component.Icon svg="ssh" interactive={toolbar} title="Node shell"/>
|
||||||
|
<span className="title">Shell</span>
|
||||||
|
</Component.MenuItem>
|
||||||
|
{!node.isUnschedulable() && (
|
||||||
|
<Component.MenuItem onClick={cordon}>
|
||||||
|
<Component.Icon material="pause_circle_filled" title="Cordon" interactive={toolbar}/>
|
||||||
|
<span className="title">Cordon</span>
|
||||||
|
</Component.MenuItem>
|
||||||
|
)}
|
||||||
|
{node.isUnschedulable() && (
|
||||||
|
<Component.MenuItem onClick={unCordon}>
|
||||||
|
<Component.Icon material="play_circle_filled" title="Uncordon" interactive={toolbar}/>
|
||||||
|
<span className="title">Uncordon</span>
|
||||||
|
</Component.MenuItem>
|
||||||
|
)}
|
||||||
|
<Component.MenuItem onClick={drain}>
|
||||||
|
<Component.Icon material="delete_sweep" title="Drain" interactive={toolbar}/>
|
||||||
|
<span className="title">Drain</span>
|
||||||
|
</Component.MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
extensions/node-menu/tsconfig.json
Normal file
27
extensions/node-menu/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"
|
||||||
|
]
|
||||||
|
}
|
||||||
35
extensions/node-menu/webpack.config.js
Normal file
35
extensions/node-menu/webpack.config.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
5
extensions/pod-menu/Makefile
Normal file
5
extensions/pod-menu/Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
install-deps:
|
||||||
|
npm install
|
||||||
|
|
||||||
|
build: install-deps
|
||||||
|
npm run build
|
||||||
3508
extensions/pod-menu/package-lock.json
generated
Normal file
3508
extensions/pod-menu/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
extensions/pod-menu/package.json
Normal file
22
extensions/pod-menu/package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "lens-pod-menu",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Lens pod menu",
|
||||||
|
"renderer": "dist/renderer.js",
|
||||||
|
"lens": {
|
||||||
|
"metadata": {},
|
||||||
|
"styles": []
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --config webpack.config.js",
|
||||||
|
"dev": "npm run build --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"ts-loader": "^8.0.4",
|
||||||
|
"typescript": "^4.0.3",
|
||||||
|
"webpack": "^4.44.2",
|
||||||
|
"mobx": "^5.15.5",
|
||||||
|
"react": "^16.13.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
31
extensions/pod-menu/renderer.tsx
Normal file
31
extensions/pod-menu/renderer.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Registry, LensRendererExtension } from "@k8slens/extensions";
|
||||||
|
import { PodShellMenu } from "./src/shell-menu"
|
||||||
|
import { PodLogsMenu } from "./src/logs-menu"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export default class PodMenuRendererExtension extends LensRendererExtension {
|
||||||
|
async onActivate() {
|
||||||
|
console.log("pod-menu extension activated")
|
||||||
|
}
|
||||||
|
|
||||||
|
registerKubeObjectMenus(registry: Registry.KubeObjectMenuRegistry) {
|
||||||
|
this.disposers.push(
|
||||||
|
registry.add({
|
||||||
|
kind: "Pod",
|
||||||
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
MenuItem: (props) => <PodShellMenu {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
this.disposers.push(
|
||||||
|
registry.add({
|
||||||
|
kind: "Pod",
|
||||||
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
MenuItem: (props) => <PodLogsMenu {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
58
extensions/pod-menu/src/logs-menu.tsx
Normal file
58
extensions/pod-menu/src/logs-menu.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions";
|
||||||
|
|
||||||
|
interface Props extends Component.KubeObjectMenuProps<K8sApi.Pod> {
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PodLogsMenu extends React.Component<Props> {
|
||||||
|
showLogs(container: K8sApi.IPodContainer) {
|
||||||
|
Navigation.hideDetails();
|
||||||
|
const pod = this.props.object;
|
||||||
|
Component.createPodLogsTab({
|
||||||
|
pod,
|
||||||
|
containers: pod.getContainers(),
|
||||||
|
initContainers: pod.getInitContainers(),
|
||||||
|
selectedContainer: container,
|
||||||
|
showTimestamps: false,
|
||||||
|
previous: false,
|
||||||
|
tailLines: 1000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { object: pod, toolbar } = this.props
|
||||||
|
const containers = pod.getAllContainers();
|
||||||
|
const statuses = pod.getContainerStatuses();
|
||||||
|
if (!containers.length) return;
|
||||||
|
return (
|
||||||
|
<Component.MenuItem onClick={Util.prevDefault(() => this.showLogs(containers[0]))}>
|
||||||
|
<Component.Icon material="subject" title="Logs" interactive={toolbar}/>
|
||||||
|
<span className="title">Logs</span>
|
||||||
|
{containers.length > 1 && (
|
||||||
|
<>
|
||||||
|
<Component.Icon className="arrow" material="keyboard_arrow_right"/>
|
||||||
|
<Component.SubMenu>
|
||||||
|
{
|
||||||
|
containers.map(container => {
|
||||||
|
const { name } = container
|
||||||
|
const status = statuses.find(status => status.name === name);
|
||||||
|
const brick = status ? (
|
||||||
|
<Component.StatusBrick
|
||||||
|
className={Util.cssNames(Object.keys(status.state)[0], { ready: status.ready })}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
return (
|
||||||
|
<Component.MenuItem key={name} onClick={Util.prevDefault(() => this.showLogs(container))} className="flex align-center">
|
||||||
|
{brick}
|
||||||
|
{name}
|
||||||
|
</Component.MenuItem>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Component.SubMenu>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Component.MenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
64
extensions/pod-menu/src/shell-menu.tsx
Normal file
64
extensions/pod-menu/src/shell-menu.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions";
|
||||||
|
|
||||||
|
interface Props extends Component.KubeObjectMenuProps<K8sApi.Pod> {
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PodShellMenu extends React.Component<Props> {
|
||||||
|
async execShell(container?: string) {
|
||||||
|
Navigation.hideDetails();
|
||||||
|
const { object: pod } = this.props
|
||||||
|
const containerParam = container ? `-c ${container}` : ""
|
||||||
|
let command = `kubectl exec -i -t -n ${pod.getNs()} ${pod.getName()} ${containerParam} "--"`
|
||||||
|
if (window.navigator.platform !== "Win32") {
|
||||||
|
command = `exec ${command}`
|
||||||
|
}
|
||||||
|
if (pod.getSelectedNodeOs() === "windows") {
|
||||||
|
command = `${command} powershell`
|
||||||
|
} else {
|
||||||
|
command = `${command} sh -c "clear; (bash || ash || sh)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const shell = Component.createTerminalTab({
|
||||||
|
title: `Pod: ${pod.getName()} (namespace: ${pod.getNs()})`
|
||||||
|
});
|
||||||
|
|
||||||
|
Component.terminalStore.sendCommand(command, {
|
||||||
|
enter: true,
|
||||||
|
tabId: shell.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { object, toolbar } = this.props
|
||||||
|
console.log(Object.keys(object))
|
||||||
|
const containers = object.getRunningContainers();
|
||||||
|
if (!containers.length) return;
|
||||||
|
return (
|
||||||
|
<Component.MenuItem onClick={Util.prevDefault(() => this.execShell(containers[0].name))}>
|
||||||
|
<Component.Icon svg="ssh" interactive={toolbar} title="Pod shell"/>
|
||||||
|
<span className="title">Shell</span>
|
||||||
|
{containers.length > 1 && (
|
||||||
|
<>
|
||||||
|
<Component.Icon className="arrow" material="keyboard_arrow_right"/>
|
||||||
|
<Component.SubMenu>
|
||||||
|
{
|
||||||
|
containers.map(container => {
|
||||||
|
const { name } = container;
|
||||||
|
return (
|
||||||
|
<Component.MenuItem key={name} onClick={Util.prevDefault(() => this.execShell(name))} className="flex align-center">
|
||||||
|
<Component.StatusBrick/>
|
||||||
|
{name}
|
||||||
|
</Component.MenuItem>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Component.SubMenu>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Component.MenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
27
extensions/pod-menu/tsconfig.json
Normal file
27
extensions/pod-menu/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"
|
||||||
|
]
|
||||||
|
}
|
||||||
35
extensions/pod-menu/webpack.config.js
Normal file
35
extensions/pod-menu/webpack.config.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -175,6 +175,8 @@
|
|||||||
"lens": {
|
"lens": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"telemetry",
|
"telemetry",
|
||||||
|
"pod-menu",
|
||||||
|
"node-menu",
|
||||||
"support-page"
|
"support-page"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -90,13 +90,19 @@ export class BaseStore<T = any> extends Singleton {
|
|||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
const callback = (event: IpcRendererEvent, model: T) => {
|
const callback = (event: IpcRendererEvent, model: T) => {
|
||||||
logger.silly(`[STORE]: SYNC ${this.name} from main`, { model });
|
logger.silly(`[STORE]: SYNC ${this.name} from main`, { model });
|
||||||
this.onSync(model);
|
this.onSyncFromMain(model);
|
||||||
};
|
};
|
||||||
ipcRenderer.on(this.syncChannel, callback);
|
ipcRenderer.on(this.syncChannel, callback);
|
||||||
this.syncDisposers.push(() => ipcRenderer.off(this.syncChannel, callback));
|
this.syncDisposers.push(() => ipcRenderer.off(this.syncChannel, callback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected onSyncFromMain(model: T) {
|
||||||
|
this.applyWithoutSync(() => {
|
||||||
|
this.onSync(model)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
unregisterIpcListener() {
|
unregisterIpcListener() {
|
||||||
ipcRenderer.removeAllListeners(this.syncChannel)
|
ipcRenderer.removeAllListeners(this.syncChannel)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export const clusterIpc = {
|
|||||||
channel: "cluster:refresh",
|
channel: "cluster:refresh",
|
||||||
handle: (clusterId: ClusterId) => {
|
handle: (clusterId: ClusterId) => {
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = clusterStore.getById(clusterId);
|
||||||
if (cluster) return cluster.refresh();
|
if (cluster) return cluster.refresh({ refreshMetadata: true })
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,10 @@ export interface ClusterIconUpload {
|
|||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ClusterMetadata {
|
||||||
|
[key: string]: string | number | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ClusterStoreModel {
|
export interface ClusterStoreModel {
|
||||||
activeCluster?: ClusterId; // last opened cluster
|
activeCluster?: ClusterId; // last opened cluster
|
||||||
clusters?: ClusterModel[]
|
clusters?: ClusterModel[]
|
||||||
@ -32,6 +36,7 @@ export interface ClusterModel {
|
|||||||
workspace?: WorkspaceId;
|
workspace?: WorkspaceId;
|
||||||
contextName?: string;
|
contextName?: string;
|
||||||
preferences?: ClusterPreferences;
|
preferences?: ClusterPreferences;
|
||||||
|
metadata?: ClusterMetadata;
|
||||||
kubeConfigPath: string;
|
kubeConfigPath: string;
|
||||||
|
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
|
|||||||
@ -1 +1,3 @@
|
|||||||
export { Singleton } from "../../common/utils"
|
export { Singleton } from "../../common/utils"
|
||||||
|
export { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault"
|
||||||
|
export { cssNames } from "../../renderer/utils/cssNames"
|
||||||
|
|||||||
@ -6,7 +6,9 @@ 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, menuRegistry, pageRegistry, statusBarRegistry } from "./registries";
|
import { appPreferenceRegistry, menuRegistry, pageRegistry, statusBarRegistry, kubeObjectMenuRegistry } from "./registries";
|
||||||
|
import { app, remote, ipcRenderer } from "electron"
|
||||||
|
import { pageRegistry } from "./page-registry";
|
||||||
|
|
||||||
export interface InstalledExtension extends ExtensionModel {
|
export interface InstalledExtension extends ExtensionModel {
|
||||||
manifestPath: string;
|
manifestPath: string;
|
||||||
@ -54,6 +56,7 @@ export class ExtensionLoader {
|
|||||||
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
||||||
this.autoloadExtensions((instance: LensRendererExtension) => {
|
this.autoloadExtensions((instance: LensRendererExtension) => {
|
||||||
instance.registerPages(pageRegistry)
|
instance.registerPages(pageRegistry)
|
||||||
|
instance.registerKubeObjectMenus(kubeObjectMenuRegistry)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { LensExtension } from "./lens-extension"
|
import { LensExtension } from "./lens-extension"
|
||||||
import type { PageRegistry } from "./registries/page-registry"
|
import type { PageRegistry, AppPreferenceRegistry, StatusBarRegistry } from "./registries/page-registry"
|
||||||
import type { AppPreferenceRegistry } from "./registries/app-preference-registry";
|
import type { KubeObjectMenuRegistry } from "../renderer/api/kube-object-menu-registry";
|
||||||
import type { StatusBarRegistry } from "./registries";
|
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension {
|
export class LensRendererExtension extends LensExtension {
|
||||||
registerPages(registry: PageRegistry) {
|
registerPages(registry: PageRegistry) {
|
||||||
@ -15,4 +14,8 @@ export class LensRendererExtension extends LensExtension {
|
|||||||
registerStatusBarIcon(registry: StatusBarRegistry) {
|
registerStatusBarIcon(registry: StatusBarRegistry) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerKubeObjectMenus(registry: KubeObjectMenuRegistry) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,3 +7,16 @@ export * from "../../renderer/components/button"
|
|||||||
export * from "../../renderer/components/tabs"
|
export * from "../../renderer/components/tabs"
|
||||||
export * from "../../renderer/components/badge"
|
export * from "../../renderer/components/badge"
|
||||||
export * from "../../renderer/components/layout/page-layout"
|
export * from "../../renderer/components/layout/page-layout"
|
||||||
|
export * from "../../renderer/components/drawer"
|
||||||
|
|
||||||
|
// kube helpers
|
||||||
|
export { KubeObjectDetailsProps, KubeObjectMenuProps } from "../../renderer/components/kube-object"
|
||||||
|
export { KubeObjectMeta } from "../../renderer/components/kube-object/kube-object-meta";
|
||||||
|
export { KubeEventDetails } from "../../renderer/components/+events/kube-event-details"
|
||||||
|
|
||||||
|
// specific exports
|
||||||
|
export { ConfirmDialog } from "../../renderer/components/confirm-dialog";
|
||||||
|
export { MenuItem, SubMenu } from "../../renderer/components/menu";
|
||||||
|
export { StatusBrick } from "../../renderer/components/status-brick";
|
||||||
|
export { terminalStore, createTerminalTab } from "../../renderer/components/dock/terminal.store";
|
||||||
|
export { createPodLogsTab } from "../../renderer/components/dock/pod-logs.store";
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
|
export { apiManager } from "../../renderer/api/api-manager";
|
||||||
export { KubeApi } from "../../renderer/api/kube-api";
|
export { KubeApi } from "../../renderer/api/kube-api";
|
||||||
export { KubeObject } from "../../renderer/api/kube-object";
|
export { KubeObject } from "../../renderer/api/kube-object";
|
||||||
export { Pod, podsApi } 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";
|
||||||
export { Deployment, deploymentApi } from "../../renderer/api/endpoints";
|
export { Deployment, deploymentApi } from "../../renderer/api/endpoints";
|
||||||
export { DaemonSet, daemonSetApi } from "../../renderer/api/endpoints";
|
export { DaemonSet, daemonSetApi } from "../../renderer/api/endpoints";
|
||||||
@ -9,7 +10,7 @@ export { StatefulSet, statefulSetApi } from "../../renderer/api/endpoints";
|
|||||||
export { Job, jobApi } from "../../renderer/api/endpoints";
|
export { Job, jobApi } from "../../renderer/api/endpoints";
|
||||||
export { CronJob, cronJobApi } from "../../renderer/api/endpoints";
|
export { CronJob, cronJobApi } from "../../renderer/api/endpoints";
|
||||||
export { ConfigMap, configMapApi } from "../../renderer/api/endpoints";
|
export { ConfigMap, configMapApi } from "../../renderer/api/endpoints";
|
||||||
export { Secret, secretsApi } from "../../renderer/api/endpoints";
|
export { Secret, secretsApi, ISecretRef } from "../../renderer/api/endpoints";
|
||||||
export { ResourceQuota, resourceQuotaApi } from "../../renderer/api/endpoints";
|
export { ResourceQuota, resourceQuotaApi } from "../../renderer/api/endpoints";
|
||||||
export { HorizontalPodAutoscaler, hpaApi } from "../../renderer/api/endpoints";
|
export { HorizontalPodAutoscaler, hpaApi } from "../../renderer/api/endpoints";
|
||||||
export { PodDisruptionBudget, pdbApi } from "../../renderer/api/endpoints";
|
export { PodDisruptionBudget, pdbApi } from "../../renderer/api/endpoints";
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export { navigate } from "../../renderer/navigation"
|
export { navigate, hideDetails, showDetails } from "../../renderer/navigation"
|
||||||
|
|||||||
@ -38,6 +38,7 @@ 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";
|
||||||
|
|
||||||
const mockedRequest = request as jest.MockedFunction<typeof request>
|
const mockedRequest = request as jest.MockedFunction<typeof request>
|
||||||
|
|
||||||
@ -73,6 +74,7 @@ describe("create clusters", () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
mockFs(mockOpts)
|
mockFs(mockOpts)
|
||||||
|
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true))
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -116,7 +118,7 @@ describe("create clusters", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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")
|
||||||
@ -159,7 +161,7 @@ describe("create clusters", () => {
|
|||||||
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()
|
||||||
jest.resetAllMocks()
|
jest.resetAllMocks()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
33
src/main/cluster-detectors/base-cluster-detector.ts
Normal file
33
src/main/cluster-detectors/base-cluster-detector.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import request, { RequestPromiseOptions } from "request-promise-native"
|
||||||
|
import { Cluster } from "../cluster";
|
||||||
|
|
||||||
|
export type ClusterDetectionResult = {
|
||||||
|
value: string | number | boolean
|
||||||
|
accuracy: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BaseClusterDetector {
|
||||||
|
cluster: Cluster
|
||||||
|
key: string
|
||||||
|
|
||||||
|
constructor(cluster: Cluster) {
|
||||||
|
this.cluster = cluster
|
||||||
|
}
|
||||||
|
|
||||||
|
detect(): Promise<ClusterDetectionResult> {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
|
||||||
|
const apiUrl = this.cluster.kubeProxyUrl + path;
|
||||||
|
return request(apiUrl, {
|
||||||
|
json: true,
|
||||||
|
timeout: 30000,
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
Host: `${this.cluster.id}.${new URL(this.cluster.kubeProxyUrl).host}`, // required in ClusterManager.getClusterForRequest()
|
||||||
|
...(options.headers || {}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/main/cluster-detectors/cluster-id-detector.ts
Normal file
23
src/main/cluster-detectors/cluster-id-detector.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { BaseClusterDetector } from "./base-cluster-detector";
|
||||||
|
import { createHash } from "crypto"
|
||||||
|
import { ClusterMetadataKey } from "../cluster";
|
||||||
|
|
||||||
|
export class ClusterIdDetector extends BaseClusterDetector {
|
||||||
|
key = ClusterMetadataKey.CLUSTER_ID
|
||||||
|
|
||||||
|
public async detect() {
|
||||||
|
let id: string
|
||||||
|
try {
|
||||||
|
id = await this.getDefaultNamespaceId()
|
||||||
|
} catch(_) {
|
||||||
|
id = this.cluster.apiUrl
|
||||||
|
}
|
||||||
|
const value = createHash("sha256").update(id).digest("hex")
|
||||||
|
return { value: value, accuracy: 100 }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async getDefaultNamespaceId() {
|
||||||
|
const response = await this.k8sRequest("/api/v1/namespaces/default")
|
||||||
|
return response.metadata.uid
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/main/cluster-detectors/detector-registry.ts
Normal file
45
src/main/cluster-detectors/detector-registry.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { observable } from "mobx";
|
||||||
|
import { ClusterMetadata } from "../../common/cluster-store";
|
||||||
|
import { Cluster } from "../cluster";
|
||||||
|
import { BaseClusterDetector, ClusterDetectionResult } from "./base-cluster-detector";
|
||||||
|
import { ClusterIdDetector } from "./cluster-id-detector";
|
||||||
|
import { DistributionDetector } from "./distribution-detector";
|
||||||
|
import { LastSeenDetector } from "./last-seen-detector";
|
||||||
|
import { NodesCountDetector } from "./nodes-count-detector";
|
||||||
|
import { VersionDetector } from "./version-detector";
|
||||||
|
|
||||||
|
export class DetectorRegistry {
|
||||||
|
registry = observable.array<typeof BaseClusterDetector>([], { deep: false });
|
||||||
|
|
||||||
|
add(detectorClass: typeof BaseClusterDetector) {
|
||||||
|
this.registry.push(detectorClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
async detectForCluster(cluster: Cluster): Promise<ClusterMetadata> {
|
||||||
|
const results: {[key: string]: ClusterDetectionResult } = {}
|
||||||
|
for (const detectorClass of this.registry) {
|
||||||
|
const detector = new detectorClass(cluster)
|
||||||
|
try {
|
||||||
|
const data = await detector.detect()
|
||||||
|
if (!data) continue;
|
||||||
|
const existingValue = results[detector.key]
|
||||||
|
if (existingValue && existingValue.accuracy > data.accuracy) continue; // previous value exists and is more accurate
|
||||||
|
results[detector.key] = data
|
||||||
|
} catch (e) {
|
||||||
|
// detector raised error, do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const metadata: ClusterMetadata = {}
|
||||||
|
for (const [key, result] of Object.entries(results)) {
|
||||||
|
metadata[key] = result.value
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const detectorRegistry = new DetectorRegistry()
|
||||||
|
detectorRegistry.add(ClusterIdDetector)
|
||||||
|
detectorRegistry.add(LastSeenDetector)
|
||||||
|
detectorRegistry.add(VersionDetector)
|
||||||
|
detectorRegistry.add(DistributionDetector)
|
||||||
|
detectorRegistry.add(NodesCountDetector)
|
||||||
80
src/main/cluster-detectors/distribution-detector.ts
Normal file
80
src/main/cluster-detectors/distribution-detector.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { BaseClusterDetector } from "./base-cluster-detector";
|
||||||
|
import { ClusterMetadataKey } from "../cluster";
|
||||||
|
|
||||||
|
export class DistributionDetector extends BaseClusterDetector {
|
||||||
|
key = ClusterMetadataKey.DISTRIBUTION
|
||||||
|
version: string
|
||||||
|
|
||||||
|
public async detect() {
|
||||||
|
this.version = await this.getKubernetesVersion()
|
||||||
|
if (await this.isRancher()) {
|
||||||
|
return { value: "rancher", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isGKE()) {
|
||||||
|
return { value: "gke", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isEKS()) {
|
||||||
|
return { value: "eks", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isIKS()) {
|
||||||
|
return { value: "iks", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isAKS()) {
|
||||||
|
return { value: "aks", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isDigitalOcean()) {
|
||||||
|
return { value: "digitalocean", accuracy: 90}
|
||||||
|
}
|
||||||
|
if (this.isMinikube()) {
|
||||||
|
return { value: "minikube", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isCustom()) {
|
||||||
|
return { value: "custom", accuracy: 10}
|
||||||
|
}
|
||||||
|
return { value: "vanilla", accuracy: 10}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getKubernetesVersion() {
|
||||||
|
if (this.cluster.version) return this.cluster.version
|
||||||
|
|
||||||
|
const response = await this.k8sRequest("/version")
|
||||||
|
return response.gitVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isGKE() {
|
||||||
|
return this.version.includes("gke")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isEKS() {
|
||||||
|
return this.version.includes("eks")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isIKS() {
|
||||||
|
return this.version.includes("IKS")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isAKS() {
|
||||||
|
return this.cluster.apiUrl.endsWith("azmk8s.io")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isDigitalOcean() {
|
||||||
|
return this.cluster.apiUrl.endsWith("k8s.ondigitalocean.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isMinikube() {
|
||||||
|
return this.cluster.contextName.startsWith("minikube")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isCustom() {
|
||||||
|
return this.version.includes("+")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async isRancher() {
|
||||||
|
try {
|
||||||
|
const response = await this.k8sRequest("")
|
||||||
|
return response.data.find((api: any) => api?.apiVersion?.group === "meta.cattle.io") !== undefined
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main/cluster-detectors/last-seen-detector.ts
Normal file
13
src/main/cluster-detectors/last-seen-detector.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { BaseClusterDetector } from "./base-cluster-detector";
|
||||||
|
import { ClusterMetadataKey } from "../cluster";
|
||||||
|
|
||||||
|
export class LastSeenDetector extends BaseClusterDetector {
|
||||||
|
key = ClusterMetadataKey.LAST_SEEN
|
||||||
|
|
||||||
|
public async detect() {
|
||||||
|
if (!this.cluster.accessible) return null;
|
||||||
|
|
||||||
|
await this.k8sRequest("/version")
|
||||||
|
return { value: new Date().toJSON(), accuracy: 100 }
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/main/cluster-detectors/nodes-count-detector.ts
Normal file
18
src/main/cluster-detectors/nodes-count-detector.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { BaseClusterDetector } from "./base-cluster-detector";
|
||||||
|
import { ClusterMetadataKey } from "../cluster";
|
||||||
|
|
||||||
|
export class NodesCountDetector extends BaseClusterDetector {
|
||||||
|
key = ClusterMetadataKey.NODES_COUNT
|
||||||
|
|
||||||
|
public async detect() {
|
||||||
|
const nodeCount = await this.getNodeCount()
|
||||||
|
return { value: nodeCount, accuracy: 100}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async getNodeCount(): Promise<number> {
|
||||||
|
if (!this.cluster.accessible) return null;
|
||||||
|
|
||||||
|
const response = await this.k8sRequest("/api/v1/nodes")
|
||||||
|
return response.items.length
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/cluster-detectors/version-detector.ts
Normal file
17
src/main/cluster-detectors/version-detector.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { BaseClusterDetector } from "./base-cluster-detector";
|
||||||
|
import { ClusterMetadataKey } from "../cluster";
|
||||||
|
|
||||||
|
export class VersionDetector extends BaseClusterDetector {
|
||||||
|
key = ClusterMetadataKey.VERSION
|
||||||
|
value: string
|
||||||
|
|
||||||
|
public async detect() {
|
||||||
|
const version = await this.getKubernetesVersion()
|
||||||
|
return { value: version, accuracy: 100}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getKubernetesVersion() {
|
||||||
|
const response = await this.k8sRequest("/version")
|
||||||
|
return response.gitVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ClusterId, 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 type { FeatureStatusMap } from "./feature"
|
||||||
@ -14,6 +14,8 @@ import { getFeatures, installFeature, uninstallFeature, upgradeFeature } from ".
|
|||||||
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"
|
||||||
|
import { VersionDetector } from "./cluster-detectors/version-detector";
|
||||||
|
import { detectorRegistry } from "./cluster-detectors/detector-registry";
|
||||||
|
|
||||||
export enum ClusterStatus {
|
export enum ClusterStatus {
|
||||||
AccessGranted = 2,
|
AccessGranted = 2,
|
||||||
@ -21,6 +23,18 @@ export enum ClusterStatus {
|
|||||||
Offline = 0
|
Offline = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ClusterMetadataKey {
|
||||||
|
VERSION = "version",
|
||||||
|
CLUSTER_ID = "id",
|
||||||
|
DISTRIBUTION = "distribution",
|
||||||
|
NODES_COUNT = "nodes",
|
||||||
|
LAST_SEEN = "lastSeen"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClusterRefreshOptions = {
|
||||||
|
refreshMetadata?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface ClusterState extends ClusterModel {
|
export interface ClusterState extends ClusterModel {
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
@ -29,10 +43,7 @@ export interface ClusterState extends ClusterModel {
|
|||||||
accessible: boolean;
|
accessible: boolean;
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
failureReason: string;
|
failureReason: string;
|
||||||
nodes: number;
|
|
||||||
eventCount: number;
|
eventCount: number;
|
||||||
version: string;
|
|
||||||
distribution: string;
|
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
allowedNamespaces: string[]
|
allowedNamespaces: string[]
|
||||||
allowedResources: string[]
|
allowedResources: string[]
|
||||||
@ -63,12 +74,10 @@ export class Cluster implements ClusterModel {
|
|||||||
@observable reconnecting = false;
|
@observable reconnecting = false;
|
||||||
@observable disconnected = true;
|
@observable disconnected = true;
|
||||||
@observable failureReason: string;
|
@observable failureReason: string;
|
||||||
@observable nodes = 0;
|
|
||||||
@observable version: string;
|
|
||||||
@observable distribution = "unknown";
|
|
||||||
@observable isAdmin = false;
|
@observable isAdmin = false;
|
||||||
@observable eventCount = 0;
|
@observable eventCount = 0;
|
||||||
@observable preferences: ClusterPreferences = {};
|
@observable preferences: ClusterPreferences = {};
|
||||||
|
@observable metadata: ClusterMetadata = {};
|
||||||
@observable features: FeatureStatusMap = {};
|
@observable features: FeatureStatusMap = {};
|
||||||
@observable allowedNamespaces: string[] = [];
|
@observable allowedNamespaces: string[] = [];
|
||||||
@observable allowedResources: string[] = [];
|
@observable allowedResources: string[] = [];
|
||||||
@ -76,6 +85,9 @@ export class Cluster implements ClusterModel {
|
|||||||
@computed get available() {
|
@computed get available() {
|
||||||
return this.accessible && !this.disconnected;
|
return this.accessible && !this.disconnected;
|
||||||
}
|
}
|
||||||
|
get version(): string {
|
||||||
|
return String(this.metadata?.version) || ""
|
||||||
|
}
|
||||||
|
|
||||||
constructor(model: ClusterModel) {
|
constructor(model: ClusterModel) {
|
||||||
this.updateModel(model);
|
this.updateModel(model);
|
||||||
@ -113,10 +125,14 @@ export class Cluster implements ClusterModel {
|
|||||||
protected bindEvents() {
|
protected bindEvents() {
|
||||||
logger.info(`[CLUSTER]: bind events`, this.getMeta());
|
logger.info(`[CLUSTER]: bind events`, this.getMeta());
|
||||||
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
||||||
|
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000); // every 15 minutes
|
||||||
|
|
||||||
this.eventDisposers.push(
|
this.eventDisposers.push(
|
||||||
reaction(this.getState, this.pushState),
|
reaction(this.getState, this.pushState),
|
||||||
() => clearInterval(refreshTimer),
|
() => {
|
||||||
|
clearInterval(refreshTimer);
|
||||||
|
clearInterval(refreshMetadataTimer);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +158,7 @@ export class Cluster implements ClusterModel {
|
|||||||
await this.refreshConnectionStatus()
|
await this.refreshConnectionStatus()
|
||||||
if (this.accessible) {
|
if (this.accessible) {
|
||||||
await this.refreshAllowedResources()
|
await this.refreshAllowedResources()
|
||||||
|
this.isAdmin = await this.isClusterAdmin()
|
||||||
this.ready = true
|
this.ready = true
|
||||||
this.kubeCtl = new Kubectl(this.version)
|
this.kubeCtl = new Kubectl(this.version)
|
||||||
this.kubeCtl.ensureKubectl() // download kubectl in background, so it's not blocking dashboard
|
this.kubeCtl.ensureKubectl() // download kubectl in background, so it's not blocking dashboard
|
||||||
@ -172,29 +189,37 @@ export class Cluster implements ClusterModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async refresh() {
|
async refresh(opts: ClusterRefreshOptions = {}) {
|
||||||
logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
||||||
await this.whenInitialized;
|
await this.whenInitialized;
|
||||||
await this.refreshConnectionStatus();
|
await this.refreshConnectionStatus();
|
||||||
if (this.accessible) {
|
if (this.accessible) {
|
||||||
this.distribution = this.detectKubernetesDistribution(this.version)
|
const [features, isAdmin] = await Promise.all([
|
||||||
const [features, isAdmin, nodesCount] = await Promise.all([
|
|
||||||
getFeatures(this),
|
getFeatures(this),
|
||||||
this.isClusterAdmin(),
|
this.isClusterAdmin(),
|
||||||
this.getNodeCount(),
|
|
||||||
]);
|
]);
|
||||||
this.features = features;
|
this.features = features;
|
||||||
this.isAdmin = isAdmin;
|
this.isAdmin = isAdmin;
|
||||||
this.nodes = nodesCount;
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.refreshEvents(),
|
this.refreshEvents(),
|
||||||
this.refreshAllowedResources(),
|
this.refreshAllowedResources(),
|
||||||
]);
|
]);
|
||||||
|
if (opts.refreshMetadata) {
|
||||||
|
this.refreshMetadata()
|
||||||
|
}
|
||||||
this.ready = true
|
this.ready = true
|
||||||
}
|
}
|
||||||
this.pushState();
|
this.pushState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async refreshMetadata() {
|
||||||
|
logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
|
||||||
|
const metadata = await detectorRegistry.detectForCluster(this)
|
||||||
|
const existingMetadata = this.metadata
|
||||||
|
this.metadata = Object.assign(existingMetadata, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async refreshConnectionStatus() {
|
async refreshConnectionStatus() {
|
||||||
const connectionStatus = await this.getConnectionStatus();
|
const connectionStatus = await this.getConnectionStatus();
|
||||||
@ -263,9 +288,9 @@ export class Cluster implements ClusterModel {
|
|||||||
|
|
||||||
protected async getConnectionStatus(): Promise<ClusterStatus> {
|
protected async getConnectionStatus(): Promise<ClusterStatus> {
|
||||||
try {
|
try {
|
||||||
const response = await this.k8sRequest("/version")
|
const versionDetector = new VersionDetector(this)
|
||||||
this.version = response.gitVersion
|
const versionData = await versionDetector.detect()
|
||||||
this.failureReason = null
|
this.metadata.version = versionData.value
|
||||||
return ClusterStatus.AccessGranted;
|
return ClusterStatus.AccessGranted;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to connect cluster "${this.contextName}": ${error}`)
|
logger.error(`Failed to connect cluster "${this.contextName}": ${error}`)
|
||||||
@ -314,27 +339,6 @@ export class Cluster implements ClusterModel {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected detectKubernetesDistribution(kubernetesVersion: string): string {
|
|
||||||
if (kubernetesVersion.includes("gke")) return "gke"
|
|
||||||
if (kubernetesVersion.includes("eks")) return "eks"
|
|
||||||
if (kubernetesVersion.includes("IKS")) return "iks"
|
|
||||||
if (this.apiUrl.endsWith("azmk8s.io")) return "aks"
|
|
||||||
if (this.apiUrl.endsWith("k8s.ondigitalocean.com")) return "digitalocean"
|
|
||||||
if (this.contextName.startsWith("minikube")) return "minikube"
|
|
||||||
if (kubernetesVersion.includes("+")) return "custom"
|
|
||||||
return "vanilla"
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getNodeCount(): Promise<number> {
|
|
||||||
try {
|
|
||||||
const response = await this.k8sRequest("/api/v1/nodes")
|
|
||||||
return response.items.length
|
|
||||||
} catch (error) {
|
|
||||||
logger.debug(`failed to request node list: ${error.message}`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getEventCount(): Promise<number> {
|
protected async getEventCount(): Promise<number> {
|
||||||
if (!this.isAdmin) {
|
if (!this.isAdmin) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -377,6 +381,7 @@ export class Cluster implements ClusterModel {
|
|||||||
kubeConfigPath: this.kubeConfigPath,
|
kubeConfigPath: this.kubeConfigPath,
|
||||||
workspace: this.workspace,
|
workspace: this.workspace,
|
||||||
preferences: this.preferences,
|
preferences: this.preferences,
|
||||||
|
metadata: this.metadata,
|
||||||
};
|
};
|
||||||
return toJS(model, {
|
return toJS(model, {
|
||||||
recurseEverything: true
|
recurseEverything: true
|
||||||
@ -394,9 +399,6 @@ export class Cluster implements ClusterModel {
|
|||||||
disconnected: this.disconnected,
|
disconnected: this.disconnected,
|
||||||
accessible: this.accessible,
|
accessible: this.accessible,
|
||||||
failureReason: this.failureReason,
|
failureReason: this.failureReason,
|
||||||
nodes: this.nodes,
|
|
||||||
version: this.version,
|
|
||||||
distribution: this.distribution,
|
|
||||||
isAdmin: this.isAdmin,
|
isAdmin: this.isAdmin,
|
||||||
features: this.features,
|
features: this.features,
|
||||||
eventCount: this.eventCount,
|
eventCount: this.eventCount,
|
||||||
|
|||||||
@ -53,22 +53,6 @@ export class ApiManager {
|
|||||||
getStore(api: string | KubeApi): KubeObjectStore {
|
getStore(api: string | KubeApi): KubeObjectStore {
|
||||||
return this.stores.get(this.resolveApi(api));
|
return this.stores.get(this.resolveApi(api));
|
||||||
}
|
}
|
||||||
|
|
||||||
registerViews(api: KubeApi | KubeApi[], views: ApiComponents) {
|
|
||||||
if (Array.isArray(api)) {
|
|
||||||
api.forEach(api => this.registerViews(api, views));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const currentViews = this.views.get(api) || {};
|
|
||||||
this.views.set(api, {
|
|
||||||
...currentViews,
|
|
||||||
...views,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getViews(api: string | KubeApi): ApiComponents {
|
|
||||||
return this.views.get(this.resolveApi(api)) || {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const apiManager = new ApiManager();
|
export const apiManager = new ApiManager();
|
||||||
|
|||||||
@ -1,262 +0,0 @@
|
|||||||
// Kubernetes certificate management controller apis
|
|
||||||
// Reference: https://docs.cert-manager.io/en/latest/reference/index.html
|
|
||||||
// API docs: https://docs.cert-manager.io/en/latest/reference/api-docs/index.html
|
|
||||||
|
|
||||||
import { KubeObject } from "../kube-object";
|
|
||||||
import { ISecretRef, secretsApi } from "./secret.api";
|
|
||||||
import { getDetailsUrl } from "../../navigation";
|
|
||||||
import { KubeApi } from "../kube-api";
|
|
||||||
|
|
||||||
export class Certificate extends KubeObject {
|
|
||||||
static kind = "Certificate"
|
|
||||||
|
|
||||||
spec: {
|
|
||||||
secretName: string;
|
|
||||||
commonName?: string;
|
|
||||||
dnsNames?: string[];
|
|
||||||
organization?: string[];
|
|
||||||
ipAddresses?: string[];
|
|
||||||
duration?: string;
|
|
||||||
renewBefore?: string;
|
|
||||||
isCA?: boolean;
|
|
||||||
keySize?: number;
|
|
||||||
keyAlgorithm?: "rsa" | "ecdsa";
|
|
||||||
issuerRef: {
|
|
||||||
kind?: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
acme?: {
|
|
||||||
config: {
|
|
||||||
domains: string[];
|
|
||||||
http01: {
|
|
||||||
ingress?: string;
|
|
||||||
ingressClass?: string;
|
|
||||||
};
|
|
||||||
dns01?: {
|
|
||||||
provider: string;
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
status: {
|
|
||||||
conditions?: {
|
|
||||||
lastTransitionTime: string; // 2019-06-04T07:35:58Z,
|
|
||||||
message: string; // Certificate is up to date and has not expired,
|
|
||||||
reason: string; // Ready,
|
|
||||||
status: string; // True,
|
|
||||||
type: string; // Ready
|
|
||||||
}[];
|
|
||||||
notAfter: string; // 2019-11-01T05:36:27Z
|
|
||||||
lastFailureTime?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
getType(): string {
|
|
||||||
const { isCA, acme } = this.spec;
|
|
||||||
if (isCA) return "CA"
|
|
||||||
if (acme) return "ACME"
|
|
||||||
}
|
|
||||||
|
|
||||||
getCommonName() {
|
|
||||||
return this.spec.commonName || ""
|
|
||||||
}
|
|
||||||
|
|
||||||
getIssuerName() {
|
|
||||||
return this.spec.issuerRef.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSecretName() {
|
|
||||||
return this.spec.secretName;
|
|
||||||
}
|
|
||||||
|
|
||||||
getIssuerDetailsUrl() {
|
|
||||||
return getDetailsUrl(issuersApi.getUrl({
|
|
||||||
namespace: this.getNs(),
|
|
||||||
name: this.getIssuerName(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
getSecretDetailsUrl() {
|
|
||||||
return getDetailsUrl(secretsApi.getUrl({
|
|
||||||
namespace: this.getNs(),
|
|
||||||
name: this.getSecretName(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
getConditions() {
|
|
||||||
const { conditions = [] } = this.status;
|
|
||||||
return conditions.map(condition => {
|
|
||||||
const { message, reason, lastTransitionTime, status } = condition;
|
|
||||||
return {
|
|
||||||
...condition,
|
|
||||||
isReady: status === "True",
|
|
||||||
tooltip: `${message || reason} (${lastTransitionTime})`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Issuer extends KubeObject {
|
|
||||||
static kind = "Issuer"
|
|
||||||
|
|
||||||
spec: {
|
|
||||||
acme?: {
|
|
||||||
email: string;
|
|
||||||
server: string;
|
|
||||||
skipTLSVerify?: boolean;
|
|
||||||
privateKeySecretRef: ISecretRef;
|
|
||||||
solvers?: {
|
|
||||||
dns01?: {
|
|
||||||
cnameStrategy: string;
|
|
||||||
acmedns?: {
|
|
||||||
host: string;
|
|
||||||
accountSecretRef: ISecretRef;
|
|
||||||
};
|
|
||||||
akamai?: {
|
|
||||||
accessTokenSecretRef: ISecretRef;
|
|
||||||
clientSecretSecretRef: ISecretRef;
|
|
||||||
clientTokenSecretRef: ISecretRef;
|
|
||||||
serviceConsumerDomain: string;
|
|
||||||
};
|
|
||||||
azuredns?: {
|
|
||||||
clientID: string;
|
|
||||||
clientSecretSecretRef: ISecretRef;
|
|
||||||
hostedZoneName: string;
|
|
||||||
resourceGroupName: string;
|
|
||||||
subscriptionID: string;
|
|
||||||
tenantID: string;
|
|
||||||
};
|
|
||||||
clouddns?: {
|
|
||||||
project: string;
|
|
||||||
serviceAccountSecretRef: ISecretRef;
|
|
||||||
};
|
|
||||||
cloudflare?: {
|
|
||||||
email: string;
|
|
||||||
apiKeySecretRef: ISecretRef;
|
|
||||||
};
|
|
||||||
digitalocean?: {
|
|
||||||
tokenSecretRef: ISecretRef;
|
|
||||||
};
|
|
||||||
rfc2136?: {
|
|
||||||
nameserver: string;
|
|
||||||
tsigAlgorithm: string;
|
|
||||||
tsigKeyName: string;
|
|
||||||
tsigSecretSecretRef: ISecretRef;
|
|
||||||
};
|
|
||||||
route53?: {
|
|
||||||
accessKeyID: string;
|
|
||||||
hostedZoneID: string;
|
|
||||||
region: string;
|
|
||||||
secretAccessKeySecretRef: ISecretRef;
|
|
||||||
};
|
|
||||||
webhook?: {
|
|
||||||
config: object; // arbitrary json
|
|
||||||
groupName: string;
|
|
||||||
solverName: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
http01?: {
|
|
||||||
ingress: {
|
|
||||||
class: string;
|
|
||||||
name: string;
|
|
||||||
serviceType: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
selector?: {
|
|
||||||
dnsNames: string[];
|
|
||||||
matchLabels: {
|
|
||||||
[label: string]: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
ca?: {
|
|
||||||
secretName: string;
|
|
||||||
};
|
|
||||||
vault?: {
|
|
||||||
path: string;
|
|
||||||
server: string;
|
|
||||||
caBundle: string; // <base64 encoded caBundle PEM file>
|
|
||||||
auth: {
|
|
||||||
appRole: {
|
|
||||||
path: string;
|
|
||||||
roleId: string;
|
|
||||||
secretRef: ISecretRef;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
selfSigned?: {};
|
|
||||||
venafi?: {
|
|
||||||
zone: string;
|
|
||||||
cloud?: {
|
|
||||||
apiTokenSecretRef: ISecretRef;
|
|
||||||
};
|
|
||||||
tpp?: {
|
|
||||||
url: string;
|
|
||||||
caBundle: string; // <base64 encoded caBundle PEM file>
|
|
||||||
credentialsRef: {
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
status: {
|
|
||||||
acme?: {
|
|
||||||
uri: string;
|
|
||||||
};
|
|
||||||
conditions?: {
|
|
||||||
lastTransitionTime: string; // 2019-06-05T07:10:42Z,
|
|
||||||
message: string; // The ACME account was registered with the ACME server,
|
|
||||||
reason: string; // ACMEAccountRegistered,
|
|
||||||
status: string; // True,
|
|
||||||
type: string; // Ready
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
getType() {
|
|
||||||
const { acme, ca, selfSigned, vault, venafi } = this.spec;
|
|
||||||
if (acme) return "ACME"
|
|
||||||
if (ca) return "CA"
|
|
||||||
if (selfSigned) return "SelfSigned"
|
|
||||||
if (vault) return "Vault"
|
|
||||||
if (venafi) return "Venafi"
|
|
||||||
}
|
|
||||||
|
|
||||||
getConditions() {
|
|
||||||
if (!this.status?.conditions) return [];
|
|
||||||
const { conditions = [] } = this.status;
|
|
||||||
return conditions.map(condition => {
|
|
||||||
const { message, reason, lastTransitionTime, status } = condition;
|
|
||||||
return {
|
|
||||||
...condition,
|
|
||||||
isReady: status === "True",
|
|
||||||
tooltip: `${message || reason} (${lastTransitionTime})`,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ClusterIssuer extends Issuer {
|
|
||||||
static kind = "ClusterIssuer"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const certificatesApi = new KubeApi({
|
|
||||||
kind: Certificate.kind,
|
|
||||||
apiBase: "/apis/cert-manager.io/v1alpha2/certificates",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: Certificate,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const issuersApi = new KubeApi({
|
|
||||||
kind: Issuer.kind,
|
|
||||||
apiBase: "/apis/cert-manager.io/v1alpha2/issuers",
|
|
||||||
isNamespaced: true,
|
|
||||||
objectConstructor: Issuer,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const clusterIssuersApi = new KubeApi({
|
|
||||||
kind: ClusterIssuer.kind,
|
|
||||||
apiBase: "/apis/cert-manager.io/v1alpha2/clusterissuers",
|
|
||||||
isNamespaced: false,
|
|
||||||
objectConstructor: ClusterIssuer,
|
|
||||||
});
|
|
||||||
33
src/renderer/api/kube-object-detail-registry.ts
Normal file
33
src/renderer/api/kube-object-detail-registry.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { observable } from "mobx"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export interface KubeObjectDetailComponents {
|
||||||
|
Details: React.ComponentType<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KubeObjectDetailRegistration {
|
||||||
|
kind: string;
|
||||||
|
apiVersions: string[];
|
||||||
|
components: KubeObjectDetailComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KubeObjectDetailRegistry {
|
||||||
|
items = observable.array<KubeObjectDetailRegistration>([], { deep: false });
|
||||||
|
|
||||||
|
add(item: KubeObjectDetailRegistration) {
|
||||||
|
this.items.push(item)
|
||||||
|
return () => {
|
||||||
|
this.items.replace(
|
||||||
|
this.items.filter(c => c !== item)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemsForKind(kind: string, apiVersion: string) {
|
||||||
|
return this.items.filter((item) => {
|
||||||
|
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const kubeObjectDetailRegistry = new KubeObjectDetailRegistry()
|
||||||
33
src/renderer/api/kube-object-menu-registry.ts
Normal file
33
src/renderer/api/kube-object-menu-registry.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { observable } from "mobx"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export interface KubeObjectMenuComponents {
|
||||||
|
MenuItem: React.ComponentType<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KubeObjectMenuRegistration {
|
||||||
|
kind: string;
|
||||||
|
apiVersions: string[];
|
||||||
|
components: KubeObjectMenuComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KubeObjectMenuRegistry {
|
||||||
|
items = observable.array<KubeObjectMenuRegistration>([], { deep: false });
|
||||||
|
|
||||||
|
add(item: KubeObjectMenuRegistration) {
|
||||||
|
this.items.push(item)
|
||||||
|
return () => {
|
||||||
|
this.items.replace(
|
||||||
|
this.items.filter(c => c !== item)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemsForKind(kind: string, apiVersion: string) {
|
||||||
|
return this.items.filter((item) => {
|
||||||
|
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const kubeObjectMenuRegistry = new KubeObjectMenuRegistry()
|
||||||
@ -35,7 +35,7 @@ export class ClusterSettings extends React.Component<Props> {
|
|||||||
refreshCluster = async () => {
|
refreshCluster = async () => {
|
||||||
if (this.cluster) {
|
if (this.cluster) {
|
||||||
await clusterIpc.activate.invokeFromRenderer(this.cluster.id);
|
await clusterIpc.activate.invokeFromRenderer(this.cluster.id);
|
||||||
clusterIpc.refresh.invokeFromRenderer(this.cluster.id);
|
await clusterIpc.refresh.invokeFromRenderer(this.cluster.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,10 +21,10 @@ export class Status extends React.Component<Props> {
|
|||||||
const { cluster } = this.props;
|
const { cluster } = this.props;
|
||||||
const rows = [
|
const rows = [
|
||||||
["Online Status", cluster.online ? "online" : `offline (${cluster.failureReason || "unknown reason"})`],
|
["Online Status", cluster.online ? "online" : `offline (${cluster.failureReason || "unknown reason"})`],
|
||||||
["Distribution", cluster.distribution],
|
["Distribution", cluster.metadata.distribution ? String(cluster.metadata.distribution) : "N/A"],
|
||||||
["Kernel Version", cluster.version],
|
["Kernel Version", cluster.metadata.version ? String(cluster.metadata.version) : "N/A"],
|
||||||
["API Address", cluster.apiUrl],
|
["API Address", cluster.apiUrl || "N/A"],
|
||||||
["Nodes Count", cluster.nodes || "0"]
|
["Nodes Count", cluster.metadata.nodes ? String(cluster.metadata.nodes) : "N/A"]
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<Table scrollable={false}>
|
<Table scrollable={false}>
|
||||||
|
|||||||
@ -7,14 +7,14 @@ import { DrawerItem, DrawerTitle } from "../drawer";
|
|||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { HorizontalPodAutoscaler, hpaApi, HpaMetricType, IHpaMetric } from "../../api/endpoints/hpa.api";
|
import { HorizontalPodAutoscaler, HpaMetricType, IHpaMetric } from "../../api/endpoints/hpa.api";
|
||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { Trans } from "@lingui/macro";
|
import { Trans } from "@lingui/macro";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { getDetailsUrl } from "../../navigation";
|
import { getDetailsUrl } from "../../navigation";
|
||||||
import { lookupApiLink } from "../../api/kube-api";
|
import { lookupApiLink } from "../../api/kube-api";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<HorizontalPodAutoscaler> {
|
interface Props extends KubeObjectDetailsProps<HorizontalPodAutoscaler> {
|
||||||
}
|
}
|
||||||
@ -128,6 +128,10 @@ export class HpaDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(hpaApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: HpaDetails,
|
kind: "HorizontalPodAutoscaler",
|
||||||
});
|
apiVersions: ["autoscaling/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <HpaDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -80,20 +80,8 @@ export class HorizontalPodAutoscalers extends React.Component<Props> {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: HorizontalPodAutoscaler) => {
|
|
||||||
return <HpaMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HpaMenu(props: KubeObjectMenuProps<HorizontalPodAutoscaler>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(hpaApi, {
|
|
||||||
Menu: HpaMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -11,9 +11,9 @@ import { Button } from "../button";
|
|||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { configMapsStore } from "./config-maps.store";
|
import { configMapsStore } from "./config-maps.store";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { ConfigMap, configMapApi } from "../../api/endpoints";
|
import { ConfigMap } from "../../api/endpoints";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<ConfigMap> {
|
interface Props extends KubeObjectDetailsProps<ConfigMap> {
|
||||||
}
|
}
|
||||||
@ -94,6 +94,10 @@ export class ConfigMapDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(configMapApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: ConfigMapDetails
|
kind: "ConfigMap",
|
||||||
})
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <ConfigMapDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -50,20 +50,8 @@ export class ConfigMaps extends React.Component<Props> {
|
|||||||
configMap.getKeys().join(", "),
|
configMap.getKeys().join(", "),
|
||||||
configMap.getAge(),
|
configMap.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: ConfigMap) => {
|
|
||||||
return <ConfigMapMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConfigMapMenu(props: KubeObjectMenuProps<ConfigMap>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(configMapApi, {
|
|
||||||
Menu: ConfigMapMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -3,14 +3,12 @@ import "./pod-disruption-budgets-details.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Trans } from "@lingui/macro";
|
import { Trans } from "@lingui/macro";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem } from "../drawer";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { PodDisruptionBudget, pdbApi } from "../../api/endpoints";
|
import { PodDisruptionBudget } from "../../api/endpoints";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectStore } from "../../kube-object.store";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PodDisruptionBudget> {
|
interface Props extends KubeObjectDetailsProps<PodDisruptionBudget> {
|
||||||
}
|
}
|
||||||
@ -56,6 +54,10 @@ export class PodDisruptionBudgetDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(pdbApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: PodDisruptionBudgetDetails,
|
kind: "PodDisruptionBudget",
|
||||||
});
|
apiVersions: ["policy/v1beta1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <PodDisruptionBudgetDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -64,20 +64,7 @@ export class PodDisruptionBudgets extends React.Component<Props> {
|
|||||||
pdb.getAge(),
|
pdb.getAge(),
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
renderItemMenu={(pdb: PodDisruptionBudget) => {
|
|
||||||
return <PodDisruptionBudgetsMenu object={pdb}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PodDisruptionBudgetsMenu(props: KubeObjectMenuProps<PodDisruptionBudget>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(pdbApi, {
|
|
||||||
Menu: PodDisruptionBudgetsMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -6,11 +6,12 @@ import { Trans } from "@lingui/macro";
|
|||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { cpuUnitsToNumber, cssNames, unitsToBytes, metricUnitsToNumber } from "../../utils";
|
import { cpuUnitsToNumber, cssNames, unitsToBytes, metricUnitsToNumber } from "../../utils";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { ResourceQuota, resourceQuotaApi } from "../../api/endpoints/resource-quota.api";
|
import { ResourceQuota } from "../../api/endpoints/resource-quota.api";
|
||||||
import { LineProgress } from "../line-progress";
|
import { LineProgress } from "../line-progress";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
import { ReplicaSetDetails } from "../+workloads-replicasets";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<ResourceQuota> {
|
interface Props extends KubeObjectDetailsProps<ResourceQuota> {
|
||||||
}
|
}
|
||||||
@ -97,6 +98,10 @@ export class ResourceQuotaDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(resourceQuotaApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: ResourceQuotaDetails
|
kind: "ResourceQuota",
|
||||||
})
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <ReplicaSetDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -48,9 +48,6 @@ export class ResourceQuotas extends React.Component<Props> {
|
|||||||
resourceQuota.getNs(),
|
resourceQuota.getNs(),
|
||||||
resourceQuota.getAge(),
|
resourceQuota.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: ResourceQuota) => {
|
|
||||||
return <ResourceQuotaMenu object={item}/>
|
|
||||||
}}
|
|
||||||
addRemoveButtons={{
|
addRemoveButtons={{
|
||||||
onAdd: () => AddQuotaDialog.open(),
|
onAdd: () => AddQuotaDialog.open(),
|
||||||
addTooltip: <Trans>Create new ResourceQuota</Trans>
|
addTooltip: <Trans>Create new ResourceQuota</Trans>
|
||||||
@ -61,13 +58,3 @@ export class ResourceQuotas extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ResourceQuotaMenu(props: KubeObjectMenuProps<ResourceQuota>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(resourceQuotaApi, {
|
|
||||||
Menu: ResourceQuotaMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -13,10 +13,10 @@ import { base64 } from "../../utils";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { secretsStore } from "./secrets.store";
|
import { secretsStore } from "./secrets.store";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { Secret, secretsApi } from "../../api/endpoints";
|
import { Secret } from "../../api/endpoints";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Secret> {
|
interface Props extends KubeObjectDetailsProps<Secret> {
|
||||||
}
|
}
|
||||||
@ -113,6 +113,10 @@ export class SecretDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(secretsApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: SecretDetails,
|
kind: "Secret",
|
||||||
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <SecretDetails {...props} />
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -61,9 +61,6 @@ export class Secrets extends React.Component<Props> {
|
|||||||
secret.type,
|
secret.type,
|
||||||
secret.getAge(),
|
secret.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: Secret) => {
|
|
||||||
return <SecretMenu object={item}/>
|
|
||||||
}}
|
|
||||||
addRemoveButtons={{
|
addRemoveButtons={{
|
||||||
onAdd: () => AddSecretDialog.open(),
|
onAdd: () => AddSecretDialog.open(),
|
||||||
addTooltip: <Trans>Create new Secret</Trans>
|
addTooltip: <Trans>Create new Secret</Trans>
|
||||||
@ -74,13 +71,3 @@ export class Secrets extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SecretMenu(props: KubeObjectMenuProps<Secret>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(secretsApi, {
|
|
||||||
Menu: SecretMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
$cert-status-colors: (
|
|
||||||
ready: $colorOk,
|
|
||||||
);
|
|
||||||
|
|
||||||
@mixin cert-status-bgc {
|
|
||||||
@each $status, $color in $cert-status-colors {
|
|
||||||
&.#{$status} {
|
|
||||||
background: $color;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
@import "cert-manager.mixins";
|
|
||||||
|
|
||||||
.CertificateDetails {
|
|
||||||
.Badge {
|
|
||||||
@include cert-status-bgc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,142 +0,0 @@
|
|||||||
import "./certificate-details.scss"
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import moment from "moment"
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { Trans } from "@lingui/macro";
|
|
||||||
import { DrawerItem, DrawerTitle } from "../../drawer";
|
|
||||||
import { Badge } from "../../badge";
|
|
||||||
import { KubeEventDetails } from "../../+events/kube-event-details";
|
|
||||||
import { KubeObjectDetailsProps } from "../../kube-object";
|
|
||||||
import { Certificate, certificatesApi } from "../../../api/endpoints/cert-manager.api";
|
|
||||||
import { cssNames } from "../../../utils";
|
|
||||||
import { apiManager } from "../../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../../kube-object/kube-object-meta";
|
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Certificate> {
|
|
||||||
}
|
|
||||||
|
|
||||||
@observer
|
|
||||||
export class CertificateDetails extends React.Component<Props> {
|
|
||||||
render() {
|
|
||||||
const { object: cert, className } = this.props;
|
|
||||||
if (!cert) return;
|
|
||||||
const { spec, status } = cert;
|
|
||||||
const { acme, isCA, commonName, secretName, dnsNames, duration, ipAddresses, keyAlgorithm, keySize, organization, renewBefore } = spec;
|
|
||||||
const { lastFailureTime, notAfter } = status;
|
|
||||||
return (
|
|
||||||
<div className={cssNames("CertificateDetails", className)}>
|
|
||||||
<KubeObjectMeta object={cert}/>
|
|
||||||
|
|
||||||
<DrawerItem name={<Trans>Issuer</Trans>}>
|
|
||||||
<Link to={cert.getIssuerDetailsUrl()}>
|
|
||||||
{cert.getIssuerName()}
|
|
||||||
</Link>
|
|
||||||
</DrawerItem>
|
|
||||||
|
|
||||||
<DrawerItem name={<Trans>Secret Name</Trans>}>
|
|
||||||
<Link to={cert.getSecretDetailsUrl()}>
|
|
||||||
{secretName}
|
|
||||||
</Link>
|
|
||||||
</DrawerItem>
|
|
||||||
|
|
||||||
<DrawerItem name="CA">
|
|
||||||
{isCA ? <Trans>Yes</Trans> : <Trans>No</Trans>}
|
|
||||||
</DrawerItem>
|
|
||||||
|
|
||||||
{commonName && (
|
|
||||||
<DrawerItem name={<Trans>Common Name</Trans>}>
|
|
||||||
{commonName}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
{dnsNames && (
|
|
||||||
<DrawerItem name={<Trans>DNS names</Trans>} labelsOnly>
|
|
||||||
{dnsNames.map(name => <Badge key={name} label={name}/>)}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
{ipAddresses && (
|
|
||||||
<DrawerItem name={<Trans>IP addresses</Trans>}>
|
|
||||||
{ipAddresses.join(", ")}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
{organization && (
|
|
||||||
<DrawerItem name={<Trans>Organization</Trans>}>
|
|
||||||
{organization.join(", ")}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
{duration && (
|
|
||||||
<DrawerItem name={<Trans>Duration</Trans>}>
|
|
||||||
{duration}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
{renewBefore && (
|
|
||||||
<DrawerItem name={<Trans>Renew Before</Trans>}>
|
|
||||||
{renewBefore}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
{keySize && (
|
|
||||||
<DrawerItem name={<Trans>Key Size</Trans>}>
|
|
||||||
{keySize}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
{keyAlgorithm && (
|
|
||||||
<DrawerItem name={<Trans>Key Algorithm</Trans>}>
|
|
||||||
{keyAlgorithm}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DrawerItem name={<Trans>Not After</Trans>}>
|
|
||||||
{moment(notAfter).format("LLL")}
|
|
||||||
</DrawerItem>
|
|
||||||
|
|
||||||
{lastFailureTime && (
|
|
||||||
<DrawerItem name={<Trans>Last Failure Time</Trans>}>
|
|
||||||
{lastFailureTime}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
<DrawerItem name={<Trans>Status</Trans>} labelsOnly>
|
|
||||||
{cert.getConditions().map(({ type, tooltip, isReady }) => {
|
|
||||||
return (
|
|
||||||
<Badge
|
|
||||||
key={type}
|
|
||||||
label={type}
|
|
||||||
tooltip={tooltip}
|
|
||||||
className={cssNames({ [type.toLowerCase()]: isReady })}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</DrawerItem>
|
|
||||||
|
|
||||||
{acme && (
|
|
||||||
<>
|
|
||||||
<DrawerTitle title="ACME"/>
|
|
||||||
{acme.config.map(({ domains, http01, dns01 }, index) => {
|
|
||||||
return (
|
|
||||||
<div key={index} className="acme-config">
|
|
||||||
<DrawerItem name={<Trans>Domains</Trans>} labelsOnly>
|
|
||||||
{domains.map(domain => <Badge key={domain} label={domain}/>)}
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem name={<Trans>Http01</Trans>}>
|
|
||||||
{Object.entries(http01).map(([key, val]) => `${key}: ${val}`)[0]}
|
|
||||||
</DrawerItem>
|
|
||||||
{dns01 && (
|
|
||||||
<DrawerItem name={<Trans>DNS Provider</Trans>} labelsOnly>
|
|
||||||
{dns01.provider}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<KubeEventDetails object={cert}/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(certificatesApi, {
|
|
||||||
Details: CertificateDetails
|
|
||||||
})
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
@import "cert-manager.mixins";
|
|
||||||
|
|
||||||
.Certificates {
|
|
||||||
.TableCell {
|
|
||||||
&.name {
|
|
||||||
flex: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type {
|
|
||||||
flex: .5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status {
|
|
||||||
flex: .5;
|
|
||||||
@include table-cell-labels-offsets;
|
|
||||||
|
|
||||||
.Badge {
|
|
||||||
@include cert-status-bgc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.age {
|
|
||||||
flex: .5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
import "./certificates.scss"
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { Trans } from "@lingui/macro";
|
|
||||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../../kube-object/kube-object-menu";
|
|
||||||
import { KubeObjectListLayout, KubeObjectListLayoutProps } from "../../kube-object";
|
|
||||||
import { Certificate, certificatesApi } from "../../../api/endpoints/cert-manager.api";
|
|
||||||
import { cssNames, stopPropagation } from "../../../utils";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { Badge } from "../../badge";
|
|
||||||
import { apiManager } from "../../../api/api-manager";
|
|
||||||
import { Spinner } from "../../spinner";
|
|
||||||
|
|
||||||
enum sortBy {
|
|
||||||
name = "name",
|
|
||||||
namespace = "namespace",
|
|
||||||
age = "age",
|
|
||||||
commonName = "common-name",
|
|
||||||
secretName = "secret",
|
|
||||||
issuer = "issuer",
|
|
||||||
type = "type",
|
|
||||||
}
|
|
||||||
|
|
||||||
@observer
|
|
||||||
export class Certificates extends React.Component<KubeObjectListLayoutProps> {
|
|
||||||
render() {
|
|
||||||
const { store = apiManager.getStore(certificatesApi), ...layoutProps } = this.props;
|
|
||||||
if (!store) {
|
|
||||||
return <Spinner center/>
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<KubeObjectListLayout
|
|
||||||
{...layoutProps}
|
|
||||||
store={store}
|
|
||||||
className="Certificates"
|
|
||||||
sortingCallbacks={{
|
|
||||||
[sortBy.name]: (item: Certificate) => item.getName(),
|
|
||||||
[sortBy.namespace]: (item: Certificate) => item.getNs(),
|
|
||||||
[sortBy.secretName]: (item: Certificate) => item.getSecretName(),
|
|
||||||
[sortBy.commonName]: (item: Certificate) => item.getCommonName(),
|
|
||||||
[sortBy.issuer]: (item: Certificate) => item.getIssuerName(),
|
|
||||||
[sortBy.type]: (item: Certificate) => item.getType(),
|
|
||||||
}}
|
|
||||||
searchFilters={[
|
|
||||||
(item: Certificate) => item.getSearchFields(),
|
|
||||||
(item: Certificate) => item.getSecretName(),
|
|
||||||
(item: Certificate) => item.getCommonName(),
|
|
||||||
(item: Certificate) => item.getIssuerName(),
|
|
||||||
(item: Certificate) => item.getType(),
|
|
||||||
]}
|
|
||||||
renderHeaderTitle={<Trans>Certificates</Trans>}
|
|
||||||
renderTableHeader={[
|
|
||||||
{ title: <Trans>Name</Trans>, className: "name", sortBy: sortBy.name },
|
|
||||||
{ title: <Trans>Namespace</Trans>, className: "namespace", sortBy: sortBy.namespace },
|
|
||||||
{ title: <Trans>Common Name</Trans>, className: "common-name", sortBy: sortBy.type },
|
|
||||||
{ title: <Trans>Type</Trans>, className: "type", sortBy: sortBy.type },
|
|
||||||
{ title: <Trans>Issuer</Trans>, className: "issuer", sortBy: sortBy.issuer },
|
|
||||||
{ title: <Trans>Secret</Trans>, className: "secret", sortBy: sortBy.secretName },
|
|
||||||
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
|
||||||
{ title: <Trans>Status</Trans>, className: "status" },
|
|
||||||
]}
|
|
||||||
renderTableContents={(cert: Certificate) => {
|
|
||||||
return [
|
|
||||||
cert.getName(),
|
|
||||||
cert.getNs(),
|
|
||||||
cert.getCommonName(),
|
|
||||||
cert.getType(),
|
|
||||||
<Link to={cert.getIssuerDetailsUrl()} onClick={stopPropagation}>
|
|
||||||
{cert.getIssuerName()}
|
|
||||||
</Link>,
|
|
||||||
<Link to={cert.getSecretDetailsUrl()} onClick={stopPropagation}>
|
|
||||||
{cert.getSecretName()}
|
|
||||||
</Link>,
|
|
||||||
cert.getAge(),
|
|
||||||
cert.getConditions().map(({ type, tooltip, isReady }) => {
|
|
||||||
return (
|
|
||||||
<Badge
|
|
||||||
key={type}
|
|
||||||
label={type}
|
|
||||||
tooltip={tooltip}
|
|
||||||
className={cssNames({ [type.toLowerCase()]: isReady })}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}}
|
|
||||||
renderItemMenu={(item: Certificate) => {
|
|
||||||
return <CertificateMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CertificateMenu(props: KubeObjectMenuProps<Certificate>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(certificatesApi, {
|
|
||||||
List: Certificates,
|
|
||||||
Menu: CertificateMenu
|
|
||||||
})
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export * from "./certificates"
|
|
||||||
export * from "./certificate-details"
|
|
||||||
export * from "./issuers"
|
|
||||||
export * from "./issuer-details"
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
@import "cert-manager.mixins";
|
|
||||||
|
|
||||||
.IssuerDetails {
|
|
||||||
.Badge {
|
|
||||||
@include cert-status-bgc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,175 +0,0 @@
|
|||||||
import "./issuer-details.scss"
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { Trans } from "@lingui/macro";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { DrawerItem, DrawerTitle } from "../../drawer";
|
|
||||||
import { Badge } from "../../badge";
|
|
||||||
import { KubeEventDetails } from "../../+events/kube-event-details";
|
|
||||||
import { KubeObjectDetailsProps } from "../../kube-object";
|
|
||||||
import { clusterIssuersApi, Issuer, issuersApi } from "../../../api/endpoints/cert-manager.api";
|
|
||||||
import { autobind, cssNames } from "../../../utils";
|
|
||||||
import { getDetailsUrl } from "../../../navigation";
|
|
||||||
import { secretsApi } from "../../../api/endpoints";
|
|
||||||
import { apiManager } from "../../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../../kube-object/kube-object-meta";
|
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Issuer> {
|
|
||||||
}
|
|
||||||
|
|
||||||
@observer
|
|
||||||
export class IssuerDetails extends React.Component<Props> {
|
|
||||||
@autobind()
|
|
||||||
renderSecretLink(secretName: string) {
|
|
||||||
const namespace = this.props.object.getNs();
|
|
||||||
if (!namespace) {
|
|
||||||
return secretName;
|
|
||||||
}
|
|
||||||
const secretDetailsUrl = getDetailsUrl(secretsApi.getUrl({
|
|
||||||
namespace: namespace,
|
|
||||||
name: secretName,
|
|
||||||
}));
|
|
||||||
return (
|
|
||||||
<Link to={secretDetailsUrl}>
|
|
||||||
{secretName}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { object: issuer, className } = this.props;
|
|
||||||
if (!issuer) return;
|
|
||||||
const { renderSecretLink } = this;
|
|
||||||
const { spec: { acme, ca, vault, venafi }, status } = issuer;
|
|
||||||
return (
|
|
||||||
<div className={cssNames("IssuerDetails", className)}>
|
|
||||||
<KubeObjectMeta object={issuer}/>
|
|
||||||
|
|
||||||
<DrawerItem name={<Trans>Type</Trans>}>
|
|
||||||
{issuer.getType()}
|
|
||||||
</DrawerItem>
|
|
||||||
|
|
||||||
<DrawerItem name={<Trans>Status</Trans>} labelsOnly>
|
|
||||||
{issuer.getConditions().map(({ type, tooltip, isReady }) => {
|
|
||||||
return (
|
|
||||||
<Badge
|
|
||||||
key={type}
|
|
||||||
label={type}
|
|
||||||
tooltip={tooltip}
|
|
||||||
className={cssNames({ [type.toLowerCase()]: isReady })}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</DrawerItem>
|
|
||||||
|
|
||||||
{acme && (() => {
|
|
||||||
const { email, server, skipTLSVerify, privateKeySecretRef, solvers } = acme;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DrawerTitle title="ACME"/>
|
|
||||||
<DrawerItem name={<Trans>E-mail</Trans>}>
|
|
||||||
{email}
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem name={<Trans>Server</Trans>}>
|
|
||||||
{server}
|
|
||||||
</DrawerItem>
|
|
||||||
{status.acme && (
|
|
||||||
<DrawerItem name={<Trans>Status URI</Trans>}>
|
|
||||||
{status.acme.uri}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
<DrawerItem name={<Trans>Private Key Secret</Trans>}>
|
|
||||||
{renderSecretLink(privateKeySecretRef.name)}
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem name={<Trans>Skip TLS Verify</Trans>}>
|
|
||||||
{skipTLSVerify ? <Trans>Yes</Trans> : <Trans>No</Trans>}
|
|
||||||
</DrawerItem>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})()}
|
|
||||||
|
|
||||||
{ca && (() => {
|
|
||||||
const { secretName } = ca;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DrawerTitle title="CA"/>
|
|
||||||
<DrawerItem name={<Trans>Secret Name</Trans>}>
|
|
||||||
{renderSecretLink(secretName)}
|
|
||||||
</DrawerItem>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})()}
|
|
||||||
|
|
||||||
{vault && (() => {
|
|
||||||
const { auth, caBundle, path, server } = vault;
|
|
||||||
const { path: authPath, roleId, secretRef } = auth.appRole;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DrawerTitle title="Vault"/>
|
|
||||||
<DrawerItem name={<Trans>Server</Trans>}>
|
|
||||||
{server}
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem name={<Trans>Path</Trans>}>
|
|
||||||
{path}
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem name={<Trans>CA Bundle</Trans>} labelsOnly>
|
|
||||||
<Badge label={caBundle}/>
|
|
||||||
</DrawerItem>
|
|
||||||
|
|
||||||
<DrawerTitle title={<Trans>Auth App Role</Trans>}/>
|
|
||||||
<DrawerItem name={<Trans>Path</Trans>}>
|
|
||||||
{authPath}
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem name={<Trans>Role ID</Trans>}>
|
|
||||||
{roleId}
|
|
||||||
</DrawerItem>
|
|
||||||
{secretRef && (
|
|
||||||
<DrawerItem name={<Trans>Secret</Trans>}>
|
|
||||||
{renderSecretLink(secretRef.name)}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})()}
|
|
||||||
|
|
||||||
{venafi && (() => {
|
|
||||||
const { zone, cloud, tpp } = venafi;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DrawerTitle title="CA"/>
|
|
||||||
<DrawerItem name={<Trans>Zone</Trans>}>
|
|
||||||
{zone}
|
|
||||||
</DrawerItem>
|
|
||||||
{cloud && (
|
|
||||||
<DrawerItem name={<Trans>Cloud API Token Secret</Trans>}>
|
|
||||||
{renderSecretLink(cloud.apiTokenSecretRef.name)}
|
|
||||||
</DrawerItem>
|
|
||||||
)}
|
|
||||||
{tpp && (
|
|
||||||
<>
|
|
||||||
<DrawerTitle title="TPP"/>
|
|
||||||
<DrawerItem name={<Trans>URL</Trans>}>
|
|
||||||
{tpp.url}
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem name={<Trans>CA Bundle</Trans>} labelsOnly>
|
|
||||||
<Badge label={tpp.caBundle}/>
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem name={<Trans>Credentials Ref</Trans>}>
|
|
||||||
{renderSecretLink(tpp.credentialsRef.name)}
|
|
||||||
</DrawerItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})()}
|
|
||||||
|
|
||||||
<KubeEventDetails object={issuer}/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews([issuersApi, clusterIssuersApi], {
|
|
||||||
Details: IssuerDetails
|
|
||||||
})
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
@import "cert-manager.mixins";
|
|
||||||
|
|
||||||
.Issuers {
|
|
||||||
.TableCell {
|
|
||||||
&.name {
|
|
||||||
flex: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.labels {
|
|
||||||
flex: 2;
|
|
||||||
@include table-cell-labels-offsets;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status {
|
|
||||||
flex: .5;
|
|
||||||
@include table-cell-labels-offsets;
|
|
||||||
|
|
||||||
.Badge {
|
|
||||||
@include cert-status-bgc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.age {
|
|
||||||
flex: .5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
import "./issuers.scss"
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { Trans } from "@lingui/macro";
|
|
||||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../../kube-object/kube-object-menu";
|
|
||||||
import { KubeObjectListLayout, KubeObjectListLayoutProps } from "../../kube-object";
|
|
||||||
import { clusterIssuersApi, Issuer, issuersApi } from "../../../api/endpoints/cert-manager.api";
|
|
||||||
import { cssNames } from "../../../utils";
|
|
||||||
import { Badge } from "../../badge";
|
|
||||||
import { Spinner } from "../../spinner";
|
|
||||||
import { apiManager } from "../../../api/api-manager";
|
|
||||||
|
|
||||||
enum sortBy {
|
|
||||||
name = "name",
|
|
||||||
namespace = "namespace",
|
|
||||||
type = "type",
|
|
||||||
labels = "labels",
|
|
||||||
age = "age",
|
|
||||||
}
|
|
||||||
|
|
||||||
@observer
|
|
||||||
export class ClusterIssuers extends React.Component<KubeObjectListLayoutProps> {
|
|
||||||
render() {
|
|
||||||
const store = apiManager.getStore(clusterIssuersApi);
|
|
||||||
return (
|
|
||||||
<Issuers
|
|
||||||
{...this.props}
|
|
||||||
isClusterScoped={true}
|
|
||||||
store={store}
|
|
||||||
renderHeaderTitle={<Trans>Cluster Issuers</Trans>}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@observer
|
|
||||||
export class Issuers extends React.Component<KubeObjectListLayoutProps> {
|
|
||||||
render() {
|
|
||||||
const { store = apiManager.getStore(issuersApi), ...layoutProps } = this.props;
|
|
||||||
if (!store) {
|
|
||||||
return <Spinner center/>
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<KubeObjectListLayout
|
|
||||||
store={store}
|
|
||||||
renderHeaderTitle={<Trans>Issuers</Trans>}
|
|
||||||
{...layoutProps}
|
|
||||||
className="Issuers"
|
|
||||||
sortingCallbacks={{
|
|
||||||
[sortBy.name]: (item: Issuer) => item.getName(),
|
|
||||||
[sortBy.namespace]: (item: Issuer) => item.getNs(),
|
|
||||||
[sortBy.type]: (item: Issuer) => item.getType(),
|
|
||||||
[sortBy.labels]: (item: Issuer) => item.getLabels(),
|
|
||||||
[sortBy.age]: (item: Issuer) => item.metadata.creationTimestamp,
|
|
||||||
}}
|
|
||||||
searchFilters={[
|
|
||||||
(item: Issuer) => item.getSearchFields(),
|
|
||||||
(item: Issuer) => item.getType(),
|
|
||||||
]}
|
|
||||||
renderTableHeader={[
|
|
||||||
{ title: <Trans>Name</Trans>, className: "name", sortBy: sortBy.name },
|
|
||||||
{ title: <Trans>Namespace</Trans>, className: "namespace", sortBy: sortBy.namespace },
|
|
||||||
{ title: <Trans>Labels</Trans>, className: "labels", sortBy: sortBy.labels },
|
|
||||||
{ title: <Trans>Type</Trans>, className: "type", sortBy: sortBy.type },
|
|
||||||
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
|
||||||
{ title: <Trans>Status</Trans>, className: "status" },
|
|
||||||
]}
|
|
||||||
renderTableContents={(issuer: Issuer) => [
|
|
||||||
issuer.getName(),
|
|
||||||
issuer.getNs(),
|
|
||||||
issuer.getLabels().map(label => <Badge key={label} label={label} title={label}/>),
|
|
||||||
issuer.getType(),
|
|
||||||
issuer.getAge(),
|
|
||||||
issuer.getConditions().map(({ type, tooltip, isReady }) => {
|
|
||||||
return (
|
|
||||||
<Badge
|
|
||||||
key={type}
|
|
||||||
label={type}
|
|
||||||
tooltip={tooltip}
|
|
||||||
className={cssNames({ [type.toLowerCase()]: isReady })}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
]}
|
|
||||||
renderItemMenu={(item: Issuer) => {
|
|
||||||
return <IssuerMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function IssuerMenu(props: KubeObjectMenuProps<Issuer>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews([issuersApi, clusterIssuersApi], {
|
|
||||||
List: Issuers,
|
|
||||||
Menu: IssuerMenu,
|
|
||||||
})
|
|
||||||
@ -14,6 +14,7 @@ import { KubeObjectDetailsProps } from "../kube-object";
|
|||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { Input } from "../input";
|
import { Input } from "../input";
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<CustomResourceDefinition> {
|
interface Props extends KubeObjectDetailsProps<CustomResourceDefinition> {
|
||||||
}
|
}
|
||||||
@ -133,6 +134,10 @@ export class CRDDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(crdApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: CRDDetails
|
kind: "CustomResourceDefinition",
|
||||||
})
|
apiVersions: ["apiextensions.k8s.io/v1", "apiextensions.k8s.io/v1beta1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <CRDDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -8,9 +8,7 @@ import { Link } from "react-router-dom";
|
|||||||
import { stopPropagation } from "../../utils";
|
import { stopPropagation } from "../../utils";
|
||||||
import { KubeObjectListLayout } from "../kube-object";
|
import { KubeObjectListLayout } from "../kube-object";
|
||||||
import { crdStore } from "./crd.store";
|
import { crdStore } from "./crd.store";
|
||||||
import { apiManager } from "../../api/api-manager";
|
import { CustomResourceDefinition } from "../../api/endpoints/crd.api";
|
||||||
import { crdApi, CustomResourceDefinition } from "../../api/endpoints/crd.api";
|
|
||||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
|
||||||
import { Select, SelectOption } from "../select";
|
import { Select, SelectOption } from "../select";
|
||||||
import { navigation, setQueryParams } from "../../navigation";
|
import { navigation, setQueryParams } from "../../navigation";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
@ -103,20 +101,8 @@ export class CrdList extends React.Component {
|
|||||||
crd.getAge(),
|
crd.getAge(),
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
renderItemMenu={(item: CustomResourceDefinition) => {
|
|
||||||
return <CRDMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CRDMenu(props: KubeObjectMenuProps<CustomResourceDefinition>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(crdApi, {
|
|
||||||
Menu: CRDMenu,
|
|
||||||
});
|
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { cssNames } from "../../utils";
|
|||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { DrawerItem } from "../drawer";
|
import { DrawerItem } from "../drawer";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { crdStore } from "./crd.store";
|
import { crdStore } from "./crd.store";
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { Input } from "../input";
|
import { Input } from "../input";
|
||||||
@ -39,20 +38,13 @@ export class CrdResourceDetails extends React.Component<Props> {
|
|||||||
return crdStore.getByObject(this.props.object);
|
return crdStore.getByObject(this.props.object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get CustomDetailsViews() {
|
|
||||||
return apiManager.getViews(this.props.object.selfLink).Details;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { object } = this.props;
|
const { object } = this.props;
|
||||||
const { crd, CustomDetailsViews } = this;
|
const { crd } = this;
|
||||||
if (!object || !crd) return null;
|
if (!object || !crd) return null;
|
||||||
const className = cssNames("CrdResourceDetails", crd.getResourceKind());
|
const className = cssNames("CrdResourceDetails", crd.getResourceKind());
|
||||||
const extraColumns = crd.getPrinterColumns();
|
const extraColumns = crd.getPrinterColumns();
|
||||||
const showStatus = !extraColumns.find(column => column.name == "Status") && object.status?.conditions;
|
const showStatus = !extraColumns.find(column => column.name == "Status") && object.status?.conditions;
|
||||||
if (CustomDetailsViews) {
|
|
||||||
return <CustomDetailsViews className={className} object={object}/>
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<KubeObjectMeta object={object}/>
|
<KubeObjectMeta object={object}/>
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { Trans } from "@lingui/macro";
|
|||||||
import { RouteComponentProps } from "react-router";
|
import { RouteComponentProps } from "react-router";
|
||||||
import { KubeObjectListLayout } from "../kube-object";
|
import { KubeObjectListLayout } from "../kube-object";
|
||||||
import { KubeObject } from "../../api/kube-object";
|
import { KubeObject } from "../../api/kube-object";
|
||||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
|
||||||
import { ICRDRouteParams } from "./crd.route";
|
import { ICRDRouteParams } from "./crd.route";
|
||||||
import { autorun, computed } from "mobx";
|
import { autorun, computed } from "mobx";
|
||||||
import { crdStore } from "./crd.store";
|
import { crdStore } from "./crd.store";
|
||||||
@ -59,9 +58,7 @@ export class CrdResources extends React.Component<Props> {
|
|||||||
extraColumns.forEach(column => {
|
extraColumns.forEach(column => {
|
||||||
sortingCallbacks[column.name] = (item: KubeObject) => jsonPath.query(item, column.jsonPath.slice(1))
|
sortingCallbacks[column.name] = (item: KubeObject) => jsonPath.query(item, column.jsonPath.slice(1))
|
||||||
})
|
})
|
||||||
// todo: merge extra columns and other params to predefined view
|
const ListView = KubeObjectListLayout;
|
||||||
const { List } = apiManager.getViews(crd.getResourceApiBase());
|
|
||||||
const ListView = List || KubeObjectListLayout;
|
|
||||||
return (
|
return (
|
||||||
<ListView
|
<ListView
|
||||||
className="CrdResources"
|
className="CrdResources"
|
||||||
@ -93,20 +90,7 @@ export class CrdResources extends React.Component<Props> {
|
|||||||
}),
|
}),
|
||||||
crdInstance.getAge(),
|
crdInstance.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: KubeObject) => {
|
|
||||||
return <CrdResourceMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CrdResourceMenu(props: KubeObjectMenuProps<KubeObject>) {
|
|
||||||
const { Menu } = apiManager.getViews(props.object.selfLink);
|
|
||||||
if (Menu) {
|
|
||||||
return <Menu {...props}/>
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -3,6 +3,3 @@ export * from "./crd-list";
|
|||||||
export * from "./crd-details";
|
export * from "./crd-details";
|
||||||
export * from "./crd-resources";
|
export * from "./crd-resources";
|
||||||
export * from "./crd-resource-details";
|
export * from "./crd-resource-details";
|
||||||
|
|
||||||
// customized crd-s
|
|
||||||
export * from "./certmanager.k8s.io"
|
|
||||||
|
|||||||
@ -7,12 +7,12 @@ import { DrawerItem, DrawerTitle } from "../drawer";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { eventApi, KubeEvent } from "../../api/endpoints/events.api";
|
import { KubeEvent } from "../../api/endpoints/events.api";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { getDetailsUrl } from "../../navigation";
|
import { getDetailsUrl } from "../../navigation";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { lookupApiLink } from "../../api/kube-api";
|
import { lookupApiLink } from "../../api/kube-api";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<KubeEvent> {
|
interface Props extends KubeObjectDetailsProps<KubeEvent> {
|
||||||
}
|
}
|
||||||
@ -74,6 +74,10 @@ export class EventDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(eventApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: EventDetails,
|
kind: "Event",
|
||||||
});
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <EventDetails {...props}/>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -6,14 +6,14 @@ import { observer } from "mobx-react";
|
|||||||
import { Trans } from "@lingui/macro";
|
import { Trans } from "@lingui/macro";
|
||||||
import { DrawerItem } from "../drawer";
|
import { DrawerItem } from "../drawer";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { Namespace, namespacesApi } from "../../api/endpoints";
|
import { Namespace } from "../../api/endpoints";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { getDetailsUrl } from "../../navigation";
|
import { getDetailsUrl } from "../../navigation";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import { resourceQuotaStore } from "../+config-resource-quotas/resource-quotas.store";
|
import { resourceQuotaStore } from "../+config-resource-quotas/resource-quotas.store";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Namespace> {
|
interface Props extends KubeObjectDetailsProps<Namespace> {
|
||||||
}
|
}
|
||||||
@ -56,6 +56,10 @@ export class NamespaceDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(namespacesApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: NamespaceDetails
|
kind: "Namespace",
|
||||||
});
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <NamespaceDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -53,9 +53,6 @@ export class Namespaces extends React.Component<Props> {
|
|||||||
item.getAge(),
|
item.getAge(),
|
||||||
{ title: item.getStatus(), className: item.getStatus().toLowerCase() },
|
{ title: item.getStatus(), className: item.getStatus().toLowerCase() },
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: Namespace) => {
|
|
||||||
return <NamespaceMenu object={item}/>
|
|
||||||
}}
|
|
||||||
addRemoveButtons={{
|
addRemoveButtons={{
|
||||||
addTooltip: <Trans>Add Namespace</Trans>,
|
addTooltip: <Trans>Add Namespace</Trans>,
|
||||||
onAdd: () => AddNamespaceDialog.open(),
|
onAdd: () => AddNamespaceDialog.open(),
|
||||||
@ -69,13 +66,3 @@ export class Namespaces extends React.Component<Props> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NamespaceMenu(props: KubeObjectMenuProps<Namespace>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(namespacesApi, {
|
|
||||||
Menu: NamespaceMenu,
|
|
||||||
});
|
|
||||||
|
|||||||
@ -2,16 +2,15 @@ import "./endpoint-details.scss"
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { t, Trans } from "@lingui/macro";
|
import { Trans } from "@lingui/macro";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerTitle } from "../drawer";
|
||||||
import { Badge } from "../badge";
|
|
||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { Endpoint, endpointApi } from "../../api/endpoints";
|
import { Endpoint } from "../../api/endpoints";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { EndpointSubsetList } from "./endpoint-subset-list";
|
import { EndpointSubsetList } from "./endpoint-subset-list";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Endpoint> {
|
interface Props extends KubeObjectDetailsProps<Endpoint> {
|
||||||
}
|
}
|
||||||
@ -39,6 +38,10 @@ export class EndpointDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(endpointApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: EndpointDetails,
|
kind: "Endpoints",
|
||||||
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <EndpointDetails {...props} />
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -47,9 +47,6 @@ export class Endpoints extends React.Component<Props> {
|
|||||||
endpoint.toString(),
|
endpoint.toString(),
|
||||||
endpoint.getAge(),
|
endpoint.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: Endpoint) => {
|
|
||||||
return <EndpointMenu object={item}/>
|
|
||||||
}}
|
|
||||||
tableProps={{
|
tableProps={{
|
||||||
customRowHeights: (item: Endpoint, lineHeight, paddings) => {
|
customRowHeights: (item: Endpoint, lineHeight, paddings) => {
|
||||||
const lines = item.getEndpointSubsets().length || 1;
|
const lines = item.getEndpointSubsets().length || 1;
|
||||||
@ -60,13 +57,3 @@ export class Endpoints extends React.Component<Props> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EndpointMenu(props: KubeObjectMenuProps<Endpoint>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(endpointApi, {
|
|
||||||
Menu: EndpointMenu
|
|
||||||
})
|
|
||||||
|
|||||||
@ -5,15 +5,15 @@ import { disposeOnUnmount, observer } from "mobx-react";
|
|||||||
import { reaction } from "mobx";
|
import { reaction } from "mobx";
|
||||||
import { Trans } from "@lingui/macro";
|
import { Trans } from "@lingui/macro";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { Ingress, ILoadBalancerIngress, ingressApi } from "../../api/endpoints";
|
import { Ingress, ILoadBalancerIngress } from "../../api/endpoints";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { ingressStore } from "./ingress.store";
|
import { ingressStore } from "./ingress.store";
|
||||||
import { ResourceMetrics } from "../resource-metrics";
|
import { ResourceMetrics } from "../resource-metrics";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { IngressCharts } from "./ingress-charts";
|
import { IngressCharts } from "./ingress-charts";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Ingress> {
|
interface Props extends KubeObjectDetailsProps<Ingress> {
|
||||||
}
|
}
|
||||||
@ -134,6 +134,10 @@ export class IngressDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(ingressApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: IngressDetails,
|
kind: "Ingress",
|
||||||
})
|
apiVersions: ["extensions/v1beta1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <IngressDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -48,9 +48,6 @@ export class Ingresses extends React.Component<Props> {
|
|||||||
ingress.getRoutes().map(route => <p key={route}>{route}</p>),
|
ingress.getRoutes().map(route => <p key={route}>{route}</p>),
|
||||||
ingress.getAge(),
|
ingress.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: Ingress) => {
|
|
||||||
return <IngressMenu object={item}/>
|
|
||||||
}}
|
|
||||||
tableProps={{
|
tableProps={{
|
||||||
customRowHeights: (item: Ingress, lineHeight, paddings) => {
|
customRowHeights: (item: Ingress, lineHeight, paddings) => {
|
||||||
const lines = item.getRoutes().length || 1;
|
const lines = item.getRoutes().length || 1;
|
||||||
@ -61,13 +58,3 @@ export class Ingresses extends React.Component<Props> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IngressMenu(props: KubeObjectMenuProps<Ingress>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(ingressApi, {
|
|
||||||
Menu: IngressMenu
|
|
||||||
})
|
|
||||||
|
|||||||
@ -47,20 +47,7 @@ export class NetworkPolicies extends React.Component<Props> {
|
|||||||
item.getTypes().join(", "),
|
item.getTypes().join(", "),
|
||||||
item.getAge(),
|
item.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: NetworkPolicy) => {
|
|
||||||
return <NetworkPolicyMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NetworkPolicyMenu(props: KubeObjectMenuProps<NetworkPolicy>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(networkPolicyApi, {
|
|
||||||
Menu: NetworkPolicyMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -4,15 +4,15 @@ import get from "lodash/get";
|
|||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
import { t, Trans } from "@lingui/macro";
|
import { t, Trans } from "@lingui/macro";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { IPolicyEgress, IPolicyIngress, IPolicyIpBlock, IPolicySelector, NetworkPolicy, networkPolicyApi } from "../../api/endpoints/network-policy.api";
|
import { IPolicyEgress, IPolicyIngress, IPolicyIpBlock, IPolicySelector, NetworkPolicy } from "../../api/endpoints/network-policy.api";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<NetworkPolicy> {
|
interface Props extends KubeObjectDetailsProps<NetworkPolicy> {
|
||||||
}
|
}
|
||||||
@ -144,6 +144,10 @@ export class NetworkPolicyDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(networkPolicyApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: NetworkPolicyDetails
|
kind: "NetworkPolicy",
|
||||||
})
|
apiVersions: ["networking.k8s.io/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <NetworkPolicyDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -7,13 +7,13 @@ import { DrawerItem, DrawerTitle } from "../drawer";
|
|||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { Service, serviceApi, endpointApi } from "../../api/endpoints";
|
import { Service, endpointApi } from "../../api/endpoints";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { ServicePortComponent } from "./service-port-component";
|
import { ServicePortComponent } from "./service-port-component";
|
||||||
import { endpointStore } from "../+network-endpoints/endpoints.store";
|
import { endpointStore } from "../+network-endpoints/endpoints.store";
|
||||||
import { ServiceDetailsEndpoint } from "./service-details-endpoint";
|
import { ServiceDetailsEndpoint } from "./service-details-endpoint";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Service> {
|
interface Props extends KubeObjectDetailsProps<Service> {
|
||||||
}
|
}
|
||||||
@ -85,6 +85,10 @@ export class ServiceDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(serviceApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: ServiceDetails,
|
kind: "Service",
|
||||||
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <ServiceDetails {...props} />
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -70,20 +70,7 @@ export class Services extends React.Component<Props> {
|
|||||||
service.getAge(),
|
service.getAge(),
|
||||||
{ title: service.getStatus(), className: service.getStatus().toLowerCase() },
|
{ title: service.getStatus(), className: service.getStatus().toLowerCase() },
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: Service) => {
|
|
||||||
return <ServiceMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ServiceMenu(props: KubeObjectMenuProps<Service>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(serviceApi, {
|
|
||||||
Menu: ServiceMenu
|
|
||||||
})
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
export * from "./nodes"
|
export * from "./nodes"
|
||||||
export * from "./nodes.route"
|
export * from "./nodes.route"
|
||||||
export * from "./node-menu"
|
export * from "./node-details"
|
||||||
export * from "./node-details"
|
|
||||||
|
|||||||
@ -11,13 +11,13 @@ import { nodesStore } from "./nodes.store";
|
|||||||
import { ResourceMetrics } from "../resource-metrics";
|
import { ResourceMetrics } from "../resource-metrics";
|
||||||
import { podsStore } from "../+workloads-pods/pods.store";
|
import { podsStore } from "../+workloads-pods/pods.store";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { Node, nodesApi } from "../../api/endpoints";
|
import { Node } from "../../api/endpoints";
|
||||||
import { NodeCharts } from "./node-charts";
|
import { NodeCharts } from "./node-charts";
|
||||||
import { reaction } from "mobx";
|
import { reaction } from "mobx";
|
||||||
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Node> {
|
interface Props extends KubeObjectDetailsProps<Node> {
|
||||||
}
|
}
|
||||||
@ -155,6 +155,10 @@ export class NodeDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(nodesApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: NodeDetails,
|
kind: "Node",
|
||||||
});
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <NodeDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -1,78 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { t, Trans } from "@lingui/macro";
|
|
||||||
import { Node } from "../../api/endpoints/nodes.api";
|
|
||||||
import { createTerminalTab, terminalStore } from "../dock/terminal.store";
|
|
||||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
|
||||||
import { MenuItem } from "../menu";
|
|
||||||
import { Icon } from "../icon";
|
|
||||||
import { _i18n } from "../../i18n";
|
|
||||||
import { hideDetails } from "../../navigation";
|
|
||||||
|
|
||||||
export function NodeMenu(props: KubeObjectMenuProps<Node>) {
|
|
||||||
const { object: node, toolbar } = props;
|
|
||||||
if (!node) return null;
|
|
||||||
const nodeName = node.getName();
|
|
||||||
|
|
||||||
const sendToTerminal = (command: string) => {
|
|
||||||
terminalStore.sendCommand(command, {
|
|
||||||
enter: true,
|
|
||||||
newTab: true,
|
|
||||||
});
|
|
||||||
hideDetails();
|
|
||||||
}
|
|
||||||
|
|
||||||
const shell = () => {
|
|
||||||
createTerminalTab({
|
|
||||||
title: _i18n._(t`Node`) + `: ${nodeName}`,
|
|
||||||
node: nodeName,
|
|
||||||
});
|
|
||||||
hideDetails();
|
|
||||||
}
|
|
||||||
|
|
||||||
const cordon = () => {
|
|
||||||
sendToTerminal(`kubectl cordon ${nodeName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const unCordon = () => {
|
|
||||||
sendToTerminal(`kubectl uncordon ${nodeName}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const drain = () => {
|
|
||||||
const command = `kubectl drain ${nodeName} --delete-local-data --ignore-daemonsets --force`;
|
|
||||||
ConfirmDialog.open({
|
|
||||||
ok: () => sendToTerminal(command),
|
|
||||||
labelOk: _i18n._(t`Drain Node`),
|
|
||||||
message: (
|
|
||||||
<p>
|
|
||||||
<Trans>Are you sure you want to drain <b>{nodeName}</b>?</Trans>
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}>
|
|
||||||
<MenuItem onClick={shell}>
|
|
||||||
<Icon svg="ssh" interactive={toolbar} title={_i18n._(t`Node shell`)}/>
|
|
||||||
<span className="title"><Trans>Shell</Trans></span>
|
|
||||||
</MenuItem>
|
|
||||||
{!node.isUnschedulable() && (
|
|
||||||
<MenuItem onClick={cordon}>
|
|
||||||
<Icon material="pause_circle_filled" title={_i18n._(t`Cordon`)} interactive={toolbar}/>
|
|
||||||
<span className="title"><Trans>Cordon</Trans></span>
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{node.isUnschedulable() && (
|
|
||||||
<MenuItem onClick={unCordon}>
|
|
||||||
<Icon material="play_circle_filled" title={_i18n._(t`Uncordon`)} interactive={toolbar}/>
|
|
||||||
<span className="title"><Trans>Uncordon</Trans></span>
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
<MenuItem onClick={drain}>
|
|
||||||
<Icon material="delete_sweep" title={_i18n._(t`Drain`)} interactive={toolbar}/>
|
|
||||||
<span className="title"><Trans>Drain</Trans></span>
|
|
||||||
</MenuItem>
|
|
||||||
</KubeObjectMenu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -9,15 +9,13 @@ import { nodesStore } from "./nodes.store";
|
|||||||
import { podsStore } from "../+workloads-pods/pods.store";
|
import { podsStore } from "../+workloads-pods/pods.store";
|
||||||
import { KubeObjectListLayout } from "../kube-object";
|
import { KubeObjectListLayout } from "../kube-object";
|
||||||
import { INodesRouteParams } from "./nodes.route";
|
import { INodesRouteParams } from "./nodes.route";
|
||||||
import { Node, nodesApi } from "../../api/endpoints/nodes.api";
|
import { Node } from "../../api/endpoints/nodes.api";
|
||||||
import { NodeMenu } from "./node-menu";
|
|
||||||
import { LineProgress } from "../line-progress";
|
import { LineProgress } from "../line-progress";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { bytesToUnits } from "../../utils/convertMemory";
|
import { bytesToUnits } from "../../utils/convertMemory";
|
||||||
import { Tooltip, TooltipPosition } from "../tooltip";
|
import { Tooltip, TooltipPosition } from "../tooltip";
|
||||||
import kebabCase from "lodash/kebabCase";
|
import kebabCase from "lodash/kebabCase";
|
||||||
import upperFirst from "lodash/upperFirst";
|
import upperFirst from "lodash/upperFirst";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
|
|
||||||
enum sortBy {
|
enum sortBy {
|
||||||
name = "name",
|
name = "name",
|
||||||
@ -178,15 +176,8 @@ export class Nodes extends React.Component<Props> {
|
|||||||
this.renderConditions(node),
|
this.renderConditions(node),
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
renderItemMenu={(item: Node) => {
|
|
||||||
return <NodeMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</TabLayout>
|
</TabLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(nodesApi, {
|
|
||||||
Menu: NodeMenu,
|
|
||||||
});
|
|
||||||
|
|||||||
@ -50,20 +50,7 @@ export class PodSecurityPolicies extends React.Component {
|
|||||||
item.getAge(),
|
item.getAge(),
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
renderItemMenu={(item: PodSecurityPolicy) => {
|
|
||||||
return <PodSecurityPolicyMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PodSecurityPolicyMenu(props: KubeObjectMenuProps<PodSecurityPolicy>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(pspApi, {
|
|
||||||
Menu: PodSecurityPolicyMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -5,11 +5,11 @@ import { observer } from "mobx-react";
|
|||||||
import { Trans } from "@lingui/macro";
|
import { Trans } from "@lingui/macro";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { PodSecurityPolicy, pspApi } from "../../api/endpoints";
|
import { PodSecurityPolicy } from "../../api/endpoints";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PodSecurityPolicy> {
|
interface Props extends KubeObjectDetailsProps<PodSecurityPolicy> {
|
||||||
}
|
}
|
||||||
@ -209,6 +209,10 @@ export class PodSecurityPolicyDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(pspApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: PodSecurityPolicyDetails,
|
kind: "PodSecurityPolicy",
|
||||||
});
|
apiVersions: ["policy/v1beta1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <PodSecurityPolicyDetails {...props}/>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -8,10 +8,10 @@ import { Badge } from "../badge";
|
|||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { StorageClass, storageClassApi } from "../../api/endpoints";
|
import { StorageClass } from "../../api/endpoints";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<StorageClass> {
|
interface Props extends KubeObjectDetailsProps<StorageClass> {
|
||||||
}
|
}
|
||||||
@ -62,6 +62,10 @@ export class StorageClassDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(storageClassApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: StorageClassDetails
|
kind: "StorageClass",
|
||||||
})
|
apiVersions: ["storage.k8s.io/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <StorageClassDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -53,20 +53,7 @@ export class StorageClasses extends React.Component<Props> {
|
|||||||
storageClass.isDefault() ? <Trans>Yes</Trans> : null,
|
storageClass.isDefault() ? <Trans>Yes</Trans> : null,
|
||||||
storageClass.getAge(),
|
storageClass.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: StorageClass) => {
|
|
||||||
return <StorageClassMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StorageClassMenu(props: KubeObjectMenuProps<StorageClass>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(storageClassApi, {
|
|
||||||
Menu: StorageClassMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -14,10 +14,10 @@ import { getDetailsUrl } from "../../navigation";
|
|||||||
import { ResourceMetrics } from "../resource-metrics";
|
import { ResourceMetrics } from "../resource-metrics";
|
||||||
import { VolumeClaimDiskChart } from "./volume-claim-disk-chart";
|
import { VolumeClaimDiskChart } from "./volume-claim-disk-chart";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { PersistentVolumeClaim, pvcApi } from "../../api/endpoints";
|
import { PersistentVolumeClaim } from "../../api/endpoints";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PersistentVolumeClaim> {
|
interface Props extends KubeObjectDetailsProps<PersistentVolumeClaim> {
|
||||||
}
|
}
|
||||||
@ -95,6 +95,10 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(pvcApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: PersistentVolumeClaimDetails,
|
kind: "PersistentVolumeClaim",
|
||||||
})
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <PersistentVolumeClaimDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -82,20 +82,7 @@ export class PersistentVolumeClaims extends React.Component<Props> {
|
|||||||
{ title: pvc.getStatus(), className: pvc.getStatus().toLowerCase() },
|
{ title: pvc.getStatus(), className: pvc.getStatus().toLowerCase() },
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
renderItemMenu={(item: PersistentVolumeClaim) => {
|
|
||||||
return <PersistentVolumeClaimMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PersistentVolumeClaimMenu(props: KubeObjectMenuProps<PersistentVolumeClaim>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(pvcApi, {
|
|
||||||
Menu: PersistentVolumeClaimMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -9,10 +9,10 @@ import { DrawerItem, DrawerTitle } from "../drawer";
|
|||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { getDetailsUrl } from "../../navigation";
|
import { getDetailsUrl } from "../../navigation";
|
||||||
import { PersistentVolume, persistentVolumeApi, pvcApi } from "../../api/endpoints";
|
import { PersistentVolume, pvcApi } from "../../api/endpoints";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PersistentVolume> {
|
interface Props extends KubeObjectDetailsProps<PersistentVolume> {
|
||||||
}
|
}
|
||||||
@ -103,6 +103,10 @@ export class PersistentVolumeDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(persistentVolumeApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: PersistentVolumeDetails
|
kind: "PersistentVolume",
|
||||||
})
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <PersistentVolumeDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -72,20 +72,7 @@ export class PersistentVolumes extends React.Component<Props> {
|
|||||||
{ title: volume.getStatus(), className: volume.getStatus().toLowerCase() }
|
{ title: volume.getStatus(), className: volume.getStatus().toLowerCase() }
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
renderItemMenu={(item: PersistentVolume) => {
|
|
||||||
return <PersistentVolumeMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PersistentVolumeMenu(props: KubeObjectMenuProps<PersistentVolume>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(persistentVolumeApi, {
|
|
||||||
Menu: PersistentVolumeMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import "./role-binding-details.scss"
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { t, Trans } from "@lingui/macro";
|
import { t, Trans } from "@lingui/macro";
|
||||||
import { AddRemoveButtons } from "../add-remove-buttons";
|
import { AddRemoveButtons } from "../add-remove-buttons";
|
||||||
import { clusterRoleBindingApi, IRoleBindingSubject, RoleBinding, roleBindingApi } from "../../api/endpoints";
|
import { IRoleBindingSubject, RoleBinding } from "../../api/endpoints";
|
||||||
import { autobind, prevDefault } from "../../utils";
|
import { autobind, prevDefault } from "../../utils";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
@ -15,8 +15,8 @@ import { roleBindingsStore } from "./role-bindings.store";
|
|||||||
import { AddRoleBindingDialog } from "./add-role-binding-dialog";
|
import { AddRoleBindingDialog } from "./add-role-binding-dialog";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<RoleBinding> {
|
interface Props extends KubeObjectDetailsProps<RoleBinding> {
|
||||||
}
|
}
|
||||||
@ -125,6 +125,17 @@ export class RoleBindingDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews([roleBindingApi, clusterRoleBindingApi], {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: RoleBindingDetails,
|
kind: "RoleBinding",
|
||||||
});
|
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <RoleBindingDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
kubeObjectDetailRegistry.add({
|
||||||
|
kind: "ClusterRoleBinding",
|
||||||
|
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <RoleBindingDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -54,9 +54,6 @@ export class RoleBindings extends React.Component<Props> {
|
|||||||
binding.getNs() || "-",
|
binding.getNs() || "-",
|
||||||
binding.getAge(),
|
binding.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: RoleBinding) => {
|
|
||||||
return <RoleBindingMenu object={item}/>
|
|
||||||
}}
|
|
||||||
addRemoveButtons={{
|
addRemoveButtons={{
|
||||||
onAdd: () => AddRoleBindingDialog.open(),
|
onAdd: () => AddRoleBindingDialog.open(),
|
||||||
addTooltip: <Trans>Create new RoleBinding</Trans>,
|
addTooltip: <Trans>Create new RoleBinding</Trans>,
|
||||||
@ -65,13 +62,3 @@ export class RoleBindings extends React.Component<Props> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RoleBindingMenu(props: KubeObjectMenuProps<RoleBinding>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews([roleBindingApi, clusterRoleBindingApi], {
|
|
||||||
Menu: RoleBindingMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import { DrawerTitle } from "../drawer";
|
|||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { clusterRoleApi, Role, roleApi } from "../../api/endpoints";
|
import { Role } from "../../api/endpoints";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Role> {
|
interface Props extends KubeObjectDetailsProps<Role> {
|
||||||
}
|
}
|
||||||
@ -66,6 +66,18 @@ export class RoleDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews([roleApi, clusterRoleApi], {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: RoleDetails,
|
kind: "Role",
|
||||||
})
|
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <RoleDetails {...props}/>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
kubeObjectDetailRegistry.add({
|
||||||
|
kind: "ClusterRole",
|
||||||
|
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <RoleDetails {...props}/>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -5,12 +5,10 @@ import { observer } from "mobx-react";
|
|||||||
import { Trans } from "@lingui/macro";
|
import { Trans } from "@lingui/macro";
|
||||||
import { RouteComponentProps } from "react-router";
|
import { RouteComponentProps } from "react-router";
|
||||||
import { IRolesRouteParams } from "../+user-management/user-management.route";
|
import { IRolesRouteParams } from "../+user-management/user-management.route";
|
||||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
|
||||||
import { rolesStore } from "./roles.store";
|
import { rolesStore } from "./roles.store";
|
||||||
import { clusterRoleApi, Role, roleApi } from "../../api/endpoints";
|
import { Role } from "../../api/endpoints";
|
||||||
import { KubeObjectListLayout } from "../kube-object";
|
import { KubeObjectListLayout } from "../kube-object";
|
||||||
import { AddRoleDialog } from "./add-role-dialog";
|
import { AddRoleDialog } from "./add-role-dialog";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
|
|
||||||
enum sortBy {
|
enum sortBy {
|
||||||
name = "name",
|
name = "name",
|
||||||
@ -48,9 +46,6 @@ export class Roles extends React.Component<Props> {
|
|||||||
role.getNs() || "-",
|
role.getNs() || "-",
|
||||||
role.getAge(),
|
role.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: Role) => {
|
|
||||||
return <RoleMenu object={item}/>
|
|
||||||
}}
|
|
||||||
addRemoveButtons={{
|
addRemoveButtons={{
|
||||||
onAdd: () => AddRoleDialog.open(),
|
onAdd: () => AddRoleDialog.open(),
|
||||||
addTooltip: <Trans>Create new Role</Trans>,
|
addTooltip: <Trans>Create new Role</Trans>,
|
||||||
@ -61,13 +56,3 @@ export class Roles extends React.Component<Props> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RoleMenu(props: KubeObjectMenuProps<Role>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews([roleApi, clusterRoleApi], {
|
|
||||||
Menu: RoleMenu,
|
|
||||||
});
|
|
||||||
|
|||||||
@ -9,13 +9,13 @@ import { DrawerItem, DrawerTitle } from "../drawer";
|
|||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { secretsStore } from "../+config-secrets/secrets.store";
|
import { secretsStore } from "../+config-secrets/secrets.store";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Secret, ServiceAccount, serviceAccountsApi } from "../../api/endpoints";
|
import { Secret, ServiceAccount } from "../../api/endpoints";
|
||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { getDetailsUrl } from "../../navigation";
|
import { getDetailsUrl } from "../../navigation";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<ServiceAccount> {
|
interface Props extends KubeObjectDetailsProps<ServiceAccount> {
|
||||||
}
|
}
|
||||||
@ -132,6 +132,10 @@ export class ServiceAccountsDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(serviceAccountsApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: ServiceAccountsDetails
|
kind: "ServiceAccount",
|
||||||
})
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <ServiceAccountsDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { KubeObjectListLayout } from "../kube-object";
|
|||||||
import { IServiceAccountsRouteParams } from "../+user-management";
|
import { IServiceAccountsRouteParams } from "../+user-management";
|
||||||
import { serviceAccountsStore } from "./service-accounts.store";
|
import { serviceAccountsStore } from "./service-accounts.store";
|
||||||
import { CreateServiceAccountDialog } from "./create-service-account-dialog";
|
import { CreateServiceAccountDialog } from "./create-service-account-dialog";
|
||||||
import { apiManager } from "../../api/api-manager";
|
import { kubeObjectMenuRegistry } from "../../api/kube-object-menu-registry";
|
||||||
|
|
||||||
enum sortBy {
|
enum sortBy {
|
||||||
name = "name",
|
name = "name",
|
||||||
@ -64,18 +64,20 @@ export class ServiceAccounts extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ServiceAccountMenu(props: KubeObjectMenuProps<ServiceAccount>) {
|
function ServiceAccountMenu(props: KubeObjectMenuProps<ServiceAccount>) {
|
||||||
const { object, toolbar } = props;
|
const { object, toolbar } = props;
|
||||||
return (
|
return (
|
||||||
<KubeObjectMenu {...props}>
|
<MenuItem onClick={() => openServiceAccountKubeConfig(object)}>
|
||||||
<MenuItem onClick={() => openServiceAccountKubeConfig(object)}>
|
<Icon material="insert_drive_file" title="Kubeconfig File" interactive={toolbar}/>
|
||||||
<Icon material="insert_drive_file" title="Kubeconfig File" interactive={toolbar}/>
|
<span className="title"><Trans>Kubeconfig</Trans></span>
|
||||||
<span className="title"><Trans>Kubeconfig</Trans></span>
|
</MenuItem>
|
||||||
</MenuItem>
|
|
||||||
</KubeObjectMenu>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(serviceAccountsApi, {
|
kubeObjectMenuRegistry.add({
|
||||||
Menu: ServiceAccountMenu,
|
kind: "ServiceAccount",
|
||||||
|
apiVersions: ["v1"],
|
||||||
|
components: {
|
||||||
|
MenuItem: ServiceAccountMenu
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -12,9 +12,9 @@ import { KubeEventDetails } from "../+events/kube-event-details";
|
|||||||
import { cronJobStore } from "./cronjob.store";
|
import { cronJobStore } from "./cronjob.store";
|
||||||
import { getDetailsUrl } from "../../navigation";
|
import { getDetailsUrl } from "../../navigation";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { CronJob, cronJobApi, Job } from "../../api/endpoints";
|
import { CronJob, Job } from "../../api/endpoints";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<CronJob> {
|
interface Props extends KubeObjectDetailsProps<CronJob> {
|
||||||
}
|
}
|
||||||
@ -87,6 +87,10 @@ export class CronJobDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(cronJobApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: CronJobDetails,
|
kind: "CronJob",
|
||||||
})
|
apiVersions: ["batch/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <CronJobDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -15,8 +15,8 @@ import { ICronJobsRouteParams } from "../+workloads";
|
|||||||
import { KubeObjectListLayout } from "../kube-object";
|
import { KubeObjectListLayout } from "../kube-object";
|
||||||
import { KubeEventIcon } from "../+events/kube-event-icon";
|
import { KubeEventIcon } from "../+events/kube-event-icon";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { CronJobTriggerDialog } from "./cronjob-trigger-dialog";
|
import { CronJobTriggerDialog } from "./cronjob-trigger-dialog";
|
||||||
|
import { kubeObjectMenuRegistry } from "../../api/kube-object-menu-registry";
|
||||||
|
|
||||||
enum sortBy {
|
enum sortBy {
|
||||||
name = "name",
|
name = "name",
|
||||||
@ -85,15 +85,17 @@ export class CronJobs extends React.Component<Props> {
|
|||||||
export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
|
export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
|
||||||
const { object, toolbar } = props;
|
const { object, toolbar } = props;
|
||||||
return (
|
return (
|
||||||
<KubeObjectMenu {...props}>
|
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
|
||||||
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
|
<Icon material="play_circle_filled" title={_i18n._(t`Trigger`)} interactive={toolbar}/>
|
||||||
<Icon material="play_circle_filled" title={_i18n._(t`Trigger`)} interactive={toolbar}/>
|
<span className="title"><Trans>Trigger</Trans></span>
|
||||||
<span className="title"><Trans>Trigger</Trans></span>
|
</MenuItem>
|
||||||
</MenuItem>
|
|
||||||
</KubeObjectMenu>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(cronJobApi, {
|
kubeObjectMenuRegistry.add({
|
||||||
Menu: CronJobMenu,
|
kind: "CronJob",
|
||||||
|
apiVersions: ["batch/v1beta1"],
|
||||||
|
components: {
|
||||||
|
MenuItem: CronJobMenu
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -12,13 +12,13 @@ import { KubeEventDetails } from "../+events/kube-event-details";
|
|||||||
import { daemonSetStore } from "./daemonsets.store";
|
import { daemonSetStore } from "./daemonsets.store";
|
||||||
import { podsStore } from "../+workloads-pods/pods.store";
|
import { podsStore } from "../+workloads-pods/pods.store";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { DaemonSet, daemonSetApi } from "../../api/endpoints";
|
import { DaemonSet } from "../../api/endpoints";
|
||||||
import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics";
|
import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics";
|
||||||
import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
|
import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
|
||||||
import { reaction } from "mobx";
|
import { reaction } from "mobx";
|
||||||
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<DaemonSet> {
|
interface Props extends KubeObjectDetailsProps<DaemonSet> {
|
||||||
}
|
}
|
||||||
@ -97,6 +97,10 @@ export class DaemonSetDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(daemonSetApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: DaemonSetDetails,
|
kind: "DaemonSet",
|
||||||
})
|
apiVersions: ["apps/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props: any) => <DaemonSetDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -70,20 +70,8 @@ export class DaemonSets extends React.Component<Props> {
|
|||||||
this.renderNodeSelector(daemonSet),
|
this.renderNodeSelector(daemonSet),
|
||||||
daemonSet.getAge(),
|
daemonSet.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: DaemonSet) => {
|
|
||||||
return <DaemonSetMenu object={item}/>
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DaemonSetMenu(props: KubeObjectMenuProps<DaemonSet>) {
|
|
||||||
return (
|
|
||||||
<KubeObjectMenu {...props}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiManager.registerViews(daemonSetApi, {
|
|
||||||
Menu: DaemonSetMenu,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -21,8 +21,8 @@ import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
|
|||||||
import { reaction } from "mobx";
|
import { reaction } from "mobx";
|
||||||
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||||
import { ReplicaSets } from "../+workloads-replicasets";
|
import { ReplicaSets } from "../+workloads-replicasets";
|
||||||
import { apiManager } from "../../api/api-manager";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Deployment> {
|
interface Props extends KubeObjectDetailsProps<Deployment> {
|
||||||
}
|
}
|
||||||
@ -122,6 +122,10 @@ export class DeploymentDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiManager.registerViews(deploymentApi, {
|
kubeObjectDetailRegistry.add({
|
||||||
Details: DeploymentDetails
|
kind: "Deployment",
|
||||||
})
|
apiVersions: ["apps/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props: any) => <DeploymentDetails {...props} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user