Merge branch 'master' into feature/webpack
This commit is contained in:
@@ -3,9 +3,7 @@ FROM alpine:3.4
|
||||
RUN apk add --no-cache python3 python3-dev alpine-sdk zlib-dev libffi-dev openssl-dev nodejs && \
|
||||
python3 -m ensurepip && \
|
||||
rm -r /usr/lib/python*/ensurepip && \
|
||||
# compile gevent
|
||||
pip3 install --upgrade pip setuptools gevent && \
|
||||
# cleanup
|
||||
apk del python3-dev alpine-sdk zlib-dev libffi-dev openssl-dev && \
|
||||
rm -rf /var/cache/apk/* /root/.cache /tmp/*
|
||||
|
||||
|
||||
15
README.rst
15
README.rst
@@ -34,7 +34,7 @@ You can run the app locally:
|
||||
|
||||
$ pip3 install -r requirements.txt
|
||||
$ kubectl proxy &
|
||||
$ cd app && npm start
|
||||
$ cd app && npm start &
|
||||
$ cd -
|
||||
$ ./app.py
|
||||
|
||||
@@ -56,6 +56,19 @@ Afterwards you can open "kube-ops-view" via the kubectl proxy:
|
||||
Now direct your browser to http://localhost:8001/api/v1/proxy/namespaces/default/services/kube-ops-view/
|
||||
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
The following environment variables are supported:
|
||||
|
||||
``CLUSTERS``
|
||||
Comma separated list of Kubernetes API server URLs. It defaults to ``http://localhost:8001/`` (default endpoint of ``kubectl proxy``).
|
||||
``CREDENTIALS_DIR``
|
||||
Directory to read (OAuth) credentials from --- these credentials are only used for non-localhost cluster URLs.
|
||||
``DEBUG``
|
||||
Set to a non-empty value for local development to reload code changes.
|
||||
|
||||
|
||||
Supported Browsers
|
||||
==================
|
||||
|
||||
|
||||
58
app.py
58
app.py
@@ -8,11 +8,19 @@ import flask
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
import tokens
|
||||
|
||||
from urllib.parse import urljoin
|
||||
|
||||
|
||||
DEFAULT_CLUSTERS = 'http://localhost:8001/'
|
||||
|
||||
app = connexion.App(__name__)
|
||||
session = requests.Session()
|
||||
|
||||
tokens.configure(from_file_only=True)
|
||||
tokens.manage('read-only')
|
||||
|
||||
|
||||
@app.app.route('/')
|
||||
def index():
|
||||
@@ -20,36 +28,42 @@ def index():
|
||||
|
||||
|
||||
def get_clusters():
|
||||
api_server_url = 'http://localhost:8001/'
|
||||
response = session.get('{}/api/v1/nodes'.format(api_server_url), timeout=5)
|
||||
response.raise_for_status()
|
||||
nodes = []
|
||||
nodes_by_name = {}
|
||||
for node in response.json()['items']:
|
||||
obj = {'name': node['metadata']['name'], 'labels': node['metadata']['labels'], 'status': node['status'], 'pods': []}
|
||||
nodes.append(obj)
|
||||
nodes_by_name[obj['name']] = obj
|
||||
response = session.get('http://localhost:8001/api/v1/pods', timeout=5)
|
||||
response.raise_for_status()
|
||||
for pod in response.json()['items']:
|
||||
if 'nodeName' in pod['spec']:
|
||||
nodes_by_name[pod['spec']['nodeName']]['pods'].append(pod)
|
||||
|
||||
try:
|
||||
response = session.get('{}/api/v1/namespaces/kube-system/services/heapster/proxy/apis/metrics/v1alpha1/nodes'.format(api_server_url), timeout=5)
|
||||
clusters = []
|
||||
for api_server_url in os.getenv('CLUSTERS', DEFAULT_CLUSTERS).split(','):
|
||||
if 'localhost' not in api_server_url:
|
||||
# TODO: hacky way of detecting whether we need a token or not
|
||||
session.headers['Authorization'] = 'Bearer {}'.format(tokens.get('read-only'))
|
||||
response = session.get(urljoin(api_server_url, '/api/v1/nodes'), timeout=5)
|
||||
response.raise_for_status()
|
||||
for metrics in response.json()['items']:
|
||||
nodes_by_name[metrics['metadata']['name']]['usage'] = metrics['usage']
|
||||
except:
|
||||
logging.exception('Failed to get metrics')
|
||||
nodes = []
|
||||
nodes_by_name = {}
|
||||
for node in response.json()['items']:
|
||||
obj = {'name': node['metadata']['name'], 'labels': node['metadata']['labels'], 'status': node['status'], 'pods': []}
|
||||
nodes.append(obj)
|
||||
nodes_by_name[obj['name']] = obj
|
||||
response = session.get(urljoin(api_server_url, '/api/v1/pods'), timeout=5)
|
||||
response.raise_for_status()
|
||||
for pod in response.json()['items']:
|
||||
if 'nodeName' in pod['spec']:
|
||||
nodes_by_name[pod['spec']['nodeName']]['pods'].append(pod)
|
||||
|
||||
return {'kubernetes_clusters': [{'nodes': nodes}]}
|
||||
try:
|
||||
response = session.get(urljoin(api_server_url, '/api/v1/namespaces/kube-system/services/heapster/proxy/apis/metrics/v1alpha1/nodes'), timeout=5)
|
||||
response.raise_for_status()
|
||||
for metrics in response.json()['items']:
|
||||
nodes_by_name[metrics['metadata']['name']]['usage'] = metrics['usage']
|
||||
except:
|
||||
logging.exception('Failed to get metrics')
|
||||
clusters.append({'api_server_url': api_server_url, 'nodes': nodes})
|
||||
|
||||
return {'kubernetes_clusters': clusters}
|
||||
|
||||
|
||||
app.add_api('swagger.yaml')
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
if os.getenv('DEBUG', False):
|
||||
kwargs = {'debug': True}
|
||||
else:
|
||||
|
||||
@@ -52,6 +52,46 @@ class Tooltip extends PIXI.Graphics {
|
||||
}
|
||||
}
|
||||
|
||||
class Cluster extends PIXI.Graphics {
|
||||
constructor (cluster, tooltip) {
|
||||
super()
|
||||
this.cluster = cluster
|
||||
this.tooltip = tooltip
|
||||
}
|
||||
draw () {
|
||||
|
||||
var rows = [10, 10]
|
||||
for (var node of this.cluster.nodes) {
|
||||
var nodeBox = new Node(node, tooltip)
|
||||
nodeBox.draw()
|
||||
if (nodeBox.isMaster()) {
|
||||
nodeBox.x = rows[0]
|
||||
rows[0] += nodeBox.width + 5
|
||||
nodeBox.y = 20
|
||||
} else {
|
||||
nodeBox.x = rows[1]
|
||||
rows[1] += nodeBox.width + 5
|
||||
nodeBox.y = nodeBox.height + 25
|
||||
}
|
||||
this.addChild(nodeBox)
|
||||
}
|
||||
this.lineStyle(2, 0xaaaaff, 1);
|
||||
const width = Math.max(rows[0], rows[1])
|
||||
this.drawRect(0, 0, width, nodeBox.height * 2 + 30);
|
||||
|
||||
var topHandle = new PIXI.Graphics()
|
||||
topHandle.beginFill(0xaaaaff, 1)
|
||||
topHandle.drawRect(0, 0, width, 15)
|
||||
topHandle.endFill()
|
||||
var text = new PIXI.Text(this.cluster.api_server_url, {fontSize: 10, fill: 0x000000})
|
||||
text.x = 2
|
||||
text.y = 2
|
||||
topHandle.addChild(text)
|
||||
this.addChild(topHandle)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Node extends PIXI.Graphics {
|
||||
constructor (node, tooltip) {
|
||||
super()
|
||||
@@ -265,30 +305,14 @@ tooltip.draw()
|
||||
function update(clusters) {
|
||||
graphics.removeChildren();
|
||||
graphics.lineStyle(2, 0xaaaaff, 1);
|
||||
var x = 50;
|
||||
var y = 50;
|
||||
for (var cluster of clusters) {
|
||||
var clusterBox = new PIXI.Graphics()
|
||||
clusterBox.x = x
|
||||
clusterBox.y = 50
|
||||
var clusterBox = new Cluster(cluster, tooltip)
|
||||
clusterBox.draw()
|
||||
clusterBox.x = 50
|
||||
clusterBox.y = y
|
||||
graphics.addChild(clusterBox)
|
||||
var rows = [10, 10]
|
||||
for (var node of cluster.nodes) {
|
||||
var nodeBox = new Node(node, tooltip)
|
||||
nodeBox.draw()
|
||||
if (nodeBox.isMaster()) {
|
||||
nodeBox.x = rows[0]
|
||||
rows[0] += nodeBox.width + 5
|
||||
nodeBox.y = 10
|
||||
} else {
|
||||
nodeBox.x = rows[1]
|
||||
rows[1] += nodeBox.width + 5
|
||||
nodeBox.y = nodeBox.height + 15
|
||||
}
|
||||
clusterBox.addChild(nodeBox)
|
||||
}
|
||||
clusterBox.lineStyle(2, 0xaaaaff, 1);
|
||||
clusterBox.drawRect(0, 0, Math.max(rows[0], rows[1]), nodeBox.height * 2 + 20);
|
||||
x += 250;
|
||||
y += 270;
|
||||
}
|
||||
graphics.addChild(tooltip)
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
connexion
|
||||
gevent
|
||||
stups-tokens
|
||||
|
||||
Reference in New Issue
Block a user