From 30464ac8e58b1c49726d491c1d2c3f6325020a68 Mon Sep 17 00:00:00 2001 From: forest Date: Wed, 13 May 2020 00:28:53 -0500 Subject: [PATCH] metrics are working!!! --- Pipfile | 2 + Pipfile.lock | 91 ++++++- capsulflask/__init__.py | 6 +- capsulflask/console.py | 16 +- capsulflask/db.py | 6 +- capsulflask/db_model.py | 4 + capsulflask/landing.py | 11 - capsulflask/metrics.py | 234 ++++++++++++++++++ capsulflask/payment_btcpay.py | 12 + .../02_up_accounts_vms_etc.sql | 3 + capsulflask/static/style.css | 35 +++ capsulflask/templates/account-balance.html | 53 ++-- capsulflask/templates/btcpay.html | 38 +++ capsulflask/templates/capsul-detail.html | 55 +++- capsulflask/templates/capsuls.html | 51 ++-- capsulflask/templates/changelog.html | 2 +- capsulflask/templates/create-capsul.html | 11 +- capsulflask/templates/display-metric.html | 31 +++ capsulflask/templates/faq.html | 2 +- capsulflask/templates/index.html | 1 + capsulflask/templates/login-landing.html | 2 +- capsulflask/templates/login.html | 2 +- capsulflask/templates/ssh-public-keys.html | 6 +- capsulflask/templates/stripe.html | 4 +- capsulflask/templates/support.html | 4 +- 25 files changed, 601 insertions(+), 81 deletions(-) create mode 100644 capsulflask/metrics.py create mode 100644 capsulflask/payment_btcpay.py create mode 100644 capsulflask/templates/btcpay.html create mode 100644 capsulflask/templates/display-metric.html diff --git a/Pipfile b/Pipfile index 2d0e3f2..6338378 100644 --- a/Pipfile +++ b/Pipfile @@ -25,6 +25,8 @@ typed-ast = "==1.4.1" Werkzeug = "==1.0.1" wrapt = "==1.12.1" stripe = "*" +matplotlib = "*" +requests = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 062d3fe..4f2d9cc 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "8355b0bc9024432220ab4f05b2997f827af534691520249c6ff8bb2db9014dc8" + "sha256": "30ae24715ab8f45ada9d367e0a7ef6e1b1a6a5f88cb0dd6bb6c3cd3b385f817f" }, "pipfile-spec": 6, "requires": { @@ -53,6 +53,13 @@ "index": "pypi", "version": "==7.1.2" }, + "cycler": { + "hashes": [ + "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", + "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8" + ], + "version": "==0.10.0" + }, "flask": { "hashes": [ "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", @@ -107,6 +114,27 @@ "index": "pypi", "version": "==2.11.2" }, + "kiwisolver": { + "hashes": [ + "sha256:03662cbd3e6729f341a97dd2690b271e51a67a68322affab12a5b011344b973c", + "sha256:18d749f3e56c0480dccd1714230da0f328e6e4accf188dd4e6884bdd06bf02dd", + "sha256:247800260cd38160c362d211dcaf4ed0f7816afb5efe56544748b21d6ad6d17f", + "sha256:443c2320520eda0a5b930b2725b26f6175ca4453c61f739fef7a5847bd262f74", + "sha256:4eadb361baf3069f278b055e3bb53fa189cea2fd02cb2c353b7a99ebb4477ef1", + "sha256:556da0a5f60f6486ec4969abbc1dd83cf9b5c2deadc8288508e55c0f5f87d29c", + "sha256:603162139684ee56bcd57acc74035fceed7dd8d732f38c0959c8bd157f913fec", + "sha256:60a78858580761fe611d22127868f3dc9f98871e6fdf0a15cc4203ed9ba6179b", + "sha256:7cc095a4661bdd8a5742aaf7c10ea9fac142d76ff1770a0f84394038126d8fc7", + "sha256:c31bc3c8e903d60a1ea31a754c72559398d91b5929fcb329b1c3a3d3f6e72113", + "sha256:c955791d80e464da3b471ab41eb65cf5a40c15ce9b001fdc5bbc241170de58ec", + "sha256:d069ef4b20b1e6b19f790d00097a5d5d2c50871b66d10075dab78938dc2ee2cf", + "sha256:d52b989dc23cdaa92582ceb4af8d5bcc94d74b2c3e64cd6785558ec6a879793e", + "sha256:e586b28354d7b6584d8973656a7954b1c69c93f708c0c07b77884f91640b7657", + "sha256:efcf3397ae1e3c3a4a0a0636542bcad5adad3b1dd3e8e629d0b6e201347176c8", + "sha256:fccefc0d36a38c57b7bd233a9b485e2f1eb71903ca7ad7adacad6c28a56d62d2" + ], + "version": "==1.2.0" + }, "lazy-object-proxy": { "hashes": [ "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", @@ -173,6 +201,26 @@ "index": "pypi", "version": "==1.1.1" }, + "matplotlib": { + "hashes": [ + "sha256:2466d4dddeb0f5666fd1e6736cc5287a4f9f7ae6c1a9e0779deff798b28e1d35", + "sha256:282b3fc8023c4365bad924d1bb442ddc565c2d1635f210b700722776da466ca3", + "sha256:4bb50ee4755271a2017b070984bcb788d483a8ce3132fab68393d1555b62d4ba", + "sha256:56d3147714da5c7ac4bc452d041e70e0e0b07c763f604110bd4e2527f320b86d", + "sha256:7a9baefad265907c6f0b037c8c35a10cf437f7708c27415a5513cf09ac6d6ddd", + "sha256:aae7d107dc37b4bb72dcc45f70394e6df2e5e92ac4079761aacd0e2ad1d3b1f7", + "sha256:af14e77829c5b5d5be11858d042d6f2459878f8e296228c7ea13ec1fd308eb68", + "sha256:c1cf735970b7cd424502719b44288b21089863aaaab099f55e0283a721aaf781", + "sha256:ce378047902b7a05546b6485b14df77b2ff207a0054e60c10b5680132090c8ee", + "sha256:d35891a86a4388b6965c2d527b9a9f9e657d9e110b0575ca8a24ba0d4e34b8fc", + "sha256:e06304686209331f99640642dee08781a9d55c6e32abb45ed54f021f46ccae47", + "sha256:e20ba7fb37d4647ac38f3c6d8672dd8b62451ee16173a0711b37ba0ce42bf37d", + "sha256:f4412241e32d0f8d3713b68d3ca6430190a5e8a7c070f1c07d7833d8c5264398", + "sha256:ffe2f9cdcea1086fc414e82f42271ecf1976700b8edd16ca9d376189c6d93aee" + ], + "index": "pypi", + "version": "==3.2.1" + }, "mccabe": { "hashes": [ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", @@ -189,6 +237,32 @@ "index": "pypi", "version": "==2.0.0" }, + "numpy": { + "hashes": [ + "sha256:00d7b54c025601e28f468953d065b9b121ddca7fff30bed7be082d3656dd798d", + "sha256:02ec9582808c4e48be4e93cd629c855e644882faf704bc2bd6bbf58c08a2a897", + "sha256:0e6f72f7bb08f2f350ed4408bb7acdc0daba637e73bce9f5ea2b207039f3af88", + "sha256:1be2e96314a66f5f1ce7764274327fd4fb9da58584eaff00b5a5221edefee7d6", + "sha256:2466fbcf23711ebc5daa61d28ced319a6159b260a18839993d871096d66b93f7", + "sha256:2b573fcf6f9863ce746e4ad00ac18a948978bb3781cffa4305134d31801f3e26", + "sha256:3f0dae97e1126f529ebb66f3c63514a0f72a177b90d56e4bce8a0b5def34627a", + "sha256:50fb72bcbc2cf11e066579cb53c4ca8ac0227abb512b6cbc1faa02d1595a2a5d", + "sha256:57aea170fb23b1fd54fa537359d90d383d9bf5937ee54ae8045a723caa5e0961", + "sha256:709c2999b6bd36cdaf85cf888d8512da7433529f14a3689d6e37ab5242e7add5", + "sha256:7d59f21e43bbfd9a10953a7e26b35b6849d888fc5a331fa84a2d9c37bd9fe2a2", + "sha256:904b513ab8fbcbdb062bed1ce2f794ab20208a1b01ce9bd90776c6c7e7257032", + "sha256:96dd36f5cdde152fd6977d1bbc0f0561bccffecfde63cd397c8e6033eb66baba", + "sha256:9933b81fecbe935e6a7dc89cbd2b99fea1bf362f2790daf9422a7bb1dc3c3085", + "sha256:bbcc85aaf4cd84ba057decaead058f43191cc0e30d6bc5d44fe336dc3d3f4509", + "sha256:dccd380d8e025c867ddcb2f84b439722cf1f23f3a319381eac45fd077dee7170", + "sha256:e22cd0f72fc931d6abc69dc7764484ee20c6a60b0d0fee9ce0426029b1c1bdae", + "sha256:ed722aefb0ebffd10b32e67f48e8ac4c5c4cf5d3a785024fdf0e9eb17529cd9d", + "sha256:efb7ac5572c9a57159cf92c508aad9f856f1cb8e8302d7fdb99061dbe52d712c", + "sha256:efdba339fffb0e80fcc19524e4fdbda2e2b5772ea46720c44eaac28096d60720", + "sha256:f22273dd6a403ed870207b853a856ff6327d5cbce7a835dfa0645b3fc00273ec" + ], + "version": "==1.18.4" + }, "psycopg2": { "hashes": [ "sha256:132efc7ee46a763e68a815f4d26223d9c679953cd190f1f218187cb60decf535", @@ -216,11 +290,26 @@ "index": "pypi", "version": "==2.5.2" }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "version": "==2.4.7" + }, + "python-dateutil": { + "hashes": [ + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + ], + "version": "==2.8.1" + }, "requests": { "hashes": [ "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], + "index": "pypi", "markers": "python_version >= '3.0'", "version": "==2.23.0" }, diff --git a/capsulflask/__init__.py b/capsulflask/__init__.py index 9e01212..0503997 100644 --- a/capsulflask/__init__.py +++ b/capsulflask/__init__.py @@ -24,6 +24,8 @@ app.config.from_mapping( MAIL_PASSWORD=os.environ.get("MAIL_PASSWORD", default=""), MAIL_DEFAULT_SENDER=os.environ.get("MAIL_DEFAULT_SENDER", default="forest@nullhex.com"), + PROMETHEUS_URL=os.environ.get("PROMETHEUS_URL", default="https://prometheus.cyberia.club"), + STRIPE_API_VERSION=os.environ.get("STRIPE_API_VERSION", default="2020-03-02"), STRIPE_SECRET_KEY=os.environ.get("STRIPE_SECRET_KEY", default=""), STRIPE_PUBLISHABLE_KEY=os.environ.get("STRIPE_PUBLISHABLE_KEY", default="") @@ -40,12 +42,14 @@ from capsulflask import db db.init_app(app) -from capsulflask import auth, landing, console, payment_stripe +from capsulflask import auth, landing, console, payment_stripe, payment_btcpay, metrics app.register_blueprint(landing.bp) app.register_blueprint(auth.bp) app.register_blueprint(console.bp) app.register_blueprint(payment_stripe.bp) +app.register_blueprint(payment_btcpay.bp) +app.register_blueprint(metrics.bp) app.add_url_rule("/", endpoint="index") diff --git a/capsulflask/console.py b/capsulflask/console.py index cf46c5a..4df63c2 100644 --- a/capsulflask/console.py +++ b/capsulflask/console.py @@ -12,8 +12,8 @@ from flask_mail import Message from werkzeug.exceptions import abort from nanoid import generate +from capsulflask.metrics import durations as metric_durations from capsulflask.auth import account_required - from capsulflask.db import get_model, my_exec_info_message bp = Blueprint("console", __name__, url_prefix="/console") @@ -53,7 +53,7 @@ def index(): ipv4=(x['ipv4'] if x['ipv4'] else "..booting.."), ipv4_status=("ok" if x['ipv4'] else "waiting-pulse"), os=x['os'], - created=x['created'].strftime("%b %d %Y %H:%M") + created=x['created'].strftime("%b %d %Y") ), vms )) @@ -63,6 +63,11 @@ def index(): @bp.route("/") @account_required def detail(id): + + duration=request.args.get('duration') + if not duration: + duration = "5m" + vm = get_model().get_vm_detail(email=session["account"], id=id) if vm is None: @@ -72,7 +77,12 @@ def detail(id): 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 "" - return render_template("capsul-detail.html", vm=vm) + return render_template( + "capsul-detail.html", + vm=vm, + durations=list(map(lambda x: x.strip("_"), metric_durations.keys())), + duration=duration + ) @bp.route("/create", methods=("GET", "POST")) diff --git a/capsulflask/db.py b/capsulflask/db.py index e0b0f16..7ad21c5 100644 --- a/capsulflask/db.py +++ b/capsulflask/db.py @@ -27,7 +27,11 @@ def init_app(app): schemaMigrationsPath = join(app.root_path, 'schema_migrations') print("loading schema migration scripts from {}".format(schemaMigrationsPath)) for filename in listdir(schemaMigrationsPath): - key = re.search(r"^\d+_(up|down)", filename).group() + result = re.search(r"^\d+_(up|down)", filename) + if not result: + print(f"schemaVersion {filename} must match ^\d+_(up|down). exiting.") + exit(1) + key = result.group() with open(join(schemaMigrationsPath, filename), 'rb') as file: schemaMigrations[key] = file.read().decode("utf8") diff --git a/capsulflask/db_model.py b/capsulflask/db_model.py index 9390c34..a14f334 100644 --- a/capsulflask/db_model.py +++ b/capsulflask/db_model.py @@ -111,6 +111,10 @@ class DBModel: ) self.connection.commit() + # def vm_exists(self, email, id): + # self.cursor.execute("SELECT id FROM vms WHERE email = %s AND id = %s ", (email, id)) + # return len(self.cursor.fetchall()) > 0 + def get_vm_detail(self, email, id): self.cursor.execute(""" SELECT vms.id, vms.last_seen_ipv4, vms.last_seen_ipv6, os_images.description, vms.created, vms.deleted, diff --git a/capsulflask/landing.py b/capsulflask/landing.py index 2ae4cee..ae20272 100644 --- a/capsulflask/landing.py +++ b/capsulflask/landing.py @@ -1,16 +1,5 @@ from flask import Blueprint -from flask import flash -from flask import current_app -from flask import g -from flask import redirect -from flask import url_for -from flask import request -from flask import session from flask import render_template -from flask_mail import Message -from werkzeug.exceptions import abort - -from capsulflask.db import get_model bp = Blueprint("landing", __name__, url_prefix="/") diff --git a/capsulflask/metrics.py b/capsulflask/metrics.py new file mode 100644 index 0000000..f4db056 --- /dev/null +++ b/capsulflask/metrics.py @@ -0,0 +1,234 @@ +import matplotlib.ticker as ticker +import matplotlib.pyplot as pyplot +import matplotlib.dates as mdates +import requests +#import json +from datetime import datetime +from io import BytesIO +from flask import Blueprint +from flask import current_app +from flask import session +from flask import render_template, make_response +from werkzeug.exceptions import abort + +from capsulflask.db import get_model +from capsulflask.auth import account_required + +bp = Blueprint("metrics", __name__, url_prefix="/metrics") + +durations = dict( + _5m=[60*5, 15], + _1h=[60*60, 60], + _1d=[60*60*24, 60*20], + _30d=[60*60*24*30, 60*300] +) +sizes = dict( + s=[0.77, 0.23, 4], + m=[1, 1, 2], + l=[6, 4, 1], +) + +green = (121/255, 240/255, 50/255) +blue = (70/255, 150/255, 255/255) +red = (255/255, 50/255, 8/255) + +@bp.route("/html///") +@account_required +def display_metric(metric, capsulid, duration): + vm = get_model().get_vm_detail(session["account"], capsulid) + + return render_template( + "display-metric.html", + vm=vm, + duration=duration, + durations=list(map(lambda x: x.strip("_"), durations.keys())), + metric=metric + ) + + +@bp.route("////") +@account_required +def metric_png(metric, capsulid, duration, size): + result = get_plot_bytes(metric, capsulid, duration, size) + + if result[0] != 200: + abort(result[0]) + + response = make_response(result[1]) + response.headers.set('Content-Type', 'image/png') + return response + + +def get_plot_bytes(metric, capsulid, duration, size): + + duration = f"_{duration}" + + if duration not in durations: + return (404, None) + + if size not in sizes: + return (404, None) + + vm = get_model().get_vm_detail(session["account"], capsulid) + + if not vm: + return (404, None) + + now_unix = int(datetime.strftime(datetime.now(), "%s")) + duration_seconds = durations[duration][0] + interval_seconds = durations[duration][1] * sizes[size][2] + if interval_seconds < 30: + interval_seconds = 30 + + metric_queries = dict( + cpu=f"irate(libvirtd_domain_info_cpu_time_seconds_total{{domain='{capsulid}'}}[30s])", + memory=f"libvirtd_domain_info_memory_usage_bytes{{domain='{capsulid}'}}", + network_in=f"rate(libvirtd_domain_interface_stats_receive_bytes_total{{domain='{capsulid}'}}[{interval_seconds}s])", + network_out=f"rate(libvirtd_domain_interface_stats_transmit_bytes_total{{domain='{capsulid}'}}[{interval_seconds}s])", + disk=f"rate(libvirtd_domain_block_stats_read_bytes_total{{domain='{capsulid}'}}[{interval_seconds}s])%2Brate(libvirtd_domain_block_stats_write_bytes_total{{domain='{capsulid}'}}[{interval_seconds}s])", + ) + + scales = dict( + cpu=vm["vcpus"], + memory=vm["memory_mb"]*1024*1024, + network_in=1024*1024*2, + network_out=1024*200, + disk=1024*1024*2, + ) + + if metric not in metric_queries: + return (404, None) + + range_and_interval = f"start={now_unix-duration_seconds}&end={now_unix}&step={interval_seconds}" + + prometheus_range_url = f"{current_app.config['PROMETHEUS_URL']}/api/v1/query_range" + + #print(f"{prometheus_range_url}?query={metric_queries[metric]}&{range_and_interval}") + + prometheus_response = requests.get(f"{prometheus_range_url}?query={metric_queries[metric]}&{range_and_interval}") + if prometheus_response.status_code >= 300: + return (502, None) + + + + time_series_data = list(map( + lambda x: (datetime.fromtimestamp(x[0]), float(x[1])), + prometheus_response.json()["data"]["result"][0]["values"] + )) + + plot_bytes = draw_plot_bytes(time_series_data, scale=scales[metric], size_x=sizes[size][0], size_y=sizes[size][1]) + + return (200, plot_bytes) + + +def draw_plot_bytes(data, scale, size_x=3, size_y=1): + + pyplot.style.use("seaborn-dark") + fig, my_plot = pyplot.subplots(figsize=(size_x, size_y)) + + # x=range(1, 15) + # y=[1,4,6,8,4,5,3,2,4,1,5,6,8,7] + + divide_by = 1 + unit = "" + + if scale > 1024 and scale < 1024*1024*1024: + divide_by = 1024*1024 + unit = "MB" + if scale > 1024*1024*1024: + divide_by = 1024*1024*1024 + unit = "GB" + + scale /= divide_by + + if scale > 10: + my_plot.get_yaxis().set_major_formatter( ticker.FuncFormatter(lambda x, p: "{}{}".format(int(x), unit)) ) + elif scale > 1: + my_plot.get_yaxis().set_major_formatter( ticker.FuncFormatter(lambda x, p: "{:.1f}{}".format(x, unit)) ) + else: + my_plot.get_yaxis().set_major_formatter( ticker.FuncFormatter(lambda x, p: "{:.2f}{}".format(x, unit)) ) + + x=list(map(lambda x: x[0], data)) + y=list(map(lambda x: x[1]/divide_by, data)) + + minutes = float((x[len(x)-1] - x[0]).total_seconds())/float(60) + hours = minutes/float(60) + days = hours/float(24) + + day_locator = mdates.WeekdayLocator() + minute_locator = mdates.MinuteLocator() + ten_minute_locator = mdates.MinuteLocator(interval=10) + hour_locator = mdates.HourLocator(interval=6) + hour_minute_formatter = mdates.DateFormatter('%H:%M') + day_formatter = mdates.DateFormatter('%b %d') + + if minutes < 10: + my_plot.xaxis.set_major_locator(minute_locator) + my_plot.xaxis.set_major_formatter(hour_minute_formatter) + elif hours < 2: + my_plot.xaxis.set_major_locator(ten_minute_locator) + my_plot.xaxis.set_major_formatter(hour_minute_formatter) + elif days < 2: + my_plot.xaxis.set_major_locator(hour_locator) + my_plot.xaxis.set_major_formatter(hour_minute_formatter) + else: + my_plot.xaxis.set_major_locator(day_locator) + my_plot.xaxis.set_major_formatter(day_formatter) + + + average=(sum(y)/len(y))/scale + average=average*1.25+0.1 + + bg_color=color_gradient(average) + + average -= 0.1 + + fill_color=color_gradient(average) + highlight_color=lerp_rgb_tuples(fill_color, (1,1,1), 0.5) + + my_plot.fill_between( x, scale, color=bg_color, alpha=0.13) + my_plot.fill_between( x, y, color=highlight_color, alpha=0.3) + my_plot.plot(x, y, 'r-', color=highlight_color) + + my_plot.patch.set_facecolor('red') + my_plot.patch.set_alpha(0.5) + + if size_y < 4: + my_plot.set_yticks([0, scale*0.5, scale]) + my_plot.set_ylim(0, scale) + + my_plot.xaxis.label.set_color(highlight_color) + my_plot.tick_params(axis='x', colors=highlight_color) + my_plot.yaxis.label.set_color(highlight_color) + my_plot.tick_params(axis='y', colors=highlight_color) + + if size_x < 4: + my_plot.set_xticklabels([]) + if size_y < 1: + my_plot.set_yticklabels([]) + + image_binary = BytesIO() + fig.savefig(image_binary, transparent=True, bbox_inches="tight", pad_inches=0.05) + return image_binary.getvalue() + + +def lerp_rgb_tuples(a, b, lerp): + if lerp < 0: + lerp = 0 + if lerp > 1: + lerp = 1 + return ( + a[0]*(1.0-lerp)+b[0]*lerp, + a[1]*(1.0-lerp)+b[1]*lerp, + a[2]*(1.0-lerp)+b[2]*lerp + ) + +def color_gradient(value): + if value < 0: + value = 0 + if value > 1: + value = 1 + if value < 0.5: + return lerp_rgb_tuples(green, blue, value*2) + else: + return lerp_rgb_tuples(blue, red, (value-0.5)*2) \ No newline at end of file diff --git a/capsulflask/payment_btcpay.py b/capsulflask/payment_btcpay.py new file mode 100644 index 0000000..9c10a1d --- /dev/null +++ b/capsulflask/payment_btcpay.py @@ -0,0 +1,12 @@ +from flask import Blueprint +from flask import render_template + +from capsulflask.db import get_model +from capsulflask.auth import account_required + +bp = Blueprint("btcpay", __name__, url_prefix="/btcpay") + +@bp.route("/") +@account_required +def index(): + return render_template("btcpay.html") diff --git a/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql b/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql index 00f1ba2..50136bb 100644 --- a/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql +++ b/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql @@ -92,4 +92,7 @@ VALUES ('forest.n.johnson@gmail.com'); INSERT INTO payments (email, dollars, created) VALUES ('forest.n.johnson@gmail.com', 20.00, TO_TIMESTAMP('2020-05-05','YYYY-MM-DD')); +INSERT INTO vms (id, email, os, size) +VALUES ('capsul-yi9ffqbjly', 'forest.n.johnson@gmail.com', 'alpine311', 'f1-xx'); + UPDATE schemaversion SET version = 2; \ No newline at end of file diff --git a/capsulflask/static/style.css b/capsulflask/static/style.css index 73a64c9..ddfd3e0 100644 --- a/capsulflask/static/style.css +++ b/capsulflask/static/style.css @@ -61,12 +61,15 @@ main { } .full-margin { + width: 100%; margin: 3rem 0; } .half-margin { + width: 100%; margin: 1.5rem 0; } .third-margin { + width: 100%; margin: 1rem 0; } @@ -79,6 +82,12 @@ main { .wrap { flex-wrap: wrap; } +.row.grid-large > div { + flex: 1 1 20em; +} +.row.grid-small > div { + flex: 0 0 8em; +} .justify-start { justify-content: flex-start; } @@ -163,6 +172,12 @@ input[type=number] { -moz-appearance: textfield; } +input[type=image].submit { + background-color: #0000; +} + + + h1, h2, h3, h4, h5 { font-size:calc(0.40rem + 1vmin); margin: initial; @@ -174,6 +189,10 @@ ul li { margin: 0.5em 0; } +hr { + width: 100%; +} + table{ border-collapse: collapse; } @@ -188,6 +207,15 @@ table.small td, table.small th { font: calc(0.35rem + 0.83vmin) monospace; padding: 0.1em 1em; } +table.small td.metrics { + padding: 0; +} +td.metrics img { + margin-left: -20px; + margin-right: -20px; + margin-top: -5px; + margin-bottom: -5px; +} th { border-right: 4px solid #241e1e; text-align: left; @@ -204,6 +232,13 @@ td { to { color: rgba(221, 169, 56, 0.2); } } +div.metric { + display: flex; + align-items: last baseline; + flex-direction: column; + margin: 0.7em; +} + .code { display: inline-block; padding: 0.5em 1.2em; diff --git a/capsulflask/templates/account-balance.html b/capsulflask/templates/account-balance.html index 4062f8f..a7fccdd 100644 --- a/capsulflask/templates/account-balance.html +++ b/capsulflask/templates/account-balance.html @@ -3,7 +3,7 @@ {% block title %}Account Balance{% endblock %} {% block content %} -
+

Account Balance: ${{ account_balance }}

@@ -11,7 +11,7 @@
{% if has_payments %}
-
+

Payments

@@ -52,33 +52,36 @@ {% if has_vms %} -
+

Capsuls Billed

- - - - - - - - - - - - - {% for vm in vms_billed %} +
+
idcreateddeleted$/monthmonths$ billed
+ - - - - - - + + + + + + - {% endfor %} - -
{{ vm["id"] }}{{ vm["created"] }}{{ vm["deleted"] }}${{ vm["dollars_per_month"] }}{{ vm["months"] }}${{ vm["dollars"] }}idcreateddeleted$/monthmonths$ billed
+ + + {% for vm in vms_billed %} + + {{ vm["id"] }} + {{ vm["created"] }} + {{ vm["deleted"] }} + ${{ vm["dollars_per_month"] }} + {{ vm["months"] }} + ${{ vm["dollars"] }} + + {% endfor %} + + +
+ {% endif %}
{% endblock %} diff --git a/capsulflask/templates/btcpay.html b/capsulflask/templates/btcpay.html new file mode 100644 index 0000000..8f001ea --- /dev/null +++ b/capsulflask/templates/btcpay.html @@ -0,0 +1,38 @@ + + +{% extends 'base.html' %} + +{% block title %}Pay with Cryptocurrency{% endblock %} + +{% block content %} +
+

Pay with Cryptocurrency

+
+
+ +
+ + +
+ + + +
+
+
+ + +{% endblock %} + +{% block pagesource %}/templates/btcpay.html{% endblock %} diff --git a/capsulflask/templates/capsul-detail.html b/capsulflask/templates/capsul-detail.html index ff192c0..afae791 100644 --- a/capsulflask/templates/capsul-detail.html +++ b/capsulflask/templates/capsul-detail.html @@ -3,10 +3,11 @@ {% block title %}{{ vm['id'] }}{% endblock %} {% block content %} -
+

{{ vm['id'] }}

-
+
+
{{ vm['created'] }} @@ -47,7 +48,57 @@ {{ vm['ssh_public_keys'] }}
+
+
+
+
+ +
+
+

cpu

+ + + +
+ +
+

memory

+ + + +
+ +
+

network_in

+ + + +
+ +
+

network_out

+ + + +
+ +
+

disk

+ + + +
{% endblock %} diff --git a/capsulflask/templates/capsuls.html b/capsulflask/templates/capsuls.html index 6e1b887..2b4ace7 100644 --- a/capsulflask/templates/capsuls.html +++ b/capsulflask/templates/capsuls.html @@ -3,7 +3,7 @@ {% block title %}Capsuls{% endblock %} {% block content %} -
+

Capsuls

@@ -11,30 +11,37 @@ - - - - - - - - - - - - {% for vm in vms %} +
+
idsizeipv4oscreated
+ - - - - - + + + + + + + - {% endfor %} - -
{{ vm["id"] }}{{ vm["size"] }}{{ vm["ipv4"] }}{{ vm["os"] }}{{ vm["created"] }}idsizecpumemipv4oscreated
+ + + {% for vm in vms %} + + {{ vm["id"] }} + {{ vm["size"] }} + + + {{ vm["ipv4"] }} + {{ vm["os"] }} + {{ vm["created"] }} + + {% endfor %} + + +
+ {% else %} - You don't have any Capsuls running. Create one today! +
You don't have any Capsuls running. Create one today!
{% endif %}
{% endblock %} diff --git a/capsulflask/templates/changelog.html b/capsulflask/templates/changelog.html index bbfb449..89deb19 100644 --- a/capsulflask/templates/changelog.html +++ b/capsulflask/templates/changelog.html @@ -3,7 +3,7 @@ {% block title %}Changelog{% endblock %} {% block content %} -

CHANGELOG

+

CHANGELOG

{% endblock %} {% block subcontent %}

diff --git a/capsulflask/templates/create-capsul.html b/capsulflask/templates/create-capsul.html index 5962bc9..6cb9682 100644 --- a/capsulflask/templates/create-capsul.html +++ b/capsulflask/templates/create-capsul.html @@ -3,10 +3,11 @@ {% block title %}Create{% endblock %} {% block content %} -

+

Create Capsul

-
+
+ {% if created_os %}

Your Capsul was successfully created! You should already see it listed on the @@ -46,7 +47,8 @@ * net is calculated as a per-month average * all VMs come standard with one public IPv4 addr

-      Your account balance: ${{ account_balance }}
+ Your account balance: ${{ account_balance }} +
@@ -79,12 +81,13 @@
+
{% endif %} {% endif %} {% endif %} -
+
{% endblock %} {% block subcontent %} diff --git a/capsulflask/templates/display-metric.html b/capsulflask/templates/display-metric.html new file mode 100644 index 0000000..ec23ed5 --- /dev/null +++ b/capsulflask/templates/display-metric.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} + +{% block title %}{{ vm['id'] }} - {{ metric }} - {{ duration }}{% endblock %} + +{% block content %} + +
+

{{ vm['id'] }} - {{ metric }} - {{ duration }}

+
+ +
+
+

cpu

+ +
+
+ +{% endblock %} + +{% block pagesource %}/templates/display-metric.html{% endblock %} diff --git a/capsulflask/templates/faq.html b/capsulflask/templates/faq.html index 8e3c5dd..874273d 100644 --- a/capsulflask/templates/faq.html +++ b/capsulflask/templates/faq.html @@ -3,7 +3,7 @@ {% block title %}FAQ{% endblock %} {% block content %} -

