├── .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 |
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 &&
}
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 |
100 |
101 |
102 |
103 | KLUSTER
104 |
105 |
106 |
107 |
108 | NODE MAP
109 |
110 |
111 |
112 |
113 | PODS
114 |
115 |
116 |
117 |
118 |
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 |
57 |
58 |
59 | Kyle Slugg
60 |
61 | 💻
62 | 🖇️
63 | 🐙
64 |
65 |
66 |
67 |
68 | Shahmar Aliyev
69 |
70 | 🖇️
71 | 🐙
72 |
73 |
74 |
75 |
76 | Mike Nunn
77 |
78 | 🖇️
79 | 🐙
80 |
81 |
82 |
83 |
84 | Jonathan Tsai
85 |
86 | 🖇️
87 | 🐙
88 |
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 |
100 |
101 |
102 |
103 |
104 | Kyle Slugg
105 |
106 |
111 |
112 |
113 |
114 |
115 | Shahmar Aliyev
116 |
117 |
122 |
123 |
124 |
125 |
126 | Mike Nunn
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | Jonathan Tsai
137 |
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------