Merge pull request #228 from PedroLiu/master

Add settings and logs panel
This commit is contained in:
PedroLiu 2017-05-08 17:12:23 +08:00 committed by GitHub
commit d7406f5ed3
17 changed files with 691 additions and 384 deletions

View File

@ -24,6 +24,8 @@ import http.server, cgi, json, sys, shutil
import xmlrpc.client
from socketserver import ThreadingMixIn
import nodemgr, vclustermgr, etcdlib, network, imagemgr, notificationmgr
from settings import settings
from logs import logs
import userManager,beansapplicationmgr
import monitor,traceback
import threading
@ -109,6 +111,35 @@ def beans_check(func):
def isalive(user, beans, form):
return json.dumps({'success':'true'})
@app.route("/settings/list/", methods=['POST'])
@login_required
def settings_list(user, beans, form):
user_group = post_to_user('/user/selfQuery/', {'token': request.form.get("token", None)}).get('data', None).get('group', None)
return json.dumps(settings.list(user_group = user_group))
@app.route("/settings/update/", methods=['POST'])
@login_required
def settings_update(user, beans, form):
user_group = post_to_user('/user/selfQuery/', {'token': request.form.get("token", None)}).get('data', None).get('group', None)
newSetting = {}
newSetting['ADMIN_EMAIL_ADDRESS'] = form.get('ADMIN_EMAIL_ADDRESS', '')
newSetting['EMAIL_FROM_ADDRESS'] = form.get('EMAIL_FROM_ADDRESS', '')
return json.dumps(settings.update(user_group = user_group, newSetting = newSetting))
@app.route("/logs/list/", methods=['POST'])
@login_required
def logs_list(user, beans, form):
user_group = post_to_user('/user/selfQuery/', {'token': request.form.get("token", None)}).get('data', None).get('group', None)
return json.dumps(logs.list(user_group = user_group))
@app.route("/logs/get/", methods=['POST'])
@login_required
def logs_get(user, beans, form):
user_group = post_to_user('/user/selfQuery/', {'token': request.form.get("token", None)}).get('data', None).get('group', None)
return json.dumps(logs.get(user_group = user_group, filename = form.get('filename', '')))
@app.route("/cluster/create/", methods=['POST'])
@login_required
@beans_check

45
src/logs.py Normal file
View File

@ -0,0 +1,45 @@
#!/usr/bin/python3
import env
import json, os
from log import logger
from werkzeug.utils import secure_filename
logsPath = env.getenv('FS_PREFIX') + '/local/log/'
class logsClass:
setting = {}
def list(*args, **kwargs):
if ( ('user_group' in kwargs) == False):
return {"success":'false', "reason":"Cannot get user_group"}
user_group = kwargs['user_group']
if (not ((user_group == 'admin') or (user_group == 'root'))):
return {"success": 'false', "reason": 'Unauthorized Action'}
s = os.listdir(logsPath)
r = []
for i in s:
if ('log' in i):
r.append(i)
return {'success': 'true', 'result': r}
def get(*args, **kwargs):
if ( ('user_group' in kwargs) == False):
return {"success":'false', "reason":"Cannot get user_group"}
user_group = kwargs['user_group']
if (not ((user_group == 'admin') or (user_group == 'root'))):
return {"success": 'false', "reason": 'Unauthorized Action'}
filepath = logsPath + secure_filename(kwargs['filename'])
try:
if not os.path.exists(filepath):
return {"success": 'false', "reason": 'file not exist'}
logfile = open(filepath, 'r')
logtext = logfile.read()
logfile.close()
return {'success': 'true', 'result': logtext}
except:
return {'success': 'false', 'reason': 'file read error'}
logs = logsClass()

View File

@ -148,13 +148,13 @@ class UserUsage(db.Model):
cpu = db.Column(db.String(10))
memory = db.Column(db.String(10))
disk = db.Column(db.String(10))
def __init__(self, name):
self.username = name
self.cpu = '0'
self.memory = '0'
self.disk = '0'
def __repr__(self):
return '<UserUsage %r>' % self.name
@ -210,7 +210,7 @@ class VNode(db.Model):
laststopruntime = db.Column(db.Integer)
billing = db.Column(db.Integer)
histories = db.relationship('History', backref='v_node', lazy='dynamic')
def __init__(self, vnode_name):
self.name = vnode_name
self.laststopcpuval = 0
@ -236,7 +236,7 @@ class History(db.Model):
self.cputime = cputime
self.billing = billing
self.actionTime = datetime.now()
def __repr__(self):
return "{\"id\":\"%d\",\"vnode\":\"%s\",\"action\":\"%s\",\"runningtime\":\"%d\",\"cputime\":\"%f\",\"billing\":\"%d\",\"actionTime\":\"%s\"}" % (self.id, self.vnode, self.action, self.runningtime, self.cputime, self.billing, self.actionTime.strftime("%Y-%m-%d %H:%M:%S"))
@ -248,7 +248,7 @@ class ApplyMsg(db.Model):
reason = db.Column(db.String(600))
status = db.Column(db.String(10))
time = db.Column(db.DateTime(10))
def __init__(self,username, number, reason):
self.username = username
self.number = number

