laiwei před 8 roky
rodič
revize
38ee0cb354

+ 4 - 1
rrd/__init__.py

@@ -11,4 +11,7 @@ def all_exception_handler(error):
     print "exception: %s" %error
     return u'dashboard 暂时无法访问,请联系管理员', 500
 
-from view import api, chart, screen, index
+from view import index
+from view import api
+from view import chart, screen
+from view import auth, user, team

+ 0 - 1
rrd/config.py

@@ -4,7 +4,6 @@ import os
 #-- app config --
 DEBUG = True
 SECRET_KEY = "secret-key"
-SESSION_COOKIE_NAME = "open-falcon"
 PERMANENT_SESSION_LIFETIME = 3600 * 24 * 30
 SITE_COOKIE = "open-falcon-ck"
 

+ 24 - 18
rrd/corelib/__init__.py

@@ -1,24 +1,30 @@
 #-*- coding:utf-8 -*-
-from rrd import config 
-from rrd.utils import randbytes
+import requests
+import json
 
-def auth_user_from_session(session_):
-    user = None
-    if config.SITE_COOKIE in session_:
-        cookies = session_[config.SITE_COOKIE]
-        user_id, session_id = cookies.split(":")
+def auth_requests(user_token, method, *args, **kwargs):
+    if not user_token:
+        raise Exception("no api token")
 
-    return user 
+    headers = {
+            "Apitoken": json.dumps({"name":user_token.name, "sig":user_token.sig})
+    }
 
-def set_user_cookie(user, session_):
-    if not user:
-        return None
-    session_id = user.session_id if user.session_id else randbytes(8)
-    #user.update_session(session_id)
-    session_[config.SITE_COOKIE] = "%s:%s" % (user.id, session_id)
+    if not kwargs:
+        kwargs = {}
 
-def logout_user(user):
-    if not user:
-        return 
-    #user.clear_session()
+    if "headers" in kwargs:
+        headers.update(kwargs["headers"])
+        del kwargs["headers"]
+
+    if method == "POST":
+        return requests.post(*args, headers=headers, **kwargs)
+    elif method == "GET":
+        return requests.get(*args, headers=headers, **kwargs)
+    elif method == "PUT":
+        return requests.put(*args, headers=headers, **kwargs)
+    elif method == "DELETE":
+        return requests.delete(*args, headers=headers, **kwargs)
+    else:
+        raise Exception("invalid http method")
 

+ 20 - 9
rrd/model/user.py

@@ -1,14 +1,25 @@
 #-*- coding:utf-8 -*-
-class User(object):
-    def __init__(self, id):
-        self.name = None
-        self.session_id = None
+class UserToken(object):
+    def __init__(self, name, sig):
+        self.name = name
+        self.sig = sig
     
     def __repr__(self):
-        return "<User name=%s, session_id=%s>" \
-                % (self.name, self.session_id)
+        return "<User name=%s, sig=%s>"  % (self.name, self.sig)
     __str__ = __repr__
 
-    @classmethod
-    def get(cls, name):
-        return None
+class User(object):
+    def __init__(self, id, name, cnname, email, phone, im, qq, role):
+        self.id = id
+        self.name = name
+        self.cnname = cnname
+        self.email = email
+        self.phone = phone
+        self.im = im
+        self.qq = qq
+        self.role = role
+
+    def __repr__(self):
+        return "<UserProfile id=%s, name=%s, cnname=%s>" \
+                % (self.id, self.name, self.cnname)
+    __str__ = __repr__

+ 112 - 0
rrd/static/js/g.js

