Bläddra i källkod

add alarm events in dashboard

laiwei 8 år sedan
förälder
incheckning
cb85716d1f

+ 13 - 5
rrd/config.py

@@ -12,11 +12,19 @@ API_ADDR = "http://127.0.0.1:8080/api/v1"
 
 # portal database
 # TODO: read from api instead of db
-DB_HOST = "127.0.0.1"
-DB_PORT = 3306
-DB_USER = "root"
-DB_PASS = ""
-DB_NAME = "falcon_portal"
+PORTAL_DB_HOST = "127.0.0.1"
+PORTAL_DB_PORT = 3306
+PORTAL_DB_USER = "root"
+PORTAL_DB_PASS = ""
+PORTAL_DB_NAME = "falcon_portal"
+
+# alarm database
+# TODO: read from api instead of db
+ALARM_DB_HOST = "127.0.0.1"
+ALARM_DB_PORT = 3306
+ALARM_DB_USER = "root"
+ALARM_DB_PASS = ""
+ALARM_DB_NAME = "alarms"
 
 # ldap config
 LDAP_ENABLED = False

+ 134 - 5
rrd/model/portal/alarm.py

@@ -1,15 +1,144 @@
 # -*- coding:utf-8 -*-
 from .bean import Bean
+from rrd.store import alarm_db
+
+class Event(Bean):
+    _db = alarm_db
+    _tbl = 'events'
+    _cols = 'id, event_caseId, step, cond, status, timestamp'
+
+    def __init__(self, id, event_caseId, step, cond, status, timestamp):
+        self.id = id
+        self.event_caseId = event_caseId
+        self.step = step
+        self.cond = cond
+        self.status = status
+        self.timestamp = timestamp
+    
+    @classmethod
+    def query(cls, page, limit, event_caseId):
+        where = 'event_caseId = %s'
+        params = [event_caseId]
+        
+        vs = cls.select_vs(where=where, params=params, page=page, limit=limit, order='timestamp desc')
+        total = cls.total(where, params)
+
+        return vs, total
+
 
 class EventCase(Bean):
+    _db = alarm_db
     _tbl = 'event_cases'
-    _cols = ''
+    _cols = 'id, endpoint, metric, func, cond, note, max_step, current_step, priority, status, timestamp, update_at, closed_at, closed_note, user_modified, tpl_creator, expression_id, strategy_id, template_id, process_note, process_status'
 
-class Event(Bean):
-    _tbl = 'events'
-    _cols = ''
+    def __init__(self, id, endpoint, metric, func, cond, note, max_step, current_step, priority,\
+            status, timestamp, update_at, closed_at, closed_note, user_modified, tpl_creator, \
+            expression_id, strategy_id, template_id, process_note, process_status):
+        self.id = id
+        self.endpoint = endpoint
+        self.metric = metric
+        self.func = func
+        self.cond = cond
+        self.note = note
+        self.max_step = max_step
+        self.current_step = current_step
+        self.priority = priority
+        self.status = status
+        self.timestamp = timestamp
+        self.update_at = update_at
+        self.closed_at = closed_at
+        self.closed_note = closed_note
+        self.user_modified = user_modified
+        self.tpl_creator = tpl_creator
+        self.expression_id = expression_id
+        self.strategy_id = strategy_id
+        self.template_id = template_id
+        self.process_note = process_note
+        self.process_status = process_status
+
+    @classmethod
+    def query(cls, page, limit, endpoint_query, metric_query, status):
+        where = '1=1'
+        params = []
+        if status == "PROBLEM" or status == "OK":
+            where = 'status = %s'
+            params = [status]
+
+        if endpoint_query != "":
+            where += ' and endpoint like %s'
+            params.append('%' + endpoint_query + '%')
+
+        if metric_query != "":
+            where += ' and metric like %s'
+            params.append('%' + metric_query + '%')
+
+        vs = cls.select_vs(where=where, params=params, page=page, limit=limit, order='update_at desc')
+        total = cls.total(where, params)
+
+        return vs, total
 
 class EventNote(Bean):
+    _db = alarm_db
     _tbl = 'event_note'
-    _cols = ''
+    _cols = 'id, event_caseId, note, case_id, status, timestamp, user_id'
+
+    def __init__(self, id, event_caseId, note, case_id, status, timestamp, user_id):
+        self.id = id
+        self.event_caseId = event_caseId
+        self.note = note
+        self.case_id = case_id
+        self.status = status
+        self.timestamp = timestamp
+        self.user_id = user_id
 
+#desc events;
+#+--------------+------------------+------+-----+-------------------+-----------------------------+
+#| Field        | Type             | Null | Key | Default           | Extra                       |
+#+--------------+------------------+------+-----+-------------------+-----------------------------+
+#| id           | mediumint(9)     | NO   | PRI | NULL              | auto_increment              |
+#| event_caseId | varchar(50)      | YES  | MUL | NULL              |                             |
+#| step         | int(10) unsigned | YES  |     | NULL              |                             |
+#| cond         | varchar(200)     | NO   |     | NULL              |                             |
+#| status       | int(3) unsigned  | YES  |     | 0                 |                             |
+#| timestamp    | timestamp        | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+#+--------------+------------------+------+-----+-------------------+-----------------------------+
+#
+#desc event_note;
+#+--------------+------------------+------+-----+-------------------+-----------------------------+
+#| Field        | Type             | Null | Key | Default           | Extra                       |
+#+--------------+------------------+------+-----+-------------------+-----------------------------+
+#| id           | mediumint(9)     | NO   | PRI | NULL              | auto_increment              |
+#| event_caseId | varchar(50)      | YES  | MUL | NULL              |                             |
+#| note         | varchar(300)     | YES  |     | NULL              |                             |
+#| case_id      | varchar(20)      | YES  |     | NULL              |                             |
+#| status       | varchar(15)      | YES  |     | NULL              |                             |
+#| timestamp    | timestamp        | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+#| user_id      | int(10) unsigned | YES  | MUL | NULL              |                             |
+#+--------------+------------------+------+-----+-------------------+-----------------------------+
+#
+#desc event_cases;
+#+----------------+------------------+------+-----+-------------------+-----------------------------+
+#| Field          | Type             | Null | Key | Default           | Extra                       |
+#+----------------+------------------+------+-----+-------------------+-----------------------------+
+#| id             | varchar(50)      | NO   | PRI | NULL              |                             |
+#| endpoint       | varchar(100)     | NO   | MUL | NULL              |                             |
+#| metric         | varchar(200)     | NO   |     | NULL              |                             |
+#| func           | varchar(50)      | YES  |     | NULL              |                             |
+#| cond           | varchar(200)     | NO   |     | NULL              |                             |
+#| note           | varchar(500)     | YES  |     | NULL              |                             |
+#| max_step       | int(10) unsigned | YES  |     | NULL              |                             |
+#| current_step   | int(10) unsigned | YES  |     | NULL              |                             |
+#| priority       | int(6)           | NO   |     | NULL              |                             |
+#| status         | varchar(20)      | NO   |     | NULL              |                             |
+#| timestamp      | timestamp        | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+#| update_at      | timestamp        | YES  |     | NULL              |                             |
+#| closed_at      | timestamp        | YES  |     | NULL              |                             |
+#| closed_note    | varchar(250)     | YES  |     | NULL              |                             |
+#| user_modified  | int(10) unsigned | YES  |     | NULL              |                             |
+#| tpl_creator    | varchar(64)      | YES  |     | NULL              |                             |
+#| expression_id  | int(10) unsigned | YES  |     | NULL              |                             |
+#| strategy_id    | int(10) unsigned | YES  |     | NULL              |                             |
+#| template_id    | int(10) unsigned | YES  |     | NULL              |                             |
+#| process_note   | mediumint(9)     | YES  |     | NULL              |                             |
+#| process_status | varchar(20)      | YES  |     | unresolved        |                             |
+#+----------------+------------------+------+-----+-------------------+-----------------------------+

+ 9 - 9
rrd/model/portal/bean.py

@@ -2,11 +2,11 @@
 __author__ = 'Ulric Qin'
 from rrd.store import db
 
-
 class Bean(object):
     _tbl = ''
     _id = 'id'
     _cols = ''
+    _db = db
 
     @classmethod
     def insert(cls, data=None):
@@ -17,27 +17,27 @@ class Bean(object):
         keys = data.keys()
         safe_keys = ['`%s`' % k for k in keys]
         sql = 'INSERT INTO `%s`(%s) VALUES(%s)' % (cls._tbl, ','.join(safe_keys), '%s' + ',%s' * (size - 1))
-        last_id = db.insert(sql, [data[key] for key in keys])
+        last_id = cls._db.insert(sql, [data[key] for key in keys])
         return last_id
 
     @classmethod
     def delete(cls, where=None, params=None):
         sql = 'DELETE FROM `%s`' % cls._tbl
         if not where:
-            return db.update(sql)
+            return cls._db.update(sql)
 
         sql += ' WHERE ' + where
-        return db.update(sql, params)
+        return cls._db.update(sql, params)
 
     @classmethod
     def delete_one(cls, pk=None):
         sql = 'DELETE FROM `%s` WHERE %s = %%s' % (cls._tbl, cls._id)
-        return db.update(sql, [pk])
+        return cls._db.update(sql, [pk])
 
     @classmethod
     def update(cls, clause=None, params=None):
         sql = 'UPDATE `%s` SET %s' % (cls._tbl, clause)
-        return db.update(sql, params)
+        return cls._db.update(sql, params)
 
     @classmethod
     def update_dict(cls, data=None, where='', params=None):
@@ -83,7 +83,7 @@ class Bean(object):
                 offset = 0
             sql = '%s OFFSET %s' % (sql, offset)
 
-        return db.query_all(sql, params)
+        return cls._db.query_all(sql, params)
 
     @classmethod
     def select_vs(cls, where=None, params=None, order=None, limit=None, page=None, offset=None):
@@ -114,11 +114,11 @@ class Bean(object):
     def total(cls, where=None, params=None):
         sql = 'SELECT COUNT(1) FROM `%s`' % cls._tbl
         if not where:
-            ret = db.query_column(sql)
+            ret = cls._db.query_column(sql)
             return ret[0]
 
         sql += ' WHERE ' + where
-        ret = db.query_column(sql, params)
+        ret = cls._db.query_column(sql, params)
         return ret[0]
 
     @classmethod

+ 0 - 1
rrd/model/portal/host.py

@@ -3,7 +3,6 @@ __author__ = 'Ulric Qin'
 from .bean import Bean
 from rrd.store import db
 
-
 class Host(Bean):
     _tbl = 'host'
     _cols = 'id, hostname, maintain_begin, maintain_end'

+ 83 - 34
rrd/static/js/portal.js

@@ -59,7 +59,7 @@ function create_hostgroup() {
         handle_quietly(json, function () {
             window.location.reload();
         });
-    });
+    }, "json");
 }
 
 function delete_hostgroup(group_id) {
@@ -80,7 +80,7 @@ function edit_hostgroup(group_id, grp_name) {
             handle_quietly(json, function () {
                 location.reload();
             });
-        });
+        }, "json");
     })
 }
 
@@ -91,7 +91,7 @@ function rename_hostgroup() {
         handle_quietly(json, function () {
             window.location.href = '/?q=' + new_str;
         });
-    });
+    }, "json");
 }
 
 function bind_plugin(group_id) {
@@ -100,7 +100,7 @@ function bind_plugin(group_id) {
         handle_quietly(json, function () {
             location.reload();
         });
-    });
+    }, "json");
 }
 
 function unbind_plugin(plugin_id) {
@@ -148,7 +148,7 @@ function remove_hosts() {
         handle_quietly(json, function () {
             location.reload();
         });
-    });
+    }, "json");
 }
 
 function maintain() {
@@ -178,7 +178,7 @@ function maintain() {
         handle_quietly(json, function () {
             location.reload();
         });
-    });
+    }, "json");
 }
 
 function no_maintain() {
@@ -197,7 +197,7 @@ function no_maintain() {
         handle_quietly(json, function () {
             location.reload();
         });
-    });
+    }, "json");
 }
 
 function batch_add_host() {
@@ -213,7 +213,7 @@ function batch_add_host() {
         } else {
             $("#message").html(json.data);
         }
-    });
+    }, "json");
 }
 
 function host_unbind_group(host_id, group_id) {
@@ -266,8 +266,7 @@ function update_expression() {
         },
         function (json) {
             handle_quietly(json);
-        }
-    );
+        }, "json");
 }
 
 function pause_expression(id) {
@@ -356,7 +355,7 @@ function create_template() {
         } else {
             location.href = '/portal/template/update/' + json.id;
         }
-    });
+    }, "json");
 }
 
 function make_select2_for_template(selector) {
@@ -441,7 +440,7 @@ function update_template() {
     var parent_id = $("#parent_id").val();
     $.post('/portal/template/rename/' + tpl_id, {'name': name, 'parent_id': parent_id}, function (json) {
         handle_quietly(json);
-    });
+    }, "json");
 }
 
 function save_action_for_tpl(tpl_id) {
@@ -460,8 +459,7 @@ function save_action_for_tpl(tpl_id) {
         },
         function (json) {
             handle_quietly(json);
-        }
-    );
+        }, "json");
 }
 
 function goto_strategy_add_div() {
@@ -489,7 +487,7 @@ function save_strategy() {
         handle_quietly(json, function () {
             location.reload();
         });
-    })
+    }, "json")
 }
 
 function clone_strategy(sid) {
@@ -582,7 +580,7 @@ function node_bind_tpl() {
         handle_quietly(json, function () {
             location.reload();
         });
-    });
+    }, "json");
 }
 
 function create_cluster_monitor_metric(grp_id) {
@@ -597,7 +595,7 @@ function create_cluster_monitor_metric(grp_id) {
         handle_quietly(json, function () {
             location.href = "/group/" + grp_id + "/cluster";
         });
-    })
+    }, "json")
 }
 
 function update_cluster_monitor_metric(cluster_id, grp_id) {
@@ -611,7 +609,7 @@ function update_cluster_monitor_metric(cluster_id, grp_id) {
         'grp_id': grp_id
     }, function (json) {
         handle_quietly(json);
-    });
+    }, "json");
 }
 
 function delete_cluster_monitor_item(cluster_id) {
@@ -620,21 +618,37 @@ function delete_cluster_monitor_item(cluster_id) {
             handle_quietly(json, function () {
                 location.reload();
             })
-        });
+        }, "json");
     }, function () {
         return false;
     });
 }
 
 // - alarm-dash business function -
