├── .gitattributes ├── .gitignore ├── .jscs.json ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── dist ├── README.md ├── components │ ├── clusters │ │ ├── clusterConfig.js │ │ ├── clusterConfig.js.map │ │ ├── clusterInfo.js │ │ ├── clusterInfo.js.map │ │ ├── clusterWorkloads.js │ │ ├── clusterWorkloads.js.map │ │ ├── clusters.js │ │ ├── clusters.js.map │ │ ├── nodeInfo.js │ │ ├── nodeInfo.js.map │ │ ├── partials │ │ │ ├── cluster_config.html │ │ │ ├── cluster_info.html │ │ │ ├── cluster_workloads.html │ │ │ ├── clusters.html │ │ │ ├── node_info.html │ │ │ └── pod_info.html │ │ ├── podInfo.js │ │ └── podInfo.js.map │ └── config │ │ ├── config.html │ │ ├── config.js │ │ └── config.js.map ├── css │ ├── dark.css │ └── light.css ├── dashboards │ ├── kubernetes-cluster.json │ ├── kubernetes-container.json │ ├── kubernetes-deployments.json │ └── kubernetes-node.json ├── datasource │ ├── config.html │ ├── datasource.js │ ├── datasource.js.map │ ├── img │ │ └── logo.svg │ ├── module.js │ ├── module.js.map │ └── plugin.json ├── img │ ├── app-menu-screenshot.png │ ├── cluster-dashboard-screenshot.png │ ├── container-dashboard-screenshot.png │ ├── logo.svg │ ├── namespace-details-screenshot.png │ ├── node-dashboard-screenshot.png │ ├── overview-screenshot.png │ └── pod-details-screenshot.png ├── module.js ├── module.js.map ├── panels │ ├── nodeData │ │ ├── module.js │ │ ├── module.js.map │ │ ├── nodeData.js │ │ ├── nodeData.js.map │ │ ├── nodeStats.js │ │ ├── nodeStats.js.map │ │ ├── partials │ │ │ └── node_info.html │ │ └── plugin.json │ └── podNav │ │ ├── module.js │ │ ├── module.js.map │ │ ├── partials │ │ └── pod_nav.html │ │ ├── plugin.json │ │ ├── podNav.js │ │ └── podNav.js.map └── plugin.json ├── package.json └── src ├── components ├── clusters │ ├── clusterConfig.js │ ├── clusterInfo.js │ ├── clusterWorkloads.js │ ├── clusters.js │ ├── nodeInfo.js │ ├── partials │ │ ├── cluster_config.html │ │ ├── cluster_info.html │ │ ├── cluster_workloads.html │ │ ├── clusters.html │ │ ├── node_info.html │ │ └── pod_info.html │ └── podInfo.js └── config │ ├── config.html │ └── config.js ├── css ├── dark.css └── light.css ├── dashboards ├── kubernetes-cluster.json ├── kubernetes-container.json ├── kubernetes-deployments.json └── kubernetes-node.json ├── datasource ├── config.html ├── datasource.js ├── img │ └── logo.svg ├── module.js └── plugin.json ├── img ├── app-menu-screenshot.png ├── cluster-dashboard-screenshot.png ├── container-dashboard-screenshot.png ├── logo.svg ├── namespace-details-screenshot.png ├── node-dashboard-screenshot.png ├── overview-screenshot.png └── pod-details-screenshot.png ├── module.js ├── panels ├── nodeData │ ├── module.js │ ├── nodeData.js │ ├── nodeStats.js │ ├── partials │ │ └── node_info.html │ └── plugin.json └── podNav │ ├── module.js │ ├── partials │ └── pod_nav.html │ ├── plugin.json │ └── podNav.js └── plugin.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Don't diff files in dist/ 2 | *.map binary 3 | dist/** binary 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /.jscs.json: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "disallowImplicitTypeConversion": ["string"], 4 | "disallowKeywords": ["with"], 5 | "disallowMultipleLineBreaks": true, 6 | "disallowMixedSpacesAndTabs": true, 7 | "disallowTrailingWhitespace": true, 8 | "requireSpacesInFunctionExpression": { 9 | "beforeOpeningCurlyBrace": true 10 | }, 11 | "disallowSpacesInsideArrayBrackets": true, 12 | "disallowSpacesInsideParentheses": true, 13 | "validateIndentation": 2 14 | } 15 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "esnext": true, 4 | "bitwise":false, 5 | "curly": true, 6 | "eqnull": true, 7 | "strict": false, 8 | "devel": true, 9 | "eqeqeq": true, 10 | "forin": false, 11 | "immed": true, 12 | "supernew": true, 13 | "expr": true, 14 | "indent": 2, 15 | "latedef": false, 16 | "newcap": true, 17 | "noarg": true, 18 | "noempty": true, 19 | "undef": true, 20 | "boss": true, 21 | "trailing": true, 22 | "laxbreak": true, 23 | "laxcomma": true, 24 | "sub": true, 25 | "unused": true, 26 | "maxdepth": 6, 27 | "maxlen": 140, 28 | 29 | "globals": { 30 | "System": true, 31 | "Promise": true, 32 | "define": true, 33 | "require": true, 34 | "Chromath": false, 35 | "setImmediate": true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | require('load-grunt-tasks')(grunt); 4 | 5 | grunt.loadNpmTasks('grunt-execute'); 6 | grunt.loadNpmTasks('grunt-contrib-clean'); 7 | 8 | grunt.initConfig({ 9 | 10 | clean: ["dist"], 11 | 12 | copy: { 13 | src_to_dist: { 14 | cwd: 'src', 15 | expand: true, 16 | src: ['**/*', '!**/*.js', '!**/*.scss'], 17 | dest: 'dist' 18 | }, 19 | includes_to_dist: { 20 | cwd: 'includes', 21 | expand: true, 22 | src: ['**/*', '!**/*.js', '!**/*.scss'], 23 | dest: 'dist' 24 | }, 25 | pluginDef: { 26 | expand: true, 27 | src: ['README.md'], 28 | dest: 'dist', 29 | } 30 | }, 31 | 32 | watch: { 33 | rebuild_all: { 34 | files: ['src/**/*', 'README.md'], 35 | tasks: ['default'], 36 | options: {spawn: false} 37 | }, 38 | }, 39 | 40 | babel: { 41 | options: { 42 | sourceMap: true, 43 | presets: ["es2015"], 44 | plugins: ['transform-es2015-modules-systemjs', "transform-es2015-for-of"], 45 | }, 46 | dist: { 47 | files: [{ 48 | cwd: 'src', 49 | expand: true, 50 | src: ['**/*.js', '!src/directives/*.js', '!src/filters/*.js'], 51 | dest: 'dist', 52 | ext:'.js' 53 | }] 54 | }, 55 | }, 56 | 57 | jshint: { 58 | source: { 59 | files: { 60 | src: ['src/**/*.js'], 61 | } 62 | }, 63 | options: { 64 | jshintrc: true, 65 | reporter: require('jshint-stylish'), 66 | ignores: [ 67 | 'node_modules/*', 68 | 'dist/*', 69 | ] 70 | } 71 | }, 72 | jscs: { 73 | src: ['src/**/*.js'], 74 | options: { 75 | config: ".jscs.json", 76 | }, 77 | }, 78 | 79 | sass: { 80 | options: { 81 | sourceMap: true 82 | }, 83 | dist: { 84 | files: { 85 | "dist/css/kubernetes.dark.css": "src/sass/kubernetes.dark.scss", 86 | "dist/css/kubernetes.light.css": "src/sass/kubernetes.light.scss", 87 | } 88 | } 89 | } 90 | }); 91 | 92 | grunt.registerTask('default', [ 93 | 'clean', 94 | 'sass', 95 | 'copy:src_to_dist', 96 | 'copy:pluginDef', 97 | 'babel', 98 | 'jshint', 99 | 'jscs', 100 | ]); 101 | 102 | // does not have sass due to grafana file dependency 103 | grunt.registerTask('test', [ 104 | 'clean', 105 | 'copy:src_to_dist', 106 | 'copy:pluginDef', 107 | 'babel', 108 | 'jshint', 109 | 'jscs', 110 | ]); 111 | }; 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING: This project is no longer maintained. Please consider using the Prometheus compatible kubernetes application: https://github.com/grafana/kubernetes-app 2 | 3 | # Grafana App for Kubernetes 4 | 5 | [Kubernetes](http://kubernetes.io/) is an open-source system for automating deployment, scaling, and management of containerized applications. 6 | 7 | The Grafana Kubernetes App allows you to monitor your Kubernetes cluster's performance. It includes 4 dashboards, Cluster, Node, Pod/Container and Deployment. It also comes with [Intel Snap](http://snap-telemetry.io/) collectors that are deployed to your cluster to collect health metrics. The metrics collected are high-level cluster and node stats as well as lower level pod and container stats. Use the high-level metrics to alert on and the low-level metrics to troubleshoot. 8 | 9 | ![Container Dashboard](https://raw.githubusercontent.com/raintank/kubernetes-app/master/src/img/cluster-dashboard-screenshot.png) 10 | 11 | ![Container Dashboard](https://raw.githubusercontent.com/raintank/kubernetes-app/master/src/img/container-dashboard-screenshot.png) 12 | 13 | ![Node Dashboard](https://raw.githubusercontent.com/raintank/kubernetes-app/master/src/img/node-dashboard-screenshot.png) 14 | 15 | ### Requirements 16 | 17 | 1. Currently only has support for **Graphite**. 18 | 2. For automatic deployment of the collectors, then Kubernetes 1.4 or higher is required. 19 | 3. Grafana 4 is required if using TLS Client Auth (rather than Basic Auth). 20 | 21 | ### Features 22 | 23 | - The app uses Kubernetes tags to allow you to filter pod metrics. Kubernetes clusters tend to have a lot of pods and a lot of pod metrics. The Pod/Container dashboard leverages the pod tags so you can easily find the relevant pod or pods. 24 | 25 | - Easy installation of collectors, either a one click deploy from Grafana or detailed instructions to deploy them manually them with kubectl (also quite easy!) 26 | 27 | - Cluster level metrics that are not available in Heapster, like CPU Capacity vs CPU Usage. 28 | 29 | - Pod and Container status metrics. See the [Snap Kubestate Collector](https://github.com/raintank/snap-plugin-collector-kubestate) for more details. 30 | 31 | ### Cluster Metrics 32 | 33 | - Pod Capacity/Usage 34 | - Memory Capacity/Usage 35 | - CPU Capacity/Usage 36 | - Disk Capacity/Usage (measurements from each container's /var/lib/docker) 37 | - Overview of Nodes, Pods and Containers 38 | 39 | ### Node Metrics 40 | 41 | - CPU 42 | - Memory Available 43 | - Load per CPU 44 | - Read IOPS 45 | - Write IOPS 46 | - %Util 47 | - Network Traffic/second 48 | - Network Packets/second 49 | - Network Errors/second 50 | 51 | ### Pod/Container Metrics 52 | 53 | - Memory Usage 54 | - Network Traffic 55 | - TCP Connections 56 | - CPU Usage 57 | - Read IOPS 58 | - Write IOPS 59 | - All Established TCP Conn 60 | 61 | ### Documentation 62 | 63 | #### Installation 64 | 65 | 1. Use the grafana-cli tool to install kubernetes from the commandline: 66 | 67 | ``` 68 | grafana-cli plugins install raintank-kubernetes-app 69 | ``` 70 | 71 | 2. Restart your Grafana server. 72 | 73 | 3. Log into your Grafana instance. Navigate to the Plugins section, found in the Grafana main menu. Click the Apps tabs in the Plugins section and select the newly installed Kubernetes app. To enable the app, click the Config tab and click on the Enable button. 74 | 75 | #### Connecting to your Cluster 76 | 77 | 1. Go to the Cluster List page via the Kubernetes app menu. 78 | 79 | ![Cluster List in main menu](https://raw.githubusercontent.com/raintank/kubernetes-app/master/src/img/app-menu-screenshot.png) 80 | 81 | 2. Click the `New Cluster` button. 82 | 83 | 3. Fill in the Auth details for your cluster. 84 | 85 | 4. Choose the Graphite datasource that will be used for reading data in the dashboards. 86 | 87 | 5. Fill in the details for the Carbon host that is used to write to Graphite. This url has to be available from inside the cluster. 88 | 89 | 6. Click `Deploy`. This will deploy a DaemonSet, to collect health metrics for every node, and a pod that collects cluster metrics. 90 | 91 | #### Manual Deployment of Collectors 92 | 93 | If you do not want to deploy the collector DaemonSet and pod automatically, then it can be deployed manually with kubectl. If using an older version of Kubernetes than 1.4, you will have to adapt the json files, particularly for the daemonset, and remove some newer features. Please create an issue if you need support for older versions of Kubernetes built in to the app. 94 | 95 | The manual deployment instructions and files needed, can be downloaded from the Cluster Config page. At the bottom of the page, there is a help section with instructions and links to all the json files needed. 96 | 97 | #### Uninstalling the Collectors (DaemonSet and Pod) 98 | 99 | There is an Undeploy button on the Cluster Config page. To manually undeploy them: 100 | 101 | ```bash 102 | kubectl delete daemonsets -n kube-system snap 103 | 104 | kubectl delete deployments -n kube-system snap-kubestate-deployment 105 | 106 | kubectl delete configmaps -n kube-system snap-tasks 107 | 108 | kubectl delete configmaps -n kube-system snap-tasks-kubestate 109 | ``` 110 | 111 | #### Technical Details 112 | 113 | Metrics are collected by the [Intel Snap](http://snap-telemetry.io/) collector using the [Docker plugin](https://github.com/intelsdi-x/snap-plugin-collector-docker/blob/master/METRICS.md). A DaemonSet with Snap is deployed to your Kubernetes cluster when you add a new cluster in the app. For cluster level metrics, one Snap pod is also deployed to the cluster. The [snap_k8s](https://github.com/raintank/snap_k8s) docker image used for this is based off of Intel's Snap docker image. 114 | 115 | The following Snap plugins are used to collect metrics: 116 | 117 | - [CPU Collector](https://github.com/intelsdi-x/snap-plugin-collector-cpu/blob/master/METRICS.md) 118 | - [Docker Collector](https://github.com/intelsdi-x/snap-plugin-collector-docker/blob/master/METRICS.md) 119 | - [Network Interface Collector](https://github.com/intelsdi-x/snap-plugin-collector-interface/blob/master/METRICS.md) 120 | - [IOStat Collector](https://github.com/intelsdi-x/snap-plugin-collector-iostat) 121 | - [Load Collector](https://github.com/intelsdi-x/snap-plugin-collector-load#collected-metrics) 122 | - [MemInfo Collector](https://github.com/intelsdi-x/snap-plugin-collector-meminfo/blob/master/METRICS.md) 123 | - [Kubestate Collector](https://github.com/raintank/snap-plugin-collector-kubestate) 124 | 125 | ### Feedback and Questions 126 | 127 | Please submit any issues with the app on [Github](https://github.com/raintank/kubernetes-app/issues). 128 | 129 | #### Troubleshooting 130 | 131 | If there are any problems with metrics not being collected then you can collect some logs with the following steps: 132 | 133 | 1. Get the snap pod (or pods if you have multiple nodes) name with: 134 | 135 | `kubectl get pods -n kube-system` 136 | 137 | 2. Check if the task is running with (replace xxxx with the guid): 138 | 139 | `kubectl exec -it snap-xxxxx-n kube-system -- /opt/snap/bin/snaptel task list` 140 | 141 | Include the output in the issue. 142 | 143 | 3. Get the logs with: 144 | 145 | `kubectl logs snap-xxxxx -n kube-system` 146 | 147 | Include this output in the issue too. 148 | -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | # WARNING: This project is no longer maintained. Please consider using the Prometheus compatible kubernetes application: https://github.com/grafana/kubernetes-app 2 | 3 | # Grafana App for Kubernetes 4 | 5 | [Kubernetes](http://kubernetes.io/) is an open-source system for automating deployment, scaling, and management of containerized applications. 6 | 7 | The Grafana Kubernetes App allows you to monitor your Kubernetes cluster's performance. It includes 4 dashboards, Cluster, Node, Pod/Container and Deployment. It also comes with [Intel Snap](http://snap-telemetry.io/) collectors that are deployed to your cluster to collect health metrics. The metrics collected are high-level cluster and node stats as well as lower level pod and container stats. Use the high-level metrics to alert on and the low-level metrics to troubleshoot. 8 | 9 | ![Container Dashboard](https://raw.githubusercontent.com/raintank/kubernetes-app/master/src/img/cluster-dashboard-screenshot.png) 10 | 11 | ![Container Dashboard](https://raw.githubusercontent.com/raintank/kubernetes-app/master/src/img/container-dashboard-screenshot.png) 12 | 13 | ![Node Dashboard](https://raw.githubusercontent.com/raintank/kubernetes-app/master/src/img/node-dashboard-screenshot.png) 14 | 15 | ### Requirements 16 | 17 | 1. Currently only has support for **Graphite**. 18 | 2. For automatic deployment of the collectors, then Kubernetes 1.4 or higher is required. 19 | 3. Grafana 4 is required if using TLS Client Auth (rather than Basic Auth). 20 | 21 | ### Features 22 | 23 | - The app uses Kubernetes tags to allow you to filter pod metrics. Kubernetes clusters tend to have a lot of pods and a lot of pod metrics. The Pod/Container dashboard leverages the pod tags so you can easily find the relevant pod or pods. 24 | 25 | - Easy installation of collectors, either a one click deploy from Grafana or detailed instructions to deploy them manually them with kubectl (also quite easy!) 26 | 27 | - Cluster level metrics that are not available in Heapster, like CPU Capacity vs CPU Usage. 28 | 29 | - Pod and Container status metrics. See the [Snap Kubestate Collector](https://github.com/raintank/snap-plugin-collector-kubestate) for more details. 30 | 31 | ### Cluster Metrics 32 | 33 | - Pod Capacity/Usage 34 | - Memory Capacity/Usage 35 | - CPU Capacity/Usage 36 | - Disk Capacity/Usage (measurements from each container's /var/lib/docker) 37 | - Overview of Nodes, Pods and Containers 38 | 39 | ### Node Metrics 40 | 41 | - CPU 42 | - Memory Available 43 | - Load per CPU 44 | - Read IOPS 45 | - Write IOPS 46 | - %Util 47 | - Network Traffic/second 48 | - Network Packets/second 49 | - Network Errors/second 50 | 51 | ### Pod/Container Metrics 52 | 53 | - Memory Usage 54 | - Network Traffic 55 | - TCP Connections 56 | - CPU Usage 57 | - Read IOPS 58 | - Write IOPS 59 | - All Established TCP Conn 60 | 61 | ### Documentation 62 | 63 | #### Installation 64 | 65 | 1. Use the grafana-cli tool to install kubernetes from the commandline: 66 | 67 | ``` 68 | grafana-cli plugins install raintank-kubernetes-app 69 | ``` 70 | 71 | 2. Restart your Grafana server. 72 | 73 | 3. Log into your Grafana instance. Navigate to the Plugins section, found in the Grafana main menu. Click the Apps tabs in the Plugins section and select the newly installed Kubernetes app. To enable the app, click the Config tab and click on the Enable button. 74 | 75 | #### Connecting to your Cluster 76 | 77 | 1. Go to the Cluster List page via the Kubernetes app menu. 78 | 79 | ![Cluster List in main menu](https://raw.githubusercontent.com/raintank/kubernetes-app/master/src/img/app-menu-screenshot.png) 80 | 81 | 2. Click the `New Cluster` button. 82 | 83 | 3. Fill in the Auth details for your cluster. 84 | 85 | 4. Choose the Graphite datasource that will be used for reading data in the dashboards. 86 | 87 | 5. Fill in the details for the Carbon host that is used to write to Graphite. This url has to be available from inside the cluster. 88 | 89 | 6. Click `Deploy`. This will deploy a DaemonSet, to collect health metrics for every node, and a pod that collects cluster metrics. 90 | 91 | #### Manual Deployment of Collectors 92 | 93 | If you do not want to deploy the collector DaemonSet and pod automatically, then it can be deployed manually with kubectl. If using an older version of Kubernetes than 1.4, you will have to adapt the json files, particularly for the daemonset, and remove some newer features. Please create an issue if you need support for older versions of Kubernetes built in to the app. 94 | 95 | The manual deployment instructions and files needed, can be downloaded from the Cluster Config page. At the bottom of the page, there is a help section with instructions and links to all the json files needed. 96 | 97 | #### Uninstalling the Collectors (DaemonSet and Pod) 98 | 99 | There is an Undeploy button on the Cluster Config page. To manually undeploy them: 100 | 101 | ```bash 102 | kubectl delete daemonsets -n kube-system snap 103 | 104 | kubectl delete deployments -n kube-system snap-kubestate-deployment 105 | 106 | kubectl delete configmaps -n kube-system snap-tasks 107 | 108 | kubectl delete configmaps -n kube-system snap-tasks-kubestate 109 | ``` 110 | 111 | #### Technical Details 112 | 113 | Metrics are collected by the [Intel Snap](http://snap-telemetry.io/) collector using the [Docker plugin](https://github.com/intelsdi-x/snap-plugin-collector-docker/blob/master/METRICS.md). A DaemonSet with Snap is deployed to your Kubernetes cluster when you add a new cluster in the app. For cluster level metrics, one Snap pod is also deployed to the cluster. The [snap_k8s](https://github.com/raintank/snap_k8s) docker image used for this is based off of Intel's Snap docker image. 114 | 115 | The following Snap plugins are used to collect metrics: 116 | 117 | - [CPU Collector](https://github.com/intelsdi-x/snap-plugin-collector-cpu/blob/master/METRICS.md) 118 | - [Docker Collector](https://github.com/intelsdi-x/snap-plugin-collector-docker/blob/master/METRICS.md) 119 | - [Network Interface Collector](https://github.com/intelsdi-x/snap-plugin-collector-interface/blob/master/METRICS.md) 120 | - [IOStat Collector](https://github.com/intelsdi-x/snap-plugin-collector-iostat) 121 | - [Load Collector](https://github.com/intelsdi-x/snap-plugin-collector-load#collected-metrics) 122 | - [MemInfo Collector](https://github.com/intelsdi-x/snap-plugin-collector-meminfo/blob/master/METRICS.md) 123 | - [Kubestate Collector](https://github.com/raintank/snap-plugin-collector-kubestate) 124 | 125 | ### Feedback and Questions 126 | 127 | Please submit any issues with the app on [Github](https://github.com/raintank/kubernetes-app/issues). 128 | 129 | #### Troubleshooting 130 | 131 | If there are any problems with metrics not being collected then you can collect some logs with the following steps: 132 | 133 | 1. Get the snap pod (or pods if you have multiple nodes) name with: 134 | 135 | `kubectl get pods -n kube-system` 136 | 137 | 2. Check if the task is running with (replace xxxx with the guid): 138 | 139 | `kubectl exec -it snap-xxxxx-n kube-system -- /opt/snap/bin/snaptel task list` 140 | 141 | Include the output in the issue. 142 | 143 | 3. Get the logs with: 144 | 145 | `kubectl logs snap-xxxxx -n kube-system` 146 | 147 | Include this output in the issue too. 148 | -------------------------------------------------------------------------------- /dist/components/clusters/clusterWorkloads.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['lodash', 'jquery'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var _, $, _createClass, ClusterWorkloadsCtrl; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | function slugify(str) { 15 | var slug = str.replace("@", "at").replace("&", "and").replace(/[.]/g, "_").replace("/\W+/", ""); 16 | return slug; 17 | } 18 | 19 | return { 20 | setters: [function (_lodash) { 21 | _ = _lodash.default; 22 | }, function (_jquery) { 23 | $ = _jquery.default; 24 | }], 25 | execute: function () { 26 | _createClass = function () { 27 | function defineProperties(target, props) { 28 | for (var i = 0; i < props.length; i++) { 29 | var descriptor = props[i]; 30 | descriptor.enumerable = descriptor.enumerable || false; 31 | descriptor.configurable = true; 32 | if ("value" in descriptor) descriptor.writable = true; 33 | Object.defineProperty(target, descriptor.key, descriptor); 34 | } 35 | } 36 | 37 | return function (Constructor, protoProps, staticProps) { 38 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 39 | if (staticProps) defineProperties(Constructor, staticProps); 40 | return Constructor; 41 | }; 42 | }(); 43 | 44 | _export('ClusterWorkloadsCtrl', ClusterWorkloadsCtrl = function () { 45 | /** @ngInject */ 46 | function ClusterWorkloadsCtrl($scope, $injector, backendSrv, datasourceSrv, $q, $location, alertSrv) { 47 | var _this = this; 48 | 49 | _classCallCheck(this, ClusterWorkloadsCtrl); 50 | 51 | this.$q = $q; 52 | this.backendSrv = backendSrv; 53 | this.datasourceSrv = datasourceSrv; 54 | this.$location = $location; 55 | document.title = 'Grafana Kubernetes App'; 56 | 57 | this.pageReady = false; 58 | this.cluster = {}; 59 | this.namespaces = []; 60 | this.namespace = ""; 61 | this.daemonSets = []; 62 | this.replicationControllers = []; 63 | this.deployments = []; 64 | this.pods = []; 65 | 66 | if (!("cluster" in $location.search())) { 67 | alertSrv.set("no cluster specified.", "no cluster specified in url", 'error'); 68 | return; 69 | } 70 | 71 | if ("namespace" in $location.search()) { 72 | this.namespace = $location.search().namespace; 73 | } 74 | 75 | this.getCluster($location.search().cluster).then(function (clusterDS) { 76 | _this.clusterDS = clusterDS; 77 | _this.pageReady = true; 78 | _this.getWorkloads(); 79 | }); 80 | } 81 | 82 | _createClass(ClusterWorkloadsCtrl, [{ 83 | key: 'getCluster', 84 | value: function getCluster(id) { 85 | var _this2 = this; 86 | 87 | return this.backendSrv.get('api/datasources/' + id).then(function (ds) { 88 | _this2.cluster = ds; 89 | return _this2.datasourceSrv.get(ds.name); 90 | }); 91 | } 92 | }, { 93 | key: 'getWorkloads', 94 | value: function getWorkloads() { 95 | var _this3 = this; 96 | 97 | var namespace = this.namespace; 98 | this.clusterDS.getNamespaces().then(function (namespaces) { 99 | _this3.namespaces = namespaces; 100 | }); 101 | this.clusterDS.getDaemonSets(namespace).then(function (daemonSets) { 102 | _this3.daemonSets = daemonSets; 103 | }); 104 | this.clusterDS.getReplicationControllers(namespace).then(function (rc) { 105 | _this3.replicationControllers = rc; 106 | }); 107 | this.clusterDS.getDeployments(namespace).then(function (deployments) { 108 | _this3.deployments = deployments; 109 | }); 110 | this.clusterDS.getPods(namespace).then(function (pods) { 111 | _this3.pods = pods; 112 | }); 113 | } 114 | }, { 115 | key: 'componentHealth', 116 | value: function componentHealth(component) { 117 | var health = "unhealthy"; 118 | _.forEach(component.conditions, function (condition) { 119 | if (condition.type === "Healthy" && condition.status === "True") { 120 | health = "healthy"; 121 | } 122 | }); 123 | return health; 124 | } 125 | }, { 126 | key: 'isComponentHealthy', 127 | value: function isComponentHealthy(component) { 128 | return this.componentHealth(component) === "healthy"; 129 | } 130 | }, { 131 | key: 'goToPodDashboard', 132 | value: function goToPodDashboard(pod) { 133 | this.$location.path("dashboard/db/kubernetes-container").search({ 134 | "var-datasource": this.cluster.jsonData.ds, 135 | "var-cluster": this.cluster.name, 136 | "var-node": slugify(pod.spec.nodeName), 137 | "var-namespace": pod.metadata.namespace, 138 | "var-pod": pod.metadata.name 139 | }); 140 | } 141 | }, { 142 | key: 'goToDeploymentDashboard', 143 | value: function goToDeploymentDashboard(deploy) { 144 | this.$location.path("dashboard/db/kubernetes-deployments").search({ 145 | "var-datasource": this.cluster.jsonData.ds, 146 | "var-cluster": this.cluster.name, 147 | "var-namespace": deploy.metadata.namespace, 148 | "var-deployment": deploy.metadata.name 149 | }); 150 | } 151 | }, { 152 | key: 'goToPodInfo', 153 | value: function goToPodInfo(pod, evt) { 154 | var clickTargetIsLinkOrHasLinkParents = $(evt.target).closest('a').length > 0; 155 | 156 | var closestElm = _.head($(evt.target).closest('div')); 157 | var clickTargetClickAttr = _.find(closestElm.attributes, { name: "ng-click" }); 158 | var clickTargetIsNodeDashboard = clickTargetClickAttr ? clickTargetClickAttr.value === "ctrl.goToPodDashboard(pod, $event)" : false; 159 | if (clickTargetIsLinkOrHasLinkParents === false && clickTargetIsNodeDashboard === false) { 160 | this.$location.path("plugins/raintank-kubernetes-app/page/pod-info").search({ 161 | "cluster": this.cluster.id, 162 | "namespace": slugify(pod.metadata.namespace), 163 | "pod": pod.metadata.name 164 | }); 165 | } 166 | } 167 | }]); 168 | 169 | return ClusterWorkloadsCtrl; 170 | }()); 171 | 172 | _export('ClusterWorkloadsCtrl', ClusterWorkloadsCtrl); 173 | 174 | ClusterWorkloadsCtrl.templateUrl = 'components/clusters/partials/cluster_workloads.html'; 175 | } 176 | }; 177 | }); 178 | //# sourceMappingURL=clusterWorkloads.js.map 179 | -------------------------------------------------------------------------------- /dist/components/clusters/clusterWorkloads.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/components/clusters/clusterWorkloads.js"],"names":["slugify","str","slug","replace","_","$","ClusterWorkloadsCtrl","$scope","$injector","backendSrv","datasourceSrv","$q","$location","alertSrv","document","title","pageReady","cluster","namespaces","namespace","daemonSets","replicationControllers","deployments","pods","search","set","getCluster","then","clusterDS","getWorkloads","id","get","ds","name","getNamespaces","getDaemonSets","getReplicationControllers","rc","getDeployments","getPods","component","health","forEach","conditions","condition","type","status","componentHealth","pod","path","jsonData","spec","nodeName","metadata","deploy","evt","clickTargetIsLinkOrHasLinkParents","target","closest","length","closestElm","head","clickTargetClickAttr","find","attributes","clickTargetIsNodeDashboard","value","templateUrl"],"mappings":";;;;;;;;;;;;;AAGA,WAASA,OAAT,CAAiBC,GAAjB,EAAsB;AACpB,QAAIC,OAAOD,IAAIE,OAAJ,CAAY,GAAZ,EAAiB,IAAjB,EAAuBA,OAAvB,CAA+B,GAA/B,EAAoC,KAApC,EAA2CA,OAA3C,CAAmD,MAAnD,EAA2D,GAA3D,EAAgEA,OAAhE,CAAwE,OAAxE,EAAiF,EAAjF,CAAX;AACA,WAAOD,IAAP;AACD;;;;AANME,O;;AACAC,O;;;;;;;;;;;;;;;;;;;;;sCAOMC,oB;AACX;AACA,sCAAYC,MAAZ,EAAoBC,SAApB,EAA+BC,UAA/B,EAA2CC,aAA3C,EAA0DC,EAA1D,EAA8DC,SAA9D,EAAyEC,QAAzE,EAAmF;AAAA;;AAAA;;AACjF,eAAKF,EAAL,GAAUA,EAAV;AACA,eAAKF,UAAL,GAAkBA,UAAlB;AACA,eAAKC,aAAL,GAAqBA,aAArB;AACA,eAAKE,SAAL,GAAiBA,SAAjB;AACAE,mBAASC,KAAT,GAAiB,wBAAjB;;AAEA,eAAKC,SAAL,GAAiB,KAAjB;AACA,eAAKC,OAAL,GAAe,EAAf;AACA,eAAKC,UAAL,GAAkB,EAAlB;AACA,eAAKC,SAAL,GAAiB,EAAjB;AACA,eAAKC,UAAL,GAAkB,EAAlB;AACA,eAAKC,sBAAL,GAA8B,EAA9B;AACA,eAAKC,WAAL,GAAmB,EAAnB;AACA,eAAKC,IAAL,GAAY,EAAZ;;AAEA,cAAI,EAAE,aAAaX,UAAUY,MAAV,EAAf,CAAJ,EAAwC;AACtCX,qBAASY,GAAT,CAAa,uBAAb,EAAsC,6BAAtC,EAAqE,OAArE;AACA;AACD;;AAED,cAAI,eAAeb,UAAUY,MAAV,EAAnB,EAAuC;AACrC,iBAAKL,SAAL,GAAiBP,UAAUY,MAAV,GAAmBL,SAApC;AACD;;AAED,eAAKO,UAAL,CAAgBd,UAAUY,MAAV,GAAmBP,OAAnC,EACGU,IADH,CACQ,qBAAa;AACjB,kBAAKC,SAAL,GAAiBA,SAAjB;AACA,kBAAKZ,SAAL,GAAiB,IAAjB;AACA,kBAAKa,YAAL;AACD,WALH;AAMD;;;;qCAEUC,E,EAAI;AAAA;;AACb,mBAAO,KAAKrB,UAAL,CAAgBsB,GAAhB,CAAoB,qBAAmBD,EAAvC,EAA2CH,IAA3C,CAAgD,cAAM;AAC3D,qBAAKV,OAAL,GAAee,EAAf;AACA,qBAAO,OAAKtB,aAAL,CAAmBqB,GAAnB,CAAuBC,GAAGC,IAA1B,CAAP;AACD,aAHM,CAAP;AAID;;;yCAEc;AAAA;;AACb,gBAAId,YAAY,KAAKA,SAArB;AACA,iBAAKS,SAAL,CAAeM,aAAf,GAA+BP,IAA/B,CAAoC,sBAAc;AAChD,qBAAKT,UAAL,GAAkBA,UAAlB;AACD,aAFD;AAGA,iBAAKU,SAAL,CAAeO,aAAf,CAA6BhB,SAA7B,EAAwCQ,IAAxC,CAA6C,sBAAc;AACzD,qBAAKP,UAAL,GAAkBA,UAAlB;AACD,aAFD;AAGA,iBAAKQ,SAAL,CAAeQ,yBAAf,CAAyCjB,SAAzC,EAAoDQ,IAApD,CAAyD,cAAM;AAC7D,qBAAKN,sBAAL,GAA8BgB,EAA9B;AACD,aAFD;AAGA,iBAAKT,SAAL,CAAeU,cAAf,CAA8BnB,SAA9B,EAAyCQ,IAAzC,CAA8C,uBAAe;AAC3D,qBAAKL,WAAL,GAAmBA,WAAnB;AACD,aAFD;AAGA,iBAAKM,SAAL,CAAeW,OAAf,CAAuBpB,SAAvB,EAAkCQ,IAAlC,CAAuC,gBAAQ;AAC7C,qBAAKJ,IAAL,GAAYA,IAAZ;AACD,aAFD;AAGD;;;0CAEeiB,S,EAAW;AACzB,gBAAIC,SAAS,WAAb;AACArC,cAAEsC,OAAF,CAAUF,UAAUG,UAApB,EAAgC,UAASC,SAAT,EAAoB;AAClD,kBAAKA,UAAUC,IAAV,KAAmB,SAApB,IAAmCD,UAAUE,MAAV,KAAqB,MAA5D,EAAqE;AACnEL,yBAAS,SAAT;AACD;AACF,aAJD;AAKA,mBAAOA,MAAP;AACD;;;6CAEkBD,S,EAAW;AAC5B,mBAAO,KAAKO,eAAL,CAAqBP,SAArB,MAAoC,SAA3C;AACD;;;2CAEgBQ,G,EAAK;AACpB,iBAAKpC,SAAL,CAAeqC,IAAf,CAAoB,mCAApB,EACCzB,MADD,CACQ;AACN,gCAAkB,KAAKP,OAAL,CAAaiC,QAAb,CAAsBlB,EADlC;AAEN,6BAAe,KAAKf,OAAL,CAAagB,IAFtB;AAGN,0BAAYjC,QAAQgD,IAAIG,IAAJ,CAASC,QAAjB,CAHN;AAIN,+BAAiBJ,IAAIK,QAAJ,CAAalC,SAJxB;AAKN,yBAAW6B,IAAIK,QAAJ,CAAapB;AALlB,aADR;AAQD;;;kDAEuBqB,M,EAAQ;AAC9B,iBAAK1C,SAAL,CAAeqC,IAAf,CAAoB,qCAApB,EACCzB,MADD,CACQ;AACN,gCAAkB,KAAKP,OAAL,CAAaiC,QAAb,CAAsBlB,EADlC;AAEN,6BAAe,KAAKf,OAAL,CAAagB,IAFtB;AAGN,+BAAiBqB,OAAOD,QAAP,CAAgBlC,SAH3B;AAIN,gCAAkBmC,OAAOD,QAAP,CAAgBpB;AAJ5B,aADR;AAOD;;;sCAEWe,G,EAAKO,G,EAAK;AACpB,gBAAIC,oCAAoCnD,EAAEkD,IAAIE,MAAN,EAAcC,OAAd,CAAsB,GAAtB,EAA2BC,MAA3B,GAAoC,CAA5E;;AAEA,gBAAIC,aAAaxD,EAAEyD,IAAF,CAAOxD,EAAEkD,IAAIE,MAAN,EAAcC,OAAd,CAAsB,KAAtB,CAAP,CAAjB;AACA,gBAAII,uBAAuB1D,EAAE2D,IAAF,CAAOH,WAAWI,UAAlB,EAA8B,EAAC/B,MAAM,UAAP,EAA9B,CAA3B;AACA,gBAAIgC,6BAA6BH,uBAAuBA,qBAAqBI,KAArB,KAA+B,oCAAtD,GAA6F,KAA9H;AACA,gBAAIV,sCAAsC,KAAtC,IACAS,+BAA+B,KADnC,EAC0C;AACxC,mBAAKrD,SAAL,CAAeqC,IAAf,CAAoB,+CAApB,EACCzB,MADD,CACQ;AACN,2BAAW,KAAKP,OAAL,CAAaa,EADlB;AAEN,6BAAa9B,QAAQgD,IAAIK,QAAJ,CAAalC,SAArB,CAFP;AAGN,uBAAO6B,IAAIK,QAAJ,CAAapB;AAHd,eADR;AAMD;AACF;;;;;;;;AAGH3B,2BAAqB6D,WAArB,GAAmC,qDAAnC","file":"clusterWorkloads.js","sourcesContent":["import _ from 'lodash';\nimport $ from 'jquery';\n\nfunction slugify(str) {\n var slug = str.replace(\"@\", \"at\").replace(\"&\", \"and\").replace(/[.]/g, \"_\").replace(\"/\\W+/\", \"\");\n return slug;\n}\n\nexport class ClusterWorkloadsCtrl {\n /** @ngInject */\n constructor($scope, $injector, backendSrv, datasourceSrv, $q, $location, alertSrv) {\n this.$q = $q;\n this.backendSrv = backendSrv;\n this.datasourceSrv = datasourceSrv;\n this.$location = $location;\n document.title = 'Grafana Kubernetes App';\n\n this.pageReady = false;\n this.cluster = {};\n this.namespaces = [];\n this.namespace = \"\";\n this.daemonSets = [];\n this.replicationControllers = [];\n this.deployments = [];\n this.pods = [];\n\n if (!(\"cluster\" in $location.search())) {\n alertSrv.set(\"no cluster specified.\", \"no cluster specified in url\", 'error');\n return;\n }\n\n if (\"namespace\" in $location.search()) {\n this.namespace = $location.search().namespace;\n }\n\n this.getCluster($location.search().cluster)\n .then(clusterDS => {\n this.clusterDS = clusterDS;\n this.pageReady = true;\n this.getWorkloads();\n });\n }\n\n getCluster(id) {\n return this.backendSrv.get('api/datasources/'+id).then(ds => {\n this.cluster = ds;\n return this.datasourceSrv.get(ds.name);\n });\n }\n\n getWorkloads() {\n let namespace = this.namespace;\n this.clusterDS.getNamespaces().then(namespaces => {\n this.namespaces = namespaces;\n });\n this.clusterDS.getDaemonSets(namespace).then(daemonSets => {\n this.daemonSets = daemonSets;\n });\n this.clusterDS.getReplicationControllers(namespace).then(rc => {\n this.replicationControllers = rc;\n });\n this.clusterDS.getDeployments(namespace).then(deployments => {\n this.deployments = deployments;\n });\n this.clusterDS.getPods(namespace).then(pods => {\n this.pods = pods;\n });\n }\n\n componentHealth(component) {\n var health = \"unhealthy\";\n _.forEach(component.conditions, function(condition) {\n if ((condition.type === \"Healthy\") && (condition.status === \"True\")) {\n health = \"healthy\";\n }\n });\n return health;\n }\n\n isComponentHealthy(component) {\n return this.componentHealth(component) === \"healthy\";\n }\n\n goToPodDashboard(pod) {\n this.$location.path(\"dashboard/db/kubernetes-container\")\n .search({\n \"var-datasource\": this.cluster.jsonData.ds,\n \"var-cluster\": this.cluster.name,\n \"var-node\": slugify(pod.spec.nodeName),\n \"var-namespace\": pod.metadata.namespace,\n \"var-pod\": pod.metadata.name\n });\n }\n\n goToDeploymentDashboard(deploy) {\n this.$location.path(\"dashboard/db/kubernetes-deployments\")\n .search({\n \"var-datasource\": this.cluster.jsonData.ds,\n \"var-cluster\": this.cluster.name,\n \"var-namespace\": deploy.metadata.namespace,\n \"var-deployment\": deploy.metadata.name\n });\n }\n\n goToPodInfo(pod, evt) {\n var clickTargetIsLinkOrHasLinkParents = $(evt.target).closest('a').length > 0;\n\n var closestElm = _.head($(evt.target).closest('div'));\n var clickTargetClickAttr = _.find(closestElm.attributes, {name: \"ng-click\"});\n var clickTargetIsNodeDashboard = clickTargetClickAttr ? clickTargetClickAttr.value === \"ctrl.goToPodDashboard(pod, $event)\" : false;\n if (clickTargetIsLinkOrHasLinkParents === false &&\n clickTargetIsNodeDashboard === false) {\n this.$location.path(\"plugins/raintank-kubernetes-app/page/pod-info\")\n .search({\n \"cluster\": this.cluster.id,\n \"namespace\": slugify(pod.metadata.namespace),\n \"pod\": pod.metadata.name\n });\n }\n }\n}\n\nClusterWorkloadsCtrl.templateUrl = 'components/clusters/partials/cluster_workloads.html';\n"]} -------------------------------------------------------------------------------- /dist/components/clusters/clusters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['lodash', 'app/core/app_events'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var _, appEvents, _createClass, ClustersCtrl; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | return { 15 | setters: [function (_lodash) { 16 | _ = _lodash.default; 17 | }, function (_appCoreApp_events) { 18 | appEvents = _appCoreApp_events.default; 19 | }], 20 | execute: function () { 21 | _createClass = function () { 22 | function defineProperties(target, props) { 23 | for (var i = 0; i < props.length; i++) { 24 | var descriptor = props[i]; 25 | descriptor.enumerable = descriptor.enumerable || false; 26 | descriptor.configurable = true; 27 | if ("value" in descriptor) descriptor.writable = true; 28 | Object.defineProperty(target, descriptor.key, descriptor); 29 | } 30 | } 31 | 32 | return function (Constructor, protoProps, staticProps) { 33 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 34 | if (staticProps) defineProperties(Constructor, staticProps); 35 | return Constructor; 36 | }; 37 | }(); 38 | 39 | _export('ClustersCtrl', ClustersCtrl = function () { 40 | /** @ngInject */ 41 | function ClustersCtrl($scope, $injector, backendSrv, contextSrv, $location) { 42 | _classCallCheck(this, ClustersCtrl); 43 | 44 | var self = this; 45 | this.isOrgEditor = contextSrv.hasRole('Editor') || contextSrv.hasRole('Admin'); 46 | this.backendSrv = backendSrv; 47 | this.$location = $location; 48 | document.title = 'Grafana Kubernetes App'; 49 | this.clusters = {}; 50 | this.pageReady = false; 51 | this.getClusters().then(function () { 52 | self.pageReady = true; 53 | }); 54 | } 55 | 56 | _createClass(ClustersCtrl, [{ 57 | key: 'getClusters', 58 | value: function getClusters() { 59 | var self = this; 60 | return this.backendSrv.get('/api/datasources').then(function (result) { 61 | self.clusters = _.filter(result, { "type": "raintank-kubernetes-datasource" }); 62 | }); 63 | } 64 | }, { 65 | key: 'confirmDelete', 66 | value: function confirmDelete(id) { 67 | var _this = this; 68 | 69 | this.backendSrv.delete('/api/datasources/' + id).then(function () { 70 | _this.getClusters(); 71 | }); 72 | } 73 | }, { 74 | key: 'deleteCluster', 75 | value: function deleteCluster(cluster) { 76 | var _this2 = this; 77 | 78 | appEvents.emit('confirm-modal', { 79 | title: 'Delete', 80 | text: 'Are you sure you want to delete this data source? ' + 'If you need to undeploy the collectors, then do that before deleting the data source.', 81 | yesText: "Delete", 82 | icon: "fa-trash", 83 | onConfirm: function onConfirm() { 84 | _this2.confirmDelete(cluster.id); 85 | } 86 | }); 87 | } 88 | }, { 89 | key: 'clusterInfo', 90 | value: function clusterInfo(cluster) { 91 | this.$location.path("plugins/raintank-kubernetes-app/page/cluster-info").search({ "cluster": cluster.id }); 92 | } 93 | }]); 94 | 95 | return ClustersCtrl; 96 | }()); 97 | 98 | _export('ClustersCtrl', ClustersCtrl); 99 | 100 | ClustersCtrl.templateUrl = 'components/clusters/partials/clusters.html'; 101 | } 102 | }; 103 | }); 104 | //# sourceMappingURL=clusters.js.map 105 | -------------------------------------------------------------------------------- /dist/components/clusters/clusters.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/components/clusters/clusters.js"],"names":["_","appEvents","ClustersCtrl","$scope","$injector","backendSrv","contextSrv","$location","self","isOrgEditor","hasRole","document","title","clusters","pageReady","getClusters","then","get","result","filter","id","delete","cluster","emit","text","yesText","icon","onConfirm","confirmDelete","path","search","templateUrl"],"mappings":";;;;;;;;;;;;;;;AAAOA,O;;AACAC,e;;;;;;;;;;;;;;;;;;;;;8BAEMC,Y;AACX;AACA,8BAAYC,MAAZ,EAAoBC,SAApB,EAA+BC,UAA/B,EAA2CC,UAA3C,EAAuDC,SAAvD,EAAkE;AAAA;;AAChE,cAAIC,OAAO,IAAX;AACA,eAAKC,WAAL,GAAmBH,WAAWI,OAAX,CAAmB,QAAnB,KAAgCJ,WAAWI,OAAX,CAAmB,OAAnB,CAAnD;AACA,eAAKL,UAAL,GAAkBA,UAAlB;AACA,eAAKE,SAAL,GAAiBA,SAAjB;AACAI,mBAASC,KAAT,GAAiB,wBAAjB;AACA,eAAKC,QAAL,GAAgB,EAAhB;AACA,eAAKC,SAAL,GAAiB,KAAjB;AACA,eAAKC,WAAL,GAAmBC,IAAnB,CAAwB,YAAM;AAC5BR,iBAAKM,SAAL,GAAiB,IAAjB;AACD,WAFD;AAGD;;;;wCAEa;AACZ,gBAAIN,OAAO,IAAX;AACA,mBAAO,KAAKH,UAAL,CAAgBY,GAAhB,CAAoB,kBAApB,EACND,IADM,CACD,UAACE,MAAD,EAAY;AAChBV,mBAAKK,QAAL,GAAgBb,EAAEmB,MAAF,CAASD,MAAT,EAAiB,EAAC,QAAQ,gCAAT,EAAjB,CAAhB;AACD,aAHM,CAAP;AAID;;;wCAEaE,E,EAAI;AAAA;;AAChB,iBAAKf,UAAL,CAAgBgB,MAAhB,CAAuB,sBAAsBD,EAA7C,EAAiDJ,IAAjD,CAAsD,YAAM;AAC1D,oBAAKD,WAAL;AACD,aAFD;AAGD;;;wCAEaO,O,EAAS;AAAA;;AACrBrB,sBAAUsB,IAAV,CAAe,eAAf,EAAgC;AAC9BX,qBAAO,QADuB;AAE9BY,oBAAM,uDACJ,uFAH4B;AAI9BC,uBAAS,QAJqB;AAK9BC,oBAAM,UALwB;AAM9BC,yBAAW,qBAAM;AACf,uBAAKC,aAAL,CAAmBN,QAAQF,EAA3B;AACD;AAR6B,aAAhC;AAUD;;;sCAEWE,O,EAAS;AACnB,iBAAKf,SAAL,CAAesB,IAAf,CAAoB,mDAApB,EAAyEC,MAAzE,CAAgF,EAAC,WAAWR,QAAQF,EAApB,EAAhF;AACD;;;;;;;;AAGHlB,mBAAa6B,WAAb,GAA2B,4CAA3B","file":"clusters.js","sourcesContent":["import _ from 'lodash';\nimport appEvents from 'app/core/app_events';\n\nexport class ClustersCtrl {\n /** @ngInject */\n constructor($scope, $injector, backendSrv, contextSrv, $location) {\n var self = this;\n this.isOrgEditor = contextSrv.hasRole('Editor') || contextSrv.hasRole('Admin');\n this.backendSrv = backendSrv;\n this.$location = $location;\n document.title = 'Grafana Kubernetes App';\n this.clusters = {};\n this.pageReady = false;\n this.getClusters().then(() => {\n self.pageReady = true;\n });\n }\n\n getClusters() {\n var self = this;\n return this.backendSrv.get('/api/datasources')\n .then((result) => {\n self.clusters = _.filter(result, {\"type\": \"raintank-kubernetes-datasource\"});\n });\n }\n\n confirmDelete(id) {\n this.backendSrv.delete('/api/datasources/' + id).then(() => {\n this.getClusters();\n });\n }\n\n deleteCluster(cluster) {\n appEvents.emit('confirm-modal', {\n title: 'Delete',\n text: 'Are you sure you want to delete this data source? ' +\n 'If you need to undeploy the collectors, then do that before deleting the data source.',\n yesText: \"Delete\",\n icon: \"fa-trash\",\n onConfirm: () => {\n this.confirmDelete(cluster.id);\n }\n });\n }\n\n clusterInfo(cluster) {\n this.$location.path(\"plugins/raintank-kubernetes-app/page/cluster-info\").search({\"cluster\": cluster.id});\n }\n}\n\nClustersCtrl.templateUrl = 'components/clusters/partials/clusters.html';\n"]} -------------------------------------------------------------------------------- /dist/components/clusters/nodeInfo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['moment'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var moment, _createClass, NodeInfoCtrl; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | function slugify(str) { 15 | var slug = str.replace("@", "at").replace("&", "and").replace(/[.]/g, "_").replace("/\W+/", ""); 16 | return slug; 17 | } 18 | 19 | return { 20 | setters: [function (_moment) { 21 | moment = _moment.default; 22 | }], 23 | execute: function () { 24 | _createClass = function () { 25 | function defineProperties(target, props) { 26 | for (var i = 0; i < props.length; i++) { 27 | var descriptor = props[i]; 28 | descriptor.enumerable = descriptor.enumerable || false; 29 | descriptor.configurable = true; 30 | if ("value" in descriptor) descriptor.writable = true; 31 | Object.defineProperty(target, descriptor.key, descriptor); 32 | } 33 | } 34 | 35 | return function (Constructor, protoProps, staticProps) { 36 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 37 | if (staticProps) defineProperties(Constructor, staticProps); 38 | return Constructor; 39 | }; 40 | }(); 41 | 42 | _export('NodeInfoCtrl', NodeInfoCtrl = function () { 43 | /** @ngInject */ 44 | function NodeInfoCtrl($scope, $injector, backendSrv, datasourceSrv, $q, $location, alertSrv) { 45 | var _this = this; 46 | 47 | _classCallCheck(this, NodeInfoCtrl); 48 | 49 | this.$q = $q; 50 | this.backendSrv = backendSrv; 51 | this.datasourceSrv = datasourceSrv; 52 | this.$location = $location; 53 | document.title = 'Grafana Kubernetes App'; 54 | 55 | this.pageReady = false; 56 | this.cluster = {}; 57 | this.clusterDS = {}; 58 | this.node = {}; 59 | 60 | if (!("cluster" in $location.search())) { 61 | alertSrv.set("no cluster specified.", "no cluster specified in url", 'error'); 62 | return; 63 | } else { 64 | var cluster_id = $location.search().cluster; 65 | var node_name = $location.search().node; 66 | 67 | this.loadDatasource(cluster_id).then(function () { 68 | _this.clusterDS.getNode(node_name).then(function (node) { 69 | _this.node = node; 70 | _this.pageReady = true; 71 | }); 72 | }); 73 | } 74 | } 75 | 76 | _createClass(NodeInfoCtrl, [{ 77 | key: 'loadDatasource', 78 | value: function loadDatasource(id) { 79 | var _this2 = this; 80 | 81 | return this.backendSrv.get('api/datasources/' + id).then(function (ds) { 82 | _this2.cluster = ds; 83 | return _this2.datasourceSrv.get(ds.name); 84 | }).then(function (clusterDS) { 85 | _this2.clusterDS = clusterDS; 86 | return clusterDS; 87 | }); 88 | } 89 | }, { 90 | key: 'goToNodeDashboard', 91 | value: function goToNodeDashboard() { 92 | this.$location.path("dashboard/db/kubernetes-node").search({ 93 | "var-datasource": this.cluster.jsonData.ds, 94 | "var-cluster": this.cluster.name, 95 | "var-node": slugify(this.node.metadata.name) 96 | }); 97 | } 98 | }, { 99 | key: 'conditionStatus', 100 | value: function conditionStatus(condition) { 101 | var status; 102 | if (condition.type === "Ready") { 103 | status = condition.status === "True"; 104 | } else { 105 | status = condition.status === "False"; 106 | } 107 | 108 | return { 109 | value: status, 110 | text: status ? "Ok" : "Error" 111 | }; 112 | } 113 | }, { 114 | key: 'isConditionOk', 115 | value: function isConditionOk(condition) { 116 | return this.conditionStatus(condition).value; 117 | } 118 | }, { 119 | key: 'conditionLastTransitionTime', 120 | value: function conditionLastTransitionTime(condition) { 121 | return moment(condition.lastTransitionTime).format('YYYY-MM-DD HH:mm:ss'); 122 | } 123 | }]); 124 | 125 | return NodeInfoCtrl; 126 | }()); 127 | 128 | _export('NodeInfoCtrl', NodeInfoCtrl); 129 | 130 | NodeInfoCtrl.templateUrl = 'components/clusters/partials/node_info.html'; 131 | } 132 | }; 133 | }); 134 | //# sourceMappingURL=nodeInfo.js.map 135 | -------------------------------------------------------------------------------- /dist/components/clusters/nodeInfo.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/components/clusters/nodeInfo.js"],"names":["slugify","str","slug","replace","moment","NodeInfoCtrl","$scope","$injector","backendSrv","datasourceSrv","$q","$location","alertSrv","document","title","pageReady","cluster","clusterDS","node","search","set","cluster_id","node_name","loadDatasource","then","getNode","id","get","ds","name","path","jsonData","metadata","condition","status","type","value","text","conditionStatus","lastTransitionTime","format","templateUrl"],"mappings":"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","file":"nodeInfo.js","sourcesContent":["import moment from 'moment';\n\nexport class NodeInfoCtrl {\n /** @ngInject */\n constructor($scope, $injector, backendSrv, datasourceSrv, $q, $location, alertSrv) {\n this.$q = $q;\n this.backendSrv = backendSrv;\n this.datasourceSrv = datasourceSrv;\n this.$location = $location;\n document.title = 'Grafana Kubernetes App';\n\n this.pageReady = false;\n this.cluster = {};\n this.clusterDS = {};\n this.node = {};\n\n if (!(\"cluster\" in $location.search())) {\n alertSrv.set(\"no cluster specified.\", \"no cluster specified in url\", 'error');\n return;\n } else {\n let cluster_id = $location.search().cluster;\n let node_name = $location.search().node;\n\n this.loadDatasource(cluster_id).then(() => {\n this.clusterDS.getNode(node_name).then(node => {\n this.node = node;\n this.pageReady = true;\n });\n });\n }\n }\n\n loadDatasource(id) {\n return this.backendSrv.get('api/datasources/' + id)\n .then(ds => {\n this.cluster = ds;\n return this.datasourceSrv.get(ds.name);\n }).then(clusterDS => {\n this.clusterDS = clusterDS;\n return clusterDS;\n });\n }\n\n goToNodeDashboard() {\n this.$location.path(\"dashboard/db/kubernetes-node\")\n .search({\n \"var-datasource\": this.cluster.jsonData.ds,\n \"var-cluster\": this.cluster.name,\n \"var-node\": slugify(this.node.metadata.name)\n });\n }\n\n conditionStatus(condition) {\n var status;\n if (condition.type === \"Ready\") {\n status = condition.status === \"True\";\n } else {\n status = condition.status === \"False\";\n }\n\n return {\n value: status,\n text: status ? \"Ok\" : \"Error\"\n };\n }\n\n isConditionOk(condition) {\n return this.conditionStatus(condition).value;\n }\n\n conditionLastTransitionTime(condition) {\n return moment(condition.lastTransitionTime).format('YYYY-MM-DD HH:mm:ss');\n }\n}\n\nfunction slugify(str) {\n var slug = str.replace(\"@\", \"at\").replace(\"&\", \"and\").replace(/[.]/g, \"_\").replace(\"/\\W+/\", \"\");\n return slug;\n}\n\nNodeInfoCtrl.templateUrl = 'components/clusters/partials/node_info.html';\n"]} -------------------------------------------------------------------------------- /dist/components/clusters/partials/cluster_config.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |
8 |
9 | Name 10 | 11 |
12 |
13 |
14 | 17 | 18 |
19 |
20 |

Data Source Settings

21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 |
33 | Grafana Labs is now offering stealth access to our 100% Graphite compatible hosted metrics to select customers. 34 | Learn More 35 |
36 |
37 |
38 |
39 | 40 | 41 |
42 |

Graphite Read

43 |
44 |
45 |
46 | Datasource 47 | 48 |
49 |
50 |
51 |

Graphite Write

52 |
53 |
54 |
55 | Carbon Host 56 | 57 | 58 | Specify a url to the Carbon Host. Metrics from Kubernetes are sent to the Carbon host and saved to Graphite. This url must be available from inside the Kubernetes cluster. 59 | 60 |
61 |
62 | Port 63 | 64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |

The Deploy button will deploy the Snap Kubernetes Docker image with two metrics collectors that provides summary performance and availability metrics of a Kubernetes Node and its containers. (The manual deployment instructions are at the bottom of the page.)

73 |
74 | 75 | 76 | 77 | Cancel 78 |
79 | 80 | 81 | 82 | Manual Deploy Instructions 83 | 84 | 85 | 86 |
87 |
Manual Deploy
88 |

If you want to deploy manually or to automate the deployment to Kubernetes, the following files are needed.

89 |

90 | 91 | 92 |

93 | 94 | 95 | 96 | 97 |

(The ConfigMap is generated from the settings so remember to save your settings before downloading.)

98 |

To deploy the Config Map and Daemon Set, execute the following:

99 |

kubectl create -f snap-configmap.json

100 |

kubectl create -f snap-kubestate-configmap.json

101 |

kubectl create -f snap-daemonset.json

102 |

kubectl create -f snap-kubestate.json

103 |
104 |
Removing the Daemon Set and Config Map
105 |

kubectl delete daemonsets -n kube-system snap

106 |

kubectl delete deployments -n kube-system snap-kubestate-deployment

107 |

kubectl delete configmaps -n kube-system snap-tasks

108 |

kubectl delete configmaps -n kube-system snap-tasks-kubestate

109 |
110 | -------------------------------------------------------------------------------- /dist/components/clusters/partials/cluster_info.html: -------------------------------------------------------------------------------- 1 | 7 |
8 | 9 | 55 | 56 |

Browse Details from the Kubernetes API

57 |
58 | 59 |

60 | Namespaces (click on a namespace to see its pods and deployments) 61 |

62 |
    63 |
  1. 64 |
    65 |
    66 |
    67 |
    68 | {{ns.metadata.name}} 69 |
    70 |
    71 | {{ns.status.phase}} 72 |
    73 |
    74 |
    75 |
    76 |
  2. 77 |
78 |
79 | 80 |
81 |

82 | Component Statuses 83 |

84 |
    85 |
  1. 86 |
    87 |
    88 |
    89 | {{component.healthState.message}} 90 | 91 | 92 | {{component.healthState.text}} 93 | 94 |
    95 |
    96 |
    97 |
    98 |
    99 | {{component.metadata.name}} 100 |
    101 |
    102 |
    103 |
    104 |
  2. 105 |
106 |
107 | 108 |
109 |

110 | Nodes (click to see node details) 111 |

112 |
    113 |
  1. 114 |
    115 |
    117 |
    119 | 120 | Node Stats Dashboard 121 |
    122 |
    123 |
    124 |
    125 |
    126 | {{node.metadata.name}} 127 |
    128 |
    129 | {{node.healthState.message}} 130 | 131 | 132 | {{node.healthState.text}} 133 | 134 |
    135 |
    136 |
    137 |
    138 |
  2. 139 |
140 |
141 | -------------------------------------------------------------------------------- /dist/components/clusters/partials/cluster_workloads.html: -------------------------------------------------------------------------------- 1 | 5 |
6 | 7 |
8 |
9 |
10 | Namespace: 11 |
12 | 15 |
16 |
17 |
18 |
19 | 20 |
21 |

22 | Daemon Sets 23 |

24 |
    25 |
  1. 26 |
    27 |
    28 |
    29 |
    30 | {{ds.metadata.name}} 31 |
    32 |
    33 |
    34 |
    35 |
  2. 36 |
37 |
38 | 39 |
40 |

41 | Replication Controllers 42 |

43 |
    44 |
  1. 45 |
    46 |
    47 |
    48 |
    49 | {{rc.metadata.name}} 50 |
    51 |
    52 |
    53 |
    54 |
  2. 55 |
56 |
57 | 58 |
59 |

60 | Deployments 61 |

62 |
    63 |
  1. 64 |
    65 |
    66 |
    68 | 69 | Deployment Stats Dashboard 70 |
    71 |
    72 |
    73 |
    74 |
    75 | {{deploy.metadata.name}} 76 |
    77 |
    78 |
    79 |
    80 |
  2. 81 |
82 |
83 | 84 |
85 |

86 | Pods 87 |

88 |
    89 |
  1. 90 |
    91 |
    92 |
    94 | 95 | Pod/Container Stats Dashboard 96 |
    97 |
    98 |
    99 |
    100 |
    101 | {{pod.metadata.name}} 102 |
    103 |
    104 |
    105 |
    106 |
  2. 107 |
108 |
109 | -------------------------------------------------------------------------------- /dist/components/clusters/partials/clusters.html: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | ...loading 11 |
12 | 13 |
14 |
15 |
16 | 17 |

Looks like you don’t have any clulsters yet.
18 | Add a new cluster 19 |

20 |

Your org does not have any clusters configured. Contact your org admin. 21 |

22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 39 | 48 | 49 | 50 |
Name
35 | 36 | {{cluster.name}} Overview 37 | 38 | 40 | 41 | Cluster Config 42 | 43 |    44 | 45 | 46 | 47 |
51 |
52 | -------------------------------------------------------------------------------- /dist/components/clusters/partials/node_info.html: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |
15 |

Addresses

16 |
17 | 18 | {{addr.type}}: {{addr.address}} 19 | 20 |
21 |
22 |
23 |

Capacity

24 |
25 | 26 | {{k}}: {{v}} 27 | 28 |
29 |
30 |
31 |

Labels

32 |
33 | 34 | {{k}}: {{v}} 35 | 36 |
37 |
38 |
39 | 40 |
41 |

Conditions

42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 59 | 60 | 61 | 62 | 63 |
StatusTypeMessageLast Change
51 | 56 | 57 | {{ctrl.conditionStatus(condition).text}} 58 | {{condition.type}}{{condition.message}}{{ctrl.conditionLastTransitionTime(condition)}}
64 |
65 |
66 | 67 |

Info

68 | 69 | 70 | 71 | 72 | 73 |
{{k}}{{v}}
74 |
75 | -------------------------------------------------------------------------------- /dist/components/clusters/partials/pod_info.html: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |
15 |

Node

16 |
17 | 18 | {{ctrl.pod.spec.nodeName}} 19 | 20 |
21 |
22 | 23 | IP: {{ctrl.pod.status.hostIP}} 24 | 25 |
26 |
27 |
28 |

Status

29 |
30 | 31 | Status: {{ctrl.pod.status.phase}} 32 | 33 |
34 |
35 | 36 | Start time: {{ctrl.formatTime(ctrl.pod.status.startTime)}} 37 | 38 |
39 |
40 |
41 |
42 | 43 |
44 |
45 |

Conditions

46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 62 | 63 | 64 | 65 |
StatusTypeLast Change
54 | 59 | 60 | {{ctrl.conditionStatus(condition).text}} 61 | {{condition.type}}{{ctrl.formatTime(condition.lastTransitionTime)}}
66 |
67 |
68 |

Labels

69 |
70 | 71 | {{k}}: {{v}} 72 | 73 |
74 |
75 |
76 |
77 | 78 |
79 |

Containers

80 |
    81 |
  1. 82 |
    83 |
    84 |
    85 |
    86 | {{container.name}} 87 |
    88 |
    89 | image: {{container.image}} 90 |
    91 |
    92 |
    93 |
    94 |
  2. 95 |
96 |
97 | -------------------------------------------------------------------------------- /dist/components/clusters/podInfo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | System.register(["moment"], function (_export, _context) { 4 | "use strict"; 5 | 6 | var moment, _createClass, PodInfoCtrl; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | function slugify(str) { 15 | var slug = str.replace("@", "at").replace("&", "and").replace(/[.]/g, "_").replace("/\W+/", ""); 16 | return slug; 17 | } 18 | 19 | return { 20 | setters: [function (_moment) { 21 | moment = _moment.default; 22 | }], 23 | execute: function () { 24 | _createClass = function () { 25 | function defineProperties(target, props) { 26 | for (var i = 0; i < props.length; i++) { 27 | var descriptor = props[i]; 28 | descriptor.enumerable = descriptor.enumerable || false; 29 | descriptor.configurable = true; 30 | if ("value" in descriptor) descriptor.writable = true; 31 | Object.defineProperty(target, descriptor.key, descriptor); 32 | } 33 | } 34 | 35 | return function (Constructor, protoProps, staticProps) { 36 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 37 | if (staticProps) defineProperties(Constructor, staticProps); 38 | return Constructor; 39 | }; 40 | }(); 41 | 42 | _export("PodInfoCtrl", PodInfoCtrl = function () { 43 | /** @ngInject */ 44 | function PodInfoCtrl($scope, $injector, backendSrv, datasourceSrv, $q, $location, alertSrv) { 45 | var _this = this; 46 | 47 | _classCallCheck(this, PodInfoCtrl); 48 | 49 | this.$q = $q; 50 | this.backendSrv = backendSrv; 51 | this.datasourceSrv = datasourceSrv; 52 | this.$location = $location; 53 | document.title = 'Grafana Kubernetes App'; 54 | 55 | this.pageReady = false; 56 | this.pod = {}; 57 | if (!("cluster" in $location.search())) { 58 | alertSrv.set("no cluster specified.", "no cluster specified in url", 'error'); 59 | return; 60 | } else { 61 | this.cluster_id = $location.search().cluster; 62 | var pod_name = $location.search().pod; 63 | 64 | this.loadDatasource(this.cluster_id).then(function () { 65 | _this.clusterDS.getPod(pod_name).then(function (pod) { 66 | _this.pod = pod; 67 | _this.pageReady = true; 68 | }); 69 | }); 70 | } 71 | } 72 | 73 | _createClass(PodInfoCtrl, [{ 74 | key: "loadDatasource", 75 | value: function loadDatasource(id) { 76 | var _this2 = this; 77 | 78 | return this.backendSrv.get('api/datasources/' + id).then(function (ds) { 79 | _this2.datasource = ds.jsonData.ds; 80 | return _this2.datasourceSrv.get(ds.name); 81 | }).then(function (clusterDS) { 82 | _this2.clusterDS = clusterDS; 83 | return clusterDS; 84 | }); 85 | } 86 | }, { 87 | key: "conditionStatus", 88 | value: function conditionStatus(condition) { 89 | var status; 90 | if (condition.type === "Ready") { 91 | status = condition.status === "True"; 92 | } else { 93 | status = condition.status === "False"; 94 | } 95 | 96 | return { 97 | value: status, 98 | text: status ? "Ok" : "Error" 99 | }; 100 | } 101 | }, { 102 | key: "goToPodDashboard", 103 | value: function goToPodDashboard(pod) { 104 | this.$location.path("dashboard/db/kubernetes-container").search({ 105 | "var-datasource": this.datasource, 106 | "var-cluster": this.clusterDS.name, 107 | "var-node": slugify(pod.spec.nodeName), 108 | "var-namespace": pod.metadata.namespace, 109 | "var-pod": pod.metadata.name 110 | }); 111 | } 112 | }, { 113 | key: "isConditionOk", 114 | value: function isConditionOk(condition) { 115 | return this.conditionStatus(condition).value; 116 | } 117 | }, { 118 | key: "formatTime", 119 | value: function formatTime(time) { 120 | return moment(time).format('YYYY-MM-DD HH:mm:ss'); 121 | } 122 | }]); 123 | 124 | return PodInfoCtrl; 125 | }()); 126 | 127 | _export("PodInfoCtrl", PodInfoCtrl); 128 | 129 | PodInfoCtrl.templateUrl = 'components/clusters/partials/pod_info.html'; 130 | } 131 | }; 132 | }); 133 | //# sourceMappingURL=podInfo.js.map 134 | -------------------------------------------------------------------------------- /dist/components/clusters/podInfo.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/components/clusters/podInfo.js"],"names":["slugify","str","slug","replace","moment","PodInfoCtrl","$scope","$injector","backendSrv","datasourceSrv","$q","$location","alertSrv","document","title","pageReady","pod","search","set","cluster_id","cluster","pod_name","loadDatasource","then","clusterDS","getPod","id","get","datasource","ds","jsonData","name","condition","status","type","value","text","path","spec","nodeName","metadata","namespace","conditionStatus","time","format","templateUrl"],"mappings":"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","file":"podInfo.js","sourcesContent":["import moment from 'moment';\n\nfunction slugify(str) {\n var slug = str.replace(\"@\", \"at\").replace(\"&\", \"and\").replace(/[.]/g, \"_\").replace(\"/\\W+/\", \"\");\n return slug;\n}\n\nexport class PodInfoCtrl {\n /** @ngInject */\n constructor($scope, $injector, backendSrv, datasourceSrv, $q, $location, alertSrv) {\n this.$q = $q;\n this.backendSrv = backendSrv;\n this.datasourceSrv = datasourceSrv;\n this.$location = $location;\n document.title = 'Grafana Kubernetes App';\n\n this.pageReady = false;\n this.pod = {};\n if (!(\"cluster\" in $location.search())) {\n alertSrv.set(\"no cluster specified.\", \"no cluster specified in url\", 'error');\n return;\n } else {\n this.cluster_id = $location.search().cluster;\n let pod_name = $location.search().pod;\n\n this.loadDatasource(this.cluster_id).then(() => {\n this.clusterDS.getPod(pod_name).then(pod => {\n this.pod = pod;\n this.pageReady = true;\n });\n });\n }\n }\n\n loadDatasource(id) {\n return this.backendSrv.get('api/datasources/' + id)\n .then(ds => {\n this.datasource = ds.jsonData.ds;\n return this.datasourceSrv.get(ds.name);\n }).then(clusterDS => {\n this.clusterDS = clusterDS;\n return clusterDS;\n });\n }\n\n conditionStatus(condition) {\n var status;\n if (condition.type === \"Ready\") {\n status = condition.status === \"True\";\n } else {\n status = condition.status === \"False\";\n }\n\n return {\n value: status,\n text: status ? \"Ok\" : \"Error\"\n };\n }\n\n goToPodDashboard(pod) {\n this.$location.path(\"dashboard/db/kubernetes-container\")\n .search({\n \"var-datasource\": this.datasource,\n \"var-cluster\": this.clusterDS.name,\n \"var-node\": slugify(pod.spec.nodeName),\n \"var-namespace\": pod.metadata.namespace,\n \"var-pod\": pod.metadata.name\n });\n }\n\n isConditionOk(condition) {\n return this.conditionStatus(condition).value;\n }\n\n formatTime(time) {\n return moment(time).format('YYYY-MM-DD HH:mm:ss');\n }\n}\n\nPodInfoCtrl.templateUrl = 'components/clusters/partials/pod_info.html';\n"]} -------------------------------------------------------------------------------- /dist/components/config/config.html: -------------------------------------------------------------------------------- 1 |

Kubernetes App

2 | This app integrates the data collected from Snap running in Kubernetes with data available via the Kubernetes API. 3 |
4 | Dashboards imported. Next up: Connect to your Kubernetes Cluster and deploy metric collectors 5 |
6 | -------------------------------------------------------------------------------- /dist/components/config/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | System.register([], function (_export, _context) { 4 | "use strict"; 5 | 6 | var _createClass, ConfigCtrl; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | return { 15 | setters: [], 16 | execute: function () { 17 | _createClass = function () { 18 | function defineProperties(target, props) { 19 | for (var i = 0; i < props.length; i++) { 20 | var descriptor = props[i]; 21 | descriptor.enumerable = descriptor.enumerable || false; 22 | descriptor.configurable = true; 23 | if ("value" in descriptor) descriptor.writable = true; 24 | Object.defineProperty(target, descriptor.key, descriptor); 25 | } 26 | } 27 | 28 | return function (Constructor, protoProps, staticProps) { 29 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 30 | if (staticProps) defineProperties(Constructor, staticProps); 31 | return Constructor; 32 | }; 33 | }(); 34 | 35 | _export("ConfigCtrl", ConfigCtrl = function () { 36 | /** @ngInject */ 37 | function ConfigCtrl($scope, $injector, $q) { 38 | _classCallCheck(this, ConfigCtrl); 39 | 40 | this.$q = $q; 41 | this.enabled = false; 42 | this.appEditCtrl.setPostUpdateHook(this.postUpdate.bind(this)); 43 | } 44 | 45 | _createClass(ConfigCtrl, [{ 46 | key: "postUpdate", 47 | value: function postUpdate() { 48 | var _this = this; 49 | 50 | if (!this.appModel.enabled) { 51 | return this.$q.resolve(); 52 | } 53 | return this.appEditCtrl.importDashboards().then(function () { 54 | _this.enabled = true; 55 | return { 56 | url: "plugins/raintank-kubernetes-app/page/clusters", 57 | message: "Kubernetes App enabled!" 58 | }; 59 | }); 60 | } 61 | }]); 62 | 63 | return ConfigCtrl; 64 | }()); 65 | 66 | _export("ConfigCtrl", ConfigCtrl); 67 | 68 | ConfigCtrl.templateUrl = 'components/config/config.html'; 69 | } 70 | }; 71 | }); 72 | //# sourceMappingURL=config.js.map 73 | -------------------------------------------------------------------------------- /dist/components/config/config.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/components/config/config.js"],"names":["ConfigCtrl","$scope","$injector","$q","enabled","appEditCtrl","setPostUpdateHook","postUpdate","bind","appModel","resolve","importDashboards","then","url","message","templateUrl"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAAaA,U;AACX;AACA,4BAAYC,MAAZ,EAAoBC,SAApB,EAA+BC,EAA/B,EAAmC;AAAA;;AACjC,eAAKA,EAAL,GAAUA,EAAV;AACA,eAAKC,OAAL,GAAe,KAAf;AACA,eAAKC,WAAL,CAAiBC,iBAAjB,CAAmC,KAAKC,UAAL,CAAgBC,IAAhB,CAAqB,IAArB,CAAnC;AACD;;;;uCAEY;AAAA;;AACX,gBAAI,CAAC,KAAKC,QAAL,CAAcL,OAAnB,EAA4B;AAC1B,qBAAO,KAAKD,EAAL,CAAQO,OAAR,EAAP;AACD;AACD,mBAAO,KAAKL,WAAL,CAAiBM,gBAAjB,GAAoCC,IAApC,CAAyC,YAAM;AACpD,oBAAKR,OAAL,GAAe,IAAf;AACA,qBAAO;AACLS,qBAAK,+CADA;AAELC,yBAAS;AAFJ,eAAP;AAID,aANM,CAAP;AAOD;;;;;;;;AAEHd,iBAAWe,WAAX,GAAyB,+BAAzB","file":"config.js","sourcesContent":["export class ConfigCtrl {\n /** @ngInject */\n constructor($scope, $injector, $q) {\n this.$q = $q;\n this.enabled = false;\n this.appEditCtrl.setPostUpdateHook(this.postUpdate.bind(this));\n }\n\n postUpdate() {\n if (!this.appModel.enabled) {\n return this.$q.resolve();\n }\n return this.appEditCtrl.importDashboards().then(() => {\n this.enabled = true;\n return {\n url: \"plugins/raintank-kubernetes-app/page/clusters\",\n message: \"Kubernetes App enabled!\"\n };\n });\n }\n}\nConfigCtrl.templateUrl = 'components/config/config.html';\n"]} -------------------------------------------------------------------------------- /dist/css/dark.css: -------------------------------------------------------------------------------- 1 | tr.dashlist-item:hover { 2 | background-color: #333; 3 | cursor: pointer; 4 | } 5 | 6 | /* pod nav panel */ 7 | .podnav-wrapper { 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | } 12 | 13 | .podnav-tags-wrapper { 14 | width: 60%; 15 | } 16 | 17 | .chosen-tags-container { 18 | display: block; 19 | } 20 | 21 | .chosen-tags-container .label-tag { 22 | line-height: 20px; 23 | margin-bottom: 10px; 24 | } 25 | 26 | .podnav-tags { 27 | display: flex; 28 | flex-direction: column; 29 | flex-wrap: wrap; 30 | align-content: flex-start; 31 | max-height: 300px; 32 | overflow: auto; 33 | } 34 | 35 | .podnav-pods { 36 | width: 40%; 37 | display: flex; 38 | flex-direction: column; 39 | } 40 | 41 | .podnav-results { 42 | display: flex; 43 | flex-direction: column; 44 | overflow: auto; 45 | max-height: 300px; 46 | } 47 | 48 | .podnav-result { 49 | display: block; 50 | padding: 3px 10px; 51 | background-color: #292929; 52 | margin-bottom: 4px; 53 | } 54 | 55 | .podnav-result:hover { 56 | background-color: #333; 57 | cursor: pointer; 58 | } 59 | 60 | .podnav-result button { 61 | margin-right: 15px; 62 | } 63 | 64 | /* plugin config */ 65 | 66 | .pluginconfig-message { 67 | margin: 20px; 68 | } 69 | 70 | .k8s-icon-success { 71 | color: #10a345; 72 | font-size: 24px; 73 | text-decoration: none; 74 | vertical-align: sub; 75 | } 76 | 77 | /* cluster info */ 78 | .main-dash-links { 79 | margin-bottom: 40px; 80 | } 81 | .card-item--main-dash-link { 82 | padding: 20px; 83 | } 84 | -------------------------------------------------------------------------------- /dist/css/light.css: -------------------------------------------------------------------------------- 1 | tr.dashlist-item:hover { 2 | background-color: #ECECEC; 3 | cursor: pointer; 4 | } 5 | 6 | /* pod nav panel */ 7 | .podnav-wrapper { 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | } 12 | 13 | .podnav-tags-wrapper { 14 | width: 60%; 15 | } 16 | 17 | .chosen-tags-container { 18 | display: block; 19 | } 20 | 21 | .chosen-tags-container .label-tag { 22 | line-height: 20px; 23 | margin-bottom: 10px; 24 | } 25 | 26 | .podnav-tags { 27 | display: flex; 28 | flex-direction: column; 29 | flex-wrap: wrap; 30 | align-content: flex-start; 31 | max-height: 300px; 32 | overflow: auto; 33 | } 34 | 35 | .podnav-pods { 36 | width: 40%; 37 | display: flex; 38 | flex-direction: column; 39 | } 40 | 41 | .podnav-results { 42 | display: flex; 43 | flex-direction: column; 44 | overflow: auto; 45 | max-height: 300px; 46 | } 47 | 48 | .podnav-result { 49 | display: block; 50 | padding: 3px 10px; 51 | background-color: #f4f5f8; 52 | margin-bottom: 4px; 53 | } 54 | 55 | .podnav-result:hover { 56 | background-color: #ECECEC; 57 | cursor: pointer; 58 | } 59 | 60 | .podnav-result button { 61 | margin-right: 15px; 62 | } 63 | 64 | /* plugin config */ 65 | 66 | .pluginconfig-message { 67 | margin: 20px; 68 | } 69 | 70 | .k8s-icon-success { 71 | color: #10a345; 72 | font-size: 24px; 73 | text-decoration: none; 74 | vertical-align: sub; 75 | } 76 | 77 | /* cluster info */ 78 | .main-dash-links { 79 | margin-bottom: 40px; 80 | } 81 | .card-item--main-dash-link { 82 | padding: 20px; 83 | } 84 | -------------------------------------------------------------------------------- /dist/datasource/config.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /dist/datasource/datasource.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['lodash'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var _, _createClass, K8sDatasource; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | function addNamespace(namespace) { 15 | return namespace ? 'namespaces/' + namespace + '/' : ''; 16 | } 17 | 18 | function addLabels(labels) { 19 | var querystring = ''; 20 | _.forEach(labels, function (value, label) { 21 | querystring += label + '%3D' + value + '%2C'; 22 | }); 23 | return _.trimEnd(querystring, '%2C'); 24 | } 25 | return { 26 | setters: [function (_lodash) { 27 | _ = _lodash.default; 28 | }], 29 | execute: function () { 30 | _createClass = function () { 31 | function defineProperties(target, props) { 32 | for (var i = 0; i < props.length; i++) { 33 | var descriptor = props[i]; 34 | descriptor.enumerable = descriptor.enumerable || false; 35 | descriptor.configurable = true; 36 | if ("value" in descriptor) descriptor.writable = true; 37 | Object.defineProperty(target, descriptor.key, descriptor); 38 | } 39 | } 40 | 41 | return function (Constructor, protoProps, staticProps) { 42 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 43 | if (staticProps) defineProperties(Constructor, staticProps); 44 | return Constructor; 45 | }; 46 | }(); 47 | 48 | _export('K8sDatasource', K8sDatasource = function () { 49 | function K8sDatasource(instanceSettings, backendSrv, $q) { 50 | _classCallCheck(this, K8sDatasource); 51 | 52 | this.type = instanceSettings.type; 53 | this.url = instanceSettings.url; 54 | this.name = instanceSettings.name; 55 | this.backendSrv = backendSrv; 56 | this.$q = $q; 57 | 58 | this.baseApiUrl = '/api/v1/'; 59 | } 60 | 61 | _createClass(K8sDatasource, [{ 62 | key: 'testDatasource', 63 | value: function testDatasource() { 64 | return this.backendSrv.datasourceRequest({ 65 | url: this.url + '/', 66 | method: 'GET' 67 | }).then(function (response) { 68 | if (response.status === 200) { 69 | return { status: "success", message: "Data source is working", title: "Success" }; 70 | } 71 | }); 72 | } 73 | }, { 74 | key: '_get', 75 | value: function _get(apiResource) { 76 | return this.backendSrv.datasourceRequest({ 77 | url: this.url + apiResource, 78 | method: "GET", 79 | headers: { 'Content-Type': 'application/json' } 80 | }).then(function (response) { 81 | return response.data; 82 | }, function (error) { 83 | return error; 84 | }); 85 | } 86 | }, { 87 | key: 'getNodes', 88 | value: function getNodes() { 89 | return this._get('/api/v1/nodes').then(function (result) { 90 | return result.items; 91 | }); 92 | } 93 | }, { 94 | key: 'getNode', 95 | value: function getNode(name) { 96 | return this._get('/api/v1/nodes/' + name); 97 | } 98 | }, { 99 | key: 'getNamespaces', 100 | value: function getNamespaces() { 101 | return this._get('/api/v1/namespaces').then(function (result) { 102 | return result.items; 103 | }); 104 | } 105 | }, { 106 | key: 'getComponentStatuses', 107 | value: function getComponentStatuses() { 108 | return this._get('/api/v1/componentstatuses').then(function (result) { 109 | return result.items; 110 | }); 111 | } 112 | }, { 113 | key: 'getDaemonSets', 114 | value: function getDaemonSets(namespace) { 115 | return this._get('/apis/extensions/v1beta1/' + addNamespace(namespace) + 'daemonsets').then(function (result) { 116 | return result.items; 117 | }); 118 | } 119 | }, { 120 | key: 'getReplicationControllers', 121 | value: function getReplicationControllers(namespace) { 122 | return this._get('/api/v1/' + addNamespace(namespace) + 'replicationcontrollers').then(function (result) { 123 | return result.items; 124 | }); 125 | } 126 | }, { 127 | key: 'getDeployments', 128 | value: function getDeployments(namespace) { 129 | return this._get('/apis/extensions/v1beta1/' + addNamespace(namespace) + 'deployments').then(function (result) { 130 | return result.items; 131 | }); 132 | } 133 | }, { 134 | key: 'getPods', 135 | value: function getPods(namespace) { 136 | return this._get('/api/v1/' + addNamespace(namespace) + 'pods').then(function (result) { 137 | return result.items; 138 | }); 139 | } 140 | }, { 141 | key: 'getPodsByLabel', 142 | value: function getPodsByLabel(namespace, labels) { 143 | return this._get('/api/v1/' + addNamespace(namespace) + 'pods?labelSelector=' + addLabels(labels)).then(function (result) { 144 | return result.items; 145 | }); 146 | } 147 | }, { 148 | key: 'getPod', 149 | value: function getPod(name) { 150 | return this._get('/api/v1/pods/?fieldSelector=metadata.name%3D' + name).then(function (result) { 151 | if (result.items && result.items.length === 1) { 152 | return result.items[0]; 153 | } else { 154 | return result.items; 155 | } 156 | }); 157 | } 158 | }, { 159 | key: 'getPodsByName', 160 | value: function getPodsByName(names) { 161 | var _this = this; 162 | 163 | var promises = []; 164 | if (Array.isArray(names)) { 165 | _.forEach(names, function (name) { 166 | promises.push(_this.getPod(name)); 167 | }); 168 | return this.$q.all(promises); 169 | } else { 170 | return this.getPod(names).then(function (pod) { 171 | return [pod]; 172 | }); 173 | } 174 | } 175 | }]); 176 | 177 | return K8sDatasource; 178 | }()); 179 | 180 | _export('K8sDatasource', K8sDatasource); 181 | } 182 | }; 183 | }); 184 | //# sourceMappingURL=datasource.js.map 185 | -------------------------------------------------------------------------------- /dist/datasource/datasource.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../src/datasource/datasource.js"],"names":["addNamespace","namespace","addLabels","labels","querystring","_","forEach","value","label","trimEnd","K8sDatasource","instanceSettings","backendSrv","$q","type","url","name","baseApiUrl","datasourceRequest","method","then","response","status","message","title","apiResource","headers","data","error","_get","result","items","length","names","promises","Array","isArray","push","getPod","all","pod"],"mappings":"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","file":"datasource.js","sourcesContent":["import _ from 'lodash';\n\nexport class K8sDatasource {\n constructor(instanceSettings, backendSrv, $q) {\n this.type = instanceSettings.type;\n this.url = instanceSettings.url;\n this.name = instanceSettings.name;\n this.backendSrv = backendSrv;\n this.$q = $q;\n\n this.baseApiUrl = '/api/v1/';\n }\n\n testDatasource() {\n return this.backendSrv.datasourceRequest({\n url: this.url + '/',\n method: 'GET'\n }).then(response => {\n if (response.status === 200) {\n return { status: \"success\", message: \"Data source is working\", title: \"Success\" };\n }\n });\n }\n\n _get(apiResource) {\n return this.backendSrv.datasourceRequest({\n url: this.url + apiResource,\n method: \"GET\",\n headers: { 'Content-Type': 'application/json' }\n }).then(\n response => {\n return response.data;\n }, error => {\n return error;\n });\n }\n\n getNodes() {\n return this._get('/api/v1/nodes')\n .then(result => {\n return result.items;\n });\n }\n\n getNode(name) {\n return this._get('/api/v1/nodes/' + name);\n }\n\n getNamespaces() {\n return this._get('/api/v1/namespaces')\n .then(result => {\n return result.items;\n });\n }\n\n getComponentStatuses() {\n return this._get('/api/v1/componentstatuses')\n .then(result => {\n return result.items;\n });\n }\n\n getDaemonSets(namespace) {\n return this._get('/apis/extensions/v1beta1/' + addNamespace(namespace) + 'daemonsets')\n .then(result => {\n return result.items;\n });\n }\n\n getReplicationControllers(namespace) {\n return this._get('/api/v1/' + addNamespace(namespace) + 'replicationcontrollers')\n .then(result => {\n return result.items;\n });\n }\n\n getDeployments(namespace) {\n return this._get('/apis/extensions/v1beta1/' + addNamespace(namespace) + 'deployments')\n .then(result => {\n return result.items;\n });\n }\n\n getPods(namespace) {\n return this._get('/api/v1/' + addNamespace(namespace) + 'pods')\n .then(result => {\n return result.items;\n });\n }\n\n getPodsByLabel(namespace, labels) {\n return this._get('/api/v1/' + addNamespace(namespace) + 'pods?labelSelector=' + addLabels(labels))\n .then(result => {\n return result.items;\n });\n }\n\n getPod(name) {\n return this._get('/api/v1/pods/?fieldSelector=metadata.name%3D' + name)\n .then(result => {\n if (result.items && result.items.length === 1) {\n return result.items[0];\n } else {\n return result.items;\n }\n });\n }\n\n getPodsByName(names) {\n const promises = [];\n if (Array.isArray(names)) {\n _.forEach(names, name => {\n promises.push(this.getPod(name));\n });\n return this.$q.all(promises);\n } else {\n return this.getPod(names)\n .then(pod => {\n return [pod];\n });\n }\n }\n}\n\nfunction addNamespace(namespace) {\n return namespace ? 'namespaces/' + namespace + '/' : '';\n}\n\nfunction addLabels(labels) {\n let querystring = '';\n _.forEach(labels, (value, label) => {\n querystring += label + '%3D' + value + '%2C';\n });\n return _.trimEnd(querystring, '%2C');\n}\n"]} -------------------------------------------------------------------------------- /dist/datasource/module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['./datasource'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var K8sDatasource, K8sConfigCtrl; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | return { 15 | setters: [function (_datasource) { 16 | K8sDatasource = _datasource.K8sDatasource; 17 | }], 18 | execute: function () { 19 | _export('ConfigCtrl', K8sConfigCtrl = function K8sConfigCtrl() { 20 | _classCallCheck(this, K8sConfigCtrl); 21 | }); 22 | 23 | K8sConfigCtrl.templateUrl = 'datasource/config.html'; 24 | 25 | _export('Datasource', K8sDatasource); 26 | 27 | _export('ConfigCtrl', K8sConfigCtrl); 28 | } 29 | }; 30 | }); 31 | //# sourceMappingURL=module.js.map 32 | -------------------------------------------------------------------------------- /dist/datasource/module.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../src/datasource/module.js"],"names":["K8sDatasource","K8sConfigCtrl","templateUrl"],"mappings":";;;;;;;;;;;;;;;AAAQA,mB,eAAAA,a;;;4BAEFC,a;;;;AACNA,oBAAcC,WAAd,GAA4B,wBAA5B;;4BAGEF,a;;4BACAC,a","file":"module.js","sourcesContent":["import {K8sDatasource} from './datasource';\n\nclass K8sConfigCtrl {}\nK8sConfigCtrl.templateUrl = 'datasource/config.html';\n\nexport {\n K8sDatasource as Datasource,\n K8sConfigCtrl as ConfigCtrl\n};"]} -------------------------------------------------------------------------------- /dist/datasource/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kubernetes", 3 | "id": "raintank-kubernetes-datasource", 4 | "type": "datasource", 5 | 6 | "staticRoot": ".", 7 | 8 | "partials": { 9 | "config": "public/app/plugins/raintank-kubernetes-app/datasource/config.html" 10 | }, 11 | 12 | "metrics": false, 13 | "annotations": false, 14 | 15 | "info": { 16 | "description": "Kubernetes datasource", 17 | "author": { 18 | "name": "Raintank Inc.", 19 | "url": "http://raintank.io" 20 | }, 21 | "logos": { 22 | "small": "img/logo.svg", 23 | "large": "img/logo.svg" 24 | }, 25 | "links": [ 26 | {"name": "GitHub", "url": "https://github.com/raintank/kubernetes-app"}, 27 | {"name": "License", "url": "https://github.com/raintank/kubernetes-app/blob/master/LICENSE"} 28 | ], 29 | "version": "0.0.1", 30 | "updated": "2016-09-27" 31 | }, 32 | 33 | "dependencies": { 34 | "grafanaVersion": "3.x.x", 35 | "plugins": [ ] 36 | } 37 | } -------------------------------------------------------------------------------- /dist/img/app-menu-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/dist/img/app-menu-screenshot.png -------------------------------------------------------------------------------- /dist/img/cluster-dashboard-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/dist/img/cluster-dashboard-screenshot.png -------------------------------------------------------------------------------- /dist/img/container-dashboard-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/dist/img/container-dashboard-screenshot.png -------------------------------------------------------------------------------- /dist/img/namespace-details-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/dist/img/namespace-details-screenshot.png -------------------------------------------------------------------------------- /dist/img/node-dashboard-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/dist/img/node-dashboard-screenshot.png -------------------------------------------------------------------------------- /dist/img/overview-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/dist/img/overview-screenshot.png -------------------------------------------------------------------------------- /dist/img/pod-details-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/dist/img/pod-details-screenshot.png -------------------------------------------------------------------------------- /dist/module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['./components/config/config', './components/clusters/clusters', './components/clusters/clusterConfig', './components/clusters/clusterInfo', './components/clusters/clusterWorkloads', './components/clusters/nodeInfo', './components/clusters/podInfo', 'app/plugins/sdk'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var ConfigCtrl, ClustersCtrl, ClusterConfigCtrl, ClusterInfoCtrl, ClusterWorkloadsCtrl, NodeInfoCtrl, PodInfoCtrl, loadPluginCss; 7 | return { 8 | setters: [function (_componentsConfigConfig) { 9 | ConfigCtrl = _componentsConfigConfig.ConfigCtrl; 10 | }, function (_componentsClustersClusters) { 11 | ClustersCtrl = _componentsClustersClusters.ClustersCtrl; 12 | }, function (_componentsClustersClusterConfig) { 13 | ClusterConfigCtrl = _componentsClustersClusterConfig.ClusterConfigCtrl; 14 | }, function (_componentsClustersClusterInfo) { 15 | ClusterInfoCtrl = _componentsClustersClusterInfo.ClusterInfoCtrl; 16 | }, function (_componentsClustersClusterWorkloads) { 17 | ClusterWorkloadsCtrl = _componentsClustersClusterWorkloads.ClusterWorkloadsCtrl; 18 | }, function (_componentsClustersNodeInfo) { 19 | NodeInfoCtrl = _componentsClustersNodeInfo.NodeInfoCtrl; 20 | }, function (_componentsClustersPodInfo) { 21 | PodInfoCtrl = _componentsClustersPodInfo.PodInfoCtrl; 22 | }, function (_appPluginsSdk) { 23 | loadPluginCss = _appPluginsSdk.loadPluginCss; 24 | }], 25 | execute: function () { 26 | 27 | loadPluginCss({ 28 | dark: 'plugins/raintank-kubernetes-app/css/dark.css', 29 | light: 'plugins/raintank-kubernetes-app/css/light.css' 30 | }); 31 | 32 | _export('ConfigCtrl', ConfigCtrl); 33 | 34 | _export('ClustersCtrl', ClustersCtrl); 35 | 36 | _export('ClusterConfigCtrl', ClusterConfigCtrl); 37 | 38 | _export('ClusterInfoCtrl', ClusterInfoCtrl); 39 | 40 | _export('ClusterWorkloadsCtrl', ClusterWorkloadsCtrl); 41 | 42 | _export('NodeInfoCtrl', NodeInfoCtrl); 43 | 44 | _export('PodInfoCtrl', PodInfoCtrl); 45 | } 46 | }; 47 | }); 48 | //# sourceMappingURL=module.js.map 49 | -------------------------------------------------------------------------------- /dist/module.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/module.js"],"names":["ConfigCtrl","ClustersCtrl","ClusterConfigCtrl","ClusterInfoCtrl","ClusterWorkloadsCtrl","NodeInfoCtrl","PodInfoCtrl","loadPluginCss","dark","light"],"mappings":";;;;;;;;AAAQA,gB,2BAAAA,U;;AACAC,kB,+BAAAA,Y;;AACAC,uB,oCAAAA,iB;;AACAC,qB,kCAAAA,e;;AACAC,0B,uCAAAA,oB;;AACAC,kB,+BAAAA,Y;;AACAC,iB,8BAAAA,W;;AACAC,mB,kBAAAA,a;;;;AAERA,oBAAc;AACZC,cAAM,8CADM;AAEZC,eAAO;AAFK,OAAd;;4BAMET,U;;8BACAC,Y;;mCACAC,iB;;iCACAC,e;;sCACAC,oB;;8BACAC,Y;;6BACAC,W","file":"module.js","sourcesContent":["import {ConfigCtrl} from './components/config/config';\nimport {ClustersCtrl} from './components/clusters/clusters';\nimport {ClusterConfigCtrl} from './components/clusters/clusterConfig';\nimport {ClusterInfoCtrl} from './components/clusters/clusterInfo';\nimport {ClusterWorkloadsCtrl} from './components/clusters/clusterWorkloads';\nimport {NodeInfoCtrl} from './components/clusters/nodeInfo';\nimport {PodInfoCtrl} from './components/clusters/podInfo';\nimport {loadPluginCss} from 'app/plugins/sdk';\n\nloadPluginCss({\n dark: 'plugins/raintank-kubernetes-app/css/dark.css',\n light: 'plugins/raintank-kubernetes-app/css/light.css'\n});\n\nexport {\n ConfigCtrl,\n ClustersCtrl,\n ClusterConfigCtrl,\n ClusterInfoCtrl,\n ClusterWorkloadsCtrl,\n NodeInfoCtrl,\n PodInfoCtrl\n};\n"]} -------------------------------------------------------------------------------- /dist/panels/nodeData/module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['./nodeData', 'app/plugins/sdk'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var NodeDataCtrl, loadPluginCss; 7 | return { 8 | setters: [function (_nodeData) { 9 | NodeDataCtrl = _nodeData.NodeDataCtrl; 10 | }, function (_appPluginsSdk) { 11 | loadPluginCss = _appPluginsSdk.loadPluginCss; 12 | }], 13 | execute: function () { 14 | 15 | loadPluginCss({ 16 | dark: 'plugins/raintank-kubernetes-app/css/dark.css', 17 | light: 'plugins/raintank-kubernetes-app/css/light.css' 18 | }); 19 | 20 | _export('PanelCtrl', NodeDataCtrl); 21 | } 22 | }; 23 | }); 24 | //# sourceMappingURL=module.js.map 25 | -------------------------------------------------------------------------------- /dist/panels/nodeData/module.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/panels/nodeData/module.js"],"names":["NodeDataCtrl","loadPluginCss","dark","light"],"mappings":";;;;;;;;AACQA,kB,aAAAA,Y;;AACAC,mB,kBAAAA,a;;;;AAERA,oBAAc;AACZC,cAAM,8CADM;AAEZC,eAAO;AAFK,OAAd;;2BAMEH,Y","file":"module.js","sourcesContent":["\nimport {NodeDataCtrl} from './nodeData';\nimport {loadPluginCss} from 'app/plugins/sdk';\n\nloadPluginCss({\n dark: 'plugins/raintank-kubernetes-app/css/dark.css',\n light: 'plugins/raintank-kubernetes-app/css/light.css'\n});\n\nexport {\n NodeDataCtrl as PanelCtrl\n};\n"]} -------------------------------------------------------------------------------- /dist/panels/nodeData/nodeStats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['app/core/utils/kbn', 'lodash'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var kbn, _, _createClass, NodeStatsDatasource; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | function slugify(str) { 15 | var slug = str.replace("@", "at").replace("&", "and").replace(/[.]/g, "_").replace("/\W+/", ""); 16 | return slug; 17 | } 18 | return { 19 | setters: [function (_appCoreUtilsKbn) { 20 | kbn = _appCoreUtilsKbn.default; 21 | }, function (_lodash) { 22 | _ = _lodash.default; 23 | }], 24 | execute: function () { 25 | _createClass = function () { 26 | function defineProperties(target, props) { 27 | for (var i = 0; i < props.length; i++) { 28 | var descriptor = props[i]; 29 | descriptor.enumerable = descriptor.enumerable || false; 30 | descriptor.configurable = true; 31 | if ("value" in descriptor) descriptor.writable = true; 32 | Object.defineProperty(target, descriptor.key, descriptor); 33 | } 34 | } 35 | 36 | return function (Constructor, protoProps, staticProps) { 37 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 38 | if (staticProps) defineProperties(Constructor, staticProps); 39 | return Constructor; 40 | }; 41 | }(); 42 | 43 | NodeStatsDatasource = function () { 44 | function NodeStatsDatasource(datasourceSrv, timeSrv) { 45 | _classCallCheck(this, NodeStatsDatasource); 46 | 47 | this.datasourceSrv = datasourceSrv; 48 | this.timeSrv = timeSrv; 49 | } 50 | 51 | _createClass(NodeStatsDatasource, [{ 52 | key: 'issueGraphiteQuery', 53 | value: function issueGraphiteQuery(graphiteDs, query) { 54 | var _this = this; 55 | 56 | return this.datasourceSrv.get(graphiteDs).then(function (datasource) { 57 | var metricsQuery = { 58 | range: _this.timeSrv.timeRange(), 59 | rangeRaw: _this.timeSrv.timeRange().raw, 60 | interval: _this.interval, 61 | intervalMs: _this.intervalMs, 62 | targets: [{ 63 | refId: 'A', 64 | target: query 65 | }], 66 | format: 'json', 67 | maxDataPoints: 1000 68 | }; 69 | 70 | return datasource.query(metricsQuery); 71 | }).then(function (result) { 72 | if (result && result.data) { 73 | return result.data; 74 | } 75 | 76 | return {}; 77 | }); 78 | } 79 | }, { 80 | key: 'getNodeStats', 81 | value: function getNodeStats(cluster_id, graphiteDs) { 82 | var _this2 = this; 83 | 84 | var podsPerNode = void 0, 85 | cpuPerNode = void 0, 86 | memoryPerNode = void 0; 87 | 88 | var podQuery = 'aliasByNode(keepLastValue(groupByNode(snap.' + cluster_id + ".grafanalabs.kubestate.pod.*.*.*.status.phase.Running, 6, 'sum'), 100), 0)"; 89 | var cpuQuery = 'aliasByNode(keepLastValue(groupByNode(snap.' + cluster_id + ".grafanalabs.kubestate.container.*.*.*.*.requested.cpu.cores, 6, 'sum'), 100), 0)"; 90 | var memoryQuery = 'aliasByNode(keepLastValue(groupByNode(snap.' + cluster_id + ".grafanalabs.kubestate.container.*.*.*.*.requested.memory.bytes, 6, 'sum'), 100), 0)"; 91 | 92 | return this.issueGraphiteQuery(graphiteDs, podQuery).then(function (data) { 93 | podsPerNode = data; 94 | return; 95 | }).then(function () { 96 | return _this2.issueGraphiteQuery(graphiteDs, cpuQuery); 97 | }).then(function (data) { 98 | cpuPerNode = data; 99 | return; 100 | }).then(function () { 101 | return _this2.issueGraphiteQuery(graphiteDs, memoryQuery); 102 | }).then(function (data) { 103 | memoryPerNode = data; 104 | return { podsPerNode: podsPerNode, cpuPerNode: cpuPerNode, memoryPerNode: memoryPerNode }; 105 | }); 106 | } 107 | }, { 108 | key: 'updateNodeWithStats', 109 | value: function updateNodeWithStats(node, nodeStats) { 110 | var formatFunc = kbn.valueFormats['percentunit']; 111 | var nodeName = slugify(node.metadata.name); 112 | var podsUsedData = _.find(nodeStats.podsPerNode, { 'target': nodeName }); 113 | if (podsUsedData) { 114 | node.podsUsed = _.last(podsUsedData.datapoints)[0]; 115 | node.podsUsedPerc = formatFunc(node.podsUsed / node.status.capacity.pods, 2, 5); 116 | } 117 | 118 | var cpuData = _.find(nodeStats.cpuPerNode, { 'target': nodeName }); 119 | if (cpuData) { 120 | node.cpuUsage = _.last(cpuData.datapoints)[0]; 121 | node.cpuUsageFormatted = kbn.valueFormats['none'](node.cpuUsage, 2, null); 122 | node.cpuUsagePerc = formatFunc(node.cpuUsage / node.status.capacity.cpu, 2, 5); 123 | } 124 | 125 | var memData = _.find(nodeStats.memoryPerNode, { 'target': nodeName }); 126 | if (memData) { 127 | node.memoryUsage = _.last(memData.datapoints)[0]; 128 | var memCapacity = node.status.capacity.memory.substring(0, node.status.capacity.memory.length - 2) * 1000; 129 | node.memUsageFormatted = kbn.valueFormats['bytes'](node.memoryUsage, 2, null); 130 | node.memCapacityFormatted = kbn.valueFormats['bytes'](memCapacity, 2, null); 131 | node.memoryUsagePerc = formatFunc(node.memoryUsage / memCapacity, 2, 5); 132 | } 133 | 134 | return node; 135 | } 136 | }]); 137 | 138 | return NodeStatsDatasource; 139 | }(); 140 | 141 | _export('default', NodeStatsDatasource); 142 | } 143 | }; 144 | }); 145 | //# sourceMappingURL=nodeStats.js.map 146 | -------------------------------------------------------------------------------- /dist/panels/nodeData/nodeStats.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/panels/nodeData/nodeStats.js"],"names":["slugify","str","slug","replace","kbn","_","NodeStatsDatasource","datasourceSrv","timeSrv","graphiteDs","query","get","then","datasource","metricsQuery","range","timeRange","rangeRaw","raw","interval","intervalMs","targets","refId","target","format","maxDataPoints","result","data","cluster_id","podsPerNode","cpuPerNode","memoryPerNode","podQuery","cpuQuery","memoryQuery","issueGraphiteQuery","node","nodeStats","formatFunc","valueFormats","nodeName","metadata","name","podsUsedData","find","podsUsed","last","datapoints","podsUsedPerc","status","capacity","pods","cpuData","cpuUsage","cpuUsageFormatted","cpuUsagePerc","cpu","memData","memoryUsage","memCapacity","memory","substring","length","memUsageFormatted","memCapacityFormatted","memoryUsagePerc"],"mappings":"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","file":"nodeStats.js","sourcesContent":["import kbn from 'app/core/utils/kbn';\nimport _ from 'lodash';\n\nexport default class NodeStatsDatasource {\n constructor(datasourceSrv, timeSrv) {\n this.datasourceSrv = datasourceSrv;\n this.timeSrv = timeSrv;\n }\n\n issueGraphiteQuery(graphiteDs, query) {\n return this.datasourceSrv.get(graphiteDs)\n .then((datasource) => {\n var metricsQuery = {\n range: this.timeSrv.timeRange(),\n rangeRaw: this.timeSrv.timeRange().raw,\n interval: this.interval,\n intervalMs: this.intervalMs,\n targets: [\n {\n refId: 'A',\n target: query\n }\n ],\n format: 'json',\n maxDataPoints: 1000,\n };\n\n return datasource.query(metricsQuery);\n }).then((result) => {\n if (result && result.data) {\n return result.data;\n }\n\n return {};\n });\n }\n\n getNodeStats(cluster_id, graphiteDs) {\n let podsPerNode, cpuPerNode, memoryPerNode;\n\n const podQuery = 'aliasByNode(keepLastValue(groupByNode(snap.' + cluster_id\n + \".grafanalabs.kubestate.pod.*.*.*.status.phase.Running, 6, 'sum'), 100), 0)\";\n const cpuQuery = 'aliasByNode(keepLastValue(groupByNode(snap.' + cluster_id\n + \".grafanalabs.kubestate.container.*.*.*.*.requested.cpu.cores, 6, 'sum'), 100), 0)\";\n const memoryQuery = 'aliasByNode(keepLastValue(groupByNode(snap.' + cluster_id\n + \".grafanalabs.kubestate.container.*.*.*.*.requested.memory.bytes, 6, 'sum'), 100), 0)\";\n\n return this.issueGraphiteQuery(graphiteDs, podQuery)\n .then(data => {\n podsPerNode = data;\n return;\n }).then(() => {\n return this.issueGraphiteQuery(graphiteDs, cpuQuery);\n })\n .then(data => {\n cpuPerNode = data;\n return;\n }).then(() => {\n return this.issueGraphiteQuery(graphiteDs, memoryQuery);\n })\n .then(data => {\n memoryPerNode = data;\n return {podsPerNode, cpuPerNode, memoryPerNode};\n });\n }\n\n updateNodeWithStats(node, nodeStats) {\n var formatFunc = kbn.valueFormats['percentunit'];\n const nodeName = slugify(node.metadata.name);\n const podsUsedData = _.find(nodeStats.podsPerNode, {'target': nodeName});\n if (podsUsedData) {\n node.podsUsed = _.last(podsUsedData.datapoints)[0];\n node.podsUsedPerc = formatFunc(node.podsUsed / node.status.capacity.pods, 2, 5);\n }\n\n const cpuData = _.find(nodeStats.cpuPerNode, {'target': nodeName});\n if (cpuData) {\n node.cpuUsage = _.last(cpuData.datapoints)[0];\n node.cpuUsageFormatted = kbn.valueFormats['none'](node.cpuUsage, 2, null);\n node.cpuUsagePerc = formatFunc(node.cpuUsage / node.status.capacity.cpu, 2, 5);\n }\n\n const memData = _.find(nodeStats.memoryPerNode, {'target': nodeName});\n if (memData) {\n node.memoryUsage = _.last(memData.datapoints)[0];\n const memCapacity = node.status.capacity.memory.substring(0, node.status.capacity.memory.length - 2) * 1000;\n node.memUsageFormatted = kbn.valueFormats['bytes'](node.memoryUsage, 2, null);\n node.memCapacityFormatted = kbn.valueFormats['bytes'](memCapacity, 2, null);\n node.memoryUsagePerc = formatFunc((node.memoryUsage / memCapacity), 2, 5);\n }\n\n return node;\n }\n}\n\nfunction slugify(str) {\n var slug = str.replace(\"@\", \"at\").replace(\"&\", \"and\").replace(/[.]/g, \"_\").replace(\"/\\W+/\", \"\");\n return slug;\n}\n"]} -------------------------------------------------------------------------------- /dist/panels/nodeData/partials/node_info.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{group.header}} 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
Node NameHealthPods%CPU requests (cores)%Memory requests (bytes)%
{{node.metadata.name}}{{node.healthState.message}} 23 | 24 | {{node.healthState.text}} 25 | {{(node.podsUsed || '?') + ' / ' + node.status.capacity.pods}}{{node.podsUsedPerc || '?%'}}{{(node.cpuUsageFormatted || '?') + ' / ' + node.status.capacity.cpu}}{{node.cpuUsagePerc || '?%'}}{{(node.memUsageFormatted || '?') + ' / ' + node.memCapacityFormatted}}{{node.memoryUsagePerc || '?%'}}
35 |
36 |
37 | 38 |
39 | 40 | 44 | 45 |
46 |
47 |

Addresses

48 |
49 | 50 | {{addr.type}}: {{addr.address}} 51 | 52 |
53 |
54 |
55 |

Capacity

56 |
57 | 58 | {{k}}: {{v}} 59 | 60 |
61 |
62 |
63 |

Labels

64 |
65 | 66 | {{k}}: {{v}} 67 | 68 |
69 |
70 |
71 | 72 |
73 |

Conditions

74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 91 | 92 | 93 | 94 | 95 |
StatusTypeMessageLast Change
83 | 88 | 89 | {{ctrl.conditionStatus(condition).text}} 90 | {{condition.type}}{{condition.message}}{{ctrl.conditionLastTransitionTime(condition)}}
96 |
97 |
98 | -------------------------------------------------------------------------------- /dist/panels/nodeData/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "panel", 3 | "name": "Kubernetes Node Info", 4 | "id": "raintank-kubernetes-nodeinfo-panel" 5 | } 6 | -------------------------------------------------------------------------------- /dist/panels/podNav/module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['./podNav', 'app/plugins/sdk'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var PodNavCtrl, loadPluginCss; 7 | return { 8 | setters: [function (_podNav) { 9 | PodNavCtrl = _podNav.PodNavCtrl; 10 | }, function (_appPluginsSdk) { 11 | loadPluginCss = _appPluginsSdk.loadPluginCss; 12 | }], 13 | execute: function () { 14 | 15 | loadPluginCss({ 16 | dark: 'plugins/raintank-kubernetes-app/css/dark.css', 17 | light: 'plugins/raintank-kubernetes-app/css/light.css' 18 | }); 19 | 20 | _export('PanelCtrl', PodNavCtrl); 21 | } 22 | }; 23 | }); 24 | //# sourceMappingURL=module.js.map 25 | -------------------------------------------------------------------------------- /dist/panels/podNav/module.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../../src/panels/podNav/module.js"],"names":["PodNavCtrl","loadPluginCss","dark","light"],"mappings":";;;;;;;;AACQA,gB,WAAAA,U;;AACAC,mB,kBAAAA,a;;;;AAERA,oBAAc;AACZC,cAAM,8CADM;AAEZC,eAAO;AAFK,OAAd;;2BAMEH,U","file":"module.js","sourcesContent":["\nimport {PodNavCtrl} from './podNav';\nimport {loadPluginCss} from 'app/plugins/sdk';\n\nloadPluginCss({\n dark: 'plugins/raintank-kubernetes-app/css/dark.css',\n light: 'plugins/raintank-kubernetes-app/css/light.css'\n});\n\nexport {\n PodNavCtrl as PanelCtrl\n};\n"]} -------------------------------------------------------------------------------- /dist/panels/podNav/partials/pod_nav.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Filter Pods By Kubernetes Tag

4 |
5 | 6 | 7 | {{tag}}={{value}} 8 | 9 | 10 |
11 |
12 |
13 |
14 | 17 |
18 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |

Filter by Pod Name

28 |
29 | 30 | 31 | {{podTag}} 32 | 33 | 34 |
35 |
36 | 37 | 38 | {{pod}} 39 | 40 |
41 |
42 |
43 | -------------------------------------------------------------------------------- /dist/panels/podNav/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "panel", 3 | "name": "Kubernetes Pod Nav", 4 | "id": "raintank-kubernetes-podnav-panel" 5 | } 6 | -------------------------------------------------------------------------------- /dist/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "app", 3 | "name": "kubernetes", 4 | "id": "raintank-kubernetes-app", 5 | 6 | "routes": [ 7 | ], 8 | 9 | "info": { 10 | "description": "Kubernetes app. shows data collected by snap.", 11 | "author": { 12 | "name": "Raintank Inc.", 13 | "url": "https://grafana.com/" 14 | }, 15 | "keywords": ["raintank", "kubernetes", "snap"], 16 | "logos": { 17 | "small": "img/logo.svg", 18 | "large": "img/logo.svg" 19 | }, 20 | "links": [ 21 | {"name": "New App", "url": "https://github.com/grafana/kubernetes-app"}, 22 | {"name": "GitHub", "url": "https://github.com/raintank/legacy-kubernetes-app"}, 23 | {"name": "License", "url": "https://github.com/raintank/legacy-kubernetes-app/blob/master/LICENSE"} 24 | ], 25 | "screenshots": [ 26 | {"name": "Cluster Dashboard", "path": "img/cluster-dashboard-screenshot.png"}, 27 | {"name": "Container Dashboard", "path": "img/container-dashboard-screenshot.png"}, 28 | {"name": "Node Dashboard", "path": "img/node-dashboard-screenshot.png"}, 29 | {"name": "Overview Page", "path": "img/overview-screenshot.png"}, 30 | {"name": "Pod Details Page", "path": "img/pod-details-screenshot.png"}, 31 | {"name": "Namespace Details Page", "path": "img/namespace-details-screenshot.png"} 32 | ], 33 | "version": "0.0.9", 34 | "updated": "2018-02-27" 35 | }, 36 | 37 | "includes": [ 38 | { "type": "page", "name": "Clusters", "component": "ClustersCtrl", "role": "Viewer", "addToNav": true, "defaultNav": true}, 39 | { "type": "page", "name": "Cluster Config", "component": "ClusterConfigCtrl", "role": "Editor", "addToNav": false}, 40 | { "type": "page", "name": "Cluster Info", "component": "ClusterInfoCtrl", "role": "Viewer", "addToNav": false}, 41 | { "type": "page", "name": "Cluster Workloads", "component": "ClusterWorkloadsCtrl", "role": "Viewer", "addToNav": false}, 42 | { "type": "page", "name": "Node Info", "component": "NodeInfoCtrl", "role": "Viewer", "addToNav": false}, 43 | { "type": "page", "name": "Pod Info", "component": "PodInfoCtrl", "role": "Viewer", "addToNav": false}, 44 | { 45 | "type": "datasource", 46 | "name": "kubernetes DS" 47 | }, 48 | { 49 | "type": "dashboard", 50 | "name": "Kubernetes Node", 51 | "path": "dashboards/kubernetes-node.json", 52 | "addToNav": false 53 | }, 54 | { 55 | "type": "dashboard", 56 | "name": "Kubernetes Container", 57 | "path": "dashboards/kubernetes-container.json", 58 | "addToNav": false 59 | }, 60 | { 61 | "type": "dashboard", 62 | "name": "Kubernetes Cluster", 63 | "path": "dashboards/kubernetes-cluster.json", 64 | "addToNav": false 65 | }, 66 | { 67 | "type": "dashboard", 68 | "name": "Kubernetes Deployments", 69 | "path": "dashboards/kubernetes-deployments.json", 70 | "addToNav": false 71 | }, 72 | { 73 | "type": "panel", 74 | "name": "Kubernetes Node Info" 75 | }, 76 | { 77 | "type": "panel", 78 | "name": "Kubernetes Pod Nav" 79 | } 80 | ], 81 | 82 | "dependencies": { 83 | "grafanaVersion": "3.0+", 84 | "plugins": [] 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/raintank/kubernetes-app.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/raintank/kubernetes-app/issues" 17 | }, 18 | "devDependencies": { 19 | "grunt": "~0.4.5", 20 | "babel": "~6.5.1", 21 | "grunt-babel": "~6.0.0", 22 | "grunt-contrib-copy": "~0.8.2", 23 | "grunt-contrib-watch": "^0.6.1", 24 | "grunt-contrib-uglify": "~0.11.0", 25 | "grunt-systemjs-builder": "^0.2.5", 26 | "load-grunt-tasks": "~3.2.0", 27 | "grunt-execute": "~0.2.2", 28 | "grunt-sass": "^1.1.0", 29 | "grunt-contrib-clean": "~0.6.0" 30 | }, 31 | "dependencies": { 32 | "babel-plugin-transform-es2015-for-of": "^6.5.2", 33 | "babel-plugin-transform-es2015-modules-systemjs": "^6.5.0", 34 | "babel-preset-es2015": "^6.5.0", 35 | "grunt-contrib-jshint": "^1.0.0", 36 | "grunt-jscs": "^2.8.0", 37 | "grunt-cli": "^1.2.0", 38 | "lodash": "~4.0.0", 39 | "jshint-stylish": "~2.1.0" 40 | }, 41 | "homepage": "https://raintank.io" 42 | } 43 | -------------------------------------------------------------------------------- /src/components/clusters/clusterInfo.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import $ from 'jquery'; 3 | 4 | function slugify(str) { 5 | var slug = str.replace("@", "at").replace("&", "and").replace(/[.]/g, "_").replace("/\W+/", ""); 6 | return slug; 7 | } 8 | 9 | export class ClusterInfoCtrl { 10 | /** @ngInject */ 11 | constructor($scope, $injector, backendSrv, datasourceSrv, $q, $location, alertSrv) { 12 | this.$q = $q; 13 | this.backendSrv = backendSrv; 14 | this.datasourceSrv = datasourceSrv; 15 | this.$location = $location; 16 | document.title = 'Grafana Kubernetes App'; 17 | 18 | this.pageReady = false; 19 | this.cluster = {}; 20 | this.componentStatuses = []; 21 | this.namespaces = []; 22 | this.namespace = ""; 23 | this.nodes = []; 24 | 25 | if (!("cluster" in $location.search())) { 26 | alertSrv.set("no cluster specified.", "no cluster specified in url", 'error'); 27 | return; 28 | } 29 | 30 | this.getCluster($location.search().cluster) 31 | .then(clusterDS => { 32 | this.clusterDS = clusterDS; 33 | this.pageReady = true; 34 | this.getClusterInfo(); 35 | }); 36 | } 37 | 38 | getCluster(id) { 39 | return this.backendSrv.get('api/datasources/' + id).then(ds => { 40 | this.cluster = ds; 41 | return this.datasourceSrv.get(ds.name); 42 | }); 43 | } 44 | 45 | getClusterInfo() { 46 | this.clusterDS.getComponentStatuses().then(stats => { 47 | this.componentStatuses = _.map(stats, stat => { 48 | stat.healthState = getComponentHealth(stat); 49 | return stat; 50 | }); 51 | }); 52 | this.clusterDS.getNamespaces().then(namespaces => { 53 | this.namespaces = namespaces; 54 | }); 55 | this.clusterDS.getNodes().then(nodes => { 56 | this.nodes = _.map(nodes, node => { 57 | node.healthState = getNodeHealth(node); 58 | return node; 59 | }); 60 | }); 61 | } 62 | 63 | goToClusterDashboard() { 64 | this.$location.path("dashboard/db/kubernetes-cluster") 65 | .search({ 66 | "var-datasource": this.cluster.jsonData.ds, 67 | "var-cluster": this.cluster.name 68 | }); 69 | } 70 | 71 | goToPodDashboard() { 72 | this.$location.path("dashboard/db/kubernetes-container") 73 | .search({ 74 | "var-datasource": this.cluster.jsonData.ds, 75 | "var-cluster": this.cluster.name, 76 | "var-node": 'All', 77 | "var-namespace": 'All', 78 | "var-pod": 'All' 79 | }); 80 | } 81 | 82 | goToNodeDashboard(node, evt) { 83 | var clickTargetIsLinkOrHasLinkParents = $(evt.target).closest('a').length > 0; 84 | if (clickTargetIsLinkOrHasLinkParents === false) { 85 | this.$location.path("dashboard/db/kubernetes-node") 86 | .search({ 87 | "var-datasource": this.cluster.jsonData.ds, 88 | "var-cluster": this.cluster.name, 89 | "var-node": node === 'All' ? 'All': slugify(node.metadata.name) 90 | }); 91 | } 92 | } 93 | 94 | goToWorkloads(ns, evt) { 95 | var clickTargetIsLinkOrHasLinkParents = $(evt.target).closest('a').length > 0; 96 | if (clickTargetIsLinkOrHasLinkParents === false) { 97 | this.$location.path("plugins/raintank-kubernetes-app/page/cluster-workloads") 98 | .search({ 99 | "cluster": this.cluster.id, 100 | "namespace": slugify(ns.metadata.name) 101 | }); 102 | } 103 | } 104 | 105 | goToNodeInfo(node, evt) { 106 | var clickTargetIsLinkOrHasLinkParents = $(evt.target).closest('a').length > 0; 107 | 108 | var closestElm = _.head($(evt.target).closest('div')); 109 | var clickTargetClickAttr = _.find(closestElm.attributes, {name: "ng-click"}); 110 | var clickTargetIsNodeDashboard = clickTargetClickAttr ? clickTargetClickAttr.value === "ctrl.goToNodeDashboard(node, $event)" : false; 111 | if (clickTargetIsLinkOrHasLinkParents === false && 112 | clickTargetIsNodeDashboard === false) { 113 | this.$location.path("plugins/raintank-kubernetes-app/page/node-info") 114 | .search({ 115 | "cluster": this.cluster.id, 116 | "node": node.metadata.name 117 | }); 118 | } 119 | } 120 | } 121 | 122 | function getComponentHealth(component) { 123 | let health = "unhealthy"; 124 | let message = ''; 125 | _.forEach(component.conditions, condition => { 126 | if (condition.type === "Healthy" && 127 | condition.status === "True") { 128 | health = "ok"; 129 | } else { 130 | message = condition.message; 131 | } 132 | }); 133 | return getHealthState(health, message); 134 | } 135 | 136 | function getNodeHealth(node) { 137 | let health = "unhealthy"; 138 | let message = ''; 139 | _.forEach(node.status.conditions, condition => { 140 | if (condition.type === "Ready" && 141 | condition.status === "True") { 142 | health = "ok"; 143 | } else { 144 | message = condition.message; 145 | } 146 | }); 147 | return getHealthState(health, message); 148 | } 149 | 150 | function getHealthState(health, message) { 151 | switch (health) { 152 | case 'ok': { 153 | return { 154 | text: 'OK', 155 | iconClass: 'icon-gf icon-gf-online', 156 | stateClass: 'alert-state-ok' 157 | }; 158 | } 159 | case 'unhealthy': { 160 | return { 161 | text: 'UNHEALTHY', 162 | iconClass: 'icon-gf icon-gf-critical', 163 | stateClass: 'alert-state-critical', 164 | message: message || '' 165 | }; 166 | } 167 | case 'warning': { 168 | return { 169 | text: 'warning', 170 | iconClass: "icon-gf icon-gf-critical", 171 | stateClass: 'alert-state-warning', 172 | message: message || '' 173 | }; 174 | } 175 | } 176 | } 177 | 178 | ClusterInfoCtrl.templateUrl = 'components/clusters/partials/cluster_info.html'; 179 | -------------------------------------------------------------------------------- /src/components/clusters/clusterWorkloads.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import $ from 'jquery'; 3 | 4 | function slugify(str) { 5 | var slug = str.replace("@", "at").replace("&", "and").replace(/[.]/g, "_").replace("/\W+/", ""); 6 | return slug; 7 | } 8 | 9 | export class ClusterWorkloadsCtrl { 10 | /** @ngInject */ 11 | constructor($scope, $injector, backendSrv, datasourceSrv, $q, $location, alertSrv) { 12 | this.$q = $q; 13 | this.backendSrv = backendSrv; 14 | this.datasourceSrv = datasourceSrv; 15 | this.$location = $location; 16 | document.title = 'Grafana Kubernetes App'; 17 | 18 | this.pageReady = false; 19 | this.cluster = {}; 20 | this.namespaces = []; 21 | this.namespace = ""; 22 | this.daemonSets = []; 23 | this.replicationControllers = []; 24 | this.deployments = []; 25 | this.pods = []; 26 | 27 | if (!("cluster" in $location.search())) { 28 | alertSrv.set("no cluster specified.", "no cluster specified in url", 'error'); 29 | return; 30 | } 31 | 32 | if ("namespace" in $location.search()) { 33 | this.namespace = $location.search().namespace; 34 | } 35 | 36 | this.getCluster($location.search().cluster) 37 | .then(clusterDS => { 38 | this.clusterDS = clusterDS; 39 | this.pageReady = true; 40 | this.getWorkloads(); 41 | }); 42 | } 43 | 44 | getCluster(id) { 45 | return this.backendSrv.get('api/datasources/'+id).then(ds => { 46 | this.cluster = ds; 47 | return this.datasourceSrv.get(ds.name); 48 | }); 49 | } 50 | 51 | getWorkloads() { 52 | let namespace = this.namespace; 53 | this.clusterDS.getNamespaces().then(namespaces => { 54 | this.namespaces = namespaces; 55 | }); 56 | this.clusterDS.getDaemonSets(namespace).then(daemonSets => { 57 | this.daemonSets = daemonSets; 58 | }); 59 | this.clusterDS.getReplicationControllers(namespace).then(rc => { 60 | this.replicationControllers = rc; 61 | }); 62 | this.clusterDS.getDeployments(namespace).then(deployments => { 63 | this.deployments = deployments; 64 | }); 65 | this.clusterDS.getPods(namespace).then(pods => { 66 | this.pods = pods; 67 | }); 68 | } 69 | 70 | componentHealth(component) { 71 | var health = "unhealthy"; 72 | _.forEach(component.conditions, function(condition) { 73 | if ((condition.type === "Healthy") && (condition.status === "True")) { 74 | health = "healthy"; 75 | } 76 | }); 77 | return health; 78 | } 79 | 80 | isComponentHealthy(component) { 81 | return this.componentHealth(component) === "healthy"; 82 | } 83 | 84 | goToPodDashboard(pod) { 85 | this.$location.path("dashboard/db/kubernetes-container") 86 | .search({ 87 | "var-datasource": this.cluster.jsonData.ds, 88 | "var-cluster": this.cluster.name, 89 | "var-node": slugify(pod.spec.nodeName), 90 | "var-namespace": pod.metadata.namespace, 91 | "var-pod": pod.metadata.name 92 | }); 93 | } 94 | 95 | goToDeploymentDashboard(deploy) { 96 | this.$location.path("dashboard/db/kubernetes-deployments") 97 | .search({ 98 | "var-datasource": this.cluster.jsonData.ds, 99 | "var-cluster": this.cluster.name, 100 | "var-namespace": deploy.metadata.namespace, 101 | "var-deployment": deploy.metadata.name 102 | }); 103 | } 104 | 105 | goToPodInfo(pod, evt) { 106 | var clickTargetIsLinkOrHasLinkParents = $(evt.target).closest('a').length > 0; 107 | 108 | var closestElm = _.head($(evt.target).closest('div')); 109 | var clickTargetClickAttr = _.find(closestElm.attributes, {name: "ng-click"}); 110 | var clickTargetIsNodeDashboard = clickTargetClickAttr ? clickTargetClickAttr.value === "ctrl.goToPodDashboard(pod, $event)" : false; 111 | if (clickTargetIsLinkOrHasLinkParents === false && 112 | clickTargetIsNodeDashboard === false) { 113 | this.$location.path("plugins/raintank-kubernetes-app/page/pod-info") 114 | .search({ 115 | "cluster": this.cluster.id, 116 | "namespace": slugify(pod.metadata.namespace), 117 | "pod": pod.metadata.name 118 | }); 119 | } 120 | } 121 | } 122 | 123 | ClusterWorkloadsCtrl.templateUrl = 'components/clusters/partials/cluster_workloads.html'; 124 | -------------------------------------------------------------------------------- /src/components/clusters/clusters.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import appEvents from 'app/core/app_events'; 3 | 4 | export class ClustersCtrl { 5 | /** @ngInject */ 6 | constructor($scope, $injector, backendSrv, contextSrv, $location) { 7 | var self = this; 8 | this.isOrgEditor = contextSrv.hasRole('Editor') || contextSrv.hasRole('Admin'); 9 | this.backendSrv = backendSrv; 10 | this.$location = $location; 11 | document.title = 'Grafana Kubernetes App'; 12 | this.clusters = {}; 13 | this.pageReady = false; 14 | this.getClusters().then(() => { 15 | self.pageReady = true; 16 | }); 17 | } 18 | 19 | getClusters() { 20 | var self = this; 21 | return this.backendSrv.get('/api/datasources') 22 | .then((result) => { 23 | self.clusters = _.filter(result, {"type": "raintank-kubernetes-datasource"}); 24 | }); 25 | } 26 | 27 | confirmDelete(id) { 28 | this.backendSrv.delete('/api/datasources/' + id).then(() => { 29 | this.getClusters(); 30 | }); 31 | } 32 | 33 | deleteCluster(cluster) { 34 | appEvents.emit('confirm-modal', { 35 | title: 'Delete', 36 | text: 'Are you sure you want to delete this data source? ' + 37 | 'If you need to undeploy the collectors, then do that before deleting the data source.', 38 | yesText: "Delete", 39 | icon: "fa-trash", 40 | onConfirm: () => { 41 | this.confirmDelete(cluster.id); 42 | } 43 | }); 44 | } 45 | 46 | clusterInfo(cluster) { 47 | this.$location.path("plugins/raintank-kubernetes-app/page/cluster-info").search({"cluster": cluster.id}); 48 | } 49 | } 50 | 51 | ClustersCtrl.templateUrl = 'components/clusters/partials/clusters.html'; 52 | -------------------------------------------------------------------------------- /src/components/clusters/nodeInfo.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | export class NodeInfoCtrl { 4 | /** @ngInject */ 5 | constructor($scope, $injector, backendSrv, datasourceSrv, $q, $location, alertSrv) { 6 | this.$q = $q; 7 | this.backendSrv = backendSrv; 8 | this.datasourceSrv = datasourceSrv; 9 | this.$location = $location; 10 | document.title = 'Grafana Kubernetes App'; 11 | 12 | this.pageReady = false; 13 | this.cluster = {}; 14 | this.clusterDS = {}; 15 | this.node = {}; 16 | 17 | if (!("cluster" in $location.search())) { 18 | alertSrv.set("no cluster specified.", "no cluster specified in url", 'error'); 19 | return; 20 | } else { 21 | let cluster_id = $location.search().cluster; 22 | let node_name = $location.search().node; 23 | 24 | this.loadDatasource(cluster_id).then(() => { 25 | this.clusterDS.getNode(node_name).then(node => { 26 | this.node = node; 27 | this.pageReady = true; 28 | }); 29 | }); 30 | } 31 | } 32 | 33 | loadDatasource(id) { 34 | return this.backendSrv.get('api/datasources/' + id) 35 | .then(ds => { 36 | this.cluster = ds; 37 | return this.datasourceSrv.get(ds.name); 38 | }).then(clusterDS => { 39 | this.clusterDS = clusterDS; 40 | return clusterDS; 41 | }); 42 | } 43 | 44 | goToNodeDashboard() { 45 | this.$location.path("dashboard/db/kubernetes-node") 46 | .search({ 47 | "var-datasource": this.cluster.jsonData.ds, 48 | "var-cluster": this.cluster.name, 49 | "var-node": slugify(this.node.metadata.name) 50 | }); 51 | } 52 | 53 | conditionStatus(condition) { 54 | var status; 55 | if (condition.type === "Ready") { 56 | status = condition.status === "True"; 57 | } else { 58 | status = condition.status === "False"; 59 | } 60 | 61 | return { 62 | value: status, 63 | text: status ? "Ok" : "Error" 64 | }; 65 | } 66 | 67 | isConditionOk(condition) { 68 | return this.conditionStatus(condition).value; 69 | } 70 | 71 | conditionLastTransitionTime(condition) { 72 | return moment(condition.lastTransitionTime).format('YYYY-MM-DD HH:mm:ss'); 73 | } 74 | } 75 | 76 | function slugify(str) { 77 | var slug = str.replace("@", "at").replace("&", "and").replace(/[.]/g, "_").replace("/\W+/", ""); 78 | return slug; 79 | } 80 | 81 | NodeInfoCtrl.templateUrl = 'components/clusters/partials/node_info.html'; 82 | -------------------------------------------------------------------------------- /src/components/clusters/partials/cluster_config.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |
8 |
9 | Name 10 | 11 |
12 |
13 |
14 | 17 | 18 |
19 |
20 |

Data Source Settings

21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 |
33 | Grafana Labs is now offering stealth access to our 100% Graphite compatible hosted metrics to select customers. 34 | Learn More 35 |
36 |
37 |
38 |
39 | 40 | 41 |
42 |

Graphite Read

43 |
44 |
45 |
46 | Datasource 47 | 48 |
49 |
50 |
51 |

Graphite Write

52 |
53 |
54 |
55 | Carbon Host 56 | 57 | 58 | Specify a url to the Carbon Host. Metrics from Kubernetes are sent to the Carbon host and saved to Graphite. This url must be available from inside the Kubernetes cluster. 59 | 60 |
61 |
62 | Port 63 | 64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |

The Deploy button will deploy the Snap Kubernetes Docker image with two metrics collectors that provides summary performance and availability metrics of a Kubernetes Node and its containers. (The manual deployment instructions are at the bottom of the page.)

73 |
74 | 75 | 76 | 77 | Cancel 78 |
79 | 80 | 81 | 82 | Manual Deploy Instructions 83 | 84 | 85 | 86 |
87 |
Manual Deploy
88 |

If you want to deploy manually or to automate the deployment to Kubernetes, the following files are needed.

89 |

90 | 91 | 92 |

93 | 94 | 95 | 96 | 97 |

(The ConfigMap is generated from the settings so remember to save your settings before downloading.)

98 |

To deploy the Config Map and Daemon Set, execute the following:

99 |

kubectl create -f snap-configmap.json

100 |

kubectl create -f snap-kubestate-configmap.json

101 |

kubectl create -f snap-daemonset.json

102 |

kubectl create -f snap-kubestate.json

103 |
104 |
Removing the Daemon Set and Config Map
105 |

kubectl delete daemonsets -n kube-system snap

106 |

kubectl delete deployments -n kube-system snap-kubestate-deployment

107 |

kubectl delete configmaps -n kube-system snap-tasks

108 |

kubectl delete configmaps -n kube-system snap-tasks-kubestate

109 |
110 | -------------------------------------------------------------------------------- /src/components/clusters/partials/cluster_info.html: -------------------------------------------------------------------------------- 1 | 7 |
8 | 9 | 55 | 56 |

Browse Details from the Kubernetes API

57 |
58 | 59 |

60 | Namespaces (click on a namespace to see its pods and deployments) 61 |

62 |
    63 |
  1. 64 |
    65 |
    66 |
    67 |
    68 | {{ns.metadata.name}} 69 |
    70 |
    71 | {{ns.status.phase}} 72 |
    73 |
    74 |
    75 |
    76 |
  2. 77 |
78 |
79 | 80 |
81 |

82 | Component Statuses 83 |

84 |
    85 |
  1. 86 |
    87 |
    88 |
    89 | {{component.healthState.message}} 90 | 91 | 92 | {{component.healthState.text}} 93 | 94 |
    95 |
    96 |
    97 |
    98 |
    99 | {{component.metadata.name}} 100 |
    101 |
    102 |
    103 |
    104 |
  2. 105 |
106 |
107 | 108 |
109 |

110 | Nodes (click to see node details) 111 |

112 |
    113 |
  1. 114 |
    115 |
    117 |
    119 | 120 | Node Stats Dashboard 121 |
    122 |
    123 |
    124 |
    125 |
    126 | {{node.metadata.name}} 127 |
    128 |
    129 | {{node.healthState.message}} 130 | 131 | 132 | {{node.healthState.text}} 133 | 134 |
    135 |
    136 |
    137 |
    138 |
  2. 139 |
140 |
141 | -------------------------------------------------------------------------------- /src/components/clusters/partials/cluster_workloads.html: -------------------------------------------------------------------------------- 1 | 5 |
6 | 7 |
8 |
9 |
10 | Namespace: 11 |
12 | 15 |
16 |
17 |
18 |
19 | 20 |
21 |

22 | Daemon Sets 23 |

24 |
    25 |
  1. 26 |
    27 |
    28 |
    29 |
    30 | {{ds.metadata.name}} 31 |
    32 |
    33 |
    34 |
    35 |
  2. 36 |
37 |
38 | 39 |
40 |

41 | Replication Controllers 42 |

43 |
    44 |
  1. 45 |
    46 |
    47 |
    48 |
    49 | {{rc.metadata.name}} 50 |
    51 |
    52 |
    53 |
    54 |
  2. 55 |
56 |
57 | 58 |
59 |

60 | Deployments 61 |

62 |
    63 |
  1. 64 |
    65 |
    66 |
    68 | 69 | Deployment Stats Dashboard 70 |
    71 |
    72 |
    73 |
    74 |
    75 | {{deploy.metadata.name}} 76 |
    77 |
    78 |
    79 |
    80 |
  2. 81 |
82 |
83 | 84 |
85 |

86 | Pods 87 |

88 |
    89 |
  1. 90 |
    91 |
    92 |
    94 | 95 | Pod/Container Stats Dashboard 96 |
    97 |
    98 |
    99 |
    100 |
    101 | {{pod.metadata.name}} 102 |
    103 |
    104 |
    105 |
    106 |
  2. 107 |
108 |
109 | -------------------------------------------------------------------------------- /src/components/clusters/partials/clusters.html: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | ...loading 11 |
12 | 13 |
14 |
15 |
16 | 17 |

Looks like you don’t have any clulsters yet.
18 | Add a new cluster 19 |

20 |

Your org does not have any clusters configured. Contact your org admin. 21 |

22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 39 | 48 | 49 | 50 |
Name
35 | 36 | {{cluster.name}} Overview 37 | 38 | 40 | 41 | Cluster Config 42 | 43 |    44 | 45 | 46 | 47 |
51 |
52 | -------------------------------------------------------------------------------- /src/components/clusters/partials/node_info.html: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |
15 |

Addresses

16 |
17 | 18 | {{addr.type}}: {{addr.address}} 19 | 20 |
21 |
22 |
23 |

Capacity

24 |
25 | 26 | {{k}}: {{v}} 27 | 28 |
29 |
30 |
31 |

Labels

32 |
33 | 34 | {{k}}: {{v}} 35 | 36 |
37 |
38 |
39 | 40 |
41 |

Conditions

42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 59 | 60 | 61 | 62 | 63 |
StatusTypeMessageLast Change
51 | 56 | 57 | {{ctrl.conditionStatus(condition).text}} 58 | {{condition.type}}{{condition.message}}{{ctrl.conditionLastTransitionTime(condition)}}
64 |
65 |
66 | 67 |

Info

68 | 69 | 70 | 71 | 72 | 73 |
{{k}}{{v}}
74 |
75 | -------------------------------------------------------------------------------- /src/components/clusters/partials/pod_info.html: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |
15 |

Node

16 |
17 | 18 | {{ctrl.pod.spec.nodeName}} 19 | 20 |
21 |
22 | 23 | IP: {{ctrl.pod.status.hostIP}} 24 | 25 |
26 |
27 |
28 |

Status

29 |
30 | 31 | Status: {{ctrl.pod.status.phase}} 32 | 33 |
34 |
35 | 36 | Start time: {{ctrl.formatTime(ctrl.pod.status.startTime)}} 37 | 38 |
39 |
40 |
41 |
42 | 43 |
44 |
45 |

Conditions

46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 62 | 63 | 64 | 65 |
StatusTypeLast Change
54 | 59 | 60 | {{ctrl.conditionStatus(condition).text}} 61 | {{condition.type}}{{ctrl.formatTime(condition.lastTransitionTime)}}
66 |
67 |
68 |

Labels

69 |
70 | 71 | {{k}}: {{v}} 72 | 73 |
74 |
75 |
76 |
77 | 78 |
79 |

Containers

80 |
    81 |
  1. 82 |
    83 |
    84 |
    85 |
    86 | {{container.name}} 87 |
    88 |
    89 | image: {{container.image}} 90 |
    91 |
    92 |
    93 |
    94 |
  2. 95 |
96 |
97 | -------------------------------------------------------------------------------- /src/components/clusters/podInfo.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | function slugify(str) { 4 | var slug = str.replace("@", "at").replace("&", "and").replace(/[.]/g, "_").replace("/\W+/", ""); 5 | return slug; 6 | } 7 | 8 | export class PodInfoCtrl { 9 | /** @ngInject */ 10 | constructor($scope, $injector, backendSrv, datasourceSrv, $q, $location, alertSrv) { 11 | this.$q = $q; 12 | this.backendSrv = backendSrv; 13 | this.datasourceSrv = datasourceSrv; 14 | this.$location = $location; 15 | document.title = 'Grafana Kubernetes App'; 16 | 17 | this.pageReady = false; 18 | this.pod = {}; 19 | if (!("cluster" in $location.search())) { 20 | alertSrv.set("no cluster specified.", "no cluster specified in url", 'error'); 21 | return; 22 | } else { 23 | this.cluster_id = $location.search().cluster; 24 | let pod_name = $location.search().pod; 25 | 26 | this.loadDatasource(this.cluster_id).then(() => { 27 | this.clusterDS.getPod(pod_name).then(pod => { 28 | this.pod = pod; 29 | this.pageReady = true; 30 | }); 31 | }); 32 | } 33 | } 34 | 35 | loadDatasource(id) { 36 | return this.backendSrv.get('api/datasources/' + id) 37 | .then(ds => { 38 | this.datasource = ds.jsonData.ds; 39 | return this.datasourceSrv.get(ds.name); 40 | }).then(clusterDS => { 41 | this.clusterDS = clusterDS; 42 | return clusterDS; 43 | }); 44 | } 45 | 46 | conditionStatus(condition) { 47 | var status; 48 | if (condition.type === "Ready") { 49 | status = condition.status === "True"; 50 | } else { 51 | status = condition.status === "False"; 52 | } 53 | 54 | return { 55 | value: status, 56 | text: status ? "Ok" : "Error" 57 | }; 58 | } 59 | 60 | goToPodDashboard(pod) { 61 | this.$location.path("dashboard/db/kubernetes-container") 62 | .search({ 63 | "var-datasource": this.datasource, 64 | "var-cluster": this.clusterDS.name, 65 | "var-node": slugify(pod.spec.nodeName), 66 | "var-namespace": pod.metadata.namespace, 67 | "var-pod": pod.metadata.name 68 | }); 69 | } 70 | 71 | isConditionOk(condition) { 72 | return this.conditionStatus(condition).value; 73 | } 74 | 75 | formatTime(time) { 76 | return moment(time).format('YYYY-MM-DD HH:mm:ss'); 77 | } 78 | } 79 | 80 | PodInfoCtrl.templateUrl = 'components/clusters/partials/pod_info.html'; 81 | -------------------------------------------------------------------------------- /src/components/config/config.html: -------------------------------------------------------------------------------- 1 |

Kubernetes App

2 | This app integrates the data collected from Snap running in Kubernetes with data available via the Kubernetes API. 3 |
4 | Dashboards imported. Next up: Connect to your Kubernetes Cluster and deploy metric collectors 5 |
6 | -------------------------------------------------------------------------------- /src/components/config/config.js: -------------------------------------------------------------------------------- 1 | export class ConfigCtrl { 2 | /** @ngInject */ 3 | constructor($scope, $injector, $q) { 4 | this.$q = $q; 5 | this.enabled = false; 6 | this.appEditCtrl.setPostUpdateHook(this.postUpdate.bind(this)); 7 | } 8 | 9 | postUpdate() { 10 | if (!this.appModel.enabled) { 11 | return this.$q.resolve(); 12 | } 13 | return this.appEditCtrl.importDashboards().then(() => { 14 | this.enabled = true; 15 | return { 16 | url: "plugins/raintank-kubernetes-app/page/clusters", 17 | message: "Kubernetes App enabled!" 18 | }; 19 | }); 20 | } 21 | } 22 | ConfigCtrl.templateUrl = 'components/config/config.html'; 23 | -------------------------------------------------------------------------------- /src/css/dark.css: -------------------------------------------------------------------------------- 1 | tr.dashlist-item:hover { 2 | background-color: #333; 3 | cursor: pointer; 4 | } 5 | 6 | /* pod nav panel */ 7 | .podnav-wrapper { 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | } 12 | 13 | .podnav-tags-wrapper { 14 | width: 60%; 15 | } 16 | 17 | .chosen-tags-container { 18 | display: block; 19 | } 20 | 21 | .chosen-tags-container .label-tag { 22 | line-height: 20px; 23 | margin-bottom: 10px; 24 | } 25 | 26 | .podnav-tags { 27 | display: flex; 28 | flex-direction: column; 29 | flex-wrap: wrap; 30 | align-content: flex-start; 31 | max-height: 300px; 32 | overflow: auto; 33 | } 34 | 35 | .podnav-pods { 36 | width: 40%; 37 | display: flex; 38 | flex-direction: column; 39 | } 40 | 41 | .podnav-results { 42 | display: flex; 43 | flex-direction: column; 44 | overflow: auto; 45 | max-height: 300px; 46 | } 47 | 48 | .podnav-result { 49 | display: block; 50 | padding: 3px 10px; 51 | background-color: #292929; 52 | margin-bottom: 4px; 53 | } 54 | 55 | .podnav-result:hover { 56 | background-color: #333; 57 | cursor: pointer; 58 | } 59 | 60 | .podnav-result button { 61 | margin-right: 15px; 62 | } 63 | 64 | /* plugin config */ 65 | 66 | .pluginconfig-message { 67 | margin: 20px; 68 | } 69 | 70 | .k8s-icon-success { 71 | color: #10a345; 72 | font-size: 24px; 73 | text-decoration: none; 74 | vertical-align: sub; 75 | } 76 | 77 | /* cluster info */ 78 | .main-dash-links { 79 | margin-bottom: 40px; 80 | } 81 | .card-item--main-dash-link { 82 | padding: 20px; 83 | } 84 | -------------------------------------------------------------------------------- /src/css/light.css: -------------------------------------------------------------------------------- 1 | tr.dashlist-item:hover { 2 | background-color: #ECECEC; 3 | cursor: pointer; 4 | } 5 | 6 | /* pod nav panel */ 7 | .podnav-wrapper { 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | } 12 | 13 | .podnav-tags-wrapper { 14 | width: 60%; 15 | } 16 | 17 | .chosen-tags-container { 18 | display: block; 19 | } 20 | 21 | .chosen-tags-container .label-tag { 22 | line-height: 20px; 23 | margin-bottom: 10px; 24 | } 25 | 26 | .podnav-tags { 27 | display: flex; 28 | flex-direction: column; 29 | flex-wrap: wrap; 30 | align-content: flex-start; 31 | max-height: 300px; 32 | overflow: auto; 33 | } 34 | 35 | .podnav-pods { 36 | width: 40%; 37 | display: flex; 38 | flex-direction: column; 39 | } 40 | 41 | .podnav-results { 42 | display: flex; 43 | flex-direction: column; 44 | overflow: auto; 45 | max-height: 300px; 46 | } 47 | 48 | .podnav-result { 49 | display: block; 50 | padding: 3px 10px; 51 | background-color: #f4f5f8; 52 | margin-bottom: 4px; 53 | } 54 | 55 | .podnav-result:hover { 56 | background-color: #ECECEC; 57 | cursor: pointer; 58 | } 59 | 60 | .podnav-result button { 61 | margin-right: 15px; 62 | } 63 | 64 | /* plugin config */ 65 | 66 | .pluginconfig-message { 67 | margin: 20px; 68 | } 69 | 70 | .k8s-icon-success { 71 | color: #10a345; 72 | font-size: 24px; 73 | text-decoration: none; 74 | vertical-align: sub; 75 | } 76 | 77 | /* cluster info */ 78 | .main-dash-links { 79 | margin-bottom: 40px; 80 | } 81 | .card-item--main-dash-link { 82 | padding: 20px; 83 | } 84 | -------------------------------------------------------------------------------- /src/datasource/config.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/datasource/datasource.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export class K8sDatasource { 4 | constructor(instanceSettings, backendSrv, $q) { 5 | this.type = instanceSettings.type; 6 | this.url = instanceSettings.url; 7 | this.name = instanceSettings.name; 8 | this.backendSrv = backendSrv; 9 | this.$q = $q; 10 | 11 | this.baseApiUrl = '/api/v1/'; 12 | } 13 | 14 | testDatasource() { 15 | return this.backendSrv.datasourceRequest({ 16 | url: this.url + '/', 17 | method: 'GET' 18 | }).then(response => { 19 | if (response.status === 200) { 20 | return { status: "success", message: "Data source is working", title: "Success" }; 21 | } 22 | }); 23 | } 24 | 25 | _get(apiResource) { 26 | return this.backendSrv.datasourceRequest({ 27 | url: this.url + apiResource, 28 | method: "GET", 29 | headers: { 'Content-Type': 'application/json' } 30 | }).then( 31 | response => { 32 | return response.data; 33 | }, error => { 34 | return error; 35 | }); 36 | } 37 | 38 | getNodes() { 39 | return this._get('/api/v1/nodes') 40 | .then(result => { 41 | return result.items; 42 | }); 43 | } 44 | 45 | getNode(name) { 46 | return this._get('/api/v1/nodes/' + name); 47 | } 48 | 49 | getNamespaces() { 50 | return this._get('/api/v1/namespaces') 51 | .then(result => { 52 | return result.items; 53 | }); 54 | } 55 | 56 | getComponentStatuses() { 57 | return this._get('/api/v1/componentstatuses') 58 | .then(result => { 59 | return result.items; 60 | }); 61 | } 62 | 63 | getDaemonSets(namespace) { 64 | return this._get('/apis/extensions/v1beta1/' + addNamespace(namespace) + 'daemonsets') 65 | .then(result => { 66 | return result.items; 67 | }); 68 | } 69 | 70 | getReplicationControllers(namespace) { 71 | return this._get('/api/v1/' + addNamespace(namespace) + 'replicationcontrollers') 72 | .then(result => { 73 | return result.items; 74 | }); 75 | } 76 | 77 | getDeployments(namespace) { 78 | return this._get('/apis/extensions/v1beta1/' + addNamespace(namespace) + 'deployments') 79 | .then(result => { 80 | return result.items; 81 | }); 82 | } 83 | 84 | getPods(namespace) { 85 | return this._get('/api/v1/' + addNamespace(namespace) + 'pods') 86 | .then(result => { 87 | return result.items; 88 | }); 89 | } 90 | 91 | getPodsByLabel(namespace, labels) { 92 | return this._get('/api/v1/' + addNamespace(namespace) + 'pods?labelSelector=' + addLabels(labels)) 93 | .then(result => { 94 | return result.items; 95 | }); 96 | } 97 | 98 | getPod(name) { 99 | return this._get('/api/v1/pods/?fieldSelector=metadata.name%3D' + name) 100 | .then(result => { 101 | if (result.items && result.items.length === 1) { 102 | return result.items[0]; 103 | } else { 104 | return result.items; 105 | } 106 | }); 107 | } 108 | 109 | getPodsByName(names) { 110 | const promises = []; 111 | if (Array.isArray(names)) { 112 | _.forEach(names, name => { 113 | promises.push(this.getPod(name)); 114 | }); 115 | return this.$q.all(promises); 116 | } else { 117 | return this.getPod(names) 118 | .then(pod => { 119 | return [pod]; 120 | }); 121 | } 122 | } 123 | } 124 | 125 | function addNamespace(namespace) { 126 | return namespace ? 'namespaces/' + namespace + '/' : ''; 127 | } 128 | 129 | function addLabels(labels) { 130 | let querystring = ''; 131 | _.forEach(labels, (value, label) => { 132 | querystring += label + '%3D' + value + '%2C'; 133 | }); 134 | return _.trimEnd(querystring, '%2C'); 135 | } 136 | -------------------------------------------------------------------------------- /src/datasource/module.js: -------------------------------------------------------------------------------- 1 | import {K8sDatasource} from './datasource'; 2 | 3 | class K8sConfigCtrl {} 4 | K8sConfigCtrl.templateUrl = 'datasource/config.html'; 5 | 6 | export { 7 | K8sDatasource as Datasource, 8 | K8sConfigCtrl as ConfigCtrl 9 | }; -------------------------------------------------------------------------------- /src/datasource/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kubernetes", 3 | "id": "raintank-kubernetes-datasource", 4 | "type": "datasource", 5 | 6 | "staticRoot": ".", 7 | 8 | "partials": { 9 | "config": "public/app/plugins/raintank-kubernetes-app/datasource/config.html" 10 | }, 11 | 12 | "metrics": false, 13 | "annotations": false, 14 | 15 | "info": { 16 | "description": "Kubernetes datasource", 17 | "author": { 18 | "name": "Raintank Inc.", 19 | "url": "http://raintank.io" 20 | }, 21 | "logos": { 22 | "small": "img/logo.svg", 23 | "large": "img/logo.svg" 24 | }, 25 | "links": [ 26 | {"name": "GitHub", "url": "https://github.com/raintank/kubernetes-app"}, 27 | {"name": "License", "url": "https://github.com/raintank/kubernetes-app/blob/master/LICENSE"} 28 | ], 29 | "version": "0.0.1", 30 | "updated": "2016-09-27" 31 | }, 32 | 33 | "dependencies": { 34 | "grafanaVersion": "3.x.x", 35 | "plugins": [ ] 36 | } 37 | } -------------------------------------------------------------------------------- /src/img/app-menu-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/src/img/app-menu-screenshot.png -------------------------------------------------------------------------------- /src/img/cluster-dashboard-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/src/img/cluster-dashboard-screenshot.png -------------------------------------------------------------------------------- /src/img/container-dashboard-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/src/img/container-dashboard-screenshot.png -------------------------------------------------------------------------------- /src/img/namespace-details-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/src/img/namespace-details-screenshot.png -------------------------------------------------------------------------------- /src/img/node-dashboard-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/src/img/node-dashboard-screenshot.png -------------------------------------------------------------------------------- /src/img/overview-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/src/img/overview-screenshot.png -------------------------------------------------------------------------------- /src/img/pod-details-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raintank/legacy-kubernetes-app/d98a681e674b73b77779ede285a80b199108545c/src/img/pod-details-screenshot.png -------------------------------------------------------------------------------- /src/module.js: -------------------------------------------------------------------------------- 1 | import {ConfigCtrl} from './components/config/config'; 2 | import {ClustersCtrl} from './components/clusters/clusters'; 3 | import {ClusterConfigCtrl} from './components/clusters/clusterConfig'; 4 | import {ClusterInfoCtrl} from './components/clusters/clusterInfo'; 5 | import {ClusterWorkloadsCtrl} from './components/clusters/clusterWorkloads'; 6 | import {NodeInfoCtrl} from './components/clusters/nodeInfo'; 7 | import {PodInfoCtrl} from './components/clusters/podInfo'; 8 | import {loadPluginCss} from 'app/plugins/sdk'; 9 | 10 | loadPluginCss({ 11 | dark: 'plugins/raintank-kubernetes-app/css/dark.css', 12 | light: 'plugins/raintank-kubernetes-app/css/light.css' 13 | }); 14 | 15 | export { 16 | ConfigCtrl, 17 | ClustersCtrl, 18 | ClusterConfigCtrl, 19 | ClusterInfoCtrl, 20 | ClusterWorkloadsCtrl, 21 | NodeInfoCtrl, 22 | PodInfoCtrl 23 | }; 24 | -------------------------------------------------------------------------------- /src/panels/nodeData/module.js: -------------------------------------------------------------------------------- 1 | 2 | import {NodeDataCtrl} from './nodeData'; 3 | import {loadPluginCss} from 'app/plugins/sdk'; 4 | 5 | loadPluginCss({ 6 | dark: 'plugins/raintank-kubernetes-app/css/dark.css', 7 | light: 'plugins/raintank-kubernetes-app/css/light.css' 8 | }); 9 | 10 | export { 11 | NodeDataCtrl as PanelCtrl 12 | }; 13 | -------------------------------------------------------------------------------- /src/panels/nodeData/nodeData.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import {PanelCtrl} from 'app/plugins/sdk'; 3 | import _ from 'lodash'; 4 | import NodeStatsDatasource from './nodeStats'; 5 | 6 | const panelDefaults = { 7 | }; 8 | 9 | export class NodeDataCtrl extends PanelCtrl { 10 | /** @ngInject */ 11 | constructor($scope, $injector, backendSrv, datasourceSrv, $location, alertSrv, timeSrv, variableSrv) { 12 | super($scope, $injector); 13 | _.defaults(this.panel, panelDefaults); 14 | 15 | this.backendSrv = backendSrv; 16 | this.datasourceSrv = datasourceSrv; 17 | this.$location = $location; 18 | this.alertSrv = alertSrv; 19 | this.timeSrv = timeSrv; 20 | this.variableSrv = variableSrv; 21 | this.templateVariables = this.variableSrv.variables; 22 | this.nodeStatsDatasource = new NodeStatsDatasource(datasourceSrv, timeSrv); 23 | document.title = 'Grafana Kubernetes App'; 24 | 25 | this.pageReady = false; 26 | this.cluster = {}; 27 | this.clusterDS = {}; 28 | this.node = {}; 29 | 30 | this.isInListMode = false; 31 | this.nodes = []; 32 | 33 | this.loadCluster(); 34 | } 35 | 36 | loadCluster() { 37 | const cluster = _.find(this.templateVariables, {'name': 'cluster'}); 38 | if (!cluster) { 39 | this.alertSrv.set("no cluster specified.", "no cluster specified in url", 'error'); 40 | return; 41 | } else { 42 | const cluster_id = cluster.current.value; 43 | const nodeVar = _.find(this.templateVariables, {'name': 'node'}); 44 | const node_name = nodeVar.current.value !== '$__all' ? nodeVar.current.value : 'All'; 45 | const graphiteDs = _.find(this.templateVariables, {'name': 'datasource'}).current.value; 46 | 47 | this.loadDatasource(cluster_id).then(() => { 48 | return this.nodeStatsDatasource.getNodeStats(cluster_id, graphiteDs); 49 | }).then(nodeStats => { 50 | if (node_name === 'All') { 51 | this.isInListMode = true; 52 | this.clusterDS.getNodes().then(nodes => { 53 | this.nodes = _.map(nodes, node => { 54 | node.healthState = this.getNodeHealth(node); 55 | this.nodeStatsDatasource.updateNodeWithStats(node, nodeStats); 56 | 57 | return node; 58 | }); 59 | }); 60 | } else { 61 | this.isInListMode = false; 62 | this.clusterDS.getNode(unslugify(node_name)).then(node => { 63 | this.node = node; 64 | this.pageReady = true; 65 | }); 66 | } 67 | }); 68 | } 69 | } 70 | 71 | getNodeHealth(node) { 72 | let health = "unhealthy"; 73 | let message = ''; 74 | _.forEach(node.status.conditions, condition => { 75 | if (condition.type === "Ready" && 76 | condition.status === "True") { 77 | health = "ok"; 78 | } else { 79 | message = condition.message; 80 | } 81 | }); 82 | return this.getHealthState(health, message); 83 | } 84 | 85 | getHealthState(health, message) { 86 | switch (health) { 87 | case 'ok': { 88 | return { 89 | text: 'OK', 90 | iconClass: 'icon-gf icon-gf-online', 91 | stateClass: 'alert-state-ok' 92 | }; 93 | } 94 | case 'unhealthy': { 95 | return { 96 | text: 'UNHEALTHY', 97 | iconClass: 'icon-gf icon-gf-critical', 98 | stateClass: 'alert-state-critical', 99 | message: message || '' 100 | }; 101 | } 102 | case 'warning': { 103 | return { 104 | text: 'warning', 105 | iconClass: "icon-gf icon-gf-critical", 106 | stateClass: 'alert-state-warning', 107 | message: message || '' 108 | }; 109 | } 110 | } 111 | } 112 | 113 | refresh() { 114 | this.loadCluster(); 115 | } 116 | 117 | loadDatasource(id) { 118 | return this.backendSrv.get('api/datasources') 119 | .then(result => { 120 | return _.filter(result, {"type": "raintank-kubernetes-datasource", "name": id})[0]; 121 | }) 122 | .then(ds => { 123 | if (!ds) { 124 | this.alertSrv.set("Failed to connect", "Could not connect to the specified cluster.", 'error'); 125 | throw "Failed to connect to " + id; 126 | } 127 | this.cluster = ds; 128 | return this.datasourceSrv.get(ds.name); 129 | }).then(clusterDS => { 130 | this.clusterDS = clusterDS; 131 | return clusterDS; 132 | }); 133 | } 134 | 135 | goToNodeDashboard(node) { 136 | const variable = _.find(this.templateVariables, {'name': 'node'}); 137 | variable.current.text = node === 'All' ? 'All': slugify(node.metadata.name); 138 | variable.current.value = node === 'All' ? '$__all': slugify(node.metadata.name); 139 | 140 | this.variableSrv.variableUpdated(variable).then(() => { 141 | this.$scope.$emit('template-variable-value-updated'); 142 | this.$scope.$root.$broadcast('refresh'); 143 | }); 144 | } 145 | 146 | conditionStatus(condition) { 147 | var status; 148 | if (condition.type === "Ready") { 149 | status = condition.status === "True"; 150 | } else { 151 | status = condition.status === "False"; 152 | } 153 | 154 | return { 155 | value: status, 156 | text: status ? "Ok" : "Error" 157 | }; 158 | } 159 | 160 | isConditionOk(condition) { 161 | return this.conditionStatus(condition).value; 162 | } 163 | 164 | conditionLastTransitionTime(condition) { 165 | return moment(condition.lastTransitionTime).format('YYYY-MM-DD HH:mm:ss'); 166 | } 167 | } 168 | 169 | function slugify(str) { 170 | var slug = str.replace("@", "at").replace("&", "and").replace(/[.]/g, "_").replace("/\W+/", ""); 171 | return slug; 172 | } 173 | 174 | function unslugify(str) { 175 | var slug = str.replace(/[_]/g, "."); 176 | return slug; 177 | } 178 | 179 | NodeDataCtrl.templateUrl = 'panels/nodeData/partials/node_info.html'; 180 | -------------------------------------------------------------------------------- /src/panels/nodeData/nodeStats.js: -------------------------------------------------------------------------------- 1 | import kbn from 'app/core/utils/kbn'; 2 | import _ from 'lodash'; 3 | 4 | export default class NodeStatsDatasource { 5 | constructor(datasourceSrv, timeSrv) { 6 | this.datasourceSrv = datasourceSrv; 7 | this.timeSrv = timeSrv; 8 | } 9 | 10 | issueGraphiteQuery(graphiteDs, query) { 11 | return this.datasourceSrv.get(graphiteDs) 12 | .then((datasource) => { 13 | var metricsQuery = { 14 | range: this.timeSrv.timeRange(), 15 | rangeRaw: this.timeSrv.timeRange().raw, 16 | interval: this.interval, 17 | intervalMs: this.intervalMs, 18 | targets: [ 19 | { 20 | refId: 'A', 21 | target: query 22 | } 23 | ], 24 | format: 'json', 25 | maxDataPoints: 1000, 26 | }; 27 | 28 | return datasource.query(metricsQuery); 29 | }).then((result) => { 30 | if (result && result.data) { 31 | return result.data; 32 | } 33 | 34 | return {}; 35 | }); 36 | } 37 | 38 | getNodeStats(cluster_id, graphiteDs) { 39 | let podsPerNode, cpuPerNode, memoryPerNode; 40 | 41 | const podQuery = 'aliasByNode(keepLastValue(groupByNode(snap.' + cluster_id 42 | + ".grafanalabs.kubestate.pod.*.*.*.status.phase.Running, 6, 'sum'), 100), 0)"; 43 | const cpuQuery = 'aliasByNode(keepLastValue(groupByNode(snap.' + cluster_id 44 | + ".grafanalabs.kubestate.container.*.*.*.*.requested.cpu.cores, 6, 'sum'), 100), 0)"; 45 | const memoryQuery = 'aliasByNode(keepLastValue(groupByNode(snap.' + cluster_id 46 | + ".grafanalabs.kubestate.container.*.*.*.*.requested.memory.bytes, 6, 'sum'), 100), 0)"; 47 | 48 | return this.issueGraphiteQuery(graphiteDs, podQuery) 49 | .then(data => { 50 | podsPerNode = data; 51 | return; 52 | }).then(() => { 53 | return this.issueGraphiteQuery(graphiteDs, cpuQuery); 54 | }) 55 | .then(data => { 56 | cpuPerNode = data; 57 | return; 58 | }).then(() => { 59 | return this.issueGraphiteQuery(graphiteDs, memoryQuery); 60 | }) 61 | .then(data => { 62 | memoryPerNode = data; 63 | return {podsPerNode, cpuPerNode, memoryPerNode}; 64 | }); 65 | } 66 | 67 | updateNodeWithStats(node, nodeStats) { 68 | var formatFunc = kbn.valueFormats['percentunit']; 69 | const nodeName = slugify(node.metadata.name); 70 | const podsUsedData = _.find(nodeStats.podsPerNode, {'target': nodeName}); 71 | if (podsUsedData) { 72 | node.podsUsed = _.last(podsUsedData.datapoints)[0]; 73 | node.podsUsedPerc = formatFunc(node.podsUsed / node.status.capacity.pods, 2, 5); 74 | } 75 | 76 | const cpuData = _.find(nodeStats.cpuPerNode, {'target': nodeName}); 77 | if (cpuData) { 78 | node.cpuUsage = _.last(cpuData.datapoints)[0]; 79 | node.cpuUsageFormatted = kbn.valueFormats['none'](node.cpuUsage, 2, null); 80 | node.cpuUsagePerc = formatFunc(node.cpuUsage / node.status.capacity.cpu, 2, 5); 81 | } 82 | 83 | const memData = _.find(nodeStats.memoryPerNode, {'target': nodeName}); 84 | if (memData) { 85 | node.memoryUsage = _.last(memData.datapoints)[0]; 86 | const memCapacity = node.status.capacity.memory.substring(0, node.status.capacity.memory.length - 2) * 1000; 87 | node.memUsageFormatted = kbn.valueFormats['bytes'](node.memoryUsage, 2, null); 88 | node.memCapacityFormatted = kbn.valueFormats['bytes'](memCapacity, 2, null); 89 | node.memoryUsagePerc = formatFunc((node.memoryUsage / memCapacity), 2, 5); 90 | } 91 | 92 | return node; 93 | } 94 | } 95 | 96 | function slugify(str) { 97 | var slug = str.replace("@", "at").replace("&", "and").replace(/[.]/g, "_").replace("/\W+/", ""); 98 | return slug; 99 | } 100 | -------------------------------------------------------------------------------- /src/panels/nodeData/partials/node_info.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{group.header}} 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
Node NameHealthPods%CPU requests (cores)%Memory requests (bytes)%
{{node.metadata.name}}{{node.healthState.message}} 23 | 24 | {{node.healthState.text}} 25 | {{(node.podsUsed || '?') + ' / ' + node.status.capacity.pods}}{{node.podsUsedPerc || '?%'}}{{(node.cpuUsageFormatted || '?') + ' / ' + node.status.capacity.cpu}}{{node.cpuUsagePerc || '?%'}}{{(node.memUsageFormatted || '?') + ' / ' + node.memCapacityFormatted}}{{node.memoryUsagePerc || '?%'}}
35 |
36 |
37 | 38 |
39 | 40 | 44 | 45 |
46 |
47 |

Addresses

48 |
49 | 50 | {{addr.type}}: {{addr.address}} 51 | 52 |
53 |
54 |
55 |

Capacity

56 |
57 | 58 | {{k}}: {{v}} 59 | 60 |
61 |
62 |
63 |

Labels

64 |
65 | 66 | {{k}}: {{v}} 67 | 68 |
69 |
70 |
71 | 72 |
73 |

Conditions

74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 91 | 92 | 93 | 94 | 95 |
StatusTypeMessageLast Change
83 | 88 | 89 | {{ctrl.conditionStatus(condition).text}} 90 | {{condition.type}}{{condition.message}}{{ctrl.conditionLastTransitionTime(condition)}}
96 |
97 |
98 | -------------------------------------------------------------------------------- /src/panels/nodeData/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "panel", 3 | "name": "Kubernetes Node Info", 4 | "id": "raintank-kubernetes-nodeinfo-panel" 5 | } 6 | -------------------------------------------------------------------------------- /src/panels/podNav/module.js: -------------------------------------------------------------------------------- 1 | 2 | import {PodNavCtrl} from './podNav'; 3 | import {loadPluginCss} from 'app/plugins/sdk'; 4 | 5 | loadPluginCss({ 6 | dark: 'plugins/raintank-kubernetes-app/css/dark.css', 7 | light: 'plugins/raintank-kubernetes-app/css/light.css' 8 | }); 9 | 10 | export { 11 | PodNavCtrl as PanelCtrl 12 | }; 13 | -------------------------------------------------------------------------------- /src/panels/podNav/partials/pod_nav.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Filter Pods By Kubernetes Tag

4 |
5 | 6 | 7 | {{tag}}={{value}} 8 | 9 | 10 |
11 |
12 |
13 |
14 | 17 |
18 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |

Filter by Pod Name

28 |
29 | 30 | 31 | {{podTag}} 32 | 33 | 34 |
35 |
36 | 37 | 38 | {{pod}} 39 | 40 |
41 |
42 |
43 | -------------------------------------------------------------------------------- /src/panels/podNav/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "panel", 3 | "name": "Kubernetes Pod Nav", 4 | "id": "raintank-kubernetes-podnav-panel" 5 | } 6 | -------------------------------------------------------------------------------- /src/panels/podNav/podNav.js: -------------------------------------------------------------------------------- 1 | import {PanelCtrl} from 'app/plugins/sdk'; 2 | import _ from 'lodash'; 3 | 4 | const panelDefaults = { 5 | }; 6 | 7 | export class PodNavCtrl extends PanelCtrl { 8 | constructor($scope, $injector, backendSrv, datasourceSrv, $location, alertSrv, variableSrv, $q) { 9 | super($scope, $injector); 10 | _.defaults(this.panel, panelDefaults); 11 | 12 | this.backendSrv = backendSrv; 13 | this.datasourceSrv = datasourceSrv; 14 | this.$location = $location; 15 | this.alertSrv = alertSrv; 16 | this.variableSrv = variableSrv; 17 | this.$q = $q; 18 | 19 | this.templateVariables = this.variableSrv.variables; 20 | this.namespace = "All"; 21 | this.currentTags = {}; 22 | this.currentPods = []; 23 | this.selectedPods = []; 24 | 25 | this.setDefaults(); 26 | this.loadTags(); 27 | this.chosenTags = {}; 28 | } 29 | 30 | refresh() { 31 | if (this.needsRefresh()) { 32 | this.currentTags = {}; 33 | this.currentPods = []; 34 | this.chosenTags = {}; 35 | this.selectedPods = []; 36 | 37 | this.setDefaults(); 38 | this.loadTags(); 39 | } 40 | } 41 | 42 | needsRefresh() { 43 | const cluster = _.find(this.templateVariables, {'name': 'cluster'}); 44 | const ns = _.find(this.templateVariables, {'name': 'namespace'}); 45 | 46 | if (this.clusterName !== cluster.current.value) { return true; } 47 | 48 | if ((ns.current.value === '$__all' || ns.current.value[0] === '$__all') 49 | && (this.namespace === ns.current.value || this.namespace === '')) { 50 | return false; 51 | } 52 | 53 | if (ns.current.value !== this.namespace) { return true; } 54 | 55 | return false; 56 | } 57 | 58 | loadTags() { 59 | this.getCluster().then(() => { 60 | return this.getPods().then(pods => { 61 | this.parseTagsFromPods(pods); 62 | this.currentPods = _.uniq(_.map(pods, p => { return p.metadata.name; })); 63 | }); 64 | }); 65 | } 66 | 67 | setDefaults() { 68 | const cluster = _.find(this.templateVariables, {'name': 'cluster'}); 69 | if (!cluster) { 70 | this.alertSrv.set("no cluster specified.", "no cluster specified in url", 'error'); 71 | return; 72 | } 73 | 74 | const ns = _.find(this.templateVariables, {'name': 'namespace'}); 75 | this.namespace = ns.current.value !== '$__all' && ns.current.value[0] !== '$__all' ? ns.current.value : ''; 76 | const podVariable = _.find(this.templateVariables, {'name': 'pod'}); 77 | 78 | if (podVariable.current.value !== '$__all') { 79 | this.selectedPods = _.isArray(podVariable.current.value) ? podVariable.current.value : [podVariable.current.value]; 80 | } 81 | } 82 | 83 | getPods() { 84 | if (this.currentPods.length === 0) { 85 | if (_.isArray(this.namespace)) { 86 | const promises = []; 87 | _.forEach(this.namespace, ns => { 88 | promises.push(this.clusterDS.getPods(ns)); 89 | }); 90 | return this.$q.all(promises) 91 | .then(pods => { 92 | return _.flatten(pods).filter(n => n); 93 | }); 94 | } else { 95 | return this.clusterDS.getPods(this.namespace); 96 | } 97 | } else { 98 | return this.clusterDS.getPodsByName(this.currentPods); 99 | } 100 | } 101 | 102 | parseTagsFromPods(pods) { 103 | this.currentTags = {}; 104 | 105 | _.forEach(pods, pod => { 106 | _.forEach(pod.metadata.labels, (value, label) => { 107 | if (!this.currentTags[label]) { 108 | this.currentTags[label] = []; 109 | } 110 | if (!this.currentTags[label].includes(value)) { 111 | this.currentTags[label].push(value); 112 | } 113 | }); 114 | }); 115 | } 116 | 117 | onTagSelect() { 118 | this.removeEmptyTags(); 119 | this.selectedPods = []; 120 | 121 | this.getPodsByLabel() 122 | .then(pods => { 123 | this.currentPods = _.uniq(_.map(pods, p => { return p.metadata.name; })); 124 | this.parseTagsFromPods(pods); 125 | this.updateTemplateVariableWithPods(); 126 | }); 127 | } 128 | 129 | getPodsByLabel() { 130 | if (_.isArray(this.namespace)) { 131 | const promises = []; 132 | _.forEach(this.namespace, ns => { 133 | promises.push(this.clusterDS.getPodsByLabel(ns, this.chosenTags)); 134 | }); 135 | return this.$q.all(promises) 136 | .then(pods => { 137 | return _.flatten(pods).filter(n => n); 138 | }); 139 | } else { 140 | return this.clusterDS.getPodsByLabel(this.namespace, this.chosenTags); 141 | } 142 | } 143 | 144 | updateTemplateVariableWithPods() { 145 | const variable = _.find(this.templateVariables, {'name': 'pod'}); 146 | 147 | if (this.selectedPods.length > 0) { 148 | variable.current.text = this.selectedPods.join(' + '); 149 | variable.current.value = this.selectedPods; 150 | } else { 151 | variable.current.text = _.isEmpty(this.chosenTags) ? 'All': this.currentPods.join(' + '); 152 | variable.current.value = _.isEmpty(this.chosenTags) ? '$__all': this.currentPods; 153 | } 154 | 155 | this.variableSrv.updateOptions(variable).then(() => { 156 | this.variableSrv.variableUpdated(variable).then(() => { 157 | this.$scope.$emit('template-variable-value-updated'); 158 | this.$scope.$root.$broadcast('refresh'); 159 | }); 160 | }); 161 | } 162 | 163 | removeEmptyTags() { 164 | this.chosenTags = _.omitBy(this.chosenTags, val => { return !val;}); 165 | } 166 | 167 | getCluster() { 168 | const clusterName = _.find(this.templateVariables, {'name': 'cluster'}).current.value; 169 | this.clusterName = clusterName; 170 | 171 | return this.backendSrv.get('/api/datasources') 172 | .then(result => { 173 | return _.filter(result, {"name": clusterName})[0]; 174 | }) 175 | .then((ds) => { 176 | if (!ds) { 177 | this.alertSrv.set("Failed to connect", "Could not connect to the specified cluster.", 'error'); 178 | throw "Failed to connect to " + clusterName; 179 | } 180 | 181 | if (!(ds.jsonData.ds)) { 182 | ds.jsonData.ds = ""; 183 | } 184 | return this.datasourceSrv.get(ds.name); 185 | }).then(clusterDS => { 186 | this.clusterDS = clusterDS; 187 | }); 188 | } 189 | 190 | removeTag(tag) { 191 | delete this.chosenTags[tag]; 192 | this.getPodsByLabel() 193 | .then(pods => { 194 | this.currentPods = _.uniq(_.map(pods, p => { return p.metadata.name; })); 195 | this.parseTagsFromPods(pods); 196 | this.updateTemplateVariableWithPods(); 197 | }); 198 | } 199 | 200 | selectPod(podName) { 201 | this.chosenTags = {}; 202 | 203 | if (!this.selectedPods.includes(podName)) { 204 | this.selectedPods.push(podName); 205 | } 206 | 207 | this.updateTemplateVariableWithPods(); 208 | } 209 | 210 | removePodTag(podName) { 211 | _.remove(this.selectedPods, p => { return p === podName;}); 212 | this.updateTemplateVariableWithPods(); 213 | 214 | if (this.selectedPods.length === 0) { 215 | this.currentPods = []; 216 | this.loadTags(); 217 | } 218 | } 219 | } 220 | 221 | PodNavCtrl.templateUrl = 'panels/podNav/partials/pod_nav.html'; 222 | -------------------------------------------------------------------------------- /src/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "app", 3 | "name": "kubernetes", 4 | "id": "raintank-kubernetes-app", 5 | 6 | "routes": [ 7 | ], 8 | 9 | "info": { 10 | "description": "Kubernetes app. shows data collected by snap.", 11 | "author": { 12 | "name": "Raintank Inc.", 13 | "url": "https://grafana.com/" 14 | }, 15 | "keywords": ["raintank", "kubernetes", "snap"], 16 | "logos": { 17 | "small": "img/logo.svg", 18 | "large": "img/logo.svg" 19 | }, 20 | "links": [ 21 | {"name": "New App", "url": "https://github.com/grafana/kubernetes-app"}, 22 | {"name": "GitHub", "url": "https://github.com/raintank/legacy-kubernetes-app"}, 23 | {"name": "License", "url": "https://github.com/raintank/legacy-kubernetes-app/blob/master/LICENSE"} 24 | ], 25 | "screenshots": [ 26 | {"name": "Cluster Dashboard", "path": "img/cluster-dashboard-screenshot.png"}, 27 | {"name": "Container Dashboard", "path": "img/container-dashboard-screenshot.png"}, 28 | {"name": "Node Dashboard", "path": "img/node-dashboard-screenshot.png"}, 29 | {"name": "Overview Page", "path": "img/overview-screenshot.png"}, 30 | {"name": "Pod Details Page", "path": "img/pod-details-screenshot.png"}, 31 | {"name": "Namespace Details Page", "path": "img/namespace-details-screenshot.png"} 32 | ], 33 | "version": "0.0.9", 34 | "updated": "2018-02-27" 35 | }, 36 | 37 | "includes": [ 38 | { "type": "page", "name": "Clusters", "component": "ClustersCtrl", "role": "Viewer", "addToNav": true, "defaultNav": true}, 39 | { "type": "page", "name": "Cluster Config", "component": "ClusterConfigCtrl", "role": "Editor", "addToNav": false}, 40 | { "type": "page", "name": "Cluster Info", "component": "ClusterInfoCtrl", "role": "Viewer", "addToNav": false}, 41 | { "type": "page", "name": "Cluster Workloads", "component": "ClusterWorkloadsCtrl", "role": "Viewer", "addToNav": false}, 42 | { "type": "page", "name": "Node Info", "component": "NodeInfoCtrl", "role": "Viewer", "addToNav": false}, 43 | { "type": "page", "name": "Pod Info", "component": "PodInfoCtrl", "role": "Viewer", "addToNav": false}, 44 | { 45 | "type": "datasource", 46 | "name": "kubernetes DS" 47 | }, 48 | { 49 | "type": "dashboard", 50 | "name": "Kubernetes Node", 51 | "path": "dashboards/kubernetes-node.json", 52 | "addToNav": false 53 | }, 54 | { 55 | "type": "dashboard", 56 | "name": "Kubernetes Container", 57 | "path": "dashboards/kubernetes-container.json", 58 | "addToNav": false 59 | }, 60 | { 61 | "type": "dashboard", 62 | "name": "Kubernetes Cluster", 63 | "path": "dashboards/kubernetes-cluster.json", 64 | "addToNav": false 65 | }, 66 | { 67 | "type": "dashboard", 68 | "name": "Kubernetes Deployments", 69 | "path": "dashboards/kubernetes-deployments.json", 70 | "addToNav": false 71 | }, 72 | { 73 | "type": "panel", 74 | "name": "Kubernetes Node Info" 75 | }, 76 | { 77 | "type": "panel", 78 | "name": "Kubernetes Pod Nav" 79 | } 80 | ], 81 | 82 | "dependencies": { 83 | "grafanaVersion": "3.0+", 84 | "plugins": [] 85 | } 86 | } 87 | --------------------------------------------------------------------------------