View File

@ -460,7 +460,7 @@ class NetworkMgr(object):
usrpools = self.users[username]
if(workerip is not None):
ip = workerip
worker = nodemgr.ip_to_rpc(workerip)
worker = nodemgr.ip_to_rpc(workerip)
logger.info("setup gateway for %s with %s and vlan=%s on %s" % (username, usrpools.get_gateway_cidr(), str(usrpools.vlanid), ip))
self.usrgws[username] = ip
self.dump_usrgw(username)

View File

@ -148,4 +148,3 @@ class NodeMgr(object):
def ip_to_rpc(self,ip):
return xmlrpc.client.ServerProxy("http://%s:%s" % (ip, env.getenv("WORKER_PORT")))

54
src/settings.py Normal file
View File

@ -0,0 +1,54 @@
#!/usr/bin/python3
import env
import json, os
from functools import wraps
from log import logger
class settingsClass:
setting = {}
def __init__(self):
settingPath = env.getenv('FS_PREFIX') + '/local/settings.conf'
if not os.path.exists(settingPath):
settingFile = open(settingPath,'w')
setting = {}
settingFile.write(json.dumps(setting))
settingFile.close()
else:
settingFile = open(settingPath, 'r')
settingText = settingFile.read()
settingFile.close()
self.setting = json.loads(settingText)
def get(self, arg):
return self.setting.get(arg,'')
def list(*args, **kwargs):
if ( ('user_group' in kwargs) == False):
return {"success":'false', "reason":"Cannot get user_group"}
user_group = kwargs['user_group']
if (not ((user_group == 'admin') or (user_group == 'root'))):
return {"success": 'false', "reason": 'Unauthorized Action'}
return {'success': 'true', 'result': args[0].setting}
def update(*args, **kwargs):
try:
if ( ('user_group' in kwargs) == False):
return {"success":'false', "reason":"Cannot get user_group"}
user_group = kwargs['user_group']
if (not ((user_group == 'admin') or (user_group == 'root'))):
return {"success": 'false', "reason": 'Unauthorized Action'}
newSetting = kwargs['newSetting']
settingPath = env.getenv('FS_PREFIX') + '/local/settings.conf';
settingText = json.dumps(newSetting)
settingFile = open(settingPath,'w')
settingFile.write(settingText)
settingFile.close()
args[0].setting = newSetting
return {'success': 'true'}
except:
return {'success': 'false'}
settings = settingsClass()

View File

@ -14,6 +14,7 @@ import hashlib
import pam
from base64 import b64encode
import env
from settings import settings
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
@ -23,8 +24,6 @@ import json
from log import logger
from lvmtool import *
email_from_address = env.getenv('EMAIL_FROM_ADDRESS')
admin_email_address = env.getenv('ADMIN_EMAIL_ADDRESS')
PAM = pam.pam()
fspath = env.getenv('FS_PREFIX')
data_quota = env.getenv('DATA_QUOTA')
@ -71,6 +70,7 @@ def token_required(func):
return wrapper
def send_activated_email(to_address, username):
email_from_address = settings.get('EMAIL_FROM_ADDRESS')
if (email_from_address in ['\'\'', '\"\"', '']):
return
#text = 'Dear '+ username + ':\n' + ' Your account in docklet has been activated'
@ -97,8 +97,10 @@ def send_activated_email(to_address, username):
s.close()
def send_remind_activating_email(username):
admin_email_address = env.getenv('ADMIN_EMAIL_ADDRESS')
#admin_email_address = env.getenv('ADMIN_EMAIL_ADDRESS')
nulladdr = ['\'\'', '\"\"', '']
email_from_address = settings.get('EMAIL_FROM_ADDRESS')
admin_email_address = settings.get('ADMIN_EMAIL_ADDRESS')
if (email_from_address in nulladdr or admin_email_address in nulladdr):
return
#text = 'Dear '+ username + ':\n' + ' Your account in docklet has been activated'
@ -123,7 +125,7 @@ def send_remind_activating_email(username):
textmsg = MIMEText(text,'html','utf-8')
msg['Subject'] = Header(subject, 'utf-8')
msg['From'] = email_from_address
msg['To'] = alladdr
msg['To'] = alladdr
msg.attach(textmsg)
s = smtplib.SMTP()
s.connect()

View File

@ -26,7 +26,6 @@ G_masterips = []
for masterip in masterips:
G_masterips.append(masterip.split("@")[0] + ":" + str(env.getenv("MASTER_PORT")))
from flask import Flask, request, session, render_template, redirect, send_from_directory, make_response, url_for, abort
from functools import wraps
import userManager,beansapplicationmgr, notificationmgr