Frequently Asked Questions

+

Frequently Asked Questions

{% endblock %} {% block subcontent %} diff --git a/capsulflask/templates/index.html b/capsulflask/templates/index.html index c5ab27b..783e6a8 100644 --- a/capsulflask/templates/index.html +++ b/capsulflask/templates/index.html @@ -12,6 +12,7 @@ \ / `"` + Simple, fast, private compute by cyberia.club {% endblock %} diff --git a/capsulflask/templates/login-landing.html b/capsulflask/templates/login-landing.html index 5a32a26..83f56ee 100644 --- a/capsulflask/templates/login-landing.html +++ b/capsulflask/templates/login-landing.html @@ -3,7 +3,7 @@ {% block title %}check your email{% endblock %} {% block content %} -
Check your email. A login link has been sent to {{ email }}
+
Check your email. A login link has been sent to {{ email }}
{% endblock %} {% block pagesource %}/templates/login-landing.html{% endblock %} \ No newline at end of file diff --git a/capsulflask/templates/login.html b/capsulflask/templates/login.html index 3fbe8a1..deff0cb 100644 --- a/capsulflask/templates/login.html +++ b/capsulflask/templates/login.html @@ -3,7 +3,7 @@ {% block title %}login{% endblock %} {% block content %} -
+

LOGIN

