James Iter 9 лет назад
Родитель
Сommit
cef0e3faac

+ 1 - 0
docs/todo.md

@@ -42,6 +42,7 @@
 - [ ] 初始化页面检测redis、mysql是否准备好
 - [ ] 初始化页面检测redis、mysql是否准备好
 - [ ] 允许删除创建失败的虚拟机
 - [ ] 允许删除创建失败的虚拟机
 - [ ] 日志里面加入操作轨迹
 - [ ] 日志里面加入操作轨迹
+- [ ] 日志实现简短字段及详情字段,详情字段用text,存储完整的日志文本。因为日志太长,无法在简短日志字段存放完整。
 - [x] 修改密码功能
 - [x] 修改密码功能
 - [ ] 密码[找回|重置]功能
 - [ ] 密码[找回|重置]功能
 - [ ] 前端页面校验字段有效值
 - [ ] 前端页面校验字段有效值

+ 1 - 0
misc/init.sql

@@ -163,6 +163,7 @@ CREATE TABLE IF NOT EXISTS log(
     timestamp BIGINT UNSIGNED NOT NULL,
     timestamp BIGINT UNSIGNED NOT NULL,
     host CHAR(15),
     host CHAR(15),
     message VARCHAR(255) NOT NULL DEFAULT '',
     message VARCHAR(255) NOT NULL DEFAULT '',
+    full_message TEXT,
     PRIMARY KEY (id))
     PRIMARY KEY (id))
     ENGINE=Innodb
     ENGINE=Innodb
     DEFAULT CHARSET=utf8;
     DEFAULT CHARSET=utf8;

+ 2 - 1
models/event_processor.py

@@ -43,7 +43,8 @@ class EventProcessor(object):
     @classmethod
     @classmethod
     def log_processor(cls):
     def log_processor(cls):
         cls.log.set(type=cls.message['type'], timestamp=cls.message['timestamp'], host=cls.message['host'],
         cls.log.set(type=cls.message['type'], timestamp=cls.message['timestamp'], host=cls.message['host'],
-                    message=cls.message['message'])
+                    message=cls.message['message'],
+                    full_message='' if cls.message['message'].__len__() < 255 else cls.message['message'])
 
 
         cls.log.create()
         cls.log.create()
 
 

+ 1 - 1
models/initialize.py

@@ -49,7 +49,7 @@ class Init(object):
         'guest_boot_jobs_wait_time': 600,
         'guest_boot_jobs_wait_time': 600,
         'db_charset': 'utf8',
         'db_charset': 'utf8',
         'db_pool_size': 10,
         'db_pool_size': 10,
-        'DEBUG': False,
+        'DEBUG': True,
         'jwt_algorithm': 'HS512',
         'jwt_algorithm': 'HS512',
         'token_ttl': 604800,
         'token_ttl': 604800,
         'SESSION_TYPE': 'filesystem',
         'SESSION_TYPE': 'filesystem',

+ 2 - 0
models/log.py

@@ -24,12 +24,14 @@ class Log(ORM):
         self.timestamp = kwargs.get('timestamp', 0)
         self.timestamp = kwargs.get('timestamp', 0)
         self.host = kwargs.get('host', None)
         self.host = kwargs.get('host', None)
         self.message = kwargs.get('message', None)
         self.message = kwargs.get('message', None)
+        self.full_message = kwargs.get('full_message', None)
 
 
     def set(self, **kwargs):
     def set(self, **kwargs):
         self.type = kwargs.get('type', None)
         self.type = kwargs.get('type', None)
         self.timestamp = kwargs.get('timestamp', 0)
         self.timestamp = kwargs.get('timestamp', 0)
         self.host = kwargs.get('host', None)
         self.host = kwargs.get('host', None)
         self.message = kwargs.get('message', None)
         self.message = kwargs.get('message', None)
+        self.full_message = kwargs.get('full_message', None)
 
 
     @staticmethod
     @staticmethod
     def get_filter_keywords():
     def get_filter_keywords():

+ 287 - 0
static/bootstrap/js/bootstrap-tooltip.js

