-
5.gitignore
-
0README.md
-
4app.py
-
66app/__init__.py
-
8app/admin.py
-
8app/forms.py
-
55app/models.py
-
96app/static/css/app.css
-
BINapp/static/img/android.png
-
BINapp/static/img/binary.jpg
-
BINapp/static/img/bitcoins.png
-
BINapp/static/img/facebook.png
-
BINapp/static/img/gnupg.png
-
BINapp/static/img/ios.png
-
BINapp/static/img/openvpn.png
-
BINapp/static/img/osx.png
-
BINapp/static/img/reddit.png
-
BINapp/static/img/twitter.png
-
BINapp/static/img/windows.png
-
7643app/static/lib/uikit/css/uikit.almost-flat.css
-
3app/static/lib/uikit/css/uikit.almost-flat.min.css
-
6890app/static/lib/uikit/css/uikit.css
-
7759app/static/lib/uikit/css/uikit.gradient.css
-
3app/static/lib/uikit/css/uikit.gradient.min.css
-
3app/static/lib/uikit/css/uikit.min.css
-
BINapp/static/lib/uikit/fonts/FontAwesome.otf
-
BINapp/static/lib/uikit/fonts/fontawesome-webfont.eot
-
BINapp/static/lib/uikit/fonts/fontawesome-webfont.ttf
-
BINapp/static/lib/uikit/fonts/fontawesome-webfont.woff
-
1780app/static/lib/uikit/js/uikit.js
-
3app/static/lib/uikit/js/uikit.min.js
-
14app/templates/_macros.html
-
84app/templates/confirm_purchase.html
-
130app/templates/dashboard.html
-
26app/templates/editticket.html
-
9app/templates/error.html
-
88app/templates/index.html
-
66app/templates/layout.html
-
26app/templates/newticket.html
-
24app/templates/purchase.html
-
23app/templates/security/change_password.html
-
25app/templates/security/forgot_password.html
-
31app/templates/security/login_user.html
-
31app/templates/security/register_user.html
-
22app/templates/security/reset_password.html
-
17app/templates/viewticket.html
-
201app/views.py
@ -0,0 +1,5 @@ |
|||||
|
*.pyc |
||||
|
*.db |
||||
|
*.log |
||||
|
venv/ |
||||
|
|
@ -0,0 +1,4 @@ |
|||||
|
#!venv/bin/python |
||||
|
from app import app |
||||
|
|
||||
|
app.run(host='0.0.0.0', debug=True) |
@ -0,0 +1,66 @@ |
|||||
|
from flask import Flask |
||||
|
from flask.ext.sqlalchemy import SQLAlchemy |
||||
|
from flask_mail import Mail |
||||
|
from flask.ext.security import SQLAlchemyUserDatastore, Security, user_registered |
||||
|
from flask.ext.admin import Admin |
||||
|
from flask.ext.admin.contrib.sqla import ModelView |
||||
|
from config import ADMINS, MAILCONF, SECURITY_EMAIL_SENDER |
||||
|
|
||||
|
app = Flask(__name__) |
||||
|
app.config.from_object('config') |
||||
|
|
||||
|
# Setup SQL database and ORM |
||||
|
db = SQLAlchemy(app) |
||||
|
|
||||
|
# Initialize Flask-Security |
||||
|
from models import User, Role, Ticket, Invoice |
||||
|
user_datastore = SQLAlchemyUserDatastore(db, User, Role) |
||||
|
security = Security(app, user_datastore) |
||||
|
mail = Mail(app) |
||||
|
|
||||
|
# Initialize Flask-Admin |
||||
|
from app import admin |
||||
|
admin = Admin(app, name='PacketCrypt', index_view=admin.AdminIndex()) |
||||
|
admin.add_view(ModelView(User, db.session)) |
||||
|
admin.add_view(ModelView(Ticket, db.session)) |
||||
|
admin.add_view(ModelView(Invoice, db.session)) |
||||
|
|
||||
|
@app.before_first_request |
||||
|
def initialize(): |
||||
|
try: |
||||
|
db.create_all() |
||||
|
user = user_datastore.find_user(email='br4n@atr0phy.net') |
||||
|
if not user: |
||||
|
user = user_datastore.create_user(email='br4n@atr0phy.net', password='packetcrypt') |
||||
|
user_datastore.add_role_to_user(user, 'Admin') |
||||
|
app.logger.info("First run, create default admin user") |
||||
|
for role in ('Admin', 'User'): |
||||
|
user_datastore.create_role(name=role) |
||||
|
db.session.commit() |
||||
|
except Exception, e: |
||||
|
app.logger.error(str(e)) |
||||
|
|
||||
|
@user_registered.connect_via(app) |
||||
|
def on_user_registered(sender, **extra): |
||||
|
default_role = user_datastore.find_role("User") |
||||
|
user_datastore.add_role_to_user(user, default_role) |
||||
|
db.session.commit() |
||||
|
|
||||
|
# Import views |
||||
|
from app import views |
||||
|
|
||||
|
if not app.debug: |
||||
|
import logging |
||||
|
from logging.handlers import SMTPHandler, RotatingFileHandler |
||||
|
credentials = None |
||||
|
if MAILCONF['MAIL_USERNAME'] or MAILCONF['MAIL_PASSWORD']: |
||||
|
credentials = (MAILCONF['MAIL_USERNAME'], MAILCONF['MAIL_PASSWORD']) |
||||
|
mail_handler = SMTPHandler((MAILCONF['MAIL_SERVER'], MAILCONF['MAIL_PORT']), SECURITY_EMAIL_SENDER, ADMINS, 'PacketCrypt failure', credentials) |
||||
|
mail_handler.setLevel(logging.ERROR) |
||||
|
app.logger.addHandler(mail_handler) |
||||
|
file_handler = RotatingFileHandler('app.log', 'a', 1 * 1024 * 1024, 10) |
||||
|
file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')) |
||||
|
app.logger.setLevel(logging.INFO) |
||||
|
file_handler.setLevel(logging.INFO) |
||||
|
app.logger.addHandler(file_handler) |
||||
|
app.logger.info('PacketCrypt startup') |
@ -0,0 +1,8 @@ |
|||||
|
from flask.ext.security import current_user |
||||
|
from flask.ext.admin import AdminIndexView, BaseView, expose |
||||
|
|
||||
|
class AdminIndex(AdminIndexView): |
||||
|
def is_accessible(self): |
||||
|
return current_user.has_role('Admin') |
||||
|
|
||||
|
|
@ -0,0 +1,8 @@ |
|||||
|
from flask.ext.wtf import Form |
||||
|
from wtforms import TextField, SubmitField, TextAreaField |
||||
|
from wtforms.validators import Required |
||||
|
|
||||
|
class TicketForm(Form): |
||||
|
subject = TextField('Subject', validators = [Required()]) |
||||
|
body = TextAreaField('Message', validators = [Required()]) |
||||
|
|
@ -0,0 +1,55 @@ |
|||||
|
from app import db |
||||
|
from flask.ext.sqlalchemy import SQLAlchemy |
||||
|
from flask.ext.security import UserMixin, RoleMixin |
||||
|
|
||||
|
roles_users = db.Table('roles_users', |
||||
|
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), |
||||
|
db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))) |
||||
|
|
||||
|
class Role(db.Model, RoleMixin): |
||||
|
id = db.Column(db.Integer(), primary_key=True) |
||||
|
name = db.Column(db.String(80), unique=True) |
||||
|
description = db.Column(db.String(255)) |
||||
|
|
||||
|
class User(db.Model, UserMixin): |
||||
|
id = db.Column(db.Integer, primary_key=True) |
||||
|
email = db.Column(db.String(255), unique=True) |
||||
|
password = db.Column(db.String(255)) |
||||
|
active = db.Column(db.Boolean()) |
||||
|
confirmed_at = db.Column(db.DateTime()) |
||||
|
roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic')) |
||||
|
tickets = db.relationship('Ticket', backref='creator', lazy='dynamic') |
||||
|
invoices = db.relationship('Invoice', backref='customer', lazy='dynamic') |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '<User %r>' % (self.email) |
||||
|
|
||||
|
class Ticket(db.Model): |
||||
|
id = db.Column(db.Integer, primary_key=True) |
||||
|
subject = db.Column(db.String(140), unique=True) |
||||
|
body = db.Column(db.String(2000)) |
||||
|
created = db.Column(db.DateTime) |
||||
|
timestamp = db.Column(db.DateTime) |
||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id')) |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '<Ticket %r>' % (self.subject) |
||||
|
|
||||
|
class Invoice(db.Model): |
||||
|
id = db.Column(db.Integer, primary_key=True) |
||||
|
is_confirmed = db.Column(db.Boolean()) |
||||
|
paid = db.Column(db.Boolean()) |
||||
|
datepaid = db.Column(db.DateTime) |
||||
|
datecreated = db.Column(db.DateTime) |
||||
|
dateends = db.Column(db.DateTime) |
||||
|
total_btc = db.Column(db.Float) |
||||
|
exchange_rate_when_paid = db.Column(db.Float) |
||||
|
address = db.Column(db.String(34)) |
||||
|
confirmations = db.Column(db.Integer) |
||||
|
transaction_hash = db.Column(db.String) |
||||
|
input_transaction_hash = db.Column(db.String) |
||||
|
value_paid = db.Column(db.Float) |
||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id')) |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '<Invoice %r>' % (self.id) |
@ -0,0 +1,96 @@ |
|||||
|
body { |
||||
|
} |
||||
|
|
||||
|
#top-bar { |
||||
|
position: absolute; |
||||
|
width:100%; |
||||
|
top:0; |
||||
|
left:0; |
||||
|
background-color: rgba(255,255,255,0.85); |
||||
|
} |
||||
|
|
||||
|
#top-bar div:last-child { |
||||
|
margin: 1.0%; |
||||
|
} |
||||
|
|
||||
|
#big-top-bar { |
||||
|
background: url('/static/img/binary.jpg') no-repeat; |
||||
|
min-height: 400px; |
||||
|
position:relative; |
||||
|
} |
||||
|
|
||||
|
.pc-market-block { |
||||
|
max-width: 60%; |
||||
|
margin-top: 2em; |
||||
|
} |
||||
|
|
||||
|
#tech-images > img { |
||||
|
border-radius: 1.0; |
||||
|
padding: 40px 30px; |
||||
|
display: inline-block; |
||||
|
width: 100px; |
||||
|
} |
||||
|
|
||||
|
#pc-dashboard-wrapper { |
||||
|
max-width:60%; |
||||
|
} |
||||
|
|
||||
|
#footer { |
||||
|
padding-top:4em; |
||||
|
} |
||||
|
|
||||
|
#footer-social > img { |
||||
|
border-radius: 100px;; |
||||
|
padding: 0 8px; |
||||
|
display: inline-block; |
||||
|
width: 60px; |
||||
|
} |
||||
|
#three-fold-container { |
||||
|
max-width: 80%; |
||||
|
} |
||||
|
#two-fold-container { |
||||
|
padding-top:6em; |
||||
|
max-width:80%; |
||||
|
} |
||||
|
|
||||
|
#container { |
||||
|
} |
||||
|
|
||||
|
#form-container { |
||||
|
margin-top:20px; |
||||
|
} |
||||
|
|
||||
|
#form-container .uk-form-row > textarea { |
||||
|
width: 100%; |
||||
|
height: 200px; |
||||
|
} |
||||
|
|
||||
|
#bitcoin-logo { |
||||
|
bottom: 10px; |
||||
|
position: absolute; |
||||
|
right: 10px; |
||||
|
width: 200px; |
||||
|
} |
||||
|
|
||||
|
#btc_qr { |
||||
|
margin: 0 auto; |
||||
|
display:block |
||||
|
} |
||||
|
|
||||
|
.pc-form-row { |
||||
|
margin-bottom: 2em; |
||||
|
} |
||||
|
|
||||
|
#form-info { |
||||
|
width:55%; |
||||
|
} |
||||
|
#form-info > .uk-button { |
||||
|
margin-top:2em; |
||||
|
} |
||||
|
#form-container > form { |
||||
|
width:40%; |
||||
|
display:inline-block; |
||||
|
} |
||||
|
.pc-form-row > input { |
||||
|
width:100%; |
||||
|
} |
After Width: 22 | Height: 29 | Size: 3.1 KiB |
After Width: 2000 | Height: 1500 | Size: 470 KiB |
After Width: 1680 | Height: 641 | Size: 94 KiB |
After Width: 300 | Height: 300 | Size: 3.2 KiB |
After Width: 512 | Height: 512 | Size: 78 KiB |
After Width: 79 | Height: 52 | Size: 6.2 KiB |
After Width: 256 | Height: 256 | Size: 6.9 KiB |
After Width: 42 | Height: 52 | Size: 4.9 KiB |
After Width: 500 | Height: 500 | Size: 9.2 KiB |
After Width: 300 | Height: 300 | Size: 4.3 KiB |
After Width: 51 | Height: 52 | Size: 6.4 KiB |
7643
app/static/lib/uikit/css/uikit.almost-flat.css
File diff suppressed because it is too large
View File
3
app/static/lib/uikit/css/uikit.almost-flat.min.css
File diff suppressed because it is too large
View File
6890
app/static/lib/uikit/css/uikit.css
File diff suppressed because it is too large
View File
7759
app/static/lib/uikit/css/uikit.gradient.css
File diff suppressed because it is too large
View File
3
app/static/lib/uikit/css/uikit.gradient.min.css
File diff suppressed because it is too large
View File
3
app/static/lib/uikit/css/uikit.min.css
File diff suppressed because it is too large
View File
1780
app/static/lib/uikit/js/uikit.js
File diff suppressed because it is too large
View File
3
app/static/lib/uikit/js/uikit.min.js
File diff suppressed because it is too large
View File
@ -0,0 +1,14 @@ |
|||||
|
{% macro render_field_with_label(field) %} |
||||
|
{% set css_class=kwargs.pop('class', '') %} |
||||
|
<span class="uk-text-small uk-text-bold">{{ field.label }}:</span> |
||||
|
<div class="uk-form-row pc-form-row">{{ field(class=css_class, **kwargs)|safe }}</div> |
||||
|
{% endmacro %} |
||||
|
|
||||
|
{% macro render_field_with_label_oneline(field) %} |
||||
|
{% set css_class=kwargs.pop('class', '') %} |
||||
|
<div class="uk-form-row">{{field.label}} {{field(class=css_class, **kwargs)|safe }}</div> |
||||
|
{% endmacro %} |
||||
|
|
||||
|
{% macro render_field(field) %} |
||||
|
<div class="uk-form-row">{{ field(class=css_class, **kwargs)|safe }}</div> |
||||
|
{% endmacro %} |
@ -0,0 +1,84 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
|
||||
|
{% block title %}Confirming purchase - packetcrypt{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center"> |
||||
|
<h2>Invoice Created</h2> |
||||
|
Please send {{ invoice.total_btc - invoice.value_paid}} BTC to: <a href="bitcoin:{{invoice.address}}?amount={{invoice.total_btc}}">{{ invoice.address }}</a> |
||||
|
<br/><br/> |
||||
|
<img src="https://chart.googleapis.com/chart?cht=qr&chs=300x300&chl=bitcoin:{{invoice.address}}?amount={{invoice.total_btc}}&callback=?" id="btc_qr" /> |
||||
|
<button class="uk-button uk-button-primary uk-button-expand" id="confirm">Confirm Payment <i class="uk-icon-spinner"></i></button> |
||||
|
<form action="{{url_for('confirm_purchase', invoice_id=invoice.id)}}" method="POST" name="delete_ticket" style="width:100%;"> |
||||
|
<button type="submit" class="uk-button uk-button-expand" id="cancel">Cancel Payment</button> |
||||
|
</form> |
||||
|
</div> |
||||
|
|
||||
|
<div id="modal" class="uk-modal"> |
||||
|
<div class="uk-modal-dialog uk-modal-dialog-slide"> |
||||
|
<a class="uk-modal-close uk-close"></a> |
||||
|
<h1>Thank you!</h1> |
||||
|
<p>Your payment of <label id="valuepaid"></label> <i class="uk-icon-btc"></i> has been confirmed.</p> |
||||
|
<p id="paid">Your VPN service is being configured and should be available from your dashboard in the next 5-10 minutes.</p> |
||||
|
<p id="notpaid"> |
||||
|
However, this does not cover the total cost for your PacketCrypt service. |
||||
|
Please send <label id="remaining"></label> <i class="uk-icon-btc"></i> to: <a id="btcaddy" >{{invoice.address}}</a> |
||||
|
</p> |
||||
|
<p>Transaction: <a id="hashlink"></a></p> |
||||
|
<a class="uk-button uk-button-success uk-button-expand" href="{{url_for('dashboard')}}">Return to Dashboard</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block postscript %} |
||||
|
<script> |
||||
|
var amountPaidOnLoad = {{invoice.value_paid}}; |
||||
|
$(document).ready(function(){ |
||||
|
var confirmButton = $('#confirm'); |
||||
|
var cancelButton = $('#cancel'); |
||||
|
|
||||
|
confirmButton.click(checkPayment); |
||||
|
|
||||
|
checkPayment(); |
||||
|
|
||||
|
}); |
||||
|
var checkPayment = function (arg){ |
||||
|
$('#confirm > i').addClass('uk-icon-spin'); |
||||
|
$.post('/invoice_status', { |
||||
|
invoice_id: {{invoice.id}} |
||||
|
}).done(function(d){ |
||||
|
var paidInFull = (d['value_paid'] >= d['total_btc']); |
||||
|
var transactionHash = d['input_transaction_hash']; |
||||
|
var confirmations = d['confirmations']; |
||||
|
|
||||
|
window.setTimeout(stopSpin, 3500); |
||||
|
|
||||
|
if(confirmations == null || amountPaidOnLoad == d['value_paid']){ |
||||
|
//$('#flash-wrapper').children(":first").append("<div class='uk-alert uk-alert-warning' data-uk-alert><a class='uk-alert-close uk-close'></a>Unable to confirm payment. Wait a few seconds and try again.</div>"); |
||||
|
window.setTimeout(checkPayment, 10000); |
||||
|
} else { |
||||
|
// Setup the modal before display |
||||
|
var priceLeft = (d['total_btc'] - d['value_paid']); |
||||
|
var btcLink = "bitcoin:{{invoice.address}}?amount="+priceLeft; |
||||
|
var hashLink = "https://blockchain.info/tx/"+transactionHash; |
||||
|
$('#valuepaid').html(d['value_paid']); |
||||
|
$('#remaining').html(d['total_btc'] - d['value_paid']); |
||||
|
(paidInFull) ? $('#notpaid').css('display', 'none') : $('#paid').css('display', 'none'); |
||||
|
$('#btcaddy').attr('href', btcLink); |
||||
|
$('#hashlink').attr('href', hashLink).html(transactionHash); |
||||
|
|
||||
|
// Display teh modal |
||||
|
var modal = new $.UIkit.modal.Modal('#modal'); |
||||
|
modal.show(); |
||||
|
} |
||||
|
}).fail(function(){ |
||||
|
$('#flash-wrapper').children(":first").append("<div class='uk-alert uk-alert-danger' data-uk-alert><a href='' class='uk-alert-close uk-close'></a>An error occured while trying to confirm your payment.</div>"); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
var stopSpin = function (arg){ |
||||
|
$('#confirm > i').removeClass('uk-icon-spin'); |
||||
|
}; |
||||
|
</script> |
||||
|
{% endblock %} |
||||
|
|
@ -0,0 +1,130 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
|
||||
|
{% block title %} Dashboard - packetcrypt {% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div class="uk-container-center" id="pc-dashboard-wrapper"> |
||||
|
<div class="uk-grid"> |
||||
|
<div class="uk-width-1-1"> |
||||
|
<h4 class="uk-float-right">Hello, {{ user.email }}!</h4> |
||||
|
<br/> |
||||
|
</div> |
||||
|
<div class="uk-width-1-2"> |
||||
|
<div class="uk-panel"> |
||||
|
<h2 class="uk-panel-title">Account Information <i class="uk-icon-user"></i> </h2> |
||||
|
<ul> |
||||
|
<li>Email Address: {{ user.email }}</li> |
||||
|
<li>Last Payment Date: {{ lastpaid }}</li> |
||||
|
<li>Plan Expires: {{ expires }}</li> |
||||
|
<li>Number of Support Tickets: {{ user.tickets.all() | length }} </li> |
||||
|
<li class="uk-text-muted">Traffic This Month: 000Mb (00%)</li> |
||||
|
<li class="uk-text-muted">Number of Referred Signups: 0</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="uk-width-1-2"> |
||||
|
<div class="uk-panel uk-panel-box uk-panel-box-secondary"> |
||||
|
<h2 class="uk-panel-title">Controls <i class="uk-icon-cogs uk-float-right"></i> </h2> |
||||
|
<hr class="uk-panel-divider"> |
||||
|
<ul class="uk-nav"> |
||||
|
<li><a href="">Logout</a></li> |
||||
|
<li><a href="">Change Password</a></li> |
||||
|
<li><a href="{{ url_for('newticket') }}">Open Support Ticket</a></li> |
||||
|
</ul> |
||||
|
<div class="uk-panel" style="margin-top:1em;"> |
||||
|
{% if latest %} |
||||
|
<a href="{{url_for('purchase')}}" class="uk-button uk-button-success uk-align-left uk-button-small">Renew</a> |
||||
|
{% else %} |
||||
|
<a href="{{url_for('purchase')}}" class="uk-button uk-button-success uk-align-left uk-button-small">Purchase</a> |
||||
|
{% endif %} |
||||
|
|
||||
|
|
||||
|
<div class="uk-button-group uk-align-right" style=""> |
||||
|
<button class="uk-button uk-button-disabled uk-button-small" type="submit"><i class="uk-icon-folder-open-alt"></i> Download</button> |
||||
|
<div data-uk-dropdown> |
||||
|
<a href="" class="uk-button uk-button-small"><i class="uk-icon-caret-down"></i></a> |
||||
|
<div class="uk-dropdown uk-dropdown-small"> |
||||
|
<ul class="uk-nav uk-nav-dropdown"> |
||||
|
<li><a href="">Windows</a></li> |
||||
|
<li><a href="">OSX</a></li> |
||||
|
<li><a href="">Android</a></li> |
||||
|
<li><a href="">iOS</a></li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> <!-- end button-group --> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="uk-width-1-1"> |
||||
|
<table class="uk-table uk-table-hover uk-table-striped"> |
||||
|
<caption>Support Tickets <div class="uk-badge" style="margin-left:1em;">{{ g.user.tickets.all() | length }}</div></caption> |
||||
|
<thead> |
||||
|
<tr><th>Date</th><th>Subject</th><th>Status</th><th>Last Updated</th></tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
{% if g.user.tickets.all() %} |
||||
|
{% for ticket in g.user.tickets.all() %} |
||||
|
<tr class="ticket-row"> |
||||
|
<td>{{ ticket.timestamp |date }}</td> |
||||
|
<td><a href="{{ url_for('viewticket', tid=ticket.id) }}"></a>{{ ticket.subject }}</td> |
||||
|
<td>N/A</td> |
||||
|
<td>N/A</td> |
||||
|
</tr> |
||||
|
{% endfor %} |
||||
|
{% else %} |
||||
|
<tr><td>You have no support ticket history</td><td></td><td></td><td></td></tr> |
||||
|
{% endif %} |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<br/><br/> |
||||
|
<table class="uk-table uk-table-hover uk-table-striped"> |
||||
|
<caption>Invoices <div class="uk-badge uk-badge-danger" style="margin-left:1em;">{{ g.user.invoices.all() | length }}</div></caption> |
||||
|
<thead> |
||||
|
<tr><th>Date Paid</th><th>Amount Paid</th><th>Payment Address</th><th>Confirmed</th></tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
{% if g.user.invoices.all() %} |
||||
|
{% for invoice in g.user.invoices.all() %} |
||||
|
<tr class="invoice-row"> |
||||
|
{% if invoice.datepaid %} |
||||
|
<td>{{invoice.datepaid | date}}</td> |
||||
|
{% else %} |
||||
|
<td>Unpaid</td> |
||||
|
{% endif %} |
||||
|
<td>{{invoice.value_paid}}</td> |
||||
|
<td>{{invoice.address}}</td> |
||||
|
{% if invoice.is_confirmed %} |
||||
|
<td><a href="{{url_for('confirm_purchase', invoice_id=invoice.id)}}"><i class="uk-icon-ok"></i></a></td> |
||||
|
{% else %} |
||||
|
{% if invoice.paid %} |
||||
|
<td><a href="{{url_for('confirm_purchase', invoice_id=invoice.id)}}"><i class="uk-icon-ban-circle"></i></a></td> |
||||
|
{% else %} |
||||
|
<td><a href="{{url_for('confirm_purchase', invoice_id=invoice.id)}}" class="uk-button">Pay <i class="uk-icon-btc"></i></a></td> |
||||
|
{% endif %} |
||||
|
{% endif %} |
||||
|
</tr> |
||||
|
{% endfor %} |
||||
|
{% endif %} |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block postscript %} |
||||
|
<script type="text/javascript"> |
||||
|
$(window).load(function(){ |
||||
|
$(".ticket-row").click(function(){ |
||||
|
var tid = $(this).find("a").attr("href"); |
||||
|
window.location = tid |
||||
|
}); |
||||
|
$(".invoice-row").click(function(){ |
||||
|
var invoice_id = $(this).find("a").attr("href"); |
||||
|
window.location = invoice_id |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
</script> |
||||
|
{% endblock %} |
@ -0,0 +1,26 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
{% from "_macros.html" import render_field_with_label, render_field %} |
||||
|
|
||||
|
{% block title %}Update Support Ticket - packetcrypt{% endblock %} |
||||
|
{% block flash %} |
||||
|
{% for field in form.errors %} |
||||
|
{% for error in form.errors[field] %} |
||||
|
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div> |
||||
|
{% endfor %} |
||||
|
{% endfor %} |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center"> |
||||
|
<form action="{{ url_for('editticket', tid=ticket.id) }}" method="POST" name="form" class="uk-form" style="width:100%"> |
||||
|
{{ form.hidden_tag() }} |
||||
|
{{ render_field_with_label(form.subject) }} |
||||
|
{{ render_field_with_label(form.body) }} |
||||
|
<div class="uk-form-row"> |
||||
|
<button class="uk-button uk-button-success" type="submit">Update Ticket</button> |
||||
|
<a href="{{url_for('dashboard')}}" class="uk-button uk-button-primary">Close</a> |
||||
|
<a href="{{url_for('deleteticket', tid=ticket.id)}}" class="uk-button uk-button-danger">Delete Ticket</a> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div> |
||||
|
{% endblock %} |
@ -0,0 +1,9 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="form-container" class="uk-width-1-4 uk-panel uk-panel-box uk-container-center"> |
||||
|
<h1> The file was not found and/or an unexpected error occurred.</h1> |
||||
|
<p>The administrator has been notified. Sorry for the inconvenience!</p> |
||||
|
<p><a href="{{url_for('index')}}" class="uk-button">Back</a></p> |
||||
|
</div> |
||||
|
{% endblock %} |
@ -0,0 +1,88 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
|
||||
|
{% block title %}PacketCrypt - Secure, anonymous VPN service for humans{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div class="uk-grid"> |
||||
|
<div class="uk-width-1-1"> |
||||
|
<div class="uk-panel uk-container-center pc-market-block uk-text-center"> |
||||
|
<h1>We handle the security, you browse the web.</h1> |
||||
|
<span class="uk-text-large"> |
||||
|
PacketCrypt offers the the quickest way available to keep your web traffic safe. |
||||
|
We follow financial industry standards to ensure your data is protected no matter where you're grabbing a connection. |
||||
|
PacketCrypt makes it simple to encrypt your internet trafic and go about your day. |
||||
|
</span> |
||||
|
<div id="tech-images"> |
||||
|
<img src={{ url_for('static', filename='img/windows.png') }} /> |
||||
|
<img src={{ url_for('static', filename='img/openvpn.png') }} /> |
||||
|
<img src={{ url_for('static', filename='img/android.png') }} /> |
||||
|
<img src={{ url_for('static', filename='img/ios.png') }} /> |
||||
|
<img src={{ url_for('static', filename='img/osx.png') }} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="uk-width-1-1"> |
||||
|
<div id="three-fold-container" class="uk-container-center"> |
||||
|
<div class="uk-grid" data-uk-grid-match="{target:'.uk-panel'}"> |
||||
|
<div class="uk-width-large-1-3 uk-width-medium-1-1"> |
||||
|
<div class="uk-panel uk-panel-box uk-panel-box-primary"> |
||||
|
<h3 class="uk-panel-title uk-text-center"><i class="uk-icon-lock uk-icon-large"></i> Secure</h3> |
||||
|
We don't take chances with your data or ours. |
||||
|
All of PacketCrypt's services rely on OpenVPN, open source security software trusted by over 5 million users. |
||||
|
Run on the safest platform in the world: FreeBSD. Our servers are stable and secured to make sure we're always there for you. |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="uk-width-large-1-3 uk-width-medium-1-1"> |
||||
|
<div class="uk-panel uk-panel-box uk-panel-box-primary"> |
||||
|
<h3 class="uk-panel-title uk-text-center"><i class="uk-icon-download-alt uk-icon-large"></i> Fast</h3> |
||||
|
Unlike other anonymous VPN providers, you don't have to sacrifice speed for safety with PacketCrypt. |
||||
|
All of our servers worldwide are on a Gigabit pipeline, so you can download as fast as your local connection can handle. |
||||
|
Stream movies or download large files fast, and without leaving a trace. |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="uk-width-large-1-3 uk-width-medium-1-1"> |
||||
|
<div class="uk-panel uk-panel-box uk-panel-box-primary"> |
||||
|
<h3 class="uk-panel-title uk-text-center"><i class="uk-icon-group uk-icon-large"></i> Anonymous</h3> |
||||
|
The first rule of security is <em>Trust No One</em>, and that includes us. |
||||
|
PacketCrypt keeps no logs, and doesn't track your login sessions. |
||||
|
You pay us in Bitcoin and all we ask for is an email address. |
||||
|
Not only does PacketCrypt protect you from the rest of the internet, we protect you from ourselves. |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="uk-width-1-1"> |
||||
|
<div id="two-fold-container" class="uk-container-center"> |
||||
|
<div class="uk-grid" data-uk-grid-match="{target:'.uk-panel'}"> |
||||
|
<div class="uk-width-1-3"> |
||||
|
<div class="uk-panel uk-align-left uk-text-center"> |
||||
|
<h2>Sign Up Now!</h2> |
||||
|
<ul class="uk-list uk-list-striped"> |
||||
|
<li>No personal information required</li> |
||||
|
<li>Only <strong id="btcprice">0</strong><i class="uk-icon-btc"></i> BTC</li> |
||||
|
<li>Supports all major platforms</li> |
||||
|
<li>Unthrottled Gigabit connection</li> |
||||
|
<li>1000Gb of transfer per month!</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="uk-width-2-3"> |
||||
|
<div class="uk-panel"> |
||||
|
<h2>Getting started is easy:</h2> |
||||
|
<p> |
||||
|
All you need to register is an email address! Bitcoin transactions are processed instantly, |
||||
|
and your access will be available within 10 minutes of purchase. Use our custom VPN client |
||||
|
for an install-and-go experience or download your client key and use the tools you're |
||||
|
familiar with! Click below to sign up for an account and let Packetcrypt secure your web traffic. |
||||
|
</p> |
||||
|
</span> |
||||
|
<a href="{{url_for('purchase')}}" class="uk-button uk-button-primary uk-button-large uk-align-right">Buy Now!</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{% endblock %} |
@ -0,0 +1,66 @@ |
|||||
|
<html> |
||||
|
<head> |
||||
|
<!-- Stylesheets --> |
||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='lib/uikit/css/uikit.gradient.min.css') }}" /> |
||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}" /> |
||||
|
|
||||
|
<!-- Javascript --> |
||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> |
||||
|
<script src="{{ url_for('static', filename='lib/uikit/js/uikit.min.js') }}"></script> |
||||
|
|
||||
|
<title>{% block title %}{% endblock %}</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="container" class="uk-grid"> |
||||
|
<div id="big-top-bar" class="uk-width-1-1"> |
||||
|
<div id="top-bar" class=""> |
||||
|
<div class="uk-float-left"><img src="http://placehold.it/250x65" /></div> |
||||
|
<div class="uk-float-right uk-button-group"> |
||||
|
<a class="uk-button" href="{{ url_for('index') }}">Home</a> |
||||
|
<a class="uk-button" href=" {{ url_for('blog') }}">Blog</a> |
||||
|
{% if g.user.is_authenticated() %} |
||||
|
<a class="uk-button uk-button-success" href="{{ url_for_security('logout') }}">Logoff</a> |
||||
|
<a class="uk-button uk-button-primary" href=" {{ url_for('dashboard') }}">Dashboard</a> |
||||
|
{% else %} |
||||
|
<a class="uk-button uk-button-success" href="{{ url_for_security('login') }}">Login</a> |
||||
|
<a class="uk-button uk-button-primary" href="{{ url_for_security('register') }}">Signup</a> |
||||
|
{% endif %} |
||||
|
{% if g.user.has_role('Admin') %} |
||||
|
<a class="uk-button uk-button-danger" href="/admin/">Admin Panel</a> |
||||
|
{% endif %} |
||||
|
</div> |
||||
|
</div> |
||||
|
<a href="http://bitcoin.org"><img id="bitcoin-logo" src="{{ url_for('static', filename='img/bitcoins.png') }}" /></a> |
||||
|
</div> |
||||
|
<div id="flash-wrapper" class="uk-width-1-1"> |
||||
|
<div class="uk-panel uk-width-1-2 uk-container-center"> |
||||
|
{% with messages = get_flashed_messages() %} |
||||
|
{% if messages %} |
||||
|
{% for message in messages %} |
||||
|
<div class="uk-alert uk-alert-success" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ message }}</div> |
||||
|
{% endfor %} |
||||
|
{% endif %} |
||||
|
{% block flash %}{% endblock %} |
||||
|
{% endwith %} |
||||
|
</div> |
||||
|
</div> |
||||
|
<div id="content" class="uk-width-1-1"> |
||||
|
{% block content %}{% endblock %} |
||||
|
</div> |
||||
|
<div id="footer" class="uk-width-1-1"> |
||||
|
<hr/> |
||||
|
<div class="uk-float-left"> |
||||
|
© 2013 PacketCrypt, Inc. All Rights Reserved. PacketCrypt™ is property of PacketCrypt, Inc.<br/> All other marks are the property of their respective owners. |
||||
|
<br /> |
||||
|
<a href="">Terms</a> | <a href="">Privacy Policy</a> | <a href="">Contact us</a> |
||||
|
</div> |
||||
|
<div class="uk-float-right" id="footer-social"> |
||||
|
<img src="{{ url_for('static', filename='img/reddit.png') }}"/> |
||||
|
<img src="{{ url_for('static', filename='img/twitter.png') }}"/> |
||||
|
<img src="{{ url_for('static', filename='img/facebook.png') }}"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
{% block postscript %}{% endblock %} |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,26 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
{% from "_macros.html" import render_field_with_label, render_field %} |
||||
|
|
||||
|
{% block title %}Create Support Ticket - packetcrypt{% endblock %} |
||||
|
{% block flash %} |
||||
|
{% for field in form.errors %} |
||||
|
{% for error in form.errors[field] %} |
||||
|
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div> |
||||
|
{% endfor %} |
||||
|
{% endfor %} |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center"> |
||||
|
<form action="{{ url_for('newticket') }}" method="POST" name="form" class="uk-form" style="width:100%;"> |
||||
|
{{ form.hidden_tag() }} |
||||
|
{{ render_field_with_label(form.subject) }} |
||||
|
{{ render_field_with_label(form.body) }} |
||||
|
<div class="uk-form-row"> |
||||
|
<button class="uk-button uk-button-primary" type="Submit">Create Ticket</button> |
||||
|
<button class="uk-button uk-button-danger" type="reset">Reset</button> |
||||
|
<a class="uk-button" href="{{ url_for('dashboard') }}">Cancel</a> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div> |
||||
|
{% endblock %} |
@ -0,0 +1,24 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
|
||||
|
{% block title %}Purchase VPN Service Now - packetcrypt{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center"> |
||||
|
<h4>Purchase PacketCrypt VPN Service</h4> |
||||
|
<ul> |
||||
|
<li>Service in 5 different countries</li> |
||||
|
<li>4 weeks of access</li> |
||||
|
<li>Price (USD): ${{service_price}}</li> |
||||
|
{% if price %} |
||||
|
<li>Price (BTC): {{ price }}</li> |
||||
|
{% endif %} |
||||
|
</ul> |
||||
|
{% if g.user.is_authenticated() %} |
||||
|
<a href="{{url_for('confirm_purchase')}}" class="uk-button uk-button-success">Purchase</a> |
||||
|
<a href="{{url_for('dashboard')}}" class="uk-button uk-button-danger">Cancel</a> |
||||
|
{% else %} |
||||
|
<a href="{{url_for_security('login')}}" class="uk-button uk-button-primary">Login to Purchase</a> |
||||
|
<a href="{{url_for_security('register')}}" class="uk-button uk-button-success">Register an Account</a> |
||||
|
{% endif %} |
||||
|
</div> |
||||
|
{% endblock %} |
@ -0,0 +1,23 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
{% from "_macros.html" import render_field_with_label, render_field %} |
||||
|
|
||||
|
{% block title %}Change Password - packetcrypt{% endblock %} |
||||
|
{% block flash %} |
||||
|
{% for field in change_password_form.errors %} |
||||
|
{% for error in change_password_form.errors[field] %} |
||||
|
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div> |
||||
|
{% endfor %} |
||||
|
{% endfor %} |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="form-container" class="uk-width-1-4 uk-panel uk-panel-box uk-container-center"> |
||||
|
<form action="{{ url_for_security('change_password') }}" method="POST" name="change_password_form" class="uk-form"> |
||||
|
{{ change_password_form.hidden_tag() }} |
||||
|
{{ render_field_with_label(change_password_form.password) }} |
||||
|
{{ render_field_with_label(change_password_form.new_password) }} |
||||
|
{{ render_field_with_label(change_password_form.new_password_confirm) }} |
||||
|
{{ render_field(change_password_form.submit) }} |
||||
|
</form> |
||||
|
</div> |
||||
|
{% endblock %} |
@ -0,0 +1,25 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
{% from "_macros.html" import render_field_with_label, render_field %} |
||||
|
|
||||
|
{% block title %}Forgot Password - packetcrypt{% endblock %} |
||||
|
{% block flash %} |
||||
|
{% for field in forgot_password_form.errors %} |
||||
|
{% for error in forgot_password_form.errors[field] %} |
||||
|
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div> |
||||
|
{% endfor %} |
||||
|
{% endfor %} |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="form-container" class="uk-width-3-5 uk-panel uk-panel-box uk-container-center"> |
||||
|
<form action="{{ url_for_security('forgot_password') }}" method="POST" name="forgot_password_form" class="uk-form"> |
||||
|
{{ forgot_password_form.hidden_tag() }} |
||||
|
{{ render_field_with_label(forgot_password_form.email) }} |
||||
|
{{ render_field(forgot_password_form.submit, class="uk-button") }} |
||||
|
</form> |
||||
|
<div id="form-info" class="uk-align-right uk-text-info"> |
||||
|
Enter the email-address associated with your account to the left. |
||||
|
Instructions on resetting your password will be emailed to you. |
||||
|
</div> |
||||
|
</div> |
||||
|
{% endblock %} |
@ -0,0 +1,31 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
{% from "_macros.html" import render_field_with_label, render_field_with_label_oneline, render_field %} |
||||
|
|
||||
|
{% block title %}Login - packetcrypt{% endblock %} |
||||
|
{% block flash %} |
||||
|
{% for field in login_user_form.errors %} |
||||
|
{% for error in login_user_form.errors[field] %} |
||||
|
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div> |
||||
|
{% endfor %} |
||||
|
{% endfor %} |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="form-container" class="uk-width-3-5 uk-panel uk-panel-box uk-container-center"> |
||||
|
<form action="{{ url_for_security('login') }}" method="POST" name="login_user_form" class="uk-form"> |
||||
|
{{ login_user_form.hidden_tag() }} |
||||
|
{{ render_field_with_label(login_user_form.email) }} |
||||
|
{{ render_field_with_label(login_user_form.password) }} |
||||
|
{{ render_field_with_label_oneline(login_user_form.remember) }} |
||||
|
{{ render_field(login_user_form.next) }} |
||||
|
{{ render_field(login_user_form.submit, class="uk-button uk-button-primary") }} |
||||
|
</form> |
||||
|
<div id="form-info" class="uk-align-right uk-text-info"> |
||||
|
Log in with your Packetcrypt email and password to access your dashboard, download the VPN client, and purchase extended service! |
||||
|
If you are unable to remember your password, please use the button below to reset it. |
||||
|
|
||||
|
<a href="{{url_for_security('forgot_password')}}" class="uk-button">Forgot Password?</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
{% endblock %} |
||||
|
|
@ -0,0 +1,31 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
{% from "_macros.html" import render_field_with_label, render_field %} |
||||
|
|
||||
|
{% block title %}Register - packetcrypt{% endblock %} |
||||
|
{% block flash %} |
||||
|
{% for field in register_user_form.errors %} |
||||
|
{% for error in register_user_form.errors[field] %} |
||||
|
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div> |
||||
|
{% endfor %} |
||||
|
{% endfor %} |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="form-container" class="uk-width-3-5 uk-panel uk-panel-box uk-container-center"> |
||||
|
<form action="{{ url_for_security('register') }}" method="POST" name="register_user_form" class="uk-form" style="display:inline-block;"> |
||||
|
{{ register_user_form.hidden_tag() }} |
||||
|
{{ render_field_with_label(register_user_form.email) }} |
||||
|
{{ render_field_with_label(register_user_form.password) }} |
||||
|
{% if register_user_form.password_confirm %} |
||||
|
{{ render_field_with_label(register_user_form.password_confirm) }} |
||||
|
{% endif %} |
||||
|
{{ render_field(register_user_form.submit, class='uk-button uk-button-primary') }} |
||||
|
</form> |
||||
|
<div id="form-info" class="uk-align-right uk-text-info"> |
||||
|
All we need is an email address and a password to create an account, and you'll be clicks away from browsing securely. |
||||
|
After registering please check your inbox for a verification email. |
||||
|
|
||||
|
<br/><p><a href="{{url_for_security('login')}}" class="uk-text-bold uk-text-muted">Click here to sign in to an existing account.</a></p> |
||||
|
</div> |
||||
|
</div> |
||||
|
{% endblock %} |
@ -0,0 +1,22 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
{% from "_macros.html" import render_field_with_label, render_field %} |
||||
|
|
||||
|
{% block title %}Reset Password - packetcrypt{% endblock %} |
||||
|
{% block flash %} |
||||
|
{% for field in reset_password_form.errors %} |
||||
|
{% for error in reset_password_form.errors[field] %} |
||||
|
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div> |
||||
|
{% endfor %} |
||||
|
{% endfor %} |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="form-container" class="uk-width-1-4 uk-panel uk-panel-box uk-container-center"> |
||||
|
<form action="{{ url_for_security('reset_password') }}" method="POST" name="reset_password_form" class="uk-form"> |
||||
|
{{ reset_password_form.hidden_tag() }} |
||||
|
{{ render_field_with_label(reset_password_form.password) }} |
||||
|
{{ render_field_with_label(reset_password_form.password_confirm) }} |
||||
|
{{ render_field(reset_password_form.submit) }} |
||||
|
</form> |
||||
|
</div> |
||||
|
{% endblock %} |
@ -0,0 +1,17 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
|
||||
|
{% block title %}View Support Ticket - packetcrypt{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center"> |
||||
|
<h4 class="uk-panel-title">{{ ticket.subject }}</h4> |
||||
|
<hr class="uk-panel-divider"/> |
||||
|
<span class="ticket-text">{{ ticket.body }}</span> |
||||
|
<br/> |
||||
|
<span class="uk-article-meta">{{ ticket.timestamp }}</span> |
||||
|
<br/><br/> |
||||
|
<a href="{{url_for('dashboard')}}" class="uk-button uk-button-primary" type="submit">Close</a> |
||||
|
<a href="{{url_for('editticket', tid=ticket.id)}}" class="uk-button uk-button-success">Edit</a> |
||||
|
<a href="{{url_for('deleteticket', tid=ticket.id)}}" class="uk-button uk-button-danger">Delete</a> |
||||
|
</div> |
||||
|
{% endblock %} |
@ -0,0 +1,201 @@ |
|||||
|
from flask import render_template, flash, redirect, g, request, url_for, jsonify |
||||
|
from app import app, db |
||||
|
from models import Ticket, Invoice |
||||
|
from forms import TicketForm |
||||
|
from flask.ext.security import login_required, current_user |
||||
|
from config import BLOCKCHAIN_URL, SECRET_KEY, STASH_WALLET, PRICE_OF_SERVICE, CONFIRMATION_CAP |
||||
|
import simplejson as json |
||||
|
import urllib2 |
||||
|
|
||||
|
# Page routes |
||||
|
@app.route('/') |
||||
|
@app.route('/index') |
||||
|
def index(): |
||||
|
user = g.user |
||||
|
return render_template("index.html", user=user) |
||||
|
|
||||
|
@app.route('/blog') |
||||
|
def blog(): |
||||
|
return "Da Blog!" |
||||
|
|
||||
|
@app.route('/dashboard') |
||||
|
@login_required |
||||
|
def dashboard(): |
||||
|
user = g.user |
||||
|
latest_invoice = user.invoices.order_by(Invoice.datepaid.desc()).first() |
||||
|
lastpaid = latest_invoice.datepaid if latest_invoice else "Unpurchased" |
||||
|
expires = latest_invoice.dateends if latest_invoice else "No service enabled" |
||||
|
return render_template("dashboard.html", user=user, latest=latest_invoice, lastpaid=lastpaid, expires=expires) |
||||
|
|
||||
|
@app.route('/newticket', methods=['GET', 'POST']) |
||||
|
@login_required |
||||
|
def newticket(): |
||||
|
user = g.user |
||||
|
form = TicketForm() |
||||
|
if form.validate_on_submit(): |
||||
|
import datetime |
||||
|
t = Ticket() |
||||
|
form.populate_obj(t) |
||||
|
t.timestamp = datetime.datetime.utcnow() |
||||
|
t.created = datetime.datetime.utcnow() |
||||
|
t.user_id = user.id |
||||
|
db.session.add(t); |
||||
|
db.session.commit() |
||||
|
|
||||
|
flash('New ticket submitted: ' + form.subject.data) |
||||
|
return redirect('/dashboard') |
||||
|
return render_template('newticket.html', form=form, user=user) |
||||
|
|
||||
|
@app.route('/viewticket/<int:tid>', methods=['GET', 'POST']) |
||||
|
@login_required |
||||
|
def viewticket(tid): |
||||
|
user = g.user |
||||
|
t = Ticket.query.get(tid) |
||||
|
return render_template('viewticket.html', user=user, ticket=t) |
||||
|
|
||||
|
@app.route('/editticket/<int:tid>', methods=['GET', 'POST']) |
||||
|
@login_required |
||||
|
def editticket(tid): |
||||
|
user = g.user |
||||
|
t = Ticket.query.get(tid) |
||||
|
form = TicketForm(subject=t.subject, body=t.body) |
||||
|
if form.validate_on_submit(): |
||||
|
import datetime |
||||
|
form.populate_obj(t) |
||||
|
t.timestamp = datetime.datetime.utcnow() |
||||
|
flash("Updated ticket: " + t.subject) |
||||
|
db.session.commit() |
||||
|
return redirect('/dashboard') |
||||
|
return render_template('editticket.html', user=user, ticket=t, form=form) |
||||
|
|
||||
|
@app.route('/deleteticket/<int:tid>', methods=['GET', 'POST']) |
||||
|
@login_required |
||||
|
def deleteticket(tid): |
||||
|
user = g.user |
||||
|
t = Ticket.query.get(tid) |
||||
|
return render_template('viewticket.html', user=user, ticket=t) |
||||
|
|
||||
|
# Start Bitcoin stuff -- blockchain.info api |
||||
|
@app.route('/purchase') |
||||
|
def purchase(): |
||||
|
user = g.user |
||||
|
try: |
||||
|
exchange_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/tobtc?currency=USD&value=30"))) |
||||
|
except urllib2.URLError as e: |
||||
|
flash('Unable to fetch current BTC exchange rate.') |
||||
|
return render_template('purchase.html', user=user, price=0, service_price=PRICE_OF_SERVICE) |
||||
|
return render_template('purchase.html', user=user, price=str(exchange_data), service_price=PRICE_OF_SERVICE) |
||||
|
|
||||
|
@app.route('/confirm_purchase/', defaults={'invoice_id': None}) |
||||
|
@app.route('/confirm_purchase/<int:invoice_id>', methods=['GET', 'POST']) |
||||
|
@login_required |
||||
|
def confirm_purchase(invoice_id): |
||||
|
import datetime |
||||
|
user = g.user |
||||
|
if invoice_id is None: |
||||
|
i = Invoice() |
||||
|
i.paid = False |
||||
|
i.datecreated = datetime.datetime.utcnow() |
||||
|
i.user_id = user.id |
||||
|
db.session.add(i) |
||||
|
db.session.commit() |
||||
|
try: |
||||
|
callback_url = url_for('pay_invoice', _external=True)+'?secret='+SECRET_KEY+'%26invoice_id='+str(i.id) |
||||
|
url = BLOCKCHAIN_URL+'?method=create&address='+STASH_WALLET+'&callback='+callback_url |
||||
|
xhr = urllib2.Request(url) |
||||
|
data = json.load(urllib2.build_opener().open(xhr)) |
||||
|
price_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/tobtc?currency=USD&value="+str(PRICE_OF_SERVICE)))) |
||||
|
exchange_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/ticker"))) |
||||
|
app.logger.info("Sent to blockchain api: " + url) |
||||
|
except urllib2.URLError as e: |
||||
|
app.logger.error('Unable to access the blockchain.info api: ' + url) |
||||
|
flash('There was an error creating a new invoice. Please try again later.') |
||||
|
return redirect('/dashboard') |
||||
|
i.address = data['input_address'] |
||||
|
i.total_btc = price_data |
||||
|
i.exchange_rate_when_paid = exchange_data['USD']['last'] |
||||
|
db.session.commit() |
||||
|
# TODO: Generate a QR code and/or other e-z payment options for BTC services |
||||
|
return redirect(url_for('confirm_purchase', invoice_id=i.id)) |
||||
|
else: |
||||
|
i = Invoice.query.get(invoice_id) |
||||
|
if request.method == 'POST': |
||||
|
flash('Invoice ('+i.address+') was deleted succesfully.') |
||||
|
db.session.delete(i) |
||||
|
db.session.commit() |
||||
|
return redirect(url_for('dashboard')) |
||||
|
return render_template('confirm_purchase.html', user=user, invoice=i, min_confirm=CONFIRMATION_CAP) |
||||
|
|
||||
|
|
||||
|
# AJAX Callbacks |
||||
|
@app.route('/invoice_status', methods=['POST']) |
||||
|
def invoice_status(): |
||||
|
data = request.form |
||||
|
i = Invoice.query.get(data['invoice_id']) |
||||
|
if i is None: |
||||
|
return 0 |
||||
|
return jsonify({ |
||||
|
'confirmations': i.confirmations, |
||||
|
'value_paid': i.value_paid, |
||||
|
'total_btc': i.total_btc, |
||||
|
'input_transaction_hash': i.input_transaction_hash |
||||
|
}) |
||||
|
|
||||
|
@app.route('/pay_invoice', methods=['GET']) |
||||
|
def pay_invoice(): |
||||
|
data = request.args |
||||
|
if 'test' in data: |
||||
|
app.logger.info('Test response recieved from Blockchain.info. return: *test*') |
||||
|
return "*test*" |
||||
|
if 'secret' in data and data['secret'] == SECRET_KEY: |
||||
|
import datetime |
||||
|
i = Invoice.query.get(data['invoice_id']) |
||||
|
if i is None: |
||||
|
# could not find invoice - do we ignore or create? |
||||
|
app.logger.info("Callback received for non-existant invoice. return: *error*") |
||||
|
return "*error*" |
||||
|
if not i.paid: |
||||
|
i.value_paid = float(data['value']) / 100000000 |
||||
|
else: |
||||
|
i.value_paid += float(data['value']) / 100000000 |
||||
|
i.datepaid = datetime.datetime.utcnow() |
||||
|
i.confirmations = data['confirmations'] |
||||
|
i.transaction_hash = data['transaction_hash'] |
||||
|
i.input_transaction_hash = data['input_transaction_hash'] |
||||
|
if i.value_paid == i.total_btc: |
||||
|
app.logger.info("Invoice {} paid on {} for {} BTC.".format(i.id, i.datepaid, i.value_paid)) |
||||
|
i.paid = True |
||||
|
db.session.commit() |
||||
|
if i.paid and i.confirmations > CONFIRMATION_CAP: |
||||
|
app.logger.info("Invoice {} was confirmed at {}. return: *ok*".format(i.id, i.datepaid)) |
||||
|
i.is_confirmed = True |
||||
|
i.dateends = i.datepaid + datetime.timedelta(weeks=4) |
||||
|
return "*ok*" |
||||
|
app.logger.info("Callback received for invoice {}: awaiting confirmation (current: {}). return: *unconfirmed*".format(i.id, i.confirmations)) |
||||
|
return "*unconfirmed*" |
||||
|
else: |
||||
|
app.logger.info('Payment callback with invalid secret key recieved. return: *error*') |
||||
|
return "*error*" |
||||
|
|
||||
|
# Not routes |
||||
|
@app.errorhandler(404) |
||||
|
def internal_error(error): |
||||
|
return render_template('error.html'), 404 |
||||
|
|
||||
|
@app.errorhandler(500) |
||||
|
def internal_error(error): |
||||
|
db.session.rollback() |
||||
|
return render_template('error.html'), 500 |
||||
|
|
||||
|
@app.before_request |
||||
|
def before_request(): |
||||
|
g.user = current_user |
||||
|
|
||||
|
@app.template_filter('date') |
||||
|
def _jinja2_filter_datetime(date, fmt=None): |
||||
|
if not date: |
||||
|
return None |
||||
|
if fmt: |
||||
|
return date.strftime(fmt) |
||||
|
else: |
||||
|
return date.strftime("%m/%d/%y (%I:%M%p)") |