separate livesupport to another blueprint

This commit is contained in:
deflax 2018-04-22 14:59:44 +03:00
parent 75dacd8c8a
commit 50665628c9
15 changed files with 350 additions and 99 deletions

View file

@ -68,6 +68,9 @@ app.register_blueprint(smanager_blueprint, url_prefix='/smanager')
from .dmanager import dmanager as dmanager_blueprint
app.register_blueprint(dmanager_blueprint, url_prefix='/dmanager')
from .livesupport import livesupport as livesupport_blueprint
app.register_blueprint(livesupport_blueprint, url_prefix='/support')
from .uinvoice import uinvoice as uinvoice_blueprint
app.register_blueprint(uinvoice_blueprint, url_prefix='/uinvoice')

View file

@ -0,0 +1,3 @@
from flask import Blueprint
livesupport = Blueprint('livesupport', __name__)
from . import routes

27
app/livesupport/forms.py Normal file
View file

@ -0,0 +1,27 @@
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, SelectField, DecimalField
from flask_pagedown.fields import PageDownField
from wtforms import validators, ValidationError
from wtforms.fields.html5 import EmailField, DecimalRangeField
from .. import db
class OrderForm(FlaskForm):
region_choices = [(1, 'Plovdiv, Bulgaria'), (2, 'International Space Station')]
region = SelectField('Region:', choices=region_choices, coerce=int)
recipe_choices = [(1, 'RootVPS')]
recipe = SelectField('Type:', choices=recipe_choices, coerce=int)
cpu = DecimalRangeField('Processor Cores', default=2)
memory = DecimalRangeField('Memory', default=2048)
storage = DecimalRangeField('Storage', default=20)
alias = StringField('Name:', [validators.Regexp(message='ex.: myservice1.com, myservice2.local', regex='^[a-zA-Z0-9][a-zA-Z0-9-_]{0,61}[a-zA-Z0-9]{0,1}\.([a-zA-Z]{1,6}|[a-zA-Z0-9-]{1,30}\.[a-zA-Z]{2,3})$'), validators.Length(6,64)])
submit = SubmitField('DEPLOY')
class MessageForm(FlaskForm):
line = PageDownField('Enter your message...', validators=[validators.DataRequired()])
submit = SubmitField('Submit')

83
app/livesupport/routes.py Normal file
View file

@ -0,0 +1,83 @@
from flask import render_template, abort, redirect, url_for, abort, flash, request, current_app, make_response, g
from flask_login import login_required, login_user, logout_user, current_user
from flask_sqlalchemy import get_debug_queries
from . import livesupport
from .forms import MessageForm
from .. import db
from ..email import send_email
from ..models import User, Permission, SupportTopic, SupportLine, contact_proxmaster
import base64
from datetime import date, time, datetime
from dateutil.relativedelta import relativedelta
@livesupport.after_app_request
def after_request(response):
for query in get_debug_queries():
if query.duration >= current_app.config['SLOW_DB_QUERY_TIME']:
current_app.logger.warning('Slow query: %s\nParameters: %s\nDuration: %fs\nContext: %s\n' % (query.statement, query.parameters, query.duration, query.context))
return response
#SUPPORT
@livesupport.route("/support", methods=['GET'])
@login_required
def support_list():
""" general enquiry and list all open support tasks """
cuser = current_user
form = MessageForm()
alltopics = cuser.inv_topics.all()
return render_template('livesupport/support_list.html', form=form, inv_topics=alltopics)
@livesupport.route("/support/<string:topic>/", methods=['GET', 'POST'])
@login_required
def support(topic):
""" block item for support chatbox. invoked from vdc_pool or supportlist """
cuser = current_user
form = MessageForm()
if request.method == "GET":
support_topic = SupportTopic.query.filter_by(hashtag=str(topic)).first()
if support_topic == None:
class EmptySupport():
hashtag=str(topic)
timestamp=datetime.utcnow()
support_topic = EmptySupport()
return render_template('livesupport/support_item.html', form=form, support=support_topic)
else:
if support_topic.user_id != cuser.pid:
abort(403) #TODO: hidden 403. there is a topic like that but its not yours!
else:
#topic is yours. show it.
return render_template('livesupport/support_item.html', form=form, support=support_topic)
if request.method == "POST" and form.validate_on_submit():
support_topic = SupportTopic.query.filter_by(hashtag=str(topic)).first()
if support_topic == None:
#no topic. create one?
if cuser.inv_topics.all() != []:
#check if other topics exist, and ratelimit
last_topic = cuser.inv_topics.order_by(SupportTopic.timestamp.desc()).first()
now = datetime.utcnow()
time_last_topic = last_topic.timestamp
expiry = time_last_topic + relativedelta(time_last_topic, minutes=+5)
if now < expiry:
flash('ratelimit. try again later')
return redirect(url_for('livesupport.support_list'))
#create new topic
new_topic = SupportTopic(user_id=cuser.pid, hashtag=str(topic))
db.session.add(new_topic)
new_line = SupportLine(topic_id=new_topic.pid, line=str(form.line.data))
db.session.add(new_line)
else:
if support_topic.user_id == cuser.pid:
new_line = SupportLine(topic_id=support_topic.pid, line=form.line.data)
db.session.add(new_line)
else:
abort(403) #TODO: hidden 404
db.session.commit()
return redirect(url_for('livesupport.support_list'))

View file

@ -441,7 +441,7 @@ class SupportTopic(db.Model):
class SupportLine(db.Model):
__tablename__ = 'support_line'
pid = db.Column(db.Integer, primary_key=True)
topic_id = db.Column(db.ForeignKey('support_topic.pid'))
topic_id = db.Column(db.ForeignKey('support_topic.pid')) #FK
line = db.Column(db.Unicode)
timestamp = db.Column(db.DateTime(), default=datetime.utcnow)

View file

@ -21,7 +21,4 @@ class OrderForm(FlaskForm):
submit = SubmitField('DEPLOY')
class MessageForm(FlaskForm):
line = PageDownField('Enter your message...', validators=[validators.DataRequired()])
submit = SubmitField('Submit')

View file

@ -6,7 +6,7 @@ from . import panel
from .forms import OrderForm, MessageForm
from .. import db
from ..email import send_email
from ..models import User, Permission, Recipe, Order, Server, Deployment, Service, Region, Address, Domain, SupportTopic, SupportLine, contact_proxmaster
from ..models import User, Permission, Recipe, Order, Server, Deployment, Service, Region, Address, Domain, contact_proxmaster
import base64
from datetime import date, time, datetime
@ -99,63 +99,3 @@ def dashboard(user_pid):
continue
return render_template('panel/dashboard.html', sys_regions=sys_regions, inv_deployments=inv_deployments, inv_services=inv_services, inv_domains=inv_domains, inv_addresses=inv_addresses, rrd=rrd, status=statuses, warnflag=warnflag, regions=regions)
#SUPPORT
@panel.route("/support", methods=['GET'])
@login_required
def list_support():
""" general enquiry and list all open support tasks """
cuser = current_user
alltopics = cuser.inv_topics.all()
return render_template('panel/support_list.html', inv_topics=alltopics)
@panel.route("/support/<string:topic>/", methods=['GET', 'POST'])
@login_required
def support(topic):
""" block item for support chatbox. invoked from vdc_pool or supportlist """
cuser = current_user
form = MessageForm()
if request.method == "GET":
support_topic = SupportTopic.query.filter_by(hashtag=str(topic)).first()
if support_topic == None:
class EmptySupport():
hashtag=str(topic)
support_topic = EmptySupport()
return render_template('panel/support_item.html', form=form, support=support_topic)
else:
if support_topic.user_id != cuser.pid:
abort(403) #TODO: hidden 403. there is a topic like that but its not yours!
else:
#topic is yours. show it.
return render_template('panel/support_item.html', form=form, support=support_topic)
if request.method == "POST" and form.validate_on_submit():
support_topic = SupportTopic.query.filter_by(hashtag=str(topic)).first()
if support_topic == None:
#no topic. create one?
if cuser.inv_topics.all() != []:
#check if other topics exist, and ratelimit
last_topic = cuser.inv_topics.order_by(SupportTopic.timestamp.desc()).first()
now = datetime.utcnow()
time_last_topic = last_topic.timestamp
expiry = time_last_topic + relativedelta(time_last_topic, minutes=+5)
if now < expiry:
flash('ratelimit. try again later')
return redirect(url_for('panel.dashboard'))
#create new topic
new_topic = SupportTopic(user_id=cuser.pid, hashtag=str(topic))
db.session.add(new_topic)
new_line = SupportLine(topic_id=new_topic.pid, line=str(form.line.data))
db.session.add(new_line)
else:
if support_topic.user_id == cuser.pid:
new_line = SupportLine(topic_id=support_topic.pid, line=form.line.data)
db.session.add(new_line)
else:
abort(403) #TODO: hidden 404
db.session.commit()
return redirect(url_for('panel.dashboard'))

View file

@ -24,7 +24,7 @@ class EditProfileForm(FlaskForm):
org_account = BooleanField('This is a business account.')
org_companyname = StringField('Company Name:')
org_regaddress = StringField('Company Address:')
org_responsible = StringField('Accountable Person (optional):')
org_responsible = StringField('Accountable Person:')
org_vatnum = StringField('VAT Number:')
twofactor = BooleanField('Enable 2-factor authentication')
submit = SubmitField('Update')

View file

@ -200,7 +200,7 @@ a:active {
}
.form {
max-width: 500px;
max-width: 660px;
}
.padding-left-32 {

View file

@ -0,0 +1,99 @@
.mytext{
border:0;padding:10px;background:whitesmoke;
}
.text{
width:75%;display:flex;flex-direction:column;
}
.text > p:first-of-type{
width:100%;margin-top:0;margin-bottom:auto;line-height: 13px;font-size: 12px;
}
.text > p:last-of-type{
width:100%;text-align:right;color:silver;margin-bottom:-7px;margin-top:auto;
}
.text-l{
float:left;padding-right:10px;
}
.text-r{
float:right;padding-left:10px;
}
.avatar{
display:flex;
justify-content:center;
align-items:center;
width:25%;
float:left;
padding-right:10px;
}
.macro{
margin-top:5px;width:85%;border-radius:5px;padding:5px;display:flex;
}
.msj-rta{
float:right;background:whitesmoke;
}
.msj{
float:left;background:white;
}
.frame{
background:#e0e0de;
height:450px;
overflow:hidden;
padding:0;
}
.frame > div:last-of-type{
position:absolute;bottom:0;width:100%;display:flex;
}
body > div > div > div:nth-child(2) > span{
background: whitesmoke;padding: 10px;font-size: 21px;border-radius: 50%;
}
body > div > div > div.msj-rta.macro{
margin:auto;margin-left:1%;
}
ul {
width:100%;
list-style-type: none;
padding:18px;
position:absolute;
bottom:47px;
display:flex;
flex-direction: column;
top:0;
overflow-y:scroll;
}
.msj:before{
width: 0;
height: 0;
content:"";
top:-5px;
left:-14px;
position:relative;
border-style: solid;
border-width: 0 13px 13px 0;
border-color: transparent #ffffff transparent transparent;
}
.msj-rta:after{
width: 0;
height: 0;
content:"";
top:-5px;
left:14px;
position:relative;
border-style: solid;
border-width: 13px 13px 0 0;
border-color: whitesmoke transparent transparent transparent;
}
input:focus{
outline: none;
}
::-webkit-input-placeholder { /* Chrome/Opera/Safari */
color: #d4d4d4;
}
::-moz-placeholder { /* Firefox 19+ */
color: #d4d4d4;
}
:-ms-input-placeholder { /* IE 10+ */
color: #d4d4d4;
}
:-moz-placeholder { /* Firefox 18- */
color: #d4d4d4;
}

View file

@ -0,0 +1,83 @@
var me = {};
me.avatar = "https://lh6.googleusercontent.com/-lr2nyjhhjXw/AAAAAAAAAAI/AAAAAAAARmE/MdtfUmC0M4s/photo.jpg?sz=48";
var you = {};
you.avatar = "https://a11.t26.net/taringa/avatares/9/1/2/F/7/8/Demon_King1/48x48_5C5.jpg";
function formatAMPM(date) {
var hours = date.getHours();
var minutes = date.getMinutes();
var ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12; // the hour '0' should be '12'
minutes = minutes < 10 ? '0'+minutes : minutes;
var strTime = hours + ':' + minutes + ' ' + ampm;
return strTime;
}
//-- No use time. It is a javaScript effect.
function insertChat(who, text, time){
if (time === undefined){
time = 0;
}
var control = "";
var date = formatAMPM(new Date());
if (who == "me"){
control = '<li style="width:100%">' +
'<div class="msj macro">' +
'<div class="avatar"><img class="img-circle" style="width:100%;" src="'+ me.avatar +'" /></div>' +
'<div class="text text-l">' +
'<p>'+ text +'</p>' +
'<p><small>'+date+'</small></p>' +
'</div>' +
'</div>' +
'</li>';
}else{
control = '<li style="width:100%;">' +
'<div class="msj-rta macro">' +
'<div class="text text-r">' +
'<p>'+text+'</p>' +
'<p><small>'+date+'</small></p>' +
'</div>' +
'<div class="avatar" style="padding:0px 0px 0px 10px !important"><img class="img-circle" style="width:100%;" src="'+you.avatar+'" /></div>' +
'</li>';
}
setTimeout(
function(){
$("ul").append(control).scrollTop($("ul").prop('scrollHeight'));
}, time);
}
function resetChat(){
$("ul").empty();
}
$(".mytext").on("keydown", function(e){
if (e.which == 13){
var text = $(this).val();
if (text !== ""){
insertChat("me", text);
$(this).val('');
}
}
});
$('body > div > div > div:nth-child(2) > span').click(function(){
$(".mytext").trigger({type: 'keydown', which: 13, keyCode: 13});
})
//-- Clear Chat
resetChat();
//-- Print Messages
insertChat("me", "Hello Tom...", 0);
insertChat("you", "Hi, Pablo", 1500);
insertChat("me", "What would you like to talk about today?", 3500);
insertChat("you", "Tell me a joke",7000);
insertChat("me", "Spaceman: Computer! Computer! Do we bring battery?!", 9500);
insertChat("you", "LOL", 12000);
//-- NOTE: No use time on insertChat.

View file

@ -0,0 +1,39 @@
{% block support_item %}
{% if support.inv_lines != [] %}
<div class="col-sm-3 col-sm-offset-4 frame">
<ul></ul>
<div>
<div class="msj-rta macro">
<div class="text text-r" style="background:whitesmoke !important">
<input class="mytext" placeholder="Type a message"/>
</div>
</div>
<span class="glyphicon glyphicon-share-alt"></span>
</div>
</div>
<div class="panel panel-default" style="margin-top: 0px">
<div class="panel-heading" href="#support{{ support.pid }}" aria-expanded="true" aria-controls="support{{ support.pid }}" role="tab" id="support{{ support.pid }}">
<font color="green">started at {{ moment(support.timestamp).format('ll') }}</font></b></a>
</div>
</div> <!-- end of heading -->
<br />
{% for line in support.inv_lines %}
<div align="right">{{ line.topic.owner.email }} {{ moment(line.timestamp).format('lll') }}</div>
{{ line.line }} <br />
{% endfor %}
{% endif %}
<form method="POST" action="{{ url_for('panel.support', topic=support.hashtag) }}">
{{ form.line | safe }}
{% for error in form.line.errors %}
{{ error }}<br />
{% endfor %}
{{ form.csrf_token() }}
{{ form.submit }}
</form>
{% endblock %}

View file

@ -19,18 +19,19 @@ $('a[data-toggle="tooltip"]').tooltip({
<div class="row">
{% if inv_topics != [] %}
{% for support in inv_topics %}
<div class="col-md-12">
<div class="panel panel-info" id="deployments">
<div class="panel-heading">Support</div>
<div class="panel-heading">{{ support.hashtag }}</div>
<div class="panel-body"><p>
<div class="panel-group" id="<s" role="tablist" aria-multiselectable="true">
{% for support in inv_topics %}
<div class="panel-group" id="support{{ support.pid }}" role="tablist" aria-multiselectable="true">
{% include "panel/support_item.html" %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endif %}
<div class="row">

View file

@ -52,7 +52,7 @@
<li><a href="{{ url_for('uinvoice.transactions') }}"><span class="glyphicon glyphicon-list-alt"></span> Transactions</a></li>
<li role="separator" class="divider"></li>
<li><a href="{{ url_for('settings.profile') }}"><span class="glyphicon glyphicon-user"></span> Profile</a></li>
<li><a href="{{ url_for('main.chat') }}" target="_blank"><span class="glyphicon glyphicon-question-sign"></span> Live Chat</a></li>
<li><a href="{{ url_for('livesupport.support_list') }}"><span class="glyphicon glyphicon-question-sign"></span> Support</a></li>
<li><a href="{{ url_for('auth.logout') }}"><span class="glyphicon glyphicon-off"></span> Logout</a></li>
</ul>
</li>

View file

@ -1,24 +0,0 @@
{% block support_item %}
<div class="panel panel-danger" style="margin-top: 2px">
<div class="panel-heading" href="#support{{ support.pid }}" aria-expanded="true" aria-controls="support{{ support.pid }}" role="tab" id="support{{ support.pid }}">
<font color="green">{{ support.hashtag }} started at {{ support.timestamp }}</font></b></a>
i </div>
</div> <!-- end of heading -->
<br />
{% for line in inv_lines %}
{{ line.timestamp }}
{{ line.line }} <br />
{% endfor %}
<form method="POST" action="{{ url_for('panel.support', topic=support.hashtag) }}">
{{ form.line | safe }}
{% for error in form.line.errors %}
{{ error }}<br />
{% endfor %}
{{ form.csrf_token() }}
{{ form.submit }}
</form>
{% endblock %}