Merge branch 'master' of https://github.com/unias/docklet into billing

This commit is contained in:
zhuyj17 2016-08-10 14:33:10 +08:00
commit 1230abdd4a
14 changed files with 290 additions and 62 deletions

14
CHANGES
View File

@ -1,3 +1,17 @@
v0.2.8, Jul 28, 2016
--------------------
**Bug Fix**
* [#119] version display error
**Improvement**
* [#52] give user a total quota, let themselves decide how to use quota
* [#72] recording the user's historical resource usage
* [#85] Making workers's state consistent with master
* [#88] setting config file in admin panel
* [#96] Web notifications
* [#113] Recovery : after poweroff, just recover container, not recover service
v0.2.7, May 17, 2016
--------------------

View File

@ -1 +1 @@
0.2.7
0.2.8

View File

@ -451,14 +451,22 @@ def vnodes_monitor(cur_user, user, form, con_id, issue):
return json.dumps({'success':'true', 'monitor':res})
@app.route("/monitor/user/quotainfo/", methods=['POST'])
@app.route("/monitor/user/<issue>/", methods=['POST'])
@login_required
def user_quotainfo_monitor(cur_user, user, form):
def user_quotainfo_monitor(cur_user, user, form,issue):
global G_usermgr
logger.info("handle request: monitor/user/quotainfo/")
user_info = G_usermgr.selfQuery(cur_user = cur_user)
quotainfo = user_info['data']['groupinfo']
return json.dumps({'success':'true', 'quotainfo':quotainfo})
global G_historymgr
if issue == 'quotainfo':
logger.info("handle request: monitor/user/quotainfo/")
user_info = G_usermgr.selfQuery(cur_user = cur_user)
quotainfo = user_info['data']['groupinfo']
return json.dumps({'success':'true', 'quotainfo':quotainfo})
elif issue == 'createdvnodes':
logger.info("handle request: monitor/user/createdvnodes/")
res = G_historymgr.getCreatedVNodes(user)
return json.dumps({'success':'true', 'createdvnodes':res})
else:
return json.dumps({'success':'false', 'message':"Unspported Method!"})
@app.route("/monitor/listphynodes/", methods=['POST'])
@login_required

View File

@ -96,7 +96,7 @@ class User(db.Model):
if (date != None):
self.register_date = date
else:
self.register_date = datetime.utcnow()
self.register_date = datetime.now()
self.user_group = usergroup
self.auth_method = auth_method
@ -187,6 +187,20 @@ class NotificationGroups(db.Model):
def __repr__(self):
return '<Notification: %r, Group: %r>' % (self.notification_id, self.group_name)
class UserNotificationPair(db.Model):
id = db.Column(db.Integer, primary_key=True)
userName = db.Column(db.String(10))
notifyId = db.Column(db.Integer)
isRead = db.Column(db.Integer)
def __init__(self, username, notifyid):
self.userName = username
self.notifyId = notifyid
self.isRead = 0
def __repr__(self):
return '<UserName: %r, NotifyId: %r>' % (self.userName, self.notifyId)
class VNode(db.Model):
__bind_key__ = 'history'
name = db.Column(db.String(100), primary_key=True)

View File

@ -669,3 +669,10 @@ class History_Manager:
else:
res = History.query.filter_by(vnode=vnode_name).all()
return list(eval(str(res)))
def getCreatedVNodes(self,owner):
vnodes = VNode.query.filter(VNode.name.startswith(owner)).all()
res = []
for vnode in vnodes:
res.append(vnode.name)
return res

View File

@ -1,7 +1,7 @@
import json
from log import logger
from model import db, Notification, NotificationGroups, User
from model import db, Notification, NotificationGroups, User, UserNotificationPair
from userManager import administration_required, token_required
import smtplib
from email.mime.text import MIMEText
@ -21,6 +21,10 @@ class NotificationMgr:
NotificationGroups.query.all()
except:
db.create_all()
try:
UserNotificationPair.query.all()
except:
db.create_all()
logger.info("Notification Manager init done!")
def query_user_notifications(self, user):
@ -102,6 +106,16 @@ class NotificationMgr:
db.session.commit()
if 'sendMail' in form:
self.mail_notification(notify.id)
users = User.query.all()
for user in users:
user_group = user.user_group
for group_name in group_names:
if user_group == group_name:
tempPair = UserNotificationPair(user.username, notify.id)
db.session.add(tempPair)
break;
db.session.commit()
return {"success": 'true'}
@administration_required
@ -158,36 +172,62 @@ class NotificationMgr:
db.session.delete(notify_groups)
db.session.delete(notify)
db.session.commit()
temppairs = UserNotificationPair.query.filter_by(notifyId=notify_id).all()
for temppair in temppairs:
db.session.delete(temppair)
db.session.commit()
return {"success": 'true'}
@token_required
def query_self_notification_simple_infos(self, *args, **kwargs):
user = kwargs['cur_user']
username = user.username
notifies = self.query_user_notifications(user)
notify_simple_infos = []
for notify in notifies:
if notify is None or notify.status != 'open':
continue
notifyid = notify.id
temppair = UserNotificationPair.query.filter_by(userName=username, notifyId=notifyid).first()
if temppair == None:
isRead = 0
temppair = UserNotificationPair(username, notifyid)
db.session.add(temppair)
db.session.commit()
else:
isRead = temppair.isRead
notify_simple_infos.append({
'id': notify.id,
'title': notify.title,
'create_date': notify.create_date
'create_date': notify.create_date,
'isRead': isRead
})
return {'success': 'true', 'data': notify_simple_infos}
@token_required
def query_self_notifications_infos(self, *args, **kwargs):
user = kwargs['cur_user']
username = user.username
notifies = self.query_user_notifications(user)
notify_infos = []
for notify in notifies:
if notify is None or notify.status != 'open':
continue
notifyid = notify.id
temppair = UserNotificationPair.query.filter_by(userName=username, notifyId=notifyid).first()
if temppair == None:
temppair = UserNotificationPair(username, notifyid)
db.session.add(temppair)
isRead = 1
temppair.isRead = 1
db.session.add(temppair)
db.session.commit()
notify_infos.append({
'id': notify.id,
'title': notify.title,
'content': notify.content,
'create_date': notify.create_date
'create_date': notify.create_date,
'isRead': isRead
})
return {'success': 'true', 'data': notify_infos}
@ -208,6 +248,10 @@ class NotificationMgr:
'content': notify.content,
'create_date': notify.create_date
}
usernotifypair = UserNotificationPair.query.filter_by(userName=user.username, notifyId=notify.id).first()
usernotifypair.isRead = 1
db.session.add(usernotifypair)
db.session.commit()
if notify.status != 'open':
notify_info['title'] = 'This notification is not available'
notify_info['content'] = 'Sorry, it seems that the administrator has closed this notification.'

View File

@ -82,7 +82,7 @@ def send_activated_email(to_address, username):
<br><br>
<p> <a href='http://docklet.unias.org'>Docklet Team</a>, SEI, PKU</p>
''' % (env.getenv("PORTAL_URL"), env.getenv("PORTAL_URL"))
text += '<p>'+ str(datetime.utcnow()) + '</p>'
text += '<p>'+ str(datetime.now()) + '</p>'
text += '</html>'
subject = 'Docklet account activated'
msg = MIMEMultipart()
@ -107,7 +107,7 @@ def send_remind_activating_email(username):
<br/><br/>
<p> Docklet Team, SEI, PKU</p>
''' % (username, env.getenv("PORTAL_URL"), env.getenv("PORTAL_URL"))
text += '<p>'+ str(datetime.utcnow()) + '</p>'
text += '<p>'+ str(datetime.now()) + '</p>'
text += '</html>'
subject = 'An activating request in Docklet has been sent'
msg = MIMEMultipart()
@ -324,14 +324,14 @@ class userManager:
def set_nfs_quota_bygroup(self,groupname, quota):
if not data_quota == "True":
return
users = User.query.filter_by(user_group = groupname).all()
return
users = User.query.filter_by(user_group = groupname).all()
for user in users:
self.set_nfs_quota(user.username, quota)
def set_nfs_quota(self, username, quota):
if not data_quota == "True":
return
return
nfspath = "/users/%s/data" % username
try:
cmd = data_quota_cmd % (nfspath,quota+"GB")
@ -769,6 +769,13 @@ class userManager:
will send an e-mail when status is changed from 'applying' to 'normal'
Usage: modify(newValue = dict_from_form, cur_user = token_from_auth)
'''
if ( kwargs['newValue'].get('Instruction', '') == 'Activate'):
user_modify = User.query.filter_by(id = kwargs['newValue'].get('ID', None)).first()
user_modify.status = 'normal'
send_activated_email(user_modify.e_mail, user_modify.username)
db.session.commit()
return {"success": "true"}
user_modify = User.query.filter_by(username = kwargs['newValue'].get('username', None)).first()
if (user_modify == None):
@ -786,11 +793,12 @@ class userManager:
if (user_modify.status == 'applying' and form.get('status', '') == 'normal'):
send_activated_email(user_modify.e_mail, user_modify.username)
user_modify.status = form.get('status', '')
if (form.get('password', '') != ''):
new_password = form.get('password','')
new_password = hashlib.sha512(new_password.encode('utf-8')).hexdigest()
user_modify.password = new_password
#if (form.get('password', '') != ''):
#new_password = form.get('password','')
#new_password = hashlib.sha512(new_password.encode('utf-8')).hexdigest()
#user_modify.password = new_password
#self.chpassword(cur_user = user_modify, password = form.get('password','no_password'))
#modify password in another function now
db.session.commit()
res = self.groupQuery(name=user_modify.user_group)

View File

@ -149,6 +149,9 @@
<li id="nav_Status">
<a href='/vclusters/'><i class="fa fa-bar-chart"></i> <span class="nav-label">Status</span></a>
</li>
<li id="nav_History">
<a href='/history/'><i class="fa fa-history"></i> <span class="nav-label">History</span></a>
</li>
{% if mysession['usergroup'] == 'root' or mysession['usergroup'] == 'admin'%}
@ -242,6 +245,8 @@
$("#nav_Hosts").addClass("active");
else if(pathname[1] == 'config')
$("#nav_Config").addClass("active");
else if(pathname[1] == 'history')
$("#nav_History").addClass("active");
else if(pathname[1] == 'user')
{
if (pathname[2] == 'list')
@ -265,29 +270,35 @@
var notifies = data.data;
var len = notifies.length;
var cnt = 0;
var unread = 0;
var now = new Date().getTime();
for(var t = 0; t < len; t++) {
var notify = notifies[t];
var createDate = notify.create_date.substring(0, 19);
var isRead = notify.isRead;
createDate = createDate.replace(/-/g,'/');
var from = new Date(createDate).getTime();
var delta = now - from;
if(delta <= 604800000) {
cnt ++;
}
if(isRead == 0){
unread ++;
}
}
$('#notificationIcon').find('span').remove();
$('#notificationList').empty();
if(cnt != 0) {
$("<span class=\"label label-warning\">"+cnt+"</span>").appendTo($('#notificationIcon'));
}
if(unread != 0)
$("<span class=\"label label-warning\">"+unread+"</span>").appendTo($('#notificationIcon'));
$("#notificationHeader").html("You have " + len + " notifications, "+cnt+" in 1 week");
for(var i = 0; i < len; i++) {
notify = notifies[i];
console.log(notify);
var isRead = notify.isRead;
var a = $("<a href=\"/notification/detail/"+ notify.id +"/\"><i class=\"fa fa-envelope\"></i> "+ notify.title +"</a>")
var item = $("<li></li>");
item.append(a);

View File

@ -2,16 +2,13 @@
{% block title %}Docklet | History{% endblock %}
{% block panel_title %}History for <div id='node_name'>{{ vnode_name }}</div>{% endblock %}
{% block panel_title %}History of All Created VNodes{% endblock %}
{% block panel_list %}
<ol class="breadcrumb">
<li>
<a href="/dashboard/"><i class="fa fa-dashboard"></i>Home</a>
</li>
<li>
<a href='/vclusters/'>VClusterStatus</a>
</li>
<li class="active">
<strong>History</strong>
</li>
@ -26,44 +23,51 @@
<div class="row">
<div class="col-md-12">
<div class="box box-info">
<div class="box-header with-border">
<h3 class="box-title">History of {{ vnode_name }}</h3>
<div class="box-header with-border">
<h3 class="box-title">All Created VNodes</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
<button type="button" class="btn btn-box-tool" data-widget="remove"><i class="fa fa-times"></i></button>
</div>
</div>
<div class="ibox-body table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>History ID</th>
<th>Action</th>
<th>Running Time</th>
<th>Cpu Time</th>
<th>Billing</th>
<th>Action Time</th>
</tr>
</thead>
<div class="box-body">
<table class="table table-striped table-bordered table-hover table-image" >
<thead>
<tr>
<th>NO</th>
<th>VNode Name</th>
<th>History</th>
</tr>
</thead>
<tbody>
{% for record in history %}
{% for vnode in vnodes %}
<tr>
<th>{{ record['id'] }}</th>
<th>{{ record['action'] }}</th>
<th>{{ record['runningtime'] }} seconds</th>
<th>{{ record['cputime'] }} seconds</th>
<th>{{ record['billing'] }} beans</th>
<th>{{ record['actionTime'] }}</th>
<th>{{ loop.index }}</th>
<th>{{ vnode }}</th>
<td><a class="btn btn-info btn-xs" href='/history/{{ vnode }}'>History</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script_src %}
<script src="http://cdn.bootcss.com/datatables/1.10.11/js/jquery.dataTables.js"></script>
<script src="http://cdn.bootcss.com/datatables/1.10.11/js/dataTables.bootstrap.js"></script>
<script src="http://cdn.bootcss.com/datatables-tabletools/2.1.5/js/TableTools.min.js"></script>
<script>
$(document).ready(function() {
$(".table-image").DataTable();
});
</script>
{% endblock %}

View File

@ -0,0 +1,82 @@
{% extends 'base_AdminLTE.html' %}
{% block title %}Docklet | History{% endblock %}
{% block panel_title %}History of <div id='node_name'>{{ vnode_name }}</div>{% endblock %}
{% block panel_list %}
<ol class="breadcrumb">
<li>
<a href="/dashboard/"><i class="fa fa-dashboard"></i>Home</a>
</li>
<li>
<a href='/history/'>History</a>
</li>
<li class="active">
<strong>History of Vnode</strong>
</li>
</ol>
{% endblock %}
{% block css_src %}
<link href="/static/dist/css/flotconfig.css" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="box box-info">
<div class="box-header with-border">
<h3 class="box-title">History</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
<button type="button" class="btn btn-box-tool" data-widget="remove"><i class="fa fa-times"></i></button>
</div>
</div>
<div class="box-body">
<table class="table table-striped table-bordered table-hover table-image" >
<thead>
<tr>
<th>History ID</th>
<th>Action</th>
<th>Running Time</th>
<th>Cpu Time</th>
<th>Billing</th>
<th>Action Time</th>
</tr>
</thead>
<tbody>
{% for record in history %}
<tr>
<th>{{ record['id'] }}</th>
<th>{{ record['action'] }}</th>
<th>{{ record['runningtime'] }} seconds</th>
<th>{{ record['cputime'] }} seconds</th>
<th>{{ record['billing'] }} beans</th>
<th>{{ record['actionTime'] }}</th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script_src %}
<script src="http://cdn.bootcss.com/datatables/1.10.11/js/jquery.dataTables.js"></script>
<script src="http://cdn.bootcss.com/datatables/1.10.11/js/dataTables.bootstrap.js"></script>
<script src="http://cdn.bootcss.com/datatables-tabletools/2.1.5/js/TableTools.min.js"></script>
<script>
$(document).ready(function() {
$(".table-image").DataTable();
});
</script>
{% endblock %}

View File

@ -88,7 +88,6 @@
<th>Disk Usage</th>
<th>Billing</th>
<th>Summary</th>
<th>History</th>
</tr>
</thead>
<tbody>
@ -109,7 +108,6 @@
<td id='{{cluster}}_{{ loop.index }}_disk'>--</td>
<td id='{{cluster}}_{{ loop.index }}_billing'>--</td>
<td><a class="btn btn-info btn-xs" href='/vclusters/{{ cluster }}/{{ container['containername'] }}/'>Realtime</a></td>
<td><a class="btn btn-info btn-xs" href='/vclusters/{{ cluster }}/{{ container['containername'] }}/history/'>History</a></td>
</tr>
{% endfor %}
</tbody>

View File

@ -133,10 +133,10 @@
<input type = "text" placeholder="Enter Telephone Number" class="form-control" name="tel" id="mTel">
</div>
<div class="form-group">
<!--div class="form-group">
<label>Password</label>
<input type="password" placeholder="Enter Password" class="form-control" name="password" id="mPassword">
</div>
</div-->
<div class="form-group">
<label>User Group</label>
@ -194,7 +194,12 @@
"columnDefs": [
{
"render": function ( data, type, row ) {
return '<a class="btn btn-info btn-sm" data-toggle="modal" data-target="#ModifyUserModal" onClick="javascript:setFormUser(' + row[0] + ');">' + 'Edit' + '</a>';
str='<a class="btn btn-info btn-xs" data-toggle="modal" data-target="#ModifyUserModal" onClick="javascript:setFormUser(' + row[0] + ');">' + 'Edit' + '</a>';
if (row[6]=='applying')
{
str=str + '<a class="btn btn-danger btn-xs" onClick="javascript:setActivateUser(' + row[0] + ');">' + 'Activate' + '</a>';
}
return str;
},
"targets": 8
},
@ -249,5 +254,15 @@
$("#mDescription").val(result.description);
});
}
function setActivateUser(arg){
$.post("/user/change/",
{
ID: arg,
Instruction: "Activate",
},
function(data,status){
location.reload();
});
}
</script>
{% endblock %}

View File

@ -263,12 +263,18 @@ def statusRealtime(vcluster_name,node_name):
statusRealtimeView.node_name = node_name
return statusRealtimeView.as_view()
@app.route("/vclusters/<vcluster_name>/<vnode_name>/history/", methods=['GET'])
@login_required
def history(vcluster_name,vnode_name):
historyView.vnode_name = vnode_name
@app.route("/history/", methods=['GET'])
#@login_required
def history():
return historyView.as_view()
@app.route("/history/<vnode_name>/", methods=['GET'])
@login_required
def historyVNode(vnode_name):
historyVNodeView.vnode_name = vnode_name
return historyVNodeView.as_view()
@app.route("/monitor/hosts/<comid>/<infotype>/", methods=['POST'])
@app.route("/monitor/vnodes/<comid>/<infotype>/", methods=['POST'])
@login_required
@ -327,6 +333,11 @@ def useradd():
def usermodify():
return usermodifyView.as_view()
@app.route("/user/change/", methods=['POST'])
@administration_required
def userchange():
return usermodifyView.as_view()
@app.route("/quota/add/", methods=['POST'])
@administration_required
def quotaadd():
@ -545,4 +556,4 @@ if __name__ == '__main__':
elif opt in ("-p", "--port"):
webport = int(arg)
app.run(host = webip, port = webport, threaded=True)
app.run(host = webip, port = webport, threaded=True,)

View File

@ -46,6 +46,18 @@ class statusRealtimeView(normalView):
class historyView(normalView):
template_path = "monitor/history.html"
@classmethod
def get(self):
data = {
"user": session['username'],
}
result = dockletRequest.post('/monitor/user/createdvnodes/', data)
vnodes = result.get('createdvnodes')
return self.render(self.template_path, user = session['username'],vnodes = vnodes)
class historyVNodeView(normalView):
template_path = "monitor/historyVNode.html"
vnode_name = ""
@classmethod