-function events_all_select() {
+function alarm_case_all_select() {
+    var boxes = $("input[type=checkbox]");
+    for (var i = 0; i < boxes.length; i++) {
+        boxes[i].checked="checked";
+    }
+}
+function alarm_case_event_all_select() {
     var boxes = $("input[type=checkbox]");
     for (var i = 0; i < boxes.length; i++) {
         boxes[i].checked="checked";
     }
 }
 
-function events_reverse_select() {
+function alarm_case_reverse_select() {
+    var boxes = $("input[type=checkbox]");
+    for (var i = 0; i < boxes.length; i++) {
+        if (boxes[i].checked) {
+            boxes[i].checked=""
+        } else {
+            boxes[i].checked="checked";
+        }
+    }
+}
+function alarm_case_event_reverse_select() {
     var boxes = $("input[type=checkbox]");
     for (var i = 0; i < boxes.length; i++) {
         if (boxes[i].checked) {
@@ -645,7 +659,7 @@ function events_reverse_select() {
     }
 }
 
-function events_batch_solve() {
+function alarm_case_batch_rm() {
     var boxes = $("input[type=checkbox]");
     var ids = []
     for (var i = 0; i < boxes.length; i++) {
@@ -654,21 +668,56 @@ function events_batch_solve() {
         }
     }
 
-    $.post("/portal/event/solve", {"ids": ids.join(',,')}, function(msg){
-        if (msg=="") {
-            location.reload();
-        } else {
-            alert(msg);
-        }
+    my_confirm('确定要删除???', ['确定', '取消'], function () {
+        $.post('/portal/alarm-dash/case/delete', {"ids": ids.join(',')}, function (json) {
+            handle_quietly(json, function () {
+                location.reload();
+            });
+        }, "json");
+    }, function () {
+        return false;
     });
 }
 
-function events_solve(id) {
-    $.post("/portal/event/solve", {"ids": id}, function(msg){
-        if (msg=="") {
-            location.reload();
-        } else {
-            alert(msg);
+function alarm_case_rm(id) {
+    my_confirm('确定要删除???', ['确定', '取消'], function () {
+        $.post('/portal/alarm-dash/case/delete', {"ids": id}, function (json) {
+            handle_quietly(json, function () {
+                location.reload();
+            });
+        }, "json");
+    }, function () {
+        return false;
+    });
+}
+
+function alarm_case_event_rm(id) {
+    my_confirm('确定要删除???', ['确定', '取消'], function () {
+        $.post('/portal/alarm-dash/case/event/delete', {"ids": id}, function (json) {
+            handle_quietly(json, function () {
+                location.reload();
+            });
+        }, "json");
+    }, function () {
+        return false;
+    });
+}
+function alarm_case_event_batch_rm() {
+    var boxes = $("input[type=checkbox]");
+    var ids = []
+    for (var i = 0; i < boxes.length; i++) {
+        if (boxes[i].checked) {
+            ids.push($(boxes[i]).attr("alarm"))
         }
+    }
+
+    my_confirm('确定要删除???', ['确定', '取消'], function () {
+        $.post('/portal/alarm-dash/case/event/delete', {"ids": ids.join(',')}, function (json) {
+            handle_quietly(json, function () {
+                location.reload();
+            });
+        }, "json");
+    }, function () {
+        return false;
     });
 }

+ 23 - 6
rrd/store.py

@@ -2,14 +2,30 @@
 from rrd import config
 import MySQLdb
 
+portal_db_cfg = {
+        "DB_HOST": config.PORTAL_DB_HOST,
+        "DB_PORT": config.PORTAL_DB_PORT,
+        "DB_USER": config.PORTAL_DB_USER,
+        "DB_PASS": config.PORTAL_DB_PASS,
+        "DB_NAME": config.PORTAL_DB_NAME,
+}
+
+alarm_db_cfg = {
+        "DB_HOST": config.ALARM_DB_HOST,
+        "DB_PORT": config.ALARM_DB_PORT,
+        "DB_USER": config.ALARM_DB_USER,
+        "DB_PASS": config.ALARM_DB_PASS,
+        "DB_NAME": config.ALARM_DB_NAME,
+}
+
 def connect_db(cfg):
     try:
         conn = MySQLdb.connect(
-            host=cfg.DB_HOST,
-            port=cfg.DB_PORT,
-            user=cfg.DB_USER,
-            passwd=cfg.DB_PASS,
-            db=cfg.DB_NAME,
+            host=cfg['DB_HOST'],
+            port=cfg['DB_PORT'],
+            user=cfg['DB_USER'],
+            passwd=cfg['DB_PASS'],
+            db=cfg['DB_NAME'],
             use_unicode=True,
             charset="utf8")
         return conn
@@ -105,4 +121,5 @@ class DB(object):
                 self.conn = None
 
 
-db = DB(config)
+db = DB(portal_db_cfg)
+alarm_db = DB(alarm_db_cfg)

+ 1 - 1
rrd/templates/navbar.html

@@ -19,7 +19,7 @@
           <li {%if g.nav_menu == "p_template"%}class="active"{%endif%}><a href="/portal/template">Templates</a></li>
           <li {%if g.nav_menu == "p_expression"%}class="active"{%endif%}><a href="/portal/expression">Expressions</a></li>
           <li {%if g.nav_menu == "p_nodata"%}class="active"{%endif%}><a href="/portal/nodata">Nodata</a></li>
-          <li {%if g.nav_menu == "p_alarm-dash"%}class="active"{%endif%}><a href="/portal/alarm-dash">Alarm-Dashboard</a></li>
+          <li {%if g.nav_menu == "p_alarm-dash"%}class="active"{%endif%}><a href="/portal/alarm-dash/case">Alarm-Dashboard</a></li>
           <li class="dropdown">
               <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
                   {%if g.user%} Welcome {{g.user.name}}{%else%}Sign in{%endif%}<span class="caret"></span></a>

+ 60 - 0
rrd/templates/portal/alarm/case.html

@@ -0,0 +1,60 @@
+{% extends "portal/layout.html" %}
+{% block content %}
+
+    <div class="panel panel-danger">
+        <div class="panel-heading">
+            <h3 class="panel-title">报警case列表</h3>
+        </div>
+        <div class="panel-body">
+            <div class="alarms">
+                {%for case in cases%}
+                <div class="alarm">
+                    <input type="checkbox" alarm="{{case.id}}">
+                    {{case.status}} P{{case.priority}}
+                    [第<span class="orange">#{{case.current_step}}</span>次/最大{{case.max_step}}次]
+                    <span class="orange">{{case.timestamp|time_duration}}</span>
+                    <span class="gray">[</span>
+                    <a href="/portal/template/view/{{case.template_id}}" target="_blank">template</a>
+
+                    {%if case.strategy_id>0%}
+                    <span class="cut-line">¦</span>
+                    <a href="/portal/strategy/{{case.strategy_id}}" target="_blank">strategy</a>
+                    {%endif%}
+
+                    {%if case.expression_id>0%}
+                    <span class="cut-line">¦</span>
+                    <a href="/portal/expression/view/{{case.expression_id}}" target="_blank">expression</a>
+                    {%endif%}
+
+                    <span class="cut-line">¦</span>
+                    <a href="javascript:alarm_case_rm('{{case.id}}');">delete</a>
+                    <span class="cut-line">¦</span>
+                    <a href="/portal/alarm-dash/case/event?case_id={{case.id}}">展开事件列表</a>
+                    <span class="gray">]</span>
+                    </br>
+
+                    <span style="padding-left:17px;"> {{case.endpoint}} 
+                        <span class="cut-line">¦</span> 
+                        {{case.metric}}
+                        <span class="cut-line">¦</span> 
+                        {{case.func}} 
+                        <span class="cut-line">¦</span> 
+                        {{case.cond}}</span>
+                        <span class="cut-line">¦</span> 
+                        <span class="gray">note: {{case.note}}</span>
+                </div>
+                <hr>
+                {%endfor%}
+                <a href="javascript:alarm_case_all_select();">全选</a>/
+                <a href="javascript:alarm_case_reverse_select();">反选</a>
+                <button class="btn btn-warning btn-sm" onclick="alarm_case_batch_rm();">批量删除</button>
+            </div>
+        </div>
+
+        {% import "portal/blocks.html" as blocks %}
+        <div class="pull-left">
+            {{ blocks.pager('/portal/alarm-dash/case', total, limit, page) }}
+        </div>
+
+    </div>
+{%endblock%}

+ 45 - 0
rrd/templates/portal/alarm/case_events.html

@@ -0,0 +1,45 @@
+{% extends "portal/layout.html" %}
+{% block content %}
+
+    <div class="panel panel-danger">
+        <div class="panel-heading">
+            <h4 class="panel-title">case:{{case.id}} 所关联的事件列表 <a href="/portal/alarm-dash/case" class="pull-right">返回</a></h4>
+        </div>
+        <div class="panel-body">
+            <div class="alarms">
+                {%for evt in case_events%}
+                <div class="alarm">
+                    <input type="checkbox" alarm="{{evt.id}}">
+                    {%if evt.status==0%}PROBLEM{%else%}OK{%endif%} P{{case.priority}}
+                    [第<span class="orange">#{{evt.step}}</span>次/最大{{case.max_step}}次]
+                    <span class="orange">{{evt.timestamp|time_duration}}</span>
+                    <span class="gray">[</span>
+                    <a href="javascript:alarm_case_event_rm('{{evt.id}}');">delete</a>
+                    <span class="gray">]</span>
+                    <br/>
+
+                    <span style="padding-left:17px;"> {{case.endpoint}} 
+                        <span class="cut-line">¦</span> 
+                        {{case.metric}}
+                        <span class="cut-line">¦</span> 
+                        {{case.func}} 
+                        <span class="cut-line">¦</span> 
+                        {{evt.cond}}</span>
+                        <span class="cut-line">¦</span> 
+                        <span class="gray">note: {{case.note}}</span>
+                </div>
+                <hr>
+                {%endfor%}
+                <a href="javascript:alarm_case_event_all_select();">全选</a>/
+                <a href="javascript:alarm_case_event_reverse_select();">反选</a>
+                <button class="btn btn-warning btn-sm" onclick="alarm_case_event_batch_rm();">批量删除</button>
+            </div>
+        </div>
+
+        {% import "portal/blocks.html" as blocks %}
+        <div class="pull-left">
+            {{ blocks.pager('/portal/alarm-dash/case/event?case_id='+case.id, total, limit, page) }}
+        </div>
+
+    </div>
+{%endblock%}

+ 0 - 30
rrd/templates/portal/alarm/index.html

@@ -1,30 +0,0 @@
-{% extends "portal/layout.html" %}
-{% block content %}
-
-    <div class="panel panel-danger">
-        <div class="panel-heading">
-            <h3 class="panel-title">未恢复的报警</h3>
-        </div>
-        <div class="panel-body">
-            <div class="alarms">
-                {{range Events}}
-                <div class="alarm">
-                    <input type="checkbox" alarm="{{Id}}">
-                    [P{{Priority}} #{{CurrentStep}}/{{MaxStep}}] {{Counter}}<br>
-                    <span style="padding-left:17px;">{{Func}} {{LeftValue}}{{Operator}}{{RightValue}} {{Note}}</span>
-                    <span class="orange">{{duration Now Timestamp}}</span>
-                    <span class="gray">[</span>
-                    <a href="{{Link}}" target="_blank">config</a>
-                    <span class="cut-line">¦</span>
-                    <a href="javascript:events_solve('{{.Id}}');">solved</a>
-                    <span class="gray">]</span>
-                </div>
-                <hr>
-                {{end}}
-                <a href="javascript:events_all_select();">全选</a>/<a href="javascript:events_reverse_select();">反选</a>
-                <button class="btn btn-warning btn-sm" onclick="events_batch_solve();">标记选中部分为已解决</button>
-            </div>
-        </div>
-    </div>
-
-{%endblock%}

+ 3 - 3
rrd/templates/portal/template/list.html

@@ -33,10 +33,10 @@
         <tbody>
         {% for v in data.vs %}
             <tr>
-                <td><a target="_blank" href="{{ url_for('template_view_get', tpl_id=v.id) }}">{{ v.tpl_name }}</a></td>
-                <td>{% if v.parent %}<a target="_blank" href="{{ url_for('template_view_get', tpl_id=v.parent.id) }}">{{ v.parent.tpl_name }}</a>{% endif %}</td>
+                <td><a href="{{ url_for('template_view_get', tpl_id=v.id) }}">{{ v.tpl_name }}</a></td>
+                <td>{% if v.parent %}<a href="{{ url_for('template_view_get', tpl_id=v.parent.id) }}">{{ v.parent.tpl_name }}</a>{% endif %}</td>
                 <td>
-                    <a target="_blank" href="/user/about/{{ v.create_user }}">{{ v.create_user }}</a>
+                    <a href="/user/about/{{ v.create_user }}">{{ v.create_user }}</a>
                 </td>
                 <td>
                     <a data-toggle="tooltip" data-placement="top" title="克隆一份该模板" href="javascript:fork_template('{{ v.id }}');" style="text-decoration: none;">

+ 4 - 3
rrd/templates/portal/template/view.html

@@ -72,19 +72,20 @@
         <div class="panel-body">
 
             <div class="pull-right">
-                <a target="_blank" href="/team/list">去UIC修改报警组内成员</a>
+                <a target="_blank" href="/team/list">修改报警组内成员</a>
             </div>
 
             {% if data.tpl.action %}
-                uic: {{ data.tpl.action.uic_href() | safe }}<br>
+                告警接收组: {{ data.tpl.action.uic_href() | safe }}<br>
             {% else %}
                 没有配置报警接收人哦<br>
             {% endif %}
+            <hr/>
 
             {% if data.tpl.action.url %}
 
                 <div class="mt10">
-                    callback: {{ data.tpl.action.url }}
+                    Callback: {{ data.tpl.action.url }}
                 </div>
                 <pre><code>#callback request payload:
 

+ 20 - 0
rrd/view/__init__.py

@@ -1,5 +1,6 @@
 #-*- coding:utf-8 -*-
 import datetime
+import time
 from flask import g, session, request, redirect
 
 from rrd import app
@@ -11,6 +12,25 @@ def fmt_time_filter(value, pattern="%Y-%m-%d %H:%M"):
         return ''
     return datetime.datetime.fromtimestamp(value).strftime(pattern)
 
+@app.template_filter('time_duration')
+def time_duration(v):
+    d = time.time() - time.mktime(v.timetuple())
+
+    if d <= 60:
+        return "just now"
+    if d <= 120:
+            return "1 minute ago"
+    if d <= 3600:
+        return "%d minutes ago" % (d/60)
+    if d <= 7200:
+        return "1 hour ago"
+    if d <= 3600*24:
+        return "%d hours ago" % (d/3600)
+    if d <= 3600*24*2:
+        return "1 day ago"
+
+    return "%d days ago" % (d/3600/24)
+
 @app.teardown_request
 def app_teardown(exception):
     from rrd.store import db

+ 78 - 4
rrd/view/portal/alarm.py

@@ -1,7 +1,81 @@
 #-*- coding:utf-8 -*-
-from flask import jsonify, render_template, request, g
+from flask import jsonify, render_template, request, g, abort
 from rrd import app
+from rrd.model.portal.alarm import Event, EventCase
+import json
 
-@app.route("/portal/alarm-dash")
-def alarm_dash_get():
-    return render_template("portal/alarm/index.html", **locals())
+@app.route("/portal/alarm-dash/case")
+def alarm_dash_case_get():
+    event_cases = []
+    limit = int(request.args.get("limit") or 10)
+    page = int(request.args.get("p") or 1)
+    endpoint_q = request.args.get("endpoint_q") or ""
+    metric_q = request.args.get("metric_q") or ""
+    status = request.args.get("status") or ""
+
+    cases, total = EventCase.query(page, limit, endpoint_q, metric_q, status)
+    return render_template("portal/alarm/case.html", **locals())
+
+@app.route("/portal/alarm-dash/case/event")
+def alarm_dash_event_get():
+    limit = int(request.args.get("limit") or 10)
+    page = int(request.args.get("p") or 1)
+
+    case_id = request.args.get("case_id")
+    if not case_id:
+        abort(400, "no case id")
+
+    _cases = EventCase.select_vs(where='id=%s', params=[case_id], limit=1)
+    if len(_cases) == 0:
+        abort(400, "no such case where id=%s" %case_id)
+    case = _cases[0]
+
+    case_events, total = Event.query(event_caseId=case_id, page=page, limit=limit)
+    return render_template("portal/alarm/case_events.html", **locals())
+
+
+@app.route("/portal/alarm-dash/case/delete", methods=['POST'])
+def alarm_dash_case_delete():
+    ret = {
+        "msg": "",
+    }
+    ids = request.form.get("ids") or ""
+    ids = ids.split(",") or []
+    if not ids:
+       ret['msg'] = "no case ids" 
+       return json.dumps(ret)
+
+    holders = []
+    for x in ids:
+        holders.append("%s")
+    placeholder = ','.join(holders)
+
+    where = 'id in (' + placeholder + ')'
+    params = ids
+    EventCase.delete(where=where, params=params)
+    for x in ids:
+        Event.delete(where='event_caseId=%s', params=[x], limit=1)
+
+    return json.dumps(ret)
+
+@app.route("/portal/alarm-dash/case/event/delete", methods=['POST'])
+def alarm_dash_case_event_delete():
+    ret = {
+        "msg": "",
+    }
+    ids = request.form.get("ids") or ""
+    ids = ids.split(",") or []
+    if not ids:
+       ret['msg'] = "no case ids" 
+       return json.dumps(ret)
+
+    holders = []
+    for x in ids:
+        holders.append("%s")
+    placeholder = ','.join(holders)
+
+    where = 'id in (' + placeholder + ')'
+    params = ids
+    Event.delete(where=where, params=params)
+
+    return json.dumps(ret)

+ 0 - 1
rrd/view/portal/api.py

@@ -18,7 +18,6 @@ log = logging.getLogger(__file__)
 
 @app.route("/favicon.ico")
 def favicon():
-    print "----favicon"
     return ""