1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge remote-tracking branch 'origin/master' into extension_install_1277

This commit is contained in:
Roman 2020-11-19 22:53:19 +02:00
commit 3df6c073e9
25 changed files with 536 additions and 422 deletions

View File

@ -1,4 +1,4 @@
module.exports = { module.exports = {
ignorePatterns: ["src/extensions/npm/extensions/dist/**/*"], ignorePatterns: ["src/extensions/npm/extensions/dist/**/*"],
overrides: [ overrides: [
{ {
@ -13,12 +13,14 @@ module.exports = {
env: { env: {
node: true node: true
}, },
parserOptions: { parserOptions: {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module', sourceType: 'module',
}, },
rules: { rules: {
"indent": ["error", 2], "indent": ["error", 2, {
"SwitchCase": 1,
}],
"no-unused-vars": "off", "no-unused-vars": "off",
"semi": ["error", "always"], "semi": ["error", "always"],
} }
@ -33,10 +35,10 @@ module.exports = {
"__mocks__/*.ts", "__mocks__/*.ts",
], ],
parser: "@typescript-eslint/parser", parser: "@typescript-eslint/parser",
extends: [ extends: [
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
], ],
parserOptions: { parserOptions: {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module', sourceType: 'module',
}, },
@ -48,7 +50,9 @@ module.exports = {
"@typescript-eslint/ban-types": "off", "@typescript-eslint/ban-types": "off",
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/no-empty-interface": "off",
"indent": ["error", 2], "indent": ["error", 2, {
"SwitchCase": 1,
}],
"semi": "off", "semi": "off",
"@typescript-eslint/semi": ["error"], "@typescript-eslint/semi": ["error"],
}, },
@ -58,10 +62,10 @@ module.exports = {
"src/renderer/**/*.tsx", "src/renderer/**/*.tsx",
], ],
parser: "@typescript-eslint/parser", parser: "@typescript-eslint/parser",
extends: [ extends: [
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
], ],
parserOptions: { parserOptions: {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module', sourceType: 'module',
jsx: true, jsx: true,
@ -78,7 +82,9 @@ module.exports = {
"@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/ban-types": "off", "@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-function": "off",
"indent": ["error", 2], "indent": ["error", 2, {
"SwitchCase": 1,
}],
"semi": "off", "semi": "off",
"@typescript-eslint/semi": ["error"], "@typescript-eslint/semi": ["error"],
}, },

View File

@ -1,9 +1,10 @@
// Extensions-api -> Register page menu items // Extensions-api -> Register page menu items
import type { IconProps } from "../../renderer/components/icon"; import type { IconProps } from "../../renderer/components/icon";
import type React from "react"; import type React from "react";
import { action } from "mobx"; import { action, computed } from "mobx";
import { BaseRegistry } from "./base-registry"; import { BaseRegistry } from "./base-registry";
import { LensExtension } from "../lens-extension"; import { LensExtension } from "../lens-extension";
import { RegisteredPage } from "./page-registry";
export interface PageMenuTarget<P extends object = any> { export interface PageMenuTarget<P extends object = any> {
extensionId?: string; extensionId?: string;
@ -17,11 +18,16 @@ export interface PageMenuRegistration {
components: PageMenuComponents; components: PageMenuComponents;
} }
export interface ClusterPageMenuRegistration extends PageMenuRegistration {
id?: string;
parentId?: string;
}
export interface PageMenuComponents { export interface PageMenuComponents {
Icon: React.ComponentType<IconProps>; Icon: React.ComponentType<IconProps>;
} }
export class PageMenuRegistry extends BaseRegistry<PageMenuRegistration, Required<PageMenuRegistration>> { export class GlobalPageMenuRegistry extends BaseRegistry<PageMenuRegistration> {
@action @action
add(items: PageMenuRegistration[], ext: LensExtension) { add(items: PageMenuRegistration[], ext: LensExtension) {
const normalizedItems = items.map(menuItem => { const normalizedItems = items.map(menuItem => {
@ -35,5 +41,31 @@ export class PageMenuRegistry extends BaseRegistry<PageMenuRegistration, Require
} }
} }
export const globalPageMenuRegistry = new PageMenuRegistry(); export class ClusterPageMenuRegistry extends BaseRegistry<ClusterPageMenuRegistration> {
export const clusterPageMenuRegistry = new PageMenuRegistry(); @action
add(items: PageMenuRegistration[], ext: LensExtension) {
const normalizedItems = items.map(menuItem => {
menuItem.target = {
extensionId: ext.name,
...(menuItem.target || {}),
};
return menuItem;
});
return super.add(normalizedItems);
}
getRootItems() {
return this.getItems().filter((item) => !item.parentId);
}
getSubItems(parent: ClusterPageMenuRegistration) {
return this.getItems().filter((item) => item.parentId === parent.id && item.target.extensionId === parent.target.extensionId);
}
getByPage(page: RegisteredPage) {
return this.getItems().find((item) => item.target?.pageId == page.id && item.target?.extensionId === page.extensionId);
}
}
export const globalPageMenuRegistry = new GlobalPageMenuRegistry();
export const clusterPageMenuRegistry = new ClusterPageMenuRegistry();

View File

@ -24,60 +24,60 @@ export class PrometheusLens implements PrometheusProvider {
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery { public getQueries(opts: PrometheusQueryOpts): PrometheusQuery {
switch(opts.category) { switch(opts.category) {
case 'cluster': case 'cluster':
return { return {
memoryUsage: ` memoryUsage: `
sum( sum(
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes) node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
) by (kubernetes_name) ) by (kubernetes_name)
`.replace(/_bytes/g, `_bytes{kubernetes_node=~"${opts.nodes}"}`), `.replace(/_bytes/g, `_bytes{kubernetes_node=~"${opts.nodes}"}`),
memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`, memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`,
memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`, memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`,
memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`, memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`,
cpuUsage: `sum(rate(node_cpu_seconds_total{kubernetes_node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`, cpuUsage: `sum(rate(node_cpu_seconds_total{kubernetes_node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`,
cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`, cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`, cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`, cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
podUsage: `sum(kubelet_running_pod_count{instance=~"${opts.nodes}"})`, podUsage: `sum(kubelet_running_pod_count{instance=~"${opts.nodes}"})`,
podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`, podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`,
fsSize: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`, fsSize: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`,
fsUsage: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)` fsUsage: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`
}; };
case 'nodes': case 'nodes':
return { return {
memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_node)`, memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_node)`,
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`, memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(kubernetes_node)`, cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(kubernetes_node)`,
cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`, cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`,
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (kubernetes_node)`, fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (kubernetes_node)`,
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (kubernetes_node)` fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (kubernetes_node)`
}; };
case 'pods': case 'pods':
return { return {
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})` networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`
}; };
case 'pvc': case 'pvc':
return { return {
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`, diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`,
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)` diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`
}; };
case 'ingress': case 'ingress':
const bytesSent = (ingress: string, statuses: string) => const bytesSent = (ingress: string, statuses: string) =>
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`; `sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`;
return { return {
bytesSentSuccess: bytesSent(opts.igress, "^2\\\\d*"), bytesSentSuccess: bytesSent(opts.igress, "^2\\\\d*"),
bytesSentFailure: bytesSent(opts.ingres, "^5\\\\d*"), bytesSentFailure: bytesSent(opts.ingres, "^5\\\\d*"),
requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`, requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`,
responseDurationSeconds: `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)` responseDurationSeconds: `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`
}; };
} }
} }
} }

View File

@ -32,60 +32,60 @@ export class PrometheusOperator implements PrometheusProvider {
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery { public getQueries(opts: PrometheusQueryOpts): PrometheusQuery {
switch(opts.category) { switch(opts.category) {
case 'cluster': case 'cluster':
return { return {
memoryUsage: ` memoryUsage: `
sum( sum(
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes) node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
) )
`.replace(/_bytes/g, `_bytes * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}`), `.replace(/_bytes/g, `_bytes * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}`),
memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"})`, memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"})`,
memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"})`, memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"})`,
memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"})`, memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"})`,
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`, cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`,
cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`, cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`,
cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"})`, cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"})`,
cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"})`, cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"})`,
podUsage: `sum(kubelet_running_pod_count{node=~"${opts.nodes}"})`, podUsage: `sum(kubelet_running_pod_count{node=~"${opts.nodes}"})`,
podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"})`, podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"})`,
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`, fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`,
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})` fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`
}; };
case 'nodes': case 'nodes':
return { return {
memoryUsage: `sum((node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`, memoryUsage: `sum((node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`,
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`, memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}]) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`, cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}]) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`,
cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`, cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`,
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info) by (node)`, fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info) by (node)`,
fsUsage: `sum((node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) * on (pod,namespace) group_left(node) kube_pod_info) by (node)` fsUsage: `sum((node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`
}; };
case 'pods': case 'pods':
return { return {
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})` networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`
}; };
case 'pvc': case 'pvc':
return { return {
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`, diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`,
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)` diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`
}; };
case 'ingress': case 'ingress':
const bytesSent = (ingress: string, statuses: string) => const bytesSent = (ingress: string, statuses: string) =>
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`; `sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`;
return { return {
bytesSentSuccess: bytesSent(opts.igress, "^2\\\\d*"), bytesSentSuccess: bytesSent(opts.igress, "^2\\\\d*"),
bytesSentFailure: bytesSent(opts.ingres, "^5\\\\d*"), bytesSentFailure: bytesSent(opts.ingres, "^5\\\\d*"),
requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`, requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`,
responseDurationSeconds: `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)` responseDurationSeconds: `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`
}; };
} }
} }
} }

View File

@ -24,60 +24,60 @@ export class PrometheusStacklight implements PrometheusProvider {
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery { public getQueries(opts: PrometheusQueryOpts): PrometheusQuery {
switch(opts.category) { switch(opts.category) {
case 'cluster': case 'cluster':
return { return {
memoryUsage: ` memoryUsage: `
sum( sum(
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes) node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
) by (kubernetes_name) ) by (kubernetes_name)
`.replace(/_bytes/g, `_bytes{node=~"${opts.nodes}"}`), `.replace(/_bytes/g, `_bytes{node=~"${opts.nodes}"}`),
memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`, memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`,
memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`, memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`,
memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`, memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`,
cpuUsage: `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`, cpuUsage: `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`,
cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`, cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`, cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`, cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
podUsage: `sum(kubelet_running_pod_count{instance=~"${opts.nodes}"})`, podUsage: `sum(kubelet_running_pod_count{instance=~"${opts.nodes}"})`,
podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`, podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`,
fsSize: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`, fsSize: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`,
fsUsage: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)` fsUsage: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`
}; };
case 'nodes': case 'nodes':
return { return {
memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`, memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`,
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`, memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(node)`, cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(node)`,
cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`, cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`,
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (node)`, fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (node)`,
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (node)` fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (node)`
}; };
case 'pods': case 'pods':
return { return {
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})` networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`
}; };
case 'pvc': case 'pvc':
return { return {
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`, diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`,
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)` diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`
}; };
case 'ingress': case 'ingress':
const bytesSent = (ingress: string, statuses: string) => const bytesSent = (ingress: string, statuses: string) =>
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`; `sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`;
return { return {
bytesSentSuccess: bytesSent(opts.igress, "^2\\\\d*"), bytesSentSuccess: bytesSent(opts.igress, "^2\\\\d*"),
bytesSentFailure: bytesSent(opts.ingres, "^5\\\\d*"), bytesSentFailure: bytesSent(opts.ingres, "^5\\\\d*"),
requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`, requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`,
responseDurationSeconds: `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)` responseDurationSeconds: `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`
}; };
} }
} }
} }