@@ -0,0 +1,287 @@
+/* ===========================================================
+ * bootstrap-tooltip.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+  * =============================== */
+
+  var Tooltip = function (element, options) {
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.prototype = {
+
+    constructor: Tooltip
+
+  , init: function (type, element, options) {
+      var eventIn
+        , eventOut
+
+      this.type = type
+      this.$element = $(element)
+      this.options = this.getOptions(options)
+      this.enabled = true
+
+      if (this.options.trigger == 'click') {
+        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+      } else if (this.options.trigger != 'manual') {
+        eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
+        eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
+        this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+      }
+
+      this.options.selector ?
+        (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+        this.fixTitle()
+    }
+
+  , getOptions: function (options) {
+      options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
+
+      if (options.delay && typeof options.delay == 'number') {
+        options.delay = {
+          show: options.delay
+        , hide: options.delay
+        }
+      }
+
+      return options
+    }
+
+  , enter: function (e) {
+      var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+      if (!self.options.delay || !self.options.delay.show) return self.show()
+
+      clearTimeout(this.timeout)
+      self.hoverState = 'in'
+      this.timeout = setTimeout(function() {
+        if (self.hoverState == 'in') self.show()
+      }, self.options.delay.show)
+    }
+
+  , leave: function (e) {
+      var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+      if (this.timeout) clearTimeout(this.timeout)
+      if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+      self.hoverState = 'out'
+      this.timeout = setTimeout(function() {
+        if (self.hoverState == 'out') self.hide()
+      }, self.options.delay.hide)
+    }
+
+  , show: function () {
+      var $tip
+        , inside
+        , pos
+        , actualWidth
+        , actualHeight
+        , placement
+        , tp
+
+      if (this.hasContent() && this.enabled) {
+        $tip = this.tip()
+        this.setContent()
+
+        if (this.options.animation) {
+          $tip.addClass('fade')
+        }
+
+        placement = typeof this.options.placement == 'function' ?
+          this.options.placement.call(this, $tip[0], this.$element[0]) :
+          this.options.placement
+
+        inside = /in/.test(placement)
+
+        $tip
+          .detach()
+          .css({ top: 0, left: 0, display: 'block' })
+          .insertAfter(this.$element)
+
+        pos = this.getPosition(inside)
+
+        actualWidth = $tip[0].offsetWidth
+        actualHeight = $tip[0].offsetHeight
+
+        switch (inside ? placement.split(' ')[1] : placement) {
+          case 'bottom':
+            tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+            break
+          case 'top':
+            tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+            break
+          case 'left':
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+            break
+          case 'right':
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+            break
+        }
+
+        $tip
+          .offset(tp)
+          .addClass(placement)
+          .addClass('in')
+      }
+    }
+
+  , setContent: function () {
+      var $tip = this.tip()
+        , title = this.getTitle()
+
+      $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+      $tip.removeClass('fade in top bottom left right')
+    }
+
+  , hide: function () {
+      var that = this
+        , $tip = this.tip()
+
+      $tip.removeClass('in')
+
+      function removeWithAnimation() {
+        var timeout = setTimeout(function () {
+          $tip.off($.support.transition.end).detach()
+        }, 500)
+
+        $tip.one($.support.transition.end, function () {
+          clearTimeout(timeout)
+          $tip.detach()
+        })
+      }
+
+      $.support.transition && this.$tip.hasClass('fade') ?
+        removeWithAnimation() :
+        $tip.detach()
+
+      return this
+    }
+
+  , fixTitle: function () {
+      var $e = this.$element
+      if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+        $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+      }
+    }
+
+  , hasContent: function () {
+      return this.getTitle()
+    }
+
+  , getPosition: function (inside) {
+      return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
+        width: this.$element[0].offsetWidth
+      , height: this.$element[0].offsetHeight
+      })
+    }
+
+  , getTitle: function () {
+      var title
+        , $e = this.$element
+        , o = this.options
+
+      title = $e.attr('data-original-title')
+        || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+      return title
+    }
+
+  , tip: function () {
+      return this.$tip = this.$tip || $(this.options.template)
+    }
+
+  , validate: function () {
+      if (!this.$element[0].parentNode) {
+        this.hide()
+        this.$element = null
+        this.options = null
+      }
+    }
+
+  , enable: function () {
+      this.enabled = true
+    }
+
+  , disable: function () {
+      this.enabled = false
+    }
+
+  , toggleEnabled: function () {
+      this.enabled = !this.enabled
+    }
+
+  , toggle: function (e) {
+      var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+      self[self.tip().hasClass('in') ? 'hide' : 'show']()
+    }
+
+  , destroy: function () {
+      this.hide().$element.off('.' + this.type).removeData(this.type)
+    }
+
+  }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+  * ========================= */
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('tooltip')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tooltip.Constructor = Tooltip
+
+  $.fn.tooltip.defaults = {
+    animation: true
+  , placement: 'top'
+  , selector: false
+  , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+  , trigger: 'hover'
+  , title: ''
+  , delay: 0
+  , html: false
+  }
+
+
+ /* TOOLTIP NO CONFLICT
+  * =================== */
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(window.jQuery);

