diff --git a/.vscode/launch.json b/.vscode/launch.json index ce676bce..2c3e9d9f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,6 +25,7 @@ "-u=oncue:", "-u=vsphere:on", "-u=aws:on", + "-u=k8s:on", "-u=ipmi-secret-key:", "-c=./cloudhub-canned/", "--protoboards-path=./cloudhub-protoboards/", diff --git a/Makefile b/Makefile index dfdae48a..1f2da84f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION = 1.1.1 +VERSION = 1.2.0 ifeq ($(OS), Windows_NT) GOBINDATA := $(shell go-bindata.exe --version 2>nil) else diff --git a/README.md b/README.md index e391e114..202d665a 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ You should use [Forked **Snetsystems/salt**](https://github.com/snetsystems/salt - Optionable Addon features. - **_VMWare_ Infrastructure** Viewer. - via **_VSphere_** API and **_Salt_** - - **_K8s Infrastructure_** Diagram & Viewer. (Pending in dev-k8s branch) + - **_K8s Infrastructure_** Diagram & Viewer. - via customized `Salt Kuberetes` Module. - **_AWS_** Instances Monitoring. - SWAN Router & SDPlex(Oncue) Features. @@ -54,6 +54,7 @@ You should use [Forked **Snetsystems/salt**](https://github.com/snetsystems/salt + diff --git a/backend/server/server.go b/backend/server/server.go index a9b6e760..4a196b0b 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -65,8 +65,8 @@ type Server struct { KapacitorUsername string `long:"kapacitor-username" description:"Username of your Kapacitor instance" env:"KAPACITOR_USERNAME"` KapacitorPassword string `long:"kapacitor-password" description:"Password of your Kapacitor instance" env:"KAPACITOR_PASSWORD"` - AddonURLs map[string]string `short:"u" long:"addon-url" description:"Support addon is [salt, swan, oncue, ipmi-secret-key]. Actually, this is a key-value extensional options, Not only url But for everywhere to be used the key-value extensional options. Multiple URL can be added by using multiple of the same flag with different 'name:url' values, or as an environment variable with comma-separated 'name:url' values. E.g. via flags: '--addon-url=salt:{url} --addon-url=swan:{url} --addon-url=ipmi-secret-key:{seed key}'. E.g. via environment variable: 'export ADDON_URL=salt:{url},swan:{url}'" env:"ADDON_URL" env-delim:","` - AddonTokens map[string]string `short:"k" long:"addon-tokens" description:"Support addon is [salt, swan]. API tokens to be used to the client for a request to addon API servers. Multiple tokens can be added by using multiple of the same flag with different 'name:token' values, or as an environment variable with comma-separated 'name:token' values. E.g. via flags: '--addon-tokens=salt:{token} --addon-tokens=swan:{token}'. E.g. via environment variable: 'export ADDON_TOKENS=salt:{token},swan:{token}'" env:"ADDON_TOKENS" env-delim:","` + AddonURLs map[string]string `short:"u" long:"addon-url" description:"Support addon is [salt, aws, k8s, swan, oncue, ipmi-secret-key]. Actually, this is a key-value extensional options, Not only url But for everywhere to be used the key-value extensional options. Multiple values can be added by using multiple of the same flag with different 'name:{value}', or as an environment variable with comma-separated 'name:{value}'. E.g. via flags: '-u=salt:{url} -u=aws:on[off] -u=k8s:on[off] -u=swan:{url} -u=oncue:{port number} -u=ipmi-secret-key:{seed key}'. E.g. via environment variable: 'export ADDON_URL=salt:{url},swan:{url}'" env:"ADDON_URL" env-delim:","` + AddonTokens map[string]string `short:"k" long:"addon-tokens" description:"The token associated with addon [salt, swan]. E.g. via flags: '-k=salt:{token} -k=swan:{token}'. E.g. via environment variable: 'export ADDON_TOKENS=salt:{token},swan:{token}'" env:"ADDON_TOKENS" env-delim:","` Develop bool `short:"d" long:"develop" description:"Run server in develop mode."` BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './cloudhub-v1.db')" env:"BOLT_PATH" default:"cloudhub-v1.db"` @@ -125,8 +125,8 @@ type Server struct { LoginAuthType string `long:"login-auth-type" description:"Login auth type (mix, oauth, basic)" env:"LOGIN_AUTH_TYPE" default:"oauth"` - PasswordPolicy string `long:"password-policy" description:"Password validity rules" env:"PASSWORD_POLICY"` - PasswordPolicyMessage string `long:"password-policy-message" description:"Password validity rule description" env:"PASSWORD_POLICY_MESSAGE"` + PasswordPolicy string `long:"password-policy" description:"Regular expression to validate password strength" env:"PASSWORD_POLICY"` + PasswordPolicyMessage string `long:"password-policy-message" description:"The description about password-policy set" env:"PASSWORD_POLICY_MESSAGE"` MailSubject string `long:"mail-subject" description:"Mail subject" env:"MAIL_SUBJECT"` MailBodyMessage string `long:"mail-body-message" description:"Mail body message" env:"MAIL_BODY_MESSAGE"` diff --git a/docs/images/k8s.png b/docs/images/k8s.png new file mode 100644 index 00000000..eb017900 Binary files /dev/null and b/docs/images/k8s.png differ diff --git a/frontend/package.json b/frontend/package.json index b82faf9e..2a71c3a9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "1.1.1", + "version": "1.2.0", "private": true, "description": "This is CloudHub's User interface made in React & Typescript", "author": "Jack Kim", @@ -40,16 +40,16 @@ "@types/react-dnd-html5-backend": "^2.1.9", "@types/react-dom": "^16.0.7", "@types/react-onclickoutside": "^6.7.3", + "@types/react-resizable": "^1.7.3", "@types/react-router": "^3.0.15", "@types/react-router-redux": "4", - "@types/react-resizable": "^1.7.3", "@types/react-virtualized": "^9.18.3", + "@types/redux": "^3.3.1", "@types/redux-auth-wrapper": "^2.0.9", "@types/text-encoding": "^0.0.32", "@types/uuid": "^3.4.3", "@typescript-eslint/eslint-plugin": "^4.16.1", "@typescript-eslint/parser": "^4.16.1", - "@types/redux": "^3.3.1", "autoprefixer": "^6.3.1", "babel-core": "^7.0.0-bridge", "babel-eslint": "^10.0.1", diff --git a/frontend/src/addon/128t/reusable/layout.tsx b/frontend/src/addon/128t/reusable/layout.tsx index 4851e9c1..830c61cd 100644 --- a/frontend/src/addon/128t/reusable/layout.tsx +++ b/frontend/src/addon/128t/reusable/layout.tsx @@ -1,5 +1,5 @@ // Libraries -import React from 'react' +import React, {CSSProperties} from 'react' import classnames from 'classnames' import chroma from 'chroma-js' @@ -143,8 +143,8 @@ export const ErrorState = (): JSX.Element => ( ) -export const NoHostsState = (): JSX.Element => ( -
+export const NoHostsState = ({style}: {style?: CSSProperties}): JSX.Element => ( +

No Data.

) diff --git a/frontend/src/agent_admin/containers/AgentConfiguration.tsx b/frontend/src/agent_admin/containers/AgentConfiguration.tsx index 6e7768a2..fefef7ba 100644 --- a/frontend/src/agent_admin/containers/AgentConfiguration.tsx +++ b/frontend/src/agent_admin/containers/AgentConfiguration.tsx @@ -157,6 +157,7 @@ interface State { isPluginModalVisible: boolean } interface Plugin { + inoutType: string name: string isActivity: boolean } @@ -309,10 +310,12 @@ export class AgentConfiguration extends PureComponent { outputPlugin.shift() const inputPluginList = _.map(inputPlugin, input => ({ + inoutType: 'IN', name: input, isActivity: false, })) const outputPluginList = _.map(outputPlugin, output => ({ + inoutType: 'OUT', name: output, isActivity: false, })) @@ -826,8 +829,8 @@ export class AgentConfiguration extends PureComponent { saltMasterToken, getRunnerSaltCmdTelegraf, } = this.props - const {inputPluginList, outputPluginList, searchTerm} = this.state - const {idx, name, inoutkind} = _thisProps + const {inputPluginList, outputPluginList} = this.state + const {name, inoutkind} = _thisProps this.setState({ isPluginModalVisible: true, @@ -835,39 +838,18 @@ export class AgentConfiguration extends PureComponent { description: '', }) - const sortedInputPlugin = this.getSortedPlugin( - inputPluginList, - searchTerm, - 'name', - SortDirection.ASC - ) - - const sortedOutputPlugin = this.getSortedPlugin( - outputPluginList, - searchTerm, - 'name', - SortDirection.ASC - ) - - const mapInputPlugin = sortedInputPlugin.map(m => { - m.isActivity = false + const mapInputPlugin = inputPluginList.map(m => { + if (m.inoutType === inoutkind && m.name === name) m.isActivity = true + else m.isActivity = false return m }) - const mapOutputPlugin = sortedOutputPlugin.map(m => { - m.isActivity = false + const mapOutputPlugin = outputPluginList.map(m => { + if (m.inoutType === inoutkind && m.name === name) m.isActivity = true + else m.isActivity = false return m }) - if (inoutkind === 'IN') { - mapInputPlugin[idx].isActivity === false - ? (mapInputPlugin[idx].isActivity = true) - : (mapInputPlugin[idx].isActivity = false) - } else { - mapOutputPlugin[idx].isActivity === false - ? (mapOutputPlugin[idx].isActivity = true) - : (mapOutputPlugin[idx].isActivity = false) - } try { const {data} = await getRunnerSaltCmdTelegraf( saltMasterUrl, @@ -885,28 +867,14 @@ export class AgentConfiguration extends PureComponent { } private handlePluginClose = (): void => { - const {inputPluginList, outputPluginList, searchTerm} = this.state - - const sortedInputPlugin = this.getSortedPlugin( - inputPluginList, - searchTerm, - 'name', - SortDirection.ASC - ) - - const sortedOutputPlugin = this.getSortedPlugin( - outputPluginList, - searchTerm, - 'name', - SortDirection.ASC - ) + const {inputPluginList, outputPluginList} = this.state - const mapInputPlugin = sortedInputPlugin.map(m => { + const mapInputPlugin = inputPluginList.map(m => { m.isActivity = false return m }) - const mapOutputPlugin = sortedOutputPlugin.map(m => { + const mapOutputPlugin = outputPluginList.map(m => { m.isActivity = false return m }) diff --git a/frontend/src/external/codemirror.js b/frontend/src/external/codemirror.js index 40aab8ff..2ce0685a 100644 --- a/frontend/src/external/codemirror.js +++ b/frontend/src/external/codemirror.js @@ -5,6 +5,7 @@ import { modeInfluxQLReadOnly, modeMarkdown, modeAgentConf, + modeYaml, modeLogger, } from 'src/shared/constants/codeMirrorModes' import 'codemirror/addon/hint/show-hint' @@ -13,13 +14,13 @@ import modeXML from 'codemirror/mode/xml/xml' /* eslint-disable */ const CodeMirror = require('codemirror') -CodeMirror.defineSimpleMode = function(name, states) { - CodeMirror.defineMode(name, function(config) { +CodeMirror.defineSimpleMode = function (name, states) { + CodeMirror.defineMode(name, function (config) { return CodeMirror.simpleMode(config, states) }) } -CodeMirror.simpleMode = function(config, states) { +CodeMirror.simpleMode = function (config, states) { ensureState(states, 'start') const states_ = {}, meta = states.meta || {} @@ -95,7 +96,7 @@ CodeMirror.simpleMode = function(config, states) { return mode } -CodeMirror.defineOption('placeholder', '', function(cm, val, old) { +CodeMirror.defineOption('placeholder', '', function (cm, val, old) { var prev = old && old != CodeMirror.Init if (val && !prev) { cm.on('blur', onBlur) @@ -201,7 +202,7 @@ function Rule(data, states) { } function tokenFunction(states, config) { - return function(stream, state) { + return function (stream, state) { if (state.pending) { const pend = state.pending.shift() if (state.pending.length === 0) { @@ -346,7 +347,7 @@ function indexOf(val, arr) { } function indentFunction(states, meta) { - return function(state, textAfter, line) { + return function (state, textAfter, line) { if (state.local && state.local.mode.indent) { return state.local.mode.indent(state.localState, textAfter, line) } @@ -390,4 +391,5 @@ CodeMirror.defineSimpleMode('influxQLReadOnly', modeInfluxQLReadOnly) CodeMirror.defineSimpleMode('markdown', modeMarkdown) CodeMirror.defineSimpleMode('agentConf', modeAgentConf) CodeMirror.defineSimpleMode('logger', modeLogger) +CodeMirror.defineSimpleMode('yaml', modeYaml) CodeMirror.defineMode(('xml', modeXML)) diff --git a/frontend/src/hosts/actions/kubernetes.ts b/frontend/src/hosts/actions/kubernetes.ts new file mode 100644 index 00000000..183b81f9 --- /dev/null +++ b/frontend/src/hosts/actions/kubernetes.ts @@ -0,0 +1,701 @@ +import {Dispatch} from 'redux' +import {errorThrown} from 'src/shared/actions/errors' +import _ from 'lodash' + +// APIs +import { + getLocalK8sNamespaces, + getLocalK8sNodes, + getLocalK8sPods, + getLocalK8sDeployments, + getLocalK8sReplicaSets, + getLocalK8sReplicationControllers, + getLocalK8sDaemonSets, + getLocalK8sStatefulSets, + getLocalK8sCronJobs, + getLocalK8sJobs, + getLocalK8sServices, + getLocalK8sIngresses, + getLocalK8sConfigmaps, + getLocalK8sSecrets, + getLocalK8sServiceAccounts, + getLocalK8sClusterRoles, + getLocalK8sClusterRoleBindings, + getLocalK8sRoles, + getLocalK8sRoleBindings, + getLocalK8sPersistentVolumes, + getLocalK8sPersistentVolumeClaims, + getLocalK8sDetail, +} from 'src/shared/apis/saltStack' + +// Types +import {SaltStack} from 'src/types/saltstack' + +export enum ActionTypes { + Namespaces = 'GET_NAMESPACES', + Nodes = 'GET_NODES', + Pods = 'GET_PODS', + Deployments = 'GET_DEPLOYMENTS', + ReplicaSets = 'GET_REPLICASETS', + ReplicationControllers = 'GET_REPLICATIONCONTROLLERS', + DaemonSets = 'GET_DAEMONSETS', + StatefulSets = 'GET_STATEFULSETS', + CronJobs = 'GET_CRONJOBS', + Jobs = 'GET_JOBS', + Services = 'GET_SERVICES', + Ingresses = 'GET_INGRESSES', + Configmaps = 'GET_CONFIGMAPS', + Secrets = 'GET_SECRETS', + ServiceAccounts = 'GET_SERVICEACCOUNTS', + ClusterRoles = 'GET_CLUSTERROLES', + ClusterRoleBindings = 'GET_CLUSTERROLEBINDINGS', + Roles = 'GET_ROLES', + RoleBindings = 'GET_ROLEBINDINGS', + PersistentVolumes = 'GET_PERSISTENTVOLUMES', + PersistentVolumeClaims = 'GET_PERSISTENTVOLUMECLAIMS', + K8sDetail = 'GET_K8S_DETAIL', +} + +interface NamespacesAction { + type: ActionTypes.Namespaces +} + +interface NodesAction { + type: ActionTypes.Nodes +} + +interface PodsAction { + type: ActionTypes.Pods +} + +interface DeploymentsAction { + type: ActionTypes.Deployments +} + +interface ReplicaSetsAction { + type: ActionTypes.ReplicaSets +} + +interface ReplicationControllersAction { + type: ActionTypes.ReplicationControllers +} + +interface DaemonSetsAction { + type: ActionTypes.DaemonSets +} + +interface StatefulSetsAction { + type: ActionTypes.StatefulSets +} + +interface CronJobsAction { + type: ActionTypes.CronJobs +} + +interface JobsAction { + type: ActionTypes.Jobs +} + +interface ServicesAction { + type: ActionTypes.Services +} + +interface IngressesAction { + type: ActionTypes.Ingresses +} + +interface ConfigmapsAction { + type: ActionTypes.Configmaps +} + +interface SecretsAction { + type: ActionTypes.Secrets +} + +interface ServiceAccountsAction { + type: ActionTypes.ServiceAccounts +} + +interface ClusterRolesAction { + type: ActionTypes.ClusterRoles +} + +interface ClusterRoleBindingsAction { + type: ActionTypes.ClusterRoleBindings +} + +interface RolesAction { + type: ActionTypes.Roles +} + +interface RoleBindingsAction { + type: ActionTypes.RoleBindings +} + +interface PersistentVolumesAction { + type: ActionTypes.PersistentVolumes +} + +interface PersistentVolumeClaimsAction { + type: ActionTypes.PersistentVolumeClaims +} + +interface K8sDetailAction { + type: ActionTypes.K8sDetail +} + +export type Action = + | NamespacesAction + | NodesAction + | PodsAction + | DeploymentsAction + | ReplicaSetsAction + | ReplicationControllersAction + | DaemonSetsAction + | StatefulSetsAction + | JobsAction + | CronJobsAction + | ServicesAction + | IngressesAction + | ConfigmapsAction + | SecretsAction + | ServiceAccountsAction + | ClusterRolesAction + | ClusterRoleBindingsAction + | RolesAction + | RoleBindingsAction + | PersistentVolumesAction + | PersistentVolumeClaimsAction + | K8sDetailAction + +export const loadNamespaces = (): NamespacesAction => ({ + type: ActionTypes.Namespaces, +}) + +export const loadNodes = (): NodesAction => ({ + type: ActionTypes.Nodes, +}) + +export const loadPods = (): PodsAction => ({ + type: ActionTypes.Pods, +}) + +export const loadDeployments = (): DeploymentsAction => ({ + type: ActionTypes.Deployments, +}) + +export const loadReplicaSets = (): ReplicaSetsAction => ({ + type: ActionTypes.ReplicaSets, +}) + +export const loadReplicationControllers = (): ReplicationControllersAction => ({ + type: ActionTypes.ReplicationControllers, +}) + +export const loadDaemonSets = (): DaemonSetsAction => ({ + type: ActionTypes.DaemonSets, +}) + +export const loadStatefulSets = (): StatefulSetsAction => ({ + type: ActionTypes.StatefulSets, +}) + +export const loadCronJobs = (): CronJobsAction => ({ + type: ActionTypes.CronJobs, +}) + +export const loadJobs = (): JobsAction => ({ + type: ActionTypes.Jobs, +}) + +export const loadServices = (): ServicesAction => ({ + type: ActionTypes.Services, +}) + +export const loadIngresses = (): IngressesAction => ({ + type: ActionTypes.Ingresses, +}) + +export const loadConfigmaps = (): ConfigmapsAction => ({ + type: ActionTypes.Configmaps, +}) + +export const loadSecrets = (): SecretsAction => ({ + type: ActionTypes.Secrets, +}) + +export const loadClusterRoles = (): ClusterRolesAction => ({ + type: ActionTypes.ClusterRoles, +}) + +export const loadServiceAccounts = (): ServiceAccountsAction => ({ + type: ActionTypes.ServiceAccounts, +}) + +export const loadClusterRoleBindings = (): ClusterRoleBindingsAction => ({ + type: ActionTypes.ClusterRoleBindings, +}) + +export const loadRoles = (): RolesAction => ({ + type: ActionTypes.Roles, +}) + +export const loadRoleBindings = (): RoleBindingsAction => ({ + type: ActionTypes.RoleBindings, +}) + +export const loadPersistentVolumes = (): PersistentVolumesAction => ({ + type: ActionTypes.PersistentVolumes, +}) + +export const loadPersistentVolumeClaims = (): PersistentVolumeClaimsAction => ({ + type: ActionTypes.PersistentVolumeClaims, +}) + +export const loadK8sDetail = (): K8sDetailAction => ({ + type: ActionTypes.K8sDetail, +}) + +export const getLocalK8sNamespacesAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const namespaces = await getLocalK8sNamespaces( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadNamespaces()) + return namespaces + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sNodesAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const nodes = await getLocalK8sNodes(pUrl, pToken, pMinionId, pParam) + + dispatch(loadNodes()) + return nodes + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sPodsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const pods = await getLocalK8sPods(pUrl, pToken, pMinionId, pParam) + + dispatch(loadPods()) + return pods + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sDeploymentsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const deployments = await getLocalK8sDeployments( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadDeployments()) + return deployments + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sReplicaSetsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const replicaSets = await getLocalK8sReplicaSets( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadReplicaSets()) + return replicaSets + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sReplicationControllersAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const replicationControllers = await getLocalK8sReplicationControllers( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadReplicationControllers()) + return replicationControllers + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sDaemonSetsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const daemonSets = await getLocalK8sDaemonSets( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadDaemonSets()) + return daemonSets + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sStatefulSetsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const statefulSets = await getLocalK8sStatefulSets( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadStatefulSets()) + return statefulSets + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sCronJobsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const cronJobs = await getLocalK8sCronJobs(pUrl, pToken, pMinionId, pParam) + + dispatch(loadCronJobs()) + return cronJobs + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sJobsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const jobs = await getLocalK8sJobs(pUrl, pToken, pMinionId, pParam) + + dispatch(loadJobs()) + return jobs + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sServicesAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const services = await getLocalK8sServices(pUrl, pToken, pMinionId, pParam) + + dispatch(loadServices()) + return services + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sIngressesAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const ingresses = await getLocalK8sIngresses( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadIngresses()) + return ingresses + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sConfigmapsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const configmaps = await getLocalK8sConfigmaps( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadConfigmaps()) + return configmaps + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sSecretsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const secrets = await getLocalK8sSecrets(pUrl, pToken, pMinionId, pParam) + + dispatch(loadSecrets()) + return secrets + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sServiceAccountsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const serviceAccounts = await getLocalK8sServiceAccounts( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadServiceAccounts()) + return serviceAccounts + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sClusterRolesAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const clusterRoles = await getLocalK8sClusterRoles( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadClusterRoles()) + return clusterRoles + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sClusterRoleBindingsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const clusterRoleBindings = await getLocalK8sClusterRoleBindings( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadClusterRoleBindings()) + return clusterRoleBindings + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sRolesAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const roles = await getLocalK8sRoles(pUrl, pToken, pMinionId, pParam) + + dispatch(loadRoles()) + return roles + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sRoleBindingsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const roleBindings = await getLocalK8sRoleBindings( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadRoleBindings()) + return roleBindings + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sPersistentVolumesAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const persistentVolumes = await getLocalK8sPersistentVolumes( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadPersistentVolumes()) + return persistentVolumes + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sPersistentVolumeClaimsAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const persistentVolumeClaims = await getLocalK8sPersistentVolumeClaims( + pUrl, + pToken, + pMinionId, + pParam + ) + + dispatch(loadPersistentVolumeClaims()) + return persistentVolumeClaims + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} + +export const getLocalK8sDetailAsync = ( + pUrl: string, + pToken: string, + pMinionId: string, + pParam?: SaltStack +) => async (dispatch: Dispatch) => { + try { + const k8sDetail = await getLocalK8sDetail(pUrl, pToken, pMinionId, pParam) + + dispatch(loadK8sDetail()) + return k8sDetail + } catch (error) { + console.error(error) + dispatch(errorThrown(error)) + } +} diff --git a/frontend/src/hosts/apis/index.ts b/frontend/src/hosts/apis/index.ts index e89e8bc3..840b70a3 100644 --- a/frontend/src/hosts/apis/index.ts +++ b/frontend/src/hosts/apis/index.ts @@ -30,8 +30,10 @@ import { getLocalBoto2DescribeVolumes, getLocalBoto2DescribeInstanceTypes, } from 'src/shared/apis/saltStack' +import {getCpuAndLoadForK8s} from 'src/hosts/apis/kubernetes' -interface HostsObject { +export {getCpuAndLoadForK8s} +export interface HostsObject { [x: string]: Host } diff --git a/frontend/src/hosts/apis/kubernetes.ts b/frontend/src/hosts/apis/kubernetes.ts new file mode 100644 index 00000000..d31a6f4f --- /dev/null +++ b/frontend/src/hosts/apis/kubernetes.ts @@ -0,0 +1,111 @@ +// Libraries +import _ from 'lodash' + +// Utils +import {getDeep} from 'src/utils/wrappers' +import replaceTemplate from 'src/tempVars/utils/replace' +import {proxy} from 'src/utils/queryUrlGenerator' + +// Types +import {Template} from 'src/types' +import {KubernetesObject} from 'src/hosts/types' + +interface K8sNodeSeries { + name: string + columns: string[] + values: string[] + tags: { + node_name: string + } +} +interface K8sPodSeries { + name: string + columns: string[] + values: string[] + tags: { + pod_name: string + } +} + +const EmptyK8s = { + name: '', + type: '', + cpu: 0.0, + memory: 0.0, +} + +export const getCpuAndLoadForK8s = async ( + proxyLink: string, + telegrafDB: string, + tempVars: Template[] +): Promise => { + const query = replaceTemplate( + `SELECT last("cpu_usage_nanocores") / 1000000 FROM \":db:\".\":rp:\".\"kubernetes_node\" WHERE time > now() - 10m GROUP BY node_name; + SELECT last("memory_rss_bytes"), last("memory_working_set_bytes") FROM \":db:\".\":rp:\".\"kubernetes_node\" WHERE time > now() - 10m GROUP BY node_name; + SELECT last("cpu_usage_nanocores") / 1000000 FROM \":db:\".\":rp:\".\"kubernetes_pod_container\" WHERE time > now() - 10m GROUP BY pod_name; + SELECT last("memory_rss_bytes"), last("memory_working_set_bytes") FROM \":db:\".\":rp:\".\"kubernetes_pod_container\" WHERE time > now() - 10m GROUP BY pod_name;`, + tempVars + ) + + const {data} = await proxy({ + source: proxyLink, + query, + db: telegrafDB, + }) + + const k8sObject = {} + const nodeCpuSeries = getDeep(data, 'results.[0].series', []) + const nodeMemorySeries = getDeep( + data, + 'results.[1].series', + [] + ) + const podCpuSeries = getDeep(data, 'results.[2].series', []) + const podMemorySeries = getDeep( + data, + 'results.[3].series', + [] + ) + + _.forEach(nodeCpuSeries, s => { + const lastIndex = _.findIndex(s.columns, col => col === 'last') + k8sObject[s.tags.node_name] = { + ...EmptyK8s, + name: s.tags.node_name, + type: 'Node', + cpu: Number(s.values[0][lastIndex]), + } + }) + + _.forEach(nodeMemorySeries, s => { + const rssIndex = _.findIndex(s.columns, col => col === 'last') + const workingIndex = _.findIndex(s.columns, col => col === 'last_1') + k8sObject[s.tags.node_name].memory = Math.max( + Number(s.values[0][rssIndex]), + Number(s.values[0][workingIndex]) + ) + }) + + _.forEach(podCpuSeries, s => { + const lastIndex = _.findIndex(s.columns, col => col === 'last') + k8sObject[s.tags.pod_name] = { + ...EmptyK8s, + name: s.tags.pod_name, + type: 'Pod', + cpu: Number(s.values[0][lastIndex]), + } + }) + + _.forEach(podMemorySeries, s => { + const rssIndex = _.findIndex(s.columns, col => col === 'last') + const workingIndex = _.findIndex(s.columns, col => col === 'last_1') + k8sObject[s.tags.pod_name].memory = k8sObject[ + s.tags.pod_name + ].memory = Math.max( + Number(s.values[0][rssIndex]), + Number(s.values[0][workingIndex]) + ) + }) + + return k8sObject +} diff --git a/frontend/src/hosts/components/KubernetesBasicsTable.tsx b/frontend/src/hosts/components/KubernetesBasicsTable.tsx new file mode 100644 index 00000000..15a8171e --- /dev/null +++ b/frontend/src/hosts/components/KubernetesBasicsTable.tsx @@ -0,0 +1,251 @@ +import React, {PureComponent} from 'react' +import {TableBody, TableBodyRowItem} from 'src/addon/128t/reusable/layout' +import {KUBERNETES_BASICS_TABLE_SIZE} from 'src/hosts/constants/tableSizing' + +interface Props {} + +class KubernetesBasicsTable extends PureComponent { + constructor(props: Props) { + super(props) + } + + public render() { + const {HeaderWidth, DataWidth} = KUBERNETES_BASICS_TABLE_SIZE + return ( + <> + + <> +
+
+ Status +
+ +
+
+
+ Replica Set +
+ +
+
+
+ Service Account Name +
+ +
+
+
+ Labels +
+ +
+
+
+ Node Name +
+ +
+
+
+ Host IP +
+ +
+
+
+ Pod IP +
+ +
+
+
+ Phase +
+ + {`Running`} + [{`Start Time: 2020-10-27T01:36:04.000Z`}] +
+ } + width={DataWidth} + className={'align--start'} + /> +
+
+
+ Image +
+ +
+
+
+ Image Pull Pollicy +
+ +
+
+
+ Resources +
+ +
+ + +
+
Containers
+ + <> +
+
+ Name +
+ +
+
+
+ Image +
+ +
+
+
+ Image Pull Pollicy +
+ +
+
+
+ Container ID +
+ +
+
+
+ Container Port +
+ +
+ +
+
+ + ) + } +} + +export default KubernetesBasicsTable diff --git a/frontend/src/hosts/components/KubernetesContents.tsx b/frontend/src/hosts/components/KubernetesContents.tsx new file mode 100644 index 00000000..24588c5a --- /dev/null +++ b/frontend/src/hosts/components/KubernetesContents.tsx @@ -0,0 +1,230 @@ +// Library +import React, {PureComponent} from 'react' +import _ from 'lodash' + +// Component +import Threesizer from 'src/shared/components/threesizer/Threesizer' +import FancyScrollbar from 'src/shared/components/FancyScrollbar' +import {TableBody, TableBodyRowItem} from 'src/addon/128t/reusable/layout' + +import KubernetesBasicsTable from 'src/hosts/components/KubernetesBasicsTable' +import KubernetesRawData from 'src/hosts/components/KubernetesRawData' +import KubernetesTooltip from 'src/hosts/components/KubernetesTooltip' +import KubernetesHexagon from 'src/hosts/components/KubernetesHexagon' +import LayoutRenderer from 'src/shared/components/LayoutRenderer' +import {NoHostsState} from 'src/addon/128t/reusable' + +// Constants +import {HANDLE_VERTICAL} from 'src/shared/constants' +import {KUBERNETES_BASICS_TABLE_SIZE} from 'src/hosts/constants/tableSizing' +import {kubernetesStatusColor} from 'src/hosts/constants/color' + +// Types +import { + TooltipNode, + TooltipPosition, + FocuseNode, + D3K8sData, + KubernetesObject, +} from 'src/hosts/types' +import {Source, TimeRange, Cell, Template, RemoteDataState} from 'src/types' + +interface Props { + handleOnSetActiveEditorTab: (tab: string) => void + handleOnClickVisualizePod: (data: any) => void + handleDBClick: (data: any) => void + handleResize: (proportions: number[]) => void + handleOpenTooltip: (target: any) => void + handleCloseTooltip: () => void + proportions: number[] + activeTab: string + script: string + height: number + focuseNode: FocuseNode + pinNode: string[] + isToolipActive: boolean + targetPosition: TooltipPosition + tooltipNode: TooltipNode + kubernetesObject: KubernetesObject + kubernetesD3Data: D3K8sData + source: Source + sources: Source[] + templates: Template[] + timeRange: TimeRange + cells: Cell[] + manualRefresh: number + host: string + selectMinion: string + remoteDataState: RemoteDataState +} + +interface State {} + +class KubernetesContents extends PureComponent { + constructor(props: Props) { + super(props) + this.state = {} + } + + public render() { + const {height, handleResize} = this.props + return ( +
+ +
+ ) + } + + private get virticalDivisions() { + const {proportions} = this.props + const [leftSize, rightSize] = proportions + + return [ + { + handleDisplay: 'none', + headerButtons: [], + menuOptions: [], + render: this.KubernetesVisualize, + headerOrientation: HANDLE_VERTICAL, + size: leftSize, + }, + { + name: 'Details', + headerButtons: [], + menuOptions: [], + render: this.KubernetesInformation, + headerOrientation: HANDLE_VERTICAL, + size: rightSize, + }, + ] + } + + private KubernetesVisualize = () => { + const { + source, + sources, + cells, + templates, + timeRange, + manualRefresh, + host, + focuseNode, + pinNode, + kubernetesObject, + kubernetesD3Data, + handleDBClick, + handleOnClickVisualizePod, + handleResize, + handleOpenTooltip, + handleCloseTooltip, + } = this.props + + return ( + +
+ + + {this.tooltip} +
+ {focuseNode.name && cells.length > 0 ? ( +
+ +
+ ) : ( + + )} +
+ ) + } + + private get tooltip() { + const {isToolipActive, targetPosition, tooltipNode} = this.props + if (isToolipActive) { + return ( + + ) + } + } + + private KubernetesInformation = () => { + const {activeTab, script, focuseNode} = this.props + const {HeaderWidth, DataWidth} = KUBERNETES_BASICS_TABLE_SIZE + + return ( + +
+ + <> +
+
+ Kind +
+ {focuseNode.type}
} + width={DataWidth} + className={'align--start'} + /> +
+
+
+ Name +
+ {focuseNode.label}
+ } + width={DataWidth} + className={'align--start'} + /> + + + +
Details
+ {activeTab === 'Basic' ? ( + + ) : ( + + )} + +
+ ) + } +} + +export default KubernetesContents diff --git a/frontend/src/hosts/components/KubernetesDropdown.tsx b/frontend/src/hosts/components/KubernetesDropdown.tsx new file mode 100644 index 00000000..1cba7625 --- /dev/null +++ b/frontend/src/hosts/components/KubernetesDropdown.tsx @@ -0,0 +1,268 @@ +import React, { + PureComponent, + MouseEvent, + ChangeEvent, + KeyboardEvent, +} from 'react' +import classnames from 'classnames' +import OnClickOutside from 'src/shared/components/OnClickOutside' +import DropdownMenu, { + DropdownMenuEmpty, +} from 'src/shared/components/DropdownMenu' +import DropdownInput from 'src/shared/components/DropdownInput' +import DropdownHead from 'src/shared/components/DropdownHead' +import LoadingSpinner from 'src/flux/components/LoadingSpinner' + +import {ErrorHandling} from 'src/shared/decorators/errors' + +import {DropdownItem, DropdownAction} from 'src/types' +import {ComponentStatus} from 'src/reusable_ui' + +interface AddNew { + url?: string + text: string + handler?: () => void +} + +interface Props { + items: DropdownItem[] + onChoose: (item: DropdownItem) => void + selected: string + addNew?: AddNew + actions?: DropdownAction[] + onClick?: (e: MouseEvent) => void + iconName?: string + className?: string + buttonSize?: string + buttonColor?: string + menuWidth?: string + menuLabel?: string + menuClass?: string + useAutoComplete?: boolean + toggleStyle?: object + disabled?: boolean + tabIndex?: number + isOpen: boolean + onChange?: (item: any) => any + onClose: () => void + status?: ComponentStatus +} + +interface State { + searchTerm: string + filteredItems: DropdownItem[] + highlightedItemIndex: number +} + +@ErrorHandling +export class KubernetesDropdown extends PureComponent { + public static defaultProps: Partial = { + actions: [], + buttonSize: 'btn-sm', + buttonColor: 'btn-default', + menuWidth: '100%', + useAutoComplete: false, + disabled: false, + tabIndex: 0, + } + public dropdownRef: any + + constructor(props: Props) { + super(props) + this.state = { + searchTerm: '', + filteredItems: this.props.items, + highlightedItemIndex: null, + } + } + + public handleClickOutside = () => { + this.props.onClose() + } + + public handleSelection = (item: DropdownItem) => () => { + this.toggleMenu() + this.props.onChoose(item) + this.dropdownRef.focus() + } + + public handleHighlight = (itemIndex: number) => () => { + this.setState({highlightedItemIndex: itemIndex}) + } + + public toggleMenu = (e?: MouseEvent) => { + if (e) { + e.stopPropagation() + } + + if (!this.props.isOpen) { + this.setState({ + searchTerm: '', + filteredItems: this.props.items, + highlightedItemIndex: null, + }) + } + } + + public handleAction = (action: DropdownAction, item: DropdownItem) => ( + e: MouseEvent + ) => { + e.stopPropagation() + action.handler(item) + } + + public handleFilterKeyPress = (e: KeyboardEvent) => { + const {filteredItems, highlightedItemIndex} = this.state + + if (e.key === 'Enter' && filteredItems.length) { + this.props.onClose() + this.props.onChoose(filteredItems[highlightedItemIndex]) + } + if (e.key === 'Escape') { + this.props.onClose() + } + if (e.key === 'ArrowUp' && highlightedItemIndex > 0) { + this.setState({highlightedItemIndex: highlightedItemIndex - 1}) + } + if (e.key === 'ArrowDown') { + if (highlightedItemIndex < filteredItems.length - 1) { + this.setState({highlightedItemIndex: highlightedItemIndex + 1}) + } + if (highlightedItemIndex === null && filteredItems.length) { + this.setState({highlightedItemIndex: 0}) + } + } + } + + public handleFilterChange = (e: ChangeEvent) => { + if (e.target.value) { + return this.setState({searchTerm: e.target.value}, () => + this.applyFilter(this.state.searchTerm) + ) + } + + this.setState({ + searchTerm: '', + filteredItems: this.props.items, + highlightedItemIndex: null, + }) + } + + public applyFilter = (searchTerm: string) => { + const {items} = this.props + const filterText = searchTerm.toLowerCase() + const matchingItems = items.filter(item => { + if (!item) { + return false + } + + return item.text.toLowerCase().includes(filterText) + }) + + this.setState({ + filteredItems: matchingItems, + highlightedItemIndex: 0, + }) + } + + public render() { + const { + isOpen, + items, + addNew, + actions, + selected, + disabled, + iconName, + tabIndex, + className, + menuClass, + menuWidth, + menuLabel, + buttonSize, + buttonColor, + toggleStyle, + useAutoComplete, + status, + } = this.props + + const {searchTerm, filteredItems, highlightedItemIndex} = this.state + + const menuItems = useAutoComplete ? filteredItems : items + + return ( +
(this.dropdownRef = r)} + data-test="dropdown-toggle" + > + {status === ComponentStatus.Loading ? ( +
+ +
+ ) : null} + {useAutoComplete && this.props.isOpen ? ( + + ) : ( + + )} + {isOpen && menuItems.length ? ( + + ) : ( + + )} +
+ ) + } + + private handleClick = (e: MouseEvent) => { + const {disabled, onClick} = this.props + + if (disabled) { + return + } + + this.toggleMenu(e) + if (onClick) { + onClick(e) + } + } +} + +export default OnClickOutside(KubernetesDropdown) diff --git a/frontend/src/hosts/components/KubernetesHeader.tsx b/frontend/src/hosts/components/KubernetesHeader.tsx new file mode 100644 index 00000000..9d6cd641 --- /dev/null +++ b/frontend/src/hosts/components/KubernetesHeader.tsx @@ -0,0 +1,165 @@ +// Library +import React, {PureComponent, ChangeEvent} from 'react' + +// Component +import {Button, ButtonShape, IconFont, ComponentStatus} from 'src/reusable_ui' +import Dropdown from 'src/shared/components/Dropdown' +import KubernetesDropdown from 'src/hosts/components/KubernetesDropdown' +import AutoRefreshDropdown from 'src/shared/components/dropdown_auto_refresh/AutoRefreshDropdown' +import {AutoRefreshOption} from 'src/shared/components/dropdown_auto_refresh/autoRefreshOptions' + +// Contants +import {autoRefreshOptions} from 'src/hosts/constants/autoRefresh' + +interface Props { + handleChooseNamespace: (select: {text: string}) => void + handleChooseNode: (select: {text: string}) => void + handleChooseLimit: (select: {text: string}) => void + handleChangeLabelkey: (e: ChangeEvent) => void + handleChangeLabelValue: (e: ChangeEvent) => void + handleClickFilter: () => void + selectedNamespace: string + selectedNode: string + selectedLimit: string + labelKey: string + labelValue: string + namespaces: string[] + nodes: string[] + limits: string[] + height: number + minions: string[] + selectMinion: string + handleChoosMinion: (select: {text: string}) => void + isOpenMinions: boolean + isDisabledMinions: boolean + minionsStatus: ComponentStatus + handleCloseMinionsDropdown: () => void + onClickMinionsDropdown: () => void + selectedAutoRefresh: number + handleChooseKubernetesAutoRefresh: (options: AutoRefreshOption) => void + handleKubernetesRefresh: () => void +} +class KubernetesHeader extends PureComponent { + constructor(props: Props) { + super(props) + } + + public render() { + const { + handleChooseNamespace, + handleChooseNode, + handleChooseLimit, + handleChangeLabelkey, + handleChangeLabelValue, + handleClickFilter, + selectedNamespace, + selectedNode, + selectedLimit, + labelKey, + labelValue, + namespaces, + nodes, + limits, + height, + minions, + selectMinion, + handleChoosMinion, + isOpenMinions, + isDisabledMinions, + handleCloseMinionsDropdown, + onClickMinionsDropdown, + minionsStatus, + handleChooseKubernetesAutoRefresh, + handleKubernetesRefresh, + selectedAutoRefresh, + } = this.props + return ( +
+
+
+ +
+
+ +
+
+ +
+
=
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+ ) + } +} + +export default KubernetesHeader diff --git a/frontend/src/hosts/components/KubernetesHexagon.tsx b/frontend/src/hosts/components/KubernetesHexagon.tsx new file mode 100644 index 00000000..722f32b2 --- /dev/null +++ b/frontend/src/hosts/components/KubernetesHexagon.tsx @@ -0,0 +1,476 @@ +// Libraries +import React, {PureComponent, createRef} from 'react' +import * as d3 from 'd3' +import _ from 'lodash' + +// Components +import PageSpinner from 'src/shared/components/PageSpinner' +import LoadingSpinner from 'src/flux/components/LoadingSpinner' +import {NoHostsState} from 'src/addon/128t/reusable' + +// Constants +import { + kubernetesStatusColor, + clusterTypeColorset, +} from 'src/hosts/constants/color' + +// Types +import {D3K8sData, FocuseNode, KubernetesObject} from 'src/hosts/types' +import {RemoteDataState} from 'src/types' + +interface Props { + handleOnClickVisualizePod: (data: any) => void + handleDBClick: (data: any) => void + handleResize: (proportions: number[]) => void + handleOpenTooltip: (target: any) => void + handleCloseTooltip: () => void + kubernetesObject: KubernetesObject + kubernetesD3Data: D3K8sData + focuseNode: FocuseNode + pinNode: string[] + remoteDataState: RemoteDataState +} + +interface State {} + +class KubernetesHexagon extends PureComponent { + private containerStyles = { + width: '100%', + height: '100%', + backgroundColor: '#292933', + } + + private ref = createRef() + + private clickedTarget = null + private clickedOnce = false + private timeout = null + + private dbClickJudgementTimer = 300 + + constructor(props: Props) { + super(props) + } + + public componentDidUpdate(prevProps: Props) { + if ( + this.props.kubernetesD3Data && + JSON.stringify(prevProps.kubernetesD3Data) !== + JSON.stringify(this.props.kubernetesD3Data) + ) { + d3.select('svg.kubernetes-svg').selectAll('g').remove() + + this.drawChart() + } + } + + public render() { + return ( +
+ {this.renderKubernetes} +
+ ) + } + + private get renderKubernetes() { + if (_.isEmpty(this.props.kubernetesD3Data.name)) { + return ( + <> + {this.props.remoteDataState === RemoteDataState.Loading ? ( + + ) : ( + + )} + + ) + } else { + return ( + <> + {this.props.remoteDataState === RemoteDataState.Loading ? ( +
+ +
+ ) : null} + + + ) + } + } + + private drawChart() { + const _this = this + const {onMouseClick, onMouseOver, onMouseLeave} = _this + const { + kubernetesD3Data, + kubernetesObject, + pinNode, + focuseNode, + } = _this.props + + const dimensions = this.ref.current.getBoundingClientRect() + const data = d3 + .pack() + .size([dimensions.width, dimensions.height]) + .padding(40)( + d3 + .hierarchy(kubernetesD3Data) + .sum(d => d.value) + .sort((a, b) => b.value - a.value) + ) + + const SQRT3 = Math.sqrt(3) + const hexagonPoly = [ + [0, -1], + [SQRT3 / 2, 0.5], + [0, 1], + [-SQRT3 / 2, 0.5], + [-SQRT3 / 2, -0.5], + [0, -1], + [SQRT3 / 2, -0.5], + ] + + const generateHexagon = hexRadius => { + const hexagonPath = + 'm' + + hexagonPoly + .map(function (p) { + return [p[0] * hexRadius, p[1] * hexRadius].join(',') + }) + .join('l') + + 'z' + return hexagonPath + } + + const circle = d3 + .arc() + .innerRadius(0) + .outerRadius(d => d) + .startAngle(-Math.PI) + .endAngle(Math.PI) + + const svg = d3.select('svg') + + svg.selectAll('g').remove() + + const node = svg + .append('g') + .attr('pointer-events', 'all') + .classed('top-group', true) + .selectAll('g') + .data(data.descendants().slice(1)) + .join('g') + .attr('transform', d => `translate(${d.x},${d.y})`) + + node + .append('path') + .attr('id', d => d.data.name) + .attr('d', d => circle(d.r + 4)) + .attr('display', 'none') + + node + .filter( + d => + d.depth > 0 && + d.depth < 3 && + d.data.type !== 'CR' && + d.data.type !== 'CRB' + ) + .append('circle') + .attr('data-name', d => d.data.name) + .attr('data-label', d => d.data.label) + .attr('data-type', d => d.data.type) + .attr('data-limit-cpu', d => _.get(d.data, 'data.cpu')) + .attr('data-limit-memory', d => _.get(d.data, 'data.memory')) + .attr('class', 'nodeWrapper') + .attr('r', d => d.r) + .attr('fill', d => clusterTypeColorset[d.data.type]) + .attr('stroke', 'black') + .on('mouseover', function () { + onMouseOver(this) + }) + .on('mouseleave', function () { + onMouseLeave(this) + }) + .on('click', function (data) { + onMouseClick(this, data) + }) + .on('mousedown', function () { + d3.event.preventDefault() + }) + + node + .filter( + d => + d.depth === 3 || + (d.depth === 2 && (d.data.type === 'CR' || d.data.type === 'CRB')) + ) + .append('path') + .attr('class', 'hexagon') + .attr('data-name', d => d.data.name) + .attr('data-label', d => d.data.label) + .attr('data-type', d => d.data.type) + .attr('data-limit-cpu', d => _.get(d.data, 'data.cpu')) + .attr('data-limit-memory', d => _.get(d.data, 'data.memory')) + .attr('d', d => generateHexagon(d.r + 5)) + .classed('hexagon-alert', d => { + let isAlert = false + + if ( + (d.data.type === 'Pod' && + d.data.status !== 'Running' && + d.data.status !== 'Succeeded') || + (d.data.type === 'DP' && d.data.status !== 'Succeeded') + ) { + isAlert = true + } + + return isAlert + }) + .attr('stroke', 'black') + .attr('fill', 'white') + .on('mouseover', function () { + onMouseOver(this) + }) + .on('mouseleave', function () { + onMouseLeave(this) + }) + .on('click', function (data) { + onMouseClick(this, data) + }) + .on('mousedown', function () { + d3.event.preventDefault() + }) + + d3.select(`path`).classed('kubernetes-focuse', false) + d3.select(`path[data-name=${focuseNode.name}]`).classed( + 'kubernetes-focuse', + true + ) + + d3.select(`path`).classed('kubernetes-pin', false) + _.forEach(pinNode, pin => { + d3.select(`path[data-name=${pin}]`).classed('kubernetes-pin', true) + }) + + const textNode = svg + .append('g') + .attr('pointer-events', 'all') + .classed('top-group', true) + .selectAll('g') + .data(data.descendants().slice(1)) + .join('g') + .attr('transform', d => `translate(${d.x},${d.y})`) + + textNode + .filter( + d => + !( + d.depth === 3 || + (d.depth === 2 && (d.data.type === 'CR' || d.data.type === 'CRB')) + ) + ) + .append('text') + .attr('fill', 'white') + .append('textPath') + .attr('xlink:href', d => '#' + d.data.name) + .attr('startOffset', '50%') + .attr('font-size', d => (d.depth == 1 ? '12px' : '9px')) + .text(d => d.data.label) + + let d3NodeObject = {} + node + .select(`circle[data-type=${'Node'}]`) + .data() + .forEach(s => { + d3NodeObject[s.data.label] = { + ...d3NodeObject[s.data.label], + name: s.data.label, + cpu: s.data.data.cpu, + memory: s.data.data.memory, + } + }) + + let d3PodObject = {} + node + .select(`path[data-type=${'Pod'}]`) + .data() + .forEach(s => { + d3PodObject[s.data.label] = { + ...d3PodObject[s.data.label], + name: s.data.label, + cpu: s.data.data.cpu, + memory: s.data.data.memory, + } + }) + + _.forEach( + _.filter( + d3NodeObject, + f => + !_.map( + _.filter(kubernetesObject, k8sObj => k8sObj['type'] === 'Node'), + m => m['name'] + ).includes(f['name']) + ), + d3ModNod => { + node + .select(`circle[data-label=${d3ModNod['name']}]`) + .attr('fill', 'gray') + } + ) + + _.forEach( + _.filter( + d3PodObject, + f => + !_.map( + _.filter(kubernetesObject, k8sObj => k8sObj['type'] === 'Pod'), + m => m['name'] + ).includes(f['name']) + ), + d3ModPod => { + node.select(`path[data-label=${d3ModPod['name']}]`).attr('fill', 'gray') + } + ) + + _.forEach(kubernetesObject, m => { + if (m['type'] === 'Node') { + if ( + _.find( + node.select(`circle[data-type=${'Node'}]`).data(), + nodeData => nodeData.data.label === m['name'] + ) + ) { + const cpuUsage = + (parseFloat(m['cpu']) / + parseFloat( + node + .select(`circle[data-label=${m['name']}]`) + .attr('data-limit-cpu') + )) * + 100 + const memoryUsage = + (parseFloat(m['memory']) / + parseFloat( + node + .select(`circle[data-label=${m['name']}]`) + .attr('data-limit-memory') + )) * + 100 + const pick = cpuUsage > memoryUsage ? cpuUsage : memoryUsage + node + .select(`circle[data-label=${m['name']}]`) + .attr('data-cpu', `${cpuUsage}`) + node + .select(`circle[data-label=${m['name']}]`) + .attr('data-memory', `${memoryUsage}`) + .attr('fill', kubernetesStatusColor(pick / 100)) + } + } else { + if ( + _.find( + node.select(`path[data-type=${'Pod'}]`).data(), + podData => podData.data.label === m['name'] + ) + ) { + const cpuUsage = + (parseFloat(m['cpu']) / + parseFloat( + node + .select(`path[data-label=${m['name']}]`) + .attr('data-limit-cpu') + )) * + 100 + const memoryUsage = + (parseFloat(m['memory']) / + parseFloat( + node + .select(`path[data-label=${m['name']}]`) + .attr('data-limit-memory') + )) * + 100 + + const pick = cpuUsage > memoryUsage ? cpuUsage : memoryUsage + node + .select(`path[data-label=${m['name']}]`) + .attr('data-cpu', `${cpuUsage}`) + node + .select(`path[data-label=${m['name']}]`) + .attr('data-memory', `${memoryUsage}`) + .attr('fill', kubernetesStatusColor(pick / 100)) + } + } + }) + + const autoBox = () => { + this.ref.current.appendChild(svg.node()) + const {x, y, width, height} = svg.node().getBBox() + this.ref.current.removeChild(svg.node()) + return [x, y, width, height] + } + + return this.ref.current.append(svg.attr('viewBox', `${autoBox()}`).node()) + } + + private runOnSingleClick = (data: any) => { + this.props.handleOnClickVisualizePod(data) + this.clickedOnce = false + this.clickedTarget = null + } + + private runOnDBClick = (data: any) => { + this.clickedOnce = false + this.clickedTarget = null + clearTimeout(this.timeout) + this.onMouseDBClick(data) + } + + private onMouseClick = (target: SVGSVGElement, data: D3K8sData) => { + if (this.clickedTarget === target && this.clickedOnce) { + this.runOnDBClick(data) + } else if ( + (this.clickedTarget === null && !this.clickedOnce) || + (this.clickedTarget !== target && this.clickedOnce) + ) { + this.timeout = setTimeout(() => { + this.runOnSingleClick(data) + }, this.dbClickJudgementTimer) + + this.clickedTarget = target + this.clickedOnce = true + } + } + + private onMouseDBClick = (data: any) => { + this.props.handleDBClick(data) + this.props.handleOnClickVisualizePod(data) + } + + private onMouseOver = (target: SVGSVGElement) => { + this.props.handleOpenTooltip(target) + d3.select(target).classed('kubernetes-hover', true) + } + + private onMouseLeave = (target: SVGSVGElement) => { + this.props.handleCloseTooltip() + d3.select(target).classed('kubernetes-hover', false) + } +} + +export default KubernetesHexagon diff --git a/frontend/src/hosts/components/KubernetesRawData.tsx b/frontend/src/hosts/components/KubernetesRawData.tsx new file mode 100644 index 00000000..2f952be1 --- /dev/null +++ b/frontend/src/hosts/components/KubernetesRawData.tsx @@ -0,0 +1,46 @@ +import React, {PureComponent} from 'react' +import _ from 'lodash' +import yaml from 'js-yaml' + +import {Controlled as ReactCodeMirror} from 'react-codemirror2' + +interface Props { + script: string +} + +class KubernetesRawData extends PureComponent { + constructor(props: Props) { + super(props) + } + public render() { + const {script} = this.props + const options = { + tabIndex: 1, + readonly: false, + lineNumbers: false, + autoRefresh: true, + completeSingle: false, + lineWrapping: true, + mode: 'yaml', + theme: 'yaml', + } + + return ( +
+ +
+ ) + } + + private beforeChange = (): void => {} + private onTouchStart = (): void => {} +} + +export default KubernetesRawData diff --git a/frontend/src/hosts/components/KubernetesTooltip.tsx b/frontend/src/hosts/components/KubernetesTooltip.tsx new file mode 100644 index 00000000..9b99b0b4 --- /dev/null +++ b/frontend/src/hosts/components/KubernetesTooltip.tsx @@ -0,0 +1,138 @@ +// Libraries +import React, {PureComponent, createRef} from 'react' +import chroma from 'chroma-js' +import _ from 'lodash' + +// Types +import {TooltipNode, TooltipPosition} from 'src/hosts/types' + +// Decorators +import {ErrorHandling} from 'src/shared/decorators/errors' + +interface Props { + onDismiss?: () => void + targetPosition: TooltipPosition + tooltipNode: TooltipNode + statusColor: chroma.Scale +} + +interface State { + top: number | null + bottom: number | null + left: number | null + right: number | null +} + +@ErrorHandling +class KubernetesTooltip extends PureComponent { + private tooltipRef = createRef() + private tableSize = { + header: '40%', + body: '60%', + } + + public constructor(props: Props) { + super(props) + this.state = { + top: null, + bottom: null, + left: null, + right: null, + } + } + + public componentDidMount() { + this.calcPosition() + } + + private calcPosition = () => { + const {targetPosition} = this.props + const {top, width, right} = targetPosition + const {width: tipWidth} = this.tooltipRef.current.getBoundingClientRect() + + let position = { + bottom: window.innerHeight - top, + left: right - width / 2 - tipWidth / 2, + } + + this.setState({...position}) + } + + public render() { + const {tooltipNode, statusColor} = this.props + const {top, bottom, left, right} = this.state + const {name, cpu, memory} = tooltipNode + + return ( +
+
+
+ {name ? ( +
+
{name}
+
+ ) : null} + {!_.isNaN(cpu) && _.isNumber(cpu) ? ( +
+
+ CPU +
+
+
+
{cpu} %
+
+
+
+
+ ) : null} + {!_.isNaN(memory) && _.isNumber(memory) ? ( +
+
+ Memory +
+
+
+
{memory} %
+
+
+
+
+ ) : null} +
+
+
+ ) + } + + private get handleToolTipClassName() { + return 'kubernetes-toolbar--tooltip' + } +} + +export default KubernetesTooltip diff --git a/frontend/src/hosts/components/host.scss b/frontend/src/hosts/components/host.scss index 80206b7e..fae569d8 100644 --- a/frontend/src/hosts/components/host.scss +++ b/frontend/src/hosts/components/host.scss @@ -166,12 +166,237 @@ margin-right: 3px; } -.page.inventory-hosts-list-page { +.k8s-obj-kind { + color: #999dab; +} + +.k8s-obj-label { + color: #22adf6; +} + +.kubernetes-header--bar { + display: flex; + align-items: center; + justify-self: auto; + padding: 5px 20px; + background-color: $g4-onyx; + + .kubernetes-header--bar-item { + margin-right: 5px; + + &:last-child { + margin-right: 0px; + margin-left: auto; + } + } +} + +.kubernetes-detail-display { + min-width: 300px; + padding: 10px 20px; + height: 100%; +} + +.kubernetes-radio-btn--container { + margin: 10px 0px; +} + +.kubernetes-editor--field { + background-color: $g2-kevlar; + padding: $ix-marg-b; + height: calc(100% - 90px); + border: $ix-border solid $g5-pepper; + border-radius: $radius; + &.focus { + border-color: $c-pool; + box-shadow: 0 0 6px 0 $c-pool; + } +} + +.kubernetes-detail-title { + height: 40px; + font-size: 15px; + font-weight: 600; + color: #bec2cc; + padding: 10px 5px 0 5px; +} + +.devider--bar { + background-color: $g2-kevlar; + color: white; + height: 30px; + display: flex; + align-items: center; + padding: 10px; + margin: 20px 0 0; + font-weight: bold; +} + +.hexagon.focuse { + fill: black; +} + +// test Yaml +.cm-s-yaml { + background-color: $g1-raven; + color: #d3d4c9; + padding-right: 13px; + + // .cm-literal-block { + // color: red; + // } + + .cm-key { + color: $g11-sidewalk; + font-weight: 400; + } + .cm-array { + color: $g11-sidewalk; + font-weight: 400; + } + + .cm-value { + color: $c-viridian; + font-weight: 400; + } + + .cm-null { + color: $g8-storm; + font-style: italic; + } + + .cm-ip { + color: $c-comet; + font-weight: 400; + } + + .cm-number { + color: $c-pool; + } + + .cm-object-key { + color: $c-honeydew; + } + .cm-object-bracelet { + color: $c-honeydew; + } + + .cm-operator { + color: $g15-platinum; + } + + .cm-argument { + color: $g11-sidewalk; + } + + .cm-boolean { + color: $c-viridian; + } + + .cm-object { + color: $c-honeydew; + } + + .cm-comment { + color: $g8-storm; + } + + .CodeMirror-scrollbar-filler { + background-color: transparent; + } + + .CodeMirror-vscrollbar, + .CodeMirror-hscrollbar { + @include custom-scrollbar-round($g1-raven, $s-orange); + &::-webkit-scrollbar-thumb { + border-width: 5px; + } + } +} +.tooltip-pin.active { + text-shadow: 0 0 5px #f58220, 0 0 5px #f58220, 0 0 5px #f58220, + 0 0 5px #f58220, 0 0 9px #fff; +} + +.kubernetes-toolbar--tooltip { + position: fixed; + z-index: 30; +} + +.kubernetes-toolbar--tooltip-contents { + border: 2px solid $s-orange; + border-radius: 4px; + display: inline-flex; + flex-direction: column; + background-color: $g1-raven; + padding: 5px; +} + +.kubernetes-header--left { + display: flex; + align-items: center; +} + +.kubernetes-header--right { + display: flex; + margin-left: auto; + align-items: center; +} + +.dropdown-loading { + position: absolute; + z-index: 1; + background: rgba(0, 0, 0, 0.5); width: 100%; height: 100%; + display: flex; + align-items: center; + justify-content: center; + border-radius: 5px; +} + +.dropdown.open { + z-index: 99; +} + +.kubernetes-hover { + stroke: black; + stroke-width: 3px; +} + +.kubernetes-focuse { + stroke: #545454; + stroke-width: 3px; +} + +.kubernetes-pin { + stroke: #ffff00; + stroke-width: 3px; } -.tab-pannel { +.kubernetes-dashboard { + width: 100%; + margin: 0 auto; + padding: 22.5px 20px; + height: auto; +} + +.hexagon-alert { + animation: alert 0.25s infinite; +} + +@keyframes alert { + from { + opacity: 1; + } + to { + opacity: 0.5; + } +} + +.page.inventory-hosts-list-page { + width: 100%; + height: 100%; } .tab-pannel-contents { diff --git a/frontend/src/hosts/constants/autoRefresh.ts b/frontend/src/hosts/constants/autoRefresh.ts new file mode 100644 index 00000000..25e95eef --- /dev/null +++ b/frontend/src/hosts/constants/autoRefresh.ts @@ -0,0 +1,37 @@ +import { + AutoRefreshOptionType, + AutoRefreshOption, +} from 'src/shared/components/dropdown_auto_refresh/autoRefreshOptions' + +export const autoRefreshOptions: AutoRefreshOption[] = [ + { + id: 'auto-refresh-header', + milliseconds: 9999, + label: 'Refresh', + type: AutoRefreshOptionType.Header, + }, + { + id: 'auto-refresh-paused', + milliseconds: 0, + label: 'Paused', + type: AutoRefreshOptionType.Option, + }, + { + id: 'auto-refresh-30s', + milliseconds: 30000, + label: '30s', + type: AutoRefreshOptionType.Option, + }, + { + id: 'auto-refresh-60s', + milliseconds: 60000, + label: '60s', + type: AutoRefreshOptionType.Option, + }, + { + id: 'auto-refresh-5m', + milliseconds: 300000, + label: '5m', + type: AutoRefreshOptionType.Option, + }, +] diff --git a/frontend/src/hosts/constants/color.ts b/frontend/src/hosts/constants/color.ts new file mode 100644 index 00000000..686bf9b1 --- /dev/null +++ b/frontend/src/hosts/constants/color.ts @@ -0,0 +1,47 @@ +import chroma from 'chroma-js' + +export const clusterTypeColorset = { + Node: '#ffffff', + Pod: '#ffffff', + ClusterRoles: '#5f5f5f', + ClusterRole: '#5f5f5f', + CR: '#5f5f5f', + ClusterRoleBindings: '#5f5f5f', + ClusterRoleBinding: '#5f5f5f', + CRB: '#5f5f5f', + Namespace: '#7f7f7f', + Service: '#5f5f5f', + SVC: '#5f5f5f', + Secrets: '#5f5f5f', + Secret: '#5f5f5f', + SR: '#5f5f5f', + ServiceAccounts: '#5f5f5f', + ServiceAccount: '#5f5f5f', + SA: '#5f5f5f', + ReplicaSet: '#5f5f5f', + RS: '#5f5f5f', + Deployment: '#5f5f5f', + DP: '#5f5f5f', + Job: '#5f5f5f', + CronJob: '#5f5f5f', + CJ: '#5f5f5f', + Ingress: '#5f5f5f', + IGS: '#5f5f5f', + ReplicationController: '#5f5f5f', + RC: '#5f5f5f', + Configmaps: '#5f5f5f', + Configmap: '#5f5f5f', + CM: '#5f5f5f', + Roles: '#5f5f5f', + RL: '#5f5f5f', + RoleBindings: '#5f5f5f', + RoleBinding: '#5f5f5f', + Role: '#5f5f5f', + RB: '#5f5f5f', + DaemonSet: '#5f5f5f', + DS: '#5f5f5f', +} + +export const kubernetesStatusColor = chroma + .scale(['#30e7f1', '#00cc2c', '#ff9e00', '#ff0000']) + .mode('lrgb') diff --git a/frontend/src/hosts/constants/kubernetes.ts b/frontend/src/hosts/constants/kubernetes.ts new file mode 100644 index 00000000..8e1fd0a3 --- /dev/null +++ b/frontend/src/hosts/constants/kubernetes.ts @@ -0,0 +1,167 @@ +export enum K8sNodeType { + SVC = 'Service', + IGS = 'Ingress', + CM = 'Configmap', + SR = 'Secret', + SA = 'ServiceAccount', + CR = 'ClusterRole', + CRB = 'ClusterRoleBinding', + RL = 'Role', + RB = 'RoleBinding', + PV = 'PersistentVolume', + PVC = 'PersistentVolumeClaim', + RS = 'ReplicaSet', + DP = 'Deployment', + RC = 'ReplicationController', + DS = 'DaemonSet', + SS = 'StatefulSet', + Job = 'Job', + CJ = 'CronJob', + Pod = 'Pod', + Node = 'Node', + Namespace = 'Namespace', +} + +export const k8sNodeTypeAttrs = { + Namespace: { + name: K8sNodeType.Namespace, + saltParam: { + fun: 'kubernetes.show_namespace', + kwarg: {name: ''}, + }, + }, + Node: { + name: K8sNodeType.Node, + saltParam: {fun: 'kubernetes.node', kwarg: {name: ''}}, + }, + Pod: { + name: K8sNodeType.Pod, + saltParam: {fun: 'kubernetes.show_pod', kwarg: {namespace: '', name: ''}}, + }, + DP: { + name: K8sNodeType.DP, + saltParam: { + fun: 'kubernetes.show_deployment', + kwarg: {namespace: '', name: ''}, + }, + }, + RS: { + name: K8sNodeType.RS, + saltParam: { + fun: 'kubernetes.show_replica_set', + kwarg: {namespace: '', name: ''}, + }, + }, + RC: { + name: K8sNodeType.RC, + saltParam: { + fun: 'kubernetes.show_replication_controller', + kwarg: {namespace: '', name: ''}, + }, + }, + DS: { + name: K8sNodeType.DS, + saltParam: { + fun: 'kubernetes.show_daemon_set', + kwarg: {namespace: '', name: ''}, + }, + }, + SS: { + name: K8sNodeType.SS, + saltParam: { + fun: 'kubernetes.show_stateful_set', + kwarg: {namespace: '', name: ''}, + }, + }, + Job: { + name: K8sNodeType.Job, + saltParam: { + fun: 'kubernetes.show_job', + kwarg: {namespace: '', name: ''}, + }, + }, + CJ: { + name: K8sNodeType.CJ, + saltParam: { + fun: 'kubernetes.show_cron_job', + kwarg: {namespace: '', name: ''}, + }, + }, + SVC: { + name: K8sNodeType.SVC, + saltParam: { + fun: 'kubernetes.show_service', + kwarg: {namespace: '', name: ''}, + }, + }, + IGS: { + name: K8sNodeType.IGS, + saltParam: { + fun: 'kubernetes.show_ingress', + kwarg: {namespace: '', name: ''}, + }, + }, + CM: { + name: K8sNodeType.CM, + saltParam: { + fun: 'kubernetes.show_configmap', + kwarg: {namespace: '', name: ''}, + }, + }, + SR: { + name: K8sNodeType.SR, + saltParam: { + fun: 'kubernetes.show_secret', + kwarg: {namespace: '', name: ''}, + }, + }, + SA: { + name: K8sNodeType.SA, + saltParam: { + fun: 'kubernetes.show_service_account', + kwarg: {namespace: '', name: ''}, + }, + }, + CR: { + name: K8sNodeType.CR, + saltParam: { + fun: 'kubernetes.show_cluster_role', + kwarg: {name: ''}, + }, + }, + CRB: { + name: K8sNodeType.CRB, + saltParam: { + fun: 'kubernetes.show_cluster_role_binding', + kwarg: {name: ''}, + }, + }, + RL: { + name: K8sNodeType.RL, + saltParam: { + fun: 'kubernetes.show_role', + kwarg: {namespace: '', name: ''}, + }, + }, + RB: { + name: K8sNodeType.RB, + saltParam: { + fun: 'kubernetes.show_role_binding', + kwarg: {namespace: '', name: ''}, + }, + }, + PV: { + name: K8sNodeType.PV, + saltParam: { + fun: 'kubernetes.show_persistent_volume', + kwarg: {name: ''}, + }, + }, + PVC: { + name: K8sNodeType.PVC, + saltParam: { + fun: 'kubernetes.show_persistent_volume_claim', + kwarg: {namespace: '', name: ''}, + }, + }, +} diff --git a/frontend/src/hosts/constants/tableSizing.ts b/frontend/src/hosts/constants/tableSizing.ts index 45ca54d2..bf2f44c4 100644 --- a/frontend/src/hosts/constants/tableSizing.ts +++ b/frontend/src/hosts/constants/tableSizing.ts @@ -136,3 +136,8 @@ export const VCENTER_VM_TABLE_SIZING = { OSWidth: '18%', StatusWidth: '18%', } + +export const KUBERNETES_BASICS_TABLE_SIZE = { + HeaderWidth: '15%', + DataWidth: '85%', +} diff --git a/frontend/src/hosts/containers/Infrastructure.tsx b/frontend/src/hosts/containers/Infrastructure.tsx index ecdce572..76a958d0 100644 --- a/frontend/src/hosts/containers/Infrastructure.tsx +++ b/frontend/src/hosts/containers/Infrastructure.tsx @@ -17,6 +17,7 @@ import GraphTips from 'src/shared/components/GraphTips' import HostsPage from 'src/hosts/containers/HostsPage' import VMHostPage from 'src/hosts/containers/VMHostsPage' import InventoryTopology from 'src/hosts/containers/InventoryTopology' +import KubernetesPage from 'src/hosts/containers/KubernetesPage' // Actions import { @@ -64,7 +65,13 @@ interface Props extends ManualRefreshProps { interface State { timeRange: TimeRange activeTab: string - isUsingVsphere: boolean + headerRadioButtons: { + id: string + titleText: string + value: string + active: string + label: string + }[] } @ErrorHandling @@ -75,7 +82,7 @@ class Infrastructure extends PureComponent { this.state = { timeRange: timeRanges.find(tr => tr.lower === 'now() - 1h'), activeTab: 'topology', - isUsingVsphere: false, + headerRadioButtons: [], } } @@ -91,6 +98,25 @@ class Infrastructure extends PureComponent { const {addons} = links const infraTab = _.get(router.params, 'infraTab', 'topology') + const defaultHeaderRadioButtons = [ + { + id: 'hostspage-tab-InventoryTopology', + titleText: 'InventoryTopology', + value: 'topology', + active: 'topology', + label: 'Topology', + }, + { + id: 'hostspage-tab-Host', + titleText: 'Host', + value: 'host', + active: 'host', + label: 'Host', + }, + ] + + const headerRadioButtons = [...defaultHeaderRadioButtons] + const isUsingVsphere = !_.isEmpty( _.find(addons, addon => { const {name, url} = addon @@ -98,13 +124,49 @@ class Infrastructure extends PureComponent { }) ) - if (!isUsingVsphere && infraTab === 'vmware') { + const isUsingKubernetes = !_.isEmpty( + _.find(addons, addon => { + const {name, url} = addon + return name === 'k8s' && url === 'on' + }) + ) + + if (isUsingVsphere) { + headerRadioButtons.push({ + id: 'hostspage-tab-VMware', + titleText: 'VMware', + value: 'vmware', + active: 'vmware', + label: 'VMware', + }) + } + + if (isUsingKubernetes) { + headerRadioButtons.push({ + id: 'hostspage-tab-Kubernetes', + titleText: 'Kubernetes', + value: 'kubernetes', + active: 'kubernetes', + label: 'Kubernetes', + }) + } + + let isRedirect = false + + if ( + (!isUsingVsphere && infraTab === 'vmware') || + (!isUsingKubernetes && infraTab === 'kubernetes') + ) { + isRedirect = true + } + + if (isRedirect) { router.replace(`/sources/${source.id}/infrastructure/topology`) } return { activeTab: infraTab, - isUsingVsphere, + headerRadioButtons, } } @@ -117,7 +179,7 @@ class Infrastructure extends PureComponent { source, handleClearTimeout, } = this.props - const {isUsingVsphere, activeTab, timeRange} = this.state + const {activeTab, timeRange, headerRadioButtons} = this.state return ( @@ -125,37 +187,22 @@ class Infrastructure extends PureComponent { - +
- - Topology - - - Host - - {isUsingVsphere && ( - - VMware - - )} + {headerRadioButtons.map(rBtn => { + return ( + + {rBtn.label} + + ) + })}
@@ -203,6 +250,15 @@ class Infrastructure extends PureComponent { timeRange={timeRange} /> )} + {activeTab === 'kubernetes' && ( + //@ts-ignore + + )}
diff --git a/frontend/src/hosts/containers/InventoryTopology.tsx b/frontend/src/hosts/containers/InventoryTopology.tsx index c2478210..cc35d124 100644 --- a/frontend/src/hosts/containers/InventoryTopology.tsx +++ b/frontend/src/hosts/containers/InventoryTopology.tsx @@ -388,7 +388,10 @@ class InventoryTopology extends PureComponent { const { links: {addons}, } = this.props - const findItem = _.find(addons, addon => addon.name === template.provider) + const findItem = _.find( + addons, + addon => addon.name === template.provider && addon.url === 'on' + ) const isFind = !_.isEmpty(findItem) if (isFind) { diff --git a/frontend/src/hosts/containers/KubernetesPage.tsx b/frontend/src/hosts/containers/KubernetesPage.tsx new file mode 100644 index 00000000..a1492c29 --- /dev/null +++ b/frontend/src/hosts/containers/KubernetesPage.tsx @@ -0,0 +1,3351 @@ +// Library +import React, {PureComponent, ChangeEvent} from 'react' +import {connect} from 'react-redux' +import _ from 'lodash' +import * as d3 from 'd3' +import yaml from 'js-yaml' + +// Component +import KubernetesHeader from 'src/hosts/components/KubernetesHeader' +import KubernetesContents from 'src/hosts/components/KubernetesContents' +import {ComponentStatus} from 'src/reusable_ui' +import {AutoRefreshOption} from 'src/shared/components/dropdown_auto_refresh/autoRefreshOptions' + +// Actions +import {getMinionKeyAcceptedListAsync} from 'src/hosts/actions' +import { + getLocalK8sNamespacesAsync, + getLocalK8sNodesAsync, + getLocalK8sPodsAsync, + getLocalK8sDeploymentsAsync, + getLocalK8sReplicaSetsAsync, + getLocalK8sReplicationControllersAsync, + getLocalK8sDaemonSetsAsync, + getLocalK8sStatefulSetsAsync, + getLocalK8sJobsAsync, + getLocalK8sCronJobsAsync, + getLocalK8sServicesAsync, + getLocalK8sIngressesAsync, + getLocalK8sConfigmapsAsync, + getLocalK8sSecretsAsync, + getLocalK8sServiceAccountsAsync, + getLocalK8sClusterRolesAsync, + getLocalK8sClusterRoleBindingsAsync, + getLocalK8sRolesAsync, + getLocalK8sRoleBindingsAsync, + getLocalK8sPersistentVolumesAsync, + getLocalK8sPersistentVolumeClaimsAsync, + getLocalK8sDetailAsync, +} from 'src/hosts/actions/kubernetes' +import {notify as notifyAction} from 'src/shared/actions/notifications' + +//Middleware +import { + getLocalStorage, + setLocalStorage, + verifyLocalStorage, +} from 'src/shared/middleware/localStorage' + +// Constatns +import {EMPTY_LINKS} from 'src/dashboards/constants/dashboardHeader' +import {kubernetesStatusColor} from 'src/hosts/constants/color' +import {k8sNodeTypeAttrs} from 'src/hosts/constants/kubernetes' + +// API +import { + getLayouts, + getAppsForHost, + getMeasurementsForHost, +} from 'src/hosts/apis' +import {getCpuAndLoadForK8s} from 'src/hosts/apis' + +// Types +import {Addon} from 'src/types/auth' +import {Source, Layout, TimeRange, Links, RemoteDataState} from 'src/types' +import {DashboardSwitcherLinks} from 'src/types/dashboards' +import { + TooltipNode, + TooltipPosition, + FocuseNode, + KubernetesProps, + D3K8sData, + D3DataDepth1, + D3DataDepth2, + D3DataDepth3, + KubernetesObject, +} from 'src/hosts/types' +import {AddonType} from 'src/shared/constants' +import {SaltStack} from 'src/types/saltstack' + +// Utils +import {WindowResizeEventTrigger} from 'src/shared/utils/trigger' +import {generateForHosts} from 'src/utils/tempVars' +import {getCells} from 'src/hosts/utils/getCells' +import {transMemoryToBytes, transToCPUMillCore} from 'src/hosts/utils/transUnit' +import {GlobalAutoRefresher} from 'src/utils/AutoRefresher' + +// Error +import {ErrorHandling} from 'src/shared/decorators/errors' + +interface Props extends KubernetesProps { + source: Source + addons: Addon[] + notify: NotificationAction + manualRefresh: number + timeRange: TimeRange + autoRefresh: number + links: Links + meRole: string +} + +interface State { + proportions: number[] + activeEditorTab: string + script: string + labelKey: string + labelValue: string + selectedNamespace: string + selectedNode: string + selectedLimit: string + filterLabelKey: string + filterLabelValue: string + filterNamespace: string + filterNode: string + filterLimit: string + namespaces: string[] + nodes: string[] + limits: string[] + focuseNode: FocuseNode + pinNode: string[] + isToolipActive: boolean + targetPosition: TooltipPosition + tooltipNode: TooltipNode + minions: string[] + selectMinion: string + selectedAutoRefresh: AutoRefreshOption['milliseconds'] + isOpenMinions: boolean + isDisabledMinions: boolean + layouts: Layout[] + hostLinks: DashboardSwitcherLinks + kubernetesData: object + kubernetesD3Data: D3K8sData + kubernetesObject: KubernetesObject + remoteDataState: RemoteDataState +} + +@ErrorHandling +class KubernetesPage extends PureComponent { + private height = 40 + private getKubernetesObjectInterval: NodeJS.Timer = null + private getKubernetesResourceInterval: NodeJS.Timer = null + private noSelect: string = 'no select' + private defaultState = { + proportions: [0.75, 0.25], + selectMinion: this.noSelect, + selectedAutoRefresh: 0, + } + + constructor(props: Props) { + super(props) + + this.state = { + ...this.defaultState, + + activeEditorTab: 'Detail', + script: '', + selectedNamespace: 'All namespaces', + selectedNode: 'All nodes', + selectedLimit: 'Unlimited', + labelKey: '', + labelValue: '', + namespaces: [], + nodes: [], + limits: ['Unlimited', '20', '50', '100'], + filterLabelKey: '', + filterLabelValue: '', + filterNamespace: '', + filterNode: '', + filterLimit: '', + focuseNode: {name: null, label: null, type: null}, + pinNode: [], + isToolipActive: false, + targetPosition: { + top: null, + right: null, + left: null, + width: null, + }, + tooltipNode: { + name: null, + cpu: null, + memory: null, + }, + minions: [], + isOpenMinions: false, + isDisabledMinions: false, + kubernetesD3Data: {name: null, children: []}, + layouts: [], + hostLinks: EMPTY_LINKS, + kubernetesData: null, + kubernetesObject: null, + remoteDataState: RemoteDataState.NotStarted, + } + } + + public getNodes = async (detail: boolean = true) => { + const {selectMinion} = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = {kwarg: {detail}} + + const target = + !selectMinion || selectMinion === this.noSelect ? '*' : selectMinion + + const nodes = await this.props.handleGetNodes( + saltMasterUrl, + saltMasterToken, + target, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(nodes.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getPods = async (node: string) => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + filterLimit, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + detail: true, + fieldselector: `spec.nodeName=${node}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + namespace: `${filterNamespace}`, + limit: filterLimit !== '' ? parseInt(filterLimit) : 100, + }, + } + + const pods = await this.props.handleGetPods( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(pods.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getDeployments = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const deployments = await this.props.handleGetDeployments( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(deployments.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getReplicaSets = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const replicaSets = await this.props.handleGetReplicaSets( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(replicaSets.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getReplicationControllers = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const replicationControllers = await this.props.handleGetReplicationControllers( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(replicationControllers.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getDaemonSets = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const daemonSets = await this.props.handleGetDaemonSets( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(daemonSets.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getStatefulSets = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const statefulSets = await this.props.handleGetStatefulSets( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(statefulSets.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getCronJobs = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const cronJobs = await this.props.handleGetCronJobs( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(cronJobs.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getJobs = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const jobs = await this.props.handleGetJobs( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(jobs.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getServices = async flag => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + if (flag) { + const services = await this.props.handleGetServices( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(services.data).return[0])[0], + this.jsonRemoveNull + ) + ) + return resultJson + } else { + return null + } + } + + public getIngresses = async flag => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + if (flag) { + const ingresses = await this.props.handleGetIngresses( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(ingresses.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } else { + return null + } + } + + public getConfigmaps = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const configmaps = await this.props.handleGetConfigmaps( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(configmaps.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getSecrets = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const secrets = await this.props.handleGetSecrets( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(secrets.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getServiceAccounts = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const serviceAccounts = await this.props.handleGetServiceAccounts( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(serviceAccounts.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getClusterRoles = async () => { + const {selectMinion, filterLabelKey, filterLabelValue} = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const clusterRoles = await this.props.handleGetClusterRoles( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(clusterRoles.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getClusterRoleBindings = async () => { + const {selectMinion, filterLabelKey, filterLabelValue} = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const clusterRoleBindings = await this.props.handleGetClusterRoleBindings( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(clusterRoleBindings.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getRoles = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const roles = await this.props.handleGetRoles( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(roles.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getRoleBindings = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const roleBindings = await this.props.handleGetRoleBindings( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(roleBindings.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getPersistentVolumes = async () => { + const {selectMinion, filterLabelKey, filterLabelValue} = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const persistentVolumes = await this.props.handleGetPersistentVolumes( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(persistentVolumes.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getPersistentVolumeClaims = async () => { + const { + selectMinion, + filterNamespace, + filterLabelKey, + filterLabelValue, + } = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: { + namespace: `${filterNamespace}`, + labelselector: + !_.isEmpty(filterLabelKey) && !_.isEmpty(filterLabelValue) + ? `${filterLabelKey}=${filterLabelValue}` + : '', + detail: true, + }, + } + + const persistentVolumeClaims = await this.props.handleGetPersistentVolumeClaims( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(persistentVolumeClaims.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public jsonRemoveNull = (key: string, value: any) => { + if (value !== null && key !== 'managed_fields' && key !== 'annotations') + return value + } + + public parentNavigation = d => { + const {kubernetesData} = this.state + const findData = [] + + findData.push(d.data.name) + + if (_.get(kubernetesData, d.data.owner)) { + if (d.parent.data.type === 'Ingress') { + const spec = _.get(kubernetesData, d.data.owner) + if (_.get(spec, 'rules')) { + const objKind = _.get(d, 'parent.parent.data.type') + const objLabel = _.get(d, 'parent.parent.data.label') + _.map(_.get(spec, 'rules'), rule => { + _.map(_.get(rule, 'http.paths'), service => { + findData.push( + `${objKind}_${objLabel}_Service_${_.get( + service, + 'backend.service_name' + )}` + ) + }) + }) + } + } else { + const owner = _.get(kubernetesData, d.data.owner) + _.map(owner, owner => { + if (owner['kind'] !== d.parent.data.type) { + const objKind = _.get(d, 'parent.parent.data.type') + const objLabel = _.get(d, 'parent.parent.data.label') + findData.push( + `${objKind}_${objLabel}_${owner['kind']}_${owner['name']}` + ) + if ( + _.get( + kubernetesData, + `${objKind}.${objLabel}.${owner['kind']}.${owner['name']}.metadata.owner_references` + ) + ) { + const parentOwner = _.get( + kubernetesData, + `${objKind}.${objLabel}.${owner['kind']}.${owner['name']}.metadata.owner_references` + ) + _.map(parentOwner, parentOwner => { + if (parentOwner['kind'] !== d.parent.data.type) { + findData.push( + `${objKind}_${objLabel}_${parentOwner['kind']}_${parentOwner['name']}` + ) + } + }) + } + } + }) + } + } + + if (_.get(d, 'data.child')) { + if (_.get(kubernetesData, _.get(d, 'data.child'))) { + const pod = _.get(kubernetesData, _.get(d, 'data.child')) + _.map(pod, pod => { + if (_.get(d, 'parent')) { + if (_.get(d, 'parent.parent')) { + const objKind = _.get(d, 'parent.parent.data.type') + const objLabel = _.get(d, 'parent.parent.data.label') + + if (_.get(d, 'parent.data.type') === 'Service') { + _.map( + _.get(kubernetesData, `${objKind}.${objLabel}.Ingress`), + ingress => { + _.map(_.get(ingress.spec, 'rules'), rule => { + _.map(_.get(rule, 'http.paths'), service => { + if ( + _.get(service, 'backend.service_name') === + _.get(d, 'data.label') + ) { + findData.push( + `${objKind}_${objLabel}_Ingress_${_.get( + ingress, + 'metadata.name' + )}` + ) + } + }) + }) + } + ) + } else { + _.map( + _.get( + kubernetesData, + `${objKind}.${objLabel}.Node.${pod['node_name']}.Pod.${pod['name']}.metadata.owner_references` + ), + owner => { + findData.push( + `${objKind}_${objLabel}_${owner['kind']}_${owner['name']}` + ) + } + ) + } + + findData.push( + `${objKind}_${objLabel}_${pod['node_name']}_${pod['name']}` + ) + } + } + }) + } + } + + const relation = _.map(_.unionBy(findData), (name: string): string => + name.replace(/\:/g, '\\:').replace(/\./g, '\\.') + ) + + return relation + } + + public getMinionKeyAcceptedList = async () => { + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + try { + const minions = await this.props.handleGetMinionKeyAcceptedList( + saltMasterUrl, + saltMasterToken + ) + + return minions + } catch (error) { + return [] + } + } + + public getNamespaces = async () => { + const {selectMinion} = this.state + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + const pParam: SaltStack = { + kwarg: {namespace: '', detail: true}, + } + + const namespaces = await this.props.handleGetNamespaces( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(namespaces.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + return resultJson + } + + public getK8sObject = async () => { + this.setState({remoteDataState: RemoteDataState.Loading}) + + const info = await Promise.all([ + this.getNamespaces(), + this.getNodes(), + this.getServices(true), + this.getIngresses(true), + this.getConfigmaps(), + this.getSecrets(), + this.getServiceAccounts(), + this.getClusterRoles(), + this.getClusterRoleBindings(), + this.getRoles(), + this.getRoleBindings(), + this.getPersistentVolumes(), + this.getPersistentVolumeClaims(), + ]) + + if (typeof info[0] !== 'object') { + this.setState({remoteDataState: RemoteDataState.Error}) + return + } + + let kubernetesData = {} + const kubernetesD3Data: D3K8sData = {name: 'k8s', children: []} + const d3Namespaces = {} + + const namespaces = _.reduce( + !_.isEmpty(this.state.filterNamespace) + ? _.filter( + info[0], + namespace => + namespace['metadata']['name'] === this.state.filterNamespace + ) + : info[0], + (namespaces: object, namespace) => { + const namespaceName = _.get(namespace, 'metadata.name') + namespaces[namespaceName] = { + metadata: _.get(namespace, 'metadata'), + spec: _.get(namespace, 'spec'), + status: _.get(namespace, 'status'), + } + + d3Namespaces[namespaceName] = { + name: `Namespace_${namespaceName}`, + label: namespaceName, + type: 'Namespace', + value: 50, + children: [], + } + + return namespaces + }, + {} + ) + + const allNamespaces = _.map(info[0], namespace => + _.get(namespace, 'metadata.name') + ) + + _.map(info[2], m => { + const namespaceName = _.get(m, 'metadata.namespace') + const serviceName = _.get(m, 'metadata.name') + if ( + info[2] !== null && + !_.includes(_.keys(namespaces[namespaceName]), 'Service') + ) { + namespaces[namespaceName] = { + ...namespaces[namespaceName], + Service: {}, + } + + const d3DataDepth2: D3DataDepth2 = { + name: `Namespace_${namespaceName}_Service`, + label: 'Service', + type: 'Service', + children: [], + } + + d3Namespaces[namespaceName].children.push(d3DataDepth2) + } + + namespaces[namespaceName]['Service'][serviceName] = { + metadata: _.get(m, 'metadata'), + spec: _.get(m, 'spec'), + status: _.get(m, 'status'), + } + + const d3DataDepth3: D3DataDepth3 = { + name: `Namespace_${namespaceName}_Service_${serviceName}`, + label: serviceName, + type: 'SVC', + namespace: `${namespaceName}`, + value: 10, + } + d3Namespaces[namespaceName].children[ + _.findIndex(d3Namespaces[namespaceName].children, { + name: `Namespace_${namespaceName}_Service`, + }) + ].children.push(d3DataDepth3) + }) + + _.map(info[3], m => { + const namespaceName = _.get(m, 'metadata.namespace') + const ingressName = _.get(m, 'metadata.name') + if ( + info[3] !== null && + !_.includes(_.keys(namespaces[namespaceName]), 'Ingress') + ) { + namespaces[namespaceName] = { + ...namespaces[namespaceName], + Ingress: {}, + } + + const d3DataDepth2: D3DataDepth2 = { + name: `Namespace_${namespaceName}_Ingress`, + label: 'Ingress', + type: 'Ingress', + children: [], + } + + d3Namespaces[namespaceName].children.push(d3DataDepth2) + } + + namespaces[namespaceName]['Ingress'][ingressName] = { + metadata: _.get(m, 'metadata'), + spec: _.get(m, 'spec'), + status: _.get(m, 'status'), + } + + const d3DataDepth3: D3DataDepth3 = { + name: `Namespace_${namespaceName}_Ingress_${ingressName}`, + label: ingressName, + type: 'IGS', + namespace: `${namespaceName}`, + value: 10, + } + + d3Namespaces[namespaceName].children[ + _.findIndex(d3Namespaces[namespaceName].children, { + name: `Namespace_${namespaceName}_Ingress`, + }) + ].children.push(d3DataDepth3) + }) + + _.map(info[4], m => { + const namespaceName = _.get(m, 'metadata.namespace') + const configmapName = _.get(m, 'metadata.name') + if ( + info[4] !== null && + !_.includes(_.keys(namespaces[namespaceName]), 'Configmap') + ) { + namespaces[namespaceName] = { + ...namespaces[namespaceName], + Configmap: {}, + } + + const d3DataDepth2: D3DataDepth2 = { + name: `Namespace_${namespaceName}_Configmap`, + label: 'Configmap', + type: 'Configmap', + children: [], + } + + d3Namespaces[namespaceName].children.push(d3DataDepth2) + } + + namespaces[namespaceName]['Configmap'][configmapName] = { + metadata: _.get(m, 'metadata'), + spec: _.get(m, 'spec'), + status: _.get(m, 'status'), + } + + const d3DataDepth3: D3DataDepth3 = { + name: `Namespace_${namespaceName}_Configmap_${configmapName}`, + label: configmapName, + type: 'CM', + namespace: `${namespaceName}`, + value: 10, + } + + d3Namespaces[namespaceName].children[ + _.findIndex(d3Namespaces[namespaceName].children, { + name: `Namespace_${namespaceName}_Configmap`, + }) + ].children.push(d3DataDepth3) + }) + + _.map(info[5], m => { + const namespaceName = _.get(m, 'metadata.namespace') + const secretName = _.get(m, 'metadata.name') + if ( + info[5] !== null && + !_.includes(_.keys(namespaces[namespaceName]), 'Secret') + ) { + namespaces[namespaceName] = { + ...namespaces[namespaceName], + Secret: {}, + } + + const d3DataDepth2: D3DataDepth2 = { + name: `Namespace_${namespaceName}_Secret`, + label: 'Secret', + type: 'Secret', + children: [], + } + + d3Namespaces[namespaceName].children.push(d3DataDepth2) + } + + namespaces[namespaceName]['Secret'][secretName] = { + metadata: _.get(m, 'metadata'), + spec: _.get(m, 'spec'), + status: _.get(m, 'status'), + } + + const d3DataDepth3: D3DataDepth3 = { + name: `Namespace_${namespaceName}_Secret_${secretName}`, + label: secretName, + type: 'SR', + namespace: `${namespaceName}`, + value: 10, + } + + d3Namespaces[namespaceName].children[ + _.findIndex(d3Namespaces[namespaceName].children, { + name: `Namespace_${namespaceName}_Secret`, + }) + ].children.push(d3DataDepth3) + }) + + _.map(info[6], m => { + const namespaceName = _.get(m, 'metadata.namespace') + const serviceAccountName = _.get(m, 'metadata.name') + if ( + info[6] !== null && + !_.includes(_.keys(namespaces[namespaceName]), 'ServiceAccount') + ) { + namespaces[namespaceName] = { + ...namespaces[namespaceName], + ServiceAccount: {}, + } + + const d3DataDepth2: D3DataDepth2 = { + name: `Namespace_${namespaceName}_ServiceAccount`, + label: 'ServiceAccount', + type: 'ServiceAccount', + children: [], + } + d3Namespaces[namespaceName].children.push(d3DataDepth2) + } + + namespaces[namespaceName]['ServiceAccount'][serviceAccountName] = { + metadata: _.get(m, 'metadata'), + spec: _.get(m, 'spec'), + status: _.get(m, 'status'), + } + + const d3DataDepth3: D3DataDepth3 = { + name: `Namespace_${namespaceName}_ServiceAccount_${serviceAccountName}`, + label: serviceAccountName, + type: 'SA', + namespace: `${namespaceName}`, + value: 10, + } + + d3Namespaces[namespaceName].children[ + _.findIndex(d3Namespaces[namespaceName].children, { + name: `Namespace_${namespaceName}_ServiceAccount`, + }) + ].children.push(d3DataDepth3) + }) + + _.map(info[7], m => { + const clusterRoleName = _.get(m, 'metadata.name') + if ( + info[7] !== null && + !_.includes(_.keys(kubernetesData), 'ClusterRole') + ) { + kubernetesData = { + ...kubernetesData, + ClusterRole: {}, + } + + const d3DataDepth1: D3DataDepth1 = { + name: 'ClusterRole', + label: 'ClusterRole', + type: 'ClusterRole', + children: [], + } + + kubernetesD3Data.children.push(d3DataDepth1) + } + + kubernetesData['ClusterRole'][clusterRoleName] = { + metadata: _.get(m, 'metadata'), + spec: _.get(m, 'spec'), + status: _.get(m, 'status'), + } + + const d3DataDepth2: D3DataDepth2 = { + name: `ClusterRole_${clusterRoleName}`, + label: clusterRoleName, + type: 'CR', + value: 10, + } + + kubernetesD3Data.children[ + _.findIndex(kubernetesD3Data.children, { + name: 'ClusterRole', + }) + ].children.push(d3DataDepth2) + }) + + _.map(info[8], m => { + const clusterRoleBindingName = _.get(m, 'metadata.name') + if ( + info[8] !== null && + !_.includes(_.keys(kubernetesData), 'ClusterRoleBinding') + ) { + kubernetesData = { + ...kubernetesData, + ClusterRoleBinding: {}, + } + + const d3DataDepth1: D3DataDepth1 = { + name: 'ClusterRoleBinding', + label: 'ClusterRoleBinding', + type: 'ClusterRoleBinding', + children: [], + } + + kubernetesD3Data.children.push(d3DataDepth1) + } + + kubernetesData['ClusterRoleBinding'][clusterRoleBindingName] = { + metadata: _.get(m, 'metadata'), + spec: _.get(m, 'spec'), + status: _.get(m, 'status'), + } + + const d3DataDepth2: D3DataDepth2 = { + name: `ClusterRoleBinding_${clusterRoleBindingName}`, + label: clusterRoleBindingName, + type: 'CRB', + value: 10, + } + + kubernetesD3Data.children[ + _.findIndex(kubernetesD3Data.children, { + name: 'ClusterRoleBinding', + }) + ].children.push(d3DataDepth2) + }) + + _.map(info[9], m => { + const namespaceName = _.get(m, 'metadata.namespace') + const roleName = _.get(m, 'metadata.name') + if ( + info[9] !== null && + !_.includes(_.keys(namespaces[namespaceName]), 'Role') + ) { + namespaces[namespaceName] = { + ...namespaces[namespaceName], + Role: {}, + } + + const d3DataDepth2: D3DataDepth2 = { + name: `Namespace_${namespaceName}_Role`, + label: 'Role', + type: 'Role', + children: [], + } + d3Namespaces[namespaceName].children.push(d3DataDepth2) + } + + namespaces[namespaceName]['Role'][roleName] = { + metadata: _.get(m, 'metadata'), + spec: _.get(m, 'spec'), + status: _.get(m, 'status'), + } + + const d3DataDepth3: D3DataDepth3 = { + name: `Namespace_${namespaceName}_Role_${roleName}`, + label: roleName, + type: 'RL', + namespace: `${namespaceName}`, + value: 10, + } + d3Namespaces[namespaceName].children[ + _.findIndex(d3Namespaces[namespaceName].children, { + name: `Namespace_${namespaceName}_Role`, + }) + ].children.push(d3DataDepth3) + }) + + _.map(info[10], m => { + const namespaceName = _.get(m, 'metadata.namespace') + const roleBindingName = _.get(m, 'metadata.name') + if ( + info[10] !== null && + !_.includes(_.keys(namespaces[namespaceName]), 'RoleBinding') + ) { + namespaces[namespaceName] = { + ...namespaces[namespaceName], + RoleBinding: {}, + } + + const d3DataDepth2: D3DataDepth2 = { + name: `Namespace_${namespaceName}_RoleBinding`, + label: 'RoleBinding', + type: 'RoleBinding', + children: [], + } + + d3Namespaces[namespaceName].children.push(d3DataDepth2) + } + + namespaces[namespaceName]['RoleBinding'][roleBindingName] = { + metadata: _.get(m, 'metadata'), + spec: _.get(m, 'spec'), + status: _.get(m, 'status'), + } + + const d3DataDepth3: D3DataDepth3 = { + name: `Namespace_${namespaceName}_RoleBinding_${roleBindingName}`, + label: roleBindingName, + type: 'RB', + namespace: `${namespaceName}`, + value: 10, + } + + d3Namespaces[namespaceName].children[ + _.findIndex(d3Namespaces[namespaceName].children, { + name: `Namespace_${namespaceName}_RoleBinding`, + }) + ].children.push(d3DataDepth3) + }) + + _.map(info[11], m => { + const persistentVolumeName = _.get(m, 'metadata.name') + if ( + info[11] !== null && + !_.includes(_.keys(kubernetesData), 'PersistentVolume') + ) { + kubernetesData = { + ...kubernetesData, + PersistentVolume: {}, + } + + const d3DataDepth1: D3DataDepth1 = { + name: 'PersistentVolume', + label: 'PersistentVolume', + type: 'PersistentVolume', + children: [], + } + + kubernetesD3Data.children.push(d3DataDepth1) + } + + kubernetesData['PersistentVolume'][persistentVolumeName] = { + metadata: _.get(m, 'metadata'), + spec: _.get(m, 'spec'), + status: _.get(m, 'status'), + } + + const d3DataDepth2: D3DataDepth2 = { + name: `PersistentVolume_${persistentVolumeName}`, + label: persistentVolumeName, + type: 'PV', + value: 10, + } + + kubernetesD3Data.children[ + _.findIndex(kubernetesD3Data.children, { + name: 'PersistentVolume', + }) + ].children.push(d3DataDepth2) + }) + + _.map(info[12], m => { + const namespaceName = _.get(m, 'metadata.namespace') + const persistentVolumeClaimName = _.get(m, 'metadata.name') + if ( + info[12] !== null && + !_.includes(_.keys(namespaces[namespaceName]), 'PersistentVolumeClaim') + ) { + namespaces[namespaceName] = { + ...namespaces[namespaceName], + PersistentVolumeClaim: {}, + } + + const d3DataDepth2: D3DataDepth2 = { + name: `Namespace_${namespaceName}_PersistentVolumeClaim`, + label: 'PersistentVolumeClaim', + type: 'PersistentVolumeClaim', + children: [], + } + + d3Namespaces[namespaceName].children.push(d3DataDepth2) + } + + namespaces[namespaceName]['PersistentVolumeClaim'][ + persistentVolumeClaimName + ] = { + metadata: _.get(m, 'metadata'), + spec: _.get(m, 'spec'), + status: _.get(m, 'status'), + } + + const d3DataDepth3: D3DataDepth3 = { + name: `Namespace_${namespaceName}_PersistentVolumeClaim_${persistentVolumeClaimName}`, + label: persistentVolumeClaimName, + type: 'PVC', + namespace: `${namespaceName}`, + value: 10, + } + + d3Namespaces[namespaceName].children[ + _.findIndex(d3Namespaces[namespaceName].children, { + name: `Namespace_${namespaceName}_PersistentVolumeClaim`, + }) + ].children.push(d3DataDepth3) + }) + + const nodes = _.reduce( + !_.isEmpty(this.state.filterNode) + ? _.filter( + info[1], + node => node['metadata']['name'] === this.state.filterNode + ) + : info[1], + (nodes: object, node) => { + nodes[_.get(node, 'metadata.name')] = { + metadata: _.get(node, 'metadata'), + spec: _.get(node, 'spec'), + status: _.get(node, 'status'), + } + + return nodes + }, + {} + ) + + const allNodes = _.map(info[1], node => _.get(node, 'metadata.name')) + + const podsPromises = _.map(_.keys(nodes), this.getPods) + const pods = await Promise.all(podsPromises) + + const etcObject = await Promise.all([ + this.getDeployments(), + this.getReplicaSets(), + this.getReplicationControllers(), + this.getDaemonSets(), + this.getStatefulSets(), + this.getCronJobs(), + this.getJobs(), + ]) + + const deployments = _.reduce( + etcObject[0], + (deployments: object, deployment) => { + deployments[_.get(deployment, 'metadata.name')] = { + metadata: _.get(deployment, 'metadata'), + spec: _.get(deployment, 'spec'), + status: _.get(deployment, 'status'), + } + + return deployments + }, + {} + ) + + const replicaSets = _.reduce( + etcObject[1], + (replicaSets: object, replicaSet) => { + replicaSets[_.get(replicaSet, 'metadata.name')] = { + metadata: _.get(replicaSet, 'metadata'), + spec: _.get(replicaSet, 'spec'), + status: _.get(replicaSet, 'status'), + } + + return replicaSets + }, + {} + ) + + const replicationControllers = _.reduce( + etcObject[2], + (replicationControllers: object, replicationController) => { + replicationControllers[ + _.get(replicationController, 'metadata.name') + ] = { + metadata: _.get(replicationController, 'metadata'), + spec: _.get(replicationController, 'spec'), + status: _.get(replicationController, 'status'), + } + + return replicationControllers + }, + {} + ) + + const daemonSets = _.reduce( + etcObject[3], + (daemonSets: object, daemonSet) => { + daemonSets[_.get(daemonSet, 'metadata.name')] = { + metadata: _.get(daemonSet, 'metadata'), + spec: _.get(daemonSet, 'spec'), + status: _.get(daemonSet, 'status'), + } + + return daemonSets + }, + {} + ) + + const statefulSets = _.reduce( + etcObject[4], + (statefulSets: object, statefulSet) => { + statefulSets[_.get(statefulSet, 'metadata.name')] = { + metadata: _.get(statefulSet, 'metadata'), + spec: _.get(statefulSet, 'spec'), + status: _.get(statefulSet, 'status'), + } + + return statefulSets + }, + {} + ) + + const cronJobs = _.reduce( + etcObject[5], + (cronJobs: object, cronJob) => { + cronJobs[_.get(cronJob, 'metadata.name')] = { + metadata: _.get(cronJob, 'metadata'), + spec: _.get(cronJob, 'spec'), + status: _.get(cronJob, 'status'), + } + + return cronJobs + }, + {} + ) + + const jobs = _.reduce( + etcObject[6], + (jobs: object, job) => { + jobs[_.get(job, 'metadata.name')] = { + metadata: _.get(job, 'metadata'), + spec: _.get(job, 'spec'), + status: _.get(job, 'status'), + } + + return jobs + }, + {} + ) + + _.map(pods, pods => { + _.map(pods, pod => { + const namespace = _.get(pod, 'metadata.namespace') + const ownerReferences = _.get(pod, 'metadata.owner_references') + const podName = _.get(pod, 'metadata.name') + const nodeName = _.get(pod, 'spec.node_name') + const podContainers = _.get(pod, 'spec.containers') + const podStatus = _.get(pod, 'status.phase') + + let podCPU = 0 + let podMemory = 0 + + _.map(podContainers, podCont => { + podCPU = + podCPU + + transToCPUMillCore(_.get(podCont, 'resources.limits.cpu'), 'pod') + podMemory = + podMemory + + transMemoryToBytes(_.get(podCont, 'resources.limits.memory')) + }) + + if (!_.includes(_.keys(namespaces[namespace]), 'Node')) + namespaces[namespace] = { + ...namespaces[namespace], + Node: {}, + } + _.map(ownerReferences, po => { + const ownerKind = _.get(po, 'kind') + const ownerName = _.get(po, 'name') + if (ownerKind === 'ReplicaSet') { + if ( + replicaSets !== null && + !_.includes(_.keys(namespaces[namespace]), 'ReplicaSet') + ) { + namespaces[namespace] = { + ...namespaces[namespace], + ReplicaSet: {}, + } + + d3Namespaces[namespace].children.push({ + name: `Namespace_${namespace}_ReplicaSet`, + label: 'ReplicaSet', + type: 'ReplicaSet', + children: [], + }) + } + if ( + !_.includes( + _.keys(namespaces[namespace]['ReplicaSet']), + ownerName + ) + ) { + namespaces[namespace]['ReplicaSet'][ownerName] = { + metadata: replicaSets[ownerName].metadata, + spec: replicaSets[ownerName].spec, + status: replicaSets[ownerName].status, + Pod: [], + } + namespaces[namespace]['ReplicaSet'][ownerName]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + d3Namespaces[namespace].children[ + _.findIndex(d3Namespaces[namespace].children, { + name: `Namespace_${namespace}_ReplicaSet`, + }) + ].children.push({ + name: `Namespace_${namespace}_ReplicaSet_${ownerName}`, + owner: `Namespace.${namespace}.ReplicaSet.${ownerName}.metadata.owner_references`, + child: `Namespace.${namespace}.ReplicaSet.${ownerName}.Pod`, + label: ownerName, + type: 'RS', + namespace: `${namespace}`, + status: + _.get(replicaSets[ownerName], 'status.available_replicas') !== + _.get(replicaSets[ownerName], 'status.replicas') + ? 'Ready' + : 'Succeeded', + value: 10, + }) + + _.map( + _.get(replicaSets[ownerName], 'metadata.owner_references'), + ro => { + const ownerName = _.get(ro, 'name') + if ( + !_.includes(_.keys(namespaces[namespace]), 'Deployment') + ) { + namespaces[namespace] = { + ...namespaces[namespace], + Deployment: {}, + } + d3Namespaces[namespace].children.push({ + name: `Namespace_${namespace}_Deployment`, + label: 'Deployment', + type: 'Deployment', + children: [], + }) + } + if ( + !_.includes( + _.keys(namespaces[namespace]['Deployment']), + ownerName + ) + ) { + namespaces[namespace]['Deployment'][ownerName] = { + metadata: _.get(deployments[ownerName], 'metadata'), + spec: _.get(deployments[ownerName], 'spec'), + status: _.get(deployments[ownerName], 'status'), + Pod: [], + } + namespaces[namespace]['Deployment'][ownerName]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + d3Namespaces[namespace].children[ + _.findIndex(d3Namespaces[namespace].children, { + name: `Namespace_${namespace}_Deployment`, + }) + ].children.push({ + name: `Namespace_${namespace}_Deployment_${ownerName}`, + label: ownerName, + child: `Namespace.${namespace}.Deployment.${ownerName}.Pod`, + type: 'DP', + namespace: `${namespace}`, + status: + _.get( + deployments[ownerName], + 'status.available_replicas' + ) !== _.get(deployments[ownerName], 'status.replicas') + ? 'Ready' + : 'Succeeded', + value: 10, + }) + } else { + namespaces[namespace]['Deployment'][ownerName]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + } + } + ) + } else { + namespaces[namespace]['ReplicaSet'][ownerName]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + _.map( + _.get(replicaSets[ownerName], 'metadata.owner_references'), + ro => { + const name = _.get(ro, 'name') + if ( + !_.includes(_.keys(namespaces[namespace]), 'Deployment') + ) { + namespaces[namespace] = { + ...namespaces[namespace], + Deployment: {}, + } + + d3Namespaces[namespace].children.push({ + name: `Namespace_${namespace}_Deployment`, + label: 'Deployment', + type: 'Deployment', + children: [], + }) + } + if ( + !_.includes( + _.keys(namespaces[namespace]['Deployment']), + name + ) + ) { + namespaces[namespace]['Deployment'][name] = { + metadata: deployments[name].metadata, + spec: deployments[name].spec, + status: deployments[name].status, + Pod: [], + } + namespaces[namespace]['Deployment'][name]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + d3Namespaces[namespace].children[ + _.findIndex(d3Namespaces[namespace].children, { + name: `Namespace_${namespace}_Deployment`, + }) + ].children.push({ + name: `Namespace_${namespace}_Deployment_${name}`, + label: name, + child: `Namespace.${namespace}.Deployment.${name}.Pod`, + type: 'DP', + namespace: `${namespace}`, + status: + _.get( + deployments[ownerName], + 'status.available_replicas' + ) !== _.get(deployments[ownerName], 'status.replicas') + ? 'Ready' + : 'Succeeded', + value: 10, + }) + } else { + namespaces[namespace]['Deployment'][name]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + } + } + ) + } + } else if (ownerKind === 'ReplicationController') { + if ( + replicationControllers !== null && + !_.includes( + _.keys(namespaces[namespace]), + 'ReplicationController' + ) + ) { + namespaces[namespace] = { + ...namespaces[namespace], + ReplicationController: {}, + } + + d3Namespaces[namespace].children.push({ + name: `Namespace_${namespace}_ReplicationController`, + label: 'ReplicationController', + type: 'ReplicationController', + children: [], + }) + } + if ( + !_.includes( + _.keys(namespaces[namespace]['ReplicationController']), + ownerName + ) + ) { + namespaces[namespace]['ReplicationController'][ownerName] = { + metadata: _.get(replicationControllers[ownerName], 'metadata'), + spec: _.get(replicationControllers[ownerName], 'spec'), + status: _.get(replicationControllers[ownerName], 'status'), + Pod: [], + } + namespaces[namespace]['ReplicationController'][ownerName][ + 'Pod' + ].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + d3Namespaces[namespace].children[ + _.findIndex(d3Namespaces[namespace].children, { + name: `Namespace_${namespace}_ReplicationController`, + }) + ].children.push({ + name: `Namespace_${namespace}_ReplicationController_${ownerName}`, + label: ownerName, + type: 'RC', + namespace: `${namespace}`, + child: `Namespace.${namespace}.ReplicationController.${ownerName}.Pod`, + status: + _.get( + replicationControllers[ownerName], + 'status.available_replicas' + ) !== + _.get(replicationControllers[ownerName], 'status.replicas') + ? 'Ready' + : 'Succeeded', + value: 10, + }) + } else { + namespaces[namespace]['ReplicationController'][ownerName][ + 'Pod' + ].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + } + } else if (ownerKind === 'DaemonSet') { + if ( + daemonSets !== null && + !_.includes(_.keys(namespaces[namespace]), 'DaemonSet') + ) { + namespaces[namespace] = { + ...namespaces[namespace], + DaemonSet: {}, + } + + d3Namespaces[namespace].children.push({ + name: `Namespace_${namespace}_DaemonSet`, + label: 'DaemonSet', + type: 'DaemonSet', + children: [], + }) + } + if ( + !_.includes(_.keys(namespaces[namespace]['DaemonSet']), ownerName) + ) { + namespaces[namespace]['DaemonSet'][ownerName] = { + metadata: _.get(daemonSets[ownerName], 'metadata'), + spec: _.get(daemonSets[ownerName], 'spec'), + status: _.get(daemonSets[ownerName], 'status'), + Pod: [], + } + namespaces[namespace]['DaemonSet'][ownerName]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + d3Namespaces[namespace].children[ + _.findIndex(d3Namespaces[namespace].children, { + name: `Namespace_${namespace}_DaemonSet`, + }) + ].children.push({ + name: `Namespace_${namespace}_DaemonSet_${ownerName}`, + label: ownerName, + type: 'DS', + namespace: `${namespace}`, + child: `Namespace.${namespace}.DaemonSet.${ownerName}.Pod`, + status: + _.get(daemonSets[ownerName], 'status.numberUnavailable') && + _.get(daemonSets[ownerName], 'status.numberUnavailable') > 0 + ? 'Ready' + : 'Succeeded', + value: 10, + }) + } else { + namespaces[namespace]['DaemonSet'][ownerName]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + } + } else if (ownerKind === 'StatefulSet') { + if ( + statefulSets !== null && + !_.includes(_.keys(namespaces[namespace]), 'StatefulSet') + ) { + namespaces[namespace] = { + ...namespaces[namespace], + StatefulSet: {}, + } + + d3Namespaces[namespace].children.push({ + name: `Namespace_${namespace}_StatefulSet`, + label: 'StatefulSet', + type: 'StatefulSet', + children: [], + }) + } + if ( + !_.includes( + _.keys(namespaces[namespace]['StatefulSet']), + ownerName + ) + ) { + namespaces[namespace]['StatefulSet'][ownerName] = { + metadata: _.get(statefulSets[ownerName], 'metadata'), + spec: _.get(statefulSets[ownerName], 'spec'), + status: _.get(statefulSets[ownerName], 'status'), + Pod: [], + } + namespaces[namespace]['StatefulSet'][ownerName]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + d3Namespaces[namespace].children[ + _.findIndex(d3Namespaces[namespace].children, { + name: `Namespace_${namespace}_StatefulSet`, + }) + ].children.push({ + name: `Namespace_${namespace}_StatefulSet_${ownerName}`, + label: ownerName, + type: 'SS', + namespace: `${namespace}`, + child: `Namespace.${namespace}.StatefulSet.${ownerName}.Pod`, + status: + _.get(statefulSets[ownerName], 'status.replicas') !== + _.get(statefulSets[ownerName], 'status.currentReplicas') + ? 'Ready' + : 'Succeeded', + value: 10, + }) + } else { + namespaces[namespace]['StatefulSet'][ownerName]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + } + } else if (ownerKind === 'Job') { + if ( + jobs !== null && + !_.includes(_.keys(namespaces[namespace]), 'Job') + ) { + namespaces[namespace] = { + ...namespaces[namespace], + Job: {}, + } + + d3Namespaces[namespace].children.push({ + name: `Namespace_${namespace}_Job`, + label: 'Job', + type: 'Job', + children: [], + }) + } + if (!_.includes(_.keys(namespaces[namespace]['Job']), ownerName)) { + namespaces[namespace]['Job'][ownerName] = { + metadata: _.get(jobs[ownerName], 'metadata'), + spec: _.get(jobs[ownerName], 'spec'), + status: _.get(jobs[ownerName], 'status'), + Pod: [], + } + namespaces[namespace]['Job'][ownerName]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + d3Namespaces[namespace].children[ + _.findIndex(d3Namespaces[namespace].children, { + name: `Namespace_${namespace}_Job`, + }) + ].children.push({ + name: `Namespace_${namespace}_Job_${ownerName}`, + label: ownerName, + type: 'Job', + namespace: `${namespace}`, + owner: `Namespace.${namespace}.Job.${ownerName}.metadata.owner_references`, + child: `Namespace.${namespace}.Job.${ownerName}.Pod`, + status: + _.get(jobs[ownerName], 'status.failed') && + _.get(jobs[ownerName], 'status.failed ') > 0 + ? 'Ready' + : 'Succeeded', + value: 10, + }) + + _.map(_.get(jobs[ownerName], 'metadata.owner_references'), ro => { + const name = _.get(ro, 'name') + if (!_.includes(_.keys(namespaces[namespace]), 'CronJob')) { + namespaces[namespace] = { + ...namespaces[namespace], + CronJob: {}, + } + + d3Namespaces[namespace].children.push({ + name: `Namespace_${namespace}_CronJob`, + label: 'CronJob', + type: 'CronJob', + children: [], + }) + } + if ( + !_.includes(_.keys(namespaces[namespace]['CronJob']), name) + ) { + namespaces[namespace]['CronJob'][name] = { + metadata: _.get(cronJobs[name], 'metadata'), + spec: _.get(cronJobs[name], 'spec'), + status: _.get(cronJobs[name], 'status'), + Pod: [], + } + namespaces[namespace]['CronJob'][name]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + d3Namespaces[namespace].children[ + _.findIndex(d3Namespaces[namespace].children, { + name: `Namespace_${namespace}_CronJob`, + }) + ].children.push({ + name: `Namespace_${namespace}_CronJob_${name}`, + label: name, + type: 'CJ', + namespace: `${namespace}`, + child: `Namespace.${namespace}.CronJob.${name}.Pod`, + status: + _.get(jobs[ownerName], 'status.failed') && + _.get(jobs[ownerName], 'status.failed ') > 0 + ? 'Ready' + : 'Succeeded', + value: 10, + }) + } else { + namespaces[namespace]['CronJob'][name]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + } + }) + } else { + namespaces[namespace]['Job'][ownerName]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + _.map(jobs[ownerName].metadata.owner_references, ro => { + const name = _.get(ro, 'name') + if (!_.includes(_.keys(namespaces[namespace]), 'CronJob')) { + namespaces[namespace] = { + ...namespaces[namespace], + CronJob: {}, + } + + d3Namespaces[namespace].children.push({ + name: `Namespace_${namespace}_CronJob`, + label: 'CronJob', + type: 'CronJob', + children: [], + }) + } + if ( + !_.includes(_.keys(namespaces[namespace]['CronJob']), name) + ) { + namespaces[namespace]['CronJob'][name] = { + metadata: _.get(cronJobs[name], 'metadata'), + spec: _.get(cronJobs[name], 'spec'), + status: _.get(cronJobs[name], 'status'), + Pod: [], + } + namespaces[namespace]['CronJob'][name]['Pod'].push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + d3Namespaces[namespace].children[ + _.findIndex(d3Namespaces[namespace].children, { + name: `Namespace_${namespace}_CronJob`, + }) + ].children.push({ + name: `Namespace_${namespace}_CronJob_${name}`, + label: name, + type: 'CJ', + namespace: `${namespace}`, + child: `Namespace.${namespace}.CronJob.${name}.Pod`, + status: + _.get(jobs[ownerName], 'status.failed') && + _.get(jobs[ownerName], 'status.failed ') > 0 + ? 'Ready' + : 'Succeeded', + value: 10, + }) + } else { + namespaces[namespace]['CronJob'][name]['Pod'].push({ + name: podName, + node_name: nodeName, + }) + } + }) + } + } + }) + + if (!_.includes(_.keys(namespaces[namespace]['Node']), nodeName)) { + namespaces[namespace]['Node'][nodeName] = { + metadata: _.get(nodes[nodeName], 'metadata'), + spec: _.get(nodes[nodeName], 'spec'), + status: _.get(nodes[nodeName], 'status'), + Pod: {}, + } + d3Namespaces[namespace].children.push({ + name: `Namespace_${namespace}_${nodeName}`, + label: nodeName, + type: 'Node', + data: { + cpu: `${transToCPUMillCore( + _.get(nodes[nodeName], 'status.allocatable.cpu'), + 'node' + )}`, + memory: `${transMemoryToBytes( + _.get(nodes[nodeName], 'status.allocatable.memory') + )}`, + }, + children: [], + }) + } + + if ( + !_.includes( + _.keys(namespaces[namespace]['Node'][nodeName]['Pod']), + podName + ) + ) { + namespaces[namespace]['Node'][nodeName]['Pod'][podName] = { + metadata: _.get(pod, 'metadata'), + spec: _.get(pod, 'spec'), + status: _.get(pod, 'status'), + } + + d3Namespaces[namespace].children[ + _.findIndex(d3Namespaces[namespace].children, { + name: `Namespace_${namespace}_${nodeName}`, + }) + ].children.push({ + name: `Namespace_${namespace}_${nodeName}_${podName}`, + label: podName, + owner: `Namespace.${namespace}.Node.${nodeName}.Pod.${podName}.metadata.owner_references`, + type: 'Pod', + namespace: `${namespace}`, + data: { + cpu: `${ + podCPU !== 0 + ? podCPU + : transToCPUMillCore( + _.get(nodes[nodeName], 'status.allocatable.cpu'), + 'node' + ) + }`, + memory: `${ + podMemory !== 0 + ? podMemory + : transMemoryToBytes( + _.get(nodes[nodeName], 'status.allocatable.memory') + ) + }`, + }, + time: new Date().getSeconds(), + status: `${podStatus}`, + value: 10, + }) + } + + if (_.includes(_.keys(namespaces[namespace]), 'Service')) { + const podService = _.values(_.get(pod, 'metadata.labels'))[0] + const serviceInfo = _.filter( + namespaces[namespace]['Service'], + f => _.values(_.get(f.spec, 'selector'))[0] === podService + )[0] + + if (serviceInfo !== undefined) { + const serviceName = _.get(serviceInfo, 'metadata.name') + if ( + !_.includes( + _.keys(namespaces[namespace]['Service'][serviceName]), + 'Pod' + ) + ) { + namespaces[namespace]['Service'][serviceName] = { + ...namespaces[namespace]['Service'][serviceName], + Pod: [], + } + namespaces[namespace]['Service'][serviceName].Pod.push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + const serviceIndex = _.findIndex( + d3Namespaces[namespace].children, + { + name: `Namespace_${namespace}_Service`, + } + ) + + const serviceChildrenIndex = _.findIndex( + d3Namespaces[namespace].children[serviceIndex].children, + { + name: `Namespace_${namespace}_Service_${serviceName}`, + } + ) + + d3Namespaces[namespace].children[serviceIndex].children[ + serviceChildrenIndex + ] = { + ...d3Namespaces[namespace].children[serviceIndex].children[ + serviceChildrenIndex + ], + child: `Namespace.${namespace}.Service.${serviceName}.Pod`, + } + } else { + namespaces[namespace]['Service'][serviceName].Pod.push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + } + + if (_.includes(_.keys(namespaces[namespace]), 'Ingress')) { + _.map(namespaces[namespace]['Ingress'], ingress => { + const ingressName = _.get(ingress, 'metadata.name') + _.map(_.get(ingress.spec, 'rules'), rule => { + _.map(_.get(rule, 'http.paths'), service => { + if ( + _.get(service, 'backend.service_name') === serviceName + ) { + if ( + !_.includes( + _.keys(namespaces[namespace]['Ingress'][ingressName]), + 'Pod' + ) + ) { + namespaces[namespace]['Ingress'][ingressName] = { + ...namespaces[namespace]['Ingress'][ingressName], + Pod: [], + } + namespaces[namespace]['Ingress'][ingressName].Pod.push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + + const ingressIndex = _.findIndex( + d3Namespaces[namespace].children, + { + name: `Namespace_${namespace}_Ingress`, + } + ) + + const ingressChildrenIndex = _.findIndex( + d3Namespaces[namespace].children[ingressIndex] + .children, + { + name: `Namespace_${namespace}_Ingress_${ingressName}`, + } + ) + + d3Namespaces[namespace].children[ingressIndex].children[ + ingressChildrenIndex + ] = { + ...d3Namespaces[namespace].children[ingressIndex] + .children[ingressChildrenIndex], + owner: `Namespace.${namespace}.Ingress.${ingressName}.spec`, + child: `Namespace.${namespace}.Ingress.${ingressName}.Pod`, + } + } else { + namespaces[namespace]['Ingress'][ingressName].Pod.push({ + name: podName, + node_name: nodeName, + namespaces: namespace, + }) + } + } + }) + }) + }) + } + } + } + }) + }) + + kubernetesData['Namespace'] = namespaces + + _.forEach(d3Namespaces, m => { + kubernetesD3Data.children.push(m) + }) + + this.setState({ + kubernetesData, + kubernetesD3Data, + nodes: allNodes, + namespaces: allNamespaces, + remoteDataState: RemoteDataState.Done, + }) + } + + public setD3K8sSeries() { + const {kubernetesObject} = this.state + const node = d3.select('svg').selectAll('g') + + if (!(node.data().length > 0)) return + + let d3NodeObject = {} + _.forEach(node.select(`circle[data-type=${'Node'}]`).data(), s => { + d3NodeObject[s.data.label] = { + ...d3NodeObject[s.data.label], + name: s.data.label, + cpu: s.data.data.cpu, + memory: s.data.data.memory, + } + }) + + let d3PodObject = {} + const pod = node.select(`path[data-type=${'Pod'}]`) + _.forEach(pod.data(), s => { + d3PodObject[s.data.label] = { + ...d3PodObject[s.data.label], + name: s.data.label, + cpu: s.data.data.cpu, + memory: s.data.data.memory, + } + }) + + _.forEach( + _.filter( + d3NodeObject, + f => + !_.map( + _.filter(kubernetesObject, k8sObj => k8sObj['type'] === 'Node'), + m => m['name'] + ).includes(f['name']) + ), + d3ModNod => { + node.select(`circle[label=${d3ModNod['name']}]`).attr('fill', 'gray') + } + ) + + _.forEach( + _.filter( + d3PodObject, + f => + !_.map( + _.filter(kubernetesObject, k8sObj => k8sObj['type'] === 'Pod'), + m => m['name'] + ).includes(f['name']) + ), + d3ModPod => { + node.select(`path[label=${d3ModPod['name']}]`).attr('fill', 'gray') + } + ) + try { + _.forEach(kubernetesObject, m => { + if (m['type'] === 'Node') { + if ( + _.find( + node.select(`circle[data-type=${'Node'}]`).data(), + nodeData => nodeData.data.label === m['name'] + ) + ) { + const cpuUsage = + (parseFloat(m['cpu']) / + parseFloat( + node + .select(`circle[data-label=${m['name']}]`) + .attr('data-limit-cpu') + )) * + 100 + const memoryUsage = + (parseFloat(m['memory']) / + parseFloat( + node + .select(`circle[data-label=${m['name']}]`) + .attr('data-limit-memory') + )) * + 100 + + node + .select(`circle[data-label=${m['name']}]`) + .attr('data-cpu', `${cpuUsage}`) + node + .select(`circle[data-label=${m['name']}]`) + .attr('data-memory', `${memoryUsage}`) + const pick = cpuUsage > memoryUsage ? cpuUsage : memoryUsage + node + .select(`circle[data-label=${m['name']}]`) + .attr('fill', kubernetesStatusColor(pick / 100)) + } + } else { + if ( + _.find( + node.select(`path[data-type=${'Pod'}]`).data(), + podData => podData.data.label === m['name'] + ) + ) { + const cpuUsage = + (parseFloat(m['cpu']) / + parseFloat( + node + .select(`path[data-label=${m['name']}]`) + .attr('data-limit-cpu') + )) * + 100 + const memoryUsage = + (parseFloat(m['memory']) / + parseFloat( + node + .select(`path[data-label=${m['name']}]`) + .attr('data-limit-memory') + )) * + 100 + + node + .select(`path[data-label=${m['name']}]`) + .attr('data-cpu', `${cpuUsage}`) + node + .select(`path[data-label=${m['name']}]`) + .attr('data-memory', `${memoryUsage}`) + + const pick = cpuUsage > memoryUsage ? cpuUsage : memoryUsage + node + .select(`path[data-label=${m['name']}]`) + .attr('fill', kubernetesStatusColor(pick / 100)) + } + } + }) + } catch (error) { + console.error(error) + } + } + + public async fetchK8sData() { + const {source} = this.props + const tempVars = generateForHosts(source) + + try { + const kubernetesObject = await getCpuAndLoadForK8s( + source.links.proxy, + source.telegraf, + tempVars + ) + + this.setState({kubernetesObject}) + } catch (error) { + console.error(error) + } + } + + public getFirstMinion = (minions: string[]) => { + return _.isEmpty(minions) ? this.noSelect : minions[0] + } + + public async componentDidMount() { + verifyLocalStorage( + getLocalStorage, + setLocalStorage, + 'kubernetes', + this.defaultState + ) + + let getLocal = getLocalStorage('kubernetes') + + getLocal = { + ...this.defaultState, + ...getLocal, + } + + const { + proportions, + selectedAutoRefresh, + selectMinion: storedSelectMinion, + } = getLocal + + const minions = await this.getMinionKeyAcceptedList() + + const selectMinion = _.includes(minions, storedSelectMinion) + ? storedSelectMinion + : this.getFirstMinion(minions) + + this.handleKubernetesResourceAutoRefresh() + + this.setState({ + minions, + selectMinion, + proportions, + selectedAutoRefresh, + remoteDataState: RemoteDataState.Loading, + }) + } + + public async componentDidUpdate(prevProps: Props, prevState: State) { + const {autoRefresh, manualRefresh} = this.props + const { + kubernetesObject, + kubernetesData, + focuseNode, + selectedAutoRefresh, + selectMinion, + filterNamespace, + filterLimit, + filterNode, + filterLabelKey, + filterLabelValue, + } = this.state + + if ( + prevState.selectMinion !== selectMinion && + this.noSelect !== selectMinion && + selectedAutoRefresh === 0 + ) { + this.debouncedHandleKubernetesRefresh() + } + + if (prevProps.manualRefresh !== manualRefresh) { + this.handleKubernetesResourceRefresh() + } + + if (prevProps.autoRefresh !== autoRefresh) { + this.handleKubernetesResourceAutoRefresh() + } + + if ( + JSON.stringify(prevState.kubernetesData) !== + JSON.stringify(kubernetesData) + ) { + this.fetchK8sData() + } + + if ( + JSON.stringify(prevState.kubernetesObject) !== + JSON.stringify(kubernetesObject) + ) { + this.setD3K8sSeries() + } + + if (focuseNode.name && prevState.focuseNode.name !== focuseNode.name) { + d3.selectAll(`path`).classed('kubernetes-focuse', false) + d3.select(`path[data-name=${this.state.focuseNode.name}]`).classed( + 'kubernetes-focuse', + true + ) + } + + if (prevState.pinNode !== this.state.pinNode) { + const {pinNode} = this.state + d3.selectAll(`path`).classed('kubernetes-pin', false) + _.forEach(pinNode, pin => { + d3.select(`[data-name=${pin}]`).classed('kubernetes-pin', true) + }) + } + + if (prevProps.autoRefresh !== autoRefresh) { + GlobalAutoRefresher.poll(autoRefresh) + } + + if ( + prevState.selectedAutoRefresh !== selectedAutoRefresh || + prevState.selectMinion !== selectMinion + ) { + this.handleKubernetesAutoRefresh() + } + + if (prevState.focuseNode.name !== focuseNode.name) { + const layouts = await this.fillteredLayouts() + this.setState({ + layouts, + }) + } + + if (prevState.selectedAutoRefresh !== selectedAutoRefresh) { + const getLocal = getLocalStorage('kubernetes') + setLocalStorage('kubernetes', {...getLocal, selectedAutoRefresh}) + } + + if (prevState.selectMinion !== selectMinion) { + const getLocal = getLocalStorage('kubernetes') + setLocalStorage('kubernetes', {...getLocal, selectMinion}) + } + + if ( + prevState.filterNamespace !== filterNamespace || + prevState.filterNode !== filterNode || + prevState.filterLabelKey !== filterLabelKey || + prevState.filterLabelValue !== filterLabelValue || + prevState.filterLimit !== filterLimit + ) { + this.getK8sObject() + } + } + + public componentWillUnmount() { + this.clearKubernetesObjectInterval() + this.clearKubernetesResourceInterval() + } + + public render() { + const {source, manualRefresh, timeRange} = this.props + const { + selectedNamespace, + selectedNode, + selectedLimit, + labelKey, + labelValue, + namespaces, + nodes, + limits, + proportions, + activeEditorTab, + script, + focuseNode, + pinNode, + isToolipActive, + targetPosition, + tooltipNode, + minions, + selectMinion, + isOpenMinions, + isDisabledMinions, + selectedAutoRefresh, + layouts, + } = this.state + + const layoutCells = getCells(layouts, source) + const tempVars = generateForHosts(source) + + return ( + <> + + + + ) + } + + private fillteredLayouts = async () => { + const {focuseNode} = this.state + const { + data: {layouts}, + } = await getLayouts() + + const {host, measurements} = await this.fetchHostsAndMeasurements(layouts) + + let findMeasurement = [] + if (focuseNode.type === 'Node') { + findMeasurement = [`kubernetes_node`] + } else if (focuseNode.type === 'Pod') { + findMeasurement = [`kubernetes_pod`] + } + + const focusedApp = 'kubernetes' + let filteredLayouts = _.filter(layouts, layout => { + return focusedApp + ? layout.app === focusedApp && + _.filter(findMeasurement, (m: string): boolean => + _.includes(layout.measurement, m) + ).length > 0 + : host.apps && + _.includes(host.apps, layout.app) && + _.includes(measurements, layout.measurement) + }).sort((x, y) => { + return x.measurement < y.measurement + ? -1 + : x.measurement > y.measurement + ? 1 + : 0 + }) + + const makeWhere = (where: string) => { + _.forEach(filteredLayouts, layout => { + _.forEach(layout.cells, cell => { + _.forEach(cell.queries, query => { + if (query['wheres']) { + query['wheres'].push(`"${where}"='${focuseNode.label}'`) + } else { + query['wheres'] = [] + query['wheres'].push(`"${where}"='${focuseNode.label}'`) + } + }) + }) + }) + } + + if (focuseNode.type === 'Node') { + makeWhere('node_name') + } else if (focuseNode.type === 'Pod') { + makeWhere('pod_name') + } + + return filteredLayouts + } + + private async fetchHostsAndMeasurements(layouts: Layout[]) { + const {source} = this.props + const fetchMeasurements = getMeasurementsForHost(source, '') + const fetchHosts = getAppsForHost( + source.links.proxy, + '', + layouts, + source.telegraf + ) + + const [host, measurements] = await Promise.all([ + fetchHosts, + fetchMeasurements, + ]) + + return {host, measurements} + } + + private clearKubernetesObjectInterval = () => { + window.clearTimeout(this.getKubernetesObjectInterval) + this.getKubernetesObjectInterval = null + } + + private clearKubernetesResourceInterval = () => { + window.clearTimeout(this.getKubernetesResourceInterval) + this.getKubernetesResourceInterval = null + } + + private handleKubernetesRefresh = async () => { + await this.getK8sObject() + } + + private handleKubernetesResourceRefresh = async () => { + await this.fetchK8sData() + } + + private debouncedHandleKubernetesRefresh = _.debounce( + this.handleKubernetesRefresh, + 500 + ) + + private handleKubernetesAutoRefresh = async () => { + const {selectMinion, selectedAutoRefresh} = this.state + + this.clearKubernetesObjectInterval() + if (selectMinion === null || selectedAutoRefresh === 0) return + + await this.getK8sObject() + this.getKubernetesObjectInterval = setTimeout(() => { + this.handleKubernetesAutoRefresh() + }, selectedAutoRefresh) + } + + private handleKubernetesResourceAutoRefresh = async () => { + const {autoRefresh} = this.props + this.clearKubernetesResourceInterval() + + if (autoRefresh === 0) return + await this.fetchK8sData() + this.getKubernetesResourceInterval = setTimeout(() => { + this.handleKubernetesResourceAutoRefresh() + }, autoRefresh) + } + + private handleChooseKubernetesAutoRefresh = ({ + milliseconds, + }: { + milliseconds: AutoRefreshOption['milliseconds'] + }) => { + this.setState({selectedAutoRefresh: milliseconds}) + } + + private onClickMinionsDropdown = async () => { + const {isOpenMinions, selectMinion} = this.state + + if (!isOpenMinions) { + this.setState({isDisabledMinions: true}) + const minions: string[] = _.uniq(await this.getMinionKeyAcceptedList()) + if (_.indexOf(minions, selectMinion) === -1) { + this.setState({selectMinion: null}) + } + + this.handleOpenMinionsDropdown() + this.setState({minions, isDisabledMinions: false}) + } else { + this.handleCloseMinionsDropdown() + } + } + + private handleOpenMinionsDropdown = () => { + this.setState({isOpenMinions: true}) + } + + private handleCloseMinionsDropdown = () => { + this.setState({isOpenMinions: false}) + } + + private onChooseNamespace = (namespace: {text: string}) => { + this.setState({ + selectedNamespace: namespace.text, + }) + } + + private onChooseNodes = (node: {text: string}) => { + this.setState({selectedNode: node.text}) + } + + private onChooseLimit = (limit: {text: string}) => { + this.setState({selectedLimit: limit.text}) + } + + private onChangeLabelKey = (e: ChangeEvent) => { + this.setState({labelKey: e.target.value}) + } + + private onChangeLabelValue = (e: ChangeEvent) => { + this.setState({labelValue: e.target.value}) + } + + private onSetActiveEditorTab = (activeEditorTab: string): void => { + this.setState({ + activeEditorTab, + }) + } + + private onClickFilter = (): void => { + const { + selectedNamespace, + selectedNode, + labelKey, + labelValue, + selectedLimit, + } = this.state + this.setState({ + filterNamespace: + selectedNamespace !== 'All namespaces' ? selectedNamespace : '', + filterNode: selectedNode !== 'All nodes' ? selectedNode : '', + filterLabelKey: labelKey !== 'Unlimited' ? labelKey : '', + filterLabelValue: labelValue, + filterLimit: selectedLimit, + }) + } + + private onClickVisualizePod = async (data: any) => { + const {selectMinion} = this.state + const focuseNodeName = _.get(data, 'data.name') + const focuseNodeLabel = _.get(data, 'data.label') + const focuseNodeType = _.get(data, 'data.type') + const focuseNamespace = _.get(data, 'data.namespace') + + const addon = this.props.addons.find(addon => { + return addon.name === AddonType.salt + }) + + const saltMasterUrl = addon.url + const saltMasterToken = addon.token + let pParam: SaltStack = {} + + pParam = k8sNodeTypeAttrs?.[focuseNodeType]?.saltParam + + if (pParam !== undefined) { + let kwarg = null + + if ( + k8sNodeTypeAttrs?.[focuseNodeType]?.saltParam.kwarg.hasOwnProperty( + 'namespace' + ) + ) { + kwarg = {namespace: focuseNamespace, name: focuseNodeLabel} + } else { + kwarg = {name: focuseNodeLabel} + } + pParam = { + ...k8sNodeTypeAttrs?.[focuseNodeType]?.saltParam, + kwarg, + } + } + + if (_.isEmpty(pParam)) return + + const k8sDetail = await this.props.handleGetK8sDetail( + saltMasterUrl, + saltMasterToken, + selectMinion, + pParam + ) + + const resultJson = JSON.parse( + JSON.stringify( + _.values(yaml.safeLoad(k8sDetail.data).return[0])[0], + this.jsonRemoveNull + ) + ) + + if (focuseNodeName) { + this.setState({ + focuseNode: { + name: focuseNodeName.replace(/\:/g, '\\:').replace(/\./g, '\\.'), + label: focuseNodeLabel, + type: k8sNodeTypeAttrs?.[focuseNodeType]?.name, + }, + script: resultJson, + }) + } + } + + private onDBClick = (data: any) => { + this.handlePinNode(data) + } + + private handlePinNode = (data: any) => { + if ( + data.depth === 3 || + (data.depth === 2 && + (data.data.type === 'CR' || data.data.type === 'CRB')) + ) { + const pinNode = this.parentNavigation(data) + const target = d3.select(`[data-name=${pinNode[0]}]`) + const isNull = _.isNull(_.flatMapDeep(target._groups)[0]) + const isPin = isNull || target.classed('kubernetes-pin') + this.setState({pinNode: isPin ? [] : pinNode}) + } else { + this.setState({pinNode: []}) + } + } + + private debouncedResizeTrigger = _.debounce(() => { + WindowResizeEventTrigger() + }, 250) + + private handleResize = (proportions: number[]) => { + this.setState({proportions}) + setLocalStorage('kubernetes', { + proportions, + }) + this.debouncedResizeTrigger() + } + + private handleOpenTooltip = (target: HTMLElement) => { + const {width, top, right, left} = target.getBoundingClientRect() + this.setState({ + isToolipActive: true, + targetPosition: {width, top, right, left}, + tooltipNode: { + name: target.getAttribute('data-label'), + cpu: parseInt(target.getAttribute('data-cpu')), + memory: parseInt(target.getAttribute('data-memory')), + }, + }) + } + + private handleCloseTooltip = () => { + this.setState({ + isToolipActive: false, + targetPosition: {top: null, right: null, left: null, width: null}, + }) + } + + private onChooseMinion = (minion: {text: string}) => { + this.setState({selectMinion: minion.text}) + } +} + +const mstp = ({links: {addons}, auth: {me}}) => { + const meRole = _.get(me, 'role', null) + return { + meRole, + addons, + } +} + +const mdtp = { + handleGetMinionKeyAcceptedList: getMinionKeyAcceptedListAsync, + handleGetNamespaces: getLocalK8sNamespacesAsync, + handleGetNodes: getLocalK8sNodesAsync, + handleGetPods: getLocalK8sPodsAsync, + handleGetDeployments: getLocalK8sDeploymentsAsync, + handleGetReplicaSets: getLocalK8sReplicaSetsAsync, + handleGetReplicationControllers: getLocalK8sReplicationControllersAsync, + handleGetDaemonSets: getLocalK8sDaemonSetsAsync, + handleGetStatefulSets: getLocalK8sStatefulSetsAsync, + handleGetCronJobs: getLocalK8sCronJobsAsync, + handleGetJobs: getLocalK8sJobsAsync, + handleGetServices: getLocalK8sServicesAsync, + handleGetIngresses: getLocalK8sIngressesAsync, + handleGetConfigmaps: getLocalK8sConfigmapsAsync, + handleGetSecrets: getLocalK8sSecretsAsync, + handleGetServiceAccounts: getLocalK8sServiceAccountsAsync, + handleGetClusterRoles: getLocalK8sClusterRolesAsync, + handleGetClusterRoleBindings: getLocalK8sClusterRoleBindingsAsync, + handleGetRoles: getLocalK8sRolesAsync, + handleGetRoleBindings: getLocalK8sRoleBindingsAsync, + handleGetPersistentVolumes: getLocalK8sPersistentVolumesAsync, + handleGetPersistentVolumeClaims: getLocalK8sPersistentVolumeClaimsAsync, + handleGetK8sDetail: getLocalK8sDetailAsync, + notify: notifyAction, +} + +export default connect(mstp, mdtp, null)(KubernetesPage) diff --git a/frontend/src/hosts/types/index.ts b/frontend/src/hosts/types/index.ts index dad77e23..8e02f85c 100644 --- a/frontend/src/hosts/types/index.ts +++ b/frontend/src/hosts/types/index.ts @@ -23,6 +23,19 @@ import { } from 'src/hosts/types/type' import {CloudServiceProvider} from 'src/hosts/types/cloud' +import { + Kubernetes, + KubernetesObject, + TooltipNode, + TooltipPosition, + FocuseNode, + KubernetesProps, + D3K8sData, + D3DataDepth1, + D3DataDepth2, + D3DataDepth3, +} from 'src/hosts/types/kubernetes' + export { VMRole, VCenter, @@ -43,5 +56,15 @@ export { handleSelectHostProps, vmParam, reducerVSphere, + TooltipNode, + TooltipPosition, + FocuseNode, + KubernetesProps, + D3K8sData, + D3DataDepth1, + D3DataDepth2, + D3DataDepth3, + Kubernetes, + KubernetesObject, CloudServiceProvider, } diff --git a/frontend/src/hosts/types/kubernetes.ts b/frontend/src/hosts/types/kubernetes.ts new file mode 100644 index 00000000..d6746206 --- /dev/null +++ b/frontend/src/hosts/types/kubernetes.ts @@ -0,0 +1,200 @@ +import {SaltStack} from 'src/types/saltstack' +export interface D3K8sData { + name: string + children: D3DataDepth1[] +} + +export interface D3DataDepth1 { + name: string + label: string + type: string + value?: number + children: D3DataDepth2[] +} + +export interface D3DataDepth2 { + name: string + label: string + type: string + owner?: string + child?: string + value?: number + children?: D3DataDepth3[] +} + +export interface D3DataDepth3 { + name: string + label: string + type: string + value: number + owner?: string + namespace?: string +} + +export interface Kubernetes { + name: string + type: string + cpu: string + memory: string +} +export interface KubernetesObject { + [key: string]: Kubernetes +} + +export interface FocuseNode { + name: string + label: string + type: string +} + +export interface TooltipPosition { + top: number + right: number + left: number + width: number +} + +export interface TooltipNode { + name: string + cpu: number + memory: number +} + +export interface KubernetesProps { + handleGetMinionKeyAcceptedList: ( + saltMasterUrl: string, + saltMasterToken: string + ) => Promise + handleGetNamespaces: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetNodes: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetPods: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetDeployments: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetReplicaSets: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetReplicationControllers: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetDaemonSets: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetStatefulSets: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetJobs: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetCronJobs: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetServices: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetIngresses: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetConfigmaps: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetSecrets: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetServiceAccounts: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetClusterRoles: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetClusterRoleBindings: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetRoles: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetRoleBindings: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetPersistentVolumes: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetPersistentVolumeClaims: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise + handleGetK8sDetail: ( + saltMasterUrl: string, + saltMasterToken: string, + targetMinion: string, + pParam?: SaltStack + ) => Promise +} diff --git a/frontend/src/hosts/utils/transUnit.ts b/frontend/src/hosts/utils/transUnit.ts new file mode 100644 index 00000000..c4ea59a2 --- /dev/null +++ b/frontend/src/hosts/utils/transUnit.ts @@ -0,0 +1,58 @@ +export const transMemoryToBytes = (bytes: string) => { + if (bytes === undefined || bytes === null) return 0 + + let result = 0 + + if (typeof bytes === 'string') { + const unit = bytes.substr(bytes.length - 2, bytes.length) + const value = bytes.substr(0, bytes.length - 2) + + switch (unit) { + case 'Ki': + return (result = parseFloat(value) * 1024) + case 'Mi': + return (result = parseFloat(value) * 1024 * 1024) + case 'Gi': + return (result = parseFloat(value) * 1024 * 1024 * 1024) + case 'Ti': + return (result = parseFloat(value) * 1024 * 1024 * 1024 * 1024) + case 'Pi': + return (result = parseFloat(value) * 1024 * 1024 * 1024 * 1024 * 1024) + case 'KB': + return (result = parseFloat(value) * 1024) + case 'MB': + return (result = parseFloat(value) * 1024 * 1024) + case 'GB': + return (result = parseFloat(value) * 1024 * 1024 * 1024) + case 'TB': + return (result = parseFloat(value) * 1024 * 1024 * 1024 * 1024) + case 'PB': + return (result = parseFloat(value) * 1024 * 1024 * 1024 * 1024 * 1024) + default: + return result + } + } + + return result +} + +export const transToCPUMillCore = (param: string, objKind: string) => { + if (param === undefined || param === null) return 0 + + let result = 0 + + if (typeof param === 'string') { + if (objKind === 'pod') { + const unit = param.substr(param.length - 1, param.length) + + result = + unit !== 'm' + ? parseFloat(param) * 1000 + : parseFloat(param.substr(0, param.length - 2)) + } else if (objKind === 'node') { + result = parseFloat(param) * 1000 + } + } + + return result +} diff --git a/frontend/src/reusable_ui/types/index.ts b/frontend/src/reusable_ui/types/index.ts index 347bcd63..c560884c 100644 --- a/frontend/src/reusable_ui/types/index.ts +++ b/frontend/src/reusable_ui/types/index.ts @@ -138,6 +138,7 @@ export enum IconFont { Star = 'star', Stop = 'stop', Zap = 'zap', + Filter = 'filter', Pin = 'pin', } diff --git a/frontend/src/shared/apis/saltStack.ts b/frontend/src/shared/apis/saltStack.ts index 42043e35..64b15602 100644 --- a/frontend/src/shared/apis/saltStack.ts +++ b/frontend/src/shared/apis/saltStack.ts @@ -41,6 +41,11 @@ interface Params { group_ids?: string | string[] volume_ids?: string | string[] instance_types?: string | string[] + detail?: any + namespace?: any + fieldselector?: any + labelselector?: any + limit?: number } username?: string password?: string @@ -76,8 +81,7 @@ const apiRequest = async ( 'Content-type': 'application/json', } - const param = JSON.stringify(Object.assign(dParams, pParams)) - + const param = Object.assign(dParams, pParams) const ajaxResult = await AJAX({ method: 'POST', url: url, @@ -113,7 +117,7 @@ const apiRequestMulti = async ( 'Content-type': 'application/json', } - const param = JSON.stringify(pParams) + const param = pParams const ajaxResult = await AJAX({ method: 'POST', @@ -248,81 +252,6 @@ export async function getWheelKeyListAll(pUrl: string, pToken: string) { } } -export async function getWheelKeyAcceptedList(pUrl: string, pToken: string) { - try { - const params = { - eauth: 'pam', - client: 'wheel', - fun: 'key.list', - match: 'accepted', - } - return await apiRequest(pUrl, pToken, params, 'application/x-yaml') - } catch (error) { - console.error(error) - throw error - } -} - -export async function getLocalVSphereInfoAll( - pUrl: string, - pToken: string, - tgt: string, - address: string, - user: string, - password: string, - port: string, - protocol: string -) { - try { - const params = { - token: pToken, - eauth: 'pam', - client: 'local', - fun: 'vsphere.vsphere_info_all', - tgt: tgt, - kwarg: { - host: address, - username: user, - password, - port, - protocol, - }, - } - return await apiRequest(pUrl, pToken, params, 'application/x-yaml') - } catch (error) { - console.error(error) - throw error - } -} - -export async function getTicketRemoteConsole( - pUrl: string, - pToken: string, - tgt: string, - address: string, - user: string, - password: string -) { - try { - const params = { - token: pToken, - eauth: 'pam', - client: 'local', - fun: 'vsphere.get_ticket', - tgt: tgt, - kwarg: { - host: address, - username: user, - password: password, - }, - } - return await apiRequest(pUrl, pToken, params, 'application/x-yaml') - } catch (error) { - console.error(error) - throw error - } -} - export async function getRunnerManageAllowed(pUrl: string, pToken: string) { try { const params = { @@ -860,125 +789,156 @@ export async function getLocalHttpQuery( } } -export async function getIpmiGetPower( +export async function getWheelKeyAcceptedList(pUrl: string, pToken: string) { + try { + const params = { + eauth: 'pam', + client: 'wheel', + fun: 'key.list', + match: 'accepted', + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalVSphereInfoAll( pUrl: string, pToken: string, - pIpmis: IpmiCell[] + tgt: string, + address: string, + user: string, + password: string, + port: string, + protocol: string ) { try { - let params = [] - - _.map(pIpmis, pIpmi => { - const param = { - token: pToken, - eauth: 'pam', - client: 'local', - fun: 'ipmi.get_power', - tgt_type: 'glob', - tgt: pIpmi.target, - kwarg: { - api_host: pIpmi.host, - api_user: pIpmi.user, - api_pass: pIpmi.pass, - }, - } - params = [...params, param] - }) - - const result = await apiRequestMulti(pUrl, params, 'application/x-yaml') - - return result + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'vsphere.vsphere_info_all', + tgt: tgt, + kwarg: { + host: address, + username: user, + password, + port, + protocol, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') } catch (error) { console.error(error) throw error } } -export enum IpmiSetPowerStatus { - PowerOn = 'power_on', - PowerOff = 'power_off', - Reset = 'reset', - Shutdown = 'shutdown', +export async function getTicketRemoteConsole( + pUrl: string, + pToken: string, + tgt: string, + address: string, + user: string, + password: string +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'vsphere.get_ticket', + tgt: tgt, + kwarg: { + host: address, + username: user, + password: password, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } } -export async function setIpmiSetPower( +export async function getLocalK8sNamespaces( pUrl: string, pToken: string, - pIpmi: Ipmi, - pState: IpmiSetPowerStatus + pMinionId: string, + pParam: Params ) { try { const params = { + token: pToken, eauth: 'pam', client: 'local', - fun: 'ipmi.set_power', - tgt_type: 'glob', - tgt: pIpmi.target, + fun: 'kubernetes.namespaces', + tgt: pMinionId, kwarg: { - state: pState, - api_host: pIpmi.host, - api_user: pIpmi.user, - api_pass: pIpmi.pass, + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, }, } - - const result = await apiRequest(pUrl, pToken, params, 'application/x-yaml') - - return result + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') } catch (error) { console.error(error) throw error } } -export async function getIpmiGetSensorData( +export async function getLocalK8sNodes( pUrl: string, pToken: string, - pIpmi: Ipmi + pMinionId: string, + pParam: Params ) { try { const params = { + token: pToken, eauth: 'pam', client: 'local', - fun: 'ipmi.get_sensor_data', - tgt_type: 'glob', - tgt: pIpmi.target, + fun: 'kubernetes.nodes', + tgt: pMinionId, kwarg: { - api_host: pIpmi.host, - api_user: pIpmi.user, - api_pass: pIpmi.pass, + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, }, } - - const result = await apiRequest(pUrl, pToken, params, 'application/x-yaml') - - return result + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') } catch (error) { console.error(error) throw error } } - -export async function getLocalBotoEc2DescribeInstances( +export async function getIpmiGetPower( pUrl: string, pToken: string, - pCSPs: any[] -): Promise { + pIpmis: IpmiCell[] +) { try { let params = [] - _.map(pCSPs, pCSP => { + _.map(pIpmis, pIpmi => { const param = { token: pToken, eauth: 'pam', client: 'local', - fun: 'boto_ec2.describe_instances', + fun: 'ipmi.get_power', tgt_type: 'glob', - tgt: pCSP.minion, + tgt: pIpmi.target, kwarg: { - region: pCSP.region, - keyid: pCSP.accesskey, - key: pCSP.secretkey, + api_host: pIpmi.host, + api_user: pIpmi.user, + api_pass: pIpmi.pass, }, } params = [...params, param] @@ -993,54 +953,797 @@ export async function getLocalBotoEc2DescribeInstances( } } -export async function getLocalBotoSecgroupGetAllSecurityGroups( +export async function getLocalK8sPods( pUrl: string, pToken: string, - pCSP: any, - pGroupIds?: string[] -): Promise { + pMinionId: string, + pParam: Params +) { try { - const param = { + const params = { token: pToken, eauth: 'pam', client: 'local', - fun: 'boto_secgroup.get_all_security_groups', - tgt_type: 'glob', - tgt: pCSP.minion, + fun: 'kubernetes.pods', + tgt: pMinionId, kwarg: { - region: pCSP.region, - keyid: pCSP.accesskey, - key: pCSP.secretkey, - group_ids: pGroupIds, + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + fieldselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('fieldselector') + ? pParam.kwarg.fieldselector + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + limit: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('limit') + ? pParam.kwarg.limit + : null + : null, }, } - - const result = await apiRequest(pUrl, pToken, param, 'application/x-yaml') - - return result + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') } catch (error) { console.error(error) throw error } } -export async function getLocalBoto2DescribeVolumes( +export async function getLocalK8sDeployments( pUrl: string, pToken: string, - pCSP: any, - pVolumeIds?: string[] -): Promise { + pMinionId: string, + pParam: Params +) { try { - const param = { + const params = { token: pToken, eauth: 'pam', client: 'local', - fun: 'boto_ec2.describe_volumes', - tgt_type: 'glob', - tgt: pCSP.minion, + fun: 'kubernetes.deployments', + tgt: pMinionId, kwarg: { - region: pCSP.region, - keyid: pCSP.accesskey, + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sReplicaSets( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.replica_sets', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sReplicationControllers( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.replication_controllers', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export enum IpmiSetPowerStatus { + PowerOn = 'power_on', + PowerOff = 'power_off', + Reset = 'reset', + Shutdown = 'shutdown', +} + +export async function setIpmiSetPower( + pUrl: string, + pToken: string, + pIpmi: Ipmi, + pState: IpmiSetPowerStatus +) { + try { + const params = { + eauth: 'pam', + client: 'local', + fun: 'ipmi.set_power', + tgt_type: 'glob', + tgt: pIpmi.target, + kwarg: { + state: pState, + api_host: pIpmi.host, + api_user: pIpmi.user, + api_pass: pIpmi.pass, + }, + } + + const result = await apiRequest(pUrl, pToken, params, 'application/x-yaml') + + return result + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sDaemonSets( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.daemon_sets', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sStatefulSets( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.stateful_sets', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sJobs( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.jobs', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getIpmiGetSensorData( + pUrl: string, + pToken: string, + pIpmi: Ipmi +) { + try { + const params = { + eauth: 'pam', + client: 'local', + fun: 'ipmi.get_sensor_data', + tgt_type: 'glob', + tgt: pIpmi.target, + kwarg: { + api_host: pIpmi.host, + api_user: pIpmi.user, + api_pass: pIpmi.pass, + }, + } + + const result = await apiRequest(pUrl, pToken, params, 'application/x-yaml') + + return result + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sCronJobs( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.cron_jobs', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sServices( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.services', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sIngresses( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.ingresses', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sConfigmaps( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.configmaps', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalBotoEc2DescribeInstances( + pUrl: string, + pToken: string, + pCSPs: any[] +): Promise { + try { + let params = [] + + _.map(pCSPs, pCSP => { + const param = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'boto_ec2.describe_instances', + tgt_type: 'glob', + tgt: pCSP.minion, + kwarg: { + region: pCSP.region, + keyid: pCSP.accesskey, + key: pCSP.secretkey, + }, + } + params = [...params, param] + }) + + const result = await apiRequestMulti(pUrl, params, 'application/x-yaml') + + return result + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sSecrets( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.secrets', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sServiceAccounts( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.service_accounts', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sClusterRoles( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.cluster_roles', + tgt: pMinionId, + kwarg: { + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} +export async function getLocalBotoSecgroupGetAllSecurityGroups( + pUrl: string, + pToken: string, + pCSP: any, + pGroupIds?: string[] +): Promise { + try { + const param = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'boto_secgroup.get_all_security_groups', + tgt_type: 'glob', + tgt: pCSP.minion, + kwarg: { + region: pCSP.region, + keyid: pCSP.accesskey, + key: pCSP.secretkey, + group_ids: pGroupIds, + }, + } + + const result = await apiRequest(pUrl, pToken, param, 'application/x-yaml') + + return result + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sClusterRoleBindings( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.cluster_role_bindings', + tgt: pMinionId, + kwarg: { + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sRoles( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.roles', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sRoleBindings( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.role_bindings', + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} +export async function getLocalBoto2DescribeVolumes( + pUrl: string, + pToken: string, + pCSP: any, + pVolumeIds?: string[] +): Promise { + try { + const param = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'boto_ec2.describe_volumes', + tgt_type: 'glob', + tgt: pCSP.minion, + kwarg: { + region: pCSP.region, + keyid: pCSP.accesskey, key: pCSP.secretkey, volume_ids: pVolumeIds, }, @@ -1055,6 +1758,39 @@ export async function getLocalBoto2DescribeVolumes( } } +export async function getLocalK8sPersistentVolumes( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.persistent_volumes', + tgt: pMinionId, + kwarg: { + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + export async function getLocalBoto2DescribeInstanceTypes( pUrl: string, pToken: string, @@ -1086,6 +1822,72 @@ export async function getLocalBoto2DescribeInstanceTypes( } } +export async function getLocalK8sPersistentVolumeClaims( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: 'kubernetes.persistent_volume_claims', + tgt: pMinionId, + kwarg: { + labelselector: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('labelselector') + ? pParam.kwarg.labelselector + : '' + : '', + detail: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('detail') + ? pParam.kwarg.detail + : false + : false, + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + +export async function getLocalK8sDetail( + pUrl: string, + pToken: string, + pMinionId: string, + pParam: Params +) { + try { + const params = { + token: pToken, + eauth: 'pam', + client: 'local', + fun: pParam.fun, + tgt: pMinionId, + kwarg: { + namespace: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('namespace') + ? pParam.kwarg.namespace + : '' + : '', + name: pParam.hasOwnProperty('kwarg') + ? pParam.kwarg.hasOwnProperty('name') + ? pParam.kwarg.name + : '' + : '', + }, + } + return await apiRequest(pUrl, pToken, params, 'application/x-yaml') + } catch (error) { + console.error(error) + throw error + } +} + const saltActivityLog = async ( activity: object, result: object diff --git a/frontend/src/shared/components/Shell.scss b/frontend/src/shared/components/Shell.scss index aa0f7241..ec282e14 100644 --- a/frontend/src/shared/components/Shell.scss +++ b/frontend/src/shared/components/Shell.scss @@ -54,8 +54,8 @@ $overlay-min-height: 150px; .react-tabs__tab { border: 1px solid $g5-pepper; - background-color: #202028; - color: #999dab; + background-color: $g2-kevlar; + color: $g11-sidewalk; margin-bottom: 0px; &.react-tabs__tab--selected { background-color: $g5-pepper; @@ -82,7 +82,7 @@ $overlay-min-height: 150px; } .react-tabs__tab--remove { - color: #999dab; + color: $g11-sidewalk; background-color: unset; border-color: transparent; } diff --git a/frontend/src/shared/components/dropdown_auto_refresh/AutoRefreshDropdown.tsx b/frontend/src/shared/components/dropdown_auto_refresh/AutoRefreshDropdown.tsx index c4923189..34df3c94 100644 --- a/frontend/src/shared/components/dropdown_auto_refresh/AutoRefreshDropdown.tsx +++ b/frontend/src/shared/components/dropdown_auto_refresh/AutoRefreshDropdown.tsx @@ -20,6 +20,7 @@ interface Props { onChoose: (autoRefreshOption: AutoRefreshOption) => void showManualRefresh?: boolean onManualRefresh?: () => void + userAutoRefreshOptions?: AutoRefreshOption[] } @ErrorHandling diff --git a/frontend/src/shared/constants/codeMirrorModes.ts b/frontend/src/shared/constants/codeMirrorModes.ts index e4b6722d..fb49709a 100644 --- a/frontend/src/shared/constants/codeMirrorModes.ts +++ b/frontend/src/shared/constants/codeMirrorModes.ts @@ -454,6 +454,68 @@ export const modeAgentConf = { ], } +export const modeYaml = { + // The start state contains the rules that are intially used + start: [ + { + regex: /\>\-\n\s*/, + token: 'literal-block', + }, + { + regex: /^\s*[\$A-Za-z0-9_-]+\:/, + token: 'key', + }, + { + regex: /^\s-\s*[\$A-Za-z0-9_-]+\:/, + token: 'array', + }, + { + regex: /(\{|\})/, + token: 'object-bracelet', + }, + { + regex: /[\:\s](\d+$)/, + token: 'number', + }, + { + regex: /[^:\s]\d+\.\d+\.\d+\.\d+$/, + token: 'ip', + }, + { + regex: / (y|yes|n|no|true|false|on|off|'true'|'false'|'True'|'False'|'TRUE'|'FALSE')$/, + token: 'boolean', + }, + { + regex: /\[" "\]" ":\s+[|>]" "^\s*- /, + token: 'array', + }, + { + regex: /(^| )!!(binary|bool|float|int|map|null|omap|seq|set|str) /, + token: 'reserved', + }, + { + regex: /#.*$/, + token: 'comment', + }, + { + regex: /['\"][^['\"]]*$/, + token: 'none-closed-quote', + }, + { + regex: /['\"].*['\"]/, + token: 'closed-quote', + }, + { + regex: /:( |$)/, + token: 'equal-sign', + }, + { + regex: /[\:\s](null|undifined)/, + token: 'null', + }, + ], +} + export const modeLogger = { // The start state contains the rules that are intially used start: [ diff --git a/frontend/src/style/fonts/icomoon.eot b/frontend/src/style/fonts/icomoon.eot index 379996f0..67a062c5 100644 Binary files a/frontend/src/style/fonts/icomoon.eot and b/frontend/src/style/fonts/icomoon.eot differ diff --git a/frontend/src/style/fonts/icomoon.svg b/frontend/src/style/fonts/icomoon.svg index 071189f7..27ac90cf 100644 --- a/frontend/src/style/fonts/icomoon.svg +++ b/frontend/src/style/fonts/icomoon.svg @@ -7,101 +7,101 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/style/fonts/icomoon.ttf b/frontend/src/style/fonts/icomoon.ttf index 176a72e8..bf33d215 100644 Binary files a/frontend/src/style/fonts/icomoon.ttf and b/frontend/src/style/fonts/icomoon.ttf differ diff --git a/frontend/src/style/fonts/icomoon.woff b/frontend/src/style/fonts/icomoon.woff index 3792e27e..4dbb1ecf 100644 Binary files a/frontend/src/style/fonts/icomoon.woff and b/frontend/src/style/fonts/icomoon.woff differ diff --git a/frontend/src/style/fonts/icomoon.woff2 b/frontend/src/style/fonts/icomoon.woff2 index 25f1bad3..cf099a81 100644 Binary files a/frontend/src/style/fonts/icomoon.woff2 and b/frontend/src/style/fonts/icomoon.woff2 differ diff --git a/frontend/src/style/fonts/icon-font.scss b/frontend/src/style/fonts/icon-font.scss index 06d5fac9..1d0ee31e 100644 --- a/frontend/src/style/fonts/icon-font.scss +++ b/frontend/src/style/fonts/icon-font.scss @@ -314,6 +314,14 @@ content: '\e933'; } + &.copy:before { + content: '\e934'; + } + + &.paste:before { + content: '\e935'; + } + &._snet--logo:before { content: '\e958'; } @@ -345,12 +353,8 @@ content: '\e965'; } - &.copy:before { - content: '\e934'; - } - - &.paste:before { - content: '\e935'; + &.pin:before { + content: '\e966'; } &.undo:before { @@ -388,9 +392,7 @@ &.scissors:before { content: '\ea5a'; } - &.pin:before { - content: '\e966'; - } + &.fit:before { content: '\ea5b'; } diff --git a/frontend/src/types/saltstack.ts b/frontend/src/types/saltstack.ts new file mode 100644 index 00000000..bc4539e9 --- /dev/null +++ b/frontend/src/types/saltstack.ts @@ -0,0 +1,36 @@ +export interface SaltStack { + client?: string + fun?: string + arg?: string[] | string + tgt_type?: string + tgt?: string[] | string + match?: string + include_rejected?: string + include_denied?: string + include_accepted?: string + show_ip?: string + kwarg?: { + username?: string + password?: string + eauth?: string + name?: string + path?: string + dest?: string + makedirs?: string + fun?: string + cmd?: string + sources?: string + args?: string[] | string + url?: string + method?: string + namespace?: string + fieldselector?: string + labelselector?: string + detail?: boolean + limit?: number + } + username?: string + password?: string + eauth?: string + token_expire?: number +} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index d8c728fb..586f68d4 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3421,15 +3421,10 @@ caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001048.tgz#4f431c688cb6bc0435994dcbb8e565d004869d43" integrity sha512-HqYsBIZlVARU5GDXPziXSFwFVpGx9KqCznr62iaey7bT2sqpx7/jI4B3PvbKguKi8kGeEannJ7WEPB5H71rjFQ== -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043: - version "1.0.30001208" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz" - integrity sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA== - -caniuse-lite@^1.0.30001208: - version "1.0.30001214" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001214.tgz#70f153c78223515c6d37a9fde6cd69250da9d872" - integrity sha512-O2/SCpuaU3eASWVaesQirZv1MSjUNOvmugaD8zNSJqw6Vv5SGwoOpA9LJs3pNPfM745nxqPvfZY3MQKY4AKHYg== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001208: + version "1.0.30001286" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001286.tgz" + integrity sha512-zaEMRH6xg8ESMi2eQ3R4eZ5qw/hJiVsO/HlLwniIwErij0JDr9P+8V4dtx1l+kLq6j3yy8l8W4fst1lBnat5wQ== capture-exit@^2.0.0: version "2.0.0"