added sorting feature
This commit is contained in:
32
app.py
32
app.py
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import gevent.monkey
|
||||
|
||||
gevent.monkey.patch_all()
|
||||
|
||||
import flask
|
||||
@@ -17,7 +18,6 @@ from flask import Flask, redirect
|
||||
from flask_oauthlib.client import OAuth, OAuthRemoteApp
|
||||
from urllib.parse import urljoin
|
||||
|
||||
|
||||
DEFAULT_CLUSTERS = 'http://localhost:8001/'
|
||||
CREDENTIALS_DIR = os.getenv('CREDENTIALS_DIR', '')
|
||||
AUTHORIZE_URL = os.getenv('AUTHORIZE_URL')
|
||||
@@ -119,15 +119,20 @@ def generate_mock_cluster_data(index: int):
|
||||
phase = pod_phases[hash_int((index + 1) * (i + 1) * (j + 1)) % len(pod_phases)]
|
||||
containers = []
|
||||
for k in range(1):
|
||||
containers.append({'name': 'myapp', 'image': 'foo/bar/{}'.format(j), 'resources': {'requests': {'cpu': '100m', 'memory': '100Mi'}}})
|
||||
containers.append({'name': 'myapp', 'image': 'foo/bar/{}'.format(j),
|
||||
'resources': {'requests': {'cpu': '100m', 'memory': '100Mi'}}})
|
||||
status = {'phase': phase}
|
||||
if phase == 'Running':
|
||||
if j % 13 == 0:
|
||||
status['containerStatuses'] = [{'ready': False, 'state': {'waiting': {'reason': 'CrashLoopBackOff'}}}]
|
||||
status['containerStatuses'] = [
|
||||
{'ready': False, 'state': {'waiting': {'reason': 'CrashLoopBackOff'}}}]
|
||||
elif j % 7 == 0:
|
||||
status['containerStatuses'] = [{'ready': True, 'state': {'running': {}}, 'restartCount': 3}]
|
||||
pods.append({'name': 'my-pod-{}'.format(j), 'namespace': 'kube-system' if j < 3 else 'default', 'labels': labels, 'status': status, 'containers': containers})
|
||||
nodes.append({'name': 'node-{}'.format(i), 'labels': labels, 'status': {'capacity': {'cpu': '4', 'memory': '32Gi', 'pods': '110'}}, 'pods': pods})
|
||||
pods.append(
|
||||
{'name': 'my-pod-{}'.format(j), 'namespace': 'kube-system' if j < 3 else 'default', 'labels': labels,
|
||||
'status': status, 'containers': containers})
|
||||
nodes.append({'name': 'node-{}'.format(i), 'labels': labels,
|
||||
'status': {'capacity': {'cpu': '4', 'memory': '32Gi', 'pods': '110'}}, 'pods': pods})
|
||||
unassigned_pods = []
|
||||
return {
|
||||
'api_server_url': 'https://kube-{}.example.org'.format(index),
|
||||
@@ -156,7 +161,8 @@ def get_kubernetes_clusters():
|
||||
pods_by_namespace_name = {}
|
||||
unassigned_pods = []
|
||||
for node in response.json()['items']:
|
||||
obj = {'name': node['metadata']['name'], 'labels': node['metadata']['labels'], 'status': node['status'], 'pods': []}
|
||||
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)
|
||||
@@ -164,9 +170,15 @@ def get_kubernetes_clusters():
|
||||
for pod in response.json()['items']:
|
||||
obj = {'name': pod['metadata']['name'],
|
||||
'namespace': pod['metadata']['namespace'],
|
||||
'labels': pod['metadata'].get('labels', {}), 'status': pod['status'], 'containers': []}
|
||||
'labels': pod['metadata'].get('labels', {}),
|
||||
'status': pod['status'],
|
||||
'startTime': pod['status']['startTime'] if 'startTime' in pod['status'] else '',
|
||||
'containers': []
|
||||
}
|
||||
if 'deletionTimestamp' in pod['metadata']:
|
||||
obj['deleted'] = datetime.datetime.strptime(pod['metadata']['deletionTimestamp'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=datetime.timezone.utc).timestamp()
|
||||
obj['deleted'] = datetime.datetime.strptime(pod['metadata']['deletionTimestamp'],
|
||||
'%Y-%m-%dT%H:%M:%SZ').replace(
|
||||
tzinfo=datetime.timezone.utc).timestamp()
|
||||
for cont in pod['spec']['containers']:
|
||||
obj['containers'].append({'name': cont['name'], 'image': cont['image'], 'resources': cont['resources']})
|
||||
pods_by_namespace_name[(obj['namespace'], obj['name'])] = obj
|
||||
@@ -183,7 +195,9 @@ def get_kubernetes_clusters():
|
||||
except:
|
||||
logging.exception('Failed to get metrics')
|
||||
try:
|
||||
response = session.get(urljoin(api_server_url, '/api/v1/namespaces/kube-system/services/heapster/proxy/apis/metrics/v1alpha1/pods'), timeout=5)
|
||||
response = session.get(urljoin(api_server_url,
|
||||
'/api/v1/namespaces/kube-system/services/heapster/proxy/apis/metrics/v1alpha1/pods'),
|
||||
timeout=5)
|
||||
response.raise_for_status()
|
||||
for metrics in response.json()['items']:
|
||||
pod = pods_by_namespace_name.get((metrics['metadata']['namespace'], metrics['metadata']['name']))
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import Tooltip from './tooltip.js'
|
||||
import Cluster from './cluster.js'
|
||||
import {Pod, ALL_PODS} from './pod.js'
|
||||
import {Pod, ALL_PODS, sortByName, sortByMemory, sortByCPU, sortByAge} from './pod.js'
|
||||
import SelectBox from './selectbox'
|
||||
import { PRIMARY_VIOLET } from './colors.js'
|
||||
import 'pixi-display'
|
||||
|
||||
const PIXI = require('pixi.js')
|
||||
|
||||
|
||||
@@ -12,6 +13,7 @@ export default class App {
|
||||
constructor() {
|
||||
this.filterString = ''
|
||||
this.seenPods = {}
|
||||
this.sorterFn = ''
|
||||
}
|
||||
|
||||
filter() {
|
||||
@@ -54,13 +56,13 @@ export default class App {
|
||||
|
||||
const menuBar = new PIXI.Graphics()
|
||||
menuBar.beginFill(PRIMARY_VIOLET, 1)
|
||||
menuBar.drawRect(0, 0, window.innerWidth, 25)
|
||||
menuBar.drawRect(0, 0, window.innerWidth, 35)
|
||||
menuBar.endFill()
|
||||
stage.addChild(menuBar)
|
||||
|
||||
const searchPrompt = new PIXI.Text('>', {fontFamily: 'ShareTechMono', fontSize: 18})
|
||||
searchPrompt.x = 20
|
||||
searchPrompt.y = 5
|
||||
searchPrompt.y = 10
|
||||
PIXI.ticker.shared.add(function (_) {
|
||||
var v = Math.sin((PIXI.ticker.shared.lastTime % 2000) / 2000. * Math.PI)
|
||||
searchPrompt.alpha = v
|
||||
@@ -74,22 +76,23 @@ export default class App {
|
||||
|
||||
const items = [
|
||||
{
|
||||
text: 'Name', sorterFn: () => {}
|
||||
text: 'Name', sorterFn: sortByName
|
||||
},
|
||||
{
|
||||
text: 'Age', sorterFn: () => {}
|
||||
text: 'Age', sorterFn: sortByAge
|
||||
},
|
||||
{
|
||||
text: 'Memory', sorterFn: () => {}
|
||||
text: 'Memory', sorterFn: sortByMemory
|
||||
},
|
||||
{
|
||||
text: 'CPU', sorterFn: () => {}
|
||||
},
|
||||
|
||||
text: 'CPU', sorterFn: sortByCPU
|
||||
}
|
||||
]
|
||||
//setting default sort
|
||||
this.sorterFn = items[0].sorterFn
|
||||
const selectBox = new SelectBox(items)
|
||||
selectBox.x = 265
|
||||
selectBox.y = 5
|
||||
selectBox.y = 3
|
||||
const mainLayer = new PIXI.DisplayGroup(1, true)
|
||||
selectBox.displayGroup = mainLayer
|
||||
stage.addChild(selectBox.draw())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {Pod} from './pod.js'
|
||||
import Bars from './bars.js'
|
||||
import {parseResource} from './utils.js'
|
||||
import App from './app'
|
||||
const PIXI = require('pixi.js')
|
||||
|
||||
export default class Node extends PIXI.Graphics {
|
||||
@@ -43,9 +44,9 @@ export default class Node extends PIXI.Graphics {
|
||||
return resources
|
||||
}
|
||||
|
||||
draw () {
|
||||
var nodeBox = this
|
||||
var topHandle = new PIXI.Graphics()
|
||||
draw() {
|
||||
const nodeBox = this
|
||||
const topHandle = new PIXI.Graphics()
|
||||
topHandle.beginFill(0xaaaaff, 1)
|
||||
topHandle.drawRect(0, 0, 105, 15)
|
||||
topHandle.endFill()
|
||||
@@ -62,8 +63,8 @@ export default class Node extends PIXI.Graphics {
|
||||
nodeBox.lineStyle(2, 0xaaaaaa, 1)
|
||||
topHandle.interactive = true
|
||||
topHandle.on('mouseover', function () {
|
||||
var s = nodeBox.node.name
|
||||
for (var key of Object.keys(nodeBox.node.labels)) {
|
||||
let s = nodeBox.node.name
|
||||
for (const key of Object.keys(nodeBox.node.labels)) {
|
||||
s += '\n' + key + ': ' + nodeBox.node.labels[key]
|
||||
}
|
||||
nodeBox.tooltip.setText(s)
|
||||
@@ -79,9 +80,16 @@ export default class Node extends PIXI.Graphics {
|
||||
bars.y = 1
|
||||
nodeBox.addChild(bars.draw())
|
||||
|
||||
var px = 24
|
||||
var py = 20
|
||||
for (const pod of this.node.pods) {
|
||||
nodeBox.addPods(App.sorterFn)
|
||||
return nodeBox
|
||||
}
|
||||
|
||||
addPods(sorterFn) {
|
||||
const nodeBox = this
|
||||
let px = 24
|
||||
let py = 20
|
||||
const pods = sorterFn !== 'undefined' ? this.node.pods.sort(sorterFn) : this.node.pods
|
||||
for (const pod of pods) {
|
||||
if (pod.namespace != 'kube-system') {
|
||||
const podBox = Pod.getOrCreate(pod, this.cluster, this.tooltip) //new Pod(pod, this.tooltip)
|
||||
podBox.x = px
|
||||
@@ -93,11 +101,10 @@ export default class Node extends PIXI.Graphics {
|
||||
py += 13
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
px = 24
|
||||
py = 100
|
||||
for (const pod of this.node.pods) {
|
||||
for (const pod of pods) {
|
||||
if (pod.namespace == 'kube-system') {
|
||||
const podBox = Pod.getOrCreate(pod, this.cluster, this.tooltip) //new Pod(pod, this.tooltip)
|
||||
podBox.x = px
|
||||
@@ -109,9 +116,6 @@ export default class Node extends PIXI.Graphics {
|
||||
py -= 13
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nodeBox
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,36 @@
|
||||
const PIXI = require('pixi.js')
|
||||
import {FACTORS, parseResource, getBarColor} from './utils.js'
|
||||
import {FACTORS, getBarColor, podResource} from './utils.js'
|
||||
|
||||
export const ALL_PODS = {}
|
||||
const ALL_PODS = {}
|
||||
|
||||
const sortByName = (a, b) => {
|
||||
return a.name.localeCompare(b.name)
|
||||
}
|
||||
|
||||
const sortByAge = (a, b) => {
|
||||
const dateA = new Date(a.status.startTime)
|
||||
const dateB = new Date(b.status.startTime)
|
||||
if (dateA.getTime() < dateB.getTime()) {
|
||||
return -1
|
||||
} else if (dateA.getTime() === dateB.getTime())
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
}
|
||||
|
||||
const sortByMemory = (a, b) => {
|
||||
const aMem = podResource('memory')(a.containers, 'usage')
|
||||
const bMem = podResource('memory')(b.containers, 'usage')
|
||||
return bMem - aMem
|
||||
}
|
||||
|
||||
const sortByCPU = (a, b) => {
|
||||
const aCpu = podResource('cpu')(a.containers, 'usage')
|
||||
const bCpu = podResource('cpu')(b.containers, 'usage')
|
||||
return bCpu - aCpu
|
||||
}
|
||||
|
||||
export {ALL_PODS, sortByAge, sortByCPU, sortByMemory, sortByName}
|
||||
|
||||
export class Pod extends PIXI.Graphics {
|
||||
|
||||
@@ -18,13 +47,6 @@ export class Pod extends PIXI.Graphics {
|
||||
}
|
||||
|
||||
getResourceUsage() {
|
||||
const metric = (metric, type) =>
|
||||
metric ? (metric[type] ? parseResource(metric[type]) : 0) : 0
|
||||
|
||||
const podResource = type => (containers, resource) =>
|
||||
containers
|
||||
.map(({resources}) => metric(resources[resource], type))
|
||||
.reduce((a, b) => a + b, 0)
|
||||
|
||||
const podCpu = podResource('cpu')
|
||||
const podMem = podResource('memory')
|
||||
@@ -63,7 +85,7 @@ export class Pod extends PIXI.Graphics {
|
||||
}
|
||||
|
||||
pulsate(time) {
|
||||
const v = Math.sin((PIXI.ticker.shared.lastTime % 1000)/1000.* Math.PI)
|
||||
const v = Math.sin((PIXI.ticker.shared.lastTime % 1000) / 1000. * Math.PI)
|
||||
this.alpha = v * this._progress
|
||||
}
|
||||
|
||||
@@ -82,9 +104,9 @@ export class Pod extends PIXI.Graphics {
|
||||
|
||||
// pod.status.containerStatuses might be undefined!
|
||||
const containerStatuses = this.pod.status.containerStatuses || []
|
||||
var ready = 0
|
||||
var running = 0
|
||||
var restarts = 0
|
||||
let ready = 0
|
||||
let running = 0
|
||||
let restarts = 0
|
||||
for (const containerStatus of containerStatuses) {
|
||||
if (containerStatus.ready) {
|
||||
ready++
|
||||
@@ -98,17 +120,18 @@ export class Pod extends PIXI.Graphics {
|
||||
const allRunning = running >= containerStatuses.length
|
||||
const resources = this.getResourceUsage()
|
||||
|
||||
var newTick = null
|
||||
let newTick = null
|
||||
|
||||
const podBox = this
|
||||
podBox.interactive = true
|
||||
podBox.on('mouseover', function() {
|
||||
podBox.on('mouseover', function () {
|
||||
const filter = new PIXI.filters.ColorMatrixFilter()
|
||||
filter.brightness(1.3)
|
||||
podBox.filters = [filter]
|
||||
let s = this.pod.name
|
||||
s += '\nStatus: ' + this.pod.status.phase + ' (' + ready + '/' + containerStatuses.length + ' ready)'
|
||||
s += '\nLabels:'
|
||||
s += '\nStatus : ' + this.pod.status.phase + ' (' + ready + '/' + containerStatuses.length + ' ready)'
|
||||
s += '\nStart Time: ' + this.pod.status.startTime
|
||||
s += '\nLabels :'
|
||||
for (var key of Object.keys(this.pod.labels)) {
|
||||
if (key !== 'pod-template-hash') {
|
||||
s += '\n ' + key + ': ' + this.pod.labels[key]
|
||||
@@ -144,8 +167,8 @@ export class Pod extends PIXI.Graphics {
|
||||
this.tooltip.visible = false
|
||||
})
|
||||
podBox.lineStyle(2, 0xaaaaaa, 1)
|
||||
var i = 0
|
||||
var w = 10 / this.pod.containers.length
|
||||
let i = 0
|
||||
const w = 10 / this.pod.containers.length
|
||||
for (const container of this.pod.containers) {
|
||||
podBox.drawRect(i * w, 0, w, 10)
|
||||
i++
|
||||
@@ -190,9 +213,9 @@ export class Pod extends PIXI.Graphics {
|
||||
|
||||
if (restarts) {
|
||||
this.lineStyle(2, 0xff9999, 1)
|
||||
for (let i=0; i<Math.min(restarts, 4); i++) {
|
||||
this.moveTo(10, i*3 - 1)
|
||||
this.lineTo(10, i*3 + 1)
|
||||
for (let i = 0; i < Math.min(restarts, 4); i++) {
|
||||
this.moveTo(10, i * 3 - 1)
|
||||
this.lineTo(10, i * 3 + 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { PRIMARY_VIOLET } from './colors.js'
|
||||
import App from './app'
|
||||
|
||||
const PIXI = require('pixi.js')
|
||||
|
||||
export default class SelectBox extends PIXI.Graphics {
|
||||
@@ -8,11 +11,11 @@ export default class SelectBox extends PIXI.Graphics {
|
||||
this.text = new PIXI.Text(this.items[this.count].text, {
|
||||
fontFamily: 'ShareTechMono',
|
||||
fontSize: 14,
|
||||
fill: 0xaaaaff,
|
||||
fill: 0x000000,
|
||||
align: 'center'
|
||||
})
|
||||
this.text.x = 10
|
||||
this.text.y = 10
|
||||
this.text.y = 8
|
||||
this.addChild(this.text)
|
||||
}
|
||||
|
||||
@@ -24,6 +27,7 @@ export default class SelectBox extends PIXI.Graphics {
|
||||
selectBox.count = 0
|
||||
}
|
||||
selectBox.text.text = selectBox.items[selectBox.count].text
|
||||
App.sorterFn = selectBox.items[selectBox.count].sorterFn
|
||||
}
|
||||
|
||||
onBackPressed() {
|
||||
@@ -34,6 +38,7 @@ export default class SelectBox extends PIXI.Graphics {
|
||||
selectBox.count = selectBox.items.length - 1
|
||||
}
|
||||
selectBox.text.text = selectBox.items[selectBox.count].text
|
||||
App.sorterFn = selectBox.items[selectBox.count].sorterFn
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -44,27 +49,27 @@ export default class SelectBox extends PIXI.Graphics {
|
||||
backArrow.interactive = true
|
||||
forwardArrow.interactive = true
|
||||
selectBox.interactive = true
|
||||
// set a fill and line style
|
||||
|
||||
// draw a triangle
|
||||
backArrow.lineStyle(2, 0x000000, 1)
|
||||
backArrow.beginFill(0x1b7c87, 0.5)
|
||||
backArrow.beginFill(PRIMARY_VIOLET, 0.5)
|
||||
backArrow.moveTo(0, 2)
|
||||
backArrow.lineTo(-20, 15)
|
||||
backArrow.lineTo(0, 28)
|
||||
backArrow.lineTo(-20, 14)
|
||||
backArrow.lineTo(0, 26)
|
||||
backArrow.lineTo(0, 2)
|
||||
backArrow.endFill()
|
||||
selectBox.addChild(backArrow)
|
||||
|
||||
selectBox.lineStyle(2, 0x000000, 1)
|
||||
selectBox.beginFill(0x1b7c87, 0.5)
|
||||
selectBox.drawRoundedRect(4, 0, 100, 30, 10)
|
||||
selectBox.beginFill(PRIMARY_VIOLET, 0.5)
|
||||
selectBox.drawRoundedRect(4, 0, 100, 28, 5)
|
||||
selectBox.endFill()
|
||||
|
||||
forwardArrow.lineStyle(2, 0x000000, 1)
|
||||
forwardArrow.beginFill(0x1b7c87, 0.5)
|
||||
forwardArrow.beginFill(PRIMARY_VIOLET, 0.5)
|
||||
forwardArrow.moveTo(108, 2)
|
||||
forwardArrow.lineTo(128, 15)
|
||||
forwardArrow.lineTo(108, 28)
|
||||
forwardArrow.lineTo(128, 14)
|
||||
forwardArrow.lineTo(108, 26)
|
||||
forwardArrow.lineTo(108, 2)
|
||||
forwardArrow.endFill()
|
||||
selectBox.addChild(forwardArrow)
|
||||
|
||||
@@ -68,4 +68,12 @@ function parseResource(v) {
|
||||
return parseInt(match[1]) * factor
|
||||
}
|
||||
|
||||
export {FACTORS, hsvToRgb, getBarColor, parseResource}
|
||||
const metric = (metric, type) =>
|
||||
metric ? (metric[type] ? parseResource(metric[type]) : 0) : 0
|
||||
|
||||
const podResource = type => (containers, resource) =>
|
||||
containers
|
||||
.map(({resources}) => metric(resources[resource], type))
|
||||
.reduce((a, b) => a + b, 0)
|
||||
|
||||
export {FACTORS, hsvToRgb, getBarColor, parseResource, metric, podResource}
|
||||
|
||||
@@ -33,6 +33,7 @@ module.exports = {
|
||||
{test: /\.html$/, exclude: /node_modules/, loader: 'file-loader?name=[path][name].[ext]'},
|
||||
{test: /\.jpe?g$|\.svg$|\.png$/, exclude: /node_modules/, loader: 'file-loader?name=[path][name].[ext]'},
|
||||
{test: /\.json$/, exclude: /node_modules/, loader: 'json'},
|
||||
{test: /\.(otf|eot|svg|ttf|woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=8192&mimetype=application/font-woff'},
|
||||
{test: /\.json$/, include: path.join(__dirname, 'node_modules', 'pixi.js'), loader: 'json'}
|
||||
],
|
||||
postLoaders: [{
|
||||
|
||||
Reference in New Issue
Block a user