View File

@ -70,16 +70,16 @@ export class ShellSession extends EventEmitter {
protected async getShellArgs(shell: string): Promise<Array<string>> { protected async getShellArgs(shell: string): Promise<Array<string>> {
switch(path.basename(shell)) { switch(path.basename(shell)) {
case "powershell.exe": case "powershell.exe":
return ["-NoExit", "-command", `& {Set-Location $Env:USERPROFILE; $Env:PATH="${this.helmBinDir};${this.kubectlPathDir};$Env:PATH"}`]; return ["-NoExit", "-command", `& {Set-Location $Env:USERPROFILE; $Env:PATH="${this.helmBinDir};${this.kubectlPathDir};$Env:PATH"}`];
case "bash": case "bash":
return ["--init-file", path.join(this.kubectlBinDir, '.bash_set_path')]; return ["--init-file", path.join(this.kubectlBinDir, '.bash_set_path')];
case "fish": case "fish":
return ["--login", "--init-command", `export PATH="${this.helmBinDir}:${this.kubectlPathDir}:$PATH"; export KUBECONFIG="${this.kubeconfigPath}"`]; return ["--login", "--init-command", `export PATH="${this.helmBinDir}:${this.kubectlPathDir}:$PATH"; export KUBECONFIG="${this.kubeconfigPath}"`];
case "zsh": case "zsh":
return ["--login"]; return ["--login"];
default: default:
return []; return [];
} }
} }
@ -148,16 +148,16 @@ export class ShellSession extends EventEmitter {
const message = Buffer.from(data.slice(1, data.length), "base64").toString(); const message = Buffer.from(data.slice(1, data.length), "base64").toString();
switch (data[0]) { switch (data[0]) {
case "0": case "0":
this.shellProcess.write(message); this.shellProcess.write(message);
break; break;
case "4": case "4":
const resizeMsgObj = JSON.parse(message); const resizeMsgObj = JSON.parse(message);
this.shellProcess.resize(resizeMsgObj["Width"], resizeMsgObj["Height"]); this.shellProcess.resize(resizeMsgObj["Width"], resizeMsgObj["Height"]);
break; break;
case "9": case "9":
this.emit('newToken', message); this.emit('newToken', message);
break; break;
} }
}); });
} }