+ 2 - 2
templates/guest_create.html

@@ -158,7 +158,7 @@
                             <div class="row form-group">
                             <div class="row form-group">
                                 <label class="col-sm-2 control-label">登录密码:</label>
                                 <label class="col-sm-2 control-label">登录密码:</label>
                                 <div class="col-sm-6">
                                 <div class="col-sm-6">
-                                    <input type="password" name="password" title="实例密码" class="form-control" id="password">
+                                    <input type="password" name="password" title="实例密码" class="form-control" id="password" placeholder="如不填写,系统自动生成16位随机密码">
                                 </div>
                                 </div>
                             </div>
                             </div>
                             <div class="row form-group">
                             <div class="row form-group">
@@ -171,7 +171,7 @@
                             <div class="row form-group">
                             <div class="row form-group">
                                 <label class="col-sm-2 control-label">实例名称:</label>
                                 <label class="col-sm-2 control-label">实例名称:</label>
                                 <div class="col-sm-6">
                                 <div class="col-sm-6">
-                                    <input id="remark" name="remark" type="text" title="实例名称" class="form-control">
+                                    <input id="remark" name="remark" type="text" title="实例名称" class="form-control" placeholder="如不填写,系统自动默认生成">
                                 </div>
                                 </div>
                             </div>
                             </div>
                         </div>
                         </div>

+ 1 - 0
templates/layout.html

@@ -393,6 +393,7 @@
         <script type="text/javascript" src="{{ url_for('static', filename='tether/js/tether.js') }}"></script>
         <script type="text/javascript" src="{{ url_for('static', filename='tether/js/tether.js') }}"></script>
         <script type="text/javascript" src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"></script>
         <script type="text/javascript" src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"></script>
         <script type="text/javascript" src="{{ url_for('static', filename='bootstrap/js/affix.js') }}"></script>
         <script type="text/javascript" src="{{ url_for('static', filename='bootstrap/js/affix.js') }}"></script>
+        <!-- <script type="text/javascript" src="{{ url_for('static', filename='bootstrap/js/bootstrap-tooltip.js') }}"></script> -->
         <!-- Bootstrap Dropdown -->
         <!-- Bootstrap Dropdown -->
         <!-- <script type="text/javascript" src="{{ url_for('static', filename='widgets/dropdown/dropdown.js') }}"></script> -->
         <!-- <script type="text/javascript" src="{{ url_for('static', filename='widgets/dropdown/dropdown.js') }}"></script> -->
         <!-- Bootstrap Tooltip -->
         <!-- Bootstrap Tooltip -->

+ 1 - 1
templates/logs.html

@@ -176,7 +176,7 @@
                     </td>
                     </td>
                     <td>{{ format_datetime_by_ts(item.timestamp) }}</td>
                     <td>{{ format_datetime_by_ts(item.timestamp) }}</td>
                     <td>{{ item.host }}</td>
                     <td>{{ item.host }}</td>
-                    <td>{{ item.message }}</td>
+                    <td>{{ item.message }}<a href="#" data-toggle="tooltip" data-placement="left" style="color: royalblue;{% if item.full_message is none or item.full_message|length < 256 %}display: none;{% endif %}" title="{{ item.full_message }}">More</a></td>
                 </tr>
                 </tr>
                 {% endfor %}
                 {% endfor %}
                 </tbody>
                 </tbody>

+ 6 - 6
views/error_pages.py

@@ -12,13 +12,13 @@ __contact__ = 'james.iter.cn@gmail.com'
 __copyright__ = '(c) 2017 by James Iter.'
 __copyright__ = '(c) 2017 by James Iter.'
 
 
 
 
-@app.errorhandler(404)
-def page_not_found(error):
-    return render_template('404.html'), 404
+# @app.errorhandler(404)
+# def page_not_found(error):
+#     return render_template('404.html'), 404
 
 
 
 
-@app.errorhandler(500)
-def page_not_found(error):
-    return render_template('500.html'), 500
+# @app.errorhandler(500)
+# def page_not_found(error):
+#     return render_template('500.html'), 500