42
web/static/dist/css/filebox.css vendored Normal file
View File

@ -0,0 +1,42 @@
.file-box {
float: left;
width: 220px;
}
.file {
border: 1px solid #e7eaec;
padding: 0;
background-color: #ffffff;
position: relative;
margin-bottom: 20px;
margin-right: 20px;
}
.file .icon {
padding: 15px 10px;
text-align: center;
}
.file .icon i {
font-size: 70px;
color: #dadada;
}
.file .file-name {
padding: 10px;
background-color: #f8f8f8;
border-top: 1px solid #e7eaec;
}
.corner {
position: absolute;
display: inline-block;
width: 0;
height: 0;
line-height: 0;
border: 0.6em solid transparent;
border-right: 0.6em solid #f1f1f1;
border-bottom: 0.6em solid #f1f1f1;
right: 0em;
bottom: 0em;
}
a:hover{
text-decoration:none;
}

View File

@ -172,7 +172,7 @@
var host = window.location.host;
$.post("http://"+host+"/image/list/"+masterip+"/",{},function(data){
var images = data.images;
var imagehtml =
var imagehtml =
"<thread>"
+"<tr>"
+"<th>ImageName</th>"

View File

@ -172,9 +172,13 @@
<li id="admin">
<a href='/admin/'><i class="fa fa-user"></i> <span class="nav-label">Admin</span></a>
</li>
<li id="nav_Cloud">
<a href='/cloud/'><i class="fa fa-cloud"></i> <span class="nav-label">Cloud</span></a>
<li>
<li id="nav_Cloud">
<a href='/cloud/'><i class="fa fa-cloud"></i> <span class="nav-label">Cloud</span></a>
<li>
<li id="settings">
<a href='/settings/'><i class="fa fa-user"></i> <span class="nav-label">Settings</span></a>
</li>
{% endif %}
</ul>
@ -263,6 +267,8 @@
$("#nav_Notification").addClass('active');
else if(pathname[1] == 'admin')
$("#admin").addClass('active');
else if(pathname[1] == 'settings')
$("#settings").addClass('active');
</script>

View File

@ -1,362 +1,362 @@
{% extends "base_AdminLTE.html"%}
<!--
Config Page :
1. images
2. workspace templates
-->
{% block title %}Docklet | Config{% endblock %}
{% block panel_title %}Config{% endblock %}
{% block panel_list %}
<ol class="breadcrumb">
<li>
<a href="/dashboard/"><i class="fa fa-dashboard"></i>Home</a>
</li>
<li class="active">
<strong>Config</strong>
</li>
</ol>
{% endblock %}
{% block css_src %}
<link href="//cdn.bootcss.com/datatables/1.10.11/css/dataTables.bootstrap.min.css" rel="stylesheet">
<link href="//cdn.bootcss.com/datatables/1.10.11/css/jquery.dataTables_themeroller.css" rel="stylesheet">
<link href="/static/dist/css/modalconfig.css" rel="stylesheet">
{% endblock %}
{% block content %}
{% for master in allclusters %}
{% for clustername, clusterinfo in allclusters[master].items() %}
<div class="row">
<div class="col-md-12">
<div class="box box-info collapsed-box">
<div class="box-header with-border">
<h3 class="box-title">WorkSpace Name: {{ clustername }} <strong>@ {{master.split("@")[1]}}</strong></h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-plus"></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" style="display:none">
<div class="row">
<div class="col-md-12">
<div class="box box-info">
<div class="box-header with-border">
<h4 class="box-title">VCLUSTER</h4>
<h5>create_time:{{clusterinfo['create_time']}}&nbsp&nbsp&nbsp&nbsp&nbsp&nbspstart_time:{{clusterinfo['start_time']}}</h5>
<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">
<p>
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#Scaleout_{{ clustername }}_{{master.split("@")[1]}}"><i class="fa fa-plus"></i>Add Node</button>
</p>
<div class="modal inmodal" id="Scaleout_{{ clustername }}_{{master.split("@")[1]}}" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content animated fadeIn">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<i class="fa fa-plus modal-icon"></i>
<h4 class="modal-title">Choose Image</h4>
<small class="font-bold">Choose an image to add node</small>
</div>
<div class="modal-body">
<div class="form-group">
<form action="/workspace/scaleout/{{ clustername }}/{{master.split("@")[0]}}/" method="POST" >
<table class="table table-striped table-bordered table-hover table-image">
<thead>
<tr>
<th>ImageName</th>
<th>Type</th>
<th>Owner</th>
<th>Choose</th>
</tr>
</thead>
<tbody>
<tr>
<td>base</td>
<td>public</td>
<td>docklet</td>
<td><input type="radio" name="image" value="base_base_base" checked="checked"></td>
</tr>
{% for image in allimages[master]['private'] %}
<tr>
<td>{{image['name']}}</td>
<td>private</td>
<td>{{mysession['username']}}</td>
<td><input type="radio" name="image" value="{{image['name']}}_{{mysession['username']}}_private"></td>
</tr>
{% endfor %}
{% for p_user, p_images in allimages[master]['public'].items() %}
{% for image in p_images %}
<tr>
<td>{{image['name']}}</td>
<td>public</td>
<td>{{p_user}}</td>
<td><input type="radio" name="image" value="{{image['name']}}_{{p_user}}_public"></td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
<div class="hr-line-dashed"></div>
<div class="panel-group" id="accordion_{{clustername}}_{{master.split("@")[1]}}">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-panel="#accordion_{{clustername}}_{{master.split("@")[1]}}" href="#collapseOne_{{clustername}}_{{master.split("@")[1]}}">
show advanced options
</a>
</h4>
</div>
<div id="collapseOne_{{clustername}}_{{master.split("@")[1]}}" class="panel-collapse collapse">
<div class="panel-body">
<div class="form-group">
<label class="control-label">CPU</label>
<div ><input type="number" class="form-control" name="cpuSetting" id="cpuSetting" value = {{defaultsetting['cpu']}} /> {{usage['cpu']}}CORE/{{quota['cpu']}}CORE
</div>
</div>
<div class="form-group">
<label class="control-label">MEMORY</label>
<div ><input type="number" class="form-control" name="memorySetting" id="memorySetting" value = {{defaultsetting['memory']}} /> {{usage['memory']}}MB/{{quota['memory']}}MB
</div>
</div>
<div class="form-group">
<label class="control-label">DISK</label>
<div><input type="number" class="form-control" name="diskSetting" id="diskSetting" value= {{defaultsetting['disk']}} /> {{usage['disk']}} MB/{{quota['disk']}}MB
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-white" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-success">Add</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>Node ID</th>
<th>Node Name</th>
<th>IP Address</th>
<th>Status</th>
<th>Image</th>
<th>Save</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for container in clusterinfo['containers'] %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ container['containername'] }}</td>
<td>{{ container['ip'] }}</td>
{% if clusterinfo['status'] == 'stopped' %}
<td><div class="text-warning"><i class="fa fa-stop"></i> Stopped</div></td>
{% else %}
<td><div class="text-success"><i class="fa fa-play"></i> Running</div></td>
{% endif %}
<td>{{ container['image'] }}</td>
<td><button type="button" class="btn btn-success btn-xs" data-toggle="modal" data-target="#DelModal_{{ container['containername'] }}_{{master.split("@")[1]}}">Save</button></td>
{% if container['containername'][-2:] == '-0' %}
<td><button class="btn btn-xs btn-default">Delete</button></td>
{% else %}
<td><a class="btn btn-xs btn-danger" href="/workspace/scalein/{{ clustername }}/{{ container['containername'] }}/{{master.split("@")[0]}}/">Delete</a></td>
{% endif %}
<div class="modal inmodal" id="DelModal_{{ container['containername'] }}_{{master.split("@")[1]}}" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content animated fadeIn">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<i class="fa fa-save modal-icon"></i>
<h4 class="modal-title">Save Image</h4>
<small class="font-bold">Save Your Environment As a Image</small>
</div>
<div class="modal-body">
<div class="form-group">
<form action="/workspace/save/{{ clustername }}/{{ container['containername'] }}/{{master.split("@")[0]}}/" method="POST" id="saveImage">
<label>Image Name</label>
<input type="text" placeholder="Enter Image Name" class="form-control" name="ImageName" id="ImageName"/>
<br/>
<label>Description</label>
<textarea rows="5" cols="60" name="description" id="description">please input your description</textarea>
<div class="modal-footer">
<button type="button" class="btn btn-white" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-success">Save</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<br/>
<div class="row">
<div class="col-md-12">
<div class="box box-info">
<div class="box-header with-border">
<h3 class="box-title">SERVICE</h3>
<h5><a href="{{ clusterinfo['proxy_url'] }}" title="click here jump to your proxy server">{{ clusterinfo['proxy_url'] }}</a></h5>
<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">
<form action="/addproxy/{{ clustername }}/{{master.split("@")[0]}}/" id="addproxy" method="POST">
{% if 'proxy_ip' in clusterinfo %}
<p>ip:<input type="text" id="proxy_ip" name="proxy_ip" value={{ clusterinfo['proxy_ip'][:clusterinfo['proxy_ip'].index(':')] }} readonly="true"/>port:<input type="text" id="proxy_port" name="proxy_port" value={{ clusterinfo['proxy_ip'][clusterinfo['proxy_ip'].index(':')+1:] }} readonly="true"/>
<button type="button" class="btn-xs btn-default">enable</button>
<a href="/deleteproxy/{{ clustername }}/{{master.split("@")[0]}}"><button type="button" class="btn-xs btn-danger">disable</button></a></p>
{% else %}
<p>ip:<input type="text" id="proxy_ip" name="proxy_ip" value={{ clusterinfo["containers"][0]["ip"][:clusterinfo["containers"][0]["ip"].index("/")] }} />port:<input type="text" id="proxy_port" name="proxy_port" value="80"/>
<button type="submit" class="btn-xs btn-success">enable</button>
<button type="button" class="btn-xs btn-default">disable</button></p>
{% endif %}
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
{% endfor %}
<div class="row">
<div class="col-lg-12">
<div class="box box-info collapsed-box">
<div class="box-header with-border">
<h3 class="box-title">Image Info</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-plus"></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" style="display:none">
<table class="table table-striped table-bordered table-hover table-image" >
<thead>
<tr>
<th>ImageName</th>
<th>Type</th>
<th>Owner</th>
<th>CreateTime</th>
<th>Description</th>
<th>Location</th>
<th>Status</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<tr>
<td>base</td>
<td>public</td>
<td>docklet</td>
<td>2015-01-01 00:00:00</td>
<td>A Base Image For You</td>
<td>--</td>
<td></td>
<td></td>
</tr>
{% for master in allimages %}
{% for image in allimages[master]['private'] %}
<tr>
<td>{{image['name']}}</td>
<td>private</td>
<td>{{mysession['username']}}</td>
<td>{{image['time']}}</td>
<td><a href="/image/description/{{image['name']}}_{{mysession['username']}}_private/{{master.split("@")[0]}}/" target="_blank">{{image['description']}}</a></td>
<td>{{master.split("@")[1]}}</td>
{% if image['isshared'] == 'false' %}
<td>unshared</td>
<td>
<a href="/image/share/{{ image['name'] }}/{{master.split("@")[0]}}/"><button type="button" class="btn btn-xs btn-success">share</button></a>
<a href="/image/delete/{{ image['name'] }}/{{master.split("@")[0]}}/"><button type="button" class="btn btn-xs btn-danger">delete</button></a>
</td>
{% else %}
<td>shared</td>
<td>
<a href="/image/unshare/{{ image['name'] }}/{{master.split("@")[0]}}/"><button type="button" class="btn btn-xs btn-warning">unshare</button></a>
<a href="/image/delete/{{ image['name'] }}/{{master.split("@")[0]}}/"><button type="button" class="btn btn-xs btn-danger">delete</button></a>
</td>
{% endif %}
</tr>
{% endfor %}
{% for p_user,p_images in allimages[master]['public'].items() %}
{% for image in p_images %}
<tr>
<td>{{image['name']}}</td>
<td>public</td>
<td>{{p_user}}</td>
<td>{{image['time']}}</td>
<td><a href="/image/description/{{image['name']}}_{{p_user}}_public/{{master.split("@")[0]}}/" target="_blank">{{image['description']}}</a></td>
<td>{{master.split("@")[1]}}</td>
<td></td>
{% if p_user == mysession['username'] %}
<td><a href="/image/unshare/{{ image['name'] }}/{{master.split("@")[0]}}/"><button type="button" class="btn btn-xs btn-warning">unshare</button></a></td>
{% else %}
<td></td>
{% endif %}
</tr>
{% endfor %}
{% endfor %}
{% 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();
$(".table-image").attr("style","");
});
</script>
{% endblock %}
{% extends "base_AdminLTE.html"%}
<!--
Config Page :
1. images
2. workspace templates
-->
{% block title %}Docklet | Config{% endblock %}
{% block panel_title %}Config{% endblock %}
{% block panel_list %}
<ol class="breadcrumb">
<li>
<a href="/dashboard/"><i class="fa fa-dashboard"></i>Home</a>
</li>
<li class="active">
<strong>Config</strong>
</li>
</ol>
{% endblock %}
{% block css_src %}
<link href="//cdn.bootcss.com/datatables/1.10.11/css/dataTables.bootstrap.min.css" rel="stylesheet">
<link href="//cdn.bootcss.com/datatables/1.10.11/css/jquery.dataTables_themeroller.css" rel="stylesheet">
<link href="/static/dist/css/modalconfig.css" rel="stylesheet">
{% endblock %}
{% block content %}
{% for master in allclusters %}
{% for clustername, clusterinfo in allclusters[master].items() %}
<div class="row">
<div class="col-md-12">
<div class="box box-info collapsed-box">
<div class="box-header with-border">
<h3 class="box-title">WorkSpace Name: {{ clustername }} <strong>@ {{master.split("@")[1]}}</strong></h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-plus"></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" style="display:none">
<div class="row">
<div class="col-md-12">
<div class="box box-info">
<div class="box-header with-border">
<h4 class="box-title">VCLUSTER</h4>
<h5>create_time:{{clusterinfo['create_time']}}&nbsp&nbsp&nbsp&nbsp&nbsp&nbspstart_time:{{clusterinfo['start_time']}}</h5>
<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">
<p>
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#Scaleout_{{ clustername }}_{{master.split("@")[1]}}"><i class="fa fa-plus"></i>Add Node</button>
</p>
<div class="modal inmodal" id="Scaleout_{{ clustername }}_{{master.split("@")[1]}}" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content animated fadeIn">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<i class="fa fa-plus modal-icon"></i>
<h4 class="modal-title">Choose Image</h4>
<small class="font-bold">Choose an image to add node</small>
</div>
<div class="modal-body">
<div class="form-group">
<form action="/workspace/scaleout/{{ clustername }}/{{master.split("@")[0]}}/" method="POST" >
<table class="table table-striped table-bordered table-hover table-image">
<thead>
<tr>
<th>ImageName</th>
<th>Type</th>
<th>Owner</th>
<th>Choose</th>
</tr>
</thead>
<tbody>
<tr>
<td>base</td>
<td>public</td>
<td>docklet</td>
<td><input type="radio" name="image" value="base_base_base" checked="checked"></td>
</tr>
{% for image in allimages[master]['private'] %}
<tr>
<td>{{image['name']}}</td>
<td>private</td>
<td>{{mysession['username']}}</td>
<td><input type="radio" name="image" value="{{image['name']}}_{{mysession['username']}}_private"></td>
</tr>
{% endfor %}
{% for p_user, p_images in allimages[master]['public'].items() %}
{% for image in p_images %}
<tr>
<td>{{image['name']}}</td>
<td>public</td>
<td>{{p_user}}</td>
<td><input type="radio" name="image" value="{{image['name']}}_{{p_user}}_public"></td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
<div class="hr-line-dashed"></div>
<div class="panel-group" id="accordion_{{clustername}}_{{master.split("@")[1]}}">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-panel="#accordion_{{clustername}}_{{master.split("@")[1]}}" href="#collapseOne_{{clustername}}_{{master.split("@")[1]}}">
show advanced options
</a>
</h4>
</div>
<div id="collapseOne_{{clustername}}_{{master.split("@")[1]}}" class="panel-collapse collapse">
<div class="panel-body">
<div class="form-group">
<label class="control-label">CPU</label>
<div ><input type="number" class="form-control" name="cpuSetting" id="cpuSetting" value = {{defaultsetting['cpu']}} /> {{usage['cpu']}}CORE/{{quota['cpu']}}CORE
</div>
</div>
<div class="form-group">
<label class="control-label">MEMORY</label>
<div ><input type="number" class="form-control" name="memorySetting" id="memorySetting" value = {{defaultsetting['memory']}} /> {{usage['memory']}}MB/{{quota['memory']}}MB
</div>
</div>
<div class="form-group">
<label class="control-label">DISK</label>
<div><input type="number" class="form-control" name="diskSetting" id="diskSetting" value= {{defaultsetting['disk']}} /> {{usage['disk']}} MB/{{quota['disk']}}MB
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-white" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-success">Add</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>Node ID</th>
<th>Node Name</th>
<th>IP Address</th>
<th>Status</th>
<th>Image</th>
<th>Save</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for container in clusterinfo['containers'] %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ container['containername'] }}</td>
<td>{{ container['ip'] }}</td>
{% if clusterinfo['status'] == 'stopped' %}
<td><div class="text-warning"><i class="fa fa-stop"></i> Stopped</div></td>
{% else %}
<td><div class="text-success"><i class="fa fa-play"></i> Running</div></td>
{% endif %}
<td>{{ container['image'] }}</td>
<td><button type="button" class="btn btn-success btn-xs" data-toggle="modal" data-target="#DelModal_{{ container['containername'] }}_{{master.split("@")[1]}}">Save</button></td>
{% if container['containername'][-2:] == '-0' %}
<td><button class="btn btn-xs btn-default">Delete</button></td>
{% else %}
<td><a class="btn btn-xs btn-danger" href="/workspace/scalein/{{ clustername }}/{{ container['containername'] }}/{{master.split("@")[0]}}/">Delete</a></td>
{% endif %}
<div class="modal inmodal" id="DelModal_{{ container['containername'] }}_{{master.split("@")[1]}}" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content animated fadeIn">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<i class="fa fa-save modal-icon"></i>
<h4 class="modal-title">Save Image</h4>
<small class="font-bold">Save Your Environment As a Image</small>
</div>
<div class="modal-body">
<div class="form-group">
<form action="/workspace/save/{{ clustername }}/{{ container['containername'] }}/{{master.split("@")[0]}}/" method="POST" id="saveImage">
<label>Image Name</label>
<input type="text" placeholder="Enter Image Name" class="form-control" name="ImageName" id="ImageName"/>
<br/>
<label>Description</label>
<textarea rows="5" cols="60" name="description" id="description">please input your description</textarea>
<div class="modal-footer">
<button type="button" class="btn btn-white" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-success">Save</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<br/>
<div class="row">
<div class="col-md-12">
<div class="box box-info">
<div class="box-header with-border">
<h3 class="box-title">SERVICE</h3>
<h5><a href="{{ clusterinfo['proxy_url'] }}" title="click here jump to your proxy server">{{ clusterinfo['proxy_url'] }}</a></h5>
<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">
<form action="/addproxy/{{ clustername }}/{{master.split("@")[0]}}/" id="addproxy" method="POST">
{% if 'proxy_ip' in clusterinfo %}
<p>ip:<input type="text" id="proxy_ip" name="proxy_ip" value={{ clusterinfo['proxy_ip'][:clusterinfo['proxy_ip'].index(':')] }} readonly="true"/>port:<input type="text" id="proxy_port" name="proxy_port" value={{ clusterinfo['proxy_ip'][clusterinfo['proxy_ip'].index(':')+1:] }} readonly="true"/>
<button type="button" class="btn-xs btn-default">enable</button>
<a href="/deleteproxy/{{ clustername }}/{{master.split("@")[0]}}"><button type="button" class="btn-xs btn-danger">disable</button></a></p>
{% else %}
<p>ip:<input type="text" id="proxy_ip" name="proxy_ip" value={{ clusterinfo["containers"][0]["ip"][:clusterinfo["containers"][0]["ip"].index("/")] }} />port:<input type="text" id="proxy_port" name="proxy_port" value="80"/>
<button type="submit" class="btn-xs btn-success">enable</button>
<button type="button" class="btn-xs btn-default">disable</button></p>
{% endif %}
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
{% endfor %}
<div class="row">
<div class="col-lg-12">
<div class="box box-info collapsed-box">
<div class="box-header with-border">
<h3 class="box-title">Image Info</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-plus"></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" style="display:none">
<table class="table table-striped table-bordered table-hover table-image" >
<thead>
<tr>
<th>ImageName</th>
<th>Type</th>
<th>Owner</th>
<th>CreateTime</th>
<th>Description</th>
<th>Location</th>
<th>Status</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<tr>
<td>base</td>
<td>public</td>
<td>docklet</td>
<td>2015-01-01 00:00:00</td>
<td>A Base Image For You</td>
<td>--</td>
<td></td>
<td></td>
</tr>
{% for master in allimages %}
{% for image in allimages[master]['private'] %}
<tr>
<td>{{image['name']}}</td>
<td>private</td>
<td>{{mysession['username']}}</td>
<td>{{image['time']}}</td>
<td><a href="/image/description/{{image['name']}}_{{mysession['username']}}_private/{{master.split("@")[0]}}/" target="_blank">{{image['description']}}</a></td>
<td>{{master.split("@")[1]}}</td>
{% if image['isshared'] == 'false' %}
<td>unshared</td>
<td>
<a href="/image/share/{{ image['name'] }}/{{master.split("@")[0]}}/"><button type="button" class="btn btn-xs btn-success">share</button></a>
<a href="/image/delete/{{ image['name'] }}/{{master.split("@")[0]}}/"><button type="button" class="btn btn-xs btn-danger">delete</button></a>
</td>
{% else %}
<td>shared</td>
<td>
<a href="/image/unshare/{{ image['name'] }}/{{master.split("@")[0]}}/"><button type="button" class="btn btn-xs btn-warning">unshare</button></a>
<a href="/image/delete/{{ image['name'] }}/{{master.split("@")[0]}}/"><button type="button" class="btn btn-xs btn-danger">delete</button></a>
</td>
{% endif %}
</tr>
{% endfor %}
{% for p_user,p_images in allimages[master]['public'].items() %}
{% for image in p_images %}
<tr>
<td>{{image['name']}}</td>
<td>public</td>
<td>{{p_user}}</td>
<td>{{image['time']}}</td>
<td><a href="/image/description/{{image['name']}}_{{p_user}}_public/{{master.split("@")[0]}}/" target="_blank">{{image['description']}}</a></td>
<td>{{master.split("@")[1]}}</td>
<td></td>
{% if p_user == mysession['username'] %}
<td><a href="/image/unshare/{{ image['name'] }}/{{master.split("@")[0]}}/"><button type="button" class="btn btn-xs btn-warning">unshare</button></a></td>
{% else %}
<td></td>
{% endif %}
</tr>
{% endfor %}
{% endfor %}
{% 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();
$(".table-image").attr("style","");
});
</script>
{% endblock %}

View File

@ -0,0 +1,83 @@
{% extends "base_AdminLTE.html"%}
{% block title %}Docklet | Settings{% endblock %}
{% block panel_title %}Settings{% endblock %}
{% block panel_list %}
<ol class="breadcrumb">
<li>
<a href="/dashboard/"><i class="fa fa-dashboard"></i>Home</a>
</li>
<li class="active">
<strong>Settings</strong>
</li>
</ol>
{% endblock %}
{% block css_src %}
<link href="/static/dist/css/filebox.css" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Modify Settings</h3>
</div>
<div class="box-body">
<form role="form" action="/settings/" method="POST" >
<div class="box-body">
<div class="form-group">
<label for="ADMIN_EMAIL_ADDRESS">Admin Email Address</label>
<p class="help-block">when an activating request is sent, an e-mail will be sent to this address to remind the admin. </p>
<p class="help-block">if this address is "", no email will be sent to admin.</p>
<input type="email" class="form-control" id="ADMIN_EMAIL_ADDRESS" value="{{ settings['ADMIN_EMAIL_ADDRESS'] }}" name="ADMIN_EMAIL_ADDRESS">
</div>
<div class="form-group">
<label for="EMAIL_FROM_ADDRESS">Email From Address</label>
<p class="help-block">the e-mail address to send activating e-mail to user</p>
<p class="help-block">if this address is "", no email will be sent out.</p>
<input type="input" class="form-control" id="EMAIL_FROM_ADDRESS" name="EMAIL_FROM_ADDRESS" value="{{ settings['EMAIL_FROM_ADDRESS'] }}">
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Logs</h3>
</div>
<div class="box-body">
{% for filename in logs %}
<div class="file-box">
<div class="file">
<a href="/logs/{{ filename }}/">
<span class="corner"></span>
<div class="icon">
<i class="fa fa-file"></i>
</div>
<div class="file-name">
{{ filename }}
</div>
</a>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -27,6 +27,7 @@ from webViews.notification.notification import CreateNotificationView, Notificat
QueryNotificationView, ModifyNotificationView, DeleteNotificationView
from webViews.user.userinfo import userinfoView
from webViews.user.userActivate import userActivateView
from webViews.settings import settingsView
from webViews.user.grouplist import grouplistView, groupqueryView, groupdetailView, groupmodifyView
from functools import wraps
from webViews.dockletrequest import dockletRequest
@ -42,7 +43,7 @@ import webViews.dockletrequest
from webViews import cookie_tool
import traceback
from werkzeug.utils import secure_filename
@ -358,6 +359,22 @@ def monitorUserAll():
return monitorUserAllView.as_view()
'''
@app.route("/settings/", methods=['GET', 'POST'])
@administration_required
def settings():
return settingsView.as_view()
@app.route("/logs/<filename>/", methods=['GET'])
@administration_required
def logs_get(filename):
data = {
"filename": filename
}
result = dockletRequest.post('/logs/get/', data).get('result', '')
response = make_response(result)
response.headers["content-type"] = "text/plain"
return response
@app.route("/user/list/", methods=['GET', 'POST'])
@administration_required
def userlist():
@ -645,4 +662,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, debug = False, threaded=True)

View File

@ -147,7 +147,7 @@ class startClusterView(normalView):
error_path = "error.html"
@classmethod
def get(self):
def get(self):
masterip = self.masterip
data = {
"clustername": self.clustername
@ -215,7 +215,7 @@ class detailClusterView(normalView):
template_path = "listcontainer.html"
@classmethod
def get(self):
def get(self):
masterip = self.masterip
data = {
"clustername": self.clustername

View File

@ -3,6 +3,7 @@ from flask import abort, session
from webViews.log import logger
import os,sys,inspect
this_folder = os.path.realpath(os.path.abspath(os.path.split(inspect.getfile(inspect.currentframe ()))[0]))
src_folder = os.path.realpath(os.path.abspath(os.path.join(this_folder,"../..", "src")))
if src_folder not in sys.path:
@ -14,6 +15,12 @@ masterips=env.getenv('MASTER_IPS').split(",")
user_endpoint = "http://" + env.getenv('USER_IP') + ":" + str(env.getenv('USER_PORT'))
master_port=str(env.getenv('MASTER_PORT'))
def getip(masterip):
return masterip.split("@")[0]
def getname(masterip):
return masterip.split("@")[1]
def getip(masterip):
return masterip.split("@")[0]
@ -51,11 +58,14 @@ class dockletRequest():
if (result.get('Unauthorized', None) == 'True'):
session['401'] = 'Token Expired'
abort(401)
logger.info ("Docklet Response: user = %s result = %s, url = %s"%(session['username'], result, url))
logstr = "Docklet Response: user = %s result = %s, url = %s" % (session['username'], result, url)
if (sys.getsizeof(logstr) > 512):
logstr = "Docklet Response: user = %s, url = %s"%(session['username'], url)
logger.info(logstr)
return result
#except:
#abort(500)
@classmethod
def post_to_all(self, url = '/', data={}):
if (url == '/'):

19
web/webViews/settings.py Normal file
View File

@ -0,0 +1,19 @@
from flask import session,render_template,redirect, request
from webViews.view import normalView
from webViews.dockletrequest import dockletRequest
class settingsView(normalView):
template_path = "settings.html"
@classmethod
def get(self):
settings = dockletRequest.post('/settings/list/')['result']
logs = dockletRequest.post('/logs/list/')['result']
logs.sort()
logs.sort(key = len)
return self.render(self.template_path, settings = settings, logs = logs)
@classmethod
def post(self):
result = dockletRequest.post('/settings/update/', request.form)
return redirect('/settings/')