Kaynağa Gözat

实现详情页的第一个图表

James Iter 9 yıl önce
ebeveyn
işleme
a0c668f0d4

+ 161 - 0
api/performance.py

@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+from flask import Blueprint
+import json
+import jimit as ji
+
+from api.base import Base
+from models import CPUMemory, Traffic, DiskIO, Utils, Rules
+
+
+__author__ = 'James Iter'
+__date__ = '2017/7/2'
+__contact__ = 'james.iter.cn@gmail.com'
+__copyright__ = '(c) 2017 by James Iter.'
+
+
+blueprint = Blueprint(
+    'api_performance',
+    __name__,
+    url_prefix='/api/performance'
+)
+
+blueprints = Blueprint(
+    'api_performances',
+    __name__,
+    url_prefix='/api/performances'
+)
+
+
+cpu_memory = Base(the_class=CPUMemory, the_blueprint=blueprint, the_blueprints=blueprints)
+traffic = Base(the_class=Traffic, the_blueprint=blueprint, the_blueprints=blueprints)
+disk_io = Base(the_class=DiskIO, the_blueprint=blueprint, the_blueprints=blueprints)
+
+
+@Utils.dumps2response
+def r_cpu_memory_get_by_filter():
+    return cpu_memory.get_by_filter()
+
+
+@Utils.dumps2response
+def r_traffic_get_by_filter():
+    return traffic.get_by_filter()
+
+
+@Utils.dumps2response
+def r_disk_io_get_by_filter():
+    return disk_io.get_by_filter()
+
+
+def get_performance_data(uuid, uuid_field, the_class=None, granularity='hour'):
+
+    args_rules = [
+        Rules.UUID.value,
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {'uuid': uuid})
+        uuids_str = ':'.join([uuid_field, 'in', uuid])
+        filter_str = uuids_str
+
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+        ret['data'] = list()
+
+        limit = 60
+        if granularity == 'hour':
+            limit = 60
+
+        elif granularity == 'six_hours':
+            limit = 60 * 6
+
+        elif granularity == 'day':
+            limit = 60 * 24
+
+        elif granularity == 'seven_days':
+            limit = 60 * 24 * 7
+
+        else:
+            pass
+
+        rows, rows_count = the_class.get_by_filter(
+            offset=0, limit=limit, order_by='id', order='desc', filter_str=filter_str)
+
+        if granularity in ['day', 'seven_days']:
+            for row in rows:
+                if row['timestamp'] % 600 != 0:
+                    continue
+
+                ret['data'].append(row)
+
+        else:
+            ret['data'] = rows
+
+        return ret
+
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+
+@Utils.dumps2response
+def r_cpu_memory_last_hour(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='guest_uuid', the_class=CPUMemory, granularity='hour')
+
+
+@Utils.dumps2response
+def r_cpu_memory_last_six_hours(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='guest_uuid', the_class=CPUMemory, granularity='six_hours')
+
+
+@Utils.dumps2response
+def r_cpu_memory_last_day(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='guest_uuid', the_class=CPUMemory, granularity='day')
+
+
+@Utils.dumps2response
+def r_cpu_memory_last_seven_days(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='guest_uuid', the_class=CPUMemory, granularity='seven_days')
+
+
+@Utils.dumps2response
+def r_traffic_last_hour(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='guest_uuid', the_class=Traffic, granularity='hour')
+
+
+@Utils.dumps2response
+def r_traffic_last_six_hours(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='guest_uuid', the_class=Traffic, granularity='six_hours')
+
+
+@Utils.dumps2response
+def r_traffic_last_day(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='guest_uuid', the_class=Traffic, granularity='day')
+
+
+@Utils.dumps2response
+def r_traffic_last_seven_days(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='guest_uuid', the_class=Traffic, granularity='seven_days')
+
+
+@Utils.dumps2response
+def r_disk_io_last_hour(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='disk_uuid', the_class=DiskIO, granularity='hour')
+
+
+@Utils.dumps2response
+def r_disk_io_last_six_hours(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='disk_uuid', the_class=DiskIO, granularity='six_hours')
+
+
+@Utils.dumps2response
+def r_disk_io_last_day(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='disk_uuid', the_class=DiskIO, granularity='day')
+
+
+@Utils.dumps2response
+def r_disk_io_last_seven_days(uuid):
+    return get_performance_data(uuid=uuid, uuid_field='disk_uuid', the_class=DiskIO, granularity='seven_days')
+
+

+ 43 - 0
api_route_table.py

@@ -11,6 +11,7 @@ from api import operate_rule
 from api import os_template
 from api import log
 from api import host
+from api import performance
 
 
 __author__ = 'James Iter'
@@ -92,3 +93,45 @@ add_rule_api(log.blueprints, '/_search', api_func='log.r_content_search', method
 add_rule_api(host.blueprints, '/<ids>', api_func='host.r_get', methods=['GET'])
 add_rule_api(host.blueprints, '', api_func='host.r_get_by_filter', methods=['GET'])
 add_rule_api(host.blueprints, '/_search', api_func='host.r_content_search', methods=['GET'])
+
+# 性能查询
+add_rule_api(performance.blueprint, '/cpu_memory', api_func='performance.r_cpu_memory_get_by_filter', methods=['GET'])
+add_rule_api(performance.blueprint, '/traffic', api_func='performance.r_traffic_get_by_filter', methods=['GET'])
+add_rule_api(performance.blueprint, '/disk_io', api_func='performance.r_disk_io_get_by_filter', methods=['GET'])
+add_rule_api(performance.blueprint, '/cpu_memory/last_hour/<uuid>',
+             api_func='performance.r_cpu_memory_last_hour', methods=['GET'])
+
+add_rule_api(performance.blueprint, '/cpu_memory/last_six_hours/<uuid>',
+             api_func='performance.r_cpu_memory_last_six_hours', methods=['GET'])
+
+add_rule_api(performance.blueprint, '/cpu_memory/last_day/<uuid>',
+             api_func='performance.r_cpu_memory_last_day', methods=['GET'])
+
+add_rule_api(performance.blueprint, '/cpu_memory/last_seven_days/<uuid>',
+             api_func='performance.r_cpu_memory_last_seven_days', methods=['GET'])
+
+add_rule_api(performance.blueprint, '/traffic/last_hour/<uuid>',
+             api_func='performance.r_traffic_last_hour', methods=['GET'])
+
+add_rule_api(performance.blueprint, '/traffic/last_six_hours/<uuid>',
+             api_func='performance.r_traffic_last_six_hours', methods=['GET'])
+
+add_rule_api(performance.blueprint, '/traffic/last_day/<uuid>',
+             api_func='performance.r_traffic_last_day', methods=['GET'])
+
+add_rule_api(performance.blueprint, '/traffic/last_seven_days/<uuid>',
+             api_func='performance.r_traffic_last_seven_days', methods=['GET'])
+
+add_rule_api(performance.blueprint, '/disk_io/last_hour/<uuid>',
+             api_func='performance.r_disk_io_last_hour', methods=['GET'])
+
+add_rule_api(performance.blueprint, '/disk_io/last_six_hours/<uuid>',
+             api_func='performance.r_disk_io_last_six_hours', methods=['GET'])
+
+add_rule_api(performance.blueprint, '/disk_io/last_day/<uuid>',
+             api_func='performance.r_disk_io_last_day', methods=['GET'])
+
+add_rule_api(performance.blueprint, '/disk_io/last_seven_days/<uuid>',
+             api_func='performance.r_disk_io_last_seven_days', methods=['GET'])
+
+

+ 4 - 0
main.py

@@ -35,6 +35,8 @@ from api.log import blueprint as log_blueprint
 from api.log import blueprints as log_blueprints
 from api.host import blueprint as host_blueprint
 from api.host import blueprints as host_blueprints
+from api.performance import blueprint as performance_blueprint
+from api.performance import blueprints as performance_blueprints
 
 from views.guest import blueprint as view_guest_blueprint
 from views.guest import blueprints as view_guest_blueprints
@@ -104,6 +106,8 @@ try:
     app.register_blueprint(log_blueprints)
     app.register_blueprint(host_blueprint)
     app.register_blueprint(host_blueprints)
+    app.register_blueprint(performance_blueprint)
+    app.register_blueprint(performance_blueprints)
 
     app.register_blueprint(view_guest_blueprint)
     app.register_blueprint(view_guest_blueprints)

+ 126 - 0
static/js-core/date_format.js

@@ -0,0 +1,126 @@
+/*
+ * Date Format 1.2.3
+ * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
+ * MIT license
+ *
+ * Includes enhancements by Scott Trenda <scott.trenda.net>
+ * and Kris Kowal <cixar.com/~kris.kowal/>
+ *
+ * Accepts a date, a mask, or a date and a mask.
+ * Returns a formatted version of the given date.
+ * The date defaults to the current date/time.
+ * The mask defaults to dateFormat.masks.default.
+ * From http://blog.stevenlevithan.com/archives/date-time-format
+ */
+
+var dateFormat = function () {
+	var	token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
+		timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
+		timezoneClip = /[^-+\dA-Z]/g,
+		pad = function (val, len) {
+			val = String(val);
+			len = len || 2;
+			while (val.length < len) val = "0" + val;
+			return val;
+		};
+
+	// Regexes and supporting functions are cached through closure
+	return function (date, mask, utc) {
+		var dF = dateFormat;
+
+		// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
+		if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
+			mask = date;
+			date = undefined;
+		}
+
+		// Passing date through Date applies Date.parse, if necessary
+		date = date ? new Date(date) : new Date;
+		if (isNaN(date)) throw SyntaxError("invalid date");
+
+		mask = String(dF.masks[mask] || mask || dF.masks["default"]);
+
+		// Allow setting the utc argument via the mask
+		if (mask.slice(0, 4) == "UTC:") {
+			mask = mask.slice(4);
+			utc = true;
+		}
+
+		var	_ = utc ? "getUTC" : "get",
+			d = date[_ + "Date"](),
+			D = date[_ + "Day"](),
+			m = date[_ + "Month"](),
+			y = date[_ + "FullYear"](),
+			H = date[_ + "Hours"](),
+			M = date[_ + "Minutes"](),
+			s = date[_ + "Seconds"](),
+			L = date[_ + "Milliseconds"](),
+			o = utc ? 0 : date.getTimezoneOffset(),
+			flags = {
+				d:    d,
+				dd:   pad(d),
+				ddd:  dF.i18n.dayNames[D],
+				dddd: dF.i18n.dayNames[D + 7],
+				m:    m + 1,
+				mm:   pad(m + 1),
+				mmm:  dF.i18n.monthNames[m],
+				mmmm: dF.i18n.monthNames[m + 12],
+				yy:   String(y).slice(2),
+				yyyy: y,
+				h:    H % 12 || 12,
+				hh:   pad(H % 12 || 12),
+				H:    H,
+				HH:   pad(H),
+				M:    M,
+				MM:   pad(M),
+				s:    s,
+				ss:   pad(s),
+				l:    pad(L, 3),
+				L:    pad(L > 99 ? Math.round(L / 10) : L),
+				t:    H < 12 ? "a"  : "p",
+				tt:   H < 12 ? "am" : "pm",
+				T:    H < 12 ? "A"  : "P",
+				TT:   H < 12 ? "AM" : "PM",
+				Z:    utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
+				o:    (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
+				S:    ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
+			};
+
+		return mask.replace(token, function ($0) {
+			return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
+		});
+	};
+}();
+
+// Some common format strings
+dateFormat.masks = {
+	"default":      "ddd mmm dd yyyy HH:MM:ss",
+	shortDate:      "m/d/yy",
+	mediumDate:     "mmm d, yyyy",
+	longDate:       "mmmm d, yyyy",
+	fullDate:       "dddd, mmmm d, yyyy",
+	shortTime:      "h:MM TT",
+	mediumTime:     "h:MM:ss TT",
+	longTime:       "h:MM:ss TT Z",
+	isoDate:        "yyyy-mm-dd",
+	isoTime:        "HH:MM:ss",
+	isoDateTime:    "yyyy-mm-dd'T'HH:MM:ss",
+	isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
+};
+
+// Internationalization strings
+dateFormat.i18n = {
+	dayNames: [
+		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
+		"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
+	],
+	monthNames: [
+		"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+		"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
+	]
+};
+
+// For convenience...
+Date.prototype.format = function (mask, utc) {
+	return dateFormat(this, mask, utc);
+};

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
static/js-core/echarts.min.js


+ 176 - 0
templates/guest_detail.html

@@ -0,0 +1,176 @@
+{% extends "layout.html" %}
+{% block title %} Guest {% endblock %}
+{% block head %}
+    {{ super() }}
+
+    <style type="text/css">
+
+        @media (min-width: 768px) {
+            .form-horizontal .control-label {
+                text-align: left;
+            }
+        }
+        
+        label>span {
+            color: deepskyblue;
+        }
+
+        .btn,
+        .form-group>div>div,
+        .form-control {
+            border-radius: 0;
+        }
+
+        .btn-ability {
+            margin-right: 16px;
+            height: 50px;
+            padding: 8px 50px;
+            margin-bottom: 30px;
+        }
+
+        .btn-ability-line {
+        }
+
+    </style>
+{% endblock head %}
+{% block body %}
+<script>
+    var resource_path = window.location.pathname;
+    var resource_path_array = resource_path.split('/');
+    var uuid = resource_path_array[resource_path_array.length - 1];
+    var cpu_chart = null;
+
+    function format_time(timestamp, i) {
+        return new Date(timestamp).format('HH:MM');
+    }
+
+    function render_cpu_memory_chart(uuid) {
+        $.ajax({
+            url : '/api/performance/cpu_memory/last_six_hours/' + uuid,
+            type : 'GET',
+            contentType: "application/json; charset=utf-8",
+            dataType: 'json',
+            error : function() {
+                alter_danger('获取图表数据失败失败!');
+            },
+            success : function(data, textStatus, xhr) {
+
+                var option = {
+                    color: ['#3BC0FF'],
+                    title: {
+                        text: 'CPU'
+                    },
+                    toolbox: {
+                        feature: {
+                            dataZoom: {
+                                yAxisIndex: 'none'
+                            },
+                            magicType: {type: ['line', 'bar']},
+                            saveAsImage: {show: true}
+                        }
+                    },
+                    tooltip: {
+                        show: true,
+                        trigger: 'axis'
+                    },
+                    xAxis: {
+                        type: 'time',
+                        axisTick: {
+                            show: false
+                        },
+                        axisLine: {
+                            lineStyle: {
+                                opacity: 0.1,
+                                color: '#3d4245'
+                            }
+                        },
+                        splitLine: {
+                            show: false
+                        },
+                        axisLabel: {
+                            formatter: format_time
+                        }
+                    },
+                    yAxis: {
+                        min: 1,
+                        max: 'dataMax',
+                        minInterval: 1,
+                        axisTick: {
+                            show: false
+                        },
+                        axisLine: {
+                            lineStyle: {
+                                opacity: 0,
+                                color: '#8c8f91'
+                            }
+                        },
+                        splitLine: {
+                            show: true,
+                            lineStyle: {
+                                width: 1,
+                                opacity: 0.5
+                            }
+                        }
+                    },
+                    series: [{
+                        name: 'CPU 负载',
+                        type: 'line',
+                        showSymbol: false,
+                        smooth: true,
+                        lineStyle: {
+                            normal: {
+                                width: 1
+                            }
+                        },
+                        data: (function () {
+                            var d = [];
+                            $.each(data.data, function(i, ele) {
+                                d.push([
+                                    ele['timestamp'] * 1000,
+                                    ele['cpu_load']
+                                ])
+                            });
+                            return d;
+                        })()
+                    }]
+                };
+
+                cpu_chart.setOption(option);
+            }
+        });
+    }
+
+    $(document).ready(function() {
+        $('body').addClass('add-transition');
+        $('.add-page-transition').on('click', function(){
+            var transAttr = $(this).attr('data-transition');
+            $('.add-transition').attr('class', 'add-transition');
+            $('.add-transition').addClass(transAttr);
+        });
+
+        cpu_chart = echarts.init(document.getElementById('cpu_chart'));
+        render_cpu_memory_chart(uuid);
+    });
+</script>
+<div class="container" style="padding-top: 100px;">
+    <div class="panel">
+        <div class="panel-body">
+            <h3 class="title-hero" style="display: inline;">
+                虚拟机实例详情
+            </h3>
+            <a href="/guests" class="btn btn-xs btn-default add-page-transition" data-transition="pt-page-moveFromLeft-init" style="margin-bottom: 4px; margin-left: 10px;">
+                <span class="glyph-icon icon-separator" style="transform: rotateY(-180deg);">
+                    <i class="glyph-icon icon-level-up"></i>
+                </span>
+                <span class="button-content">
+                    返回虚拟机列表
+                </span>
+            </a>
+            <div>
+                <div id="cpu_chart" style="width: 800px;height:280px;"></div>
+                <div id="traffic_chart" style="width: 600px;height:300px;"></div>
+            </div>
+        </div>
+    </div>
+</div>
+{% endblock body %}

+ 2 - 0
templates/layout.html

@@ -406,6 +406,8 @@
         <script type="text/javascript" src="{{ url_for('static', filename='widgets/chosen/chosen-demo.js') }}"></script>
         <!-- Touchspin -->
         <script type="text/javascript" src="{{ url_for('static', filename='widgets/touchspin/touchspin.js') }}"></script>
+        <script type="text/javascript" src="{{ url_for('static', filename='js-core/echarts.min.js') }}"></script>
+        <script type="text/javascript" src="{{ url_for('static', filename='js-core/date_format.js') }}"></script>
     </div>
 </body>
 </html>

+ 4 - 0
views/guest.py

@@ -169,6 +169,10 @@ def vnc(uuid):
     return render_template('vnc_lite.html', port=port, password=guest_ret['data']['vnc_password'])
 
 
+def detail(uuid):
+    return render_template('guest_detail.html', uuid=uuid)
+
+
 def success():
     return render_template('success.html', go_back_url='/guests', timeout=10000, title='提交成功',
                            message_title='创建实例的请求已被接受',

+ 1 - 0
views_route_table.py

@@ -16,6 +16,7 @@ add_rule_views(guest.blueprints, '', views_func='guest.show', methods=['GET'])
 add_rule_views(guest.blueprints, '/create', views_func='guest.create', methods=['GET', 'POST'])
 add_rule_views(guest.blueprints, '/success', views_func='guest.success', methods=['GET'])
 add_rule_views(guest.blueprint, '/vnc/<uuid>', views_func='guest.vnc', methods=['GET'])
+add_rule_views(guest.blueprint, '/detail/<uuid>', views_func='guest.detail', methods=['GET'])
 
 add_rule_views(disk.blueprints, '', views_func='disk.show', methods=['GET'])
 add_rule_views(disk.blueprints, '/create', views_func='disk.create', methods=['GET', 'POST'])

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor