fine-tuning and starting to work on account-balance

This commit is contained in:
forest 2020-05-11 20:34:12 -05:00
parent a6e655ef72
commit d7ddf37cfa
9 changed files with 93 additions and 45 deletions

View File

@ -1,5 +1,6 @@
import re import re
import sys import sys
from datetime import datetime
from flask import Blueprint from flask import Blueprint
from flask import flash from flask import flash
from flask import current_app from flask import current_app
@ -23,16 +24,16 @@ def makeCapsulId():
lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10) lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10)
return f"capsul-{lettersAndNumbers}" return f"capsul-{lettersAndNumbers}"
def double_check_capsul_address(db_model, email, id, ipv4): def double_check_capsul_address(id, ipv4):
try: try:
result = current_app.config["VIRTUALIZATION_MODEL"].get(id) result = current_app.config["VIRTUALIZATION_MODEL"].get(id)
if result.ipv4 != ipv4: if result.ipv4 != ipv4:
ipv4 = result.ipv4 ipv4 = result.ipv4
db_model.updateVm(email=email, id=id, ipv4=result.ipv4) get_model().updateVm(email=session["account"], id=id, ipv4=result.ipv4)
except: except:
print(f""" print(f"""
error occurred in list capsuls endpoint while trying to grab ip address of {vm['id']} the virtualization model threw an error in double_check_capsul_address of {id}:
via the virtualization model: {my_exec_info_message(sys.exc_info())}""" {my_exec_info_message(sys.exc_info())}"""
) )
return ipv4 return ipv4
@ -40,13 +41,12 @@ def double_check_capsul_address(db_model, email, id, ipv4):
@bp.route("/") @bp.route("/")
@account_required @account_required
def index(): def index():
db_model = get_model() vms = get_vms()
vms = db_model.list_vms_for_account(email=session["account"])
# for now we are going to check the IP according to the virt model # for now we are going to check the IP according to the virt model
# on every request. this could be done by a background job and cached later on... # on every request. this could be done by a background job and cached later on...
for vm in vms: for vm in vms:
vm["ipv4"] = double_check_capsul_address(db_model, session["account"], vm["id"], vm["ipv4"]) vm["ipv4"] = double_check_capsul_address(vm["id"], vm["ipv4"])
vms = list(map( vms = list(map(
lambda x: dict( lambda x: dict(
@ -65,13 +65,12 @@ def index():
@bp.route("/<string:id>") @bp.route("/<string:id>")
@account_required @account_required
def detail(id): def detail(id):
db_model = get_model() vm = get_model().get_vm_detail(email=session["account"], id=id)
vm = db_model.get_vm_detail(email=session["account"], id=id)
if vm is None: if vm is None:
return abort(404, f"{id} doesn't exist.") return abort(404, f"{id} doesn't exist.")
vm["ipv4"] = double_check_capsul_address(db_model, session["account"], vm["id"], vm["ipv4"]) vm["ipv4"] = double_check_capsul_address(vm["id"], vm["ipv4"])
vm["created"] = vm['created'].strftime("%b %d %Y %H:%M") vm["created"] = vm['created'].strftime("%b %d %Y %H:%M")
vm["ssh_public_keys"] = ", ".join(vm["ssh_public_keys"]) if len(vm["ssh_public_keys"]) > 0 else "<deleted>" vm["ssh_public_keys"] = ", ".join(vm["ssh_public_keys"]) if len(vm["ssh_public_keys"]) > 0 else "<deleted>"
@ -81,10 +80,9 @@ def detail(id):
@bp.route("/create", methods=("GET", "POST")) @bp.route("/create", methods=("GET", "POST"))
@account_required @account_required
def create(): def create():
db_model = get_model() vm_sizes = get_model().vm_sizes_dict()
vm_sizes = db_model.vm_sizes_dict() operating_systems = get_model().operating_systems_dict()
operating_systems = db_model.operating_systems_dict() ssh_public_keys = get_model().list_ssh_public_keys_for_account(session["account"])
ssh_public_keys = db_model.list_ssh_public_keys_for_account(session["account"])
errors = list() errors = list()
created_os = None created_os = None
@ -125,7 +123,7 @@ def create():
if len(errors) == 0: if len(errors) == 0:
id = makeCapsulId() id = makeCapsulId()
db_model.create_vm( get_model().create_vm(
email=session["account"], email=session["account"],
id=id, id=id,
size=size, size=size,
@ -158,7 +156,6 @@ def create():
@bp.route("/ssh", methods=("GET", "POST")) @bp.route("/ssh", methods=("GET", "POST"))
@account_required @account_required
def ssh_public_keys(): def ssh_public_keys():
db_model = get_model()
errors = list() errors = list()
if request.method == "POST": if request.method == "POST":
@ -179,29 +176,58 @@ def ssh_public_keys():
if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$", content): if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$", content):
errors.append("Content must match \"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$\"") errors.append("Content must match \"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$\"")
if db_model.ssh_public_key_name_exists(session["account"], name): if get_model().ssh_public_key_name_exists(session["account"], name):
errors.append("A key with that name already exists") errors.append("A key with that name already exists")
if len(errors) == 0: if len(errors) == 0:
db_model.create_ssh_public_key(session["account"], name, content) get_model().create_ssh_public_key(session["account"], name, content)
elif method == "DELETE": elif method == "DELETE":
if len(errors) == 0: if len(errors) == 0:
db_model.delete_ssh_public_key(session["account"], name) get_model().delete_ssh_public_key(session["account"], name)
for error in errors: for error in errors:
flash(error) flash(error)
keys_list=list(map( keys_list=list(map(
lambda x: dict(name=x['name'], content=f"{x['content'][:20]}...{x['content'][len(x['content'])-20:]}"), lambda x: dict(name=x['name'], content=f"{x['content'][:20]}...{x['content'][len(x['content'])-20:]}"),
db_model.list_ssh_public_keys_for_account(session["account"]) get_model().list_ssh_public_keys_for_account(session["account"])
)) ))
return render_template("ssh-public-keys.html", ssh_public_keys=keys_list, has_ssh_public_keys=len(keys_list) > 0) return render_template("ssh-public-keys.html", ssh_public_keys=keys_list, has_ssh_public_keys=len(keys_list) > 0)
def get_vms():
if 'user_vms' not in g:
g.user_vms = get_model().list_vms_for_account(session["account"])
return g.user_vms
@bp.route("/billing") def get_payments():
if 'user_payments' not in g:
g.user_payments = get_model().list_payments_for_account(session["account"])
return g.user_payments
def get_account_balance():
average_number_of_days_in_a_month = 30.44
vm_cost_dollars = 0.0
for vm in get_vms():
end_datetime = vm["deleted"] if vm["deleted"] else datetime.now()
vm_months = ( end_datetime - vm["created"] ).days / average_number_of_days_in_a_month
vm_cost_dollars += vm_months * vm["dollars_per_month"]
payment_total = sum(map(lambda x: x["dollars"], get_payments()))
@bp.route("/account-balance")
@account_required @account_required
def faq(): def account_balance():
return render_template("billing.html") errors = list()
payments = get_payments()
return render_template("account-balance.html")

View File

@ -108,7 +108,7 @@ def init_app(app):
def get_model(): def get_model():
if 'model' not in g: if 'db_model' not in g:
connection = current_app.config['PSYCOPG2_CONNECTION_POOL'].getconn() connection = current_app.config['PSYCOPG2_CONNECTION_POOL'].getconn()
cursor = connection.cursor() cursor = connection.cursor()
g.db_model = DBModel(connection, cursor) g.db_model = DBModel(connection, cursor)
@ -116,11 +116,11 @@ def get_model():
def close_db(e=None): def close_db(e=None):
model = g.pop("model", None) db_model = g.pop("db_model", None)
if model is not None: if db_model is not None:
model.cursor.close() db_model.cursor.close()
current_app.config['PSYCOPG2_CONNECTION_POOL'].putconn(model.connection) current_app.config['PSYCOPG2_CONNECTION_POOL'].putconn(db_model.connection)
def my_exec_info_message(exec_info): def my_exec_info_message(exec_info):
return "{}: {}".format(".".join([exec_info[0].__module__, exec_info[0].__name__]), exec_info[1]) return "{}: {}".format(".".join([exec_info[0].__module__, exec_info[0].__name__]), exec_info[1])

View File

@ -80,12 +80,13 @@ class DBModel:
def list_vms_for_account(self, email): def list_vms_for_account(self, email):
self.cursor.execute(""" self.cursor.execute("""
SELECT vms.id, vms.last_seen_ipv4, vms.last_seen_ipv6, vms.size, vms.os, vms.created, vms.deleted SELECT vms.id, vms.last_seen_ipv4, vms.last_seen_ipv6, vms.size, vms.os, vms.created, vms.deleted, vm_sizes.dollars_per_month
FROM vms WHERE vms.email = %s""", FROM vms JOIN vm_sizes on vms.size = vm_sizes.id
WHERE vms.email = %s""",
(email, ) (email, )
) )
return list(map( return list(map(
lambda x: dict(id=x[0], ipv4=x[1], ipv6=x[2], size=x[3], os=x[4], created=x[5], deleted=x[6]), lambda x: dict(id=x[0], ipv4=x[1], ipv6=x[2], size=x[3], os=x[4], created=x[5], deleted=x[6], dollars_per_month=x[7]),
self.cursor.fetchall() self.cursor.fetchall()
)) ))
@ -138,3 +139,15 @@ class DBModel:
vm["ssh_public_keys"] = list(map( lambda x: x[0], self.cursor.fetchall() )) vm["ssh_public_keys"] = list(map( lambda x: x[0], self.cursor.fetchall() ))
return vm return vm
def list_payments_for_account(self, email):
self.cursor.execute("""
SELECT payments.id, payments.dollars, payments.created
FROM payments WHERE payments.email = %s""",
(email, )
)
return list(map(
lambda x: dict(id=x[0], dollars=x[1], created=x[2]),
self.cursor.fetchall()
))

View File

@ -103,8 +103,8 @@ input, textarea, select, label {
input, select, textarea { input, select, textarea {
outline: 0; outline: 0;
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
border-radius: 0.5em;
color: #bdc7b8; color: #bdc7b8;
background-color: #bdc7b805;
} }
select { select {
@ -122,14 +122,9 @@ select {
padding-right: 2em; padding-right: 2em;
} }
input, textarea { input[type=text], textarea {
background: none;
}
input[type=text] {
font: calc(0.40rem + 1vmin) monospace; font: calc(0.40rem + 1vmin) monospace;
border: 0; border: 1px solid #777e73;
border-bottom: 1px solid #777e73;
outline: 0; outline: 0;
} }
@ -147,10 +142,10 @@ input[type=checkbox] {
margin: 0; margin: 0;
} }
input[type=submit], select, textarea { input[type=submit], select {
font: calc(0.40rem + 1vmin) monospace; font: calc(0.40rem + 1vmin) monospace;
border: 1px solid #777e73; border: 1px solid #777e73;
background-color: #bdc7b810; border-radius: 0.5em;
} }
input[type=submit], select { input[type=submit], select {

View File

@ -0,0 +1,14 @@
{% extends 'base.html' %}
{% block title %}Account Balance{% endblock %}
{% block content %}
<div class="third-margin">
<h1>Account Balance</h1>
</div>
<div class="half-margin">
</div>
{% endblock %}
{% block pagesource %}/templates/create-capsul.html{% endblock %}

View File

@ -28,7 +28,7 @@
{% if session["account"] %} {% if session["account"] %}
<a href="/console">Capsuls</a> <a href="/console">Capsuls</a>
<a href="/console/ssh">SSH Public Keys</a> <a href="/console/ssh">SSH Public Keys</a>
<a href="/console/billing">Account Balance</a> <a href="/console/account-balance">Account Balance</a>
{% endif %} {% endif %}
<a href="/support">Support</a> <a href="/support">Support</a>

View File

@ -1,6 +1,6 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}Create{% endblock %} {% block title %}{{ vm['id'] }}{% endblock %}
{% block content %} {% block content %}
<div class="third-margin"> <div class="third-margin">

View File

@ -1,6 +1,6 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}Console{% endblock %} {% block title %}Capsuls{% endblock %}
{% block content %} {% block content %}
<div class="third-margin"> <div class="third-margin">

View File

@ -69,7 +69,7 @@ f1-xxx $57.58 8 16G 25G 16TB
</form> </form>
{% else %} {% else %}
<p>You don't have any ssh public keys yet.</p> <p>You don't have any ssh public keys yet.</p>
<p>You must <a href="/console/ssh/upload">upload one</a> before you can create a Capsul.</p> <p>You must <a href="/console/ssh">upload one</a> before you can create a Capsul.</p>
{% endif %} {% endif %}
{% endif %} {% endif %}