From a43d2104ef44aca44b849a90977428e83f0e79d1 Mon Sep 17 00:00:00 2001 From: deflax Date: Thu, 11 Jan 2018 15:19:59 +0200 Subject: [PATCH] order phase 2 --- app/__init__.py | 3 + app/admin/routes.py | 2 +- app/auth/routes.py | 4 +- app/main/routes.py | 63 +---------- app/models.py | 5 +- app/panel/__init__.py | 3 + app/panel/forms.py | 14 +++ app/panel/routes.py | 87 +++++++++++++++ app/smanager/routes.py | 2 +- app/static/css/range.css | 86 +++++++++++++++ app/static/images/server.png | Bin 0 -> 21830 bytes app/templates/admin/list_servers.html | 10 +- app/templates/base.html | 1 + app/templates/main/footer_index.html | 1 - app/templates/main/index.html | 12 ++- app/templates/nav.html | 4 +- app/templates/{main => panel}/dashboard.html | 6 +- app/templates/panel/order.html | 59 +++++++++++ app/templates/{main => panel}/vdc_pool.html | 0 app/templates/panel/vdc_pool2.html | 105 +++++++++++++++++++ app/templates/panel/vdc_pool3.html | 102 ++++++++++++++++++ app/vmanager/routes.py | 16 +-- 22 files changed, 500 insertions(+), 85 deletions(-) create mode 100644 app/panel/__init__.py create mode 100644 app/panel/forms.py create mode 100644 app/panel/routes.py create mode 100644 app/static/css/range.css create mode 100644 app/static/images/server.png rename app/templates/{main => panel}/dashboard.html (98%) create mode 100644 app/templates/panel/order.html rename app/templates/{main => panel}/vdc_pool.html (100%) create mode 100644 app/templates/panel/vdc_pool2.html create mode 100644 app/templates/panel/vdc_pool3.html diff --git a/app/__init__.py b/app/__init__.py index 683ee20..6e6e046 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -38,6 +38,9 @@ moment.init_app(app) from .main import main as main_blueprint app.register_blueprint(main_blueprint) +from .panel import panel as panel_blueprint +app.register_blueprint(panel_blueprint, url_prefix='/panel') + from .auth import auth as auth_blueprint app.register_blueprint(auth_blueprint, url_prefix='/auth') diff --git a/app/admin/routes.py b/app/admin/routes.py index 9b2fe8e..fd4622e 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -217,6 +217,6 @@ def dashboard(user_pid=0): status = { unit_id : query['status'] } statuses.update(status) current_app.logger.info('[{}] Enabled deployments: {}, services: {}, domains: {}, bridges: {}, addresses: {}'.format(cuser.email, inv_deployments_list, inv_services_list, inv_domains_list, inv_bridges_list, inv_addresses_list)) - return render_template('main/dashboard.html', rrd=rrd, status=statuses, inv_deployments=inv_deployments, inv_legacy=inv_legacy, inv_services=inv_services, inv_domains=inv_domains, inv_bridges=inv_bridges, inv_addresses=inv_addresses, regions=regions) + return render_template('panel/dashboard.html', rrd=rrd, status=statuses, inv_deployments=inv_deployments, inv_legacy=inv_legacy, inv_services=inv_services, inv_domains=inv_domains, inv_bridges=inv_bridges, inv_addresses=inv_addresses, regions=regions) diff --git a/app/auth/routes.py b/app/auth/routes.py index 8f85ca4..2c40553 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -72,7 +72,7 @@ def login(): send_email(current_app.config['MAIL_USERNAME'], user.email + ' logged in.', 'auth/email/adm_loginnotify', user=user, ipaddr=lastip ) #flash('Last Login: {} from {}'.format(user.last_seen.strftime("%a %d %B %Y %H:%M"), previp)) flash('Last Login: {}'.format(user.last_seen.strftime("%a %d %B %Y %H:%M"))) - return redirect(request.args.get('next') or url_for('main.dashboard')) + return redirect(request.args.get('next') or url_for('panel.dashboard')) else: flash('Invalid username or password.') @@ -104,7 +104,7 @@ def twofactor(): db.session.add(user) db.session.commit() #send_email(current_app.config['MAIL_USERNAME'], user.email + ' logged in.', 'auth/email/adm_loginnotify', user=user, ipaddr=lastip ) - return redirect(request.args.get('next') or url_for('main.dashboard')) + return redirect(request.args.get('next') or url_for('panel.dashboard')) else: flash('Invalid token.') return render_template('auth/2fa.html', page=page, form=form) diff --git a/app/main/routes.py b/app/main/routes.py index b28d3b5..5837fe8 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -19,7 +19,7 @@ def after_request(response): #STATIC PAGES @main.route("/", methods=['GET']) def index(): - allservers = Server.query.all() + allservers = Server.query.filter_by(enabled=True) return render_template('main/index.html', servers=allservers) @main.route("/chat", methods=['GET']) @@ -54,64 +54,3 @@ def market(group_id=0): abort(404) return render_template('main/marketgroup.html', groupname=allgroups[group_id], products=filtered_products) -#DASHBOARD -@main.route("/dashboard", methods=['GET', 'POST']) -@login_required -def dashboard(): - sys_regions = Region.query.all() - - cuser = current_user - inv_bridges = cuser.inv_bridges.order_by(Bridge.bridge_id.asc()).all() - inv_addresses = cuser.inv_addresses.order_by(Address.ip.asc()).all() - inv_routers = cuser.inv_routers.order_by(Router.date_created.asc()).all() - inv_deployments = cuser.inv_deployments.filter_by(deleted=False).order_by(Deployment.date_created.desc()).all() - inv_legacy = cuser.inv_deployments.filter_by(deleted=False).filter_by(bridge_id=None).order_by(Deployment.date_created.desc()).all() - - regions = {} - for region in sys_regions: - regions[region.pid] = region.description - - inv_deploycubeids = [] - for invcls in inv_deployments: - if invcls.user_id == cuser.pid: - inv_deploycubeids.extend([invcls.machine_id]) - for invcls in inv_legacy: - if invcls.user_id == cuser.pid: - inv_deploycubeids.extend([invcls.machine_id]) - - inv_services = cuser.inv_services.filter_by(deleted=False).order_by(Service.date_last_charge.asc()).all() - inv_domains = cuser.inv_domains.filter_by(deleted=False).order_by(Domain.date_created.desc()).all() - - #extract rrd and status from the deployments - rrd = {} - statuses = {} - #current_app.logger.warning(str(inv_deploycubeids)) - for unit_id in inv_deploycubeids: - rrd[unit_id] = {} - data = { 'unit_id': int(unit_id), - 'type': 'kvm' } - try: - query = contact_proxmaster(data, 'vmrrd') - except Exception as e: - current_app.logger.error(e) - flash('Support is notified.'.format(str(unit_id))) - send_email(current_app.config['MAIL_USERNAME'], 'Cube {} is unreachable'.format(unit_id), - 'vmanager/email/adm_unreachable', user=current_user, unit_id=unit_id, error=str(e)) - #current_app.logger.info('debug query:') - #current_app.logger.info(query) - - graphs_list = ['net', 'cpu', 'mem', 'hdd'] - try: - for graph in graphs_list: - raw = query[graph]['image'].encode('raw_unicode_escape') - rrd[unit_id][graph] = base64.b64encode(raw).decode() - status = { unit_id : query['status'] } - statuses.update(status) - except Exception as e: - current_app.logger.error(e) - flash('Support is notified.'.format(str(unit_id))) - send_email(current_app.config['MAIL_USERNAME'], 'Cube {} is unreachable'.format(unit_id), - 'vmanager/email/adm_unreachable', user=current_user, unit_id=unit_id, error=str(e)) - - return render_template('main/dashboard.html', sys_regions=sys_regions, inv_bridges=inv_bridges, inv_deployments=inv_deployments, inv_legacy=inv_legacy, inv_services=inv_services, inv_domains=inv_domains, inv_addresses=inv_addresses, rrd=rrd, status=statuses, regions=regions) - diff --git a/app/models.py b/app/models.py index aaa425e..e862a86 100644 --- a/app/models.py +++ b/app/models.py @@ -249,8 +249,11 @@ class Server(db.Model): user_id = db.Column(db.ForeignKey('users.pid')) #FK region_id = db.Column(db.ForeignKey('regions.pid')) #FK + enabled = db.Column(db.Boolean) name = db.Column(db.String) - description = db.Column(db.String) + cpu = db.Column(db.String) + mem = db.Column(db.String) + hdd = db.Column(db.String) address = db.Column(db.String) class Bridge(db.Model): diff --git a/app/panel/__init__.py b/app/panel/__init__.py new file mode 100644 index 0000000..537a5f1 --- /dev/null +++ b/app/panel/__init__.py @@ -0,0 +1,3 @@ +from flask import Blueprint +panel = Blueprint('panel', __name__) +from . import routes diff --git a/app/panel/forms.py b/app/panel/forms.py new file mode 100644 index 0000000..057c7c3 --- /dev/null +++ b/app/panel/forms.py @@ -0,0 +1,14 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, BooleanField, SubmitField, SelectField, DecimalField +from wtforms import validators, ValidationError +from wtforms.fields.html5 import EmailField, DecimalRangeField + +from .. import db + +class OrderForm(FlaskForm): + cpu = DecimalRangeField('Processor Cores', default=2) + memory = DecimalRangeField('Memory', default=512) + storage = DecimalRangeField('Storage', default=20) + alias = StringField('Machine Alias:', [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('Create') + diff --git a/app/panel/routes.py b/app/panel/routes.py new file mode 100644 index 0000000..1921631 --- /dev/null +++ b/app/panel/routes.py @@ -0,0 +1,87 @@ +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 panel +from .forms import OrderForm +from .. import db +from ..email import send_email +from ..models import User, Permission, Server, Deployment, Service, Region, Address, Bridge, Router, Domain, contact_proxmaster + +import base64 + +@panel.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 + +@panel.route("/order", methods=['GET', 'POST']) +def order(): + form = OrderForm() + if form.validate_on_submit(): + return redirect('main.index') + return render_template('panel/order.html', form=form) + +#DASHBOARD +@panel.route("/dashboard", methods=['GET', 'POST']) +@login_required +def dashboard(): + sys_regions = Region.query.all() + + cuser = current_user + inv_bridges = cuser.inv_bridges.order_by(Bridge.bridge_id.asc()).all() + inv_addresses = cuser.inv_addresses.order_by(Address.ip.asc()).all() + inv_routers = cuser.inv_routers.order_by(Router.date_created.asc()).all() + inv_deployments = cuser.inv_deployments.filter_by(deleted=False).order_by(Deployment.date_created.desc()).all() + inv_legacy = cuser.inv_deployments.filter_by(deleted=False).filter_by(bridge_id=None).order_by(Deployment.date_created.desc()).all() + + regions = {} + for region in sys_regions: + regions[region.pid] = region.description + + inv_deploycubeids = [] + for invcls in inv_deployments: + if invcls.user_id == cuser.pid: + inv_deploycubeids.extend([invcls.machine_id]) + for invcls in inv_legacy: + if invcls.user_id == cuser.pid: + inv_deploycubeids.extend([invcls.machine_id]) + + inv_services = cuser.inv_services.filter_by(deleted=False).order_by(Service.date_last_charge.asc()).all() + inv_domains = cuser.inv_domains.filter_by(deleted=False).order_by(Domain.date_created.desc()).all() + + #extract rrd and status from the deployments + rrd = {} + statuses = {} + #current_app.logger.warning(str(inv_deploycubeids)) + for unit_id in inv_deploycubeids: + rrd[unit_id] = {} + data = { 'unit_id': int(unit_id), + 'type': 'kvm' } + try: + query = contact_proxmaster(data, 'vmrrd') + except Exception as e: + current_app.logger.error(e) + flash('Support is notified.'.format(str(unit_id))) + send_email(current_app.config['MAIL_USERNAME'], 'Cube {} is unreachable'.format(unit_id), + 'vmanager/email/adm_unreachable', user=current_user, unit_id=unit_id, error=str(e)) + #current_app.logger.info('debug query:') + #current_app.logger.info(query) + + graphs_list = ['net', 'cpu', 'mem', 'hdd'] + try: + for graph in graphs_list: + raw = query[graph]['image'].encode('raw_unicode_escape') + rrd[unit_id][graph] = base64.b64encode(raw).decode() + status = { unit_id : query['status'] } + statuses.update(status) + except Exception as e: + current_app.logger.error(e) + flash('Support is notified.'.format(str(unit_id))) + send_email(current_app.config['MAIL_USERNAME'], 'Cube {} is unreachable'.format(unit_id), + 'vmanager/email/adm_unreachable', user=current_user, unit_id=unit_id, error=str(e)) + + return render_template('panel/dashboard.html', sys_regions=sys_regions, inv_bridges=inv_bridges, inv_deployments=inv_deployments, inv_legacy=inv_legacy, inv_services=inv_services, inv_domains=inv_domains, inv_addresses=inv_addresses, rrd=rrd, status=statuses, regions=regions) + diff --git a/app/smanager/routes.py b/app/smanager/routes.py index b90916c..119ab77 100644 --- a/app/smanager/routes.py +++ b/app/smanager/routes.py @@ -77,6 +77,6 @@ def activate(itemid=0): if owner.is_administrator: return redirect(url_for('admin.list_services')) else: - return redirect(url_for('main.dashboard')) + return redirect(url_for('panel.dashboard')) return render_template('smanager/activate.html', form=form, service=service, ppm=ppm, total=(ppm * service.period), currency=owner.currency) diff --git a/app/static/css/range.css b/app/static/css/range.css new file mode 100644 index 0000000..de5b9eb --- /dev/null +++ b/app/static/css/range.css @@ -0,0 +1,86 @@ +input[type=range] { + -webkit-appearance: none; + width: 100%; + margin: 13.8px 0; +} +input[type=range]:focus { + outline: none; +} +input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 8.4px; + cursor: pointer; + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; + background: #3071a9; + border-radius: 1.3px; + border: 0.2px solid #010101; +} +input[type=range]::-webkit-slider-thumb { + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; + border: 1px solid #000000; + height: 36px; + width: 16px; + border-radius: 3px; + background: #f8ffff; + cursor: pointer; + -webkit-appearance: none; + margin-top: -14px; +} +input[type=range]:focus::-webkit-slider-runnable-track { + background: #367ebd; +} +input[type=range]::-moz-range-track { + width: 100%; + height: 8.4px; + cursor: pointer; + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; + background: #3071a9; + border-radius: 1.3px; + border: 0.2px solid #010101; +} +input[type=range]::-moz-range-thumb { + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; + border: 1px solid #000000; + height: 36px; + width: 16px; + border-radius: 3px; + background: #f8ffff; + cursor: pointer; +} +input[type=range]::-ms-track { + width: 100%; + height: 8.4px; + cursor: pointer; + background: transparent; + border-color: transparent; + color: transparent; +} +input[type=range]::-ms-fill-lower { + background: #2a6495; + border: 0.2px solid #010101; + border-radius: 2.6px; + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; +} +input[type=range]::-ms-fill-upper { + background: #3071a9; + border: 0.2px solid #010101; + border-radius: 2.6px; + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; +} +input[type=range]::-ms-thumb { + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; + border: 1px solid #000000; + height: 36px; + width: 16px; + border-radius: 3px; + background: #f8ffff; + cursor: pointer; + height: 8.4px; +} +input[type=range]:focus::-ms-fill-lower { + background: #3071a9; +} +input[type=range]:focus::-ms-fill-upper { + background: #367ebd; +} + diff --git a/app/static/images/server.png b/app/static/images/server.png new file mode 100644 index 0000000000000000000000000000000000000000..f6870237a308dcc83c015b6a16fabb44bac10979 GIT binary patch literal 21830 zcmYg&2Rzm7`@f0^AqhzmLb5j*8OO-T&fY6~Q&x^Ljuk?7k-fKUSs`SvWbd8q^}o*d z`}_S~|L5`a^ujskbMDW5U)OtGCs;{Q`X(*~E(Qk1O<5U96$}haYxuhf&UN^cnI~)> z{&(G2PFfP<68-Pz`mAX9&J70{EoXSdDEhxE;*5zN@J(zNSp@|4+?5-6_sOy*93Ep} zP-DnSim7=_Y}Ajt-&=isd0c>Xs)+IXv5Kyw$`EVCuu5L-@E_t1H-^n_I#!-j93jCHq6MEaM#{eR#q03FrPO1QR5&j$-mFClai6GiW6Zf z$C2Y-4-F0d^&Wn}Kl`H;UzucB)Zf$7)7M8OBF5+wN6LJ4A}Z}nT3A9JT8s3M3b3rga}h?=t_jt8>z(SB)+@J;6K0aqbYnf! zy%F|E$!ulwyzeS_-y?-#HnUxcg5>{RuAdzT`PM^-UYR>%0`UPQ+iMqGLN+a!qo!U9Ir%xkJQ^;Ae|>UjA9*L;qfEVKhX17jqrcB_0@ zr|3nd#+myH&sEaU5U$DWMt8x~;aIF)!0)<7qMw;)#~I%`F+o8bjA^PR@^9W;i^VIDtIK*m03^$M&vBdC5uMWYisXee2}3t?`h97M{;D5_X6$?YBJA!`BE+pUM&vyIridIJF*`-r41YMQ;Y~};lBMx7i?Kg{ zG7YN5lu59v?55zc1C?PB5iQGm{cW!i+kt%3Ubm&vl_w95c1^yjJg(vIc*oq$@4iL3 z;r3JyAN`JEER2kEBXjr4_I4mO4)%!`Q_O&5 zMo!M-Pd(=N1O(qV-TaNnWuoBr65(Gjsj3P*K7Q>{ukRs5MQl?Fcl~xbJp;p+(RB`X zc8rLQYq4ezWr;7Cxw9qS<_zBG=U`nj z^f|bU5hWlV)@CeF9&|&Qgh%w^WPhFCc?thwHTB|^K}YiaRSrJBz}Q$)^gFQOmB<== z)rf$nX<>(Z@AnJ?j)Qrr+J4E%z(P_lFz4RJ_RoEM>%aTH_0|V@`dbklkciOz{E-fvs13x7^47L(H430N*w^dsp3n?CXfEYXZ zyY9uS-K9TFIX@EYmsUoxUiqI>MahbXhldl`;_?t>Sfa_!&Hb+{sfZe9YSxMR7|l*qHR@)x{?H(r9zx&2nd3C{-^dh{!qGK@prB* zuXSsS)y>_V&}=$Tt8Q$0@4mM8k2v8iIGFYN5pXSIn{MP`?UYxVXC0^`WuOc!1<|ct zhDp)FuH_GZ@`N&n$1$pt4z@8A5~v$+KAR00nHsYD8A_RHBV{ZRp00w)2L>E;vPe2v z?XFub?Cgm>6Yep@;sdMPobmYZ$f(Y*ZlU9dSh>qF<5;8|#);lj!P(bM-%^ zP~em+v1jM>JV8^kj`BZ6_`hY8@|JYJ`l|X<^cBOKHRZgKHFyJ|Ox;~d{}DD}L1q}d z>c1c+#_S5sq0KfT%qy@|N1qWNE{p9MA92yM@oX*j6cxHu4l(|nmQH6C(M0P%xW&( zXiyyw(*6Gn`&z=r3S|X_7aSYjO?5pJG;2KEs^Z1x7}`>j!5LQ|Gw%X z9&3hq^$Hy8+2g%sC=Nn>aW>0^jJO^tfpoHl7Ve24(ICJZgnhi%(h(ad&fP*b=j!m zii-D7UKW1-_U&##K|xT*Je2$|N;q&>pFVxc9xp%hTK|c0J|iwvJzrl@ZEfutr*AWR z$K|}UaOk0My{Q*rs9}hdXp-5yXti%VWa4#kCLkpx zHM9Swe`F*xSPfo-KKcf15skcxjf#(EcZK?_R~6UX<)ScCmLGPPlA3+h@31k@jWts! zrunk82tB9WyzEO(PO#7H^^~OLm#?x>ba^k0i{SFH`QK6o0J@n&2MXjD&&tqkhhuxf^QYOu=bO$e5P&`nd z+E)j&R>^$c@4TspFPDcn7)=${qboG%BgYUR7svVqI<$Tf8SM4z{$EU)#A#dDLt}ZN z0Te^m8y+4`V#H2!8?xSAI{!l`)Cc!D4LX6fwKcrBcBG!Vp)%q+&jj7&>5!uI@%LP& z5k?dekV)foQO0F=w^)u2$2GPG;z@y0hOp9~%`)u*S8+LL@ zZi?Y|aT^~CHe^0~2!%hRMjuBP!2)l^^FpUvtCSrNq3Eygf8Rc&B@KB+6Gvf0+g&=M z{G?Vj=1pS`R&Y+!PPfD>rT?plfCYZ`^cdOMg|oIlX?H}B)m>hkLdhYgq|DD--*3i# zKuv_Rv0nW;1-d`1i>85rG&AX7n&eP8Vi6Y`b~3b38mN$xdD)Gr3D*~3GrnO?W2B)Z zu>KxJCySMw87f5!7zXNX#gC7soWX|dI5TCe8smpJ-KE9jHUL|$cb95E{j15G>8%(= z$4?oKEYygXzYaYZ3O%5v4|F$R;|5ELpC&QYe5?s)?nbxlqEUE*&_EPEDz_yVXZQ1Z zdwau57kwqyv;P0;3O^l`=!T2^q6cy{FPUNl9TFXq6$6XBH?6U9mZZf5li~C$4sTnK zUl&X!h?K!>X=(8|*tkm`(79@Rws;2KQ-=CuCjNa`lLD(h8g zjPj4PTCuZ-r?&_K@kHX}>HEjW<7rc%uUAcmWn?@$@@lwR@*<3=Yx{Z|JO0GP1bVBe zF{%I49adCY#FX3@3=a*R*`4ozE(smn42w;f1U6K8uX?}b$AMXEjQ{IhVnl&5Oflc@ z+1Fhu8ug@$;^7Ooy>h-r%Zc~~Fvu?Njo~50gNK}m+tQ`~!^85Fv4lQn8zXm{5 zGq!DJhI(mfiCUJJnKVShhX3>=dn#0jim;}p2Fkt>Z(_)W2w+~=+a;R#&@2EdhH&1( zyn^5IIVUIQ+1MOE0j%$*)YL%zqET(nyb6~(eKyV5p5r8nM?+InVZ+1fpXBKkj(&ve zUvaWFT6Ga*L|ui}bJqcH=wX;7K|WIK3j1t zKo6^(3U6)}J37B8-E0nCdsy;+EMbml_w@W|^@Xptd&hnJ>$_#C=lQJR>IzR)p0eoG zy4_S&RmHjPZ>Xhln`=mrQ>f3x(h>(iL9-zd`k?-crf?htgsBx<0>xF02`gDK>jw4s z{TwwH!Q@!uJGk$mfPQAg{b|m1a&khAG8UsC*%9}6o}mI~p!MK@7kU|afZyP%q`N!c zz^aR%LmEn?r>6%4jas0Z?FdDTjA(3n<_7O@CiZAYE{z)a-sj@tns8O?Q!mhKDP?}+ z={dXWF>`q8EU0#i4OrhQEm;xjvb`f!rtH{Bf% zApl(Q@=|%ysxD5KwIdTzbYBVz6hiWITteF#4C=$Jrf36<%GViDG@(-Iu?Hjsfv|%s zRvxDDe$bToFTxnY8o&Pnhz_dRkrKUQilpyQ4b?ocT2thv!0K>>PNW_}4hPYYePO66 zr=32>woyGdXE|+TR+$!wp{8CQXQs93&1S+iEDk7v0?xAbLevCGH;N9>-sYyQ0^QT_ z8u>8%|J~n`de6jfiV+P@;HLLyBPxQ@f?Nd8$LT&TcV`MGbL+VL+V&iuSEKbRElOuZ zCE$F;6&!yb6Febi3=jIBTGsDiDjiRR&G57P8R42Ph!FYh0gLAS;WDpM9qgt3$#y&5RW2xVrzo`EUeKy`j518KC%T z?^8Dk#%|HGS;AXkQoD<6zIUOHaev35A(V~UNt`~r-j^cW0$Uj>5JsIQ$K=@9GXsMM zKHG;rMGwF8Tpp)h-mR^z_1Np-b6o7YbFzDbCPJRx-}7*5wm)5ZWot|7#S6l>WXd`Z zy#L)26cpU4DDu83m0q&dw|{;(-O<&BTc}^J9T(0ZM#)>sJl`43aC9^uQBqeI%4N}u z;c=@%HpaQ$ zwHPVn-w15Mczch}4h^Ep%E~nL^a>`ODevCx|6Z*?e|!8IlCgQxzA;QXg6b77^(2+o zmUF6vj?TTwes=cHH-1^IYayv|zj>eXN=Zvs2wcSgVEv;sv^$P1Jw5$e zVq)T_q@?^wXSlT^+I08uua{zDDkA|i&do=N_U;m{y$>L=F7@oV)&y_UhWDNgug+9* z_wV17Pz+wF-ICCvgkKG1KC+=iF+0OWT53sV`3vcu2l6Q$?p3!REPu+evpy+D9an@UdYTWRiOg$#v+B8rbKzXn5{iU{G%$5fIbioU~@AFwP&=XB=kY^ zU%!3R(jocwW}iu(UR6C+CrMw*LplGbVYn4PN+qAZ!>Vk&eAr2h=!YnE5XC9BU6oE5 zHW{n#uaw3+9mjKmXM>%n?MZo78m8;kPIUlPsUonOLg?{2O{07yk(Le=8u`j0VNR5R zjmL?NIVWY89fOy6vjtr|teJ6_4`fi9(euLYTS@dI3q@B!_+#MxM9VGFOu;Fh^(dVL~qTIfQ%gb~82+@hWqlTO@F8M8yg3jH_ zii*V`9`v*BzVax^`~&FaXw%*|1ZW6`$I*_F-ty>eN!j_9JrK$|hVxz$Rxo-9S8ZvK zU>$BxOS2yttJ}c_hgQ@M{$m_<Y4d2aB9Xxm}qrT5DIn^Nr8D=vZ?o10=$bQn#4{!l?hTxowu5gSPOU=CaK z=n69_UJn^+%-?}ORBa7`>&8ezvm0w^xi#GwvF&?#TBQ4MGmg}n1R=&0BQBUMMIS9j zAKjnyFxCv(j*B4l`j*H7^Wics^QuV>Zti!3SuX~&=_n|)VmY7&$Hd%`S5%z){vOYi z@GtP|mG$+OrY4Ljc1ekJM%>ugSa#5OUWlHP6g6H5D)ho-92~fz<Q9lR@i&P8LP?FuKz+ zCph=|_dOwimM(%*ezrY+Z4?09LfXoAYMm56P~!mQ0Io0p2V zOJn0@lZln9>m6rjXBqlvL-Oki3JPeMk&+;=vaf|T>Sf=-+Zth_4v?0ifVPf|kir{P z%#wfxa~&I-aO0z`ot?3{`SsnS5b=7rAJE+_t*i#xSB)x1@)sT?E!fGKm&I>fakK%9 z(AkNWscE7+-!8v)adn;D7%#&PPnsSdZ-$}^uv*0yw}26sIQX3g zpQ7@|+<%1o)4Lv&uzaK;W(GOQr+=SID!2amlR{5#YQR369h*qrS&FX}3o<9N`SVhK zBK__sAa1LxujJ+BDJ@f>Pmw=(FiKC3N^9CaG;wkH z_hV}>dW1!+X!ND85e67pBBdz1TQ_=>pMvx=*s2Cf19azF_id^bfm0AZ zggkbh-L}G@CJvstI6obmn0WJOv)KnowYCH?lK*-C+@E?+O?~|lt86pQyCA2`N52pe z_=W6-GY_wqeuJke1Z$zBgd;ycpYl|oyEIgZ@tr2~b+jhsgqYSHmQuTy?!!tN@{k-~ z!|FMh@P3#Spq#pG zZxQd4@~`$nJUkh};vG=L{HC`@&kj{8Wrsz9CG|@T`C8!d!QJy|Y|K4=rv6F^ge|TC z{FY}$qb-K)O@{0w`9yh;~Ffb4er$FzDMo@=` zLP0?lS-doi04Wa$IZfo-X2WL|78bqhfV9oRlq#gwiZ}cwHw*81vO`(V%%rc84Zdwc z=;r43YhWPlJXwaWxUw<;?hwc>P!&CdQj%`y30ORNT7Sa7(QGt2kQZP@`U(xtCZ{h* zL5gzSTqfE_JaPmYPnM;aJa9pZc_l9|4+h4eb2< zK?Y}YWGicHg8h$^TJy~Q^Xk*nMdv`kO5oLj-GtA>_)gI~IF_N&`db>)*cduGmY6ShmNIMW z=x_)L$s3P~WdF|$8nFw%ax-NRc8CH@kEybb*@?)JAvxIN&G)E!J% z8)_Z2j`j6THO_Lt9!~vzo$Nh=FET?X-xy;pREMt6x}{WYrzr zX$r4VT3(KsH5lC6+gn;m?xEbBljKb!GQZT5cy!$78}1>be>hjLcBxgM;o|PTY!lHj z?{RkI*yH{A{d+9fq)9R#RNSoKmOdYW|LSsc`M_W*- zK~smF30GmLRk^SiYBpYa5Pas}R}CoJXFt!NdHax0QaBGzF|0#zQUEf&lrr*FT0|JFS?4PQH3pP~YoS$Dcs3%!`Xob`=# z@Wgla)E4HCnw?azC7D@Rcv`UeBTv_m|FG0CIY*lg9D?m|nHuH8FN4 zE99~$ZuoYCBU(&1wj}m>^PLttbJA?@R@ zQ#ie!)_z}HY+s7N;oe>7ygjygP~-9wurpM%5D^^^5`i-IOx|AKf1_Z-%%{aoj{hjc z<^kCz@4bek&DM`4*a><+DzV3lEW9fVD(-CAcv5#`9W-%pzZ>3n4Fcy`xtIjSFKsYGW@od@?5~6z1pG^P-9AJFuEq&~Y zjF=5SIEO8>{Eky^dIaw`#OqC${~%|xARIbews#juUV-&NT^%vRu;=yXO5AlZx?*sT z;(ON`Q&bL#DMnc*9ljK8q3*Y|;O&?V3&CVy7T^?vqc08m_uQOuZ}rB?O4EBhvhcGB zTy#J|wS2E4!BOprf%udGHPJIh5~d)hg^;_2GIVKEpW5_asu%l<|jn@B769z9vS5xH*tn>(4~V56 z%gOFmCsP$Oc*)5}(`2uVDN)$1;T<>lpSb@uyj>dgriqYRzf=9{M*FHS5DoijKZ z9@Ks?BZP~QN(t;=eb+qSJmPy!Mu|eYmEC=X5DPWypO^%tKbb4#zyw`dfUP?&{k)6m zF9sG@M2GX&Xq41n5YBEXf$)c34n`FD-Mcy6vf7W87kl`oL0xw1LA}@$ihw$e$}0zh zvT~(b@co_iQM-=m#{ChCgU=Hdz8sSuF7FOgo!QkkufOk~ecQj;ejxpzIfv1bC;kH` zIW>lZK@Da^M>N-i?0?K%mDtSPXi^Mxcn}p>Gqshbtg-Mwts_S@Yv+&W_ADTGF^H)A z_#vZkubT4EYX$li0RG%@WcXUNhI5$RRLy(1O}B@crfj+Awi}Py#62C9YCqHq_lf@E z<7u|6YFTkIvar3Wh!8954=UG>C{Js0+@cif=@E$Mk2wQ3)@jKKDqb6!+g4R!Bb2$D zFZdZ3u}hip;=0v`?6NP~#B6W@@jsIjqUtO0_j>#&=cCeX984goXjCWl8eAzrdU?az zAH>bLDZma_$x-vmWx)||kr%R(g;Nc0L>Lggk&8=t=B*dQBEQ1dgoo>j2G&8aa6NuH zX;WGHIxVnG^sKv)#70$O@9!#bIhABoKPa=s1uEyC=NsJD!vGhfdkdOJP1U*^m5qlF z%?iCb_&ng*wWyJ53dWTntG>{!jB7f#u@*Vi`A^&YB?s?9T5KcJ;yp_FItUG>j~dq2o2f=Q=@O@Wh>6WQF{oRD>cq&C1wL&iJja%iV`qj4`p=HXob#c3 zQFP4^tLO+*ANm6Nm&e)4E$~*Ha3yM|?C|mNNecV`H*3v=BGQ$CrY)3jtZ!}>wdwPCV$@~t^+Us5c_N(A?#^?$7}0ID+2yk#_DDyq$TSP@ zeiauz&Q&TQkGFt$!Tekj>Xw$00zJ4atyjlK4<$+C2gnQDCJ;D^Mgf^Oj1uA{K2+F^S_y-3Yv9+vJ5cBAY|d=m&>{MK+~r; z76c#6s{+^PNpKhW-J2}1EU?6W-2Ga?NHzSU{bl_zp{IK3$7bdwqL|ZDeo@s3W8DfV zE*&)i{P()9?@~m4!yzI9O_(Fgxg9zm$n?NaAbV%XP0=4?@zsy)2`QSma14M+hZeQx z!}z*(_VM@{9XkPEk2fy3-xZ6t{0FkK@lRZh#C1 z?r}T#8W8wM927{eszST--QC?FRDp2}IxG5a^^W0P_pkOmv{URKQDXc$?(dY>2=Wcu zErEpuq>3jS1q}iP!L_s52Nw^={h0&*R&O-R9^CptHFQA*!KP-2h`M1+0`_ag3 zaB%RbO!TxYx5FcS@){5E41a4vw1{-J`<275Q&m9U$99jwkaRfyYi8>yCx`dP=hPPN zFEt6S-|}%{_N|96;zer zOc`E1WPL!`D_C@+kAD1P(~LaR^rF1k@W*T40=qh?Zdrz)dABZldiwIYA|_A>unOY# zVpicTmRp+b!FqsiTv|Xp?R)7BXxno39cXG0sGFak=l48d1~0SoE^ScPwa-hnNhv8( zAQviH>qT^^aw{--Qig|x-PtURmZ4+k;E0@`S=-%@{w#q!v z+UFcL+UDkHAPM8t-q^kP9a?a=&lWi9h;?!3B50e<-fVcOb2&Xi1lcnCLpBcR)ThM6 zmZKwn)*RQ%++u8o_-M_A1k2)w2J`0ykQDm%O(2U7*<=3X)C(JI>fkgjS}@yfZ14?^ zzLP!oIRJ`0^IC)9n?40vagf!ngt zTu90yZ;@wD!gJ0@SP;tpO{l0tjOk7gr{A;BHZ^~?w(Q2aS3sahKVrQpV!@pFM zWBPmyB2K8ao6Ox?)(s!d1%{WEBJ!d{{;OQTy+i1=SXF4qzgT#!|J04wS6o%$VT#p+ zxW;4{^`&ryrL00PxtN;0S3J}En>;VMk`?D+(_wx2hNMQzvl9o;v6`WV%g$#Fu7cf+ zD$ED@6PcC)=fSDes<_BI^!6|nqzQi4y0b}VD{r=rjeV+oqu&YaN2fA7dvqNE1l)@7 zK-DFwRR@_ZoGT11A9@>`CRQBXjTb3G{q#1&F1}2MDTRfsVDH?qnFhFAB}{$!mYSf0 z3Zn^BXWTS%Q?2%eg|DQ3CYEU3O-7_ulJctfj4C_7x7y+&^ek22qbK8Z652towKCnw*3*D@Qmt*-h> zfnK%0Tq*|ob}3$q!O1{eF@(IK3PPg=M;x8_Fy_#0^hx&dMDmB0=r5kWdGiLN&XP%~ zuzPc3BR})5>1cIlR@CLrh>kr#AR}vQyi#U~STi_!Mj)M5*-wjnh)5q?B^Z*;9k9@h zEqK$ITl9TtyN;qiGvPy;RojCKimEryDu}Idkc=*41XlqCxNRnAXy)mrygpb5Xb+4V z(cuCZ7&v6tRu-iEe2?RNgQ4PrqiqDzCWPXS21U&$K6A}YPx!1Pmv_D7{P=Hf}UZX=o+uqxr*durT(wgQb%c%{>j z-lGwEp8)&M>*f3T1qBZ&3H;5pfZctD=9J5^w*O|A%y*Xz-7{9q4G?_Jl!1PJSCRx> znHY=Oc|HV9BfX4K?3>|W(zx{-9Wd6znI}gU1QT2Z8+|+ZzvaLMDTp+#N6%r zgcS1pyb#O{a1U*~htH_IYU7`zf{=LK`2qS_1_v^K+wnodDp&h;rFOwlJ70s?#T7Bi zGGB~38HCmL#?RolISUW6C$8-n8y!Kc1FGlSzGeS3Mu%^@=%-X47nDlBWpH^iYVDQ_ zf?L0(S{dOmX09n5`HI7#UZo5{xg43Ouk{Se8!-I<*(9yNN2DR@6(=9%>5aH4x|i%0 zpPI*@HaE6?V9AKsOuQi54PX?nl8=v%tl(c^)RZ=v$HNn~NsU8xYKu@y536DtjaTpB zJ>U}(l2u?^t(kEARfV|`9~?~2P@oJ1i~QvX z$&iBqP(Q4{k{U&$iK?ED9soV2U17)fhf2cIbHYE%PXIsw!hE+iyN(F~Qa2kG=cdg- zz>g{-YomcqcHrNqq&zEb3W!06_$bpyH$!erjEphBsA(Nc|Eeq+!(ISHmxDLoUm&d+_B?29y^OoN2U`({&Pdm}u(=$7iqgw5;I~OTn zg1P(rQ=r7}D$t@&{yjFgus{b4D4P!;K>*VN-~)nyq+NpsCC=jBU{e8SNsaCh5fzPq zQ6!YWY6piv9(_zAwZ3|Zxswwyh>>U+9Z*KaXm2V^Z;a?aJBPq9HZZPDpfm||3?sn|&rb2nB+4W#r3SAPmDLG17L>vX9( zkNQ8zZ2-Xu^s3Y&AlQ_Rhf2~Y|B~u9E_p_TxheP4yo~pC_eEXH>|0phiC%X3{-g>> z>>`vQ4hD?ot{js83%DHEgj6$osh4%UvFeC3Af4J+xx`GwEdG8g0k2oXLIXvD* zCW8RJd6@u1Vvw2Em<=z>UXT%a_0$&9d1!qfOnESSMoA+>^a3HfDI4X)9gD=(&s|GS z-D*Wkn1nC|04|T8QZ(CGc6;Bf0|{j)BMMUekVc100Jc>af~d70Jw*k{A(y%I%FgbN z?ZdcD{F3r=7s(5Y#m>7ocrcaC@fXioP&D8i9GVf*M9PeR%mTB!V7AF<@*2|8!!M&f znll4A0~rdH0V3P&UsES4= zCK5XFRuJofOXQhNk1|roPB#36nl0m^Kx2T)kYzOJKJPI4kt$c^vSY>`(&u2v3FZqQ) zIgznQNKGBJ`Xi9so1`%Xn0(H-#}Nf~Jwgv0EXd_J@as~Sf9hFE;_g#{xff8DlDHX$ zNpm7QH1c?sxomQ-m5$p0pT>4eLCXnWq-;lJ;jInDab+#N>MBKN2#g^{q$Bo{1gkGq zv}0u@dnrlXO0J{SrmfPaBGG?fA(Yx`wb(3T8}PUBt5>qlm2mao2BC9x6D~0uVinst zZ403rSH#qgH-z2_d;DFv0X4^&)C4>y^e@?!GPF^l%WMUM=Y}GveojuDSps5=B;S*t zx^p|u(E+80mIYV;b#OK97VoezH-+;?xb~q`)Yr7T(vOCTVd@AtyA0i573{BIlik9> z#K2&U3wcL@3jeo06z%{N$r44kL*uPtyKY-GiKh`! zR-#=X&?ZedC^}Xo%#U(WKxQus1X~dT<%6On7oHAHM*&lr%jtRWyN-SvlFfs^4 zAD$~$V`>qizA)MW5y3^LJ9@3GI#yFw70Bh+pg#nq;e@mSgsLv`hlwZ{@_+yVjDTdN zKrD8Dw)p1Fo3Fx$(M<2~#7hLqig?lJ{Mqpytgx(~A*>QimZ|iwcA9cR_@o1d79pqz z&E2U+kusJmHlnvlOg_?Is_rT~{>7}pMaG5>gco@_I+yoiyo;c{_K=OBi{R1~BKz?1%F51UbP|KKw*h{_Yr-_S_w9HvFlf{fn-sH8B*synQi zl9B>W1P$29XtC_%->O6vwy8u=z-_JE`mDTSY~Z#4i9YiF=?;s7p$8az3ZF;gQ^>zF z2;Ab&-G%^lTP~^l^73*8R@%jd4~%~;DHSGbtI|^vv}_-?7?(7ea&B4|!k7gtJbb|f zPs=Q0ysc6RTS~trX5Sv5RdmMbKk_IDBadG8-1$#14Qy>{oEd%s-o&s8L*iE1zs*eK z1_4IDK!gJ+F{w}?Mr~nZw6>-Os2DnO2Dyfm<^JElLm|x$V`QbS%&!rY}1%!HX0h%=~9 z`r8;~0^ql=-zSB$RWUaY?bhGlU)wg(>GT+eCa)@!%(~UWESk@8w?P^VOBtL_j1mk} zQ6l6Vk=cB(F(w2Be~pf2h2=ayc*Y`LxQ6+k=Q1}3Z2=_7S*LH1_|Q~(kU8daaXx)1 zoOS2m@~rQ21|0^D7CnxZc)bK>3!IsUR1vfkVNl8!R%qKARFz3!6N?d*lQ_ao1|R!? z{MhPdbj$3oi8lUVxWgiO$I!r%68Sg(=eJ*-JR zRx$t|&wUhG$TLAnnc4L?@b-A%wQ~g)5uJIlwYIZsKR-PLAvy2vf)xlWmbx)g0>qwh z*LE}_HELMn@+=uXsT?#dI&I3?s2>j(`C!3F0{lbi2|AQtLxYIzqX@J_;8&l`F6EKZ zv6;vha&mL=M-?_4*U>_~$-`Hn;o(R#EjVdQWBvdSA zE(rU!J?316B)eQpyQ^8LFh@5B&|!iX^l8u`h2H$bDXKr1_>xpMQK>U54QVNeu(Yrz zh2AGz~lSQ;$kt{ycL-vf1a7$<{4MM!wUsD|u1PodAPzCCT$xY70DKN3We=WtJn zgC&~}wzjsSl%0h7(jmZVSo@!OO-LyfF*T~PXeO}?We1di<_b{lbVniNsfpE179=$j z#2rvl-(6i?KI&pwXpsW0#hM&k<%SRt7&|Xr&(XuaEQA+j$8_jyCChfbwx?2Py5((o zqA>9sI|Vqjj~FZ=z8LgmydeOnP2X}}jl{yjf@!!zqHP4|lqUngh=|5Ujg{z#ACCbv z>yBk*q|Y_yybH1e5aueIa$8==U$G<*Lue%!n&|x#GZk8YHSbod3aDoO^ZY>W!{har`=$HkE9mv2Ob>j|IGB0w z0T_%@r91-wkB*`!N;aJQ?lX7`fwyMRi-B~Vg#gV0&KGw-3rYR492&{_*O1JN---EX~#x%EKs1N{b@zkOdn0t6|j~7U=&&4&KJ%Y<54`_yvzz$d?9Sgd*S~=!n zQc5EqvWk#V%}0!sS5?hJOH2p?@u(Zc0sHgjOFID3e2vEnXUo193~;i+(|`nV!(Q_C zM;rIoD)bhM8RN7~l@0dRBbw7w1kTJfw%hY@4BW)#G;QsH}yEcifuWuv$M0VTj?y$iP?sYE+ zz%`t2+Rs~vp2pLYK$K)2LX*6ZEt$G>p&J>xD<>z19u}#=nd}a;&d9Zm(SKUFxo;tT z2f#M%dGC2O<2XT2nMI$IMkf$*h&Dou-eG@D9mLa^Yig^|zlD#Q34Jg?Hy9~?9is)w z_14zbND}Esp1(W@CgsM%^GlwJil*Lv94AAE{uG9?GJkXyg1LLdRD1}0>&DaCMp{oL zBzSV65fR&Y=F%*iV#2mX(zz_{-oJqg!DJ21@Z5m0G#j_uLp#MY=NOp+b-x;N~p z9DI&UC4+Md%J5@toP-Urb5+IP{C1@sTae#wZ*O->0FxK}*$1;wBOr>J9V`s|=_s%9 z1oa#t?-mj>VJVChaiR%{^1BV4s27)9G@(!aq8JqK#d? zGcdh-G4~B3iQfDhY%%mPhfxJX!?!hC^=l{H925RBbSj))S`~Ld>C{%pQ<<9iY3@9H zxZ6GEuks$X9uKSrSP2c_BM6w?EQyk-8~SnAl?B=oQgD>gQ$@@};2BB+d}xIH@MvVV=)rCElYva?J}O4_BkVNBIe;~Y z)KeIijE;`hHX*J!cg^AWGc@Glw5%KmQ*8_|@wf|wWClQ>0-t zbOs_IfB`oC@aA~#aRc-lB+bV*nXEy`@`^gL=*(+8aVyKslu*{^7b!iPyZlc=i{DhM z=+$NKMEeY4_eE__?CD*t>n)`KOKpV0d$#Q8u&!{Gl@XcU<$NnS!lZ9)ooDV9GMxLv z9ESKIi&L!043XC6zki*SO=?opKDSzgFGe%GV29izge76faQBe^Rrq_LJ;%Qa_CGD~ z7uq}-x1p2$4re>kogiECm!%|UUf%N(4g*8Nznh4WQy8V9bn~3Gz;F;EWQ}YxcjtTO zLIIZ`BTHsp*Va&oH8+=9UzW?sLl{J{87+FsXV-U1mI zn14gCJ^GP*Uxl30fpC?>3g|n7{mVd%@<3F(I@eUB#@* zA{(9WtNSyUSCaYy3ske%xkMv8X#DUow3MkYw`yQ!_TRz5RrGLlw>dKV0I(wrsLaf^ z1R|#S*x4Nx;<2HCJeEqN66f|dosN5+v4ft-rU(;{`kM3|wcon2!TiT-Pk~?k+6Nwv zQyn;u3 z-KA)~xmPlGU_0jgN>dZPWkfO9 z5tlsh+&NiCTc+QdZk@u+pS6!Z;1aYc-F9Q5-t!RmZ~hCEaP{GamcpR`JNw(&S)-3i zKe;`BnU)!OdFj@&!hkW9+vUF0QF{K<5;P@&Ee%hheV+`R0gzB(bhW@d&HGVS?Jc0V z=s*Bus9{V7gH5{@S_J8tBFsR`j@LZGuD}Ki{>1WbVjtL3#r5^xG(bvm+l~*r;kKjG zBy()2xsXe3omofz9rTB(#Quio2FWw6^1Hz+e2Yo{loTpZssX8OI)iTuvDnkKm4Bt& zNKKe0^4UwAMjN7lu?jbvAyx(_1GiGtV~2YAPu?9EWqbDG#f$Mn00|^4Y2L&h8ky|d zVwSI7HSe#F$_b=BFf#TVVKAQ8k|t9(kY7mKohzf@RFbsg4;s@ zBQ=>f{=p!#gG2Gk`7>jM-Y2x6-It8E1ZD_Kqq{c9m&zmnkn!5(bKEA|^g$VhK^Jmz zazZo{fMl?zi@9iH$h70~U`?YERUUt>K|G4}Z}&VJ?h z3p53W*qK$(Tw7a88wgWN;$dAQBbU>Ernq@ZG8{23-Sq|8k|mKhh;Q3p{ejhwC4(P? z{QN#dCu6=UzKH#S1Q~0li{r5Ia_>M?6jJHXZ)Vp_ZS;%7ZmnkgYquK{+;}a9w$EH0nC~y;ozR({60^7;3Z2URun2LuA z@Z}l6+%``jervA>jU9ZQH;3`qIOlP6|HlVWVbP`2II zGgWicWUrR!LzD`78^~Y^jn%l_Q3N&8@DVBfmie-rxdWv#z^x%oH;Q!{&P4@M?wC#G zwaX3Yj;H2bYPUhEZ7FHG=Kr;D(%4 zQ6mgCYl=MkAgi{ln{wnx8lLDOnz+0g)Q4bL!II1$g;=cKR{vP}RT}Oj5YzYkq$dYY*A#kz#3B zVQzBr@kaw5l7$3}B4$elSg)(!yvKv_&VuJMqwl;vxCCrwV4Ey@0IV(Fsg_R}c$4Z$ z6_GL70M=$?D!O@G^xIx|Tjcq-Wj|UY5gxv-B|nw5xK-H$P(?$7+-@8^ zS5X{CkUE%TX^n^)NboD~ptGOJGfC*CN`Z}|(#l~cTtml?57xJzx<45L$ss2vr{Tbh zE7m8te1EFj6fgakBeEb7B?2LE6VrdtF1!!FKleBr>dI!dthWtOJP4bpTHrq6h4qzx zD$MZdvCL(VWqP*cWPX@pXxO|mxG~Hrkc9S)#+ZNglXdE~A3Kx+#1lQ*_!tycuas|u zx97XzW}oU%fPS<()J*@?5+f#SXlR_e+Isi3kUuYD*G&T(((vRKK9$Gyhf*10R8f47 zU$5TpJTf>mltFQC@O(;wm#T1w5p2#qJ>$=h^P@LdDydGLFZ1FT0iK`rPv%YZWsk59{RP|@d1csX~|cNBFST-r-*+h7JF zzjJ;+*liGQcE8zU(%CrXNah$nyKWu(j?$?_g?c`uqHTpc34uL$p{3f_1ncI9sE;G3 zVP0~Euf$f*EYfyv#Vcl8URSYAU2QE>s&S0b*&bPsp(%$bUO(zcZPZlN-BLBcf5o!g z9obY_2proX&yVLC^SwykW(tCELzL>|Q#$tZ!;K22S`9CH>kj$|pE%2a9Bca{M!8}- zwN)p4-kMumEm zM*FR&7i4lj9HRTF4pb%Bp`}K_JzqvWTK&&JJc4)I&(Du-yD6|U^^^^fNumutcu0tj zjn#w=4Q@+f?crjryzSlXx|HxHiA#5I28<}Us0eKoba~(YTA%Yx$2>HkZ0@d*h3t%1 z#Jwgc$T)dl2XxoVhzW^nzaf((Q@dmZXVHzNL@(qO7Of8Xs5W#{=rryM=bw?(gT@<2xM+o$sq1!lIrT}*pRaDQG}2B=yet9EzHk& zxn+5(goGKA5VuY30FkaWUuZw-sI9FJz8Y0O<~{U%XN&kgcpdyN+Nw3 zIy-sV%6NCpD4YvZNueHgpQ4QXzm`hq$p&MUI``=Ng$K(#U6Vxf73u%okiIpSQCna`-M|)2cYfS7)#bZP%j+S|R1W*lnnguv z7XHrOjTXOP!~#Fd8W_$sHQPMp&RV9O3lWGsrrT0QIiHLE_~Uk~Ij48Z(tl3)x_-1q zjSwXoYP!C%*@yD7?#H!D~3#Vm6SuHGLcgH5`5+SKnkMMpK)4?7s5v z8}>XSt$~z_at&G04pxlW1x)T(yfS{+!c<5%bc&~&q@OwesV?!1gTt@lGO)X%;g-pE zBfh2HtVigqP@musAKzFpCO{z|6!h5ItZl{;x%qkJ<~&8HzE~amXKBy^jKVW7Ja&70 zl$jJr2?sVHu+bz3_6AK7KTq`iD=to}b@&dN43(~$bLDN0qG5$VfOmU&<>Ad6H*TDm ze!4ZVEfqtSrJjne6O;5HPRgg~kroxfAEK@AUhG`OM4wcUsz^vVY}y&);N1bBN^;rS zk>X?vQ4#n~mI;QoLrwlsU0waWBI8StW53TlzloW%m>v!9#z$x7zwPfv-O|gOJisK0 zbas6kFCJBBPFQ%@$l(bvASe-u!li~?G+Gv@2&)au)DzY^NX!kfio#%m(YH?u8B?Kt zg%m~j^BUn&u}qyFA3r8L(W0aW@;8bci@P+}o&ED%WT7LJk|O|j;+Vs8UyD_lK5|M} z+VD>de@%hM%r4;rN=t;Jr~XOq|G^e(r0j@7+tT79jBC&@y9Ng`wJ3VXl%+Xc%Ri=! zfgP4LI1ku~d$^w=35-6cpM0w(Ld&KqTJZ_TJ)(NdTufXZ0iPPeTckxyw1u=Ydp_s{&*v!5_gs|PuZkis=a zFmg^5x7*i+X&1I_;l#|d>SVbOTfW;#{(D<={Fn$eD3GN+X=%Lr8`tKYz1HR;!p%!{ R+tGMRaCh-!)Ht!v{vRlu4Wa-5 literal 0 HcmV?d00001 diff --git a/app/templates/admin/list_servers.html b/app/templates/admin/list_servers.html index e0f8c42..5040560 100644 --- a/app/templates/admin/list_servers.html +++ b/app/templates/admin/list_servers.html @@ -7,7 +7,7 @@
-
Transactions
+
Servers
@@ -15,7 +15,9 @@ Name - Description + CPU + MEM + HDD Address Region Seller @@ -26,7 +28,9 @@ {% for server in servers %} {{ server.name }} - {{ server.description }} + {{ server.cpu }} + {{ server.mem }} + {{ server.hdd }} {{ server.address }} {{ server.region.name }} {{ server.owner.email }} diff --git a/app/templates/base.html b/app/templates/base.html index 0d177b9..f828435 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -16,6 +16,7 @@ {{ super() }} + {% endblock %} {% block scripts %} diff --git a/app/templates/main/footer_index.html b/app/templates/main/footer_index.html index dd4ddd8..41d6396 100644 --- a/app/templates/main/footer_index.html +++ b/app/templates/main/footer_index.html @@ -8,7 +8,6 @@

Contact

-

52 Volga str.
4002 Plovdiv, Bulgaria

tel: +359 (0) 32 398 295
email: office@datapoint.bg

diff --git a/app/templates/main/index.html b/app/templates/main/index.html index 93d0aaa..67d46e1 100644 --- a/app/templates/main/index.html +++ b/app/templates/main/index.html @@ -86,6 +86,16 @@
+
+

+ +
+
+ Alias: {{ server.name }}
+ Processor: {{ server.cpu }}
+ Memory: {{ server.mem }}
+ Storage: {{ server.hdd }}
+
@@ -108,7 +118,7 @@

Поддръжка

Ще Ви помогнем във всички неприятни ситуации, по всяко време.

- +
diff --git a/app/templates/nav.html b/app/templates/nav.html index 38bd163..ca13908 100644 --- a/app/templates/nav.html +++ b/app/templates/nav.html @@ -11,7 +11,7 @@ {% if not current_user.is_authenticated %} {% else %} - + {% endif %}
@@ -49,7 +49,7 @@