diff --git a/capsulflask/templates/ssh-public-keys.html b/capsulflask/templates/ssh-public-keys.html index fc569bc..4941196 100644 --- a/capsulflask/templates/ssh-public-keys.html +++ b/capsulflask/templates/ssh-public-keys.html @@ -3,10 +3,10 @@ {% block title %}SSH Public Keys{% endblock %} {% block content %} -
+

SSH PUBLIC KEYS

-
+
{% if has_ssh_public_keys %}
{% endif %} {% for ssh_public_key in ssh_public_keys %} @@ -51,7 +51,7 @@
-
+
{% endblock %} {% block pagesource %}/templates/ssh-public-keys.html{% endblock %} diff --git a/capsulflask/templates/stripe.html b/capsulflask/templates/stripe.html index 7fc11b6..2c74101 100644 --- a/capsulflask/templates/stripe.html +++ b/capsulflask/templates/stripe.html @@ -1,11 +1,11 @@ {% extends 'base.html' %} -{% block title %}Capsuls{% endblock %} +{% block title %}Pay with Stripe{% endblock %} {% block head %}{% endblock %} {% block content %} -
+

PAY WITH STRIPE

diff --git a/capsulflask/templates/support.html b/capsulflask/templates/support.html index 609a093..ed16bd2 100644 --- a/capsulflask/templates/support.html +++ b/capsulflask/templates/support.html @@ -3,10 +3,10 @@ {% block title %}Support{% endblock %} {% block content %} -
+

SUPPORT

-
+ {% endblock %}