View File

@ -101,14 +101,14 @@ export class HorizontalPodAutoscaler extends KubeObject {
protected getMetricName(metric: IHpaMetric): string { protected getMetricName(metric: IHpaMetric): string {
const { type, resource, pods, object, external } = metric; const { type, resource, pods, object, external } = metric;
switch (type) { switch (type) {
case HpaMetricType.Resource: case HpaMetricType.Resource:
return resource.name; return resource.name;
case HpaMetricType.Pods: case HpaMetricType.Pods:
return pods.metricName; return pods.metricName;
case HpaMetricType.Object: case HpaMetricType.Object:
return object.metricName; return object.metricName;
case HpaMetricType.External: case HpaMetricType.External:
return external.metricName; return external.metricName;
} }
} }

View File

@ -15,10 +15,10 @@ export const apiKube = new KubeJsonApi({
// Common handler for HTTP api errors // Common handler for HTTP api errors
export function onApiError(error: JsonApiErrorParsed, res: Response) { export function onApiError(error: JsonApiErrorParsed, res: Response) {
switch (res.status) { switch (res.status) {
case 403: case 403:
error.isUsedForNotification = true; error.isUsedForNotification = true;
Notifications.error(error); Notifications.error(error);
break; break;
} }
} }

View File

@ -35,32 +35,32 @@ export function parseKubeApi(path: string): IKubeApiParsed {
if (namespaced) { if (namespaced) {
switch (right.length) { switch (right.length) {
case 1: case 1:
name = right[0]; name = right[0];
// fallthrough // fallthrough
case 0: case 0:
resource = "namespaces"; // special case this due to `split` removing namespaces resource = "namespaces"; // special case this due to `split` removing namespaces
break; break;
default: default:
[namespace, resource, name] = right; [namespace, resource, name] = right;
break; break;
} }
apiVersion = left.pop(); apiVersion = left.pop();
apiGroup = left.join("/"); apiGroup = left.join("/");
} else { } else {
switch (left.length) { switch (left.length) {
case 4: case 4:
[apiGroup, apiVersion, resource, name] = left; [apiGroup, apiVersion, resource, name] = left;
break; break;
case 2: case 2:
resource = left.pop(); resource = left.pop();
// fallthrough // fallthrough
case 1: case 1:
apiVersion = left.pop(); apiVersion = left.pop();
apiGroup = ""; apiGroup = "";
break; break;
default: default:
/** /**
* Given that * Given that
* - `apiVersion` is `GROUP/VERSION` and * - `apiVersion` is `GROUP/VERSION` and
@ -77,15 +77,15 @@ export function parseKubeApi(path: string): IKubeApiParsed {
* 3. otherwise assume apiVersion <- left[0] * 3. otherwise assume apiVersion <- left[0]
* 4. always resource, name <- left[(0 or 1)+1..] * 4. always resource, name <- left[(0 or 1)+1..]
*/ */
if (left[0].includes('.') || left[1].match(/^v[0-9]/)) { if (left[0].includes('.') || left[1].match(/^v[0-9]/)) {
[apiGroup, apiVersion] = left; [apiGroup, apiVersion] = left;
resource = left.slice(2).join("/"); resource = left.slice(2).join("/");
} else { } else {
apiGroup = ""; apiGroup = "";
apiVersion = left[0]; apiVersion = left[0];
[resource, name] = left.slice(1); [resource, name] = left.slice(1);
} }
break; break;
} }
} }

View File

@ -77,19 +77,19 @@ export class AddCluster extends React.Component {
this.kubeContexts.clear(); this.kubeContexts.clear();
switch (this.sourceTab) { switch (this.sourceTab) {
case KubeConfigSourceTab.FILE: case KubeConfigSourceTab.FILE:
const contexts = this.getContexts(this.kubeConfigLocal); const contexts = this.getContexts(this.kubeConfigLocal);
this.kubeContexts.replace(contexts);
break;
case KubeConfigSourceTab.TEXT:
try {
this.error = "";
const contexts = this.getContexts(loadConfig(this.customConfig || "{}"));
this.kubeContexts.replace(contexts); this.kubeContexts.replace(contexts);
} catch (err) { break;
this.error = String(err); case KubeConfigSourceTab.TEXT:
} try {
break; this.error = "";
const contexts = this.getContexts(loadConfig(this.customConfig || "{}"));
this.kubeContexts.replace(contexts);
} catch (err) {
this.error = String(err);
}
break;
} }
if (this.kubeContexts.size === 1) { if (this.kubeContexts.size === 1) {

View File

@ -84,12 +84,12 @@ export class ClusterStore extends KubeObjectStore<Cluster> {
getMetricsValues(source: Partial<IClusterMetrics>): [number, string][] { getMetricsValues(source: Partial<IClusterMetrics>): [number, string][] {
switch (this.metricType) { switch (this.metricType) {
case MetricType.CPU: case MetricType.CPU:
return normalizeMetrics(source.cpuUsage).data.result[0].values; return normalizeMetrics(source.cpuUsage).data.result[0].values;
case MetricType.MEMORY: case MetricType.MEMORY:
return normalizeMetrics(source.memoryUsage).data.result[0].values; return normalizeMetrics(source.memoryUsage).data.result[0].values;
default: default:
return []; return [];
} }
} }

View File

@ -26,30 +26,30 @@ export class HpaDetails extends React.Component<Props> {
const renderName = (metric: IHpaMetric) => { const renderName = (metric: IHpaMetric) => {
switch (metric.type) { switch (metric.type) {
case HpaMetricType.Resource: case HpaMetricType.Resource:
const addition = metric.resource.targetAverageUtilization ? <Trans>(as a percentage of request)</Trans> : ""; const addition = metric.resource.targetAverageUtilization ? <Trans>(as a percentage of request)</Trans> : "";
return <Trans>Resource {metric.resource.name} on Pods {addition}</Trans>; return <Trans>Resource {metric.resource.name} on Pods {addition}</Trans>;
case HpaMetricType.Pods: case HpaMetricType.Pods:
return <Trans>{metric.pods.metricName} on Pods</Trans>; return <Trans>{metric.pods.metricName} on Pods</Trans>;
case HpaMetricType.Object: case HpaMetricType.Object:
const { target } = metric.object; const { target } = metric.object;
const { kind, name } = target; const { kind, name } = target;
const objectUrl = getDetailsUrl(lookupApiLink(target, hpa)); const objectUrl = getDetailsUrl(lookupApiLink(target, hpa));
return ( return (
<Trans> <Trans>
{metric.object.metricName} on{" "} {metric.object.metricName} on{" "}
<Link to={objectUrl}>{kind}/{name}</Link> <Link to={objectUrl}>{kind}/{name}</Link>
</Trans> </Trans>
); );
case HpaMetricType.External: case HpaMetricType.External:
return ( return (
<Trans> <Trans>
{metric.external.metricName} on{" "} {metric.external.metricName} on{" "}
{JSON.stringify(metric.external.selector)} {JSON.stringify(metric.external.selector)}
</Trans> </Trans>
); );
} }
}; };

View File

@ -117,10 +117,10 @@ export class AddQuotaDialog extends React.Component<Props> {
onInputQuota = (evt: React.KeyboardEvent) => { onInputQuota = (evt: React.KeyboardEvent) => {
switch (evt.key) { switch (evt.key) {
case "Enter": case "Enter":
this.setQuota(); this.setQuota();
evt.preventDefault(); // don't submit form evt.preventDefault(); // don't submit form
break; break;
} }
}; };

View File

@ -58,10 +58,10 @@ export class AceEditor extends React.Component<Props, State> {
get theme() { get theme() {
switch (themeStore.activeTheme.type) { switch (themeStore.activeTheme.type) {
case "light": case "light":
return "dreamweaver"; return "dreamweaver";
case "dark": case "dark":
return "terminal"; return "terminal";
} }
} }

View File

@ -34,12 +34,15 @@ import { Terminal } from "./dock/terminal";
import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store"; import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store";
import logger from "../../main/logger"; import logger from "../../main/logger";
import { webFrame } from "electron"; import { webFrame } from "electron";
import { clusterPageRegistry } from "../../extensions/registries/page-registry"; import { clusterPageRegistry, getExtensionPageUrl, PageRegistration, RegisteredPage } from "../../extensions/registries/page-registry";
import { extensionLoader } from "../../extensions/extension-loader"; import { extensionLoader } from "../../extensions/extension-loader";
import { appEventBus } from "../../common/event-bus"; import { appEventBus } from "../../common/event-bus";
import { requestMain } from "../../common/ipc"; import { requestMain } from "../../common/ipc";
import whatInput from 'what-input'; import whatInput from 'what-input';
import { clusterSetFrameIdHandler } from "../../common/cluster-ipc"; import { clusterSetFrameIdHandler } from "../../common/cluster-ipc";
import { ClusterPageMenuRegistration, clusterPageMenuRegistry } from "../../extensions/registries";
import { TabLayoutRoute, TabLayout } from "./layout/tab-layout";
import { Trans } from "@lingui/macro";
@observer @observer
export class App extends React.Component { export class App extends React.Component {
@ -72,9 +75,48 @@ export class App extends React.Component {
return workloadsURL(); return workloadsURL();
} }
getTabLayoutRoutes(menuItem: ClusterPageMenuRegistration) {
const routes: TabLayoutRoute[] = [];
if (!menuItem.id) {
return routes;
}
clusterPageMenuRegistry.getSubItems(menuItem).forEach((item) => {
const page = clusterPageRegistry.getByPageMenuTarget(item.target);
if (page) {
routes.push({
routePath: page.routePath,
url: getExtensionPageUrl({ extensionId: page.extensionId, pageId: page.id, params: item.target.params }),
title: item.title,
component: page.components.Page,
exact: page.exact
});
}
});
return routes;
}
renderExtensionTabLayoutRoutes() {
return clusterPageMenuRegistry.getRootItems().map((menu, index) => {
const tabRoutes = this.getTabLayoutRoutes(menu);
if (tabRoutes.length > 0) {
const pageComponent = () => <TabLayout tabs={tabRoutes} />;
return <Route key={"extension-tab-layout-route-" + index} component={pageComponent}/>;
} else {
const page = clusterPageRegistry.getByPageMenuTarget(menu.target);
if (page) {
const pageComponent = () => <page.components.Page />;
return <Route key={"extension-tab-layout-route-" + index} path={page.routePath} exact={page.exact} component={pageComponent}/>;
}
}
});
}
renderExtensionRoutes() { renderExtensionRoutes() {
return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath }) => { return clusterPageRegistry.getItems().map((page, index) => {
return <Route key={routePath} path={routePath} exact={exact} component={Page}/>; const menu = clusterPageMenuRegistry.getByPage(page);
if (!menu) {
return <Route key={"extension-route-" + index} path={page.routePath} exact={page.exact} component={page.components.Page}/>;
}
}); });
} }
@ -96,6 +138,7 @@ export class App extends React.Component {
<Route component={CustomResources} {...crdRoute}/> <Route component={CustomResources} {...crdRoute}/>
<Route component={UserManagement} {...usersManagementRoute}/> <Route component={UserManagement} {...usersManagementRoute}/>
<Route component={Apps} {...appsRoute}/> <Route component={Apps} {...appsRoute}/>
{this.renderExtensionTabLayoutRoutes()}
{this.renderExtensionRoutes()} {this.renderExtensionRoutes()}
<Redirect exact from="/" to={this.startURL}/> <Redirect exact from="/" to={this.startURL}/>
<Route component={NotFound}/> <Route component={NotFound}/>

View File

@ -173,14 +173,14 @@ export class Terminal {
if (ctrlKey) { if (ctrlKey) {
switch (code) { switch (code) {
// Ctrl+C: prevent terminal exit on windows / linux (?) // Ctrl+C: prevent terminal exit on windows / linux (?)
case "KeyC": case "KeyC":
if (this.xterm.hasSelection()) return false; if (this.xterm.hasSelection()) return false;
break; break;
// Ctrl+W: prevent unexpected terminal tab closing, e.g. editing file in vim // Ctrl+W: prevent unexpected terminal tab closing, e.g. editing file in vim
case "KeyW": case "KeyW":
evt.preventDefault(); evt.preventDefault();
break; break;
} }
} }

View File

@ -85,11 +85,11 @@ export class FilePicker extends React.Component<Props> {
const { limit: [minLimit, maxLimit] = [0, Infinity], onOverLimit } = this.props; const { limit: [minLimit, maxLimit] = [0, Infinity], onOverLimit } = this.props;
if (files.length > maxLimit) { if (files.length > maxLimit) {
switch (onOverLimit) { switch (onOverLimit) {
case OverLimitStyle.CAP: case OverLimitStyle.CAP:
files.length = maxLimit; files.length = maxLimit;
break; break;
case OverLimitStyle.REJECT: case OverLimitStyle.REJECT:
throw `Too many files. Expected at most ${maxLimit}. Got ${files.length}.`; throw `Too many files. Expected at most ${maxLimit}. Got ${files.length}.`;
} }
} }
if (files.length < minLimit) { if (files.length < minLimit) {
@ -103,15 +103,15 @@ export class FilePicker extends React.Component<Props> {
const { onOverSizeLimit, maxSize } = this.props; const { onOverSizeLimit, maxSize } = this.props;
switch (onOverSizeLimit) { switch (onOverSizeLimit) {
case OverSizeLimitStyle.FILTER: case OverSizeLimitStyle.FILTER:
return files.filter(file => file.size <= maxSize ); return files.filter(file => file.size <= maxSize );
case OverSizeLimitStyle.REJECT: case OverSizeLimitStyle.REJECT:
const firstFileToLarge = files.find(file => file.size > maxSize); const firstFileToLarge = files.find(file => file.size > maxSize);
if (firstFileToLarge) { if (firstFileToLarge) {
throw `${firstFileToLarge.name} is too large. Maximum size is ${maxSize}. Has size of ${firstFileToLarge.size}`; throw `${firstFileToLarge.name} is too large. Maximum size is ${maxSize}. Has size of ${firstFileToLarge.size}`;
} }
return files; return files;
} }
} }
@ -124,20 +124,20 @@ export class FilePicker extends React.Component<Props> {
} }
switch (onOverTotalSizeLimit) { switch (onOverTotalSizeLimit) {
case OverTotalSizeLimitStyle.FILTER_LARGEST: case OverTotalSizeLimitStyle.FILTER_LARGEST:
files = _.orderBy(files, ["size"]); files = _.orderBy(files, ["size"]);
case OverTotalSizeLimitStyle.FILTER_LAST: case OverTotalSizeLimitStyle.FILTER_LAST:
let newTotalSize = totalSize; let newTotalSize = totalSize;
for (;files.length > 0;) { for (;files.length > 0;) {
newTotalSize -= files.pop().size; newTotalSize -= files.pop().size;
if (newTotalSize <= maxTotalSize) { if (newTotalSize <= maxTotalSize) {
break; break;
}
} }
} return files;
return files; case OverTotalSizeLimitStyle.REJECT:
case OverTotalSizeLimitStyle.REJECT: throw `Total file size to upload is too large. Expected at most ${maxTotalSize}. Found ${totalSize}.`;
throw `Total file size to upload is too large. Expected at most ${maxTotalSize}. Found ${totalSize}.`;
} }
} }
@ -192,12 +192,12 @@ export class FilePicker extends React.Component<Props> {
getIconRight(): React.ReactNode { getIconRight(): React.ReactNode {
switch (this.status) { switch (this.status) {
case FileInputStatus.CLEAR: case FileInputStatus.CLEAR:
return <Icon className="clean" material="cloud_upload"></Icon>; return <Icon className="clean" material="cloud_upload"></Icon>;
case FileInputStatus.PROCESSING: case FileInputStatus.PROCESSING:
return <Spinner />; return <Spinner />;
case FileInputStatus.ERROR: case FileInputStatus.ERROR:
return <Icon material="error" title={this.errorText}></Icon>; return <Icon material="error" title={this.errorText}></Icon>;
} }
} }
} }

View File

@ -48,12 +48,12 @@ export class Icon extends React.PureComponent<IconProps> {
@autobind() @autobind()
onKeyDown(evt: React.KeyboardEvent<any>) { onKeyDown(evt: React.KeyboardEvent<any>) {
switch (evt.nativeEvent.code) { switch (evt.nativeEvent.code) {
case "Space": case "Space":
case "Enter": case "Enter":
const icon = findDOMNode(this) as HTMLElement; const icon = findDOMNode(this) as HTMLElement;
setTimeout(() => icon.click()); setTimeout(() => icon.click());
evt.preventDefault(); evt.preventDefault();
break; break;
} }
if (this.props.onKeyDown) { if (this.props.onKeyDown) {
this.props.onKeyDown(evt); this.props.onKeyDown(evt);

View File

@ -224,11 +224,11 @@ export class Input extends React.Component<InputProps, State> {
} }
switch (evt.key) { switch (evt.key) {
case "Enter": case "Enter":
if (this.props.onSubmit && !modified && !evt.repeat) { if (this.props.onSubmit && !modified && !evt.repeat) {
this.props.onSubmit(this.getValue()); this.props.onSubmit(this.getValue());
} }
break; break;
} }
} }

View File

@ -9,13 +9,13 @@ interface Props extends Partial<IconProps> {
export function FilterIcon(props: Props) { export function FilterIcon(props: Props) {
const { type, ...iconProps } = props; const { type, ...iconProps } = props;
switch (type) { switch (type) {
case FilterType.NAMESPACE: case FilterType.NAMESPACE:
return <Icon small material="layers" {...iconProps}/>; return <Icon small material="layers" {...iconProps}/>;
case FilterType.SEARCH: case FilterType.SEARCH:
return <Icon small material="search" {...iconProps}/>; return <Icon small material="search" {...iconProps}/>;
default: default:
return <Icon small material="filter_list" {...iconProps}/>; return <Icon small material="filter_list" {...iconProps}/>;
} }
} }

View File

@ -21,27 +21,27 @@ export class KubeObjectStatusIcon extends React.Component<Props> {
statusClassName(level: number): string { statusClassName(level: number): string {
switch (level) { switch (level) {
case KubeObjectStatusLevel.INFO: case KubeObjectStatusLevel.INFO:
return "info"; return "info";
case KubeObjectStatusLevel.WARNING: case KubeObjectStatusLevel.WARNING:
return "warning"; return "warning";
case KubeObjectStatusLevel.CRITICAL: case KubeObjectStatusLevel.CRITICAL:
return "error"; return "error";
default: default:
return ""; return "";
} }
} }
statusTitle(level: number): string { statusTitle(level: number): string {
switch (level) { switch (level) {
case KubeObjectStatusLevel.INFO: case KubeObjectStatusLevel.INFO:
return "Info"; return "Info";
case KubeObjectStatusLevel.WARNING: case KubeObjectStatusLevel.WARNING:
return "Warning"; return "Warning";
case KubeObjectStatusLevel.CRITICAL: case KubeObjectStatusLevel.CRITICAL:
return "Critical"; return "Critical";
default: default:
return ""; return "";
} }
} }

View File

@ -29,7 +29,7 @@ import { CustomResources } from "../+custom-resources/custom-resources";
import { isActiveRoute, navigation } from "../../navigation"; import { isActiveRoute, navigation } from "../../navigation";
import { isAllowedResource } from "../../../common/rbac"; import { isAllowedResource } from "../../../common/rbac";
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import { clusterPageMenuRegistry, clusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries"; import { ClusterPageMenuRegistration, clusterPageMenuRegistry, clusterPageRegistry, getExtensionPageUrl, RegisteredPage } from "../../../extensions/registries";
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false }); const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
type SidebarContextValue = { type SidebarContextValue = {
@ -76,6 +76,52 @@ export class Sidebar extends React.Component<Props> {
}); });
} }
getTabLayoutRoutes(menu: ClusterPageMenuRegistration): TabLayoutRoute[] {
if (!menu.id) {
return [];
}
const routes: TabLayoutRoute[] = [];
clusterPageMenuRegistry.getSubItems(menu).forEach((subItem) => {
const subPage = clusterPageRegistry.getByPageMenuTarget(subItem.target);
if (subPage) {
routes.push({
routePath: subPage.routePath,
url: getExtensionPageUrl({ extensionId: subPage.extensionId, pageId: subPage.id, params: subItem.target.params }),
title: subItem.title,
component: subPage.components.Page,
exact: subPage.exact
});
}
});
return routes;
}
renderRegisteredMenus() {
return clusterPageMenuRegistry.getRootItems().map((menuItem) => {
const registeredPage = clusterPageRegistry.getByPageMenuTarget(menuItem.target);
let pageUrl: string;
let isActive = false;
if (registeredPage) {
const { extensionId, id: pageId } = registeredPage;
pageUrl = getExtensionPageUrl({ extensionId, pageId, params: menuItem.target.params });
isActive = pageUrl === navigation.location.pathname;
}
const tabRoutes = this.getTabLayoutRoutes(menuItem);
if (!registeredPage && tabRoutes.length == 0) {
return;
}
return (
<SidebarNavItem
key={pageUrl} url={pageUrl}
text={menuItem.title} icon={<menuItem.components.Icon/>}
isActive={isActive}
subMenus={tabRoutes}
/>
);
});
}
render() { render() {
const { toggle, isPinned, className } = this.props; const { toggle, isPinned, className } = this.props;
const query = namespaceStore.getContextParams(); const query = namespaceStore.getContextParams();
@ -191,20 +237,7 @@ export class Sidebar extends React.Component<Props> {
> >
{this.renderCustomResources()} {this.renderCustomResources()}
</SidebarNavItem> </SidebarNavItem>
{clusterPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => { {this.renderRegisteredMenus()}
const registeredPage = clusterPageRegistry.getByPageMenuTarget(target);
if (!registeredPage) return;
const { extensionId, id: pageId } = registeredPage;
const pageUrl = getExtensionPageUrl({ extensionId, pageId, params: target.params });
const isActive = pageUrl === navigation.location.pathname;
return (
<SidebarNavItem
key={pageUrl} url={pageUrl}
text={title} icon={<Icon/>}
isActive={isActive}
/>
);
})}
</div> </div>
</div> </div>
</SidebarContext.Provider> </SidebarContext.Provider>

View File

@ -166,25 +166,25 @@ export class Menu extends React.Component<MenuProps, State> {
onKeyDown(evt: KeyboardEvent) { onKeyDown(evt: KeyboardEvent) {
if (!this.isOpen) return; if (!this.isOpen) return;
switch (evt.code) { switch (evt.code) {
case "Escape": case "Escape":
this.close(); this.close();
break; break;
case "Space": case "Space":
case "Enter": case "Enter":
const focusedItem = this.focusedItem; const focusedItem = this.focusedItem;
if (focusedItem) { if (focusedItem) {
focusedItem.elem.click(); focusedItem.elem.click();
evt.preventDefault(); evt.preventDefault();
} }
break; break;
case "ArrowUp": case "ArrowUp":
this.focusNextItem(true); this.focusNextItem(true);
break; break;
case "ArrowDown": case "ArrowDown":
this.focusNextItem(); this.focusNextItem();
break; break;
} }
} }

View File

@ -146,38 +146,38 @@ export class Tooltip extends React.Component<TooltipProps> {
const topCenter = targetBounds.top - tooltipBounds.height - offset; const topCenter = targetBounds.top - tooltipBounds.height - offset;
const bottomCenter = targetBounds.bottom + offset; const bottomCenter = targetBounds.bottom + offset;
switch (position) { switch (position) {
case "top": case "top":
left = horizontalCenter; left = horizontalCenter;
top = topCenter; top = topCenter;
break; break;
case "bottom": case "bottom":
left = horizontalCenter; left = horizontalCenter;
top = bottomCenter; top = bottomCenter;
break; break;
case "left": case "left":
top = verticalCenter; top = verticalCenter;
left = targetBounds.left - tooltipBounds.width - offset; left = targetBounds.left - tooltipBounds.width - offset;
break; break;
case "right": case "right":
top = verticalCenter; top = verticalCenter;
left = targetBounds.right + offset; left = targetBounds.right + offset;
break; break;
case "top_left": case "top_left":
left = targetBounds.left; left = targetBounds.left;
top = topCenter; top = topCenter;
break; break;
case "top_right": case "top_right":
left = targetBounds.right - tooltipBounds.width; left = targetBounds.right - tooltipBounds.width;
top = topCenter; top = topCenter;
break; break;
case "bottom_left": case "bottom_left":
top = bottomCenter; top = bottomCenter;
left = targetBounds.left; left = targetBounds.left;
break; break;
case "bottom_right": case "bottom_right":
top = bottomCenter; top = bottomCenter;
left = targetBounds.right - tooltipBounds.width; left = targetBounds.right - tooltipBounds.width;
break; break;
} }
return { return {
left: left, left: left,

View File

@ -174,20 +174,20 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
const api = apiManager.getApi(selfLink); const api = apiManager.getApi(selfLink);
switch (type) { switch (type) {
case "ADDED": case "ADDED":
case "MODIFIED": case "MODIFIED":
const newItem = new api.objectConstructor(object); const newItem = new api.objectConstructor(object);
if (!item) { if (!item) {
items.push(newItem); items.push(newItem);
} else { } else {
items.splice(index, 1, newItem); items.splice(index, 1, newItem);
} }
break; break;
case "DELETED": case "DELETED":
if (item) { if (item) {
items.splice(index, 1); items.splice(index, 1);
} }
break; break;
} }
}); });