@@ -0,0 +1,112 @@
+function err_message_quietly(msg, f) {
+	$.layer({
+		title : false,
+		closeBtn : false,
+		time : 2,
+		dialog : {
+			msg : msg
+		},
+		end : f
+	});
+}
+
+function ok_message_quietly(msg, f) {
+	$.layer({
+		title : false,
+		closeBtn : false,
+		time : 1,
+		dialog : {
+			msg : msg,
+			type : 1
+		},
+		end : f
+	});
+}
+
+function my_confirm(msg, btns, yes_func, no_func) {
+	$.layer({
+		shade : [ 0 ],
+		area : [ 'auto', 'auto' ],
+		dialog : {
+			msg : msg,
+			btns : 2,
+			type : 4,
+			btn : btns,
+			yes : yes_func,
+			no : no_func
+		}
+	});
+}
+
+// - business function -
+
+function login() {
+	var raw = $('#ldap').prop('checked');
+	if (raw) {
+		useLdap = '1'
+	} else {
+		useLdap = '0'
+	}
+	$.post('/auth/login', {
+		'name' : $('#name').val(),
+		'password' : $("#password").val()
+	}, function(json) {
+		if (json.msg.length > 0) {
+			err_message_quietly(json.msg);
+		} else {
+			ok_message_quietly('sign in successfully', function() {
+				var redirect_url = '/user/profile';
+				if (json.data.length > 0) {
+					redirect_url = json.data;
+				}
+				location.href = redirect_url;
+			});
+		}
+	}, "json");
+}
+
+function update_profile() {
+	$.post('/user/profile', {
+		'cnname' : $("#cnname").val(),
+		'email' : $("#email").val(),
+		'phone' : $("#phone").val(),
+		'im' : $("#im").val(),
+		'qq' : $("#qq").val()
+	}, function(json) {
+		if (json.msg.length > 0) {
+			err_message_quietly(json.msg);
+		} else {
+			ok_message_quietly("更新成功:)");
+		}
+	}, "json");
+}
+
+function change_password() {
+	$.post('/user/chpwd', {
+		'old_password' : $("#old_password").val(),
+		'new_password' : $("#new_password").val(),
+		'repeat_password' : $("#repeat_password").val()
+	}, function(json) {
+		if (json.msg.length > 0) {
+			err_message_quietly(json.msg);
+		} else {
+			ok_message_quietly("密码修改成功:)");
+		}
+	}, "json");
+}
+
+function register() {
+	$.post('/auth/register', {
+		'name' : $('#name').val(),
+		'password' : $("#password").val(),
+		'repeat_password' : $("#repeat_password").val()
+	}, function(json) {
+		if (json.msg.length > 0) {
+			err_message_quietly(json.msg);
+		} else {
+			ok_message_quietly('sign up successfully', function() {
+				location.href = '/auth/login';
+			});
+		}
+	}, "json");
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
rrd/static/layer/extend/layer.ext.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 15 - 0
rrd/static/layer/layer.min.js


binární
rrd/static/layer/skin/default/icon_ext.png


binární
rrd/static/layer/skin/default/textbg.png


binární
rrd/static/layer/skin/default/xubox_ico0.png


binární
rrd/static/layer/skin/default/xubox_loading0.gif


binární
rrd/static/layer/skin/default/xubox_loading1.gif


binární
rrd/static/layer/skin/default/xubox_loading2.gif


binární
rrd/static/layer/skin/default/xubox_loading3.gif


binární
rrd/static/layer/skin/default/xubox_title0.png


+ 83 - 0
rrd/static/layer/skin/layer.css

@@ -0,0 +1,83 @@
+/** 
+ 
+ @Name: layer's style
+ @Date: 2012.09.15
+ @Author: 贤心
+ @blog: sentsin.com
+ 
+**/
+
+*html{background-image:url(about:blank); background-attachment:fixed;}
+
+/** common **/
+.xubox_shade, .xubox_layer{position:fixed; _position:absolute;}
+.xubox_shade{top:0; left:0; width:100%; height:100%; _height:expression(document.body.offsetHeight+"px");}
+.xubox_layer{top:150px; left:50%; height:auto; width:310px; margin-left:-155px;}
+.xubox_border, .xubox_title, .xubox_title i, .xubox_page, .xubox_iframe, .xubox_title em, .xubox_close, .xubox_msgico, .xubox_moves{position:absolute;}
+.xubox_border{border-radius: 5px;}
+.xubox_title{left:0; top:0;}
+.xubox_main{position:relative; height:100%; _float:left;}
+.xubox_page{top:0; left:0;}
+.xubox_load{background:url(default/xubox_loading0.gif) #fff center center no-repeat;}
+.xubox_loading{display:block; float:left; text-decoration:none; color:#FFF; _float:none; }
+.xulayer_png32{background:url(default/xubox_ico0.png) no-repeat;}
+.xubox_moves{border:3px solid #666; cursor:move; background-color:rgba(255,255,255,.3); background-color:#fff\9;  filter:alpha(opacity=50);}
+
+.xubox_msgico{width:32px; height:32px; top:52px; left:15px; background:url(default/xubox_ico0.png) no-repeat;}
+.xubox_text{ padding-left:55px; float:left; line-height:25px; word-break:break-all; padding-right:20px; overflow:hidden; font-size:14px;}
+.xubox_msgtype0{background-position:-91px -38px;} 
+.xubox_msgtype1{background-position:-128px -38px }
+.xubox_msgtype2{background-position:-163px -38px;}
+.xubox_msgtype3{background-position:-91px -75px;}
+.xubox_msgtype4{background-position:-163px -75px;}
+.xubox_msgtype5{background-position:-163px -112px;}
+.xubox_msgtype6{background-position:-163px -148px;}
+.xubox_msgtype7{background-position:-128px -75px;}
+.xubox_msgtype8{background-position:-91px -6px;}
+.xubox_msgtype9{background-position:-129px -6px;}
+.xubox_msgtype10{background-position:-163px -6px;}
+.xubox_msgtype11{background-position:-206px -6px;}
+.xubox_msgtype12{background-position:-206px -44px;}
+.xubox_msgtype13{background-position:-206px -81px;}
+.xubox_msgtype14{background-position:-206px -122px;}
+.xubox_msgtype15{background-position:-206px -157px;}
+.xubox_loading_0{width:60px; height:24px; background:url(default/xubox_loading0.gif) no-repeat;}
+.xubox_loading_1{width:37px; height:37px; background:url(default/xubox_loading1.gif) no-repeat;}
+.xubox_loading_2, .xubox_msgtype16{width:32px; height:32px; background:url(default/xubox_loading2.gif) no-repeat;}
+.xubox_loading_3{width:126px; height:22px; background:url(default/xubox_loading3.gif) no-repeat;}
+
+.xubox_setwin{position:absolute; right:10px; *right:0; top:10px; font-size:0;}
+.xubox_setwin a{position:relative; display:inline-block; *display:inline; *zoom:1; vertical-align:top; width: 14px; height:14px; margin-left:10px; font-size:12px; _overflow:hidden;}
+.xubox_setwin .xubox_min cite{position:absolute; width:14px; height:2px; left:0; top:50%; margin-top:-1px; background-color:#919191; cursor:pointer; _overflow:hidden;}
+.xubox_setwin .xubox_min:hover cite{background-color:#2D93CA; }
+.xubox_setwin .xubox_max{background-position:-6px -189px;}
+.xubox_setwin .xubox_max:hover{background-position:-6px -206px;}
+.xubox_setwin .xubox_maxmin{background-position:-29px -189px;}
+.xubox_setwin .xubox_maxmin:hover{background-position:-29px -206px;}
+.xubox_setwin .xubox_close0{ width:14px; height:14px; background-position: -31px -7px; cursor:pointer;}
+.xubox_setwin .xubox_close0:hover{background-position:-51px -7px;}
+.xubox_setwin .xubox_close1{position:absolute; right:-28px; top:-28px; width:30px; height:30px;  margin-left:0; background-position:-60px -195px; *right:-18px; _right:-15px; _top:-23px; _width:14px; _height:14px; _background-position:-31px -7px;}
+.xubox_setwin .xubox_close1:hover{ background-position:-91px -195px; _background-position:-51px -7px;}
+
+.xubox_title{width:100%; height:35px; line-height:35px; border-bottom:1px solid #D5D5D5; background:url(default/xubox_title0.png) #EBEBEB repeat-x; font-size:14px; color:#333;}
+.xubox_title em{height:20px; line-height:20px; width:60%; top:7px; left:10px; font-style:normal; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;}
+
+.xubox_botton a{position:absolute; bottom:10px; left:50%; background:url(default/xubox_ico0.png) repeat; text-decoration:none; color:#FFF;  font-size:14px;  text-align:center; font-weight:bold; overflow:hidden; }
+.xubox_botton a:hover{text-decoration:none; color:#FFF; }
+.xubox_botton .xubox_botton1{ width:79px; height:32px; line-height:32px; margin-left:-39px; background-position:-6px -34px;}
+.xubox_botton1:hover{background-position:-6px -72px;}
+.xubox_botton .xubox_botton2{margin-left:-76px; width:71px; height:29px; line-height:29px; background-position:-5px -114px;}
+.xubox_botton2:hover{ background-position:-5px -146px;}
+.xubox_botton .xubox_botton3{width:71px; height:29px; line-height:29px; margin-left:10px; background-position:-81px -114px;}
+.xubox_botton3:hover{background-position:-81px -146px;}
+.xubox_tips{position:relative; line-height:20px; min-width: 12px; padding:3px 30px 3px 10px; font-size:12px; _float:left; border-radius:3px; box-shadow: 1px 1px 3px rgba(0,0,0,.3);}
+.xubox_tips i.layerTipsG{ position:absolute;  width:0; height:0; border-width:8px; border-color:transparent; border-style:dashed; *overflow:hidden;}
+.xubox_tips i.layerTipsT, .xubox_tips i.layerTipsB{left:5px; border-right-style:solid;}
+.xubox_tips i.layerTipsT{bottom:-8px;}
+.xubox_tips i.layerTipsB{top:-8px;}
+.xubox_tips i.layerTipsR, .xubox_tips i.layerTipsL{top:1px; border-bottom-style:solid;}
+.xubox_tips i.layerTipsR{left:-8px;}
+.xubox_tips i.layerTipsL{right:-8px;}
+
+
+

+ 45 - 0
rrd/static/layer/skin/layer.ext.css

@@ -0,0 +1,45 @@
+/** 
+ 
+ @Name: layer拓展样式
+ @Date: 2012.12.13
+ @Author: 贤心
+ @blog: sentsin.com
+ 
+**/
+
+.xubox_iconext{background:url(default/icon_ext.png) no-repeat;}
+
+/* prompt模式 */
+.xubox_layer .xubox_form{width:240px; height:30px; line-height:30px; padding: 0 5px; border: 1px solid #ccc; background: url(default/textbg.png) #fff repeat-x; color:#333;}
+.xubox_layer .xubox_formArea{width:300px; height:100px; line-height:20px;}
+
+/* tab模式 */
+.xubox_layer .xubox_tab{position:relative; background-color:#fff; box-shadow:1px 1px 50px rgba(0,0,0,.4)}
+.xubox_layer .xubox_tabmove{position:absolute; width:600px; height:30px; top:0; left:0;}
+.xubox_layer .xubox_tabtit{ display:block; height:34px; border-bottom:1px solid #ccc; background-color:#eee;}
+.xubox_layer .xubox_tabtit span{position:relative; float:left; width:120px; height:34px; line-height:34px; text-align:center; cursor:default;}
+.xubox_layer .xubox_tabtit span.xubox_tabnow{left:-1px; _top:1px; height:35px; border-left:1px solid #ccc; border-right:1px solid #ccc; background-color:#fff; z-index:10;}
+.xubox_layer .xubox_tab_main{line-height:24px; clear:both;}
+.xubox_layer .xubox_tab_main .xubox_tabli{display:none;}
+.xubox_layer .xubox_tab_main .xubox_tabli.xubox_tab_layer{display:block;}
+.xubox_layer .xubox_tabclose{position:absolute; right:10px; top:5px; cursor:pointer;}
+
+/* photo模式 */
+.xubox_bigimg, .xubox_intro{height:300px}
+.xubox_bigimg{position:relative; display:block; width:600px; text-align:center; background:url(default/xubox_loading1.gif) center center no-repeat #000; overflow:hidden; }
+.xubox_bigimg img{position:relative; display:inline-block; visibility: hidden;}
+.xubox_intro{position:absolute; right:-315px; top:0; width:300px; background-color:#fff; overflow-x:hidden; overflow-y:auto;}
+.xubox_imgsee{display:none;}
+.xubox_prev, .xubox_next{position:absolute; top:50%; width:27px; _width:44px; height:44px;  margin-top:-22px; outline:none;blr:expression(this.onFocus=this.blur());}
+.xubox_prev{left:10px; background-position:-5px -5px; _background-position:-70px -5px;}
+.xubox_prev:hover{background-position:-33px -5px; _background-position:-120px -5px;}
+.xubox_next{right:10px; _right:8px; background-position:-5px -50px; _background-position:-70px -50px;}
+.xubox_next:hover{background-position:-33px -50px; _background-position:-120px -50px;}
+.xubox_imgbar{position:absolute; left:0; bottom:0; width:100%; height:32px; line-height:32px; background-color:rgba(0,0,0,.8); background-color:#000\9; filter:Alpha(opacity=80); color:#fff; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; font-size:0;}
+.xubox_imgtit{/*position:absolute; left:20px;*/}
+.xubox_imgtit *{display:inline-block; *display:inline; *zoom:1; vertical-align:top; font-size:12px;}
+.xubox_imgtit a{max-width:65%;  text-overflow: ellipsis; overflow: hidden; white-space: nowrap; color:#fff;}
+.xubox_imgtit a:hover{color:#fff; text-decoration:underline;}
+.xubox_imgtit em{padding-left:10px;}
+
+

+ 0 - 0
rrd/templates/alarm-dashboard.html


+ 65 - 0
rrd/templates/auth/login.html

@@ -0,0 +1,65 @@
+{% extends "base.html" %}
+
+{%block head_js%}
+    {{super()}}
+    <script>
+    $(function() {
+        $("#password").keypress(function(e) {
+            var key = e.which;
+            if (key == 13) {
+                login();
+            }
+        });
+    });
+    </script>
+
+    <script src="{{url_for('static', filename='js/g.js')}}"></script>
+{%endblock%}
+
+{%block navbar%}
+  {%include "navbar.html"%}
+{%endblock%}
+
+{% block container_outer %}
+<div id="container" class="container-fluid">
+	<div class="row">
+		<div class="col-md-12">
+
+			<div style="margin: 0 auto; max-width: 400px;">
+				<div class="panel panel-default">
+					<div class="panel-heading">
+						<h3 class="panel-title">
+							Sign in
+						</h3>
+					</div>
+					<div class="panel-body">
+						<div class="form-sign" role="form">
+							<div class="form-group">
+								<input type="text" placeholder="name" id="name"
+								class="form-control" required autofocus />
+							</div>
+							<div class="form-group">
+								<input type="password" placeholder="password" id="password"
+								class="form-control" required />
+							</div>
+							<div class="checkbox">
+								<label>
+									<input type="checkbox" id="ldap"> ldap account
+								</label>
+							</div>
+							<button class="btn btn-default btn-block" type="button" id="sign"
+							onclick="login();">Sign in</button>
+
+							<div style="margin-top: 10px">
+								no account? <a href="/auth/register">sign up</a>
+							</div>
+
+						</div>
+					</div>
+				</div>
+			</div>
+
+		</div>
+	</div>
+</div>
+{%endblock%}

+ 3 - 1
rrd/templates/base.html

@@ -14,6 +14,7 @@
     {% block css %}
         <link href="{{url_for('static', filename='bootstrap3/css/bootstrap.min.css')}}" rel="stylesheet">
         <link href="{{url_for('static', filename='select2/select2.css')}}" rel="stylesheet">
+        <link href="{{url_for('static', filename='layer/skin/layer.css')}}" rel="stylesheet">
         <link href="{{url_for('static', filename='css/select2-bootstrap.css')}}" rel="stylesheet">
         <link href="{{url_for('static', filename='bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css')}}" rel="stylesheet"> </link>
         <link rel="stylesheet" href="{{url_for('static', filename='bootstrap-tokenfield/css/bootstrap-tokenfield.min.css')}}">
@@ -30,6 +31,7 @@
 
     {% block head_js %}
         <script src="{{url_for('static', filename='js/jquery.min.js')}}"></script>
+        <script src="{{url_for('static', filename='layer/layer.min.js')}}"></script>
         <script src="{{url_for('static', filename='js/jquery.query.js')}}"></script>
         <script src="{{url_for('static', filename='bootstrap3/js/bootstrap.min.js')}}"></script>
         <script src="{{url_for('static', filename='js/moment.min.js')}}"></script>
@@ -88,7 +90,7 @@
 <body style="height:100%; padding-top: 0px; padding-bottom: 40px; font-size:12px;">
 {%endblock%}
 
-    {%block navbar%}{%endblock%}
+    {%block navbar%} {%endblock%}
     {% block more_css%} {% endblock %}
     {% block more_js%} {% endblock %}
     {% block nav%} {% endblock %}

+ 1 - 20
rrd/templates/index.html

@@ -52,26 +52,7 @@
 
 
 {%block navbar%}
-<nav class="navbar navbar-default" role="navigation" style="background-color: #fff;">
-<div class="container-fluid">
-    <div class="navbar-header">
-        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
-        </button>
-        <a class="navbar-brand" href="/">Falcon-Dashboard</a>
-    </div>
-    <div class="collapse navbar-collapse navbar-ex1-collapse">
-        <ul class="nav navbar-nav navbar-right">
-          <li><a href="/screen">Screen</a></li>
-          <li class="dropdown">
-              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">链接<span class="caret"></span></a>
-              <ul class="dropdown-menu" role="menu" style="font-size:12px;">
-                  <li><a href="https://github.com/open-falcon">fork me on Github</a></li>
-              </ul>
-          </li>
-        </ul>
-    </div>
-</div>
-</nav>
+  {%include "navbar.html"%}
 {%endblock%}
 
 {% block container %}

+ 35 - 0
rrd/templates/navbar.html

@@ -0,0 +1,35 @@
+<nav class="navbar navbar-default" role="navigation" style="background-color: #fff;">
+<div class="container-fluid">
+    <div class="navbar-header">
+        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
+        </button>
+        <a class="navbar-brand" href="/">Falcon-Dashboard</a>
+    </div>
+    <div class="collapse navbar-collapse navbar-ex1-collapse">
+        <ul class="nav navbar-nav navbar-right">
+          <li><a href="/">Dashboard</a></li>
+          <li><a href="/screen">Screen</a></li>
+          <li><a href="/hostgroup">HostGroups</a></li>
+          <li><a href="/template">Templates</a></li>
+          <li><a href="/expression">Expressions</a></li>
+          <li><a href="/nodata">Nodata</a></li>
+          <li><a href="/alarm-dash">Alarm-Dashboard</a></li>
+          <li class="dropdown">
+              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">链接<span class="caret"></span></a>
+              <ul class="dropdown-menu" role="menu" style="font-size:12px;">
+                  <li><a href="">Profile</a></li>
+                  <li><a href="">Users</a></li>
+                  <li><a href="">Teams</a></li>
+                  {%if g.user%}
+                      <li><a href="/auth/logout">Logout</a></li>
+                  {%else%}
+                      <li><a href="/auth/login">Login</a></li>
+                      <li><a href="/auth/register">Sign Up</a></li>
+                  {%endif%}
+                  <li><a href="https://github.com/open-falcon">fork me on Github</a></li>
+              </ul>
+          </li>
+        </ul>
+    </div>
+</div>
+</nav>

+ 28 - 0
rrd/templates/user/about.html

@@ -0,0 +1,28 @@
+{% extends "user/base.html" %}
+
+
+{% block container_outer %}
+<div id="container" class="container-fluid">
+	<div class="row">
+		<div class="col-md-12">
+
+			<div style="margin: 0 auto; max-width: 400px;">
+				<div class="thin-border" style="line-height:24px;">
+					<div class="pull-right">
+						<img src="/user/qrcode/{{user_info["id"]}}" width="120" height="120">
+					</div>
+					<div>
+						姓名:{{user_info["cnname"]}}<br>
+						手机:{{user_info["phone"]}}<br>
+						邮箱:{{user_info["email"]}}<br>
+						QQ:{{user_info["qq"]}}<br>
+						IM:{{user_info["im"]}}<br>
+					</div>
+				</div>
+			</div>
+
+		</div>
+	</div>
+
+</div>
+{%endblock%}

+ 10 - 0
rrd/templates/user/base.html

@@ -0,0 +1,10 @@
+{% extends "base.html" %}
+
+{%block head_js%}
+    {{super()}}
+    <script src="{{url_for('static', filename='js/g.js')}}"></script>
+{%endblock%}
+
+{%block navbar%}
+  {%include "navbar.html"%}
+{%endblock%}

+ 0 - 0
rrd/templates/user/create.html


+ 0 - 0
rrd/templates/user/list.html


+ 87 - 0
rrd/templates/user/profile.html

@@ -0,0 +1,87 @@
+{% extends "user/base.html" %}
+
+
+{% block container_outer %}
+<div id="container" class="container-fluid">
+
+	<div class="row">
+		<div class="col-md-12">
+			<div style="margin: 0 auto; max-width: 400px;">
+
+				<ol class="breadcrumb">
+					<li><a href="/">首页</a></li>
+					<li class="active">个人信息维护</li>
+				</ol>
+
+				<div class="panel panel-default">
+					<div class="panel-heading">
+						<h3 class="panel-title">Profile of {{current_user.name}}</h3>
+					</div>
+					<div class="panel-body">
+						<div class="form-group">
+							<label for="name">中文名(可以附带上部门、工位号之类的做详细区分):</label> <input
+							type="text" name="cnname" id="cnname" class="form-control"
+							value="{{current_user.cnname}}" />
+						</div>
+						<div class="form-group">
+							<label for="email">邮箱:</label> <input type="email" name="email"
+							id="email" class="form-control" value="{{current_user.email}}" />
+						</div>
+						<div class="form-group">
+							<label for="phone">手机:</label> <input type="text" name="phone"
+							id="phone" class="form-control" value="{{current_user.phone}}" />
+						</div>
+						<div class="form-group">
+							<label for="name">IM(内部通讯工具账号,比如百度hi、米聊):</label> <input
+							type="text" name="im" id="im" class="form-control"
+							value="{{current_user.im}}" />
+						</div>
+						<div class="form-group">
+							<label for="name">QQ:</label> <input type="text" name="qq" id="qq"
+							class="form-control" value="{{current_user.qq}}" />
+						</div>
+						<button type="button" class="btn btn-default" id="update_profile" onclick="update_profile();">
+							<span class="glyphicon glyphicon-floppy-disk"></span>
+							更新
+						</button>
+						<a href="/" class="btn btn-default">
+							<span class="glyphicon glyphicon-arrow-left"></span>
+							返回
+						</a>
+					</div>
+				</div>
+
+				<div class="panel panel-default">
+					<div class="panel-heading">
+						<h3 class="panel-title">Change Password</h3>
+					</div>
+					<div class="panel-body">
+						<div class="form-group">
+							<label for="old_password">输入旧密码:</label> <input type="password"
+							name="old_password" id="old_password" class="form-control" />
+						</div>
+						<div class="form-group">
+							<label for="new_password">输入新密码:</label> <input type="password"
+							name="new_password" id="new_password" class="form-control" />
+						</div>
+						<div class="form-group">
+							<label for="repeat_password">重复输入新密码:</label> <input type="password"
+							name="repeat_password" id="repeat_password" class="form-control" />
+						</div>
+						<button type="submit" class="btn btn-default" id="change_password" onclick="change_password();">
+							<span class="glyphicon glyphicon-floppy-disk"></span>
+							更新
+						</button>
+						<a href="/" class="btn btn-default">
+							<span class="glyphicon glyphicon-arrow-left"></span>
+							返回
+						</a>
+					</div>
+				</div>
+
+			</div>
+		</div>
+	</div>
+
+</div>
+{%endblock%}

+ 5 - 1
rrd/view/__init__.py

@@ -1,10 +1,11 @@
 #-*- coding:utf-8 -*-
 import time
-from flask import request, g, abort, render_template
+from flask import request, g, session, abort, render_template
 from MySQLdb import ProgrammingError
 
 from rrd import app
 from rrd.consts import RRD_CFS, GRAPH_TYPE_KEY, GRAPH_TYPE_HOST
+from rrd.view.utils import get_usertoken_from_session, get_current_user_profile
 
 @app.teardown_request
 def teardown_request(exception):
@@ -12,6 +13,9 @@ def teardown_request(exception):
 
 @app.before_request
 def chart_before():
+    g.user_token = get_usertoken_from_session(session)
+    g.user = get_current_user_profile(g.user_token)
+
     if request.method == "GET":
         now = int(time.time())
 

+ 49 - 0
rrd/view/api.py

@@ -0,0 +1,49 @@
+#-*- coding:utf-8 -*-
+from flask import request, g, abort, render_template
+from rrd import app
+from rrd.view import utils as view_utils
+
+import requests
+import json
+
+@app.route("/auth/login", methods=["GET", "POST"])
+def auth_login():
+    if request.method == "GET":
+        return render_template("auth/login.html", **locals())
+
+    if request.method == "POST":
+        ret = { "msg": "", }
+
+        name = request.form.get("name")
+        password = request.form.get("password")
+        if not name or not password:
+            ret["msg"] = "no name or password"
+            return json.dumps(ret)
+
+        try:
+            ut = view_utils.login_user(name, password)
+            if not ut:
+                ret["msg"] = "no such user"
+                return json.dumps(ret)
+
+            ret["data"] = {
+                    "name": ut.name,
+                    "sig": ut.sig,
+            }
+            return json.dumps(ret)
+        except Exception as e:
+            ret["msg"] = str(e)
+            return json.dumps(ret)
+
+@app.route("/auth/logout", methods=["POST",])
+def auth_logout():
+    if request.method == "POST":
+        pass
+
+@app.route("/auth/register", methods=["GET", "POST"])
+def auth_register():
+    if request.method == "GET":
+        return render_template("auth/register.html", **locals())
+
+    if request.method == "POST":
+        pass

+ 12 - 0
rrd/view/chart.py

@@ -0,0 +1,12 @@
+#-*- coding:utf-8 -*-
+from flask import request, g, abort, render_template
+from rrd import app
+
+@app.route("/team", methods=["GET", "POST"])
+def account_team():
+    if request.method == "GET":
+        return render_template("team/list.html", **locals())
+
+    if request.method == "POST":
+        pass
+

+ 96 - 0
rrd/view/user.py

@@ -0,0 +1,96 @@
+#-*- coding:utf-8 -*-
+import json
+from flask import request, g, abort, render_template
+from rrd import app
+from rrd import corelib
+from rrd import config
+from rrd.view.utils import require_login
+
+@app.route("/user/about/<username>", methods=["GET",])
+@require_login()
+def user_info(username):
+    if request.method == "GET":
+        h = {"Content-type": "application/json"}
+        r = corelib.auth_requests(g.user_token, "GET", "%s/user/u/%s" %(config.API_ADDR, username), headers=h)
+        if r.status_code != 200:
+            abort(400, "%s:%s" %(r.status_code, r.text))
+        user_info = r.json()
+        return render_template("user/about.html", **locals())
+
+@app.route("/user/profile", methods=["GET", "POST"])
+@require_login()
+def user_profile():
+    if request.method == "GET":
+        current_user = g.user
+        return render_template("user/profile.html", **locals())
+
+    if request.method == "POST":
+        ret = {"msg":""}
+
+        name = g.user.name
+        cnname = request.form.get("cnname", "")
+        email = request.form.get("email", "")
+        im = request.form.get("im", "")
+        phone = request.form.get("phone", "")
+        qq = request.form.get("qq", "")
+
+        h = {"Content-type": "application/json"}
+        d = {
+                "name": name,
+                "cnname": cnname,
+                "email": email,
+                "im": im,
+                "phone": phone,
+                "qq": qq,
+        }
+
+        r = corelib.auth_requests(g.user_token, "PUT", "%s/user/update" %(config.API_ADDR,), \
+                data=json.dumps(d), headers=h)
+        if r.status_code != 200:
+            ret["msg"] = r.text
+
+        return json.dumps(ret)
+
+@app.route("/user/chpwd", methods=["POST", ])
+@require_login()
+def user_change_passwd():
+    if request.method == "POST":
+        ret = {"msg": ""}
+
+        old_password = request.form.get("old_password", "")
+        new_password = request.form.get("new_password", "")
+        repeat_password = request.form.get("repeat_password", "")
+        if not (old_password and new_password and repeat_password):
+            ret["msg"] = "some form item missing"
+            return json.dumps(ret)
+
+        if new_password != repeat_password:
+            ret["msg"] = "repeat and new password not equal"
+            return json.dumps(ret)
+
+        h = {"Content-type":"application/json"}
+        d = {
+            "old_password": old_password,
+            "new_password": new_password,
+        }
+
+        r = corelib.auth_requests(g.user_token, "PUT", "%s/user/cgpasswd" %(config.API_ADDR,), \
+                data=json.dumps(d), headers=h)
+        if r.status_code != 200:
+            ret['msg'] = r.text
+
+        return json.dumps(ret)
+        
+
+@app.route("/user/list", methods=["GET",])
+def user_list():
+    if request.method == "GET":
+        return render_template("user/list.html", **locals())
+
+@app.route("/user/create", methods=["GET", "POST"])
+def user_create():
+    if request.method == "GET":
+        return render_template("user/create.html", **locals())
+    
+    if request.method == "POST":
+        pass

+ 77 - 0
rrd/view/utils.py

@@ -0,0 +1,77 @@
+#-*- coding:utf-8 -*-
+import json
+import requests
+from flask import g, redirect, session
+
+from functools import wraps
+
+from rrd import config 
+from rrd import corelib
+from rrd.utils import randbytes
+from rrd.model.user import User, UserToken
+
+def require_login(msg="please login first", redir=""):
+    def _(f):
+        @wraps(f)
+        def __(*a, **kw):
+            if not g.user:
+                if redir:
+                    return redirect(redir or "/")
+                else:
+                    return json.dumps({"msg": msg})
+            return f(*a, **kw)
+        return __
+    return _
+
+def set_user_cookie(user_token, session_):
+    if not user_token:
+        return None
+    session_[config.SITE_COOKIE] = "%s:%s" % (user_token.name, user_token.sig)
+
+def clear_user_cookie(session_):
+    session_[config.SITE_COOKIE] = ""
+
+def get_usertoken_from_session(session_):
+    if config.SITE_COOKIE in session_:
+        cookies = session_[config.SITE_COOKIE]
+        if not cookies:
+            return None
+
+        name, sig = cookies.split(":")
+        return UserToken(name, sig)
+
+def get_current_user_profile(user_token):
+    if not user_token:
+        return 
+
+    h = {"Content-type": "application/json"}
+    r = corelib.auth_requests(user_token, "GET", "%s/user/current" %config.API_ADDR, headers=h)
+    if r.status_code != 200:
+        return
+
+    j = r.json()
+    return User(j["id"], j["name"], j["cnname"], j["email"], j["phone"], j["im"], j["qq"], j["role"])
+
+def logout_user(user_token):
+    if not user_token:
+        return 
+
+    r = auth_requests(user_token, "POST", "%s/user/logout" %config.API_ADDR)
+    if r.status_code != 200:
+        raise Exception("%s:%s", r.status_code, r.text)
+    clear_user_cookie(session)
+
+def login_user(name, password):
+    params = {
+        "name": name,
+        "password": password,
+    }
+    r = requests.post("%s/user/login" %config.API_ADDR, data=params)
+    if r.status_code != 200:
+        raise Exception("{} : {}".format(r.status_code, r.text))
+
+    j = r.json()
+    ut = UserToken(j["name"], j["sig"])
+    set_user_cookie(ut, session)
+    return ut
+

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů