ingegrate support back to the panel
This commit is contained in:
parent
2c435e6b2b
commit
c230ddcfc8
11 changed files with 102 additions and 136 deletions
|
@ -68,9 +68,6 @@ app.register_blueprint(smanager_blueprint, url_prefix='/smanager')
|
||||||
from .dmanager import dmanager as dmanager_blueprint
|
from .dmanager import dmanager as dmanager_blueprint
|
||||||
app.register_blueprint(dmanager_blueprint, url_prefix='/dmanager')
|
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
|
from .uinvoice import uinvoice as uinvoice_blueprint
|
||||||
app.register_blueprint(uinvoice_blueprint, url_prefix='/uinvoice')
|
app.register_blueprint(uinvoice_blueprint, url_prefix='/uinvoice')
|
||||||
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
from flask import Blueprint
|
|
||||||
livesupport = Blueprint('livesupport', __name__)
|
|
||||||
from . import routes
|
|
|
@ -1,27 +0,0 @@
|
||||||
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')
|
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
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'))
|
|
||||||
|
|
|
@ -88,7 +88,6 @@ class User(db.Model, UserMixin):
|
||||||
currency = db.Column(db.String, default='BGN')
|
currency = db.Column(db.String, default='BGN')
|
||||||
inv_transactions = db.relationship('Transaction', backref='owner', lazy='dynamic')
|
inv_transactions = db.relationship('Transaction', backref='owner', lazy='dynamic')
|
||||||
inv_orders = db.relationship('Order', backref='owner', lazy='dynamic')
|
inv_orders = db.relationship('Order', backref='owner', lazy='dynamic')
|
||||||
inv_topics = db.relationship('SupportTopic', backref='owner', lazy='dynamic')
|
|
||||||
|
|
||||||
inv_servers = db.relationship('Server', backref='owner', lazy='dynamic')
|
inv_servers = db.relationship('Server', backref='owner', lazy='dynamic')
|
||||||
inv_deployments = db.relationship('Deployment', backref='owner', lazy='dynamic')
|
inv_deployments = db.relationship('Deployment', backref='owner', lazy='dynamic')
|
||||||
|
@ -99,6 +98,7 @@ class User(db.Model, UserMixin):
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(User, self).__init__(**kwargs)
|
super(User, self).__init__(**kwargs)
|
||||||
|
|
||||||
if self.role is None:
|
if self.role is None:
|
||||||
if self.email == current_app.config['ADMIN_EMAIL']:
|
if self.email == current_app.config['ADMIN_EMAIL']:
|
||||||
#if email match config admin name create admin user
|
#if email match config admin name create admin user
|
||||||
|
@ -110,6 +110,7 @@ class User(db.Model, UserMixin):
|
||||||
if self.avatar_hash is None and self.email is not None:
|
if self.avatar_hash is None and self.email is not None:
|
||||||
self.avatar_hash = hashlib.md5(self.email.encode('utf-8')).hexdigest()
|
self.avatar_hash = hashlib.md5(self.email.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
if self.otp_secret is None:
|
if self.otp_secret is None:
|
||||||
# generate a random secret
|
# generate a random secret
|
||||||
self.otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8')
|
self.otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8')
|
||||||
|
@ -283,6 +284,7 @@ class Deployment(db.Model):
|
||||||
pid = db.Column(db.Integer, primary_key=True)
|
pid = db.Column(db.Integer, primary_key=True)
|
||||||
user_id = db.Column(db.ForeignKey('users.pid')) #FK
|
user_id = db.Column(db.ForeignKey('users.pid')) #FK
|
||||||
server_id = db.Column(db.ForeignKey('servers.pid')) #FK
|
server_id = db.Column(db.ForeignKey('servers.pid')) #FK
|
||||||
|
topic_id = db.Column(db.ForeignKey('support_topic.pid')) #FK
|
||||||
|
|
||||||
date_created = db.Column(db.DateTime, default=datetime.utcnow)
|
date_created = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
deleted = db.Column(db.Boolean, default=False)
|
deleted = db.Column(db.Boolean, default=False)
|
||||||
|
@ -302,6 +304,16 @@ class Deployment(db.Model):
|
||||||
|
|
||||||
inv_pubvlans = db.relationship('PubVLAN', backref='deploy', lazy='dynamic')
|
inv_pubvlans = db.relationship('PubVLAN', backref='deploy', lazy='dynamic')
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(Deployment, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
if self.topic_id is None:
|
||||||
|
#create new topic
|
||||||
|
new_topic = SupportTopic(hashtag='deploy-' + query['unit_id'])
|
||||||
|
db.session.add(new_topic)
|
||||||
|
db.session.commit()
|
||||||
|
self.topic_id = new_topic.pid
|
||||||
|
|
||||||
class PubVLAN(db.Model):
|
class PubVLAN(db.Model):
|
||||||
__tablename__ = 'pubvlans'
|
__tablename__ = 'pubvlans'
|
||||||
pid = db.Column(db.Integer, primary_key=True)
|
pid = db.Column(db.Integer, primary_key=True)
|
||||||
|
@ -433,14 +445,15 @@ class InvoiceItem(db.Model):
|
||||||
class SupportTopic(db.Model):
|
class SupportTopic(db.Model):
|
||||||
__tablename__ = 'support_topic'
|
__tablename__ = 'support_topic'
|
||||||
pid = db.Column(db.Integer, primary_key=True)
|
pid = db.Column(db.Integer, primary_key=True)
|
||||||
user_id = db.Column(db.ForeignKey('users.pid')) #fk
|
|
||||||
hashtag = db.Column(db.String) #topic
|
hashtag = db.Column(db.String) #topic
|
||||||
timestamp = db.Column(db.DateTime(), default=datetime.utcnow)
|
timestamp = db.Column(db.DateTime(), default=datetime.utcnow)
|
||||||
inv_lines = db.relationship('SupportLine', backref='topic', lazy='dynamic')
|
inv_lines = db.relationship('SupportLine', backref='topic', lazy='dynamic')
|
||||||
|
inv_deployments = db.relationship('Deployment', backref='topic', lazy='dynamic')
|
||||||
|
|
||||||
class SupportLine(db.Model):
|
class SupportLine(db.Model):
|
||||||
__tablename__ = 'support_line'
|
__tablename__ = 'support_line'
|
||||||
pid = db.Column(db.Integer, primary_key=True)
|
pid = db.Column(db.Integer, primary_key=True)
|
||||||
|
user_id = db.Column(db.ForeignKey('users.pid')) #FK
|
||||||
topic_id = db.Column(db.ForeignKey('support_topic.pid')) #FK
|
topic_id = db.Column(db.ForeignKey('support_topic.pid')) #FK
|
||||||
line = db.Column(db.Unicode)
|
line = db.Column(db.Unicode)
|
||||||
timestamp = db.Column(db.DateTime(), default=datetime.utcnow)
|
timestamp = db.Column(db.DateTime(), default=datetime.utcnow)
|
||||||
|
|
|
@ -21,4 +21,7 @@ class OrderForm(FlaskForm):
|
||||||
|
|
||||||
submit = SubmitField('DEPLOY')
|
submit = SubmitField('DEPLOY')
|
||||||
|
|
||||||
|
class MessageForm(FlaskForm):
|
||||||
|
line = PageDownField('Enter your message...', validators=[validators.DataRequired()])
|
||||||
|
submit = SubmitField('Submit')
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ from flask_login import login_required, login_user, logout_user, current_user
|
||||||
from flask_sqlalchemy import get_debug_queries
|
from flask_sqlalchemy import get_debug_queries
|
||||||
|
|
||||||
from . import panel
|
from . import panel
|
||||||
from .forms import OrderForm
|
from .forms import OrderForm, MessageForm
|
||||||
from .. import db
|
from .. import db
|
||||||
from ..email import send_email
|
from ..email import send_email
|
||||||
from ..models import User, Permission, Recipe, Order, Server, Deployment, Service, Region, Address, Domain, contact_proxmaster
|
from ..models import User, Permission, Recipe, Order, Server, Deployment, Service, Region, Address, Domain, SupportTopic, SupportLine, contact_proxmaster
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
from datetime import date, time, datetime
|
from datetime import date, time, datetime
|
||||||
|
@ -99,3 +99,66 @@ def dashboard(user_pid):
|
||||||
continue
|
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)
|
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("/list", 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('panel/support_list.html', form=form, inv_topics=alltopics)
|
||||||
|
|
||||||
|
@panel.route("/topic/<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('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.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('panel.support_list'))
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
<li><a href="{{ url_for('uinvoice.transactions') }}"><span class="glyphicon glyphicon-list-alt"></span> Transactions</a></li>
|
<li><a href="{{ url_for('uinvoice.transactions') }}"><span class="glyphicon glyphicon-list-alt"></span> Transactions</a></li>
|
||||||
<li role="separator" class="divider"></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('settings.profile') }}"><span class="glyphicon glyphicon-user"></span> Profile</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('panel.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>
|
<li><a href="{{ url_for('auth.logout') }}"><span class="glyphicon glyphicon-off"></span> Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,16 +1,5 @@
|
||||||
{% block support_item %}
|
{% block support_item %}
|
||||||
{% if support.inv_lines != [] %}
|
{% if support != None %}
|
||||||
<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 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 }}">
|
<div class="panel-heading" href="#support{{ support.pid }}" aria-expanded="true" aria-controls="support{{ support.pid }}" role="tab" id="support{{ support.pid }}">
|
||||||
|
@ -25,7 +14,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form method="POST" action="{{ url_for('livesupport.support', topic=support.hashtag) }}">
|
<form method="POST" action="{{ url_for('panel.support', topic=support.hashtag) }}">
|
||||||
{{ form.line | safe }}
|
{{ form.line | safe }}
|
||||||
{% for error in form.line.errors %}
|
{% for error in form.line.errors %}
|
||||||
{{ error }}<br />
|
{{ error }}<br />
|
|
@ -25,7 +25,7 @@ $('a[data-toggle="tooltip"]').tooltip({
|
||||||
<div class="panel-heading">{{ support.hashtag }}</div>
|
<div class="panel-heading">{{ support.hashtag }}</div>
|
||||||
<div class="panel-body"><p>
|
<div class="panel-body"><p>
|
||||||
<div class="panel-group" id="support{{ support.pid }}" role="tablist" aria-multiselectable="true">
|
<div class="panel-group" id="support{{ support.pid }}" role="tablist" aria-multiselectable="true">
|
||||||
{% include "livesupport/support_item.html" %}
|
{% include "panel/support_item.html" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -22,6 +22,7 @@
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li class="active"><a data-toggle="tab" href="#nfo{{ deploy.machine_id }}">Information</a></li>
|
<li class="active"><a data-toggle="tab" href="#nfo{{ deploy.machine_id }}">Information</a></li>
|
||||||
<li><a data-toggle="tab" href="#controls{{ deploy.machine_id }}">Control</a></li>
|
<li><a data-toggle="tab" href="#controls{{ deploy.machine_id }}">Control</a></li>
|
||||||
|
<li><a data-toggle="tab" href="#support{{ deploy.machine_id }}">Support</a></li>
|
||||||
<li><a data-toggle="tab" href="#graphs{{ deploy.machine_id }}">Monitoring</a></li>
|
<li><a data-toggle="tab" href="#graphs{{ deploy.machine_id }}">Monitoring</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -92,7 +93,20 @@
|
||||||
</div>
|
</div>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="support{{ deploy.machine_id }}" class="tab-pane fade">
|
||||||
|
<p>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="panel panel-info" id="supportpanel{{ deploy.machine_id }}">
|
||||||
|
<div class="panel-heading"></div>
|
||||||
|
<div class="panel-body"><p>
|
||||||
|
{% with support=deploy.discussion %}
|
||||||
|
{% include "panel/support_item.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue