├── .prettierignore ├── .DS_Store ├── .dockerignore ├── client ├── navlogo.png ├── styles │ ├── _variables.scss │ ├── app.scss │ └── normalize.css ├── pages │ ├── pages.scss │ ├── Home.tsx │ ├── Pods.tsx │ └── Graph.tsx ├── index.html ├── components │ ├── Dashboard │ │ ├── Dashboard.scss │ │ └── Dashboard.tsx │ ├── NodeGraph │ │ ├── NodeModal.scss │ │ ├── NodeModal.tsx │ │ ├── NodeGraph.scss │ │ ├── utils.ts │ │ └── NodeGraph.tsx │ ├── Header │ │ ├── Header.tsx │ │ └── Header.scss │ └── Sidebar │ │ ├── Sidebar.scss │ │ └── Sidebar.tsx ├── index.tsx └── App.tsx ├── assets ├── Klusterview.png ├── LI-In-Bug.png ├── github-mark.png ├── headerLogo.png ├── world-wide-web.png ├── repository-open-graph-template.png └── github-mark.svg ├── deployment ├── .DS_Store ├── _dashboards │ └── .DS_Store ├── klusterview_chart_source │ ├── templates │ │ ├── namespace.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ ├── tests │ │ │ └── test-connection.yaml │ │ ├── hpa.yaml │ │ ├── deployment.yaml │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ └── ingress.yaml │ ├── charts │ │ └── grafana │ │ │ ├── charts │ │ │ └── prometheus │ │ │ │ ├── charts │ │ │ │ └── kube-state-metrics │ │ │ │ │ ├── values.yaml │ │ │ │ │ ├── templates │ │ │ │ │ ├── service-account.yaml │ │ │ │ │ ├── tests │ │ │ │ │ │ └── test-connection.yaml │ │ │ │ │ ├── ksm-cluster-role-binding.yaml │ │ │ │ │ ├── ksm-service.yaml │ │ │ │ │ ├── ksm-deployment.yaml │ │ │ │ │ ├── _helpers.tpl │ │ │ │ │ └── ksm-cluster-role.yaml │ │ │ │ │ ├── .helmignore │ │ │ │ │ └── Chart.yaml │ │ │ │ ├── templates │ │ │ │ ├── prometheus-pvc.yaml │ │ │ │ ├── clusterRoleBinding.yaml │ │ │ │ ├── serviceaccount.yaml │ │ │ │ ├── service.yaml │ │ │ │ ├── tests │ │ │ │ │ └── test-connection.yaml │ │ │ │ ├── clusterRole.yaml │ │ │ │ ├── hpa.yaml │ │ │ │ ├── deployment.yaml │ │ │ │ ├── NOTES.txt │ │ │ │ ├── _helpers.tpl │ │ │ │ ├── ingress.yaml │ │ │ │ └── config-map.yaml │ │ │ │ ├── .helmignore │ │ │ │ ├── Chart.yaml │ │ │ │ └── values.yaml │ │ │ ├── templates │ │ │ ├── grafana-pvc.yaml │ │ │ ├── serviceaccount.yaml │ │ │ ├── service.yaml │ │ │ ├── tests │ │ │ │ └── test-connection.yaml │ │ │ ├── grafana-datasource-config.yaml │ │ │ ├── hpa.yaml │ │ │ ├── NOTES.txt │ │ │ ├── _helpers.tpl │ │ │ ├── ingress.yaml │ │ │ └── deployment.yaml │ │ │ ├── .helmignore │ │ │ ├── Chart.yaml │ │ │ └── values.yaml │ ├── Chart.yaml │ ├── .helmignore │ └── values.yaml └── _manual_install │ ├── prometheus │ ├── prometheus-pvc.yaml │ ├── prometheus-service.yaml │ ├── clusterRole.yaml │ ├── prometheus-deployment.yaml │ └── config-map.yaml │ ├── grafana │ ├── grafana-pvc.yaml │ ├── grafana_service.yaml │ ├── grafana-datasource-config.yaml │ └── grafana_deployment.yaml │ ├── kube_state_metrics │ ├── service-account.yaml │ ├── ksm-cluster-role-binding.yaml │ ├── ksm-service.yaml │ ├── ksm-deployment.yaml │ └── ksm-cluster-role.yaml │ └── klusterview-manifest.yaml ├── ECRI-40-OSP-4.code-workspace ├── server ├── models │ └── k8_api.ts ├── routes │ ├── statusRouter.ts │ ├── grafanaRouter.ts │ └── promRouter.ts ├── types.ts ├── controllers │ ├── grafanaController.ts │ ├── helpers │ │ ├── statusController.ts │ │ └── statusHelpers.ts │ ├── promController.ts │ └── initializationController.ts └── server.ts ├── docs └── charts │ ├── klusterview-0.1.0.tgz │ ├── index.yaml │ └── klusterview-0.1.0.tgz.prov ├── babel.config.js ├── Dockerfile ├── Dockerfile-dev ├── .gitignore ├── dist ├── index.html └── bundle.js.LICENSE.txt ├── tsconfig.json ├── scripts ├── create_test_pod.sh └── setup.sh ├── .eslintrc ├── LICENSE.md ├── package.json ├── webpack.config.js ├── collateral └── Medium.md ├── __tests__ ├── frontend-react.test.jsx └── supertest.test.ts └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | deployment/klusterview/* -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KlusterView/HEAD/.DS_Store -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .git 3 | npm-debug.log 4 | Dockerfile 5 | .devcontainer -------------------------------------------------------------------------------- /client/navlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KlusterView/HEAD/client/navlogo.png -------------------------------------------------------------------------------- /client/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | $primary-color: rgb(44, 118, 126); 2 | $secondary-color: cyan; 3 | -------------------------------------------------------------------------------- /assets/Klusterview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KlusterView/HEAD/assets/Klusterview.png -------------------------------------------------------------------------------- /assets/LI-In-Bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KlusterView/HEAD/assets/LI-In-Bug.png -------------------------------------------------------------------------------- /assets/github-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KlusterView/HEAD/assets/github-mark.png -------------------------------------------------------------------------------- /assets/headerLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KlusterView/HEAD/assets/headerLogo.png -------------------------------------------------------------------------------- /deployment/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KlusterView/HEAD/deployment/.DS_Store -------------------------------------------------------------------------------- /assets/world-wide-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KlusterView/HEAD/assets/world-wide-web.png -------------------------------------------------------------------------------- /ECRI-40-OSP-4.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /deployment/_dashboards/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KlusterView/HEAD/deployment/_dashboards/.DS_Store -------------------------------------------------------------------------------- /server/models/k8_api.ts: -------------------------------------------------------------------------------- 1 | import * as k8 from '@kubernetes/client-node'; 2 | 3 | const config = new k8.KubeConfig(); 4 | -------------------------------------------------------------------------------- /docs/charts/klusterview-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KlusterView/HEAD/docs/charts/klusterview-0.1.0.tgz -------------------------------------------------------------------------------- /assets/repository-open-graph-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KlusterView/HEAD/assets/repository-open-graph-template.png -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/templates/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: {{ .Values.global.namespace }} -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', {targets: {node: 'current'}}], 4 | '@babel/preset-typescript', 5 | ], 6 | }; -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.13-alpine 2 | WORKDIR /app 3 | COPY package.json ./ 4 | RUN npm install 5 | COPY ./ ./ 6 | RUN npm run build 7 | EXPOSE 3000 8 | ENTRYPOINT ["npm", "start"] -------------------------------------------------------------------------------- /Dockerfile-dev: -------------------------------------------------------------------------------- 1 | FROM node:18.13-alpine 2 | WORKDIR /app 3 | COPY package.json ./ 4 | RUN npm install 5 | COPY ./ ./ 6 | EXPOSE 3000 7 | EXPOSE 8080 8 | ENTRYPOINT ["npm", "run", "dev"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | package-lock.json 4 | prom_grafana_setup 5 | _temp_dev_files 6 | .dsStore 7 | **/.dsStore 8 | deployment/_temp 9 | **/_deprecated 10 | .devcontainer 11 | *.code-workspace 12 | _util_scripts 13 | .prettierignore 14 | coverage/* 15 | collateral 16 | -------------------------------------------------------------------------------- /deployment/_manual_install/prometheus/prometheus-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: prometheus-pvc 5 | spec: 6 | resources: 7 | requests: 8 | storage: 512M 9 | volumeMode: Filesystem 10 | accessModes: 11 | - ReadWriteOnce 12 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/charts/kube-state-metrics/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for kube-state-metrics. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | service: 6 | type: ClusterIP 7 | port: 80 8 | 9 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | K8 TBD
-------------------------------------------------------------------------------- /deployment/_manual_install/grafana/grafana-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: grafana-pvc 5 | namespace: monitoring-kv 6 | spec: 7 | resources: 8 | requests: 9 | storage: 500M 10 | volumeMode: Filesystem 11 | accessModes: 12 | - ReadWriteOnce 13 | -------------------------------------------------------------------------------- /client/pages/pages.scss: -------------------------------------------------------------------------------- 1 | @use '../styles/variables' as *; 2 | 3 | .page { 4 | height: calc(100vh - 10.25rem); 5 | width: calc(100vw - 17rem); 6 | margin-left: 15rem; 7 | background: transparent; 8 | font-size: 2rem; 9 | color: $primary-color; 10 | } 11 | 12 | .page-title { 13 | margin: 0.5rem 0 0.5rem 4rem; 14 | } 15 | -------------------------------------------------------------------------------- /client/styles/app.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 62.5%; 3 | } 4 | 5 | body { 6 | font-size: 1.6rem; 7 | background: linear-gradient(to top left, #414141, rgb(0, 0, 0)); 8 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 9 | Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 10 | } 11 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/templates/grafana-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: grafana-pvc 5 | namespace: {{ .Values.global.namespace }} 6 | spec: 7 | resources: 8 | requests: 9 | storage: 500M 10 | volumeMode: Filesystem 11 | accessModes: 12 | - ReadWriteOnce 13 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | KlusterView 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["server/**/*", "client/**/*"], 3 | "compilerOptions": { 4 | "types": ["node", "jest"], 5 | "esModuleInterop": true, 6 | "jsx": "react", 7 | "target": "ES6", 8 | "module": "CommonJS", 9 | "moduleResolution": "node", 10 | "sourceMap": true 11 | }, 12 | "ts-node": { 13 | "esm": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/components/Dashboard/Dashboard.scss: -------------------------------------------------------------------------------- 1 | @use '../../styles/variables' as *; 2 | 3 | .dashboard-container { 4 | width: 100%; 5 | height: 90%; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | } 10 | 11 | .dashboard { 12 | width: 95%; 13 | height: 95%; 14 | border-color: $secondary-color; 15 | border-radius: 1.5rem; 16 | } 17 | -------------------------------------------------------------------------------- /deployment/_manual_install/kube_state_metrics/service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | automountServiceAccountToken: false 3 | kind: ServiceAccount 4 | metadata: 5 | labels: 6 | app.kubernetes.io/component: exporter 7 | app.kubernetes.io/name: kube-state-metrics 8 | app.kubernetes.io/version: 2.3.0 9 | name: kube-state-metrics 10 | namespace: kube-system 11 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/prometheus-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: prometheus-pvc 5 | namespace: {{ .Values.global.namespace }} 6 | spec: 7 | resources: 8 | requests: 9 | storage: 512M 10 | volumeMode: Filesystem 11 | accessModes: 12 | - ReadWriteOnce 13 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: klusterview 3 | description: A metrics visualizer for Kubernetes, using custom installations of Prometheus and Grafana 4 | type: application 5 | version: 0.1.0 6 | appVersion: '1.0.0' 7 | dependencies: 8 | - name: grafana 9 | maintainers: 10 | - email: kyle.slugg@gmail.com 11 | name: Kyle Slugg-Urbino 12 | -------------------------------------------------------------------------------- /deployment/_manual_install/grafana/grafana_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grafana 5 | namespace: monitoring-kv 6 | annotations: 7 | prometheus.io/scrape: 'true' 8 | prometheus.io/port: '3000' 9 | spec: 10 | selector: 11 | app: grafana 12 | type: NodePort 13 | ports: 14 | - port: 3000 15 | targetPort: 3000 16 | nodePort: 32000 17 | -------------------------------------------------------------------------------- /scripts/create_test_pod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | kubectl create deployment kuard --image=gcr.io/kuar-demo/kuard-amd64:blue 4 | kubectl expose deployment kuard --type=NodePort --port=8080 5 | kubectl wait --for=condition=Ready pod/$(kubectl get pod -l app=kuard -n monitoring-kv -o jsonpath="{.items[0].metadata.name}") -n monitoring-kv 6 | kubectl port-forward service/kuard 8080:8080 & 7 | 8 | #Write process ID to file 9 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/clusterRoleBinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: prometheus 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: prometheus 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: {{ .Values.global.namespace }} -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: klusterview 5 | namespace: {{ .Values.global.namespace }} 6 | spec: 7 | selector: 8 | app: klusterview 9 | type: {{ .Values.service.type }} 10 | ports: 11 | - name: prod 12 | port: {{ .Values.service.port }} 13 | targetPort: 3000 14 | nodePort: 31001 15 | protocol: TCP 16 | -------------------------------------------------------------------------------- /deployment/_manual_install/prometheus/prometheus-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: prometheus 5 | namespace: monitoring-kv 6 | annotations: 7 | prometheus.io/scrape: 'true' 8 | prometheus.io/port: '9090' 9 | 10 | spec: 11 | selector: 12 | app: prometheus-server 13 | type: NodePort 14 | ports: 15 | - port: 8080 16 | targetPort: 9090 17 | nodePort: 30000 18 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/charts/kube-state-metrics/templates/service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | automountServiceAccountToken: false 3 | kind: ServiceAccount 4 | metadata: 5 | labels: 6 | app.kubernetes.io/component: exporter 7 | app.kubernetes.io/name: kube-state-metrics 8 | app.kubernetes.io/version: 2.3.0 9 | name: kube-state-metrics 10 | namespace: kube-system 11 | -------------------------------------------------------------------------------- /client/components/NodeGraph/NodeModal.scss: -------------------------------------------------------------------------------- 1 | @use '../../styles/variables' as *; 2 | 3 | .nodeModal { 4 | z-index: 1200; 5 | position: absolute; 6 | width: 300px; 7 | height: 175px; 8 | left: 120px; 9 | top: 50px; 10 | border: 1px solid $secondary-color; 11 | border-radius: 5px; 12 | 13 | li { 14 | list-style: none; 15 | } 16 | 17 | text { 18 | color: white; 19 | margin-right: 5px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "klusterview.serviceAccountName" . }} 6 | labels: 7 | {{- include "klusterview.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "grafana.serviceAccountName" . }} 6 | labels: 7 | {{- include "grafana.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /client/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import Dashboard from '../components/Dashboard/Dashboard'; 3 | import './pages.scss'; 4 | 5 | interface HomeProps { 6 | url: string; 7 | } 8 | 9 | const Home: FC = ({ url }) => { 10 | return ( 11 |
12 |

Kluster Metrics

13 | 14 |
15 | ); 16 | }; 17 | 18 | export default Home; 19 | -------------------------------------------------------------------------------- /client/components/Dashboard/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import './Dashboard.scss'; 3 | 4 | interface DashboardProps { 5 | url: string; 6 | } 7 | 8 | const Dashboard: FC = ({ url }) => { 9 | console.log('url dashboard', url); 10 | 11 | return ( 12 |
13 | 14 |
15 | ); 16 | }; 17 | 18 | export default Dashboard; 19 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "prometheus.serviceAccountName" . }} 6 | labels: 7 | {{- include "prometheus.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grafana 5 | namespace: {{ .Values.global.namespace }} 6 | annotations: 7 | prometheus.io/scrape: 'true' 8 | prometheus.io/port: '3000' 9 | spec: 10 | selector: 11 | app: grafana 12 | type: {{ .Values.service.type }} 13 | ports: 14 | - port: {{ .Values.service.port }} 15 | targetPort: 3000 16 | nodePort: 32000 17 | 18 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: prometheus 5 | namespace: {{ .Values.global.namespace }} 6 | annotations: 7 | prometheus.io/scrape: 'true' 8 | prometheus.io/port: '9090' 9 | 10 | spec: 11 | selector: 12 | app: prometheus-server 13 | type: {{ .Values.service.type }} 14 | ports: 15 | - port: {{ .Values.service.port }} 16 | targetPort: 9090 17 | nodePort: 30000 18 | 19 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "grafana.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "grafana.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "grafana.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "klusterview.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "klusterview.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "klusterview.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /client/pages/Pods.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import Dashboard from '../components/Dashboard/Dashboard'; 3 | import './pages.scss'; 4 | 5 | interface PodsProps { 6 | url: string; 7 | podTitle: string; 8 | } 9 | 10 | const Pods: FC = ({ url, podTitle }) => { 11 | console.log(url, podTitle); 12 | 13 | return ( 14 |
15 |

{podTitle} Metrics

16 | 17 |
18 | ); 19 | }; 20 | 21 | export default Pods; 22 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/charts/kube-state-metrics/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "prometheus.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "prometheus.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "prometheus.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /deployment/_manual_install/kube_state_metrics/ksm-cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: exporter 6 | app.kubernetes.io/name: kube-state-metrics 7 | app.kubernetes.io/version: 2.3.0 8 | name: kube-state-metrics 9 | roleRef: 10 | apiGroup: rbac.authorization.k8s.io 11 | kind: ClusterRole 12 | name: kube-state-metrics 13 | subjects: 14 | - kind: ServiceAccount 15 | name: kube-state-metrics 16 | namespace: kube-system 17 | -------------------------------------------------------------------------------- /server/routes/statusRouter.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from 'express'; 2 | import initializationController from '../controllers/initializationController'; 3 | 4 | const statusRouter = express.Router(); 5 | 6 | statusRouter.get( 7 | '/init', 8 | initializationController.initializeGrafana, 9 | //initializationController.login, 10 | (req: Request, res: Response) => { 11 | return res 12 | .status(200) 13 | .json([res.locals.login, res.locals.maindash, res.locals.poddash]); 14 | } 15 | ); 16 | 17 | export default statusRouter; 18 | -------------------------------------------------------------------------------- /deployment/_manual_install/kube_state_metrics/ksm-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: exporter 6 | app.kubernetes.io/name: kube-state-metrics 7 | app.kubernetes.io/version: 2.3.0 8 | name: kube-state-metrics 9 | namespace: kube-system 10 | spec: 11 | clusterIP: None 12 | ports: 13 | - name: http-metrics 14 | port: 8080 15 | targetPort: http-metrics 16 | - name: telemetry 17 | port: 8081 18 | targetPort: telemetry 19 | selector: 20 | app.kubernetes.io/name: kube-state-metrics 21 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/charts/kube-state-metrics/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "kube-state-metrics.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "kube-state-metrics.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "kube-state-metrics.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/charts/kube-state-metrics/templates/ksm-cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: exporter 6 | app.kubernetes.io/name: kube-state-metrics 7 | app.kubernetes.io/version: 2.3.0 8 | name: kube-state-metrics 9 | roleRef: 10 | apiGroup: rbac.authorization.k8s.io 11 | kind: ClusterRole 12 | name: kube-state-metrics 13 | subjects: 14 | - kind: ServiceAccount 15 | name: kube-state-metrics 16 | namespace: kube-system 17 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/clusterRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: prometheus 5 | rules: 6 | - apiGroups: [''] 7 | resources: 8 | - nodes 9 | - nodes/proxy 10 | - services 11 | - endpoints 12 | - pods 13 | - deployments 14 | verbs: ['get', 'list', 'watch'] 15 | - apiGroups: 16 | - extensions 17 | resources: 18 | - ingresses 19 | verbs: ['get', 'list', 'watch'] 20 | - nonResourceURLs: ['/metrics'] 21 | verbs: ['get'] 22 | 23 | -------------------------------------------------------------------------------- /client/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import './Header.scss'; 3 | 4 | const Header: FC = () => { 5 | return ( 6 |
7 |
8 | logo 14 |
15 |
16 | Kluster 17 |

View

18 |
19 |
20 | ); 21 | }; 22 | 23 | export default Header; 24 | -------------------------------------------------------------------------------- /deployment/_manual_install/grafana/grafana-datasource-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: grafana-datasources 5 | namespace: monitoring-kv 6 | data: 7 | prometheus.yaml: |- 8 | { 9 | "apiVersion": 1, 10 | "datasources": [ 11 | { 12 | "access":"proxy", 13 | "editable": true, 14 | "name": "prometheus", 15 | "orgId": 1, 16 | "type": "prometheus", 17 | "url": "http://prometheus.monitoring-kv.svc:8080", 18 | "version": 1 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/charts/kube-state-metrics/templates/ksm-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: exporter 6 | app.kubernetes.io/name: kube-state-metrics 7 | app.kubernetes.io/version: 2.3.0 8 | name: kube-state-metrics 9 | namespace: kube-system 10 | spec: 11 | clusterIP: None 12 | ports: 13 | - name: http-metrics 14 | port: 8080 15 | targetPort: http-metrics 16 | - name: telemetry 17 | port: 8081 18 | targetPort: telemetry 19 | selector: 20 | app.kubernetes.io/name: kube-state-metrics 21 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/templates/grafana-datasource-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: grafana-datasources 5 | namespace: {{ .Values.global.namespace }} 6 | data: 7 | prometheus.yaml: |- 8 | { 9 | "apiVersion": 1, 10 | "datasources": [ 11 | { 12 | "access":"proxy", 13 | "editable": true, 14 | "name": "prometheus", 15 | "orgId": 1, 16 | "type": "prometheus", 17 | "url": "http://prometheus.monitoring-kv.svc:8080", 18 | "version": 1 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /client/pages/Graph.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import NodeGraph from '../components/NodeGraph/NodeGraph'; 3 | import './pages.scss'; 4 | 5 | interface GraphProps { 6 | nodeMapInfo: { [n: string]: string[] }; 7 | podStatus: { [p: string]: string }; 8 | modalInfo: { [p: string]: string }; 9 | } 10 | 11 | const Graph: FC = ({ nodeMapInfo, podStatus, modalInfo }) => { 12 | return ( 13 |
14 |

Node Graph

15 | 20 |
21 | ); 22 | }; 23 | 24 | export default Graph; 25 | -------------------------------------------------------------------------------- /docs/charts/index.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | entries: 3 | klusterview: 4 | - apiVersion: v2 5 | appVersion: 1.0.0 6 | created: "2023-06-17T14:26:02.325775625-04:00" 7 | dependencies: 8 | - name: grafana 9 | repository: "" 10 | description: A metrics visualizer for Kubernetes, using custom installations of 11 | Prometheus and Grafana 12 | digest: 35fbf68b130c170828f1d2c71ad31288f4727f2975431ba08cb548bf0883ea30 13 | maintainers: 14 | - email: kyle.slugg@gmail.com 15 | name: Kyle Slugg-Urbino 16 | name: klusterview 17 | type: application 18 | urls: 19 | - klusterview-0.1.0.tgz 20 | version: 0.1.0 21 | generated: "2023-06-17T14:26:02.320800389-04:00" 22 | -------------------------------------------------------------------------------- /client/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import App from './App'; 5 | import './styles/app.scss'; 6 | import './styles/normalize.css'; 7 | 8 | fetch('/status/init') 9 | .then((r) => console.log(`Status check: response with code ${r.status}`)) 10 | .catch((e) => console.log(`Received error on status check: ${e}`)); 11 | 12 | const rootElement = document.getElementById('root'); 13 | if (!rootElement) throw new Error('Failed to find root element'); 14 | const root = createRoot(rootElement); 15 | root.render( 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /server/types.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | 3 | export type MiddlewareFn = ( 4 | req: Request, 5 | res: Response, 6 | next: NextFunction 7 | ) => void; 8 | 9 | export type MethodError = { 10 | log: String; 11 | status: Number; 12 | message: { err: String }; 13 | }; 14 | export interface statusNames { 15 | podName: string; 16 | podStatus: string; 17 | } 18 | export interface stausObject { 19 | metric: { 20 | [keys: string]: string; 21 | }; 22 | value: string | number[]; 23 | } 24 | export interface PodMetric { 25 | metric: { 26 | pod: string; 27 | pod_ip: string; 28 | }; 29 | } 30 | 31 | export interface podObject { 32 | metric: { [key: string]: string }; 33 | value: number | string[]; 34 | } 35 | -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Setting context 4 | current_dir=$(pwd) 5 | echo "current_dir is $current_dir" 6 | parentdir="$(dirname "$current_dir")" 7 | echo $parentdir 8 | 9 | 10 | echo "Ensuring namespace monitoring-kv exists..." 11 | kubectl create namespace monitoring-kv --dry-run=client -o yaml | kubectl apply -f - 12 | 13 | echo "Enabling Kube State Metrics..." 14 | kubectl apply -f $parentdir/deployment/_manual_install/kube_state_metrics/ 15 | 16 | echo "Setting up Prometheus..." 17 | kubectl apply -f $parentdir/deployment/_manual_install/prometheus/ 18 | 19 | echo "Setting up Grafana..." 20 | kubectl apply -f $parentdir/deployment/_manual_install/grafana/ 21 | 22 | echo "Setting up KlusterView..." 23 | kubectl apply -f $parentdir/deployment/_manual_install/klusterview-manifest.yaml 24 | 25 | -------------------------------------------------------------------------------- /deployment/_manual_install/prometheus/clusterRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: prometheus 5 | rules: 6 | - apiGroups: [''] 7 | resources: 8 | - nodes 9 | - nodes/proxy 10 | - services 11 | - endpoints 12 | - pods 13 | - deployments 14 | verbs: ['get', 'list', 'watch'] 15 | - apiGroups: 16 | - extensions 17 | resources: 18 | - ingresses 19 | verbs: ['get', 'list', 'watch'] 20 | - nonResourceURLs: ['/metrics'] 21 | verbs: ['get'] 22 | --- 23 | apiVersion: rbac.authorization.k8s.io/v1 24 | kind: ClusterRoleBinding 25 | metadata: 26 | name: prometheus 27 | roleRef: 28 | apiGroup: rbac.authorization.k8s.io 29 | kind: ClusterRole 30 | name: prometheus 31 | subjects: 32 | - kind: ServiceAccount 33 | name: default 34 | namespace: monitoring-kv 35 | -------------------------------------------------------------------------------- /assets/github-mark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es2021": true, 6 | "node": true 7 | }, 8 | "plugins": ["import", "react", "jsx-a11y"], 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:import/errors", 12 | "plugin:react/recommended", 13 | "plugin:jsx-a11y/recommended", 14 | "prettier" 15 | ], 16 | "parserOptions": { 17 | "ecmaFeatures": { 18 | "jsx": true 19 | }, 20 | "sourceType": "module" 21 | }, 22 | "rules": { 23 | "no-unused-vars": ["off", { "vars": "local" }], 24 | "indent": ["warn", 2], 25 | "quotes": ["warn", "single"], 26 | "prefer-const": "warn", 27 | "semi": ["warn", "always"], 28 | "react/prefer-stateless-function": "off", 29 | "react/prop-types": "off", 30 | "react/jsx-key": "warn" 31 | }, 32 | "settings": { 33 | "react": { 34 | "version": "detect" 35 | }, 36 | "import/resolver": "webpack" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/routes/grafanaRouter.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from 'express'; 2 | import grafanaController from '../controllers/grafanaController'; 3 | const grafanaRouter = express.Router(); 4 | 5 | //this route gets the pods dashbaord url 6 | grafanaRouter.get( 7 | '/pods', 8 | grafanaController.getPods, 9 | (req: Request, res: Response) => { 10 | if (res.locals.src) { 11 | return res.status(200).json(res.locals.src); 12 | } else { 13 | return res.status(404).json('Dashboard source can not be found'); 14 | } 15 | } 16 | ); 17 | // this route get cluster/main dashboard url 18 | grafanaRouter.get( 19 | '/dashboard', 20 | grafanaController.getCluster, 21 | (req: Request, res: Response) => { 22 | if (res.locals.src) { 23 | return res.status(200).json(res.locals.src); 24 | } else { 25 | return res.status(404).json('Dashboard source can not be found'); 26 | } 27 | } 28 | ); 29 | 30 | export default grafanaRouter; 31 | -------------------------------------------------------------------------------- /server/routes/promRouter.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from 'express'; 2 | import promController from '../controllers/promController'; 3 | 4 | const promRouter = express.Router(); 5 | 6 | //this router gets names of all the pods 7 | promRouter.get( 8 | '/pods/', 9 | promController.getPodNames, 10 | (req: Request, res: Response) => { 11 | return res.status(200).json(res.locals.names); 12 | } 13 | ); 14 | //this router gets status of all the pods with their name 15 | promRouter.get( 16 | '/pod/status', 17 | promController.getPodStatuses, 18 | (req: Request, res: Response) => { 19 | return res.status(200).json(res.locals.podStatusNames); 20 | } 21 | ); 22 | //this router gets more pod level data and, nodes with their corresponding pods 23 | promRouter.get( 24 | '/pods/nodes', 25 | promController.getPodNodes, 26 | (req: Request, res: Response) => { 27 | return res.status(200).json(res.locals.result); 28 | } 29 | ); 30 | 31 | export default promRouter; 32 | -------------------------------------------------------------------------------- /client/components/NodeGraph/NodeModal.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import './NodeModal.scss'; 3 | interface NodeModalProps { 4 | status: string; 5 | modalInfo: { [key: string]: string }; 6 | } 7 | const NodeModal: FC = ({ status, modalInfo }) => { 8 | const { hostIp, podIp, node, nameSpace, job } = modalInfo; 9 | return ( 10 |
11 |
    12 |
  • 13 | Status: 14 | {status} 15 |
  • 16 |
  • 17 | Host IP: {hostIp} 18 |
  • 19 |
  • 20 | Pod IP: 21 | {podIp} 22 |
  • 23 |
  • 24 | Node: 25 | {node} 26 |
  • 27 |
  • 28 | NameSpace: {nameSpace} 29 |
  • 30 |
  • 31 | Job: {job} 32 |
  • 33 |
34 |
35 | ); 36 | }; 37 | 38 | export default NodeModal; 39 | -------------------------------------------------------------------------------- /deployment/_manual_install/klusterview-manifest.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: klusterview 5 | namespace: monitoring-kv 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: klusterview 11 | template: 12 | metadata: 13 | name: klusterview 14 | labels: 15 | app: klusterview 16 | spec: 17 | containers: 18 | - name: klusterview 19 | image: kyleslugg/klusterview:latest 20 | resources: 21 | limits: 22 | memory: '500Mi' 23 | cpu: '500m' 24 | ports: 25 | - name: prod 26 | containerPort: 3000 27 | 28 | --- 29 | apiVersion: v1 30 | kind: Service 31 | metadata: 32 | name: klusterview 33 | namespace: monitoring-kv 34 | spec: 35 | selector: 36 | app: klusterview 37 | type: NodePort 38 | ports: 39 | - name: prod 40 | port: 3000 41 | targetPort: 3000 42 | nodePort: 31001 43 | protocol: TCP 44 | -------------------------------------------------------------------------------- /client/components/Header/Header.scss: -------------------------------------------------------------------------------- 1 | @use '../../styles/variables' as *; 2 | 3 | .header { 4 | color: $primary-color; 5 | font-size: 2.5rem; 6 | display: flex; 7 | align-items: center; 8 | img { 9 | width: 60px; 10 | height: 50px; 11 | cursor: pointer; 12 | } 13 | } 14 | 15 | .header-logo { 16 | min-width: 13rem; 17 | text-align: center; 18 | margin: 2rem 0 2rem 2rem; 19 | } 20 | 21 | .header-container { 22 | width: 95%; 23 | margin: 0 auto; 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | } 28 | 29 | span { 30 | font-size: 5rem; 31 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 32 | Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 33 | font-weight: bold; 34 | background: -webkit-linear-gradient(360deg, $primary-color 20%, white 80%); 35 | -webkit-background-clip: text; 36 | -webkit-text-fill-color: transparent; 37 | } 38 | 39 | .header-name { 40 | color: white; 41 | text-shadow: 1px 1px rgb(10, 202, 224); 42 | margin: 2rem 0; 43 | } 44 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "klusterview.fullname" . }} 6 | labels: 7 | {{- include "klusterview.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "klusterview.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "grafana.fullname" . }} 6 | labels: 7 | {{- include "grafana.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "grafana.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "prometheus.fullname" . }} 6 | labels: 7 | {{- include "prometheus.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "prometheus.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: klusterview 5 | namespace: {{ .Values.global.namespace }} 6 | spec: 7 | {{- if not .Values.autoscaling.enabled }} 8 | replicas: {{ .Values.replicaCount }} 9 | {{- end }} 10 | selector: 11 | matchLabels: 12 | app: klusterview 13 | template: 14 | metadata: 15 | name: klusterview 16 | labels: 17 | app: klusterview 18 | spec: 19 | containers: 20 | - name: {{ .Chart.Name }} 21 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 22 | imagePullPolicy: {{ .Values.image.pullPolicy }} 23 | resources: 24 | limits: 25 | memory: {{ .Values.resources.limits.memory }} 26 | cpu: {{ .Values.resources.limits.cpu }} 27 | requests: 28 | memory: {{ .Values.resources.requests.memory }} 29 | cpu: {{ .Values.resources.requests.cpu }} 30 | ports: 31 | - name: prod 32 | containerPort: 3000 -------------------------------------------------------------------------------- /client/components/NodeGraph/NodeGraph.scss: -------------------------------------------------------------------------------- 1 | @use '../../styles/variables' as *; 2 | 3 | .nodeGraph-container { 4 | width: 100%; 5 | height: 90%; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | } 10 | 11 | .nodeGraph { 12 | position: relative; 13 | width: 95%; 14 | height: 95%; 15 | border: 2px solid $secondary-color; 16 | border-radius: 1.5rem; 17 | } 18 | 19 | .react-flow__panel { 20 | display: none; 21 | } 22 | 23 | .react-flow__node { 24 | border-radius: 10px; 25 | height: 50px; 26 | display: flex; 27 | align-items: center; 28 | justify-content: center; 29 | } 30 | 31 | .list { 32 | list-style-type: none; 33 | display: flex; 34 | justify-content: center; 35 | 36 | a { 37 | color: white; 38 | text-decoration: none; 39 | padding: 1rem; 40 | } 41 | } 42 | .navlink-graph { 43 | margin-right: 10px; 44 | border: 2px solid $primary-color; 45 | border-radius: 1rem; 46 | width: fit-content; 47 | padding: 1rem 0; 48 | 49 | &:hover { 50 | border-color: $secondary-color; 51 | } 52 | 53 | &:active { 54 | transform: scale(0.9); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /deployment/_manual_install/prometheus/prometheus-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: prometheus 5 | namespace: monitoring-kv 6 | labels: 7 | app: prometheus-server 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: prometheus-server 13 | template: 14 | metadata: 15 | labels: 16 | app: prometheus-server 17 | spec: 18 | containers: 19 | - name: prometheus 20 | image: prom/prometheus 21 | args: 22 | - '--config.file=/etc/prometheus/prometheus.yml' 23 | - '--storage.tsdb.path=/prometheus/' 24 | ports: 25 | - containerPort: 9090 26 | volumeMounts: 27 | - name: prometheus-config-volume 28 | mountPath: /etc/prometheus/ 29 | - name: prometheus-storage-volume 30 | mountPath: /prometheus/ 31 | volumes: 32 | - name: prometheus-config-volume 33 | configMap: 34 | defaultMode: 420 35 | name: prometheus-server-conf 36 | 37 | - name: prometheus-storage-volume 38 | emptyDir: {} 39 | #persistentVolumeClaim: 40 | # claimName: prometheus-pvc 41 | -------------------------------------------------------------------------------- /docs/charts/klusterview-0.1.0.tgz.prov: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNED MESSAGE----- 2 | Hash: SHA512 3 | 4 | apiVersion: v2 5 | appVersion: 1.0.0 6 | dependencies: 7 | - - name: grafana 8 | repository: "" 9 | description: A metrics visualizer for Kubernetes, using custom installations of Prometheus 10 | and Grafana 11 | maintainers: 12 | - - email: kyle.slugg@gmail.com 13 | name: Kyle Slugg-Urbino 14 | name: klusterview 15 | type: application 16 | version: 0.1.0 17 | 18 | ... 19 | files: 20 | klusterview-0.1.0.tgz: sha256:35fbf68b130c170828f1d2c71ad31288f4727f2975431ba08cb548bf0883ea30 21 | -----BEGIN PGP SIGNATURE----- 22 | 23 | wsDcBAEBCgAQBQJkjfqUCRDGja6t8joU0QAAcW0MADhaI4SpQ5a/nAcDGrEKmNqe 24 | 2VudrJd6zt8mzIcnrh0U+5P/98SrN2pIQ/0GwI/BgOyCUJBjCmC6144QoBpklrr3 25 | mXnRQPwmd0Y6baV0NsGHyWAtGcc23y/nWUWqVDEqRPl4mZ1FklYFWfZI5DeQJ3dy 26 | oFcYHImt8fnA/4hXxTM+sOyGwPBi8X4DV/DMFbGj+WI5u7NkPAiBvP3C7E7v1gRD 27 | AgOPXd44QAUIDB/dJdGeFkuWAQgpvrKboMNSderqms4z4zfOkMmJ0Ruiv1419goF 28 | 6XpYqZisIpciD60MP8xA4DdKhys+nboD05nJmDxXVbg+uZB9pozelO2RcQZ5BQoB 29 | sm1i8dbbhrWae6pXuaYX+tN7dk7gtWD3wCRPk/APQ/rKdT2sT+E9UZafmf6A0Yo2 30 | STso8RU4N6rAczDNO57wYqkobFyIGA9K9Ig9oyKxulnDtZvKLdRppA/AJ99AEX+i 31 | rB4Blibd58bbdvr3MEOWtxOwqKM/2y5eZlLGh5PGwg== 32 | =jiiE 33 | -----END PGP SIGNATURE----- -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/charts/kube-state-metrics/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: kube-state-metrics 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | icon: 'https://upload.wikimedia.org/wikipedia/commons/5/59/Empty.png' 26 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: prometheus 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | icon: https://upload.wikimedia.org/wikipedia/commons/5/59/Empty.png 26 | dependencies: 27 | - name: kube-state-metrics 28 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: grafana 3 | description: A custom Grafana installation to support the KlusterView visualizer 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | icon: 'https://upload.wikimedia.org/wikipedia/commons/5/59/Empty.png' 26 | dependencies: 27 | - name: prometheus 28 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: prometheus 5 | namespace: {{ .Values.global.namespace }} 6 | labels: 7 | app: prometheus-server 8 | spec: 9 | {{- if not .Values.autoscaling.enabled }} 10 | replicas: {{ .Values.replicaCount }} 11 | {{- end }} 12 | selector: 13 | matchLabels: 14 | app: prometheus-server 15 | template: 16 | metadata: 17 | labels: 18 | app: prometheus-server 19 | spec: 20 | containers: 21 | - name: prometheus 22 | image: "{{ .Values.image.repository }}" 23 | imagePullPolicy: {{ .Values.image.pullPolicy }} 24 | args: 25 | - '--config.file=/etc/prometheus/prometheus.yml' 26 | - '--storage.tsdb.path=/prometheus/' 27 | ports: 28 | - containerPort: 9090 29 | volumeMounts: 30 | - name: prometheus-config-volume 31 | mountPath: /etc/prometheus/ 32 | - name: prometheus-storage-volume 33 | mountPath: /prometheus/ 34 | volumes: 35 | - name: prometheus-config-volume 36 | configMap: 37 | defaultMode: 420 38 | name: prometheus-server-conf 39 | 40 | - name: prometheus-storage-volume 41 | emptyDir: {} 42 | #persistentVolumeClaim: 43 | # claimName: prometheus-pvc 44 | -------------------------------------------------------------------------------- /deployment/_manual_install/kube_state_metrics/ksm-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: exporter 6 | app.kubernetes.io/name: kube-state-metrics 7 | app.kubernetes.io/version: 2.3.0 8 | name: kube-state-metrics 9 | namespace: kube-system 10 | spec: 11 | replicas: 1 12 | selector: 13 | matchLabels: 14 | app.kubernetes.io/name: kube-state-metrics 15 | template: 16 | metadata: 17 | labels: 18 | app.kubernetes.io/component: exporter 19 | app.kubernetes.io/name: kube-state-metrics 20 | app.kubernetes.io/version: 2.3.0 21 | spec: 22 | automountServiceAccountToken: true 23 | containers: 24 | - image: k8s.gcr.io/kube-state-metrics/kube-state-metrics:v2.3.0 25 | livenessProbe: 26 | httpGet: 27 | path: /healthz 28 | port: 8080 29 | initialDelaySeconds: 5 30 | timeoutSeconds: 5 31 | name: kube-state-metrics 32 | ports: 33 | - containerPort: 8080 34 | name: http-metrics 35 | - containerPort: 8081 36 | name: telemetry 37 | readinessProbe: 38 | httpGet: 39 | path: / 40 | port: 8081 41 | initialDelaySeconds: 5 42 | timeoutSeconds: 5 43 | securityContext: 44 | allowPrivilegeEscalation: false 45 | readOnlyRootFilesystem: true 46 | runAsUser: 65534 47 | nodeSelector: 48 | kubernetes.io/os: linux 49 | serviceAccountName: kube-state-metrics 50 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/charts/kube-state-metrics/templates/ksm-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: exporter 6 | app.kubernetes.io/name: kube-state-metrics 7 | app.kubernetes.io/version: 2.3.0 8 | name: kube-state-metrics 9 | namespace: kube-system 10 | spec: 11 | replicas: 1 12 | selector: 13 | matchLabels: 14 | app.kubernetes.io/name: kube-state-metrics 15 | template: 16 | metadata: 17 | labels: 18 | app.kubernetes.io/component: exporter 19 | app.kubernetes.io/name: kube-state-metrics 20 | app.kubernetes.io/version: 2.3.0 21 | spec: 22 | automountServiceAccountToken: true 23 | containers: 24 | - image: k8s.gcr.io/kube-state-metrics/kube-state-metrics:v2.3.0 25 | livenessProbe: 26 | httpGet: 27 | path: /healthz 28 | port: 8080 29 | initialDelaySeconds: 5 30 | timeoutSeconds: 5 31 | name: kube-state-metrics 32 | ports: 33 | - containerPort: 8080 34 | name: http-metrics 35 | - containerPort: 8081 36 | name: telemetry 37 | readinessProbe: 38 | httpGet: 39 | path: / 40 | port: 8081 41 | initialDelaySeconds: 5 42 | timeoutSeconds: 5 43 | securityContext: 44 | allowPrivilegeEscalation: false 45 | readOnlyRootFilesystem: true 46 | runAsUser: 65534 47 | nodeSelector: 48 | kubernetes.io/os: linux 49 | serviceAccountName: kube-state-metrics 50 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "grafana.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "grafana.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "grafana.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "grafana.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "klusterview.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "klusterview.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "klusterview.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "klusterview.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "prometheus.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "prometheus.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "prometheus.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "prometheus.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for grafana. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: grafana/grafana 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: latest 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | serviceAccount: 18 | # Specifies whether a service account should be created 19 | create: true 20 | # Annotations to add to the service account 21 | annotations: {} 22 | # The name of the service account to use. 23 | # If not set and create is true, a name is generated using the fullname template 24 | name: "" 25 | 26 | podAnnotations: {} 27 | 28 | podSecurityContext: {} 29 | # fsGroup: 2000 30 | 31 | securityContext: {} 32 | # capabilities: 33 | # drop: 34 | # - ALL 35 | # readOnlyRootFilesystem: true 36 | # runAsNonRoot: true 37 | # runAsUser: 1000 38 | 39 | service: 40 | type: NodePort 41 | port: 3000 42 | 43 | ingress: 44 | enabled: false 45 | className: "" 46 | annotations: {} 47 | # kubernetes.io/ingress.class: nginx 48 | # kubernetes.io/tls-acme: "true" 49 | hosts: 50 | - host: chart-example.local 51 | paths: 52 | - path: / 53 | pathType: ImplementationSpecific 54 | tls: [] 55 | # - secretName: chart-example-tls 56 | # hosts: 57 | # - chart-example.local 58 | 59 | resources: 60 | limits: 61 | memory: '1Gi' 62 | cpu: '1000m' 63 | requests: 64 | memory: 500M 65 | cpu: '500m' 66 | 67 | autoscaling: 68 | enabled: false 69 | minReplicas: 1 70 | maxReplicas: 100 71 | targetCPUUtilizationPercentage: 80 72 | # targetMemoryUtilizationPercentage: 80 73 | 74 | nodeSelector: {} 75 | 76 | tolerations: [] 77 | 78 | affinity: {} 79 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for klusterview. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | global: 6 | namespace: monitoring-kv 7 | 8 | replicaCount: 1 9 | 10 | image: 11 | repository: kyleslugg/klusterview 12 | pullPolicy: Always 13 | # Overrides the image tag whose default is the chart appVersion. 14 | tag: 'latest' 15 | 16 | imagePullSecrets: [] 17 | nameOverride: '' 18 | fullnameOverride: '' 19 | 20 | serviceAccount: 21 | # Specifies whether a service account should be created 22 | create: true 23 | # Annotations to add to the service account 24 | annotations: {} 25 | # The name of the service account to use. 26 | # If not set and create is true, a name is generated using the fullname template 27 | name: '' 28 | 29 | podAnnotations: {} 30 | 31 | podSecurityContext: 32 | {} 33 | # fsGroup: 2000 34 | 35 | securityContext: 36 | {} 37 | # capabilities: 38 | # drop: 39 | # - ALL 40 | # readOnlyRootFilesystem: true 41 | # runAsNonRoot: true 42 | # runAsUser: 1000 43 | 44 | service: 45 | type: NodePort 46 | port: 3000 47 | 48 | ingress: 49 | enabled: false 50 | className: '' 51 | annotations: 52 | {} 53 | # kubernetes.io/ingress.class: nginx 54 | # kubernetes.io/tls-acme: "true" 55 | hosts: 56 | - host: chart-example.local 57 | paths: 58 | - path: / 59 | pathType: ImplementationSpecific 60 | tls: [] 61 | # - secretName: chart-example-tls 62 | # hosts: 63 | # - chart-example.local 64 | 65 | resources: 66 | limits: 67 | cpu: 500m 68 | memory: 512Mi 69 | requests: 70 | cpu: 200m 71 | memory: 256Mi 72 | 73 | autoscaling: 74 | enabled: false 75 | minReplicas: 1 76 | maxReplicas: 100 77 | targetCPUUtilizationPercentage: 80 78 | # targetMemoryUtilizationPercentage: 80 79 | 80 | nodeSelector: {} 81 | 82 | tolerations: [] 83 | 84 | affinity: {} 85 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "grafana.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "grafana.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "grafana.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "grafana.labels" -}} 37 | helm.sh/chart: {{ include "grafana.chart" . }} 38 | {{ include "grafana.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "grafana.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "grafana.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "grafana.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "grafana.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "klusterview.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "klusterview.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "klusterview.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "klusterview.labels" -}} 37 | helm.sh/chart: {{ include "klusterview.chart" . }} 38 | {{ include "klusterview.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "klusterview.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "klusterview.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "klusterview.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "klusterview.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "prometheus.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "prometheus.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "prometheus.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "prometheus.labels" -}} 37 | helm.sh/chart: {{ include "prometheus.chart" . }} 38 | {{ include "prometheus.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "prometheus.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "prometheus.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "prometheus.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "prometheus.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/charts/kube-state-metrics/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "kube-state-metrics.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "kube-state-metrics.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "kube-state-metrics.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "kube-state-metrics.labels" -}} 37 | helm.sh/chart: {{ include "kube-state-metrics.chart" . }} 38 | {{ include "kube-state-metrics.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "kube-state-metrics.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "kube-state-metrics.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "kube-state-metrics.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "kube-state-metrics.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for prometheus. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: prom/prometheus 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: "" 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | serviceAccount: 18 | # Specifies whether a service account should be created 19 | create: false 20 | # Annotations to add to the service account 21 | annotations: {} 22 | # The name of the service account to use. 23 | # If not set and create is true, a name is generated using the fullname template 24 | name: "" 25 | 26 | podAnnotations: {} 27 | 28 | podSecurityContext: {} 29 | # fsGroup: 2000 30 | 31 | securityContext: {} 32 | # capabilities: 33 | # drop: 34 | # - ALL 35 | # readOnlyRootFilesystem: true 36 | # runAsNonRoot: true 37 | # runAsUser: 1000 38 | 39 | service: 40 | type: NodePort 41 | port: 8080 42 | 43 | ingress: 44 | enabled: false 45 | className: "" 46 | annotations: {} 47 | # kubernetes.io/ingress.class: nginx 48 | # kubernetes.io/tls-acme: "true" 49 | hosts: 50 | - host: chart-example.local 51 | paths: 52 | - path: / 53 | pathType: ImplementationSpecific 54 | tls: [] 55 | # - secretName: chart-example-tls 56 | # hosts: 57 | # - chart-example.local 58 | 59 | resources: {} 60 | # We usually recommend not to specify default resources and to leave this as a conscious 61 | # choice for the user. This also increases chances charts run on environments with little 62 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 63 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 64 | # limits: 65 | # cpu: 100m 66 | # memory: 128Mi 67 | # requests: 68 | # cpu: 100m 69 | # memory: 128Mi 70 | 71 | autoscaling: 72 | enabled: false 73 | minReplicas: 1 74 | maxReplicas: 100 75 | targetCPUUtilizationPercentage: 80 76 | # targetMemoryUtilizationPercentage: 80 77 | 78 | nodeSelector: {} 79 | 80 | tolerations: [] 81 | 82 | affinity: {} 83 | -------------------------------------------------------------------------------- /deployment/_manual_install/kube_state_metrics/ksm-cluster-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: exporter 6 | app.kubernetes.io/name: kube-state-metrics 7 | app.kubernetes.io/version: 2.3.0 8 | name: kube-state-metrics 9 | rules: 10 | - apiGroups: 11 | - "" 12 | resources: 13 | - configmaps 14 | - secrets 15 | - nodes 16 | - pods 17 | - services 18 | - resourcequotas 19 | - replicationcontrollers 20 | - limitranges 21 | - persistentvolumeclaims 22 | - persistentvolumes 23 | - namespaces 24 | - endpoints 25 | verbs: 26 | - list 27 | - watch 28 | - apiGroups: 29 | - apps 30 | resources: 31 | - statefulsets 32 | - daemonsets 33 | - deployments 34 | - replicasets 35 | verbs: 36 | - list 37 | - watch 38 | - apiGroups: 39 | - batch 40 | resources: 41 | - cronjobs 42 | - jobs 43 | verbs: 44 | - list 45 | - watch 46 | - apiGroups: 47 | - autoscaling 48 | resources: 49 | - horizontalpodautoscalers 50 | verbs: 51 | - list 52 | - watch 53 | - apiGroups: 54 | - authentication.k8s.io 55 | resources: 56 | - tokenreviews 57 | verbs: 58 | - create 59 | - apiGroups: 60 | - authorization.k8s.io 61 | resources: 62 | - subjectaccessreviews 63 | verbs: 64 | - create 65 | - apiGroups: 66 | - policy 67 | resources: 68 | - poddisruptionbudgets 69 | verbs: 70 | - list 71 | - watch 72 | - apiGroups: 73 | - certificates.k8s.io 74 | resources: 75 | - certificatesigningrequests 76 | verbs: 77 | - list 78 | - watch 79 | - apiGroups: 80 | - storage.k8s.io 81 | resources: 82 | - storageclasses 83 | - volumeattachments 84 | verbs: 85 | - list 86 | - watch 87 | - apiGroups: 88 | - admissionregistration.k8s.io 89 | resources: 90 | - mutatingwebhookconfigurations 91 | - validatingwebhookconfigurations 92 | verbs: 93 | - list 94 | - watch 95 | - apiGroups: 96 | - networking.k8s.io 97 | resources: 98 | - networkpolicies 99 | - ingresses 100 | verbs: 101 | - list 102 | - watch 103 | - apiGroups: 104 | - coordination.k8s.io 105 | resources: 106 | - leases 107 | verbs: 108 | - list 109 | - watch 110 | -------------------------------------------------------------------------------- /client/components/NodeGraph/utils.ts: -------------------------------------------------------------------------------- 1 | interface NodeGraphProps { 2 | nodeMapInfo: { [n: string]: string[] }; 3 | podStatus: { [p: string]: string }; 4 | modalInfo: { [p: string]: string }; 5 | } 6 | type ChildNode = { 7 | id: string; 8 | position: { 9 | x: number; 10 | y: number; 11 | }; 12 | data: { 13 | label: string; 14 | }; 15 | status: string; 16 | modalData: any; 17 | style: { 18 | color: string; 19 | backgroundColor: string; 20 | }; 21 | }; 22 | interface NodeGraphProps { 23 | nodeMapInfo: { [n: string]: string[] }; 24 | podStatus: { [p: string]: string }; 25 | modalInfo: { [p: string]: string }; 26 | } 27 | 28 | //This Function takes a node name, pods under this node, 29 | // status of those pods, and some additional info on those pods 30 | //based on these data it creates necessarry nodes and edges 31 | //that is needed for ReactFlow Graph 32 | const initialGen = ( 33 | root: string, 34 | children: string[], 35 | podStatus: { [p: string]: string }, 36 | modalInfo: { [p: string]: string } 37 | ) => { 38 | const rootX = 600; 39 | const rootY = 100; 40 | const initialNodes = []; 41 | initialNodes.push({ 42 | id: '1', 43 | position: { x: rootX, y: rootY }, 44 | data: { label: root }, 45 | style: { color: 'green', backgroundColor: 'white' }, 46 | }); 47 | 48 | const initialEdges = []; 49 | let childX = 50; 50 | let childY = 300; 51 | 52 | for (let i = 0, times = 0; i < children.length; times++, i++) { 53 | const childNodeObj: ChildNode = { 54 | id: `${2 + i}`, 55 | position: { x: childX * 4 * (times + 1), y: childY }, 56 | data: { label: children[i] }, 57 | status: podStatus[children[i]], 58 | modalData: modalInfo[children[i]], 59 | style: { 60 | color: 'white', 61 | backgroundColor: podStatus[children[i]] === 'Running' ? 'green' : 'red', 62 | }, 63 | }; 64 | if (childX * 4 * (times + 1) > 1000) { 65 | childX = 40; 66 | childY += 100; 67 | times = 0; 68 | } 69 | initialNodes.push(childNodeObj); 70 | const edgeObj = { 71 | id: `el-${2 + i}`, 72 | source: '1', 73 | target: `${i + 2}`, 74 | }; 75 | initialEdges.push(edgeObj); 76 | } 77 | 78 | return { initialNodes, initialEdges }; 79 | }; 80 | 81 | export { initialGen, ChildNode, NodeGraphProps }; 82 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/charts/kube-state-metrics/templates/ksm-cluster-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: exporter 6 | app.kubernetes.io/name: kube-state-metrics 7 | app.kubernetes.io/version: 2.3.0 8 | name: kube-state-metrics 9 | rules: 10 | - apiGroups: 11 | - "" 12 | resources: 13 | - configmaps 14 | - secrets 15 | - nodes 16 | - pods 17 | - services 18 | - resourcequotas 19 | - replicationcontrollers 20 | - limitranges 21 | - persistentvolumeclaims 22 | - persistentvolumes 23 | - namespaces 24 | - endpoints 25 | verbs: 26 | - list 27 | - watch 28 | - apiGroups: 29 | - apps 30 | resources: 31 | - statefulsets 32 | - daemonsets 33 | - deployments 34 | - replicasets 35 | verbs: 36 | - list 37 | - watch 38 | - apiGroups: 39 | - batch 40 | resources: 41 | - cronjobs 42 | - jobs 43 | verbs: 44 | - list 45 | - watch 46 | - apiGroups: 47 | - autoscaling 48 | resources: 49 | - horizontalpodautoscalers 50 | verbs: 51 | - list 52 | - watch 53 | - apiGroups: 54 | - authentication.k8s.io 55 | resources: 56 | - tokenreviews 57 | verbs: 58 | - create 59 | - apiGroups: 60 | - authorization.k8s.io 61 | resources: 62 | - subjectaccessreviews 63 | verbs: 64 | - create 65 | - apiGroups: 66 | - policy 67 | resources: 68 | - poddisruptionbudgets 69 | verbs: 70 | - list 71 | - watch 72 | - apiGroups: 73 | - certificates.k8s.io 74 | resources: 75 | - certificatesigningrequests 76 | verbs: 77 | - list 78 | - watch 79 | - apiGroups: 80 | - storage.k8s.io 81 | resources: 82 | - storageclasses 83 | - volumeattachments 84 | verbs: 85 | - list 86 | - watch 87 | - apiGroups: 88 | - admissionregistration.k8s.io 89 | resources: 90 | - mutatingwebhookconfigurations 91 | - validatingwebhookconfigurations 92 | verbs: 93 | - list 94 | - watch 95 | - apiGroups: 96 | - networking.k8s.io 97 | resources: 98 | - networkpolicies 99 | - ingresses 100 | verbs: 101 | - list 102 | - watch 103 | - apiGroups: 104 | - coordination.k8s.io 105 | resources: 106 | - leases 107 | verbs: 108 | - list 109 | - watch 110 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "klusterview.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "klusterview.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "grafana.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "grafana.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /client/components/Sidebar/Sidebar.scss: -------------------------------------------------------------------------------- 1 | @use '../../styles/variables' as *; 2 | 3 | .sidebar { 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: flex-start; 7 | align-items: center; 8 | height: 84vh; 9 | width: 13rem; 10 | color: white; 11 | position: fixed; 12 | left: 0; 13 | margin-left: 2rem; 14 | overflow-y: hidden; 15 | z-index: 2; 16 | } 17 | 18 | .sidebar-list { 19 | list-style-type: none; 20 | margin: 15rem 0 0; 21 | padding-left: 0; 22 | width: 100%; 23 | } 24 | 25 | .navlink { 26 | margin: 1.75rem 0; 27 | height: 5.5rem; 28 | text-align: center; 29 | cursor: pointer; 30 | border: 1px solid transparent; 31 | transition: border 0.3s; 32 | background-color: rgb(25, 25, 25); 33 | border-radius: 1rem; 34 | 35 | &:active { 36 | transform: scale(0.9); 37 | } 38 | 39 | &:last-of-type:active { 40 | transform: none; 41 | } 42 | 43 | &:hover { 44 | border: 1px solid $secondary-color; 45 | 46 | .dropdown-content { 47 | opacity: 1; 48 | height: 25rem; 49 | } 50 | } 51 | } 52 | 53 | .dropdown-content { 54 | opacity: (0); 55 | margin: 0; 56 | overflow-y: scroll; 57 | font-size: 1rem; 58 | height: 0rem; 59 | transition: opacity 1s; 60 | } 61 | 62 | .link { 63 | color: rgb(255, 255, 255); 64 | text-decoration: none; 65 | width: 100%; 66 | height: 100%; 67 | display: flex; 68 | align-items: center; 69 | justify-content: center; 70 | font-weight: bold; 71 | letter-spacing: 1px; 72 | } 73 | 74 | .link-p { 75 | margin: 0; 76 | } 77 | 78 | .navlink-dropdown { 79 | border-color: $primary-color; 80 | margin: 0.5rem 0; 81 | height: 4.5rem; 82 | border-radius: 0.5rem; 83 | 84 | a { 85 | font-weight: normal; 86 | color: $secondary-color; 87 | } 88 | 89 | &:last-of-type:active { 90 | transform: scale(0.9); 91 | } 92 | 93 | link { 94 | padding: 0.1rem; 95 | } 96 | } 97 | 98 | ::-webkit-scrollbar { 99 | width: 0.7rem; 100 | height: 20%; 101 | } 102 | 103 | ::-webkit-scrollbar-track { 104 | background: transparent; 105 | box-shadow: inset 0 0 0.2rem $secondary-color; 106 | } 107 | 108 | ::-webkit-scrollbar-thumb { 109 | background: $secondary-color; 110 | border-radius: 0.75rem; 111 | border: 1px solid $secondary-color; 112 | } 113 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "prometheus.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "prometheus.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "ecri-40-osp-4", 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "start": "NODE_ENV=production node --loader ts-node/esm server/server.ts", 9 | "build": "NODE_ENV=production webpack", 10 | "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --open --hot --progress --color \" \"nodemon ./server/server.ts\"", 11 | "test": "jest __tests__/" 12 | }, 13 | "jest": { 14 | "moduleNameMapper": { 15 | "^.+\\.(css|less|scss)$": "babel-jest" 16 | } 17 | }, 18 | "author": "", 19 | "license": "ISC", 20 | "dependencies": { 21 | "@babel/preset-env": "^7.22.2", 22 | "@babel/preset-react": "^7.22.0", 23 | "@babel/preset-typescript": "^7.21.5", 24 | "@kubernetes/client-node": "^0.18.1", 25 | "@types/express": "^4.17.17", 26 | "@types/react": "^18.2.7", 27 | "@types/react-dom": "^18.2.4", 28 | "axios": "^1.4.0", 29 | "babel": "^6.23.0", 30 | "babel-loader": "^9.1.2", 31 | "base-64": "^1.0.0", 32 | "body-parser": "^1.20.2", 33 | "browser-router": "^0.2.0", 34 | "concurrently": "^8.0.1", 35 | "cookie-parser": "^1.4.6", 36 | "css-loader": "^6.7.4", 37 | "dotenv": "^16.0.3", 38 | "express": "^4.18.2", 39 | "html-webpack-plugin": "^5.5.1", 40 | "http-proxy-middleware": "^2.0.6", 41 | "jest-environment-jsdom": "^29.5.0", 42 | "mini-css-extract-plugin": "^2.7.6", 43 | "react": "^18.2.0", 44 | "react-dom": "^18.2.0", 45 | "react-flow-renderer": "^10.3.17", 46 | "react-router": "^6.11.2", 47 | "react-router-dom": "^6.11.2", 48 | "react-select": "^5.7.3", 49 | "reactflow": "^11.7.2", 50 | "sass": "^1.62.1", 51 | "sass-loader": "^13.3.0", 52 | "style-loader": "^3.3.3", 53 | "ts-loader": "^9.4.3", 54 | "ts-node": "^10.9.1", 55 | "typescript": "^5.0.4", 56 | "wait-port": "^1.0.4", 57 | "webpack": "^5.84.1", 58 | "webpack-cli": "^5.1.1", 59 | "webpack-dev-server": "^4.15.0", 60 | "webpack-hot-middleware": "^2.25.3" 61 | }, 62 | "devDependencies": { 63 | "@testing-library/jest-dom": "^5.16.5", 64 | "@testing-library/react": "^14.0.0", 65 | "@types/cookie-parser": "^1.4.3", 66 | "@types/jest": "^29.5.2", 67 | "@types/node": "^20.2.5", 68 | "@types/supertest": "^2.0.12", 69 | "jest": "^29.5.0", 70 | "supertest": "^6.3.3", 71 | "ts-jest": "^29.1.0" 72 | } 73 | } -------------------------------------------------------------------------------- /server/controllers/grafanaController.ts: -------------------------------------------------------------------------------- 1 | import { MiddlewareFn } from '../types'; 2 | import axios from 'axios'; 3 | 4 | const GRAF_IP = 'grafana'; 5 | const GRAF_NODE_PORT = '3000'; 6 | 7 | //CREATING UNIX TIMESTAMP VALUES FOR "FROM" & "TO" 8 | const timeStamp = () => { 9 | const currentTime = Date.now(); 10 | const timeRangeInMinutes = 15; 11 | const timeRangeInMilliseconds = timeRangeInMinutes * 60 * 1000; 12 | const from = currentTime - timeRangeInMilliseconds; 13 | const to = currentTime; 14 | return { from, to }; 15 | }; 16 | //ACCESSING DASHBOARD DETAILS LIKE URI AND UID 17 | async function getDashboard(resource: number): Promise<{ 18 | dashboardUid: string; 19 | dashboardUri: string; 20 | }> { 21 | try { 22 | const response = await axios.get( 23 | `http://admin:admin@${GRAF_IP}:${GRAF_NODE_PORT}/api/search?type=dash-db` 24 | ); 25 | const dashboards = response.data; 26 | const dashboard = dashboards[resource]; 27 | const dashboardUid = dashboard.uid; 28 | const length = dashboard.uri.split('/').length; 29 | const dashboardUri = dashboard.uri.split('/')[length - 1]; 30 | return { dashboardUid, dashboardUri }; 31 | } catch (error) { 32 | throw error; 33 | } 34 | } 35 | //middleware functions that gets pod dashboard which is first dashboard 36 | const getPods: MiddlewareFn = async (req, res, next) => { 37 | const { from, to } = timeStamp(); 38 | try { 39 | const { dashboardUid, dashboardUri } = await getDashboard(1); 40 | const src = `/grafanasvc/d/${dashboardUid}/${dashboardUri}/?orgId=1&refresh=30s&var-Node=All&var-Pod=All&var-Pod_ip=192.168.49.2&from=${from}&to=${to}`; 41 | res.locals.src = src; 42 | return next(); 43 | } catch (error) { 44 | return next({ 45 | log: 'Express error handler caught getPods middleware error', 46 | status: 404, 47 | message: { err: 'Could not find the dashboard' } 48 | }); 49 | } 50 | }; 51 | //middleware functions that gets main/cluster dashboard which is second dashboard 52 | const getCluster: MiddlewareFn = async (req, res, next) => { 53 | const { from, to } = timeStamp(); 54 | try { 55 | const { dashboardUid, dashboardUri } = await getDashboard(0); 56 | const src = `/grafanasvc/d/${dashboardUid}/${dashboardUri}/?orgId=1`; 57 | res.locals.src = src; 58 | return next(); 59 | } catch (error) { 60 | return next({ 61 | log: 'Express error handler caught getPods middleware error', 62 | status: 404, 63 | message: { err: 'Could not find the dashboard' } 64 | }); 65 | } 66 | }; 67 | 68 | export default { getPods, getCluster }; 69 | -------------------------------------------------------------------------------- /deployment/_manual_install/grafana/grafana_deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grafana 5 | namespace: monitoring-kv 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: grafana 11 | template: 12 | metadata: 13 | name: grafana 14 | labels: 15 | app: grafana 16 | spec: 17 | containers: 18 | - name: grafana 19 | image: grafana/grafana:latest 20 | ports: 21 | - name: grafana 22 | containerPort: 3000 23 | resources: 24 | limits: 25 | memory: '1Gi' 26 | cpu: '1000m' 27 | requests: 28 | memory: 500M 29 | cpu: '500m' 30 | volumeMounts: 31 | - mountPath: /var/lib/grafana 32 | name: grafana-storage 33 | - mountPath: /etc/grafana/provisioning/datasources 34 | name: grafana-datasources 35 | readOnly: false 36 | env: 37 | - name: GF_SECURITY_ALLOW_EMBEDDING 38 | value: 'true' 39 | - name: GF_AUTH_ANONYMOUS_ENABLED 40 | value: 'true' 41 | - name: GF_AUTH_ANONYMOUS_ORG_NAME 42 | value: Main Org. 43 | - name: GF_AUTH_ANONYMOUS_ORG_ROLE 44 | value: Admin 45 | - name: GF_AUTH_ANONYMOUS_HIDE_VERSION 46 | value: 'true' 47 | - name: GF_FEATURE_TOGGLES_ENABLE 48 | value: publicDashboards 49 | - name: GF_SERVER_ENFORCE_DOMAIN 50 | value: 'false' 51 | - name: GF_SERVER_DOMAIN 52 | value: grafana 53 | - name: GF_SERVER_ROOT_URL 54 | value: '%(protocol)s://%(domain)s:%(http_port)s/grafanasvc' 55 | - name: GF_SERVER_SERVE_FROM_SUB_PATH 56 | value: 'true' 57 | - name: GF_DATAPROXY_KEEP_ALIVE_SECONDS 58 | value: '240' 59 | - name: GF_DATAPROXY_TIMEOUT 60 | value: '240' 61 | - name: GF_DATAPROXY_IDLE_CONN_TIMEOUT_SECONDS 62 | value: '300' 63 | - name: 'GF_LIVE_ALLOWED_ORIGINS' 64 | value: '*' 65 | - name: 'GF_LIVE_MAX_CONNECTIONS' 66 | value: '0' 67 | volumes: 68 | - name: grafana-storage 69 | emptyDir: {} 70 | #persistentVolumeClaim: 71 | # claimName: grafana-pvc 72 | - name: grafana-datasources 73 | configMap: 74 | defaultMode: 420 75 | name: grafana-datasources 76 | -------------------------------------------------------------------------------- /server/controllers/helpers/statusController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, RequestHandler, NextFunction } from 'express'; 2 | import { MethodError } from '../../types'; 3 | import { 4 | clusterRunning, 5 | prometheusAndGrafanaRunning, 6 | runSetup 7 | } from './statusHelpers'; 8 | 9 | //Define StatusController type and initialize empty controller 10 | type StatusController = { [s: string]: RequestHandler }; 11 | 12 | const statusController: StatusController = {}; 13 | 14 | //Define module-level error generator 15 | const createError = ( 16 | method: String, 17 | log: String, 18 | status: Number, 19 | message: String = log 20 | ): MethodError => { 21 | return { 22 | log: `Encountered error in statusController.${method}: ${log}`, 23 | status: status, 24 | message: { err: message } 25 | }; 26 | }; 27 | 28 | //Define methods exported on statusController 29 | 30 | statusController.checkStatus = ( 31 | req: Request, 32 | res: Response, 33 | next: NextFunction 34 | ): void => { 35 | //Knocking this out since we're running on a cluster 36 | //clusterRunning(res, next); 37 | console.log('Cleared clusterRunning check'); 38 | 39 | //Knocking this out until I figure out how to implement in the cluster env 40 | //prometheusAndGrafanaRunning(res, next); 41 | console.log('Cleared prometheusAndGrafanaRunning check'); 42 | 43 | return next(); 44 | }; 45 | 46 | statusController.runSetup = ( 47 | req: Request, 48 | res: Response, 49 | next: NextFunction 50 | ): void => { 51 | clusterRunning(res, next); 52 | if (!prometheusAndGrafanaRunning(res, next, false)) { 53 | try { 54 | runSetup(next); 55 | let awaitCounter = 0; 56 | const awaitSetup = setInterval((): void => { 57 | if (prometheusAndGrafanaRunning(res, next, false)) { 58 | clearInterval(awaitSetup); 59 | return next(); 60 | } else if (awaitCounter > 60) { 61 | clearInterval(awaitSetup); 62 | return next( 63 | createError( 64 | 'runSetup', 65 | 'Timed out while awaiting startup completion (30 seconds). Please try again.', 66 | 500 67 | ) 68 | ); 69 | } 70 | awaitCounter += 1; 71 | }, 500); 72 | } catch (err) { 73 | return next( 74 | createError( 75 | 'runSetup', 76 | `Encountered error while awaiting execution of startup script: ${err}`, 77 | 500, 78 | 'Encountered error while awaiting execution of startup routine' 79 | ) 80 | ); 81 | } 82 | } else { 83 | return next(); 84 | } 85 | }; 86 | 87 | export default statusController; 88 | -------------------------------------------------------------------------------- /dist/bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 2 | 3 | /** 4 | * @license React 5 | * react-dom.production.min.js 6 | * 7 | * Copyright (c) Facebook, Inc. and its affiliates. 8 | * 9 | * This source code is licensed under the MIT license found in the 10 | * LICENSE file in the root directory of this source tree. 11 | */ 12 | 13 | /** 14 | * @license React 15 | * react-jsx-runtime.production.min.js 16 | * 17 | * Copyright (c) Facebook, Inc. and its affiliates. 18 | * 19 | * This source code is licensed under the MIT license found in the 20 | * LICENSE file in the root directory of this source tree. 21 | */ 22 | 23 | /** 24 | * @license React 25 | * react.production.min.js 26 | * 27 | * Copyright (c) Facebook, Inc. and its affiliates. 28 | * 29 | * This source code is licensed under the MIT license found in the 30 | * LICENSE file in the root directory of this source tree. 31 | */ 32 | 33 | /** 34 | * @license React 35 | * scheduler.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | 43 | /** 44 | * @license React 45 | * use-sync-external-store-shim.production.min.js 46 | * 47 | * Copyright (c) Facebook, Inc. and its affiliates. 48 | * 49 | * This source code is licensed under the MIT license found in the 50 | * LICENSE file in the root directory of this source tree. 51 | */ 52 | 53 | /** 54 | * @license React 55 | * use-sync-external-store-shim/with-selector.production.min.js 56 | * 57 | * Copyright (c) Facebook, Inc. and its affiliates. 58 | * 59 | * This source code is licensed under the MIT license found in the 60 | * LICENSE file in the root directory of this source tree. 61 | */ 62 | 63 | /** 64 | * @remix-run/router v1.6.2 65 | * 66 | * Copyright (c) Remix Software Inc. 67 | * 68 | * This source code is licensed under the MIT license found in the 69 | * LICENSE.md file in the root directory of this source tree. 70 | * 71 | * @license MIT 72 | */ 73 | 74 | /** 75 | * React Router DOM v6.11.2 76 | * 77 | * Copyright (c) Remix Software Inc. 78 | * 79 | * This source code is licensed under the MIT license found in the 80 | * LICENSE.md file in the root directory of this source tree. 81 | * 82 | * @license MIT 83 | */ 84 | 85 | /** 86 | * React Router v6.11.2 87 | * 88 | * Copyright (c) Remix Software Inc. 89 | * 90 | * This source code is licensed under the MIT license found in the 91 | * LICENSE.md file in the root directory of this source tree. 92 | * 93 | * @license MIT 94 | */ 95 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grafana 5 | namespace: {{ .Values.global.namespace }} 6 | spec: 7 | {{- if not .Values.autoscaling.enabled }} 8 | replicas: {{ .Values.replicaCount }} 9 | {{- end }} 10 | selector: 11 | matchLabels: 12 | app: grafana 13 | template: 14 | metadata: 15 | name: grafana 16 | labels: 17 | app: grafana 18 | spec: 19 | containers: 20 | - name: grafana 21 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 22 | imagePullPolicy: {{ .Values.image.pullPolicy }} 23 | ports: 24 | - name: grafana 25 | containerPort: 3000 26 | resources: 27 | limits: 28 | memory: {{ .Values.resources.limits.memory }} 29 | cpu: {{ .Values.resources.limits.cpu }} 30 | requests: 31 | memory: {{ .Values.resources.requests.memory }} 32 | cpu: {{ .Values.resources.requests.cpu }} 33 | volumeMounts: 34 | - mountPath: /var/lib/grafana 35 | name: grafana-storage 36 | - mountPath: /etc/grafana/provisioning/datasources 37 | name: grafana-datasources 38 | readOnly: false 39 | env: 40 | - name: GF_SECURITY_ALLOW_EMBEDDING 41 | value: 'true' 42 | - name: GF_AUTH_ANONYMOUS_ENABLED 43 | value: 'true' 44 | - name: GF_AUTH_ANONYMOUS_ORG_NAME 45 | value: Main Org. 46 | - name: GF_AUTH_ANONYMOUS_ORG_ROLE 47 | value: Admin 48 | - name: GF_AUTH_ANONYMOUS_HIDE_VERSION 49 | value: 'true' 50 | - name: GF_FEATURE_TOGGLES_ENABLE 51 | value: publicDashboards 52 | - name: GF_SERVER_ENFORCE_DOMAIN 53 | value: 'false' 54 | - name: GF_SERVER_DOMAIN 55 | value: grafana 56 | - name: GF_SERVER_ROOT_URL 57 | value: '%(protocol)s://%(domain)s:%(http_port)s/grafanasvc' 58 | - name: GF_SERVER_SERVE_FROM_SUB_PATH 59 | value: 'true' 60 | - name: GF_DATAPROXY_KEEP_ALIVE_SECONDS 61 | value: '240' 62 | - name: GF_DATAPROXY_TIMEOUT 63 | value: '240' 64 | - name: GF_DATAPROXY_IDLE_CONN_TIMEOUT_SECONDS 65 | value: '300' 66 | - name: 'GF_LIVE_ALLOWED_ORIGINS' 67 | value: '*' 68 | - name: 'GF_LIVE_MAX_CONNECTIONS' 69 | value: '0' 70 | volumes: 71 | - name: grafana-storage 72 | emptyDir: {} 73 | #persistentVolumeClaim: 74 | # claimName: grafana-pvc 75 | - name: grafana-datasources 76 | configMap: 77 | defaultMode: 420 78 | name: grafana-datasources 79 | -------------------------------------------------------------------------------- /client/components/NodeGraph/NodeGraph.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useCallback, useEffect, useState } from 'react'; 2 | import ReactFlow, { useNodesState, useEdgesState, addEdge } from 'reactflow'; 3 | 4 | import NodeModal from './NodeModal'; 5 | import { Link, useParams } from 'react-router-dom'; 6 | import { initialGen, ChildNode, NodeGraphProps } from './utils'; 7 | 8 | import 'reactflow/dist/style.css'; 9 | import './NodeGraph.scss'; 10 | 11 | const NodeGraph: FC = ({ 12 | nodeMapInfo, 13 | podStatus, 14 | modalInfo, 15 | }) => { 16 | const params = useParams(); 17 | 18 | const [modal, setModal] = useState(false); 19 | const [status, setStatus] = useState(''); 20 | const [podHoverInfo, setPodHoverInfo] = useState({}); 21 | const [root, setRoot] = useState(params.nodeName); 22 | 23 | const children = nodeMapInfo[root]; 24 | const { initialNodes, initialEdges } = initialGen( 25 | root, 26 | children, 27 | podStatus, 28 | modalInfo 29 | ); 30 | 31 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 32 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); 33 | 34 | const onConnect = useCallback( 35 | (params) => setEdges((eds) => addEdge(params, eds)), 36 | [setEdges] 37 | ); 38 | const handleMouseEnter = (e: React.SyntheticEvent, n: ChildNode) => { 39 | if (n.status && n.modalData) { 40 | setStatus(n.status); 41 | setPodHoverInfo(n.modalData); 42 | } 43 | if (n.id !== '1') { 44 | setModal(true); 45 | } 46 | }; 47 | const handleMouseLeave = (e: React.SyntheticEvent, n: ChildNode) => { 48 | setModal(false); 49 | }; 50 | 51 | // Create links to view each node graph if there are multiple 52 | 53 | const nodeLinks = Object.keys(nodeMapInfo).map((node) => { 54 | return ( 55 |
  • 56 | { 59 | const { initialNodes, initialEdges } = initialGen( 60 | node, 61 | nodeMapInfo[node], 62 | podStatus, 63 | modalInfo 64 | ); 65 | setNodes(initialNodes); 66 | setEdges(initialEdges); 67 | }} 68 | > 69 | {node} 70 | 71 |
  • 72 | ); 73 | }); 74 | 75 | return ( 76 |
    77 |
    78 | {nodeLinks.length > 1 &&
      {nodeLinks}
    } 79 | {modal && } 80 |
    81 | 90 |
    91 |
    92 |
    93 | ); 94 | }; 95 | 96 | export default NodeGraph; 97 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | const webpack = require('webpack'); 3 | const path = require('path'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 6 | 7 | module.exports = { 8 | entry: [ 9 | // entry point of our app 10 | './client/index.tsx' 11 | ], 12 | target: 'web', 13 | output: { 14 | path: path.resolve(__dirname, 'dist'), 15 | publicPath: '/', 16 | filename: 'bundle.js', 17 | clean: true 18 | }, 19 | mode: process.env.NODE_ENV, 20 | devServer: { 21 | host: '0.0.0.0', 22 | port: 8080, 23 | // enable HMR on the devServer 24 | hot: true, 25 | // fallback to root for other urls 26 | historyApiFallback: true, 27 | 28 | static: { 29 | // match the output path 30 | directory: path.resolve(__dirname, 'dist'), 31 | // match the output 'publicPath' 32 | publicPath: '/' 33 | }, 34 | 35 | headers: { 'Access-Control-Allow-Origin': '*' }, 36 | /** 37 | * proxy is required in order to make api calls to 38 | * express server while using hot-reload webpack server 39 | * routes api fetch requests from localhost:8080/api/* (webpack dev server) 40 | * to localhost:3000/api/* (where our Express server is running) 41 | */ 42 | proxy: { 43 | '/**': { 44 | target: 'http://0.0.0.0:3000/', 45 | secure: false, 46 | headers: { 47 | Connection: 'keep-alive' 48 | } 49 | } 50 | } 51 | }, 52 | module: { 53 | rules: [ 54 | { 55 | test: /.(js|jsx|ts|tsx)$/, 56 | exclude: /node_modules/, 57 | use: { 58 | loader: 'babel-loader', 59 | options: { 60 | presets: [ 61 | '@babel/preset-env', 62 | '@babel/preset-typescript', 63 | '@babel/preset-react' 64 | ] 65 | } 66 | } 67 | }, 68 | // { 69 | // test: /\.(ts|tsx)$/, 70 | // exclude: /node_modules/, 71 | // use: ['ts-loader'], 72 | // }, 73 | { 74 | test: /.(css|scss)$/, 75 | //exclude: /node_modules/, 76 | use: [ 77 | //MiniCssExtractPlugin.loader, 78 | 'style-loader', 79 | 'css-loader', 80 | { 81 | loader: 'sass-loader', 82 | options: { 83 | sourceMap: true 84 | } 85 | } 86 | ] 87 | }, 88 | 89 | { 90 | test: /\.(png|jpg|gif)$/i, 91 | type: 'asset/resource', 92 | 93 | // Added: 94 | generator: { 95 | filename: 'images/[name]-[hash][ext]' 96 | } 97 | } 98 | ] 99 | }, 100 | plugins: [ 101 | new HtmlWebpackPlugin({ 102 | template: './client/index.html' 103 | }), 104 | new MiniCssExtractPlugin() 105 | ], 106 | resolve: { 107 | // Enable importing JS / JSX files without specifying their extension 108 | extensions: ['.js', '.jsx', '.mjs', '.ts', '.tsx'] 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /client/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState, useEffect } from 'react'; 2 | import { Routes, Route } from 'react-router-dom'; 3 | import Home from './pages/Home'; 4 | import Pods from './pages/Pods'; 5 | import Graph from './pages/Graph'; 6 | import Header from './components/Header/Header'; 7 | import Sidebar from './components/Sidebar/Sidebar'; 8 | 9 | const App: FC = () => { 10 | const [url, setUrl] = useState(''); 11 | const [podsUrl, setPodsUrl] = useState(''); 12 | const [klusterUrl, setKlusterUrl] = useState(''); 13 | const [allPodsUrl, setAllPodsUrl] = useState(''); 14 | const [podTitle, setPodTitle] = useState(''); 15 | const [podInfo, setPodInfo] = useState<{ name: string; ip: number }[]>([]); 16 | const [nodeMapInfo, setNodeMapInfo] = useState<{ [n: string]: string[] }>({}); 17 | const [podStatus, setPodStatus] = useState<{ [p: string]: string }>({}); 18 | const [nodeModalInfo, setnodeModalInfo] = useState<{ [p: string]: string }>( 19 | {} 20 | ); 21 | 22 | async function getPodNodes(): Promise { 23 | try { 24 | const response = await fetch('/prom/pods/nodes'); 25 | const data = await response.json(); 26 | if (data) { 27 | setNodeMapInfo(data['podNodes']); 28 | setnodeModalInfo(data['nodeGraph']); 29 | } 30 | } catch (error) { 31 | throw new Error(); 32 | } 33 | } 34 | async function getPodStatus(): Promise { 35 | try { 36 | const response = await fetch('prom/pod/status'); 37 | const data = await response.json(); 38 | if (data) { 39 | setPodStatus(data); 40 | } 41 | } catch (error) { 42 | throw new Error(); 43 | } 44 | } 45 | 46 | // Fetch Metrics dashboard URLs 47 | const getUrl = async ( 48 | endpoint: string, 49 | setDashboard: (url: string) => void, 50 | setOriginalDashboard: (url: string) => void 51 | ) => { 52 | try { 53 | const res = await fetch(endpoint); 54 | const url = await res.json(); 55 | setDashboard(url); 56 | setOriginalDashboard(url); 57 | } catch (error) { 58 | console.log(error); 59 | } 60 | }; 61 | 62 | useEffect(() => { 63 | getUrl('/grafana/dashboard', setUrl, setKlusterUrl); 64 | getUrl('/grafana/pods', setPodsUrl, setAllPodsUrl); 65 | getPodNodes(); 66 | getPodStatus(); 67 | }, []); 68 | 69 | return ( 70 | <> 71 |
    72 | 84 | 85 | } /> 86 | } 89 | /> 90 | 98 | } 99 | /> 100 | 101 | 102 | ); 103 | }; 104 | 105 | export default App; 106 | -------------------------------------------------------------------------------- /server/controllers/promController.ts: -------------------------------------------------------------------------------- 1 | import { MiddlewareFn, PodMetric, stausObject, podObject } from '../types'; 2 | import axios from 'axios'; 3 | 4 | const PROM_IP = 'prometheus.monitoring-kv.svc.cluster.local'; 5 | const PROM_NODE_PORT = '8080'; 6 | 7 | const getPodNames: MiddlewareFn = async (req, res, next) => { 8 | try { 9 | const response = await axios.get( 10 | `http://${PROM_IP}:${PROM_NODE_PORT}/api/v1/query?query=kube_pod_info` 11 | ); 12 | 13 | const podMetrics = response.data.data.result as PodMetric[]; 14 | const podNames: { name: string; ip: string }[] = podMetrics.map((item) => { 15 | return { name: item.metric.pod, ip: item.metric.pod_ip }; 16 | }); 17 | 18 | res.locals.names = podNames; 19 | return next(); 20 | } catch (err) { 21 | next(err); 22 | } 23 | }; 24 | 25 | const getpodIP: MiddlewareFn = async (req, res, next) => { 26 | try { 27 | const response = await axios.get( 28 | `http://${PROM_IP}:${PROM_NODE_PORT}/api/v1/query?query=kube_pod_info` 29 | ); 30 | 31 | const podMetrics = response.data.data.result as PodMetric[]; 32 | const podIPs: string[] = podMetrics.map((item) => item.metric.pod_ip); 33 | 34 | res.locals.podIPs = podIPs; 35 | return next(); 36 | } catch (err) { 37 | next(err); 38 | } 39 | }; 40 | 41 | const getPodStatuses: MiddlewareFn = async (req, res, next) => { 42 | try { 43 | const response = await axios.get( 44 | `http://${PROM_IP}:${PROM_NODE_PORT}/api/v1/query?query=kube_pod_status_phase` 45 | ); 46 | const podStatusInfo = response.data.data.result; 47 | 48 | const podStatusNames = {}; 49 | 50 | podStatusInfo.forEach((el: stausObject) => { 51 | if (el.value[1] === '1') { 52 | if (!podStatusNames[el.metric.pod]) { 53 | podStatusNames[el.metric.pod] = el.metric.phase; 54 | } 55 | } 56 | }); 57 | 58 | res.locals.podStatusNames = podStatusNames; 59 | return next(); 60 | } catch (error) {} 61 | }; 62 | 63 | //controller seperates information under two categories 64 | //podNodes returns name of the nodes and corresponding pods 65 | //nodeGraphInfo return more data on pods for NodeGraph 66 | const getPodNodes: MiddlewareFn = async (req, res, next) => { 67 | try { 68 | const response = await axios.get( 69 | `http://${PROM_IP}:${PROM_NODE_PORT}/api/v1/query?query=kube_pod_info` 70 | ); 71 | const pods = response.data.data.result; 72 | const podNodes = pods.reduce( 73 | (acc: { [key: string]: string[] }, curr: podObject) => { 74 | if (acc[curr.metric.node]) { 75 | acc[curr.metric.node].push(curr.metric.pod); 76 | } else { 77 | acc[curr.metric.node] = [curr.metric.pod]; 78 | } 79 | return acc; 80 | }, 81 | {} 82 | ); 83 | let nodeGraphInfo = {}; 84 | pods.forEach((el: podObject) => { 85 | nodeGraphInfo[el.metric.pod] = {}; 86 | nodeGraphInfo[el.metric.pod]['hostIp'] = el.metric['host_ip']; 87 | nodeGraphInfo[el.metric.pod].podIp = el.metric['pod_ip']; 88 | nodeGraphInfo[el.metric.pod].node = el.metric.node; 89 | nodeGraphInfo[el.metric.pod].nameSpace = el.metric.namespace; 90 | nodeGraphInfo[el.metric.pod].job = el.metric.job; 91 | }); 92 | let result = { 93 | podNodes: podNodes, 94 | nodeGraph: nodeGraphInfo 95 | }; 96 | res.locals.result = result; 97 | return next(); 98 | } catch (error) {} 99 | }; 100 | export default { getPodNames, getpodIP, getPodStatuses, getPodNodes }; 101 | -------------------------------------------------------------------------------- /client/components/Sidebar/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, FC, MouseEventHandler } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import './Sidebar.scss'; 4 | 5 | interface PodInfo { 6 | name: string; 7 | ip: number; 8 | } 9 | 10 | interface SidebarProps { 11 | setPodTitle: (podTitle: string) => void; 12 | setUrl: (url: string) => void; 13 | url: string; 14 | podsUrl: string; 15 | setPodsUrl: (podsUrl: string) => void; 16 | klusterUrl: string; 17 | allPodsUrl: string; 18 | podInfo: PodInfo[]; 19 | setPodInfo: (podInfo: PodInfo[]) => void; 20 | nodeMapInfo: { [n: string]: string[] }; 21 | } 22 | 23 | const Sidebar: FC = ({ 24 | setPodTitle, 25 | setUrl, 26 | url, 27 | podsUrl, 28 | setPodsUrl, 29 | klusterUrl, 30 | allPodsUrl, 31 | podInfo, 32 | setPodInfo, 33 | nodeMapInfo 34 | }) => { 35 | const getPodInfo = async () => { 36 | try { 37 | const res = await fetch('/prom/pods'); 38 | const pods = await res.json(); 39 | console.log(pods); 40 | setPodInfo(pods); 41 | } catch (error) { 42 | console.log(error); 43 | } 44 | }; 45 | 46 | useEffect(() => { 47 | getPodInfo(); 48 | }, []); 49 | 50 | const handleKlusterLink = () => { 51 | setUrl(klusterUrl); 52 | }; 53 | 54 | const handlePodLink = (e: MouseEventHandler) => { 55 | // Update page title with pod name 56 | const podClassName = e.currentTarget.classList[1]; 57 | setPodTitle(podClassName); 58 | const podName = podClassName.slice(4); 59 | 60 | // //update url for all pods metrics if PODS was clicked 61 | if (podName === 'All') { 62 | setPodsUrl(allPodsUrl); 63 | } else { 64 | //Find indexes of url to add url insert 65 | const urlIndexStart = podsUrl.indexOf('&var-Pod=') + 9; 66 | const urlIndexEnd = podsUrl.indexOf('&var-phase=', urlIndexStart); 67 | // Create URL Insert 68 | const podInfoKeys = Object.keys(podInfo); 69 | let names = podInfo.map((item) => item.name); 70 | const podIndex = names.indexOf(podName); 71 | const ipAddress = podInfo[podIndex].ip; 72 | const urlInsert = `${podName}&var-Pod_ip=${ipAddress}`; 73 | // Create newURL 74 | const urlStart = podsUrl.slice(0, urlIndexStart); 75 | const urlEnd = podsUrl.slice(urlIndexEnd); 76 | const newUrl = urlStart.concat(urlInsert).concat(urlEnd); 77 | setPodsUrl(newUrl); 78 | } 79 | }; 80 | 81 | //Create dropdown pod links by mapping through podLinks 82 | const podLinks: JSX.Element[] = podInfo.map((pod: PodInfo) => { 83 | return ( 84 |
  • 85 | 90 | {pod.name} 91 | 92 |
  • 93 | ); 94 | }); 95 | 96 | const firstNodeName = Object.keys(nodeMapInfo)[0]; 97 | 98 | return ( 99 | 119 | ); 120 | }; 121 | 122 | export default Sidebar; 123 | -------------------------------------------------------------------------------- /server/server.ts: -------------------------------------------------------------------------------- 1 | import { Errback, NextFunction, Request, Response } from 'express'; 2 | import { createProxyMiddleware } from 'http-proxy-middleware'; 3 | import express from 'express'; 4 | import dotenv from 'dotenv'; 5 | import bodyParser from 'body-parser'; 6 | import cookieParser from 'cookie-parser'; 7 | import path from 'path'; 8 | import promRouter from './routes/promRouter'; 9 | import grafanaRouter from './routes/grafanaRouter'; 10 | import statusRouter from './routes/statusRouter'; 11 | import { MethodError } from './types'; 12 | 13 | //Create application and set key constants 14 | 15 | const app = express(); 16 | dotenv.config(); 17 | 18 | let staticPath: string; 19 | let mainPath: string; 20 | 21 | const PORT = 3000; 22 | 23 | const GRAF_HOST = 'grafana'; 24 | const GRAF_PORT = 3000; 25 | 26 | //Configure proxy routing data/visulization requests from the exposed frontend 27 | //to the Grafana service running in cluster 28 | 29 | const grafProxy = createProxyMiddleware({ 30 | target: `http://${GRAF_HOST}:${GRAF_PORT}`, 31 | changeOrigin: true, 32 | ws: true, 33 | auth: 'admin:admin', 34 | timeout: 30000, 35 | onProxyReq: function (proxyReq, req, res) { 36 | proxyReq.shouldKeepAlive = true; 37 | //proxyReq.setHeader('host','klusterview.monitoring-kv.svc.cluster.local') 38 | proxyReq.setHeader('origin', 'http://grafana:3000'); 39 | console.log(proxyReq.getHeaders()); 40 | console.log(req.headers); 41 | }, 42 | onProxyRes: function (proxyRes, req, res) { 43 | proxyRes.headers['Access-Control-Allow-Origin'] = '*'; 44 | proxyRes.headers['access-control-allow-credentials'] = 'true'; 45 | proxyRes.headers['access-control-allow-methods'] = 46 | 'GET, POST, PUT, DELETE, OPTIONS'; 47 | proxyRes.headers['access-control-allow-headers'] = 48 | 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With'; 49 | }, 50 | onError: (err: Error, req: Request, res: Response) => { 51 | console.log(err); 52 | console.log(res); 53 | res.status(500); 54 | res.send(err); 55 | } 56 | }); 57 | 58 | //Use proxy before everything else, and specifically before bodyParser, 59 | //which invalidates request bodies prior to forwarding 60 | app.use('/grafanasvc', grafProxy); 61 | app.use(bodyParser.json()); 62 | app.use(express.json()); 63 | app.use(cookieParser()); 64 | 65 | //Set asset paths based on environment 66 | if (process.env.NODE_ENV === 'production') { 67 | staticPath = '../dist'; 68 | mainPath = '../dist/index.html'; 69 | } else if (process.env.NODE_ENV === 'development') { 70 | staticPath = '../client'; 71 | mainPath = '../client/index.html'; 72 | } 73 | 74 | app.get('/', (req: Request, res: Response) => { 75 | return res.status(200).sendFile(path.resolve(__dirname, mainPath)); 76 | }); 77 | 78 | app.use(express.static(path.resolve(__dirname, staticPath))); 79 | 80 | //Prom router gets data from PromAPI 81 | app.use('/prom', promRouter); 82 | 83 | //Grafana router gets visualization items from Grafana Dashboard 84 | app.use('/grafana', grafanaRouter); 85 | 86 | //Status router checks Kubernetes status and takes action as appropriate 87 | app.use('/status', statusRouter); 88 | 89 | //404 Handler 90 | app.use('*', (req: Request, res: Response) => { 91 | console.log('run'); 92 | return res.status(404).json('Not found'); 93 | }); 94 | 95 | //global error handler 96 | app.use( 97 | ( 98 | err: Errback | MethodError, 99 | req: Request, 100 | res: Response, 101 | next: NextFunction 102 | ) => { 103 | const defaultErr = { 104 | log: 'Express error handler caught unknown middleware error', 105 | status: 400, 106 | message: { err: 'An unknown error occurred' } 107 | }; 108 | const errorObj = Object.assign(defaultErr, err); 109 | console.log(errorObj.log); 110 | return res.status(errorObj.status).json(errorObj.message); 111 | } 112 | ); 113 | 114 | //app starts on port 3000 115 | const server = app.listen(PORT, () => { 116 | console.log(`Server started to listen on port ${PORT}`); 117 | }); 118 | server.on('upgrade', grafProxy.upgrade); 119 | server.keepAliveTimeout = 30000; 120 | server.headersTimeout = 31000; 121 | -------------------------------------------------------------------------------- /server/controllers/initializationController.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Request, 3 | Response, 4 | RequestHandler, 5 | NextFunction, 6 | Errback 7 | } from 'express'; 8 | import { MethodError } from '../types'; 9 | import * as path from 'path'; 10 | import * as fs from 'fs'; 11 | 12 | import b64 from 'base-64'; 13 | const encode = b64.encode; 14 | 15 | const GRAF_IP = 'grafana.monitoring-kv.svc.cluster.local'; 16 | const GRAF_NODE_PORT = '3000'; 17 | 18 | //Define module-level error generator 19 | const createError = ( 20 | method: String, 21 | log: String, 22 | status: Number, 23 | message: String = log 24 | ): MethodError => { 25 | return { 26 | log: `Encountered error in initializationController.${method}: ${log}`, 27 | status: status, 28 | message: { err: message } 29 | }; 30 | }; 31 | 32 | //Define and initialize init controller 33 | type InitializationController = { [k: string]: RequestHandler }; 34 | const initializationController: InitializationController = {}; 35 | 36 | //Logs in as admin at start of session 37 | initializationController.login = async ( 38 | req: Request, 39 | res: Response, 40 | next: NextFunction 41 | ): Promise => { 42 | try { 43 | const response = await fetch(`http://${GRAF_IP}:${GRAF_NODE_PORT}/login`, { 44 | method: 'POST', 45 | headers: { 46 | Authorization: `Basic ${encode(`admin:admin`)}`, 47 | 'Content-Type': 'application/json' 48 | }, 49 | body: JSON.stringify({ 50 | user: 'admin', 51 | password: 'admin' 52 | }) 53 | }); 54 | 55 | res.locals.login = await response.json(); 56 | 57 | if (!response.ok) { 58 | const resp = await response.json(); 59 | console.log(resp); 60 | return next( 61 | createError( 62 | 'login', 63 | `Encountered error while logging into Grafana. Response other than OK: ${resp}`, 64 | 500 65 | ) 66 | ); 67 | } 68 | 69 | console.log('User signed in successfully'); 70 | return next(); 71 | } catch (error) { 72 | return next( 73 | createError( 74 | 'login', 75 | `Received error while attempting to sign in: ${error}`, 76 | 500 77 | ) 78 | ); 79 | } 80 | }; 81 | 82 | //Loads main and pod dashboards upon first deployment 83 | initializationController.initializeGrafana = async ( 84 | req: Request, 85 | res: Response, 86 | next: NextFunction 87 | ): Promise => { 88 | //Fetch data for main, pod dashboards 89 | const mainDashboardData = fs 90 | .readFileSync( 91 | path.resolve( 92 | __dirname, 93 | '../../deployment/_dashboards/Aggregate_Metrics.json' 94 | ) 95 | ) 96 | .toString(); 97 | const podsDashboardData = fs 98 | .readFileSync( 99 | path.resolve( 100 | __dirname, 101 | '../../deployment/_dashboards/pod_level_metrics.json' 102 | ) 103 | ) 104 | .toString(); 105 | //Upload aggregate metrics dashboard 106 | try { 107 | console.log(encode('admin:admin')); 108 | const mainResp = await fetch( 109 | `http://${GRAF_IP}:${GRAF_NODE_PORT}/api/dashboards/db`, 110 | { 111 | method: 'POST', 112 | body: JSON.stringify({ dashboard: JSON.parse(mainDashboardData) }), 113 | headers: { 114 | Authorization: `Basic ${encode(`admin:admin`)}`, 115 | 'Content-Type': 'application/json' 116 | } 117 | } 118 | ); 119 | 120 | res.locals.maindash = await mainResp.json(); 121 | 122 | if (!mainResp.ok) { 123 | if (mainResp.status !== 412) { 124 | const resp = await mainResp.json(); 125 | console.log(resp); 126 | return next( 127 | createError( 128 | 'initializeGrafana', 129 | `Encountered error while initializing main dashboard. Response other than OK: ${mainResp}`, 130 | 500 131 | ) 132 | ); 133 | } 134 | } 135 | } catch (err) { 136 | return next( 137 | createError( 138 | 'initializeGrafana', 139 | `Encountered error while initializing main dashboard: ${err}`, 140 | 500 141 | ) 142 | ); 143 | } 144 | 145 | //Upload pod metrics dashboard 146 | try { 147 | const podResp = await fetch( 148 | `http://${GRAF_IP}:${GRAF_NODE_PORT}/api/dashboards/db`, 149 | { 150 | method: 'POST', 151 | body: JSON.stringify({ dashboard: JSON.parse(podsDashboardData) }), 152 | headers: { 153 | Authorization: `Basic ${encode(`admin:admin`)}`, 154 | 'Content-Type': 'application/json' 155 | } 156 | } 157 | ); 158 | res.locals.poddash = await podResp.json(); 159 | if (!podResp.ok) { 160 | if (podResp.status !== 412) { 161 | const resp = await podResp.json(); 162 | console.log(resp); 163 | return next( 164 | createError( 165 | 'initializeGrafana', 166 | 'Encountered error while initializing pods dashboard', 167 | 500 168 | ) 169 | ); 170 | } 171 | } 172 | } catch (err) { 173 | return next( 174 | createError( 175 | 'initializeGrafana', 176 | `Encountered error while initializing pods dashboard: ${err}`, 177 | 500 178 | ) 179 | ); 180 | } 181 | 182 | return next(); 183 | }; 184 | 185 | export default initializationController; 186 | -------------------------------------------------------------------------------- /server/controllers/helpers/statusHelpers.ts: -------------------------------------------------------------------------------- 1 | import { Response, NextFunction } from 'express'; 2 | import { MethodError } from '../../types'; 3 | import * as child from 'child_process'; 4 | import * as path from 'path'; 5 | 6 | //Define path to scripts folder from project root as well as path to project root 7 | const SCRIPTS_PATH = '/scripts'; 8 | const ROOT_PATH = '../../..'; 9 | 10 | const createError = ( 11 | method: String, 12 | log: String, 13 | status: Number, 14 | message: String = log 15 | ): MethodError => { 16 | return { 17 | log: `Encountered error in statusController.${method}: ${log}`, 18 | status: status, 19 | message: { err: message } 20 | }; 21 | }; 22 | 23 | //Define individual status check methods 24 | /////////////////////////////// 25 | export const clusterRunning = ( 26 | res: Response, 27 | next: NextFunction, 28 | throwErr: boolean = true 29 | ): void | boolean => { 30 | try { 31 | const clusterInfo = child.execSync(`kubectl cluster-info`).toString(); 32 | return true; 33 | } catch (err) { 34 | if (throwErr) { 35 | return next( 36 | createError( 37 | 'clusterRunning', 38 | `No running cluster found when calling kubectl cluster-info: Encountered error ${err}`, 39 | 500, 40 | 'No running cluster found when calling kubectl cluster-info. Please ensure cluster to be monitored is operational and accessible.' 41 | ) 42 | ); 43 | } else { 44 | return false; 45 | } 46 | } 47 | }; 48 | 49 | export const prometheusAndGrafanaRunning = ( 50 | res: Response, 51 | next: NextFunction, 52 | throwErr: boolean = true 53 | ): void | boolean => { 54 | let runningImages: string[]; 55 | try { 56 | runningImages = child 57 | .execSync( 58 | `kubectl get pods --all-namespaces -o jsonpath="{.items[*].spec.containers[*].image}" |\\ 59 | tr -s '[[:space:]]' '\\n' |\\ 60 | sort |\\ 61 | uniq` 62 | ) 63 | .toString() 64 | .split('\n'); 65 | } catch (err) { 66 | if (throwErr) { 67 | return next( 68 | createError( 69 | 'prometheusAndGrafanaRunning', 70 | `Encountered error when querying kubectl for running container images: ${err}`, 71 | 500, 72 | 'Encountered error when querying kubectl for running container images' 73 | ) 74 | ); 75 | } else { 76 | return false; 77 | } 78 | } 79 | 80 | let prom: Boolean, grafana: Boolean, ksm: Boolean; 81 | prom = grafana = ksm = false; 82 | 83 | while (!(prom && grafana && ksm) && runningImages.length) { 84 | const image = runningImages.pop(); 85 | //console.log(image); 86 | if (image?.search(/^grafana\/g/g) !== -1) grafana = true; 87 | if (image?.search(/^prom\/prom/g) !== -1) prom = true; 88 | if (image?.search(/kube-state-metrics/g) !== -1) ksm = true; 89 | //console.log(`Prom: ${prom}, Grafana: ${grafana}, KSM: ${ksm}`); 90 | } 91 | 92 | if (ksm && grafana && prom) return true; 93 | else { 94 | if (throwErr) { 95 | if (!ksm) 96 | return next( 97 | createError( 98 | 'prometheusAndGrafanaRunning', 99 | 'Kube State Metrics not detected among running containers. Please verify.', 100 | 500 101 | ) 102 | ); 103 | else if (!prom) 104 | return next( 105 | createError( 106 | 'prometheusAndGrafanaRunning', 107 | 'Prometheus not detected among running containers. Please verify.', 108 | 500 109 | ) 110 | ); 111 | else if (!grafana) 112 | return next( 113 | createError( 114 | 'prometheusAndGrafanaRunning', 115 | 'Grafana not detected among running containers. Please verify.', 116 | 500 117 | ) 118 | ); 119 | return next( 120 | createError('prometheusAndGrafanaRunning', 'Unknown error', 500) 121 | ); 122 | } 123 | } 124 | return false; 125 | }; 126 | 127 | //Define individual setup methods 128 | export const runSetup = (next: NextFunction): void => { 129 | try { 130 | const setup = child.spawn(`./setup.sh`, { 131 | cwd: path.join(__dirname, ROOT_PATH, SCRIPTS_PATH) 132 | }); 133 | 134 | setup.stdout.on('data', (data) => { 135 | console.log(data.toString()); 136 | }); 137 | 138 | setup.addListener('error', (err) => { 139 | return next( 140 | createError( 141 | 'runSetup', 142 | `Encountered error while executing setup script: ${err}`, 143 | 500, 144 | 'Encountered error while executing setup script.' 145 | ) 146 | ); 147 | }); 148 | 149 | setup.addListener('exit', (code, signal) => { 150 | if (code === 0) { 151 | return; 152 | } else if (code) { 153 | return next( 154 | createError( 155 | 'runSetup', 156 | `Setup.sh exited with non-zero exit code ${code}`, 157 | 500, 158 | 'Error executing setup script' 159 | ) 160 | ); 161 | } else { 162 | return next( 163 | createError( 164 | 'runSetup', 165 | `Setup.sh exited with signal ${signal}`, 166 | 500, 167 | 'Error executing setup script' 168 | ) 169 | ); 170 | } 171 | }); 172 | 173 | //console.log(paths); 174 | } catch (error) { 175 | console.log(error); 176 | } 177 | return; 178 | }; 179 | -------------------------------------------------------------------------------- /deployment/klusterview_chart_source/charts/grafana/charts/prometheus/templates/config-map.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: prometheus-server-conf 5 | labels: 6 | name: prometheus-server-conf 7 | namespace: {{ .Values.global.namespace }} 8 | data: 9 | prometheus.rules: |- 10 | groups: 11 | - name: devopscube demo alert 12 | rules: 13 | - alert: High Pod Memory 14 | expr: sum(container_memory_usage_bytes) > 1 15 | for: 1m 16 | labels: 17 | severity: slack 18 | annotations: 19 | summary: High Memory Usage 20 | prometheus.yml: |- 21 | global: 22 | scrape_interval: 5s 23 | evaluation_interval: 5s 24 | rule_files: 25 | - /etc/prometheus/prometheus.rules 26 | scrape_configs: 27 | - job_name: 'node-exporter' 28 | kubernetes_sd_configs: 29 | - role: endpoints 30 | relabel_configs: 31 | - source_labels: [__meta_kubernetes_endpoints_name] 32 | regex: 'node-exporter' 33 | action: keep 34 | - job_name: 'kubernetes-apiservers' 35 | kubernetes_sd_configs: 36 | - role: endpoints 37 | scheme: https 38 | tls_config: 39 | ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 40 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 41 | relabel_configs: 42 | - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name] 43 | action: keep 44 | regex: default;kubernetes;https 45 | - job_name: 'kubernetes-nodes' 46 | scheme: https 47 | tls_config: 48 | ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 49 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 50 | kubernetes_sd_configs: 51 | - role: node 52 | relabel_configs: 53 | - action: labelmap 54 | regex: __meta_kubernetes_node_label_(.+) 55 | - target_label: __address__ 56 | replacement: kubernetes.default.svc:443 57 | - source_labels: [__meta_kubernetes_node_name] 58 | regex: (.+) 59 | target_label: __metrics_path__ 60 | replacement: /api/v1/nodes/${1}/proxy/metrics 61 | - job_name: 'kubernetes-pods' 62 | kubernetes_sd_configs: 63 | - role: pod 64 | relabel_configs: 65 | - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] 66 | action: keep 67 | regex: true 68 | - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] 69 | action: replace 70 | target_label: __metrics_path__ 71 | regex: (.+) 72 | - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port] 73 | action: replace 74 | regex: ([^:]+)(?::\d+)?;(\d+) 75 | replacement: $1:$2 76 | target_label: __address__ 77 | - action: labelmap 78 | regex: __meta_kubernetes_pod_label_(.+) 79 | - source_labels: [__meta_kubernetes_namespace] 80 | action: replace 81 | target_label: kubernetes_namespace 82 | - source_labels: [__meta_kubernetes_pod_name] 83 | action: replace 84 | target_label: kubernetes_pod_name 85 | - job_name: 'kube-state-metrics' 86 | static_configs: 87 | - targets: ['kube-state-metrics.kube-system.svc.cluster.local:8080'] 88 | - job_name: 'kubernetes-cadvisor' 89 | scheme: https 90 | tls_config: 91 | ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 92 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 93 | kubernetes_sd_configs: 94 | - role: node 95 | relabel_configs: 96 | - action: labelmap 97 | regex: __meta_kubernetes_node_label_(.+) 98 | - target_label: __address__ 99 | replacement: kubernetes.default.svc:443 100 | - source_labels: [__meta_kubernetes_node_name] 101 | regex: (.+) 102 | target_label: __metrics_path__ 103 | replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor 104 | - job_name: 'kubernetes-service-endpoints' 105 | kubernetes_sd_configs: 106 | - role: endpoints 107 | relabel_configs: 108 | - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] 109 | action: keep 110 | regex: true 111 | - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme] 112 | action: replace 113 | target_label: __scheme__ 114 | regex: (https?) 115 | - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] 116 | action: replace 117 | target_label: __metrics_path__ 118 | regex: (.+) 119 | - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] 120 | action: replace 121 | target_label: __address__ 122 | regex: ([^:]+)(?::\d+)?;(\d+) 123 | replacement: $1:$2 124 | - action: labelmap 125 | regex: __meta_kubernetes_service_label_(.+) 126 | - source_labels: [__meta_kubernetes_namespace] 127 | action: replace 128 | target_label: kubernetes_namespace 129 | - source_labels: [__meta_kubernetes_service_name] 130 | action: replace 131 | target_label: kubernetes_name 132 | -------------------------------------------------------------------------------- /deployment/_manual_install/prometheus/config-map.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: prometheus-server-conf 5 | labels: 6 | name: prometheus-server-conf 7 | namespace: monitoring-kv 8 | data: 9 | prometheus.rules: |- 10 | groups: 11 | - name: devopscube demo alert 12 | rules: 13 | - alert: High Pod Memory 14 | expr: sum(container_memory_usage_bytes) > 1 15 | for: 1m 16 | labels: 17 | severity: slack 18 | annotations: 19 | summary: High Memory Usage 20 | prometheus.yml: |- 21 | global: 22 | scrape_interval: 5s 23 | evaluation_interval: 5s 24 | rule_files: 25 | - /etc/prometheus/prometheus.rules 26 | alerting: 27 | alertmanagers: 28 | - scheme: http 29 | static_configs: 30 | - targets: 31 | - "alertmanager.monitoring.svc:9093" 32 | scrape_configs: 33 | - job_name: 'node-exporter' 34 | kubernetes_sd_configs: 35 | - role: endpoints 36 | relabel_configs: 37 | - source_labels: [__meta_kubernetes_endpoints_name] 38 | regex: 'node-exporter' 39 | action: keep 40 | - job_name: 'kubernetes-apiservers' 41 | kubernetes_sd_configs: 42 | - role: endpoints 43 | scheme: https 44 | tls_config: 45 | ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 46 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 47 | relabel_configs: 48 | - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name] 49 | action: keep 50 | regex: default;kubernetes;https 51 | - job_name: 'kubernetes-nodes' 52 | scheme: https 53 | tls_config: 54 | ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 55 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 56 | kubernetes_sd_configs: 57 | - role: node 58 | relabel_configs: 59 | - action: labelmap 60 | regex: __meta_kubernetes_node_label_(.+) 61 | - target_label: __address__ 62 | replacement: kubernetes.default.svc:443 63 | - source_labels: [__meta_kubernetes_node_name] 64 | regex: (.+) 65 | target_label: __metrics_path__ 66 | replacement: /api/v1/nodes/${1}/proxy/metrics 67 | - job_name: 'kubernetes-pods' 68 | kubernetes_sd_configs: 69 | - role: pod 70 | relabel_configs: 71 | - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] 72 | action: keep 73 | regex: true 74 | - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] 75 | action: replace 76 | target_label: __metrics_path__ 77 | regex: (.+) 78 | - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port] 79 | action: replace 80 | regex: ([^:]+)(?::\d+)?;(\d+) 81 | replacement: $1:$2 82 | target_label: __address__ 83 | - action: labelmap 84 | regex: __meta_kubernetes_pod_label_(.+) 85 | - source_labels: [__meta_kubernetes_namespace] 86 | action: replace 87 | target_label: kubernetes_namespace 88 | - source_labels: [__meta_kubernetes_pod_name] 89 | action: replace 90 | target_label: kubernetes_pod_name 91 | - job_name: 'kube-state-metrics' 92 | static_configs: 93 | - targets: ['kube-state-metrics.kube-system.svc.cluster.local:8080'] 94 | - job_name: 'kubernetes-cadvisor' 95 | scheme: https 96 | tls_config: 97 | ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 98 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 99 | kubernetes_sd_configs: 100 | - role: node 101 | relabel_configs: 102 | - action: labelmap 103 | regex: __meta_kubernetes_node_label_(.+) 104 | - target_label: __address__ 105 | replacement: kubernetes.default.svc:443 106 | - source_labels: [__meta_kubernetes_node_name] 107 | regex: (.+) 108 | target_label: __metrics_path__ 109 | replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor 110 | - job_name: 'kubernetes-service-endpoints' 111 | kubernetes_sd_configs: 112 | - role: endpoints 113 | relabel_configs: 114 | - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] 115 | action: keep 116 | regex: true 117 | - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme] 118 | action: replace 119 | target_label: __scheme__ 120 | regex: (https?) 121 | - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] 122 | action: replace 123 | target_label: __metrics_path__ 124 | regex: (.+) 125 | - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] 126 | action: replace 127 | target_label: __address__ 128 | regex: ([^:]+)(?::\d+)?;(\d+) 129 | replacement: $1:$2 130 | - action: labelmap 131 | regex: __meta_kubernetes_service_label_(.+) 132 | - source_labels: [__meta_kubernetes_namespace] 133 | action: replace 134 | target_label: kubernetes_namespace 135 | - source_labels: [__meta_kubernetes_service_name] 136 | action: replace 137 | target_label: kubernetes_name 138 | -------------------------------------------------------------------------------- /collateral/Medium.md: -------------------------------------------------------------------------------- 1 | # Introducing: KlusterView 2 | 3 | ### A lightweight, ready-to-deploy Kubernetes metrics visualizer for the rest of us 4 | 5 | Anyone who works regularly with Kubernetes environments knows that, despite the array of tools on the market for monitoring the health and performance of a cluster, knowing precisely what metrics are meaningful (and how to access them for quick reference) can be challenging. This is doubly true for individual developers as well as small and midsize enterprises without large dedicated DevOps teams: the gold standard for performance monitoring in Kubernetes (and other complex orchestration platfoms), the Prometheus/Grafana/Kube State Metrics (PGK) stack, requires extensive up-front configuration, and offers a dizzying array of metrics and visualization options that can bewilder the uninitiated. 6 | 7 | Enter KlusterView: a universally compatible plug-and-play visualization tool that distills the best of what the PGK stack has to offer into a streamlined interface, offering a concise and crystal-clear picture of cluster health without any platform-specific setup or specialized configuration. Users can install KlusterView and its dependencies in a single step (details here), and be up and running in minutes, putting critical performance information within a moment's reach wherever and whenever you access your cluster. 8 | 9 | ## KlusterView v1.0 offers: 10 | 11 | - An embedded dashboard displaying moment-by-moment resource usage (CPU, memory, disk), pod and node health status, and trends in these metrics over a variable time window, rendering any concerns immediately visible 12 | - A schematic representation of cluster nodes and their constitutent pods providing top-line pod-level performance metrics at a glance 13 | - A specialized dashboard for displaying detailed pod-level metrics, including historical resource usage data, to speed identification of container-specific issues 14 | - A **single point of access** to all of this, eliminating the need to expose additional in-cluster tools to the local network 15 | 16 | **In addition, for those wishing to further integrate KlusterView with existing monitoring tools, we offer:** 17 | 18 | - A prebuilt, development-ready application image featuring hot reloading 19 | - A robust front- and back-end testing suite to safeguard core functionality and simplify maintainability 20 | - A fully accessible set of installation scripts and YAML configuration files, categorized and indexed for easy modification 21 | 22 | ### How to Get Started 23 | 24 | KlusterView and its dependencies can be installed using either a Helm chart or an installation script. Please see our [GitHub](https://github.com/oslabs-beta/KlusterView) or ArtifactHub repositories for details. 25 | 26 | After installing, simply point your browser to Port 31001 on any Node in your cluster, and check out your 27 | 28 | ## The Path Forward 29 | 30 | KlusterView is an open-source product that was developed under the tech accelerator Open Source Labs. If you are interested in contributing, our source code and iteration notes can be found on the [KlusterView GitHub](https://github.com/oslabs-beta/KlusterView). **We welcome anyone interested in this product to submit issues and/or contribute to its development** -- our users and our community make us stronger! 31 | 32 | Our development roadmap, including both current features and future plans, is as follows: 33 | 34 | | Feature | Status | 35 | | ------------------------------------------------------------------------------ | ------ | 36 | | Cluster- and node-level resource usage and pod status monitoring | ✅ | 37 | | Detailed pod-level resource usage and status monitoring | ✅ | 38 | | Cluster- and node-level structural information via node graph | ✅ | 39 | | Full TypeScript implementation | ✅ | 40 | | Full support for Grafana Live features | ⏳ | 41 | | In-window support for dashboard customization | ⏳ | 42 | | Integration with Grafana Role-Based Access Control | 🙏🏻 | 43 | | Live monitoring of pod-level error and information logs | 🙏🏻 | 44 | | Integration with Grafana Alerts Management vis a vis resource usage and status | 🙏🏻 | 45 | 46 | - ✅ = Ready to use 47 | - ⏳ = In progress 48 | - 🙏🏻 = Looking for contributors 49 | 50 | ## Meet the Team 51 | 52 | The core KlusterView development team can be found at the links below, or at klusterview@gmail.com. Please reach out with any questions/comments, or to continue the conversation! 53 | 54 | 55 | 56 | 65 | 73 | 81 | 89 | 90 | - 💻 = Website 91 | - 🖇️ = LinkedIn 92 | - 🐙 = Github 93 | -------------------------------------------------------------------------------- /__tests__/frontend-react.test.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import '@testing-library/jest-dom'; 5 | import React from 'react'; 6 | import { render, screen, cleanup, fireEvent } from '@testing-library/react'; 7 | import Header from '../client/components/Header/Header'; 8 | import Sidebar from '../client/components/Sidebar/Sidebar'; 9 | import { Routes, Route, MemoryRouter } from 'react-router-dom'; 10 | import { getNodeIPs } from '../server/controllers/initializationController'; 11 | import Dashboard from '../client/components/Dashboard/Dashboard'; 12 | import Home from '../client/pages/Home'; 13 | 14 | const IPList = getNodeIPs(); 15 | const GRAF_IP = IPList[0]; 16 | 17 | afterEach(() => { 18 | cleanup(); 19 | }); 20 | 21 | describe('Header', () => { 22 | test('renders the header with logo and name', () => { 23 | render(
    ); 24 | const logo = screen.getByText('KV'); 25 | const name1 = screen.getByText('Kluster'); 26 | const name2 = screen.getByText('View'); 27 | expect(logo).toBeInTheDocument(); 28 | expect(name1).toBeInTheDocument(); 29 | expect(name2).toBeInTheDocument(); 30 | }); 31 | 32 | test('has correct class names', () => { 33 | render(
    ); 34 | 35 | const headerElement = screen.getByRole('banner'); 36 | const logo = screen.getByText('KV'); 37 | const name1 = screen.getByText('Kluster'); 38 | const name2 = screen.getByText('View'); 39 | 40 | expect(headerElement).toHaveClass('header'); 41 | expect(logo).toHaveClass('header-logo'); 42 | expect(name1).not.toHaveClass('header-logo'); 43 | expect(name2).toHaveClass('header-name'); 44 | }); 45 | }); 46 | 47 | describe('Sidebar component', () => { 48 | test('renders all the navigation links', () => { 49 | const setPodTitle = jest.fn(); 50 | const setUrl = jest.fn(); 51 | const setPodsUrl = jest.fn(); 52 | const podInfo = [ 53 | { name: 'Pod1', ip: 1 }, 54 | { name: 'Pod2', ip: 2 }, 55 | { name: 'Pod3', ip: 3 }, 56 | ]; 57 | 58 | render( 59 | 60 | {}} 70 | nodeMapInfo={{}} 71 | /> 72 | 73 | ); 74 | 75 | // Check if all the navigation links are rendered 76 | const klusterLink = screen.getByText('KLUSTER'); 77 | const nodeMapLink = screen.getByText('NODE MAP'); 78 | const podsLink = screen.getByText('PODS'); 79 | 80 | expect(klusterLink).toBeInTheDocument(); 81 | expect(nodeMapLink).toBeInTheDocument(); 82 | expect(podsLink).toBeInTheDocument(); 83 | }); 84 | 85 | test('handles click on KLUSTER link correctly', () => { 86 | const setUrl = jest.fn(); 87 | const klusterUrl = '/kluster'; 88 | 89 | render( 90 | 91 | {}} 93 | setUrl={setUrl} 94 | url='' 95 | podsUrl='' 96 | setPodsUrl={() => {}} 97 | klusterUrl={klusterUrl} 98 | allPodsUrl='' 99 | podInfo={[]} 100 | setPodInfo={() => {}} 101 | nodeMapInfo={{}} 102 | /> 103 | 104 | ); 105 | 106 | const klusterLink = screen.getByText('KLUSTER'); 107 | fireEvent.click(klusterLink); 108 | 109 | expect(setUrl).toHaveBeenCalledTimes(1); 110 | expect(setUrl).toHaveBeenCalledWith(klusterUrl); 111 | }); 112 | 113 | test('handles click on PODS link correctly', () => { 114 | const setPodTitle = jest.fn(); 115 | const setPodsUrl = jest.fn(); 116 | const allPodsUrl = '/pods/all'; 117 | const podInfo = [ 118 | { name: 'Pod1', ip: 1 }, 119 | { name: 'Pod2', ip: 2 }, 120 | ]; 121 | 122 | render( 123 | 124 | {}} 127 | url='' 128 | podsUrl='' 129 | setPodsUrl={setPodsUrl} 130 | klusterUrl='' 131 | allPodsUrl={allPodsUrl} 132 | podInfo={podInfo} 133 | setPodInfo={() => {}} 134 | nodeMapInfo={{}} 135 | /> 136 | 137 | ); 138 | 139 | const podsLink = screen.getByText('PODS'); 140 | fireEvent.click(podsLink); 141 | 142 | expect(setPodTitle).toHaveBeenCalledTimes(1); 143 | expect(setPodTitle).toHaveBeenCalledWith('Pod-All'); 144 | expect(setPodsUrl).toHaveBeenCalledTimes(1); 145 | expect(setPodsUrl).toHaveBeenCalledWith(allPodsUrl); 146 | }); 147 | }); 148 | 149 | describe('Home', () => { 150 | test('renders Home page', () => { 151 | const { container } = render(); 152 | const homeElement = container.firstChild; 153 | expect(homeElement).toBeInTheDocument(); 154 | }); 155 | 156 | test('renders Home page title', () => { 157 | render(); 158 | const pageTitle = screen.getByText('Kluster Metrics'); 159 | expect(pageTitle).toBeInTheDocument(); 160 | }); 161 | 162 | test('renders Dashboard component', () => { 163 | const { container } = render(); 164 | const dashboardComponent = container.lastChild; 165 | expect(dashboardComponent).toBeInTheDocument(); 166 | }); 167 | }); 168 | 169 | describe('Dashboard', () => { 170 | test('renders the dashboard component', () => { 171 | const testUrl = `http://admin:admin@${GRAF_IP}:32000/api/search?type=dash-db`; 172 | const { container } = render(); 173 | const iframeElement = container.firstChild.firstChild; 174 | 175 | expect(iframeElement).toBeInTheDocument(); 176 | expect(iframeElement).toHaveAttribute('src', testUrl); 177 | }); 178 | 179 | test('has correct class names', () => { 180 | const testUrl = `http://admin:admin@${GRAF_IP}:32000/api/search?type=dash-db`; 181 | const { container } = render(); 182 | const dashboardContainer = container.firstChild; 183 | const iframeElement = dashboardContainer.firstChild; 184 | 185 | expect(dashboardContainer).toBeInTheDocument(); 186 | expect(dashboardContainer).toHaveClass('dashboard-container'); 187 | expect(iframeElement).toBeInTheDocument(); 188 | expect(iframeElement).toHaveClass('dashboard'); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /__tests__/supertest.test.ts: -------------------------------------------------------------------------------- 1 | import request from 'supertest'; 2 | import app from '../server/server'; 3 | import grafanaRouter from '../server/routes/grafanaRouter'; 4 | import promRouter from '../server/routes/promRouter'; 5 | 6 | //**************************TESTS FOR SERVER.TS *********************/ 7 | 8 | // beforeAll(async () => { 9 | // server = app.listen(3000, () => { 10 | // console.log('test server started'); 11 | // }); // Start the server and store the instance 12 | // }); 13 | 14 | // afterAll((done) => { 15 | // server.close(done); // Close the server when all tests are finished 16 | // }); 17 | 18 | describe('serving HTML files', () => { 19 | it('should respond with status 200 if successfully served static files', (done) => { 20 | request(app) 21 | .get('/') 22 | .expect('Content-Type', /text\/html; charset=[uU][tT][fF]-8/) 23 | .expect(200) 24 | .end((err, response) => { 25 | if (err) return done(err); 26 | done(); 27 | }); 28 | }); 29 | }); 30 | 31 | describe('Other endpoint router', () => { 32 | it('should respond with status 404 and return "Not found"', (done) => { 33 | request(app) 34 | .get('/testing123') 35 | .expect(404) 36 | .end((err, response) => { 37 | if (err) return done(err); 38 | 39 | expect(response.body).toBe('Not found'); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | //**************************TESTS FOR promRouter.TS *********************/ 46 | 47 | describe('Prom Router', () => { 48 | it('should get data from PromAPI', async () => { 49 | const response = await request(app).get('/prom/pods/'); 50 | expect(response.status).toBe(200); 51 | }); 52 | it('server should respond with an array of objects that should contain a name and ip property', async () => { 53 | const response = await request(app).get('/prom/pods/'); 54 | expect(response.body.length).toBeGreaterThan(0); 55 | expect(typeof response.body[0].name).toBe('string'); 56 | expect(typeof response.body[0].ip).toBe('string'); 57 | }); 58 | it('server should respond with status of 200 if the pods are running', async () => { 59 | const response = await request(app).get('/prom/pod/status'); 60 | expect(response.status).toBe(200); 61 | expect(response.body['kube-scheduler-minikube']).toBe('Running'); 62 | }); 63 | it('server should respond with an object that contains array of pod names', async () => { 64 | const response = await request(app).get('/prom/pods/nodes'); 65 | expect(response.status).toBe(200); 66 | expect(response.body['minikube'].length).toBeGreaterThan(0); 67 | }); 68 | }); 69 | 70 | //**************************TESTS FOR grafanaRouter.TS *********************/ 71 | 72 | describe('/grafana', () => { 73 | it('server should respond with status code 200 if successfully retrieved dashboard', async () => { 74 | const response = await request(app).get('/grafana/pods'); 75 | expect(response.status).toBe(200); 76 | expect(typeof response.body).toBe('string'); 77 | expect(response.body.indexOf('http')).toBe(0); 78 | }); 79 | }); 80 | 81 | describe('GET /grafana/pods', () => { 82 | test('server should response with status code 200', async () => { 83 | const response = await request(app).get('/grafana/dashboard'); 84 | expect(response.status).toBe(200); 85 | expect(typeof response.body).toBe('string'); 86 | expect(response.body.indexOf('http')).toBe(0); 87 | }); 88 | }); 89 | 90 | //**************************TESTS FOR statusRouter.TS *********************/ 91 | 92 | describe('/status', () => { 93 | it('should check Kubernetes status', async () => { 94 | const response = await request(app).get('/status'); 95 | expect(response.status).toBe(200); 96 | }); 97 | it('server should respond with status code 200 if grafana is initialized', async () => { 98 | const response = await request(app).get('/status/init'); 99 | expect(response.status).toBe(200); 100 | }); 101 | it('server should respond with status code 200 if grafana and prometheus are correctly set up', async () => { 102 | const response = await request(app).post('/status/setup'); 103 | expect(response.status).toBe(200); 104 | }); 105 | }); 106 | //**************************TESTS FOR promRouter.TS *********************/ 107 | 108 | describe('Prom Router', () => { 109 | it('should get data from PromAPI', async () => { 110 | const response = await request(app).get('/prom/pods/'); 111 | expect(response.status).toBe(200); 112 | }); 113 | it('server should respond with an array of objects that should contain a name and ip property', async () => { 114 | const response = await request(app).get('/prom/pods/'); 115 | expect(response.body.length).toBeGreaterThan(0); 116 | expect(typeof response.body[0].name).toBe('string'); 117 | expect(typeof response.body[0].ip).toBe('string'); 118 | }); 119 | it('server should respond with status of 200 if the pods are running', async () => { 120 | const response = await request(app).get('/prom/pod/status'); 121 | expect(response.status).toBe(200); 122 | expect(response.body['kube-scheduler-minikube']).toBe('Running'); 123 | }); 124 | it('server should respond with an object that contains array of pod names', async () => { 125 | const response = await request(app).get('/prom/pods/nodes'); 126 | expect(response.status).toBe(200); 127 | expect(response.body['minikube'].length).toBeGreaterThan(0); 128 | }); 129 | }); 130 | 131 | //**************************TESTS FOR grafanaRouter.TS *********************/ 132 | 133 | describe('/grafana', () => { 134 | it('server should respond with status code 200 if successfully retrieved dashboard', async () => { 135 | const response = await request(app).get('/grafana/pods'); 136 | expect(response.status).toBe(200); 137 | expect(typeof response.body).toBe('string'); 138 | expect(response.body.indexOf('http')).toBe(0); 139 | }); 140 | }); 141 | 142 | describe('GET /grafana/pods', () => { 143 | test('server should response with status code 200', async () => { 144 | const response = await request(app).get('/grafana/dashboard'); 145 | expect(response.status).toBe(200); 146 | expect(typeof response.body).toBe('string'); 147 | expect(response.body.indexOf('http')).toBe(0); 148 | }); 149 | }); 150 | 151 | //**************************TESTS FOR statusRouter.TS *********************/ 152 | 153 | describe('/status', () => { 154 | it('should check Kubernetes status', async () => { 155 | const response = await request(app).get('/status'); 156 | expect(response.status).toBe(200); 157 | }); 158 | it('server should respond with status code 200 if grafana is initialized', async () => { 159 | const response = await request(app).get('/status/init'); 160 | expect(response.status).toBe(200); 161 | }); 162 | it('server should respond with status code 200 if grafana and prometheus are correctly set up', async () => { 163 | const response = await request(app).post('/status/setup'); 164 | expect(response.status).toBe(200); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /client/styles/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { 178 | /* 1 */ 179 | overflow: visible; 180 | } 181 | 182 | /** 183 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 184 | * 1. Remove the inheritance of text transform in Firefox. 185 | */ 186 | 187 | button, 188 | select { 189 | /* 1 */ 190 | text-transform: none; 191 | } 192 | 193 | /** 194 | * Correct the inability to style clickable types in iOS and Safari. 195 | */ 196 | 197 | button, 198 | [type='button'], 199 | [type='reset'], 200 | [type='submit'] { 201 | -webkit-appearance: button; 202 | } 203 | 204 | /** 205 | * Remove the inner border and padding in Firefox. 206 | */ 207 | 208 | button::-moz-focus-inner, 209 | [type='button']::-moz-focus-inner, 210 | [type='reset']::-moz-focus-inner, 211 | [type='submit']::-moz-focus-inner { 212 | border-style: none; 213 | padding: 0; 214 | } 215 | 216 | /** 217 | * Restore the focus styles unset by the previous rule. 218 | */ 219 | 220 | button:-moz-focusring, 221 | [type='button']:-moz-focusring, 222 | [type='reset']:-moz-focusring, 223 | [type='submit']:-moz-focusring { 224 | outline: 1px dotted ButtonText; 225 | } 226 | 227 | /** 228 | * Correct the padding in Firefox. 229 | */ 230 | 231 | fieldset { 232 | padding: 0.35em 0.75em 0.625em; 233 | } 234 | 235 | /** 236 | * 1. Correct the text wrapping in Edge and IE. 237 | * 2. Correct the color inheritance from `fieldset` elements in IE. 238 | * 3. Remove the padding so developers are not caught out when they zero out 239 | * `fieldset` elements in all browsers. 240 | */ 241 | 242 | legend { 243 | box-sizing: border-box; /* 1 */ 244 | color: inherit; /* 2 */ 245 | display: table; /* 1 */ 246 | max-width: 100%; /* 1 */ 247 | padding: 0; /* 3 */ 248 | white-space: normal; /* 1 */ 249 | } 250 | 251 | /** 252 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 253 | */ 254 | 255 | progress { 256 | vertical-align: baseline; 257 | } 258 | 259 | /** 260 | * Remove the default vertical scrollbar in IE 10+. 261 | */ 262 | 263 | textarea { 264 | overflow: auto; 265 | } 266 | 267 | /** 268 | * 1. Add the correct box sizing in IE 10. 269 | * 2. Remove the padding in IE 10. 270 | */ 271 | 272 | [type='checkbox'], 273 | [type='radio'] { 274 | box-sizing: border-box; /* 1 */ 275 | padding: 0; /* 2 */ 276 | } 277 | 278 | /** 279 | * Correct the cursor style of increment and decrement buttons in Chrome. 280 | */ 281 | 282 | [type='number']::-webkit-inner-spin-button, 283 | [type='number']::-webkit-outer-spin-button { 284 | height: auto; 285 | } 286 | 287 | /** 288 | * 1. Correct the odd appearance in Chrome and Safari. 289 | * 2. Correct the outline style in Safari. 290 | */ 291 | 292 | [type='search'] { 293 | -webkit-appearance: textfield; /* 1 */ 294 | outline-offset: -2px; /* 2 */ 295 | } 296 | 297 | /** 298 | * Remove the inner padding in Chrome and Safari on macOS. 299 | */ 300 | 301 | [type='search']::-webkit-search-decoration { 302 | -webkit-appearance: none; 303 | } 304 | 305 | /** 306 | * 1. Correct the inability to style clickable types in iOS and Safari. 307 | * 2. Change font properties to `inherit` in Safari. 308 | */ 309 | 310 | ::-webkit-file-upload-button { 311 | -webkit-appearance: button; /* 1 */ 312 | font: inherit; /* 2 */ 313 | } 314 | 315 | /* Interactive 316 | ========================================================================== */ 317 | 318 | /* 319 | * Add the correct display in Edge, IE 10+, and Firefox. 320 | */ 321 | 322 | details { 323 | display: block; 324 | } 325 | 326 | /* 327 | * Add the correct display in all browsers. 328 | */ 329 | 330 | summary { 331 | display: list-item; 332 | } 333 | 334 | /* Misc 335 | ========================================================================== */ 336 | 337 | /** 338 | * Add the correct display in IE 10+. 339 | */ 340 | 341 | template { 342 | display: none; 343 | } 344 | 345 | /** 346 | * Add the correct display in IE 10. 347 | */ 348 | 349 | [hidden] { 350 | display: none; 351 | } 352 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KlusterView 2 | 3 |

    4 | 5 |

    6 | 7 | ## Introducing: KlusterView 8 | 9 | ### A lightweight, ready-to-deploy Kubernetes metrics visualizer for the rest of us 10 | 11 | Anyone who works regularly with Kubernetes environments knows that, despite the array of tools on the market for monitoring the health and performance of a cluster, knowing precisely what metrics are meaningful (and how to access them for quick reference) can be challenging. This is doubly true for individual developers as well as small and midsize enterprises without large dedicated DevOps teams: the gold standard for performance monitoring in Kubernetes (and other complex orchestration platfoms), the Prometheus/Grafana/Kube State Metrics (PGK) stack, requires extensive up-front configuration, and offers a dizzying array of metrics and visualization options that can bewilder the uninitiated. 12 | 13 | Enter KlusterView: a universally compatible plug-and-play visualization tool that distills the best of what the PGK stack has to offer into a streamlined interface, offering a concise and crystal-clear picture of cluster health without any platform-specific setup or specialized configuration. Users can install KlusterView and its dependencies in a single step (details here), and be up and running in minutes, putting critical performance information within a moment's reach wherever and whenever you access your cluster. 14 | 15 | ## KlusterView v1.0 offers: 16 | 17 | - An embedded dashboard displaying moment-by-moment resource usage (CPU, memory, disk), pod and node health status, and trends in these metrics over a variable time window, rendering any concerns immediately visible 18 | - A schematic representation of cluster nodes and their constitutent pods providing top-line pod-level performance metrics at a glance 19 | - A specialized dashboard for displaying detailed pod-level metrics, including historical resource usage data, to speed identification of container-specific issues 20 | - A **single point of access** to all of this, eliminating the need to expose additional in-cluster tools to the local network 21 | 22 | **For those wishing to further integrate KlusterView with existing monitoring tools, we offer:** 23 | 24 | - A prebuilt, development-ready application image featuring hot reloading 25 | - A robust front- and back-end testing suite to safeguard core functionality and simplify maintainability 26 | - A fully accessible set of installation scripts and YAML configuration files, categorized and indexed for easy modification 27 | 28 | ## Installation 29 | 30 | Klusterview is built atop the Grafana, Prometheus, and Kube State Metrics packages. The application will look for Grafana and Prometheus as services (by the names `grafana` and `prometheus`) in the `monitoring-kv` namespace, and will assume that Kube State Metrics has been installed on the cluster undergoing monitoring. **Either of the following methods will install each of these packages in the appropriate namespace and under the appropriate name**. 31 | 32 | ### Helm Chart 33 | 34 | KlusterView is most easily installed using its Helm chart, held in this repository. To install via this method, please ensure that the following dependencies are met, and follow the steps below. 35 | 36 | #### Prerequesites 37 | 38 | - Kubernetes 1.16+ 39 | - Helm 3+ 40 | 41 | #### Get Helm Repository Info 42 | 43 | ```shell 44 | helm repo add klusterview https://oslabs-beta.github.io/KlusterView/ 45 | helm repo update 46 | ``` 47 | 48 | #### Install Helm Chart 49 | 50 | ```shell 51 | helm install [Name of your choice] klusterview/klusterview 52 | ``` 53 | 54 | ### Manual Installation 55 | 56 | #### Prerequisites 57 | 58 | - Kubernetes 1.16+ 59 | - Sufficient privileges to create objects via Kubectl 60 | 61 | #### Installation 62 | 63 | From the project's `scripts` directory, execute `./setup.sh` with root user permissions. This will intruct Kubectl to install KlusterView and its dependencies using the manifests contained in the `deployment/_manual_install` directory, which are functionally identical to those contained in the Helm chart. 64 | 65 | ## Accessing the Application 66 | 67 | KlusterView will run on Port 31001 of each node. To display the application, access this port directly via your web browser, or use the tool of your choice (port forwarding, tunneling, MiniKube's `service` command, etc.) to forward the relevant port to your `localhost`. 68 | 69 | ## Contribution and Development Roadmap 70 | 71 | The state of current and planned features is as follows: 72 | 73 | | Feature | Status | 74 | | ------------------------------------------------------------------------------ | ------ | 75 | | Cluster- and node-level resource usage and pod status monitoring | ✅ | 76 | | Detailed pod-level resource usage and status monitoring | ✅ | 77 | | Cluster- and node-level structural information via node graph | ✅ | 78 | | Full TypeScript implementation | ✅ | 79 | | Full support for Grafana Live features | ⏳ | 80 | | In-window support for dashboard customization | ⏳ | 81 | | Integration with Grafana Role-Based Access Control | 🙏🏻 | 82 | | Live monitoring of pod-level error and information logs | 🙏🏻 | 83 | | Integration with Grafana Alerts Management vis a vis resource usage and status | 🙏🏻 | 84 | 85 | - ✅ = Ready to use 86 | - ⏳ = In progress 87 | - 🙏🏻 = Looking for contributors 88 | 89 | ### Running in Development Mode 90 | 91 | Should you wish to contribute to this project (and you are encouraged to!), you may access a live-reloading development server by using our KlusterView development [Docker image](https://hub.docker.com/repository/docker/kyleslugg/klusterview-dev/) in place of that used in production. You may also build this image from source: simply run `docker build -f Dockerfile-dev -t klusterview/dev .`Once loaded, the development server may be accessed on NodePort 31002 in the manner of your choosing. 92 | 93 | ### Running Tests 94 | 95 | To run unit tests on the app server, simply run `npm test` within the development container. For frontend testing, run `jest frontend-react.test` 96 | 97 | ## Meet the Team 98 | 99 |
    57 | 58 |
    59 | Kyle Slugg 60 |
    61 | 💻 62 | 🖇️ 63 | 🐙 64 |
    66 | 67 |
    68 | Shahmar Aliyev 69 |
    70 | 🖇️ 71 | 🐙 72 |
    74 | 75 |
    76 | Mike Nunn 77 |
    78 | 🖇️ 79 | 🐙 80 |
    82 | 83 |
    84 | Jonathan Tsai 85 |
    86 | 🖇️ 87 | 🐙 88 |
    100 | 101 | 112 | 123 | 133 | 143 | --------------------------------------------------------------------------------
    102 | 103 |
    104 | Kyle Slugg 105 |
    106 |
    107 | 108 | 109 | 110 |
    111 |
    113 | 114 |
    115 | Shahmar Aliyev 116 |
    117 |
    118 | 119 | 120 | 121 |
    122 |
    124 | 125 |
    126 | Mike Nunn 127 |
    128 |
    129 | 130 | 131 |
    132 |
    134 | 135 |
    136 | Jonathan Tsai 137 |
    138 |
    139 | 140 | 141 |
    142 |