From b6b2d6df429ba85c3d334a7270172fd9dacbf4b3 Mon Sep 17 00:00:00 2001 From: "Tim St. Clair" Date: Thu, 11 Jun 2015 17:45:17 -0700 Subject: [PATCH 1/2] Add kube-ui cluster addon for serving k8s dashboard UI. Changes include: - Add kube-ui binary for serving static dashboard UI - Add kube-ui docker image, replication controller, and service - Make the kube-ui a cluster-addon (enabled by default) - Split the compiled pkg/ui/datafile.go into separate dashboard and swagger packages - Update docs to reflect changes --- cluster/addons/kube-ui/image/Dockerfile | 19 + cluster/addons/kube-ui/image/Makefile | 23 + cluster/addons/kube-ui/image/kube-ui.go | 54 + cluster/addons/kube-ui/kube-ui-rc.yaml | 26 + cluster/addons/kube-ui/kube-ui-svc.yaml | 15 + cluster/saltbase/salt/kube-addons/init.sls | 11 + docs/ui.md | 2 +- hack/build-ui.sh | 41 +- pkg/ui/data/README.md | 9 + pkg/ui/data/dashboard/datafile.go | 5004 +++++ pkg/ui/data/swagger/datafile.go | 17091 +++++++++++++++ pkg/ui/datafile.go | 21793 ------------------- pkg/ui/doc.go | 3 +- pkg/ui/installsupport.go | 15 +- www/README.md | 10 +- 15 files changed, 22305 insertions(+), 21811 deletions(-) create mode 100644 cluster/addons/kube-ui/image/Dockerfile create mode 100644 cluster/addons/kube-ui/image/Makefile create mode 100644 cluster/addons/kube-ui/image/kube-ui.go create mode 100644 cluster/addons/kube-ui/kube-ui-rc.yaml create mode 100644 cluster/addons/kube-ui/kube-ui-svc.yaml create mode 100644 pkg/ui/data/README.md create mode 100644 pkg/ui/data/dashboard/datafile.go create mode 100644 pkg/ui/data/swagger/datafile.go delete mode 100644 pkg/ui/datafile.go diff --git a/cluster/addons/kube-ui/image/Dockerfile b/cluster/addons/kube-ui/image/Dockerfile new file mode 100644 index 00000000000..7faad8dca62 --- /dev/null +++ b/cluster/addons/kube-ui/image/Dockerfile @@ -0,0 +1,19 @@ +# Copyright 2015 The Kubernetes Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM scratch +MAINTAINER Tim St. Clair +ADD kube-ui kube-ui +EXPOSE 8080 +ENTRYPOINT ["/kube-ui"] diff --git a/cluster/addons/kube-ui/image/Makefile b/cluster/addons/kube-ui/image/Makefile new file mode 100644 index 00000000000..831957db19b --- /dev/null +++ b/cluster/addons/kube-ui/image/Makefile @@ -0,0 +1,23 @@ +# Makefile for the Docker image gcr.io/google_containers/kube-ui +# MAINTAINER: Tim St. Clair +# If you update this image please check the tag value before pushing. + +.PHONY: all container push clean + +# Keep this at dev, so no one accidentally blows away the latest published version. +TAG = dev # current version: v1 +PREFIX = gcr.io/google_containers + +all: push + +kube-ui: kube-ui.go + CGO_ENABLED=0 GOOS=linux godep go build -a -installsuffix cgo -ldflags '-w' ./kube-ui.go + +container: kube-ui + docker build -t $(PREFIX)/kube-ui:$(TAG) . + +push: container + gcloud docker push $(PREFIX)/kube-ui:$(TAG) + +clean: + rm -f kube-ui diff --git a/cluster/addons/kube-ui/image/kube-ui.go b/cluster/addons/kube-ui/image/kube-ui.go new file mode 100644 index 00000000000..1440db1031d --- /dev/null +++ b/cluster/addons/kube-ui/image/kube-ui.go @@ -0,0 +1,54 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// A simple static web server for hosting the Kubernetes cluster UI. +package main + +import ( + "flag" + "fmt" + "mime" + "net/http" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/ui/data/dashboard" + "github.com/golang/glog" + + assetfs "github.com/elazarl/go-bindata-assetfs" +) + +var ( + port = flag.Int("port", 8080, "Port number to serve at.") +) + +func main() { + flag.Parse() + + // Send correct mime type for .svg files. TODO: remove when + // https://github.com/golang/go/commit/21e47d831bafb59f22b1ea8098f709677ec8ce33 + // makes it into all of our supported go versions. + mime.AddExtensionType(".svg", "image/svg+xml") + + // Expose files in www/ on + fileServer := http.FileServer(&assetfs.AssetFS{ + Asset: dashboard.Asset, + AssetDir: dashboard.AssetDir, + Prefix: "www/app", + }) + http.Handle("/", fileServer) + + // TODO: Add support for serving over TLS. + glog.Fatal(http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", *port), nil)) +} diff --git a/cluster/addons/kube-ui/kube-ui-rc.yaml b/cluster/addons/kube-ui/kube-ui-rc.yaml new file mode 100644 index 00000000000..57f7bb37a34 --- /dev/null +++ b/cluster/addons/kube-ui/kube-ui-rc.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: kube-ui-v1 + namespace: default + labels: + k8s-app: kube-ui + version: v1 + kubernetes.io/cluster-service: "true" +spec: + replicas: 1 + selector: + k8s-app: kube-ui + version: v1 + template: + metadata: + labels: + k8s-app: kube-ui + version: v1 + kubernetes.io/cluster-service: "true" + spec: + containers: + - name: kube-ui + image: gcr.io/google_containers/kube-ui:v1 + ports: + - containerPort: 8080 diff --git a/cluster/addons/kube-ui/kube-ui-svc.yaml b/cluster/addons/kube-ui/kube-ui-svc.yaml new file mode 100644 index 00000000000..5dfcba3a3e6 --- /dev/null +++ b/cluster/addons/kube-ui/kube-ui-svc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: kube-ui + namespace: default + labels: + k8s-app: kube-ui + kubernetes.io/cluster-service: "true" + kubernetes.io/name: "KubeUI" +spec: + selector: + k8s-app: kube-ui + ports: + - port: 80 + targetPort: 8080 diff --git a/cluster/saltbase/salt/kube-addons/init.sls b/cluster/saltbase/salt/kube-addons/init.sls index 35c0a348f61..67c0ecda361 100644 --- a/cluster/saltbase/salt/kube-addons/init.sls +++ b/cluster/saltbase/salt/kube-addons/init.sls @@ -87,6 +87,17 @@ addon-dir-create: - file_mode: 644 {% endif %} +{% if pillar.get('enable_cluster_ui', 'true').lower() == 'true' %} +/etc/kubernetes/addons/kube-ui: + file.recurse: + - source: salt://kube-addons/kube-ui + - include_pat: E@^.+\.yaml$ + - user: root + - group: root + - dir_mode: 755 + - file_mode: 644 +{% endif %} + /etc/kubernetes/kube-addons.sh: file.managed: - source: salt://kube-addons/kube-addons.sh diff --git a/docs/ui.md b/docs/ui.md index 80494ec65d7..62b1d6fecb5 100644 --- a/docs/ui.md +++ b/docs/ui.md @@ -15,6 +15,6 @@ You should now be able to access it by visiting [localhost:8001](http://localhos You can also use other web servers to serve the contents of the www/app directory, as described [here](../www/README.md#serving-the-app-during-development). ### Running remotely -When Kubernetes is deployed remotely, the api server deploys the UI. To access it, visit `/static/app/` or `/ui`, which redirects to `/static/app/`, on your master server. +When Kubernetes is deployed remotely, the api server deploys the UI. To access it, visit `/ui`, which redirects to `/api/v1/proxy/namespaces/default/services/kube-ui/#/dashboard/`, on your master server. [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/ui.md?pixel)]() diff --git a/hack/build-ui.sh b/hack/build-ui.sh index f1f0bcc8ca5..f85364f1ff9 100755 --- a/hack/build-ui.sh +++ b/hack/build-ui.sh @@ -30,16 +30,39 @@ if ! which go-bindata > /dev/null 2>&1 ; then exit 1 fi -DATAFILE=pkg/ui/datafile.go -TMP_DATAFILE=/tmp/datafile.go +readonly TMP_DATAFILE="/tmp/datafile.go" +readonly DASHBOARD_SRC="www/app/..." +readonly DASHBOARD_PKG="dashboard" +readonly SWAGGER_SRC="third_party/swagger-ui/..." +readonly SWAGGER_PKG="swagger" -go-bindata -nocompress -o $DATAFILE -prefix ${PWD} -pkg ui www/app/... third_party/swagger-ui/... +function kube::hack::build_ui() { + local pkg="$1" + local src="$2" + local output_file="pkg/ui/data/${pkg}/datafile.go" -cat hooks/boilerplate.go.txt > $TMP_DATAFILE -echo "// generated by hack/build-ui.sh; DO NOT EDIT -" >> $TMP_DATAFILE -cat $DATAFILE >> $TMP_DATAFILE + go-bindata -nocompress -o "${output_file}" -prefix ${PWD} -pkg "${pkg}" "${src}" -gofmt -s -w $TMP_DATAFILE + local year=$(date +%Y) + cat hooks/boilerplate.go.txt | sed "s/YEAR/${year}/" > "${TMP_DATAFILE}" + echo -e "// generated by hack/build-ui.sh; DO NOT EDIT\n" >> "${TMP_DATAFILE}" + cat "${output_file}" >> "${TMP_DATAFILE}" -mv $TMP_DATAFILE $DATAFILE + gofmt -s -w "${TMP_DATAFILE}" + + mv "${TMP_DATAFILE}" "${output_file}" + +} + +case "${1:-}" in + dashboard) + kube::hack::build_ui "${DASHBOARD_PKG}" "${DASHBOARD_SRC}" + ;; + swagger) + kube::hack::build_ui "${SWAGGER_PKG}" "${SWAGGER_SRC}" + ;; + *) + kube::hack::build_ui "${DASHBOARD_PKG}" "${DASHBOARD_SRC}" + kube::hack::build_ui "${SWAGGER_PKG}" "${SWAGGER_SRC}" + ;; +esac diff --git a/pkg/ui/data/README.md b/pkg/ui/data/README.md new file mode 100644 index 00000000000..0cbe73d7270 --- /dev/null +++ b/pkg/ui/data/README.md @@ -0,0 +1,9 @@ +The datafiles contained in these directories were generated by the script +```sh +hack/build-ui.sh +``` + +Do not edit by hand. + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/pkg/ui/data/README.md?pixel)]() diff --git a/pkg/ui/data/dashboard/datafile.go b/pkg/ui/data/dashboard/datafile.go new file mode 100644 index 00000000000..b94d9d325f0 --- /dev/null +++ b/pkg/ui/data/dashboard/datafile.go @@ -0,0 +1,5004 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// generated by hack/build-ui.sh; DO NOT EDIT + +// Code generated by go-bindata. +// sources: +// www/app/assets/css/app.css +// www/app/assets/img/docArrow.png +// www/app/assets/img/ic_arrow_drop_down_24px.svg +// www/app/assets/img/ic_arrow_drop_up_24px.svg +// www/app/assets/img/ic_keyboard_arrow_left_24px.svg +// www/app/assets/img/ic_keyboard_arrow_right_24px.svg +// www/app/assets/img/icons/arrow-back.png +// www/app/assets/img/icons/favicon.png +// www/app/assets/img/icons/ic_arrow_forward_24px.svg +// www/app/assets/img/icons/ic_cancel_24px.svg +// www/app/assets/img/icons/ic_close_24px.svg +// www/app/assets/img/icons/ic_menu.svg +// www/app/assets/img/icons/ic_menu_24px.svg +// www/app/assets/img/icons/list_control_down.png +// www/app/assets/img/kubernetes.svg +// www/app/assets/js/app.js +// www/app/assets/js/base.js +// www/app/components/dashboard/img/icons/ic_arrow_drop_down_18px.svg +// www/app/components/dashboard/img/icons/ic_arrow_drop_down_24px.svg +// www/app/components/dashboard/img/icons/ic_close_18px.svg +// www/app/components/dashboard/img/icons/ic_close_24px.svg +// www/app/components/dashboard/manifest.json +// www/app/components/dashboard/pages/footer.html +// www/app/components/dashboard/pages/header.html +// www/app/components/dashboard/pages/home.html +// www/app/components/dashboard/views/groups.html +// www/app/components/dashboard/views/listEvents.html +// www/app/components/dashboard/views/listMinions.html +// www/app/components/dashboard/views/listPods.html +// www/app/components/dashboard/views/listPodsCards.html +// www/app/components/dashboard/views/listPodsVisualizer.html +// www/app/components/dashboard/views/listReplicationControllers.html +// www/app/components/dashboard/views/listServices.html +// www/app/components/dashboard/views/node.html +// www/app/components/dashboard/views/partials/cadvisor.html +// www/app/components/dashboard/views/partials/groupBox.html +// www/app/components/dashboard/views/partials/groupItem.html +// www/app/components/dashboard/views/partials/podTilesByName.html +// www/app/components/dashboard/views/partials/podTilesByServer.html +// www/app/components/dashboard/views/pod.html +// www/app/components/dashboard/views/replication.html +// www/app/components/dashboard/views/service.html +// www/app/index.html +// www/app/vendor/angular-json-human/dist/angular-json-human.css +// www/app/vendor/angular-material/angular-material.css +// www/app/vendor/d3/d3.min.js +// www/app/views/partials/404.html +// www/app/views/partials/kubernetes-ui-menu.tmpl.html +// www/app/views/partials/md-table.tmpl.html +// www/app/views/partials/menu-toggle.tmpl.html +// DO NOT EDIT! + +package dashboard + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "time" +) + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _wwwAppAssetsCssAppCss = []byte(`.nav-back{width:80px;font-size:14px;padding-left:14px;line-height:15px;background-size:14px 14px;background-repeat:no-repeat;display:block}a{text-decoration:none}.main-fab{position:absolute;z-index:20;font-size:30px;top:100px;left:24px;transform:scale(.88,.88)}.md-breadcrumb{padding-left:16px}.md-table{min-width:100%;border-collapse:collapse}.md-table tbody tr:focus,.md-table tbody tr:hover{cursor:pointer;background-color:rgba(63,81,181,.2)}.md-table-header{border-bottom:1px solid #e6e6e6;color:#828282;text-align:left;font-size:.75em;font-weight:700;padding:16px 16px 16px 0}.md-table-header a{text-decoration:none;color:inherit}.md-table-caret{display:inline-block;vertical-align:middle}.md-table-content{font-size:.8em;padding:16px 16px 16px 0;height:72px}.md-table-td-more{max-width:72px;width:72px;padding:16px}.md-table-thumbs{max-width:104px;width:104px;padding:16px 32px}.md-table-thumbs div{overflow:hidden;width:40px;height:40px;border-radius:20px;border:1px solid rgba(0,0,0,.2);background-size:cover;box-shadow:0 8px 10px rgba(0,0,0,.3);-webkit-box-shadow:0 8px 10px rgba(0,0,0,.1)}.md-table-footer{height:40px}.md-table-count-info{line-height:40px;font-size:.75em}.md-table-footer-item{width:40px;height:40px;vertical-align:middle}.bold,.md-table-active-page{font-weight:700}.gray,.grey{color:#888}md-input-container.md-default-theme .md-input{color:#fff;border-color:#fff;margin-top:24px}.dashboard-subnav{font-size:.9em;min-height:38px;max-height:38px;background-color:#09c1d1!important}.dashboard-subnav md-select.md-default-theme:focus .md-select-label{border-bottom:none;color:#fff}.selectSubPages p{text-align:center;color:#fff}.selectSubPages .md-default-theme .md-select-label.md-placeholder{color:#fff}.selectSubPages .md-select-label{padding-top:0;font-size:1em;line-height:1em;border-bottom:none;padding-bottom:0}.selectSubPages md-select{margin-top:10px;margin-right:80px;padding:0}md-select-menu{max-height:none}.md-toolbar-tools{padding-left:8px;padding-right:8px}.md-toolbar-small{height:38px;min-height:38px}.md-toolbar-tools-small{background-color:#09c1d1}.kubernetes-ui-menu,.kubernetes-ui-menu ul{list-style:none;padding:0}.kubernetes-ui-menu li{margin:0}.kubernetes-ui-menu>li{border-top:1px solid rgba(0,0,0,.12)}.kubernetes-ui-menu .md-button{border-radius:0;color:inherit;cursor:pointer;font-weight:400;line-height:40px;margin:0;max-height:40px;overflow:hidden;padding:0 16px;text-align:left;text-decoration:none;white-space:normal;width:100%}.kubernetes-ui-menu a.md-button{display:block}.kubernetes-ui-menu button.md-button::-moz-focus-inner{padding:0}.kubernetes-ui-menu .md-button.active{color:#03a9f4}.menu-heading{color:#888;display:block;font-size:inherit;font-weight:500;line-height:40px;margin:0;padding:0 16px;text-align:left;width:100%}.kubernetes-ui-menu li.parentActive,.kubernetes-ui-menu li.parentActive .menu-toggle-list{background-color:#f6f6f6}.menu-toggle-list{background:#fff;max-height:999px;overflow:hidden;position:relative;z-index:1;-webkit-transition:.75s cubic-bezier(.35,0,.25,1);-webkit-transition-property:max-height;-moz-transition:.75s cubic-bezier(.35,0,.25,1);-moz-transition-property:max-height;transition:.75s cubic-bezier(.35,0,.25,1);transition-property:max-height}.menu-toggle-list.ng-hide{max-height:0}.kubernetes-ui-menu .menu-toggle-list a.md-button{display:block;padding:0 16px 0 32px;text-transform:none}.md-button-toggle .md-toggle-icon{background:url(../img/icons/list_control_down.png) center center no-repeat;background-size:100% auto;display:inline-block;height:24px;margin:auto 0 auto auto;speak:none;width:24px;transition:transform .3s ease-in-out;-webkit-transition:-webkit-transform .3s ease-in-out}.md-button-toggle .md-toggle-icon.toggled{transform:rotate(180deg);-webkit-transform:rotate(180deg)}.menu-icon{background:0 0;border:none;margin-right:16px;padding:0}.whiteframedemoBasicUsage md-whiteframe{background:#fff;margin:2px;padding:2px}.tabsDefaultTabs{height:100%;width:100%}.tabsDefaultTabs .remove-tab{margin-bottom:40px}.tabsDefaultTabs .home-buttons .md-button{display:block;max-height:30px}.tabsDefaultTabs .home-buttons .md-button.add-tab{margin-top:20px;max-height:30px!important}.tabsDefaultTabs .demo-tab{display:block;position:relative;background:#fff;border:0 solid #000;min-height:0;width:100%}.tabsDefaultTabs .tab0,.tabsDefaultTabs .tab1,.tabsDefaultTabs .tab2,.tabsDefaultTabs .tab3{background-color:#bbdefb}.tabsDefaultTabs .md-header{background-color:#1976D2!important}.tabsDefaultTabs md-tab{color:#90caf9!important}.tabsDefaultTabs md-tab.active,.tabsDefaultTabs md-tab:focus{color:#fff!important}.tabsDefaultTabs md-tab[disabled]{opacity:.5}.tabsDefaultTabs .md-header .md-ripple{border-color:#FFFF8D!important}.tabsDefaultTabs md-tabs-ink-bar{background-color:#FFFF8D!important}.tabsDefaultTabs .title{padding-top:8px;padding-right:8px;text-align:left;text-transform:uppercase;color:#888;margin-top:24px}.tabsDefaultTabs [layout-align]>*,.tabsDefaultTabs form>[layout]>*{margin-left:8px}.tabsDefaultTabs .long>input{width:264px}.menuBtn{background-color:transparent;border:none;height:38px;margin:16px;position:absolute;width:36px}md-toolbar h1{margin:auto}md-list .md-button{color:inherit;font-weight:500;text-align:left;width:100%}md-list .md-button.selected{color:#03a9f4}#content{overflow:hidden}#content md-content{padding-left:0;padding-right:0;padding-top:0}#content .md-button.action{background-color:transparent;border:none;height:38px;margin:8px auto 16px 0;position:absolute;top:10px;right:25px;width:36px}#content img{display:block;height:auto;max-width:500px}.content-wrapper{position:relative}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}md-toolbar h1{font-size:1.25em;font-weight:400}.menuBtn{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGcgaWQ9IkhlYWRlciI+CiAgICA8Zz4KICAgICAgICA8cmVjdCB4PSItNjE4IiB5PSItMjIzMiIgZmlsbD0ibm9uZSIgd2lkdGg9IjE0MDAiIGhlaWdodD0iMzYwMCIvPgogICAgPC9nPgo8L2c+CjxnIGlkPSJMYWJlbCI+CjwvZz4KPGcgaWQ9Ikljb24iPgogICAgPGc+CiAgICAgICAgPHJlY3QgZmlsbD0ibm9uZSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ii8+CiAgICAgICAgPHBhdGggZD0iTTMsMThoMTh2LTJIM1YxOHogTTMsMTNoMTh2LTJIM1YxM3ogTTMsNnYyaDE4VjZIM3oiIHN0eWxlPSJmaWxsOiNmM2YzZjM7Ii8+CiAgICA8L2c+CjwvZz4KPGcgaWQ9IkdyaWQiIGRpc3BsYXk9Im5vbmUiPgogICAgPGcgZGlzcGxheT0iaW5saW5lIj4KICAgIDwvZz4KPC9nPgo8L3N2Zz4=) center center no-repeat}.actionBtn{background:url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNiIgaGVpZ2h0PSIzNiIgdmlld0JveD0iMCAwIDM2IDM2Ij4NCiAgICA8cGF0aCBkPSJNMCAwaDM2djM2aC0zNnoiIGZpbGw9Im5vbmUiLz4NCiAgICA8cGF0aCBkPSJNNCAyN2gyOHYtM2gtMjh2M3ptMC04aDI4di0zaC0yOHYzem0wLTExdjNoMjh2LTNoLTI4eiIvPg0KPC9zdmc+) center center no-repeat}.kubernetes-ui-logo{background-image:url(../img/kubernetes.svg);background-size:40px 40px;width:40px;height:40px}.kubernetes-ui-text{line-height:40px;vertical-align:middle;padding:2px}md-select-menu.md-default-theme md-option:focus:not([selected]){background:#eee}md-select-menu md-option{transition:box-shadow .4s cubic-bezier(.25,.8,.25,1),background-color .4s cubic-bezier(.25,.8,.25,1),-webkit-transform .4s cubic-bezier(.25,.8,.25,1);transition:box-shadow .4s cubic-bezier(.25,.8,.25,1),background-color .4s cubic-bezier(.25,.8,.25,1),transform .4s cubic-bezier(.25,.8,.25,1)}md-select-menu md-option:not([disabled]):focus,md-select-menu md-option:not([disabled]):hover{background-color:rgba(158,158,158,.2)}.dashboard .body-wrapper{padding:25px}.dashboard [flex-align-self=end]{-webkit-align-self:flex-end;-ms-flex-align-self:end;align-self:flex-end}.dashboard .back{font-size:18px;line-height:27px;margin-bottom:30px}.dashboard .heading{font-size:18px;line-height:21px;color:#222;margin-bottom:25px}.dashboard .heading .label{color:#777}.dashboard .clear-bg{background-color:transparent}.dashboard .list-pods .pod-group{margin:25px}.dashboard .list-pods .pod-group md-grid-list{margin-top:50px;color:#fff}.dashboard .list-pods .pod-group md-grid-list figcaption{width:100%}.dashboard .list-pods .pod-group md-grid-list md-grid-tile-header{padding-left:10px}.dashboard .list-pods .pod-group md-grid-list md-grid-tile-header .labels{width:100%}.dashboard .list-pods .pod-group md-grid-list md-grid-tile{transition:all 700ms ease-in 50ms}.dashboard .list-pods .pod-group md-grid-list .inner-box{padding-left:10px;padding-right:10px}.dashboard .list-pods .pod-group md-grid-list md-grid-tile-footer{background:rgba(0,0,0,.5)}.dashboard .list-pods .pod-group md-grid-list md-grid-tile-footer .pod-title{margin-left:10px}.dashboard .list-pods .pod-group md-grid-list md-grid-tile-footer .pod-host{text-align:right;padding-right:15px}.dashboard .list-pods .pod-group md-grid-list md-grid-tile-footer a{color:#fff}.dashboard .list-pods .pod-group md-grid-list .restarts{width:100%;text-align:right;padding-right:10px}.dashboard .list-pods .pod-group md-grid-list .restarts .restart-button,.dashboard .list-pods .pod-group md-grid-list .restarts .restart-button:focus,.dashboard .list-pods .pod-group md-grid-list .restarts .restart-button:hover,.dashboard .list-pods .pod-group md-grid-list .restarts .restart-button:not([disabled]):focus,.dashboard .list-pods .pod-group md-grid-list .restarts .restart-button:not([disabled]):hover{background-color:#ff1744;width:30px;height:30px}.dashboard .list-pods .gray{background:#f5f5f5}.dashboard .list-pods .dark-overlay{background-color:#292935;opacity:.5}.dashboard .list-pods .light-overlay{background-color:#FFF;opacity:.2}.dashboard .list-pods .color-1{background-color:#2962ff;fill:#2962ff;stroke:#2962ff}.dashboard .list-pods .color-max-1{background-color:#dce5ff;border-color:#dce5ff;fill:#dce5ff}.dashboard .list-pods md-grid-list.list-color-1 md-grid-tile.colored{background-color:#2962ff}.dashboard .list-pods .color-2{background-color:#a0f;fill:#a0f;stroke:#a0f}.dashboard .list-pods .color-max-2{background-color:#e6b3ff;border-color:#e6b3ff;fill:#e6b3ff}.dashboard .list-pods md-grid-list.list-color-2 md-grid-tile.colored{background-color:#a0f}.dashboard .list-pods .color-3{background-color:#00c853;fill:#00c853;stroke:#00c853}.dashboard .list-pods .color-max-3{background-color:#7cffb2;border-color:#7cffb2;fill:#7cffb2}.dashboard .list-pods md-grid-list.list-color-3 md-grid-tile.colored{background-color:#00c853}.dashboard .list-pods .color-4{background-color:#304ffe;fill:#304ffe;stroke:#304ffe}.dashboard .list-pods .color-max-4{background-color:#e2e6ff;border-color:#e2e6ff;fill:#e2e6ff}.dashboard .list-pods md-grid-list.list-color-4 md-grid-tile.colored{background-color:#304ffe}.dashboard .list-pods .color-5{background-color:#0091ea;fill:#0091ea;stroke:#0091ea}.dashboard .list-pods .color-max-5{background-color:#9edaff;border-color:#9edaff;fill:#9edaff}.dashboard .list-pods md-grid-list.list-color-5 md-grid-tile.colored{background-color:#0091ea}.dashboard .list-pods .color-6{background-color:#ff6d00;fill:#ff6d00;stroke:#ff6d00}.dashboard .list-pods .color-max-6{background-color:#ffd3b3;border-color:#ffd3b3;fill:#ffd3b3}.dashboard .list-pods md-grid-list.list-color-6 md-grid-tile.colored{background-color:#ff6d00}.dashboard .list-pods .color-7{background-color:#00bfa5;fill:#00bfa5;stroke:#00bfa5}.dashboard .list-pods .color-max-7{background-color:#72ffec;border-color:#72ffec;fill:#72ffec}.dashboard .list-pods md-grid-list.list-color-7 md-grid-tile.colored{background-color:#00bfa5}.dashboard .list-pods .color-8{background-color:#c51162;fill:#c51162;stroke:#c51162}.dashboard .list-pods .color-max-8{background-color:#f693bf;border-color:#f693bf;fill:#f693bf}.dashboard .list-pods md-grid-list.list-color-8 md-grid-tile.colored{background-color:#c51162}.dashboard .list-pods .color-9{background-color:#64dd17;fill:#64dd17;stroke:#64dd17}.dashboard .list-pods .color-max-9{background-color:#cbf7b0;border-color:#cbf7b0;fill:#cbf7b0}.dashboard .list-pods md-grid-list.list-color-9 md-grid-tile.colored{background-color:#64dd17}.dashboard .list-pods .color-10{background-color:#6200ea;fill:#6200ea;stroke:#6200ea}.dashboard .list-pods .color-max-10{background-color:#c69eff;border-color:#c69eff;fill:#c69eff}.dashboard .list-pods md-grid-list.list-color-10 md-grid-tile.colored{background-color:#6200ea}.dashboard .list-pods .color-11{background-color:#ffd600;fill:#ffd600;stroke:#ffd600}.dashboard .list-pods .color-max-11{background-color:#fff3b3;border-color:#fff3b3;fill:#fff3b3}.dashboard .list-pods md-grid-list.list-color-11 md-grid-tile.colored{background-color:#ffd600}.dashboard .list-pods .color-12{background-color:#00b8d4;fill:#00b8d4;stroke:#00b8d4}.dashboard .list-pods .color-max-12{background-color:#87efff;border-color:#87efff;fill:#87efff}.dashboard .list-pods md-grid-list.list-color-12 md-grid-tile.colored{background-color:#00b8d4}.dashboard .list-pods .color-13{background-color:#ffab00;fill:#ffab00;stroke:#ffab00}.dashboard .list-pods .color-max-13{background-color:#ffe6b3;border-color:#ffe6b3;fill:#ffe6b3}.dashboard .list-pods md-grid-list.list-color-13 md-grid-tile.colored{background-color:#ffab00}.dashboard .list-pods .color-14{background-color:#dd2c00;fill:#dd2c00;stroke:#dd2c00}.dashboard .list-pods .color-max-14{background-color:#ffa791;border-color:#ffa791;fill:#ffa791}.dashboard .list-pods md-grid-list.list-color-14 md-grid-tile.colored{background-color:#dd2c00}.dashboard .list-pods .color-15{background-color:#2979ff;fill:#2979ff;stroke:#2979ff}.dashboard .list-pods .color-max-15{background-color:#dce9ff;border-color:#dce9ff;fill:#dce9ff}.dashboard .list-pods md-grid-list.list-color-15 md-grid-tile.colored{background-color:#2979ff}.dashboard .list-pods .color-16{background-color:#d500f9;fill:#d500f9;stroke:#d500f9}.dashboard .list-pods .color-max-16{background-color:#f3acff;border-color:#f3acff;fill:#f3acff}.dashboard .list-pods md-grid-list.list-color-16 md-grid-tile.colored{background-color:#d500f9}.dashboard .list-pods .color-17{background-color:#00e676;fill:#00e676;stroke:#00e676}.dashboard .list-pods .color-max-17{background-color:#9affce;border-color:#9affce;fill:#9affce}.dashboard .list-pods md-grid-list.list-color-17 md-grid-tile.colored{background-color:#00e676}.dashboard .list-pods .color-18{background-color:#3d5afe;fill:#3d5afe;stroke:#3d5afe}.dashboard .list-pods .color-max-18{background-color:#eff1ff;border-color:#eff1ff;fill:#eff1ff}.dashboard .list-pods md-grid-list.list-color-18 md-grid-tile.colored{background-color:#3d5afe}.dashboard .list-pods .color-19{background-color:#00b0ff;fill:#00b0ff;stroke:#00b0ff}.dashboard .list-pods .color-max-19{background-color:#b3e7ff;border-color:#b3e7ff;fill:#b3e7ff}.dashboard .list-pods md-grid-list.list-color-19 md-grid-tile.colored{background-color:#00b0ff}.dashboard .list-pods .color-20{background-color:#ff9100;fill:#ff9100;stroke:#ff9100}.dashboard .list-pods .color-max-20{background-color:#ffdeb3;border-color:#ffdeb3;fill:#ffdeb3}.dashboard .list-pods md-grid-list.list-color-20 md-grid-tile.colored{background-color:#ff9100}.dashboard .list-pods .color-21{background-color:#1de9b6;fill:#1de9b6;stroke:#1de9b6}.dashboard .list-pods .color-max-21{background-color:#c0f9eb;border-color:#c0f9eb;fill:#c0f9eb}.dashboard .list-pods md-grid-list.list-color-21 md-grid-tile.colored{background-color:#1de9b6}.dashboard .list-pods .color-22{background-color:#f50057;fill:#f50057;stroke:#f50057}.dashboard .list-pods .color-max-22{background-color:#ffa8c7;border-color:#ffa8c7;fill:#ffa8c7}.dashboard .list-pods md-grid-list.list-color-22 md-grid-tile.colored{background-color:#f50057}.dashboard .list-pods .color-23{background-color:#76ff03;fill:#76ff03;stroke:#76ff03}.dashboard .list-pods .color-max-23{background-color:#d7ffb5;border-color:#d7ffb5;fill:#d7ffb5}.dashboard .list-pods md-grid-list.list-color-23 md-grid-tile.colored{background-color:#76ff03}.dashboard .list-pods .color-24{background-color:#651fff;fill:#651fff;stroke:#651fff}.dashboard .list-pods .color-max-24{background-color:#e0d2ff;border-color:#e0d2ff;fill:#e0d2ff}.dashboard .list-pods md-grid-list.list-color-24 md-grid-tile.colored{background-color:#651fff}.dashboard .list-pods .color-25{background-color:#ffea00;fill:#ffea00;stroke:#ffea00}.dashboard .list-pods .color-max-25{background-color:#fff9b3;border-color:#fff9b3;fill:#fff9b3}.dashboard .list-pods md-grid-list.list-color-25 md-grid-tile.colored{background-color:#ffea00}.dashboard .list-pods .color-26{background-color:#00e5ff;fill:#00e5ff;stroke:#00e5ff}.dashboard .list-pods .color-max-26{background-color:#b3f7ff;border-color:#b3f7ff;fill:#b3f7ff}.dashboard .list-pods md-grid-list.list-color-26 md-grid-tile.colored{background-color:#00e5ff}.dashboard .list-pods .color-27{background-color:#ffc400;fill:#ffc400;stroke:#ffc400}.dashboard .list-pods .color-max-27{background-color:#ffedb3;border-color:#ffedb3;fill:#ffedb3}.dashboard .list-pods md-grid-list.list-color-27 md-grid-tile.colored{background-color:#ffc400}.dashboard .list-pods .color-28{background-color:#ff3d00;fill:#ff3d00;stroke:#ff3d00}.dashboard .list-pods .color-max-28{background-color:#ffc5b3;border-color:#ffc5b3;fill:#ffc5b3}.dashboard .list-pods md-grid-list.list-color-28 md-grid-tile.colored{background-color:#ff3d00}.dashboard .list-pods .color-29{background-color:#448aff;fill:#448aff;stroke:#448aff}.dashboard .list-pods .color-max-29{background-color:#f6faff;border-color:#f6faff;fill:#f6faff}.dashboard .list-pods md-grid-list.list-color-29 md-grid-tile.colored{background-color:#448aff}.dashboard .list-pods .color-30{background-color:#e040fb;fill:#e040fb;stroke:#e040fb}.dashboard .list-pods .color-max-30{background-color:#fcefff;border-color:#fcefff;fill:#fcefff}.dashboard .list-pods md-grid-list.list-color-30 md-grid-tile.colored{background-color:#e040fb}.dashboard .list-pods .color-31{background-color:#69f0ae;fill:#69f0ae;stroke:#69f0ae}.dashboard .list-pods .color-max-31{background-color:#fff;border-color:#fff;fill:#fff}.dashboard .list-pods md-grid-list.list-color-31 md-grid-tile.colored{background-color:#69f0ae}.dashboard .list-pods .color-32{background-color:#536dfe;fill:#536dfe;stroke:#536dfe}.dashboard .list-pods .color-max-32{background-color:#fff;border-color:#fff;fill:#fff}.dashboard .list-pods md-grid-list.list-color-32 md-grid-tile.colored{background-color:#536dfe}.dashboard .list-pods .color-33{background-color:#40c4ff;fill:#40c4ff;stroke:#40c4ff}.dashboard .list-pods .color-max-33{background-color:#f3fbff;border-color:#f3fbff;fill:#f3fbff}.dashboard .list-pods md-grid-list.list-color-33 md-grid-tile.colored{background-color:#40c4ff}.dashboard .list-pods .color-34{background-color:#ffab40;fill:#ffab40;stroke:#ffab40}.dashboard .list-pods .color-max-34{background-color:#fffaf3;border-color:#fffaf3;fill:#fffaf3}.dashboard .list-pods md-grid-list.list-color-34 md-grid-tile.colored{background-color:#ffab40}.dashboard .list-pods .color-35{background-color:#64ffda;fill:#64ffda;stroke:#64ffda}.dashboard .list-pods .color-max-35{background-color:#fff;border-color:#fff;fill:#fff}.dashboard .list-pods md-grid-list.list-color-35 md-grid-tile.colored{background-color:#64ffda}.dashboard .list-pods .color-36{background-color:#ff4081;fill:#ff4081;stroke:#ff4081}.dashboard .list-pods .color-max-36{background-color:#fff3f7;border-color:#fff3f7;fill:#fff3f7}.dashboard .list-pods md-grid-list.list-color-36 md-grid-tile.colored{background-color:#ff4081}.dashboard .list-pods .color-37{background-color:#b2ff59;fill:#b2ff59;stroke:#b2ff59}.dashboard .list-pods .color-max-37{background-color:#fff;border-color:#fff;fill:#fff}.dashboard .list-pods md-grid-list.list-color-37 md-grid-tile.colored{background-color:#b2ff59}.dashboard .list-pods .color-38{background-color:#7c4dff;fill:#7c4dff;stroke:#7c4dff}.dashboard .list-pods .color-max-38{background-color:#fff;border-color:#fff;fill:#fff}.dashboard .list-pods md-grid-list.list-color-38 md-grid-tile.colored{background-color:#7c4dff}.dashboard .list-pods .color-39{background-color:#ff0;fill:#ff0;stroke:#ff0}.dashboard .list-pods .color-max-39{background-color:#ffffb3;border-color:#ffffb3;fill:#ffffb3}.dashboard .list-pods md-grid-list.list-color-39 md-grid-tile.colored{background-color:#ff0}.dashboard .list-pods .color-40{background-color:#18ffff;fill:#18ffff;stroke:#18ffff}.dashboard .list-pods .color-max-40{background-color:#cbffff;border-color:#cbffff;fill:#cbffff}.dashboard .list-pods md-grid-list.list-color-40 md-grid-tile.colored{background-color:#18ffff}.dashboard .list-pods .color-41{background-color:#ffd740;fill:#ffd740;stroke:#ffd740}.dashboard .list-pods .color-max-41{background-color:#fffcf3;border-color:#fffcf3;fill:#fffcf3}.dashboard .list-pods md-grid-list.list-color-41 md-grid-tile.colored{background-color:#ffd740}.dashboard .list-pods .color-42{background-color:#ff6e40;fill:#ff6e40;stroke:#ff6e40}.dashboard .list-pods .color-max-42{background-color:#fff6f3;border-color:#fff6f3;fill:#fff6f3}.dashboard .list-pods md-grid-list.list-color-42 md-grid-tile.colored{background-color:#ff6e40}.dashboard .list-pods .color-warning{background-color:#ff9800!important;border-color:#ff9800!important;fill:#ff9800!important;stroke:#ff9800!important}.dashboard .list-pods .color-critical{background-color:#f44336!important;border-color:#f44336!important;fill:#f44336!important;stroke:#f44336!important}.dashboard .list-pods .status-waiting{background-color:#2e2e3b!important;border-color:#dad462!important;border-width:2px!important;border-style:solid!important}.dashboard .list-pods .status-terminated,.dashboard .list-pods .status-unknown{background-color:#ff1744!important;border-color:#e3002c!important;border-width:1px!important;border-style:solid!important}.dashboard .dash-table{min-width:100%;border-collapse:collapse}.dashboard .dash-table tbody tr:focus:not(.no-link),.dashboard .dash-table tbody tr:hover:not(.no-link){cursor:pointer;background-color:rgba(63,81,181,.2)}.dashboard .dash-table .dash-table-header{border-bottom:1px solid #e6e6e6;color:#828282;text-align:left;font-size:.75em;font-weight:700;padding:16px 16px 16px 0}.dashboard .dash-table .dash-table-header a{text-decoration:none;color:inherit}.dashboard .dash-table .dash-table-caret{display:inline-block;vertical-align:middle}.dashboard .dash-table .dash-table-content{font-size:.8em;padding:16px 16px 16px 0;height:72px}.dashboard .dash-table .dash-table-td-more{max-width:72px;width:72px;padding:16px}.dashboard .dash-table .dash-table-thumbs{max-width:104px;width:104px;padding:16px 32px}.dashboard .dash-table .dash-table-thumbs div{overflow:hidden;width:40px;height:40px;border-radius:20px;border:1px solid rgba(0,0,0,.2);background-size:cover;box-shadow:0 8px 10px rgba(0,0,0,.3);-webkit-box-shadow:0 8px 10px rgba(0,0,0,.1)}.dashboard .dash-table .dash-table-footer{height:40px}.dashboard .dash-table .dash-table-count-info{line-height:40px;font-size:.75em}.dashboard .dash-table .dash-table-footer-item{width:40px;height:40px;vertical-align:middle}.dashboard .dash-table .bold,.dashboard .dash-table .dash-table-active-page{font-weight:700}.dashboard .dash-table .grey{color:grey}.dashboard .dash-table md-input-container.md-default-theme .md-input{color:#fff;border-color:#fff;margin-top:24px}.dashboard .server-overview .dark-overlay{background-color:#292935;opacity:.5}.dashboard .server-overview .light-overlay{background-color:#FFF;opacity:.2}.dashboard .server-overview md-grid-list.list-color-1 md-grid-tile.colored{background-color:#2962ff}.dashboard .server-overview md-grid-list.list-color-2 md-grid-tile.colored{background-color:#a0f}.dashboard .server-overview md-grid-list.list-color-3 md-grid-tile.colored{background-color:#00c853}.dashboard .server-overview .color-4{background-color:#304ffe;fill:#304ffe;stroke:#304ffe}.dashboard .server-overview .color-max-4{background-color:#e2e6ff;border-color:#e2e6ff;fill:#e2e6ff}.dashboard .server-overview md-grid-list.list-color-4 md-grid-tile.colored{background-color:#304ffe}.dashboard .server-overview .color-5{background-color:#0091ea;fill:#0091ea;stroke:#0091ea}.dashboard .server-overview .color-max-5{background-color:#9edaff;border-color:#9edaff;fill:#9edaff}.dashboard .server-overview md-grid-list.list-color-5 md-grid-tile.colored{background-color:#0091ea}.dashboard .server-overview .color-6{background-color:#ff6d00;fill:#ff6d00;stroke:#ff6d00}.dashboard .server-overview .color-max-6{background-color:#ffd3b3;border-color:#ffd3b3;fill:#ffd3b3}.dashboard .server-overview md-grid-list.list-color-6 md-grid-tile.colored{background-color:#ff6d00}.dashboard .server-overview .color-7{background-color:#00bfa5;fill:#00bfa5;stroke:#00bfa5}.dashboard .server-overview .color-max-7{background-color:#72ffec;border-color:#72ffec;fill:#72ffec}.dashboard .server-overview md-grid-list.list-color-7 md-grid-tile.colored{background-color:#00bfa5}.dashboard .server-overview .color-8{background-color:#c51162;fill:#c51162;stroke:#c51162}.dashboard .server-overview .color-max-8{background-color:#f693bf;border-color:#f693bf;fill:#f693bf}.dashboard .server-overview md-grid-list.list-color-8 md-grid-tile.colored{background-color:#c51162}.dashboard .server-overview .color-9{background-color:#64dd17;fill:#64dd17;stroke:#64dd17}.dashboard .server-overview .color-max-9{background-color:#cbf7b0;border-color:#cbf7b0;fill:#cbf7b0}.dashboard .server-overview md-grid-list.list-color-9 md-grid-tile.colored{background-color:#64dd17}.dashboard .server-overview .color-10{background-color:#6200ea;fill:#6200ea;stroke:#6200ea}.dashboard .server-overview .color-max-10{background-color:#c69eff;border-color:#c69eff;fill:#c69eff}.dashboard .server-overview md-grid-list.list-color-10 md-grid-tile.colored{background-color:#6200ea}.dashboard .server-overview .color-11{background-color:#ffd600;fill:#ffd600;stroke:#ffd600}.dashboard .server-overview .color-max-11{background-color:#fff3b3;border-color:#fff3b3;fill:#fff3b3}.dashboard .server-overview md-grid-list.list-color-11 md-grid-tile.colored{background-color:#ffd600}.dashboard .server-overview .color-12{background-color:#00b8d4;fill:#00b8d4;stroke:#00b8d4}.dashboard .server-overview .color-max-12{background-color:#87efff;border-color:#87efff;fill:#87efff}.dashboard .server-overview md-grid-list.list-color-12 md-grid-tile.colored{background-color:#00b8d4}.dashboard .server-overview .color-13{background-color:#ffab00;fill:#ffab00;stroke:#ffab00}.dashboard .server-overview .color-max-13{background-color:#ffe6b3;border-color:#ffe6b3;fill:#ffe6b3}.dashboard .server-overview md-grid-list.list-color-13 md-grid-tile.colored{background-color:#ffab00}.dashboard .server-overview .color-14{background-color:#dd2c00;fill:#dd2c00;stroke:#dd2c00}.dashboard .server-overview .color-max-14{background-color:#ffa791;border-color:#ffa791;fill:#ffa791}.dashboard .server-overview md-grid-list.list-color-14 md-grid-tile.colored{background-color:#dd2c00}.dashboard .server-overview .color-15{background-color:#2979ff;fill:#2979ff;stroke:#2979ff}.dashboard .server-overview .color-max-15{background-color:#dce9ff;border-color:#dce9ff;fill:#dce9ff}.dashboard .server-overview md-grid-list.list-color-15 md-grid-tile.colored{background-color:#2979ff}.dashboard .server-overview .color-16{background-color:#d500f9;fill:#d500f9;stroke:#d500f9}.dashboard .server-overview .color-max-16{background-color:#f3acff;border-color:#f3acff;fill:#f3acff}.dashboard .server-overview md-grid-list.list-color-16 md-grid-tile.colored{background-color:#d500f9}.dashboard .server-overview .color-17{background-color:#00e676;fill:#00e676;stroke:#00e676}.dashboard .server-overview .color-max-17{background-color:#9affce;border-color:#9affce;fill:#9affce}.dashboard .server-overview md-grid-list.list-color-17 md-grid-tile.colored{background-color:#00e676}.dashboard .server-overview .color-18{background-color:#3d5afe;fill:#3d5afe;stroke:#3d5afe}.dashboard .server-overview .color-max-18{background-color:#eff1ff;border-color:#eff1ff;fill:#eff1ff}.dashboard .server-overview md-grid-list.list-color-18 md-grid-tile.colored{background-color:#3d5afe}.dashboard .server-overview .color-19{background-color:#00b0ff;fill:#00b0ff;stroke:#00b0ff}.dashboard .server-overview .color-max-19{background-color:#b3e7ff;border-color:#b3e7ff;fill:#b3e7ff}.dashboard .server-overview md-grid-list.list-color-19 md-grid-tile.colored{background-color:#00b0ff}.dashboard .server-overview .color-20{background-color:#ff9100;fill:#ff9100;stroke:#ff9100}.dashboard .server-overview .color-max-20{background-color:#ffdeb3;border-color:#ffdeb3;fill:#ffdeb3}.dashboard .server-overview md-grid-list.list-color-20 md-grid-tile.colored{background-color:#ff9100}.dashboard .server-overview .color-21{background-color:#1de9b6;fill:#1de9b6;stroke:#1de9b6}.dashboard .server-overview .color-max-21{background-color:#c0f9eb;border-color:#c0f9eb;fill:#c0f9eb}.dashboard .server-overview md-grid-list.list-color-21 md-grid-tile.colored{background-color:#1de9b6}.dashboard .server-overview .color-22{background-color:#f50057;fill:#f50057;stroke:#f50057}.dashboard .server-overview .color-max-22{background-color:#ffa8c7;border-color:#ffa8c7;fill:#ffa8c7}.dashboard .server-overview md-grid-list.list-color-22 md-grid-tile.colored{background-color:#f50057}.dashboard .server-overview .color-23{background-color:#76ff03;fill:#76ff03;stroke:#76ff03}.dashboard .server-overview .color-max-23{background-color:#d7ffb5;border-color:#d7ffb5;fill:#d7ffb5}.dashboard .server-overview md-grid-list.list-color-23 md-grid-tile.colored{background-color:#76ff03}.dashboard .server-overview .color-24{background-color:#651fff;fill:#651fff;stroke:#651fff}.dashboard .server-overview .color-max-24{background-color:#e0d2ff;border-color:#e0d2ff;fill:#e0d2ff}.dashboard .server-overview md-grid-list.list-color-24 md-grid-tile.colored{background-color:#651fff}.dashboard .server-overview .color-25{background-color:#ffea00;fill:#ffea00;stroke:#ffea00}.dashboard .server-overview .color-max-25{background-color:#fff9b3;border-color:#fff9b3;fill:#fff9b3}.dashboard .server-overview md-grid-list.list-color-25 md-grid-tile.colored{background-color:#ffea00}.dashboard .server-overview .color-26{background-color:#00e5ff;fill:#00e5ff;stroke:#00e5ff}.dashboard .server-overview .color-max-26{background-color:#b3f7ff;border-color:#b3f7ff;fill:#b3f7ff}.dashboard .server-overview md-grid-list.list-color-26 md-grid-tile.colored{background-color:#00e5ff}.dashboard .server-overview .color-27{background-color:#ffc400;fill:#ffc400;stroke:#ffc400}.dashboard .server-overview .color-max-27{background-color:#ffedb3;border-color:#ffedb3;fill:#ffedb3}.dashboard .server-overview md-grid-list.list-color-27 md-grid-tile.colored{background-color:#ffc400}.dashboard .server-overview .color-28{background-color:#ff3d00;fill:#ff3d00;stroke:#ff3d00}.dashboard .server-overview .color-max-28{background-color:#ffc5b3;border-color:#ffc5b3;fill:#ffc5b3}.dashboard .server-overview md-grid-list.list-color-28 md-grid-tile.colored{background-color:#ff3d00}.dashboard .server-overview .color-29{background-color:#448aff;fill:#448aff;stroke:#448aff}.dashboard .server-overview .color-max-29{background-color:#f6faff;border-color:#f6faff;fill:#f6faff}.dashboard .server-overview md-grid-list.list-color-29 md-grid-tile.colored{background-color:#448aff}.dashboard .server-overview .color-30{background-color:#e040fb;fill:#e040fb;stroke:#e040fb}.dashboard .server-overview .color-max-30{background-color:#fcefff;border-color:#fcefff;fill:#fcefff}.dashboard .server-overview md-grid-list.list-color-30 md-grid-tile.colored{background-color:#e040fb}.dashboard .server-overview .color-31{background-color:#69f0ae;fill:#69f0ae;stroke:#69f0ae}.dashboard .server-overview .color-max-31{background-color:#fff;border-color:#fff;fill:#fff}.dashboard .server-overview md-grid-list.list-color-31 md-grid-tile.colored{background-color:#69f0ae}.dashboard .server-overview .color-32{background-color:#536dfe;fill:#536dfe;stroke:#536dfe}.dashboard .server-overview .color-max-32{background-color:#fff;border-color:#fff;fill:#fff}.dashboard .server-overview md-grid-list.list-color-32 md-grid-tile.colored{background-color:#536dfe}.dashboard .server-overview .color-33{background-color:#40c4ff;fill:#40c4ff;stroke:#40c4ff}.dashboard .server-overview .color-max-33{background-color:#f3fbff;border-color:#f3fbff;fill:#f3fbff}.dashboard .server-overview md-grid-list.list-color-33 md-grid-tile.colored{background-color:#40c4ff}.dashboard .server-overview .color-34{background-color:#ffab40;fill:#ffab40;stroke:#ffab40}.dashboard .server-overview .color-max-34{background-color:#fffaf3;border-color:#fffaf3;fill:#fffaf3}.dashboard .server-overview md-grid-list.list-color-34 md-grid-tile.colored{background-color:#ffab40}.dashboard .server-overview .color-35{background-color:#64ffda;fill:#64ffda;stroke:#64ffda}.dashboard .server-overview .color-max-35{background-color:#fff;border-color:#fff;fill:#fff}.dashboard .server-overview md-grid-list.list-color-35 md-grid-tile.colored{background-color:#64ffda}.dashboard .server-overview .color-36{background-color:#ff4081;fill:#ff4081;stroke:#ff4081}.dashboard .server-overview .color-max-36{background-color:#fff3f7;border-color:#fff3f7;fill:#fff3f7}.dashboard .server-overview md-grid-list.list-color-36 md-grid-tile.colored{background-color:#ff4081}.dashboard .server-overview .color-37{background-color:#b2ff59;fill:#b2ff59;stroke:#b2ff59}.dashboard .server-overview .color-max-37{background-color:#fff;border-color:#fff;fill:#fff}.dashboard .server-overview md-grid-list.list-color-37 md-grid-tile.colored{background-color:#b2ff59}.dashboard .server-overview .color-38{background-color:#7c4dff;fill:#7c4dff;stroke:#7c4dff}.dashboard .server-overview .color-max-38{background-color:#fff;border-color:#fff;fill:#fff}.dashboard .server-overview md-grid-list.list-color-38 md-grid-tile.colored{background-color:#7c4dff}.dashboard .server-overview .color-39{background-color:#ff0;fill:#ff0;stroke:#ff0}.dashboard .server-overview .color-max-39{background-color:#ffffb3;border-color:#ffffb3;fill:#ffffb3}.dashboard .server-overview md-grid-list.list-color-39 md-grid-tile.colored{background-color:#ff0}.dashboard .server-overview .color-40{background-color:#18ffff;fill:#18ffff;stroke:#18ffff}.dashboard .server-overview .color-max-40{background-color:#cbffff;border-color:#cbffff;fill:#cbffff}.dashboard .server-overview md-grid-list.list-color-40 md-grid-tile.colored{background-color:#18ffff}.dashboard .server-overview .color-41{background-color:#ffd740;fill:#ffd740;stroke:#ffd740}.dashboard .server-overview .color-max-41{background-color:#fffcf3;border-color:#fffcf3;fill:#fffcf3}.dashboard .server-overview md-grid-list.list-color-41 md-grid-tile.colored{background-color:#ffd740}.dashboard .server-overview .color-42{background-color:#ff6e40;fill:#ff6e40;stroke:#ff6e40}.dashboard .server-overview .color-max-42{background-color:#fff6f3;border-color:#fff6f3;fill:#fff6f3}.dashboard .server-overview md-grid-list.list-color-42 md-grid-tile.colored{background-color:#ff6e40}.dashboard .server-overview .color-warning{background-color:#ff9800!important;border-color:#ff9800!important;fill:#ff9800!important;stroke:#ff9800!important}.dashboard .server-overview .color-critical{background-color:#f44336!important;border-color:#f44336!important;fill:#f44336!important;stroke:#f44336!important}.dashboard .server-overview .status-waiting{background-color:#2e2e3b!important;border-color:#dad462!important;border-width:2px!important;border-style:solid!important}.dashboard .server-overview .status-terminated,.dashboard .server-overview .status-unknown{background-color:#ff1744!important;border-color:#e3002c!important;border-width:1px!important;border-style:solid!important}.dashboard .server-overview .color-1{background-color:#512DA8;border-color:#512DA8;fill:#512DA8;stroke:#512DA8}.dashboard .server-overview .color-2{background-color:#9C27B0;border-color:#9C27B0;fill:#9C27B0;stroke:#9C27B0}.dashboard .server-overview .color-3{background-color:#00BCD4;border-color:#00BCD4;fill:#00BCD4;stroke:#00BCD4}.dashboard .server-overview .color-max-1{background-color:#b6a2e6;border-color:#b6a2e6;fill:#b6a2e6}.dashboard .server-overview .color-max-2{background-color:#dfa0ea;border-color:#dfa0ea;fill:#dfa0ea}.dashboard .server-overview .color-max-3{background-color:#87f1ff;border-color:#87f1ff;fill:#87f1ff}.dashboard .server-overview .color-max-warning{background-color:#ffd699!important;border-color:#ffd699!important;fill:#ffd699!important}.dashboard .server-overview .color-max-critical{background-color:#fccbc7!important;border-color:#fccbc7!important;fill:#fccbc7!important}.dashboard .server-overview .max_tick_arc{stroke:#FFF!important}.dashboard .server-overview .concentricchart .bg-circle{background:#F9F9F9;fill:#F9F9F9;stroke:#FFF;stroke-width:1px}.dashboard .server-overview .concentricchart text{font-size:12px;font-family:Roboto,sans-serif}.dashboard .server-overview .concentricchart .value_group{fill:#fff}.dashboard .server-overview .concentricchart .legend_group rect{opacity:.8}.dashboard .server-overview svg.legend{height:auto}.dashboard .server-overview svg.legend text{font-size:12px;font-family:Roboto,sans-serif}.dashboard .server-overview svg.legend .hostName{font-size:16px}.dashboard .server-overview .minion-name{text-align:center;vertical-align:bottom;width:100%}.dashboard .server-overview .chart_area{width:325px;height:auto}.dashboard .groups{font-size:13px}.dashboard .groups .header{line-height:21px}.dashboard .groups .header a{padding-left:5px;padding-right:5px}.dashboard .groups .header .selector-area .filter-text{font-size:13px;margin-left:10px}.dashboard .groups .header .selector-area .cancel-button{width:18px;height:18px;padding:0}.dashboard .groups .header .selector-area .cancel-button:focus,.dashboard .groups .header .selector-area .cancel-button:hover{background-color:none!important}.dashboard .groups .header .selector-area .cancel-icon{width:15px;height:15px;fill:#777}.dashboard .groups .select-group-by{min-width:110px;margin-left:10px;margin-right:40px}.dashboard .groups .select-group-by .md-select-label{padding-top:6px;font-size:13px}.dashboard .groups .group-item{padding-top:20px}.dashboard .groups .group-item .filter-button{height:18px;width:18px}.dashboard .groups .group-item .filter-button .filter-icon{width:18px;height:18px}.dashboard .groups .icon-area{min-width:34px}.dashboard .groups .icon-area .group-icon{border-radius:21px;width:21px;height:21px}.dashboard .groups .group-main-area .subtype{line-height:21px}.dashboard .groups md-divider{margin-top:40px;margin-bottom:30px}.dashboard .groups .group-name{padding-top:10px}.dashboard .groups .selectFilter{padding-top:7px;margin-right:30px}.dashboard .groups .selectFilter .md-select-label{border-bottom:none!important;width:17px;min-width:17px;padding-right:0}.dashboard .groups md-select-menu{min-height:40px;max-height:40px}.dashboard .groups .group-link-area{padding-left:15px;padding-bottom:15px}.dashboard .groups .group-link-area button{line-height:12px}.dashboard .groups .group-type-circle{width:21px;height:21px}.dashboard .groups md-select{margin-top:0}.dashboard .detail{color:#222}.dashboard .detail .back{font-size:18px;line-height:27px;margin-bottom:30px}.dashboard .detail .heading{font-size:18px;line-height:21px;color:#222;margin-bottom:25px}.dashboard .detail .heading .label{color:#777}.dashboard .detail td.name{font-size:14px;color:#222;line-height:24px}.dashboard .detail td.value{margin-left:50px;font-size:14px;color:#888;line-height:24px}.dashboard .detail .containerTable td{padding-right:20px}.dashboard .align-top tbody{vertical-align:top}`) + +func wwwAppAssetsCssAppCssBytes() ([]byte, error) { + return _wwwAppAssetsCssAppCss, nil +} + +func wwwAppAssetsCssAppCss() (*asset, error) { + bytes, err := wwwAppAssetsCssAppCssBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/css/app.css", size: 39867, mode: os.FileMode(416), modTime: time.Unix(1435774726, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgDocarrowPng = []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00!\x00\x00\x00\"\b\x06\x00\x00\x00\xd1p\xb0\xc1\x00\x00\x00\x04sBIT\b\b\b\b|\bd\x88\x00\x00\x00\tpHYs\x00\x00\x15\xfd\x00\x00\x15\xfd\x01\xcdpQa\x00\x00\x00\x1ctEXtSoftware\x00Adobe Fireworks CS6\u8f32\x8c\x00\x00\x00\xefIDATX\x85\xd5\xd71\x0e\xc20\f\x05\u0406\xebq\x0568\x01\x8c\x1c\x80\xeep\x1aV\x18Y`\x05\xc1\x19\x10e\x81\xe13\x05UU\xab\xd8\xf1\xb7\xa2~\xc9K\x87\xf4)\x8e\xea4\x00\xa8JgR\x1aPUcB\x84\x10\xfeU\xef.X\xac\x8eh?KU2\x00\x92\x15\xb3\u065e\x01\x00\xaf\xe6\x83\xf9\xf2 >L\xc9\xf5\xa5\x88\b\x88\xd1@(\x88u}B_\xa4\x10\nb:\xdb\xe3\xf6xfCh\xed\xb0@h\b\v\x84\x8a\u0205\xd0\x119\x10\x17\x84\x16\xe2\x86\xd0@\\\x11R\x88;\x82\x01\xa1 \xac\x10\xea(\x1f\xf2&')\xab\x1d\xd7{\xff.4\xef\xaf\u007f;\xac\x003B\x02\x90\xec\xb6\xe9c%\x01\xb8!4\x00\x17\x84\x16@G\xe4\x00\xa8\x88\\\x00\ra\x01\xd0\x10C\x17]\t\x80\u068e\xee\x95_\n\xa0\"\xda\x10\r\x80\x8e\x88\x10\r@\x82\b\u0757\x94\xc8x\xfe\u02bd\xf3\x03\xba\v\x18\x94\x12\x8b\x872\x00\x00\x00\x00IEND\xaeB`\x82") + +func wwwAppAssetsImgDocarrowPngBytes() ([]byte, error) { + return _wwwAppAssetsImgDocarrowPng, nil +} + +func wwwAppAssetsImgDocarrowPng() (*asset, error) { + bytes, err := wwwAppAssetsImgDocarrowPngBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/docArrow.png", size: 373, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgIc_arrow_drop_down_24pxSvg = []byte(` + + + +`) + +func wwwAppAssetsImgIc_arrow_drop_down_24pxSvgBytes() ([]byte, error) { + return _wwwAppAssetsImgIc_arrow_drop_down_24pxSvg, nil +} + +func wwwAppAssetsImgIc_arrow_drop_down_24pxSvg() (*asset, error) { + bytes, err := wwwAppAssetsImgIc_arrow_drop_down_24pxSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/ic_arrow_drop_down_24px.svg", size: 166, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgIc_arrow_drop_up_24pxSvg = []byte(` + + + + + + + + + + + + + + + + + + + + + + + +`) + +func wwwAppAssetsImgIc_arrow_drop_up_24pxSvgBytes() ([]byte, error) { + return _wwwAppAssetsImgIc_arrow_drop_up_24pxSvg, nil +} + +func wwwAppAssetsImgIc_arrow_drop_up_24pxSvg() (*asset, error) { + bytes, err := wwwAppAssetsImgIc_arrow_drop_up_24pxSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/ic_arrow_drop_up_24px.svg", size: 795, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgIc_keyboard_arrow_left_24pxSvg = []byte(``) + +func wwwAppAssetsImgIc_keyboard_arrow_left_24pxSvgBytes() ([]byte, error) { + return _wwwAppAssetsImgIc_keyboard_arrow_left_24pxSvg, nil +} + +func wwwAppAssetsImgIc_keyboard_arrow_left_24pxSvg() (*asset, error) { + bytes, err := wwwAppAssetsImgIc_keyboard_arrow_left_24pxSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/ic_keyboard_arrow_left_24px.svg", size: 151, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgIc_keyboard_arrow_right_24pxSvg = []byte(``) + +func wwwAppAssetsImgIc_keyboard_arrow_right_24pxSvgBytes() ([]byte, error) { + return _wwwAppAssetsImgIc_keyboard_arrow_right_24pxSvg, nil +} + +func wwwAppAssetsImgIc_keyboard_arrow_right_24pxSvg() (*asset, error) { + bytes, err := wwwAppAssetsImgIc_keyboard_arrow_right_24pxSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/ic_keyboard_arrow_right_24px.svg", size: 149, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgIconsArrowBackPng = []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x80\x00\x00\x00\x80\b\x06\x00\x00\x00\xc3>a\xcb\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x020IDATx\x9c\xed\xddIn\x13q\x10F\xf1\u05c4\x05\xd7\xe0f\t\xc3\x166\x1c\xc0\x10\xa60\xc5!\x90\x046\x84Ab\xba\v7r`aY2\x02B\xecj\xf7_\xd5\xf5~\x92\x97\x91Jz_za\xc96H\x92$I\x92$I\x92$I\x92$I\x92$I\x92\xa4\xac\xb6Z\x1f\x90\xdc\x1ep\x15\xf8\xd1\xfa\x10\r\xab\x03\xde\x00?\x813\xe0V\xdbs4\xa4\xe5\xf8\x8b\x97#(\xa2\x03N\xf8=\xbe#(\xa2\x03\xa6\xfc=\xfe\xf2\bn\xb7:P\x9bs\x91\xf8\x8e`\xa4V\x89\xef\bF\xa6\x03\xf6Y-\xbe#\x18\x91\x87\xac\x17\u007f\xf1:\x1a\xfed\xf5e\xdd\xff\xfc\xc5k\xca\xfc\t\xa2\x84\xa2\xf1\x0f0~Z\xc6/,\x1a\xff%\xc6O\xeb\x05\xc6/+\x1a\xff\x10\xe3\xa7e\xfc\u008c_\xd8sb\xf1_a\xfc\xb4\x8c_X4\xfek\x8c\x9f\x96\xf1\v3~a\u03c8\xc5?\xc2\xf8i\x19\xbf0\xe3\x17\x16\x8d\u007f\x8c\xf1\xd3z\x8a\xf1\u02ca\xc6?\xc1\xf8iE\xe3\x9f\x02\x97\x86>Z\xfd0~aO0~Y\xd1\xf8\xef1~Z}\xc4\xf7;\x12\x922~a{\xc4\xe2\u007f\xc0\xf8i\x19\xbf\xb0h\xfc\x8f\x18?-\xe3\x17\xf6\x18\xe3\x975!\x16\xff\x13\xc6\xff\xa7\fo\x80\\\t\xfe\xfd\x8c\xf9\x10\x94\u0604\xd8S\xe0;py\xe8\xa3\u056f\t\x8e\xa0\xbc\t\xb1\x11|\xc3\x11\xa4w\x17GP\x9e#\x90#\x90#\x10p\x0fGP^t\x04_q\x04\xe99\x029\x02\xc1.\x8e\xa0\x8e\xa0x\xabsDG\xf0\x0eG\x90Z\xc7\xfc\xeb\xe2##\xf0\xe7\xe3G`\xdd'\xc1\f\xd8\x1e\xfe\\m\u00aa_Qc\xfc\x11\xba\xe8\bf\xc0N\xa3\x1b\xb5A\x1d0\xe5\xff\xf1\xaf\xb5:P\x9bw\xde\bf\xc0\xf5v\xa7i(\x1dp\xc0\x9f\xf1o\xb4 \xf7#U\x14\x00\x00\x02\xf4IDATx\xda|SkHSa\x18~\xce\u0659\xd3m\xce\xcd\xcdL\x99I\xf7D\xa2?QXYBBZ\x19i\xa8\xe4\r\xaa\x95\x18*\x95B\xa4T&\bQZa\x96\x81\x05I\x14R\x96\x81H\x17B\xbbHY\x16d\x14\xa5\xa2&\xe4e\xe66\xdd\u0699\xbb\x9c\xad\xef\x9cyL\xff\xf4\xfd\xfa.\xcf\xf3\xbc\xcfy\xde\xf7P\x98\xb7|>\x9f\xba\u007f\xd4}\xd1h\xf1&5<\xb5\xe9\xbf\x0e\xbb\x85\xfb\xd8%R\x18\x92\x94\xa3a!\x92\xe71Q\xd2R\x8a\xa2L\"\x87\x9a%\ua18c\x9e\x9a\x91I.\xf1z\x9b-\xb2\xf7\x97\x9f\xa8\x92\xd3\b`\x80I\xabW8\xaf\xd6Kq$Yi\x8c\xd0H\xda\u027e\x98\bMR&+\x1778\xeeyx\xa1y:b`\xcc3\xdf\x106\xc5\xc8\x10\xa9\x95\xa0\xb9\x93]p\xbfl1\x83\x924\xd5D\xb8F\x92N\xf5\x8d\xb8\x9e\x1c\xbclJb\x9d\xbe\x05\xa0\xd8h)\xf6m\x96#(\x80B\xd3k\x16=\x83\xae\x05\xef\xfc\xfd\x8d\"\xed\v\xc6\xe6\xf0\xe9Er}a(\xeaZm0\xecPb\xdd\xd2\x00\x98\xff\xf8\xadW\x1f\u0480wW\xd7j\xc5\xf1T\x15\xf2\xaf\x9a\xe0p\xf9`c\xbdzz\xcc\xcciE\xd5\xf2\xc6)\xd4\x15\x84B\xafc\xb0\xeb\xec\x04\xda{f\xd0\xf5\u0749\xdd\x15\x13$\x0f\n7\x8fiQF0\x1e\u038f\x1f1q:\xa6\xeb\x87S'\n\x90\x94A\x1c\x91\xe0(T\xe6\xaa!\x93R\xc2\xfe\\\x8e\x1a\xc1$\xd0q3G\xc4%0Z\xfc\n\xdd}N-\xf3y\xc0%\x15\x056\xac\n@\xdb\a\a\x1et\xdaq\xfb\x84\x0er\x19E:\x04\xc1\xae\xe1\x8a\t)\x1b\x83\x84`?\xf5\xfb\xf3\xe8\x19rS\u0328\x99\x9b\v\x86\xaf\x12\x1d\u01a0\"[\x8dp\xb5\x04\x8f\u07f1\u012e\x0f\xe9\xf1\n\x9c\xca\b\x81\xc7\xeb\u00f4\xdd;\x87\xff=\u0341\x11\xfb}\xfe\x80ZH\x96'\xde{i\x17B\xcb\u06ee\x10\x80w;\xec\xe0\ve%(0E\x82\xad1hp\xa9\xc5\xcag\xe0\x17\xb0\xb2^\x1c\xbdf\x16,7\x97\x87\xe1}\xaf\v\xfb\x13\xe4h\xebv\x80#\x0e\xb4*\x1a-oY\xe4'+q\xb8\xd62\x97\x01\xbf\x98\x8cx9{\xff\r+\xe7\x0f|;\xab\x9a\xa6Q[\xa0Ap\x10\x8d\xbc\xeaIp\xc4q\xd3I\x1d\u05af\x94\tU\xe7\x93\xf7\xc6\u025d\xd4O\xa3\xfb\xd9\xe9;S\x89\u07c6\u0774\xf8@D\x91\xb9U\x01\x9dJ\x02\x8a\f\xfb\x04\xf9V\xbe\x9d\u054f\xacs\xe45QRT\xe6\xa8;\x98\xe8ELjY\x9a\xac\xb7\xb0\x81\xd3[f\ag\xdb\xda@\xe4\x92\xea\xe2\x80\xf1\"\xb7\xc8\f\u0424\x84\xd7\xeb\xcf\xecLV\xc8\xe0\xf2\b&\x85&?\x04\xfb\xf1nmiU\xb6\xc2L\xd3\xff\x06j\xfeh\xf3\xad,i\xb0\bd\x1eS\x95\xab\xb4\x90\xb0w\x12\xae\x9d\x12A#&O\xe6\xab/3\xf5\u0126\x06\xffY\xc5{\x82m[b\x03\x8bVDJ\x1b\xf9\xf3_\x01\x06\x00\xf19?q\xaaE\xa0\xa4\x00\x00\x00\x00IEND\xaeB`\x82") + +func wwwAppAssetsImgIconsFaviconPngBytes() ([]byte, error) { + return _wwwAppAssetsImgIconsFaviconPng, nil +} + +func wwwAppAssetsImgIconsFaviconPng() (*asset, error) { + bytes, err := wwwAppAssetsImgIconsFaviconPngBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/icons/favicon.png", size: 1663, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgIconsIc_arrow_forward_24pxSvg = []byte(``) + +func wwwAppAssetsImgIconsIc_arrow_forward_24pxSvgBytes() ([]byte, error) { + return _wwwAppAssetsImgIconsIc_arrow_forward_24pxSvg, nil +} + +func wwwAppAssetsImgIconsIc_arrow_forward_24pxSvg() (*asset, error) { + bytes, err := wwwAppAssetsImgIconsIc_arrow_forward_24pxSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/icons/ic_arrow_forward_24px.svg", size: 158, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgIconsIc_cancel_24pxSvg = []byte(``) + +func wwwAppAssetsImgIconsIc_cancel_24pxSvgBytes() ([]byte, error) { + return _wwwAppAssetsImgIconsIc_cancel_24pxSvg, nil +} + +func wwwAppAssetsImgIconsIc_cancel_24pxSvg() (*asset, error) { + bytes, err := wwwAppAssetsImgIconsIc_cancel_24pxSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/icons/ic_cancel_24px.svg", size: 276, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgIconsIc_close_24pxSvg = []byte(``) + +func wwwAppAssetsImgIconsIc_close_24pxSvgBytes() ([]byte, error) { + return _wwwAppAssetsImgIconsIc_close_24pxSvg, nil +} + +func wwwAppAssetsImgIconsIc_close_24pxSvg() (*asset, error) { + bytes, err := wwwAppAssetsImgIconsIc_close_24pxSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/icons/ic_close_24px.svg", size: 202, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgIconsIc_menuSvg = []byte(` + + + + + + + + +`) + +func wwwAppAssetsImgIconsIc_menuSvgBytes() ([]byte, error) { + return _wwwAppAssetsImgIconsIc_menuSvg, nil +} + +func wwwAppAssetsImgIconsIc_menuSvg() (*asset, error) { + bytes, err := wwwAppAssetsImgIconsIc_menuSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/icons/ic_menu.svg", size: 791, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgIconsIc_menu_24pxSvg = []byte(` + + + + + + + + + + + + + + + + + + + + + +`) + +func wwwAppAssetsImgIconsIc_menu_24pxSvgBytes() ([]byte, error) { + return _wwwAppAssetsImgIconsIc_menu_24pxSvg, nil +} + +func wwwAppAssetsImgIconsIc_menu_24pxSvg() (*asset, error) { + bytes, err := wwwAppAssetsImgIconsIc_menu_24pxSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/icons/ic_menu_24px.svg", size: 841, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgIconsList_control_downPng = []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x000\x00\x00\x000\b\x03\x00\x00\x00`\xdc\t\xb5\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x00'PLTE\xa8\xa8\xa8\xfc\xfc\xfc\xc0\xc0\xc0\xb6\xb6\xb6\xf7\xf7\xf7\xaa\xaa\xaa\xf6\xf6\xf6\xe6\xe6\xe6\xe8\xe8\u8d75\xb5\xc3\xc3\xc3\xe5\xe5\xe5\xff\xff\xffZLu\xde\x00\x00\x00\rtRNS\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00=\xe8\"\x86\x00\x00\x00\x8bIDATx\xda\xec\xd4I\x12\x80 \fD\xd1\x04\x04\x9c\xee\u007f^\u02c1\xd2`\xb7\x96k\xc3\xd2\xfao\xa1\t\xca\xfc\xf1\x88\x03\a\xbf\x01}\xd7\x06\xb9\u007f\x02S\x8a\x8d\xc8C*\x1c\x94$bE\x1eD\xac0`\x14\xb1\"\xc7\xf5\xc9H\x81\x06+\xba\xad\x0f\xca\xdf\xc1\n\u0537_\xe9*`\u007f\x9b\xc3)p\u007f\x1f\\\x15\xa4\a\x93>\x04\xe9\xd1j\xec\x82\xf4p\x97\xaa@=^\xbe]\xc0\x9el\xeb*p\xcf\xd6[\x03\xe9\xe9}P\xf5\x9f\x80\x03\a\xafg\x11`\x00\xb0\xe4e\a\x17\x87\xea}\x00\x00\x00\x00IEND\xaeB`\x82") + +func wwwAppAssetsImgIconsList_control_downPngBytes() ([]byte, error) { + return _wwwAppAssetsImgIconsList_control_downPng, nil +} + +func wwwAppAssetsImgIconsList_control_downPng() (*asset, error) { + bytes, err := wwwAppAssetsImgIconsList_control_downPngBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/icons/list_control_down.png", size: 309, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsImgKubernetesSvg = []byte(` + + + + +`) + +func wwwAppAssetsImgKubernetesSvgBytes() ([]byte, error) { + return _wwwAppAssetsImgKubernetesSvg, nil +} + +func wwwAppAssetsImgKubernetesSvg() (*asset, error) { + bytes, err := wwwAppAssetsImgKubernetesSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/img/kubernetes.svg", size: 11663, mode: os.FileMode(416), modTime: time.Unix(1434082286, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsJsAppJs = []byte(`var componentNamespaces = ["kubernetesApp.components.dashboard"]; +// APP START +// **************************** +// /www/app/assets/app.js is autogenerated. Do not modify. +// Changes should be made in /master/modules/js or /master/components//js +// **************************** +// ----------------------------------- + +var app = angular.module('kubernetesApp', [ + 'ngRoute', + 'ngMaterial', + 'ngLodash', + 'door3.css', + 'kubernetesApp.config', + 'kubernetesApp.services', + 'angular.filter' +].concat(componentNamespaces)); + +app.factory('menu', [ + '$location', + '$rootScope', + 'sections', + '$route', + function($location, $rootScope, sections, $route) { + + var self; + + $rootScope.$on('$locationChangeSuccess', onLocationChange); + + return self = { + + sections: sections, + + setSections: function(_sections) { this.sections = _sections; }, + selectSection: function(section) { self.openedSection = section; }, + toggleSelectSection: function(section) { + self.openedSection = (self.openedSection === section ? null : section); + }, + isSectionSelected: function(section) { return self.openedSection === section; }, + selectPage: function(section, page) { + self.currentSection = section; + self.currentPage = page; + }, + isPageSelected: function(page) { return self.currentPage === page; } + }; + + function onLocationChange() { + var path = $route.current.originalPath; + + var matchPage = function(section, page) { + if (path === page.url || path === (page.url + '/')) { + self.selectSection(section); + self.selectPage(section, page); + } + }; + + sections.forEach(function(section) { + if (section.children) { + section.children.forEach(function(childSection) { + if (childSection.pages) { + childSection.pages.forEach(function(page) { matchPage(childSection, page); }); + } + }); + } else if (section.pages) { + section.pages.forEach(function(page) { matchPage(section, page); }); + } else if (section.type === 'link') { + matchPage(section, section); + } + }); + } + } +]); + +angular.module('kubernetesApp.config', []); +angular.module('kubernetesApp.services', ['kubernetesApp.config']); + +app.config([ + '$routeProvider', + function($routeProvider) { + $routeProvider.when("/404", {templateUrl: "views/partials/404.html"}) + // else 404 + .otherwise({redirectTo: "/404"}); + } +]) + .config([ + '$routeProvider', + 'manifestRoutes', + function($routeProvider, manifestRoutes) { + angular.forEach(manifestRoutes, function(r) { + var route = { + templateUrl: r.templateUrl + }; + if (r.controller) { + route.controller = r.controller; + } + if (r.css) { + route.css = r.css; + } + $routeProvider.when(r.url, route); + }); + } + ]); + +app.value("sections", [{"name":"Dashboard","url":"/dashboard","type":"link","templateUrl":"/components/dashboard/pages/home.html"},{"name":"Dashboard","type":"heading","children":[{"name":"Dashboard","type":"toggle","url":"/dashboard","templateUrl":"/components/dashboard/pages/home.html","pages":[{"name":"Pods","url":"/dashboard/pods","templateUrl":"/components/dashboard/views/listPods.html","type":"link"},{"name":"Pod Visualizer","url":"/dashboard/visualpods","templateUrl":"/components/dashboard/views/listPodsVisualizer.html","type":"link"},{"name":"Services","url":"/dashboard/services","templateUrl":"/components/dashboard/views/listServices.html","type":"link"},{"name":"Replication Controllers","url":"/dashboard/replicationcontrollers","templateUrl":"/components/dashboard/views/listReplicationControllers.html","type":"link"},{"name":"Events","url":"/dashboard/events","templateUrl":"/components/dashboard/views/listEvents.html","type":"link"},{"name":"Nodes","url":"/dashboard/nodes","templateUrl":"/components/dashboard/views/listMinions.html","type":"link"},{"name":"Replication Controller","url":"/dashboard/replicationcontrollers/:replicationControllerId","templateUrl":"/components/dashboard/views/replication.html","type":"link"},{"name":"Service","url":"/dashboard/services/:serviceId","templateUrl":"/components/dashboard/views/service.html","type":"link"},{"name": "Node","url": "/dashboard/nodes/:nodeId","templateUrl": "/components/dashboard/views/node.html","type": "link"},{"name":"Explore","url":"/dashboard/groups/:grouping*?/selector/:selector*?","templateUrl":"/components/dashboard/views/groups.html","type":"link"},{"name":"Pod","url":"/dashboard/pods/:podId","templateUrl":"/components/dashboard/views/pod.html","type":"link"}]}]},{"name":"Graph","url":"/graph","type":"link","templateUrl":"/components/graph/pages/home.html"},{"name":"Graph","url":"/graph/inspect","type":"link","templateUrl":"/components/graph/pages/inspect.html","css":"/components/graph/css/show-details-table.css"},{"name":"Graph","type":"heading","children":[{"name":"Graph","type":"toggle","url":"/graph","templateUrl":"/components/graph/pages/home.html","pages":[{"name":"Test","url":"/graph/test","type":"link","templateUrl":"/components/graph/pages/home.html"}]}]}]); + +app.directive('includeReplace', + function() { + 'use strict'; + return { + require: 'ngInclude', + restrict: 'A', /* optional */ + link: function(scope, el, attrs) { el.replaceWith(el.children()); } + }; + }) + .directive('compile', + ["$compile", function($compile) { + 'use strict'; + return function(scope, element, attrs) { + scope.$watch(function(scope) { return scope.$eval(attrs.compile); }, + function(value) { + element.html(value); + $compile(element.contents())(scope); + }); + }; + }]) + .directive("kubernetesUiMenu", + function() { + 'use strict'; + return { + templateUrl: "views/partials/kubernetes-ui-menu.tmpl.html" + }; + }) + .directive('menuToggle', function() { + 'use strict'; + return { + scope: {section: '='}, + templateUrl: 'views/partials/menu-toggle.tmpl.html', + link: function($scope, $element) { + var controller = $element.parent().controller(); + + $scope.isOpen = function() { return controller.isOpen($scope.section); }; + $scope.toggle = function() { controller.toggleOpen($scope.section); }; + + var parentNode = $element[0].parentNode.parentNode.parentNode; + if (parentNode.classList.contains('parent-list-item')) { + var heading = parentNode.querySelector('h2'); + $element[0].firstChild.setAttribute('aria-describedby', heading.id); + } + } + }; + }); + +app.filter('startFrom', + function() { + 'use strict'; + return function(input, start) { return input.slice(start); }; + }) + .filter('nospace', function() { + 'use strict'; + return function(value) { return (!value) ? '' : value.replace(/ /g, ''); }; + }); + +app.run(['$route', angular.noop]) + .run(["lodash", function(lodash) { + // Alias lodash + window['_'] = lodash; + }]); + +app.service('SidebarService', [ + '$rootScope', + function($rootScope) { + var service = this; + service.sidebarItems = []; + + service.clearSidebarItems = function() { service.sidebarItems = []; }; + + service.renderSidebar = function() { + var _entries = ''; + service.sidebarItems.forEach(function(entry) { _entries += entry.Html; }); + + if (_entries) { + $rootScope.sidenavLeft = '
' + _entries + '
'; + } + }; + + service.addSidebarItem = function(item) { + + service.sidebarItems.push(item); + + service.sidebarItems.sort(function(a, b) { return (a.order > b.order) ? 1 : ((b.order > a.order) ? -1 : 0); }); + }; + } +]); + + +app.value("tabs", [{"component":"dashboard","title":"Dashboard"}]); +app.constant("manifestRoutes", [{"description":"Dashboard visualization.","url":"/dashboard/","templateUrl":"components/dashboard/pages/home.html"},{"description":"Pods","url":"/dashboard/pods","templateUrl":"components/dashboard/views/listPods.html"},{"description":"Pod Visualizer","url":"/dashboard/visualpods","templateUrl":"components/dashboard/views/listPodsVisualizer.html"},{"description":"Services","url":"/dashboard/services","templateUrl":"components/dashboard/views/listServices.html"},{"description":"Replication Controllers","url":"/dashboard/replicationcontrollers","templateUrl":"components/dashboard/views/listReplicationControllers.html"},{"description":"Events","url":"/dashboard/events","templateUrl":"components/dashboard/views/listEvents.html"},{"description":"Nodes","url":"/dashboard/nodes","templateUrl":"components/dashboard/views/listMinions.html"},{"description":"Replication Controller","url":"/dashboard/replicationcontrollers/:replicationControllerId","templateUrl":"components/dashboard/views/replication.html"},{"description":"Service","url":"/dashboard/services/:serviceId","templateUrl":"components/dashboard/views/service.html"},{"description":"Node","url":"/dashboard/nodes/:nodeId","templateUrl":"components/dashboard/views/node.html"},{"description":"Explore","url":"/dashboard/groups/:grouping*?/selector/:selector*?","templateUrl":"components/dashboard/views/groups.html"},{"description":"Pod","url":"/dashboard/pods/:podId","templateUrl":"components/dashboard/views/pod.html"}]); + +angular.module("kubernetesApp.config", []) + +.constant("ENV", { + "/": { + "k8sApiServer": "/api/v1", + "k8sDataServer": "", + "k8sDataPollMinIntervalSec": 10, + "k8sDataPollMaxIntervalSec": 120, + "k8sDataPollErrorThreshold": 5, + "cAdvisorProxy": "", + "cAdvisorPort": "4194" + } +}) + +.constant("ngConstant", true) + +; +/**========================================================= + * Module: config.js + * App routes and resources configuration + =========================================================*/ +/**========================================================= + * Module: constants.js + * Define constants to inject across the application + =========================================================*/ +/**========================================================= + * Module: home-page.js + * Page Controller + =========================================================*/ + +app.controller('PageCtrl', [ + '$scope', + '$timeout', + '$mdSidenav', + 'menu', + '$rootScope', + function($scope, $timeout, $mdSidenav, menu, $rootScope) { + $scope.menu = menu; + + $scope.path = path; + $scope.goHome = goHome; + $scope.openMenu = openMenu; + $rootScope.openMenu = openMenu; + $scope.closeMenu = closeMenu; + $scope.isSectionSelected = isSectionSelected; + + $rootScope.$on('$locationChangeSuccess', openPage); + + // Methods used by menuLink and menuToggle directives + this.isOpen = isOpen; + this.isSelected = isSelected; + this.toggleOpen = toggleOpen; + this.shouldLockOpen = shouldLockOpen; + $scope.toggleKubernetesUiMenu = toggleKubernetesUiMenu; + + var mainContentArea = document.querySelector("[role='main']"); + var kubernetesUiMenu = document.querySelector("[role='kubernetes-ui-menu']"); + + // ********************* + // Internal methods + // ********************* + + var _t = false; + + $scope.showKubernetesUiMenu = false; + + function shouldLockOpen() { + return _t; + } + + function toggleKubernetesUiMenu() { + $scope.showKubernetesUiMenu = !$scope.showKubernetesUiMenu; + } + + function closeMenu() { + $timeout(function() { + $mdSidenav('left').close(); + }); + } + + function openMenu() { + $timeout(function() { + _t = !$mdSidenav('left').isOpen(); + $mdSidenav('left').toggle(); + }); + } + + function path() { + return $location.path(); + } + + function goHome($event) { + menu.selectPage(null, null); + $location.path( '/' ); + } + + function openPage() { + $scope.closeMenu(); + mainContentArea.focus(); + } + + function isSelected(page) { + return menu.isPageSelected(page); + } + + function isSectionSelected(section) { + var selected = false; + var openedSection = menu.openedSection; + if(openedSection === section){ + selected = true; + } + else if(section.children) { + section.children.forEach(function(childSection) { + if(childSection === openedSection){ + selected = true; + } + }); + } + return selected; + } + + function isOpen(section) { + return menu.isSectionSelected(section); + } + + function toggleOpen(section) { + menu.toggleSelectSection(section); + } + + } +]).filter('humanizeDoc', function() { + return function(doc) { + if (!doc) return; + if (doc.type === 'directive') { + return doc.name.replace(/([A-Z])/g, function($1) { + return '-'+$1.toLowerCase(); + }); + } + return doc.label || doc.name; + }; }); + +/**========================================================= + * Module: main.js + * Main Application Controller + =========================================================*/ +/**========================================================= + * Module: tabs-global.js + * Page Controller + =========================================================*/ + +app.controller('TabCtrl', [ + '$scope', + '$location', + 'tabs', + function($scope, $location, tabs) { + $scope.tabs = tabs; + + $scope.switchTab = function(index) { + var location_path = $location.path(); + var tab = tabs[index]; + + if (tab) { + var path = '/%s'.format(tab.component); + if (location_path.indexOf(path) == -1) { + $location.path(path); + } + } + }; + } +]); + +/**========================================================= + * Module: sidebar.js + * Wraps the sidebar and handles collapsed state + =========================================================*/ +(function() { + "use strict"; + + angular.module('kubernetesApp.services') + .service('cAdvisorService', ["$http", "$q", "ENV", function($http, $q, ENV) { + var _baseUrl = function(minionIp) { + var minionPort = ENV['/']['cAdvisorPort'] || "8081"; + var proxy = ENV['/']['cAdvisorProxy'] || "/api/v1/proxy/nodes/"; + + return proxy + minionIp + ':' + minionPort + '/api/v1.0/'; + }; + + this.getMachineInfo = getMachineInfo; + + function getMachineInfo(minionIp) { + var fullUrl = _baseUrl(minionIp) + 'machine'; + var deferred = $q.defer(); + + // hack + $http.get(fullUrl).success(function(data) { + deferred.resolve(data); + }).error(function(data, status) { deferred.reject('There was an error') }); + return deferred.promise; + } + + this.getContainerInfo = getContainerInfo; + // containerId optional + function getContainerInfo(minionIp, containerId) { + containerId = (typeof containerId === "undefined") ? "/" : containerId; + + var fullUrl = _baseUrl(minionIp) + 'containers' + containerId; + var deferred = $q.defer(); + + var request = { + "num_stats": 10, + "num_samples": 0 + }; + + $http.post(fullUrl, request) + .success(function(data) { deferred.resolve(data); }) + .error(function() { deferred.reject('There was an error') }); + return deferred.promise; + } + + this.getDataForMinion = function(minionIp) { + var machineData, containerData; + var deferred = $q.defer(); + + var p = $q.all([getMachineInfo(minionIp), getContainerInfo(minionIp)]) + .then( + function(dataArray) { + machineData = dataArray[0]; + containerData = dataArray[1]; + + var memoryData = parseMemory(machineData, containerData); + var cpuData = parseCpu(machineData, containerData); + var fsData = parseFilesystems(machineData, containerData); + deferred.resolve({ + memoryData: memoryData, + cpuData: cpuData, + filesystemData: fsData, + machineData: machineData, + containerData: containerData + }); + + }, + function(errorData) { deferred.reject(errorData); }); + + return deferred.promise; + }; + + // Utils to process cadvisor data + function humanize(num, size, units) { + var unit; + for (unit = units.pop(); units.length && num >= size; unit = units.pop()) { + num /= size; + } + return [num, unit]; + } + + // Following the IEC naming convention + function humanizeIEC(num) { + var ret = humanize(num, 1024, ["TiB", "GiB", "MiB", "KiB", "Bytes"]); + return ret[0].toFixed(2) + " " + ret[1]; + } + + // Following the Metric naming convention + function humanizeMetric(num) { + var ret = humanize(num, 1000, ["TB", "GB", "MB", "KB", "Bytes"]); + return ret[0].toFixed(2) + " " + ret[1]; + } + + function hasResource(stats, resource) { return stats.stats.length > 0 && stats.stats[0][resource]; } + + // Gets the length of the interval in nanoseconds. + function getInterval(current, previous) { + var cur = new Date(current); + var prev = new Date(previous); + + // ms -> ns. + return (cur.getTime() - prev.getTime()) * 1000000; + } + + function parseCpu(machineInfo, containerInfo) { + var cur = containerInfo.stats[containerInfo.stats.length - 1]; + var results = []; + + var cpuUsage = 0; + if (containerInfo.spec.has_cpu && containerInfo.stats.length >= 2) { + var prev = containerInfo.stats[containerInfo.stats.length - 2]; + var rawUsage = cur.cpu.usage.total - prev.cpu.usage.total; + var intervalInNs = getInterval(cur.timestamp, prev.timestamp); + + // Convert to millicores and take the percentage + cpuUsage = Math.round(((rawUsage / intervalInNs) / machineInfo.num_cores) * 100); + if (cpuUsage > 100) { + cpuUsage = 100; + } + } + + return { + cpuPercentUsage: cpuUsage + }; + } + + function parseFilesystems(machineInfo, containerInfo) { + var cur = containerInfo.stats[containerInfo.stats.length - 1]; + if (!cur.filesystem) { + return; + } + + var filesystemData = []; + for (var i = 0; i < cur.filesystem.length; i++) { + var data = cur.filesystem[i]; + var totalUsage = Math.floor((data.usage * 100.0) / data.capacity); + + var f = { + device: data.device, + filesystemNumber: i + 1, + usage: data.usage, + usageDescription: humanizeMetric(data.usage), + capacity: data.capacity, + capacityDescription: humanizeMetric(data.capacity), + totalUsage: Math.floor((data.usage * 100.0) / data.capacity) + }; + + filesystemData.push(f); + } + return filesystemData; + } + + var oneMegabyte = 1024 * 1024; + var oneGigabyte = 1024 * oneMegabyte; + + function parseMemory(machineInfo, containerInfo) { + if (containerInfo.spec.has_memory && !hasResource(containerInfo, "memory")) { + return; + } + + // var titles = ["Time", "Total", "Hot"]; + var data = []; + for (var i = 0; i < containerInfo.stats.length; i++) { + var cur = containerInfo.stats[i]; + + var elements = []; + elements.push(cur.timestamp); + elements.push(cur.memory.usage / oneMegabyte); + elements.push(cur.memory.working_set / oneMegabyte); + data.push(elements); + } + + // Get the memory limit, saturate to the machine size. + var memory_limit = machineInfo.memory_capacity; + if (containerInfo.spec.memory.limit && (containerInfo.spec.memory.limit < memory_limit)) { + memory_limit = containerInfo.spec.memory.limit; + } + + var cur = containerInfo.stats[containerInfo.stats.length - 1]; + + var r = { + current: { + memoryUsage: cur.memory.usage, + workingMemoryUsage: cur.memory.working_set, + memoryLimit: memory_limit, + memoryUsageDescription: humanizeMetric(cur.memory.usage), + workingMemoryUsageDescription: humanizeMetric(cur.memory.working_set), + memoryLimitDescription: humanizeMetric(memory_limit) + }, + historical: data + }; + + return r; + } + }]); +})(); + +app.provider('k8sApi', + function() { + + var urlBase = ''; + var _namespace = 'default'; + + this.setUrlBase = function(value) { urlBase = value; }; + + this.setNamespace = function(value) { _namespace = value; }; + this.getNamespace = function() { return _namespace; }; + + var _get = function($http, baseUrl, query) { + var _fullUrl = baseUrl; + + if (query !== undefined) { + _fullUrl += '/' + query; + } + + return $http.get(_fullUrl); + }; + + this.$get = ["$http", "$q", function($http, $q) { + var api = {}; + + api.getUrlBase = function() { return urlBase + '/namespaces/' + _namespace; }; + + api.getPods = function(query) { return _get($http, api.getUrlBase() + '/pods', query); }; + + api.getNodes = function(query) { return _get($http, urlBase + '/nodes', query); }; + + api.getMinions = api.getNodes; + + api.getServices = function(query) { return _get($http, api.getUrlBase() + '/services', query); }; + + api.getReplicationControllers = function(query) { + return _get($http, api.getUrlBase() + '/replicationcontrollers', query) + }; + + api.getEvents = function(query) { return _get($http, api.getUrlBase() + '/events', query); }; + + return api; + }]; + }) + .config(["k8sApiProvider", "ENV", function(k8sApiProvider, ENV) { + if (ENV && ENV['/'] && ENV['/']['k8sApiServer']) { + k8sApiProvider.setUrlBase(ENV['/']['k8sApiServer']); + } + }]); + +(function() { + "use strict"; + + var pollK8sDataServiceProvider = function PollK8sDataServiceProvider(_) { + // A set of configuration controlling the polling behavior. + // Their values should be configured in the application before + // creating the service instance. + + var useSampleData = false; + this.setUseSampleData = function(value) { useSampleData = value; }; + + var sampleDataFiles = ["shared/assets/sampleData1.json"]; + this.setSampleDataFiles = function(value) { sampleDataFiles = value; }; + + var dataServer = "http://localhost:5555/cluster"; + this.setDataServer = function(value) { dataServer = value; }; + + var pollMinIntervalSec = 10; + this.setPollMinIntervalSec = function(value) { pollMinIntervalSec = value; }; + + var pollMaxIntervalSec = 120; + this.setPollMaxIntervalSec = function(value) { pollMaxIntervalSec = value; }; + + var pollErrorThreshold = 5; + this.setPollErrorThreshold = function(value) { pollErrorThreshold = value; }; + + this.$get = function($http, $timeout) { + // Now the sequenceNumber will be used for debugging and verification purposes. + var k8sdatamodel = { + "data": undefined, + "sequenceNumber": 0, + "useSampleData": useSampleData + }; + var pollingError = 0; + var promise = undefined; + + // Implement fibonacci back off when the service is down. + var pollInterval = pollMinIntervalSec; + var pollIncrement = pollInterval; + + // Reset polling interval. + var resetCounters = function() { + pollInterval = pollMinIntervalSec; + pollIncrement = pollInterval; + }; + + // Bump error count and polling interval. + var bumpCounters = function() { + // Bump the error count. + pollingError++; + + // TODO: maybe display an error in the UI to the end user. + if (pollingError % pollErrorThreshold === 0) { + console.log("Error: " + pollingError + " consecutive polling errors for " + dataServer + "."); + } + + // Bump the polling interval. + var oldIncrement = pollIncrement; + pollIncrement = pollInterval; + pollInterval += oldIncrement; + + // Reset when limit reached. + if (pollInterval > pollMaxIntervalSec) { + resetCounters(); + } + }; + + var updateModel = function(newModel) { + var dedupe = function(dataModel) { + if (dataModel.resources) { + dataModel.resources = _.uniq(dataModel.resources, function(resource) { return resource.id; }); + } + + if (dataModel.relations) { + dataModel.relations = + _.uniq(dataModel.relations, function(relation) { return relation.source + relation.target; }); + } + }; + + dedupe(newModel); + + var newModelString = JSON.stringify(newModel); + var oldModelString = ""; + if (k8sdatamodel.data) { + oldModelString = JSON.stringify(k8sdatamodel.data); + } + + if (newModelString !== oldModelString) { + k8sdatamodel.data = newModel; + k8sdatamodel.sequenceNumber++; + } + + pollingError = 0; + resetCounters(); + }; + + var nextSampleDataFile = 0; + var getSampleDataFile = function() { + var result = ""; + if (sampleDataFiles.length > 0) { + result = sampleDataFiles[nextSampleDataFile % sampleDataFiles.length]; + ++nextSampleDataFile; + } + + return result; + }; + + var pollOnce = function(scope, repeat) { + var dataSource = (k8sdatamodel.useSampleData) ? getSampleDataFile() : dataServer; + $.getJSON(dataSource) + .done(function(newModel, jqxhr, textStatus) { + if (newModel && newModel.success) { + delete newModel.success; // Remove success indicator. + delete newModel.timestamp; // Remove changing timestamp. + updateModel(newModel); + scope.$apply(); + promise = repeat ? $timeout(function() { pollOnce(scope, true); }, pollInterval * 1000) : undefined; + return; + } + + bumpCounters(); + promise = repeat ? $timeout(function() { pollOnce(scope, true); }, pollInterval * 1000) : undefined; + }) + .fail(function(jqxhr, textStatus, error) { + bumpCounters(); + promise = repeat ? $timeout(function() { pollOnce(scope, true); }, pollInterval * 1000) : undefined; + }); + }; + + var isPolling = function() { return promise ? true : false; }; + + var start = function(scope) { + // If polling has already started, then calling start() again would + // just reset the counters and polling interval, but it will not + // start a new thread polling in parallel to the existing polling + // thread. + resetCounters(); + if (!promise) { + k8sdatamodel.data = undefined; + pollOnce(scope, true); + } + }; + + var stop = function() { + if (promise) { + $timeout.cancel(promise); + promise = undefined; + } + }; + + var refresh = function(scope) { + stop(scope); + resetCounters(); + k8sdatamodel.data = undefined; + pollOnce(scope, false); + }; + + return { + "k8sdatamodel": k8sdatamodel, + "isPolling": isPolling, + "refresh": refresh, + "start": start, + "stop": stop + }; + }; + }; + + angular.module("kubernetesApp.services") + .provider("pollK8sDataService", ["lodash", pollK8sDataServiceProvider]) + .config(["pollK8sDataServiceProvider", "ENV", function(pollK8sDataServiceProvider, ENV) { + if (ENV && ENV['/']) { + if (ENV['/']['k8sDataServer']) { + pollK8sDataServiceProvider.setDataServer(ENV['/']['k8sDataServer']); + } + if (ENV['/']['k8sDataPollIntervalMinSec']) { + pollK8sDataServiceProvider.setPollIntervalSec(ENV['/']['k8sDataPollIntervalMinSec']); + } + if (ENV['/']['k8sDataPollIntervalMaxSec']) { + pollK8sDataServiceProvider.setPollIntervalSec(ENV['/']['k8sDataPollIntervalMaxSec']); + } + if (ENV['/']['k8sDataPollErrorThreshold']) { + pollK8sDataServiceProvider.setPollErrorThreshold(ENV['/']['k8sDataPollErrorThreshold']); + } + } + }]); + +}()); + +/**========================================================= + * Module: toggle-state.js + * Services to share toggle state functionality + =========================================================*/ + + +app.controller('cAdvisorController', [ + '$scope', + '$routeParams', + 'k8sApi', + 'lodash', + 'cAdvisorService', + '$q', + '$interval', + function($scope, $routeParams, k8sApi, lodash, cAdvisorService, $q, $interval) { + $scope.k8sApi = k8sApi; + + $scope.activeMinionDataById = {}; + $scope.maxDataByById = {}; + + $scope.getData = function() { + $scope.loading = true; + + k8sApi.getMinions().success(angular.bind(this, function(res) { + $scope.minions = res; + // console.log(res); + var promises = lodash.map(res.items, function(m) { return cAdvisorService.getDataForMinion(m.metadata.name); }); + + $q.all(promises).then( + function(dataArray) { + lodash.each(dataArray, function(data, i) { + var m = res.items[i]; + + var maxData = maxMemCpuInfo(m.metadata.name, data.memoryData, data.cpuData, data.filesystemData); + + // console.log("maxData", maxData); + var hostname = ""; + if(m.status.addresses) + hostname = m.status.addresses[0].address; + + $scope.activeMinionDataById[m.metadata.name] = + transformMemCpuInfo(data.memoryData, data.cpuData, data.filesystemData, maxData, hostname); + }); + + }, + function(errorData) { + // console.log("Error: " + errorData); + $scope.loading = false; + }); + + $scope.loading = false; + })).error(angular.bind(this, this.handleError)); + }; + + function handleError(data, status, headers, config) { + // console.log("Error (" + status + "): " + data); + $scope.loading = false; + }; + + // d3 + function getColorForIndex(i, percentage) { + // var colors = ['red', 'blue', 'yellow', 'pink', 'purple', 'green', 'orange']; + // return colors[i]; + var c = "color-" + (i + 1); + if (percentage && percentage >= 90) + c = c + ' color-critical'; + else if (percentage && percentage >= 80) + c = c + ' color-warning'; + + return c; + } + + function getMaxColorForIndex(i, percentage) { + // var colors = ['red', 'blue', 'yellow', 'pink', 'purple', 'green', 'orange']; + // return colors[i]; + var c = "color-max-" + (i + 1); + if (percentage && percentage >= 90) + c = c + ' color-max-critical'; + else if (percentage && percentage >= 80) + c = c + ' color-max-warning'; + + return c; + } + + function maxMemCpuInfo(mId, mem, cpu, fsDataArray) { + if ($scope.maxDataByById[mId] === undefined) $scope.maxDataByById[mId] = {}; + + var currentMem = mem.current; + var currentCpu = cpu; + + var items = []; + + if ($scope.maxDataByById[mId]['cpu'] === undefined || + $scope.maxDataByById[mId]['cpu'] < currentCpu.cpuPercentUsage) { + // console.log("New max cpu " + mId, $scope.maxDataByById[mId].cpu, currentCpu.cpuPercentUsage); + $scope.maxDataByById[mId]['cpu'] = currentCpu.cpuPercentUsage; + } + items.push({ + maxValue: $scope.maxDataByById[mId]['cpu'], + maxTickClassNames: getColorForIndex(0, $scope.maxDataByById[mId]['cpu']), + maxClassNames: getMaxColorForIndex(0, $scope.maxDataByById[mId]['cpu']) + }); + + var memPercentage = Math.floor((currentMem.memoryUsage * 100.0) / currentMem.memoryLimit); + if ($scope.maxDataByById[mId]['mem'] === undefined || $scope.maxDataByById[mId]['mem'] < memPercentage) + $scope.maxDataByById[mId]['mem'] = memPercentage; + items.push({ + maxValue: $scope.maxDataByById[mId]['mem'], + maxTickClassNames: getColorForIndex(1, $scope.maxDataByById[mId]['mem']), + maxClassNames: getMaxColorForIndex(1, $scope.maxDataByById[mId]['mem']) + }); + + for (var i = 0; i < fsDataArray.length; i++) { + var f = fsDataArray[i]; + var fid = 'FS #' + f.filesystemNumber; + if ($scope.maxDataByById[mId][fid] === undefined || $scope.maxDataByById[mId][fid] < f.totalUsage) + $scope.maxDataByById[mId][fid] = f.totalUsage; + items.push({ + maxValue: $scope.maxDataByById[mId][fid], + maxTickClassNames: getColorForIndex(2 + i, $scope.maxDataByById[mId][fid]), + maxClassNames: getMaxColorForIndex(2 + i, $scope.maxDataByById[mId][fid]) + }); + } + + // console.log("Max Data is now " + mId, $scope.maxDataByById[mId]); + return items; + } + + function transformMemCpuInfo(mem, cpu, fsDataArray, maxData, hostName) { + var currentMem = mem.current; + var currentCpu = cpu; + + var items = []; + + items.push({ + label: 'CPU', + stats: currentCpu.cpuPercentUsage + '%', + value: currentCpu.cpuPercentUsage, + classNames: getColorForIndex(0, currentCpu.cpuPercentUsage), + maxData: maxData[0], + hostName: hostName + }); + + var memPercentage = Math.floor((currentMem.memoryUsage * 100.0) / currentMem.memoryLimit); + items.push({ + label: 'Memory', + stats: currentMem.memoryUsageDescription + ' / ' + currentMem.memoryLimitDescription, + value: memPercentage, + classNames: getColorForIndex(1, memPercentage), + maxData: maxData[1], + hostName: hostName + }); + + for (var i = 0; i < fsDataArray.length; i++) { + var f = fsDataArray[i]; + + items.push({ + label: 'Filesystem #' + f.filesystemNumber, + stats: f.usageDescription + ' / ' + f.capacityDescription, + value: f.totalUsage, + classNames: getColorForIndex(2 + i, f.totalUsage), + maxData: maxData[2 + i], + hostName: hostName + + }); + } + + var a = []; + var segments = { + segments: items + }; + a.push(segments); + return a; + }; + + // end d3 + var promise = $interval($scope.getData, 3000); + + // Cancel interval on page changes + $scope.$on('$destroy', function() { + if (angular.isDefined(promise)) { + $interval.cancel(promise); + promise = undefined; + } + }); + + $scope.getData(); + + } +]); + +/**========================================================= + * Module: Dashboard + * Visualizer for clusters + =========================================================*/ + +app.controller('DashboardCtrl', ['$scope', function($scope) {}]); + +/**========================================================= + * Module: Group + * Visualizer for groups + =========================================================*/ + +app.controller('GroupCtrl', [ + '$scope', + '$route', + '$interval', + '$routeParams', + 'k8sApi', + '$rootScope', + '$location', + 'lodash', + function($scope, $route, $interval, $routeParams, k8sApi, $rootScope, $location, _) { + 'use strict'; + $scope.doTheBack = function() { window.history.back(); }; + + $scope.capitalize = function(s) { return _.capitalize(s); }; + + $rootScope.doTheBack = $scope.doTheBack; + + $scope.resetGroupLayout = function(group) { delete group.settings; }; + + $scope.handlePath = function(path) { + var parts = path.split("/"); + // split leaves an empty string at the beginning. + parts = parts.slice(1); + + if (parts.length === 0) { + return; + } + this.handleGroups(parts.slice(1)); + }; + + $scope.getState = function(obj) { return Object.keys(obj)[0]; }; + + $scope.clearSelector = function(grouping) { $location.path("/dashboard/groups/" + grouping + "/selector/"); }; + + $scope.changeGroupBy = function() { + var grouping = encodeURIComponent($scope.selectedGroupBy); + + var s = _.clone($location.search()); + if ($scope.routeParams.grouping != grouping) + $location.path("/dashboard/groups/" + grouping + "/selector/").search(s); + }; + + $scope.createBarrier = function(count, callback) { + var barrier = count; + var barrierFunction = angular.bind(this, function(data) { + // JavaScript is single threaded so this is safe. + barrier--; + if (barrier === 0) { + if (callback) { + callback(); + } + } + }); + return barrierFunction; + }; + + $scope.handleGroups = function(parts, selector) { + $scope.groupBy = parts; + $scope.loading = true; + $scope.selector = selector; + $scope.selectorName = decodeURIComponent(selector); + var args = []; + var type = ""; + var selectedHost = ""; + if (selector && selector.length > 0) { + $scope.selectorPieces = selector.split(","); + var labels = []; + var fields = []; + for (var i = 0; i < $scope.selectorPieces.length; i++) { + var piece = decodeURIComponent($scope.selectorPieces[i]); + if (piece[0] == '$') { + fields.push(piece.slice(2)); + } else { + if (piece.indexOf("type=") === 0) { + var labelParts = piece.split("="); + if (labelParts.length > 1) { + type = labelParts[1]; + } + } + else if (piece.indexOf("host=") === 0){ + var labelParts = piece.split("="); + if (labelParts.length > 1) { + selectedHost = labelParts[1]; + } + } + else { + labels.push(piece); + } + } + } + + if (labels.length > 0) { + args.push("labelSelector=" + encodeURI(labels.join(","))); + } + if (fields.length > 0) { + args.push("fields=" + encodeURI(fields.join(","))); + } + } + var query = "?" + args.join("&"); + var list = []; + var count = type.length > 0 ? 1 : 3; + + $scope.selectedGroupByName = decodeURIComponent($routeParams.grouping) + + var barrier = $scope.createBarrier(count, function() { + $scope.groups = $scope.groupData(list, 0); + $scope.loading = false; + $scope.groupByOptions = buildGroupByOptions(); + $scope.selectedGroupBy = $routeParams.grouping; + }); + + if (type === "" || type == "pod") { + k8sApi.getPods(query).success(function(data) { + $scope.addLabel("type", "pod", data.items); + for (var i = 0; data.items && i < data.items.length; ++i) { + data.items[i].metadata.labels.host = data.items[i].spec.nodeName; + if(selectedHost.length == 0 || selectedHost == data.items[i].metadata.labels.host) + list.push(data.items[i]); + } + barrier(); + }).error($scope.handleError); + } + if (type === "" || type == "service") { + k8sApi.getServices(query).success(function(data) { + $scope.addLabel("type", "service", data.items); + for (var i = 0; data.items && i < data.items.length; ++i) { + list.push(data.items[i]); + } + barrier(); + }).error($scope.handleError); + } + if (type === "" || type == "replicationController") { + k8sApi.getReplicationControllers(query).success(angular.bind(this, function(data) { + $scope.addLabel("type", "replicationController", data.items); + for (var i = 0; data.items && i < data.items.length; ++i) { + list.push(data.items[i]); + } + barrier(); + })).error($scope.handleError); + } + }; + + $scope.addLabel = function(key, value, items) { + if (!items) { + return; + } + for (var i = 0; i < items.length; i++) { + if (!items[i].metadata.labels) { + items[i].metadata.labels = {}; + } + items[i].metadata.labels[key] = value; + } + }; + + $scope.groupData = function(items, index) { + var result = { + "items": {}, + "kind": "grouping" + }; + for (var i = 0; i < items.length; i++) { + key = items[i].metadata.labels[decodeURIComponent($scope.groupBy[index])]; + if (!key) { + key = ""; + } + var list = result.items[key]; + if (!list) { + list = []; + result.items[key] = list; + } + list.push(items[i]); + } + + if (index + 1 < $scope.groupBy.length) { + for (var key in result.items) { + result.items[key] = $scope.groupData(result.items[key], index + 1); + } + } + return result; + }; + $scope.getGroupColor = function(type) { + if (type === 'pod') { + return '#6193F0'; + } else if (type === 'replicationController') { + return '#E008FE'; + } else if (type === 'service') { + return '#7C43FF'; + } + }; + + var groups = $routeParams.grouping; + if (!groups) { + groups = ''; + } + + $scope.routeParams = $routeParams; + $scope.route = $route; + + $scope.handleGroups(groups.split('/'), $routeParams.selector); + + $scope.handleError = function(data, status, headers, config) { + console.log("Error (" + status + "): " + data); + $scope_.loading = false; + }; + + function getDefaultGroupByOptions() { return [{name: 'Type', value: 'type'}, {name: 'Name', value: 'name'}]; } + + function buildGroupByOptions() { + var g = $scope.groups; + var options = getDefaultGroupByOptions(); + var newOptions = _.map(g.items, function(vals) { return _.map(vals, function(v) { return _.keys(v.metadata.labels); }); }); + newOptions = + _.reject(_.uniq(_.flattenDeep(newOptions)), function(o) { return o == 'name' || o == 'type' || o == ""; }); + newOptions = _.map(newOptions, function(o) { + return { + name: o, + value: o + }; + }); + + options = options.concat(newOptions); + return options; + } + + $scope.changeFilterBy = function(selector) { + var grouping = $scope.selectedGroupBy; + + var s = _.clone($location.search()); + if ($scope.routeParams.selector != selector) + $location.path("/dashboard/groups/" + $scope.routeParams.grouping + "/selector/" + selector).search(s); + }; + } +]); + +/**========================================================= + * Module: Header + * Visualizer for clusters + =========================================================*/ + +angular.module('kubernetesApp.components.dashboard', []) + .controller('HeaderCtrl', [ + '$scope', + '$location', + function($scope, $location) { + 'use strict'; + $scope.$watch('Pages', function(newValue, oldValue) { + if (typeof newValue !== 'undefined') { + $location.path(newValue); + } + }); + + $scope.subPages = [ + {category: 'dashboard', name: 'Explore', value: '/dashboard/groups/type/selector/'}, + {category: 'dashboard', name: 'Pods', value: '/dashboard/pods'}, + {category: 'dashboard', name: 'Nodes', value: '/dashboard/nodes'}, + {category: 'dashboard', name: 'Replication Controllers', value: '/dashboard/replicationcontrollers'}, + {category: 'dashboard', name: 'Services', value: '/dashboard/services'}, + {category: 'dashboard', name: 'Events', value: '/dashboard/events'} + ]; + } + ]); + +/**========================================================= + * Module: List Events + * Visualizer list events + =========================================================*/ + +app.controller('ListEventsCtrl', [ + '$scope', + '$routeParams', + 'k8sApi', + '$location', + '$filter', + function($scope, $routeParams, k8sApi, $location, $filter) { + 'use strict'; + $scope.getData = getData; + $scope.loading = true; + $scope.k8sApi = k8sApi; + $scope.pods = null; + $scope.groupedPods = null; + $scope.serverView = false; + + $scope.headers = [ + {name: 'First Seen', field: 'firstSeen'}, + {name: 'Last Seen', field: 'lastSeen'}, + {name: 'Count', field: 'count'}, + {name: 'Name', field: 'name'}, + {name: 'Kind', field: 'kind'}, + {name: 'SubObject', field: 'subObject'}, + {name: 'Reason', field: 'reason'}, + {name: 'Source', field: 'source'}, + {name: 'Message', field: 'message'} + ]; + + + $scope.sortable = ['firstSeen', 'lastSeen', 'count', 'name', 'kind', 'subObject', 'reason', 'source']; + $scope.count = 10; + function handleError(data, status, headers, config) { + console.log("Error (" + status + "): " + data); + $scope.loading = false; + } + + $scope.content = []; + + function getData() { + $scope.loading = true; + k8sApi.getEvents().success(function(data) { + $scope.loading = false; + + var _fixComma = function(str) { + if (str.substring(0, 1) == ',') { + return str.substring(1); + } else { + return str; + } + }; + + data.items.forEach(function(event) { + var _sources = ''; + if (event.source) { + _sources = event.source.component + ' ' + event.source.host; + } + + + $scope.content.push({ + firstSeen: $filter('date')(event.firstTimestamp, 'medium'), + lastSeen: $filter('date')(event.lastTimestamp, 'medium'), + count: event.count, + name: event.involvedObject.name, + kind: event.involvedObject.kind, + subObject: event.involvedObject.fieldPath, + reason: event.reason, + source: _sources, + message: event.message + }); + + + + }); + + }).error($scope.handleError); + } + + getData(); + + } +]); + +/**========================================================= + * Module: Minions + * Visualizer for minions + =========================================================*/ + +app.controller('ListMinionsCtrl', [ + '$scope', + '$routeParams', + 'k8sApi', + '$location', + function($scope, $routeParams, k8sApi, $location) { + 'use strict'; + $scope.getData = getData; + $scope.loading = true; + $scope.k8sApi = k8sApi; + $scope.pods = null; + $scope.groupedPods = null; + $scope.serverView = false; + + $scope.headers = [{name: 'Name', field: 'name'}, {name: 'Addresses', field: 'addresses'}, {name: 'Status', field: 'status'}]; + + $scope.custom = { + name: '', + status: 'grey', + ip: 'grey' + }; + $scope.sortable = ['name', 'status', 'ip']; + $scope.thumbs = 'thumb'; + $scope.count = 10; + + $scope.go = function(d) { $location.path('/dashboard/nodes/' + d.name); }; + + + function handleError(data, status, headers, config) { + console.log("Error (" + status + "): " + data); + $scope.loading = false; + } + + $scope.content = []; + + function getData() { + $scope.loading = true; + k8sApi.getMinions().success(function(data) { + $scope.loading = false; + + var _fixComma = function(str) { + if (str.substring(0, 1) == ',') { + return str.substring(1); + } else { + return str; + } + }; + + data.items.forEach(function(minion) { + var _statusType = ''; + + if (minion.status.conditions) { + Object.keys(minion.status.conditions) + .forEach(function(key) { _statusType += minion.status.conditions[key].type; }); + } + + + $scope.content.push({name: minion.metadata.name, addresses: _.map(minion.status.addresses, function(a) { return a.address }).join(', '), status: _statusType}); + }); + + }).error($scope.handleError); + } + + getData(); + + } +]); + + + +app.controller('ListPodsCtrl', [ + '$scope', + '$routeParams', + 'k8sApi', + 'lodash', + '$location', + function($scope, $routeParams, k8sApi, lodash, $location) { + var _ = lodash; + $scope.getData = getData; + $scope.loading = true; + $scope.k8sApi = k8sApi; + $scope.pods = null; + $scope.groupedPods = null; + $scope.serverView = false; + + $scope.headers = [ + {name: 'Pod', field: 'pod'}, + {name: 'IP', field: 'ip'}, + {name: 'Status', field: 'status'}, + {name: 'Containers', field: 'containers'}, + {name: 'Images', field: 'images'}, + {name: 'Host', field: 'host'}, + {name: 'Labels', field: 'labels'} + ]; + + $scope.custom = { + pod: '', + ip: 'grey', + containers: 'grey', + images: 'grey', + host: 'grey', + labels: 'grey', + status: 'grey' + }; + $scope.sortable = ['pod', 'ip', 'status']; + $scope.count = 10; + + $scope.go = function(data) { $location.path('/dashboard/pods/' + data.pod); }; + + var orderedPodNames = []; + + function handleError(data, status, headers, config) { + console.log("Error (" + status + "): " + data); + $scope.loading = false; + }; + + function getPodName(pod) { return _.has(pod.metadata.labels, 'name') ? pod.metadata.labels.name : pod.metadata.name; } + + $scope.content = []; + + function getData() { + $scope.loading = true; + k8sApi.getPods().success(angular.bind(this, function(data) { + $scope.loading = false; + + var _fixComma = function(str) { + if (str.substring(0, 1) == ',') { + return str.substring(1); + } else { + return str; + } + }; + + data.items.forEach(function(pod) { + var _containers = '', _images = '', _labels = '', _uses = ''; + + if (pod.spec) { + Object.keys(pod.spec.containers) + .forEach(function(key) { + _containers += ', ' + pod.spec.containers[key].name; + _images += ', ' + pod.spec.containers[key].image; + }); + } + + if (pod.metadata.labels) { + Object.keys(pod.metadata.labels) + .forEach(function(key) { + if (key == 'name') { + _labels += ', ' + pod.metadata.labels[key]; + } + if (key == 'uses') { + _uses += ', ' + pod.metadata.labels[key]; + } + }); + } + + $scope.content.push({ + pod: pod.metadata.name, + ip: pod.status.podIP, + containers: _fixComma(_containers), + images: _fixComma(_images), + host: pod.spec.host, + labels: _fixComma(_labels) + ':' + _fixComma(_uses), + status: pod.status.phase + }); + + }); + + })).error(angular.bind(this, handleError)); + }; + + $scope.getPodRestarts = function(pod) { + var r = null; + var container = _.first(pod.spec.containers); + if (container) r = pod.status.containerStatuses[container.name].restartCount; + return r; + }; + + $scope.otherLabels = function(labels) { return _.omit(labels, 'name') }; + + $scope.podStatusClass = function(pod) { + + var s = pod.status.phase.toLowerCase(); + + if (s == 'running' || s == 'succeeded') + return null; + else + return "status-" + s; + }; + + $scope.podIndexFromName = function(pod) { + var name = getPodName(pod); + return _.indexOf(orderedPodNames, name) + 1; + }; + + getData(); + + } +]); + +/**========================================================= + * Module: Replication Controllers + * Visualizer for replication controllers + =========================================================*/ + +app.controller('ListReplicationControllersCtrl', [ + '$scope', + '$routeParams', + 'k8sApi', + '$location', + function($scope, $routeParams, k8sApi, $location) { + 'use strict'; + $scope.getData = getData; + $scope.loading = true; + $scope.k8sApi = k8sApi; + $scope.pods = null; + $scope.groupedPods = null; + $scope.serverView = false; + + $scope.headers = [ + {name: 'Controller', field: 'controller'}, + {name: 'Containers', field: 'containers'}, + {name: 'Images', field: 'images'}, + {name: 'Selector', field: 'selector'}, + {name: 'Replicas', field: 'replicas'} + ]; + + $scope.custom = { + controller: '', + containers: 'grey', + images: 'grey', + selector: 'grey', + replicas: 'grey' + }; + $scope.sortable = ['controller', 'containers', 'images']; + $scope.thumbs = 'thumb'; + $scope.count = 10; + + $scope.go = function(data) { $location.path('/dashboard/replicationcontrollers/' + data.controller); }; + + function handleError(data, status, headers, config) { + console.log("Error (" + status + "): " + data); + $scope.loading = false; + } + + $scope.content = []; + + function getData() { + $scope.loading = true; + k8sApi.getReplicationControllers().success(function(data) { + $scope.loading = false; + + var _fixComma = function(str) { + if (str.substring(0, 1) == ',') { + return str.substring(1); + } else { + return str; + } + }; + + data.items.forEach(function(replicationController) { + + var _name = '', _image = ''; + + if (replicationController.spec.template.spec.containers) { + Object.keys(replicationController.spec.template.spec.containers) + .forEach(function(key) { + _name += replicationController.spec.template.spec.containers[key].name; + _image += replicationController.spec.template.spec.containers[key].image; + }); + } + + var _selectors = ''; + + if (replicationController.spec.selector) { + _selectors = _.map(replicationController.spec.selector, function(v, k) { return k + '=' + v }).join(', '); + } + + $scope.content.push({ + controller: replicationController.metadata.name, + containers: _name, + images: _image, + selector: _selectors, + replicas: replicationController.status.replicas + }); + + }); + + }).error($scope.handleError); + } + + getData(); + + } +]); + +/**========================================================= + * Module: Services + * Visualizer for services + =========================================================*/ + +app.controller('ListServicesCtrl', [ + '$scope', + '$interval', + '$routeParams', + 'k8sApi', + '$rootScope', + '$location', + function($scope, $interval, $routeParams, k8sApi, $rootScope, $location) { + 'use strict'; + $scope.doTheBack = function() { window.history.back(); }; + + $scope.headers = [ + {name: 'Name', field: 'name'}, + {name: 'Labels', field: 'labels'}, + {name: 'Selector', field: 'selector'}, + {name: 'IP', field: 'ip'}, + {name: 'Ports', field: 'port'} + ]; + + $scope.custom = { + name: '', + ip: 'grey', + selector: 'grey', + port: 'grey', + labels: 'grey' + }; + $scope.sortable = ['name', 'ip', 'port']; + $scope.count = 10; + + $scope.go = function(data) { $location.path('/dashboard/services/' + data.name); }; + + $scope.content = []; + + $rootScope.doTheBack = $scope.doTheBack; + + $scope.handleError = function(data, status, headers, config) { + console.log("Error (" + status + "): " + data); + $scope_.loading = false; + }; + + $scope.getData = function() { + $scope.loading = true; + k8sApi.getServices().success(angular.bind(this, function(data) { + $scope.services = data; + $scope.loading = false; + + var _fixComma = function(str) { + if (str.substring(0, 1) == ',') { + return str.substring(1); + } else { + return str; + } + }; + + var addLabel = function(str, label) { + if (str) { + str = label + str; + } + return str; + }; + + if (data.items.constructor === Array) { + data.items.forEach(function(service) { + + var _labels = ''; + + if (service.metadata.labels) { + _labels = _.map(service.metadata.labels, function(v, k) { return k + '=' + v }).join(', '); + } + + var _selectors = ''; + + if (service.spec.selector) { + _selectors = _.map(service.spec.selector, function(v, k) { return k + '=' + v }).join(', '); + } + + var _ports = ''; + + if (service.spec.ports) { + _ports = _.map(service.spec.ports, function(p) { + var n = ''; + if(p.name) + n = p.name + ': '; + n = n + p.port; + return n; + }).join(', '); + } + + $scope.content.push({ + name: service.metadata.name, + ip: service.spec.portalIP, + port: _ports, + selector: _selectors, + labels: _labels + }); + }); + } + })).error($scope.handleError); + }; + + $scope.getData(); + } +]); + +/**========================================================= + * Module: Nodes + * Visualizer for nodes + =========================================================*/ + +app.controller('NodeCtrl', [ + '$scope', + '$interval', + '$routeParams', + 'k8sApi', + '$rootScope', + function($scope, $interval, $routeParams, k8sApi, $rootScope) { + 'use strict'; + $scope.doTheBack = function() { window.history.back(); }; + + $rootScope.doTheBack = $scope.doTheBack; + + $scope.handleError = function(data, status, headers, config) { + console.log("Error (" + status + "): " + data); + $scope_.loading = false; + }; + + $scope.handleNode = function(nodeId) { + $scope.loading = true; + k8sApi.getNodes(nodeId).success(angular.bind(this, function(data) { + $scope.node = data; + $scope.loading = false; + })).error($scope.handleError); + }; + + $scope.handleNode($routeParams.nodeId); + } +]); + +/**========================================================= + * Module: Pods + * Visualizer for pods + =========================================================*/ + +app.controller('PodCtrl', [ + '$scope', + '$interval', + '$routeParams', + 'k8sApi', + '$rootScope', + function($scope, $interval, $routeParams, k8sApi, $rootScope) { + 'use strict'; + $scope.doTheBack = function() { window.history.back(); }; + + $rootScope.doTheBack = $scope.doTheBack; + + $scope.handleError = function(data, status, headers, config) { + console.log("Error (" + status + "): " + data); + $scope_.loading = false; + }; + + $scope.handlePod = function(podId) { + $scope.loading = true; + k8sApi.getPods(podId).success(angular.bind(this, function(data) { + $scope.pod = data; + $scope.loading = false; + })).error($scope.handleError); + }; + + $scope.handlePod($routeParams.podId); + } +]); + +/**========================================================= + * Module: Replication + * Visualizer for replication controllers + =========================================================*/ + +function ReplicationController() { +} + +ReplicationController.prototype.getData = function(dataId) { + this.scope.loading = true; + this.k8sApi.getReplicationControllers(dataId).success(angular.bind(this, function(data) { + this.scope.replicationController = data; + this.scope.loading = false; + })).error(angular.bind(this, this.handleError)); +}; + +ReplicationController.prototype.handleError = function(data, status, headers, config) { + console.log("Error (" + status + "): " + data); + this.scope.loading = false; +}; + +app.controller('ReplicationControllerCtrl', [ + '$scope', + '$routeParams', + 'k8sApi', + function($scope, $routeParams, k8sApi) { + $scope.controller = new ReplicationController(); + $scope.controller.k8sApi = k8sApi; + $scope.controller.scope = $scope; + $scope.controller.getData($routeParams.replicationControllerId); + + $scope.doTheBack = function() { window.history.back(); }; + $scope.getSelectorUrlFragment = function(sel){ return _.map(sel, function(v, k) { return k + '=' + v }).join(','); }; + + } +]); + +/**========================================================= + * Module: Services + * Visualizer for services + =========================================================*/ + +function ServiceController() { +} + +ServiceController.prototype.getData = function(dataId) { + this.scope.loading = true; + this.k8sApi.getServices(dataId).success(angular.bind(this, function(data) { + this.scope.service = data; + this.scope.loading = false; + })).error(angular.bind(this, this.handleError)); +}; + +ServiceController.prototype.handleError = function(data, status, headers, config) { + console.log("Error (" + status + "): " + data); + this.scope.loading = false; +}; + +app.controller('ServiceCtrl', [ + '$scope', + '$routeParams', + 'k8sApi', + '$location', + function($scope, $routeParams, k8sApi, $location) { + $scope.controller = new ServiceController(); + $scope.controller.k8sApi = k8sApi; + $scope.controller.scope = $scope; + $scope.controller.getData($routeParams.serviceId); + + $scope.doTheBack = function() { window.history.back(); }; + $scope.go = function(d) { $location.path('/dashboard/services/' + d.metadata.name); } + $scope.getSelectorUrlFragment = function(sel){ return _.map(sel, function(v, k) { return k + '=' + v }).join(','); }; + + } +]); + +(function() { + 'use strict'; + + angular.module('kubernetesApp.components.dashboard') + .directive('d3MinionBarGauge', [ + 'd3DashboardService', + function(d3DashboardService) { + + return { + restrict: 'E', + scope: { + data: '=', + thickness: '@', + graphWidth: '@', + graphHeight: '@' + + }, + link: function(scope, element, attrs) { + + var draw = function(d3) { + var svg = d3.select("svg.chart"); + var legendSvg = d3.select("svg.legend"); + window.onresize = function() { return scope.$apply(); }; + + scope.$watch(function() { return angular.element(window)[0].innerWidth; }, + function() { return scope.render(scope.data); }); + + scope.$watch('data', function(newVals, oldVals) { + return initOrUpdate(newVals, oldVals); + + }, true); + + function initOrUpdate(newVals, oldVals) { + if (oldVals === null || oldVals === undefined) { + return scope.render(newVals); + } else { + return update(oldVals, newVals); + } + } + + var textOffset = 10; + var el = null; + var radius = 100; + var oldData = []; + + function init(options) { + var clone = options.data; + var preparedData = setData(clone); + setup(preparedData, options.width, options.height); + } + + function setup(data, w, h) { + svg = d3.select(element[0]).append("svg").attr("width", "100%"); + + legendSvg = d3.select(element[0]).append("svg").attr("width", "100%"); + + var chart = svg.attr("class", "chart") + .attr("width", w) + .attr("height", h - 25) + .append("svg:g") + .attr("class", "concentricchart") + .attr("transform", "translate(" + ((w / 2)) + "," + h / 4 + ")"); + + var legend = legendSvg.attr("class", "legend").attr("width", w); + + radius = Math.min(w, h) / 2; + + var hostName = legendSvg.append("text") + .attr("class", "hostName") + .attr("transform", "translate(" + ((w - 120) / 2) + "," + 15 + ")"); + + var label_legend_area = legendSvg.append("svg:g") + .attr("class", "label_legend_area") + .attr("transform", "translate(" + ((w - 215) / 2) + "," + 35 + ")"); + + var legend_group = label_legend_area.append("svg:g").attr("class", "legend_group"); + + var label_group = label_legend_area.append("svg:g") + .attr("class", "label_group") + .attr("transform", "translate(" + 25 + "," + 11 + ")"); + + var stats_group = label_legend_area.append("svg:g") + .attr("class", "stats_group") + .attr("transform", "translate(" + 115 + "," + 11 + ")"); + + var path_group = chart.append("svg:g") + .attr("class", "path_group") + .attr("transform", "translate(0," + (h / 4) + ")"); + var value_group = chart.append("svg:g") + .attr("class", "value_group") + .attr("transform", "translate(" + -(w * 0.205) + "," + -(h * 0.10) + ")"); + generateArcs(chart, data); + } + + function update(_oldData, _newData) { + if (_newData === undefined || _newData === null) { + return; + } + + var clone = jQuery.extend(true, {}, _newData); + var cloneOld = jQuery.extend(true, {}, _oldData); + var preparedData = setData(clone); + oldData = setData(cloneOld); + animate(preparedData); + } + + function animate(data) { generateArcs(null, data); } + + function setData(data) { + var diameter = 2 * Math.PI * radius; + var localData = []; + + $.each(data[0].segments, function(ri, value) { + + function calcAngles(v) { + var segmentValueSum = 200; + if (v > segmentValueSum) { + v = segmentValueSum; + } + + var segmentValue = v; + var fraction = segmentValue / segmentValueSum; + var arcBatchLength = fraction * 4 * Math.PI; + var arcPartition = arcBatchLength; + var startAngle = Math.PI * 2; + var endAngle = startAngle + arcPartition; + + return { + startAngle: startAngle, + endAngle: endAngle + }; + } + + var valueData = calcAngles(value.value); + data[0].segments[ri].startAngle = valueData.startAngle; + data[0].segments[ri].endAngle = valueData.endAngle; + + var maxData = value.maxData; + var maxTickData = calcAngles(maxData.maxValue + 0.2); + data[0].segments[ri].maxTickStartAngle = maxTickData.startAngle; + data[0].segments[ri].maxTickEndAngle = maxTickData.endAngle; + + var maxArcData = calcAngles(maxData.maxValue); + data[0].segments[ri].maxArcStartAngle = maxArcData.startAngle; + data[0].segments[ri].maxArcEndAngle = maxArcData.endAngle; + + data[0].segments[ri].index = ri; + }); + localData.push(data[0].segments); + return localData[0]; + } + + function generateArcs(_svg, data) { + var chart = svg; + var transitionTime = 750; + $.each(data, function(index, value) { + if (oldData[index] !== undefined) { + data[index].previousEndAngle = oldData[index].endAngle; + } else { + data[index].previousEndAngle = 0; + } + }); + var thickness = parseInt(scope.thickness, 10); + var ir = (parseInt(scope.graphWidth, 10) / 3); + var path_group = svg.select('.path_group'); + var arc_group = path_group.selectAll(".arc_group").data(data); + var arcEnter = arc_group.enter().append("g").attr("class", "arc_group"); + + arcEnter.append("path").attr("class", "bg-circle").attr("d", getBackgroundArc(thickness, ir)); + + arcEnter.append("path") + .attr("class", function(d, i) { return 'max_tick_arc ' + d.maxData.maxTickClassNames; }); + + arcEnter.append("path") + .attr("class", function(d, i) { return 'max_bg_arc ' + d.maxData.maxClassNames; }); + + arcEnter.append("path").attr("class", function(d, i) { return 'value_arc ' + d.classNames; }); + + var max_tick_arc = arc_group.select(".max_tick_arc"); + + max_tick_arc.transition() + .attr("class", function(d, i) { return 'max_tick_arc ' + d.maxData.maxTickClassNames; }) + .attr("d", function(d) { + var arc = maxArc(thickness, ir); + arc.startAngle(d.maxTickStartAngle); + arc.endAngle(d.maxTickEndAngle); + return arc(d); + }); + + var max_bg_arc = arc_group.select(".max_bg_arc"); + + max_bg_arc.transition() + .attr("class", function(d, i) { return 'max_bg_arc ' + d.maxData.maxClassNames; }) + .attr("d", function(d) { + var arc = maxArc(thickness, ir); + arc.startAngle(d.maxArcStartAngle); + arc.endAngle(d.maxArcEndAngle); + return arc(d); + }); + + var value_arc = arc_group.select(".value_arc"); + + value_arc.transition().ease("exp").attr("class", function(d, i) { + return 'value_arc ' + d.classNames; + }).duration(transitionTime).attrTween("d", function(d) { return arcTween(d, thickness, ir); }); + + arc_group.exit() + .select(".value_arc") + .transition() + .ease("exp") + .duration(transitionTime) + .attrTween("d", function(d) { return arcTween(d, thickness, ir); }) + .remove(); + + drawLabels(chart, data, ir, thickness); + buildLegend(chart, data); + } + + function arcTween(b, thickness, ir) { + var prev = JSON.parse(JSON.stringify(b)); + prev.endAngle = b.previousEndAngle; + var i = d3.interpolate(prev, b); + return function(t) { return getArc(thickness, ir)(i(t)); }; + } + + function maxArc(thickness, ir) { + var arc = d3.svg.arc().innerRadius(function(d) { + return getRadiusRing(ir, d.index); + }).outerRadius(function(d) { return getRadiusRing(ir + thickness, d.index); }); + return arc; + } + + function drawLabels(chart, data, ir, thickness) { + svg.select('.value_group').selectAll("*").remove(); + var counts = data.length; + var value_group = chart.select('.value_group'); + var valueLabels = value_group.selectAll("text.value").data(data); + valueLabels.enter() + .append("svg:text") + .attr("class", "value") + .attr("dx", function(d, i) { return 68; }) + .attr("dy", function(d, i) { return (thickness + 3) * i; }) + .attr("text-anchor", function(d) { return "start"; }) + .text(function(d) { return d.value; }); + valueLabels.transition().duration(300).attrTween( + "d", function(d) { return arcTween(d, thickness, ir); }); + valueLabels.exit().remove(); + } + + function buildLegend(chart, data) { + var svg = legendSvg; + svg.select('.label_group').selectAll("*").remove(); + svg.select('.legend_group').selectAll("*").remove(); + svg.select('.stats_group').selectAll("*").remove(); + + var host_name = svg.select('.hostName'); + var label_group = svg.select('.label_group'); + var stats_group = svg.select('.stats_group'); + + host_name.text(data[0].hostName); + + host_name = svg.selectAll("text.hostName").data(data); + + host_name.attr("text-anchor", function(d) { return "start"; }) + .text(function(d) { return d.hostName; }); + host_name.exit().remove(); + + var labels = label_group.selectAll("text.labels").data(data); + labels.enter() + .append("svg:text") + .attr("class", "labels") + .attr("dy", function(d, i) { return 19 * i; }) + .attr("text-anchor", function(d) { return "start"; }) + .text(function(d) { return d.label; }); + labels.exit().remove(); + + var stats = stats_group.selectAll("text.stats").data(data); + stats.enter() + .append("svg:text") + .attr("class", "stats") + .attr("dy", function(d, i) { return 19 * i; }) + .attr("text-anchor", function(d) { return "start"; }) + .text(function(d) { return d.stats; }); + stats.exit().remove(); + + var legend_group = svg.select('.legend_group'); + var legend = legend_group.selectAll("rect").data(data); + legend.enter() + .append("svg:rect") + .attr("x", 2) + .attr("y", function(d, i) { return 19 * i; }) + .attr("width", 13) + .attr("height", 13) + .attr("class", function(d, i) { return "rect " + d.classNames; }); + + legend.exit().remove(); + } + + function getRadiusRing(ir, i) { return ir - (i * 20); } + + function getArc(thickness, ir) { + var arc = d3.svg.arc() + .innerRadius(function(d) { return getRadiusRing(ir, d.index); }) + .outerRadius(function(d) { return getRadiusRing(ir + thickness, d.index); }) + .startAngle(function(d, i) { return d.startAngle; }) + .endAngle(function(d, i) { return d.endAngle; }); + return arc; + } + + function getBackgroundArc(thickness, ir) { + var arc = d3.svg.arc() + .innerRadius(function(d) { return getRadiusRing(ir, d.index); }) + .outerRadius(function(d) { return getRadiusRing(ir + thickness, d.index); }) + .startAngle(0) + .endAngle(function() { return 2 * Math.PI; }); + return arc; + } + + scope.render = function(data) { + if (data === undefined || data === null) { + return; + } + + d3.select(element[0]).select("svg.chart").remove(); + d3.select(element[0]).select("svg.legend").remove(); + + var graph = $(element[0]); + var w = scope.graphWidth; + var h = scope.graphHeight; + + var options = { + data: data, + width: w, + height: h + }; + + init(options); + }; + }; + d3DashboardService.d3().then(draw); + } + }; + } + ]); +}()); + +(function() { + 'use strict'; + + angular.module('kubernetesApp.components.dashboard') + .directive( + 'dashboardHeader', + function() { + 'use strict'; + return { + restrict: 'A', + replace: true, + scope: {user: '='}, + templateUrl: "components/dashboard/pages/header.html", + controller: [ + '$scope', + '$filter', + '$location', + 'menu', + '$rootScope', + function($scope, $filter, $location, menu, $rootScope) { + $scope.menu = menu; + $scope.$watch('page', function(newValue, oldValue) { + if (typeof newValue !== 'undefined') { + $location.path(newValue); + } + }); + + $scope.subpages = [ + { + category: 'dashboard', + name: 'Explore', + value: '/dashboard/groups/type/selector/', + id: 'groupsView' + }, + {category: 'dashboard', name: 'Pods', value: '/dashboard/pods', id: 'podsView'}, + {category: 'dashboard', name: 'Nodes', value: '/dashboard/nodes', id: 'minionsView'}, + { + category: 'dashboard', + name: 'Replication Controllers', + value: '/dashboard/replicationcontrollers', + id: 'rcView' + }, + {category: 'dashboard', name: 'Services', value: '/dashboard/services', id: 'servicesView'}, + {category: 'dashboard', name: 'Events', value: '/dashboard/events', id: 'eventsView'}, + ]; + } + ] + }; + }) + .directive('dashboardFooter', + function() { + 'use strict'; + return { + restrict: 'A', + replace: true, + templateUrl: "components/dashboard/pages/footer.html", + controller: ['$scope', '$filter', function($scope, $filter) {}] + }; + }) + .directive('mdTable', function() { + 'use strict'; + return { + restrict: 'E', + scope: { + headers: '=', + content: '=', + sortable: '=', + filters: '=', + customClass: '=customClass', + thumbs: '=', + count: '=', + doSelect: '&onSelect' + }, + transclude: true, + controller: ["$scope", "$filter", "$window", "$location", function($scope, $filter, $window, $location) { + var orderBy = $filter('orderBy'); + $scope.currentPage = 0; + $scope.nbOfPages = function() { return Math.ceil($scope.content.length / $scope.count); }; + $scope.handleSort = function(field) { + if ($scope.sortable.indexOf(field) > -1) { + return true; + } else { + return false; + } + }; + $scope.order = function(predicate, reverse) { + $scope.content = orderBy($scope.content, predicate, reverse); + $scope.predicate = predicate; + }; + $scope.order($scope.sortable[0], false); + $scope.getNumber = function(num) { return new Array(num); }; + $scope.goToPage = function(page) { $scope.currentPage = page; }; + $scope.showMore = function() { return angular.isDefined($scope.moreClick);} + }], + templateUrl: 'views/partials/md-table.tmpl.html' + }; + }); + +}()); + +angular.module('kubernetesApp.components.dashboard') + .factory('d3DashboardService', [ + '$document', + '$q', + '$rootScope', + function($document, $q, $rootScope) { + var d = $q.defer(); + function onScriptLoad() { + // Load client in the browser + $rootScope.$apply(function() { d.resolve(window.d3); }); + } + // Create a script tag with d3 as the source + // and call our onScriptLoad callback when it + // has been loaded + var scriptTag = $document[0].createElement('script'); + scriptTag.type = 'text/javascript'; + scriptTag.async = true; + scriptTag.src = 'vendor/d3/d3.min.js'; + scriptTag.onreadystatechange = function() { + if (this.readyState == 'complete') onScriptLoad(); + }; + scriptTag.onload = onScriptLoad; + + var s = $document[0].getElementsByTagName('body')[0]; + s.appendChild(scriptTag); + + return { + d3: function() { return d.promise; } + }; + } + ]); + +(function() { + 'use strict'; + + angular.module('pods', []).service('podService', PodDataService); + + /** + * Pod DataService + * Mock async data service. + * + * @returns {{loadAll: Function}} + * @constructor + */ + function PodDataService($q) { + var pods = { + "kind": "Pod", + "apiVersion": "v1", + "metadata": { + "name": "redis-master-c0r1n", + "generateName": "redis-master-", + "namespace": "default", + "selfLink": "/api/v1/namespaces/default/pods/redis-master-c0r1n", + "uid": "f12ddfaf-ff77-11e4-8f2d-080027213276", + "resourceVersion": "39", + "creationTimestamp": "2015-05-21T05:12:14Z", + "labels": { + "name": "redis-master" + }, + "annotations": { + "kubernetes.io/created-by": "{\"kind\":\"SerializedReference\",\"apiVersion\":\"v1\",\"reference\":{\"kind\":\"ReplicationController\",\"namespace\":\"default\",\"name\":\"redis-master\",\"uid\":\"f12969e0-ff77-11e4-8f2d-080027213276\",\"apiVersion\":\"v1\",\"resourceVersion\":\"26\"}}" + } + }, + "spec": { + "volumes": [ + { + "name": "default-token-zb4rq", + "secret": { + "secretName": "default-token-zb4rq" + } + } + ], + "containers": [ + { + "name": "master", + "image": "redis", + "ports": [ + { + "containerPort": 6379, + "protocol": "TCP" + } + ], + "resources": {}, + "volumeMounts": [ + { + "name": "default-token-zb4rq", + "readOnly": true, + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" + } + ], + "terminationMessagePath": "/dev/termination-log", + "imagePullPolicy": "IfNotPresent", + "capabilities": {}, + "securityContext": { + "capabilities": {}, + "privileged": false + } + } + ], + "restartPolicy": "Always", + "dnsPolicy": "ClusterFirst", + "serviceAccount": "default", + "host": "127.0.0.1" + }, + "status": { + "phase": "Running", + "Condition": [ + { + "type": "Ready", + "status": "True" + } + ], + "hostIP": "127.0.0.1", + "podIP": "172.17.0.1", + "startTime": "2015-05-21T05:12:14Z", + "containerStatuses": [ + { + "name": "master", + "state": { + "running": { + "startedAt": "2015-05-21T05:12:14Z" + } + }, + "lastState": {}, + "ready": true, + "restartCount": 0, + "image": "redis", + "imageID": "docker://95af5842ddb9b03f7c6ec7601e65924cec516fcedd7e590ae31660057085cf67", + "containerID": "docker://ae2a1e0a91a8b1015191a0b8e2ce8c55a86fb1a9a2b1e8e3b29430c9d93c8c09" + } + ] + } +}; + + // Uses promises + return { + loadAll: function() { + // Simulate async call + return $q.when(pods); + } + }; + } + PodDataService.$inject = ["$q"]; + +})(); + +(function() { + 'use strict'; + + angular.module('replicationControllers', []) + .service('replicationControllerService', ReplicationControllerDataService); + + /** + * Replication Controller DataService + * Mock async data service. + * + * @returns {{loadAll: Function}} + * @constructor + */ + function ReplicationControllerDataService($q) { + var replicationControllers = { + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "ReplicationController", + "apiVersion": "v1", + "metadata": { + "name": "redis-master", + "namespace": "default", + "selfLink": "/api/v1/namespaces/default/replicationcontrollers/redis-master", + "uid": "f12969e0-ff77-11e4-8f2d-080027213276", + "resourceVersion": "28", + "creationTimestamp": "2015-05-21T05:12:14Z", + "labels": { + "name": "redis-master" + } + }, + "spec": { + "replicas": 1, + "selector": { + "name": "redis-master" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "name": "redis-master" + } + }, + "spec": { + "containers": [ + { + "name": "master", + "image": "redis", + "ports": [ + { + "containerPort": 6379, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "imagePullPolicy": "IfNotPresent", + "capabilities": {}, + "securityContext": { + "capabilities": {}, + "privileged": false + } + } + ], + "restartPolicy": "Always", + "dnsPolicy": "ClusterFirst", + "serviceAccount": "" + } + } + }, + "status": { + "replicas": 1 + } + } + ]}; + + // Uses promises + return { + loadAll: function() { + // Simulate async call + return $q.when(replicationControllers); + } + }; + } + ReplicationControllerDataService.$inject = ["$q"]; + +})(); + +(function() { + 'use strict'; + + angular.module('services', []).service('serviceService', ServiceDataService); + + /** + * Service DataService + * Mock async data service. + * + * @returns {{loadAll: Function}} + * @constructor + */ + function ServiceDataService($q) { + var services = { + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "kubernetes", + "namespace": "default", + "selfLink": "/api/v1/namespaces/default/services/kubernetes", + "resourceVersion": "6", + "creationTimestamp": null, + "labels": { + "component": "apiserver", + "provider": "kubernetes" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 443, + "targetPort": 443 + } + ], + "portalIP": "10.0.0.2", + "sessionAffinity": "None" + }, + "status": {} + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "kubernetes-ro", + "namespace": "default", + "selfLink": "/api/v1/namespaces/default/services/kubernetes-ro", + "resourceVersion": "8", + "creationTimestamp": null, + "labels": { + "component": "apiserver", + "provider": "kubernetes" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "portalIP": "10.0.0.1", + "sessionAffinity": "None" + }, + "status": {} + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "redis-master", + "namespace": "default", + "selfLink": "/api/v1/namespaces/default/services/redis-master", + "uid": "a6fde246-ff78-11e4-8f2d-080027213276", + "resourceVersion": "72", + "creationTimestamp": "2015-05-21T05:17:19Z", + "labels": { + "name": "redis-master" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 6379, + "targetPort": 6379 + } + ], + "selector": { + "name": "redis-master" + }, + "portalIP": "10.0.0.124", + "sessionAffinity": "None" + }, + "status": {} + } + ] +}; + + // Uses promises + return { + loadAll: function() { + // Simulate async call + return $q.when(services); + } + }; + } + ServiceDataService.$inject = ["$q"]; + +})(); +`) + +func wwwAppAssetsJsAppJsBytes() ([]byte, error) { + return _wwwAppAssetsJsAppJs, nil +} + +func wwwAppAssetsJsAppJs() (*asset, error) { + bytes, err := wwwAppAssetsJsAppJsBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "www/app/assets/js/app.js", size: 91927, mode: os.FileMode(416), modTime: time.Unix(1435774726, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _wwwAppAssetsJsBaseJs = []byte(`!function(e,t){"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){function n(e){var t=e.length,n=Z.type(e);return"function"===n||Z.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e}function r(e,t,n){if(Z.isFunction(t))return Z.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return Z.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(ae.test(t))return Z.filter(t,e,n);t=Z.filter(t,e)}return Z.grep(e,function(e){return U.call(t,e)>=0!==n})}function i(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function o(e){var t=he[e]={};return Z.each(e.match(de)||[],function(e,n){t[n]=!0}),t}function s(){J.removeEventListener("DOMContentLoaded",s,!1),e.removeEventListener("load",s,!1),Z.ready()}function a(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=Z.expando+a.uid++}function u(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(be,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:xe.test(n)?Z.parseJSON(n):n}catch(i){}ye.set(e,t,n)}else n=void 0;return n}function l(){return!0}function c(){return!1}function f(){try{return J.activeElement}catch(e){}}function p(e,t){return Z.nodeName(e,"table")&&Z.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function d(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function h(e){var t=Pe.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function g(e,t){for(var n=0,r=e.length;r>n;n++)ve.set(e[n],"globalEval",!t||ve.get(t[n],"globalEval"))}function m(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(ve.hasData(e)&&(o=ve.access(e),s=ve.set(t,o),l=o.events)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)Z.event.add(t,i,l[i][n])}ye.hasData(e)&&(a=ye.access(e),u=Z.extend({},a),ye.set(t,u))}}function v(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return void 0===t||t&&Z.nodeName(e,t)?Z.merge([e],n):n}function y(e,t){var n=t.nodeName.toLowerCase();"input"===n&&Ne.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}function x(t,n){var r,i=Z(n.createElement(t)).appendTo(n.body),o=e.getDefaultComputedStyle&&(r=e.getDefaultComputedStyle(i[0]))?r.display:Z.css(i[0],"display");return i.detach(),o}function b(e){var t=J,n=$e[e];return n||(n=x(e,t),"none"!==n&&n||(We=(We||Z("