diff --git a/capsulflask/console.py b/capsulflask/console.py index 987e2d5..7ea2966 100644 --- a/capsulflask/console.py +++ b/capsulflask/console.py @@ -1,5 +1,6 @@ import re import sys +from datetime import datetime from flask import Blueprint from flask import flash from flask import current_app @@ -23,16 +24,16 @@ def makeCapsulId(): lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10) return f"capsul-{lettersAndNumbers}" -def double_check_capsul_address(db_model, email, id, ipv4): +def double_check_capsul_address(id, ipv4): try: result = current_app.config["VIRTUALIZATION_MODEL"].get(id) if result.ipv4 != 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: print(f""" - error occurred in list capsuls endpoint while trying to grab ip address of {vm['id']} - via the virtualization model: {my_exec_info_message(sys.exc_info())}""" + the virtualization model threw an error in double_check_capsul_address of {id}: + {my_exec_info_message(sys.exc_info())}""" ) return ipv4 @@ -40,13 +41,12 @@ def double_check_capsul_address(db_model, email, id, ipv4): @bp.route("/") @account_required def index(): - db_model = get_model() - vms = db_model.list_vms_for_account(email=session["account"]) + vms = get_vms() # 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... 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( lambda x: dict( @@ -65,13 +65,12 @@ def index(): @bp.route("/") @account_required def detail(id): - db_model = get_model() - vm = db_model.get_vm_detail(email=session["account"], id=id) + vm = get_model().get_vm_detail(email=session["account"], id=id) if vm is None: 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["ssh_public_keys"] = ", ".join(vm["ssh_public_keys"]) if len(vm["ssh_public_keys"]) > 0 else "" @@ -81,10 +80,9 @@ def detail(id): @bp.route("/create", methods=("GET", "POST")) @account_required def create(): - db_model = get_model() - vm_sizes = db_model.vm_sizes_dict() - operating_systems = db_model.operating_systems_dict() - ssh_public_keys = db_model.list_ssh_public_keys_for_account(session["account"]) + vm_sizes = get_model().vm_sizes_dict() + operating_systems = get_model().operating_systems_dict() + ssh_public_keys = get_model().list_ssh_public_keys_for_account(session["account"]) errors = list() created_os = None @@ -125,7 +123,7 @@ def create(): if len(errors) == 0: id = makeCapsulId() - db_model.create_vm( + get_model().create_vm( email=session["account"], id=id, size=size, @@ -158,7 +156,6 @@ def create(): @bp.route("/ssh", methods=("GET", "POST")) @account_required def ssh_public_keys(): - db_model = get_model() errors = list() if request.method == "POST": @@ -179,29 +176,58 @@ def ssh_public_keys(): if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$", content): 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") 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": 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: flash(error) keys_list=list(map( 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) +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 -def faq(): - return render_template("billing.html") \ No newline at end of file +def account_balance(): + errors = list() + payments = get_payments() + + + + return render_template("account-balance.html") \ No newline at end of file diff --git a/capsulflask/db.py b/capsulflask/db.py index a1299d2..e0b0f16 100644 --- a/capsulflask/db.py +++ b/capsulflask/db.py @@ -108,7 +108,7 @@ def init_app(app): def get_model(): - if 'model' not in g: + if 'db_model' not in g: connection = current_app.config['PSYCOPG2_CONNECTION_POOL'].getconn() cursor = connection.cursor() g.db_model = DBModel(connection, cursor) @@ -116,11 +116,11 @@ def get_model(): def close_db(e=None): - model = g.pop("model", None) + db_model = g.pop("db_model", None) - if model is not None: - model.cursor.close() - current_app.config['PSYCOPG2_CONNECTION_POOL'].putconn(model.connection) + if db_model is not None: + db_model.cursor.close() + current_app.config['PSYCOPG2_CONNECTION_POOL'].putconn(db_model.connection) def my_exec_info_message(exec_info): return "{}: {}".format(".".join([exec_info[0].__module__, exec_info[0].__name__]), exec_info[1]) \ No newline at end of file diff --git a/capsulflask/db_model.py b/capsulflask/db_model.py index fd796d0..a19023a 100644 --- a/capsulflask/db_model.py +++ b/capsulflask/db_model.py @@ -80,12 +80,13 @@ class DBModel: def list_vms_for_account(self, email): self.cursor.execute(""" - SELECT vms.id, vms.last_seen_ipv4, vms.last_seen_ipv6, vms.size, vms.os, vms.created, vms.deleted - FROM vms WHERE vms.email = %s""", + 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 JOIN vm_sizes on vms.size = vm_sizes.id + WHERE vms.email = %s""", (email, ) ) 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() )) @@ -138,3 +139,15 @@ class DBModel: vm["ssh_public_keys"] = list(map( lambda x: x[0], self.cursor.fetchall() )) 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() + )) + diff --git a/capsulflask/static/style.css b/capsulflask/static/style.css index 2e73077..656fbf9 100644 --- a/capsulflask/static/style.css +++ b/capsulflask/static/style.css @@ -103,8 +103,8 @@ input, textarea, select, label { input, select, textarea { outline: 0; padding: 0.25em 0.5em; - border-radius: 0.5em; color: #bdc7b8; + background-color: #bdc7b805; } select { @@ -122,14 +122,9 @@ select { padding-right: 2em; } -input, textarea { - background: none; -} - -input[type=text] { +input[type=text], textarea { font: calc(0.40rem + 1vmin) monospace; - border: 0; - border-bottom: 1px solid #777e73; + border: 1px solid #777e73; outline: 0; } @@ -147,10 +142,10 @@ input[type=checkbox] { margin: 0; } -input[type=submit], select, textarea { +input[type=submit], select { font: calc(0.40rem + 1vmin) monospace; border: 1px solid #777e73; - background-color: #bdc7b810; + border-radius: 0.5em; } input[type=submit], select { diff --git a/capsulflask/templates/account-balance.html b/capsulflask/templates/account-balance.html new file mode 100644 index 0000000..5df714e --- /dev/null +++ b/capsulflask/templates/account-balance.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block title %}Account Balance{% endblock %} + +{% block content %} +
+

Account Balance

+
+
+ +
+{% endblock %} + +{% block pagesource %}/templates/create-capsul.html{% endblock %} diff --git a/capsulflask/templates/base.html b/capsulflask/templates/base.html index 2045872..90df517 100644 --- a/capsulflask/templates/base.html +++ b/capsulflask/templates/base.html @@ -28,7 +28,7 @@ {% if session["account"] %} Capsuls SSH Public Keys - Account Balance + Account Balance {% endif %} Support diff --git a/capsulflask/templates/capsul-detail.html b/capsulflask/templates/capsul-detail.html index 7416d2f..ff192c0 100644 --- a/capsulflask/templates/capsul-detail.html +++ b/capsulflask/templates/capsul-detail.html @@ -1,6 +1,6 @@ {% extends 'base.html' %} -{% block title %}Create{% endblock %} +{% block title %}{{ vm['id'] }}{% endblock %} {% block content %}
diff --git a/capsulflask/templates/capsuls.html b/capsulflask/templates/capsuls.html index 68aa5e8..6e1b887 100644 --- a/capsulflask/templates/capsuls.html +++ b/capsulflask/templates/capsuls.html @@ -1,6 +1,6 @@ {% extends 'base.html' %} -{% block title %}Console{% endblock %} +{% block title %}Capsuls{% endblock %} {% block content %}
diff --git a/capsulflask/templates/create-capsul.html b/capsulflask/templates/create-capsul.html index a9768b3..f9fa8d3 100644 --- a/capsulflask/templates/create-capsul.html +++ b/capsulflask/templates/create-capsul.html @@ -69,7 +69,7 @@ f1-xxx $57.58 8 16G 25G 16TB {% else %}

You don't have any ssh public keys yet.

-

You must upload one before you can create a Capsul.

+

You must upload one before you can create a Capsul.

{% endif %} {% endif %}