forked from 3wordchant/capsul-flask
starting to build console controller and views
This commit is contained in:
parent
7932db90d5
commit
18e6a1b141
@ -1,8 +1,10 @@
|
||||
import os
|
||||
|
||||
from flask import Flask
|
||||
from flask_mail import Mail
|
||||
from flask import render_template
|
||||
import os
|
||||
|
||||
from capsulflask import virt_model
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
@ -21,6 +23,7 @@ def create_app():
|
||||
)
|
||||
|
||||
app.config['FLASK_MAIL_INSTANCE'] = Mail(app)
|
||||
app.config['VIRTUALIZATION_MODEL'] = virt_model.MockVirtualization()
|
||||
|
||||
from capsulflask import db
|
||||
|
||||
@ -29,8 +32,8 @@ def create_app():
|
||||
from capsulflask import auth, landing, console
|
||||
|
||||
app.register_blueprint(landing.bp)
|
||||
|
||||
app.register_blueprint(auth.bp)
|
||||
app.register_blueprint(console.bp)
|
||||
|
||||
app.add_url_rule("/", endpoint="index")
|
||||
|
||||
|
@ -71,11 +71,11 @@ def login():
|
||||
|
||||
@bp.route("/magic/<string:token>", methods=("GET", ))
|
||||
def magiclink(token):
|
||||
email = get_model().consumeToken(token)
|
||||
email = get_model().consume_token(token)
|
||||
if email is not None:
|
||||
session.clear()
|
||||
session["account"] = email
|
||||
return redirect(url_for("index"))
|
||||
return redirect(url_for("console.index"))
|
||||
else:
|
||||
abort(404, f"Token {token} doesn't exist or has already been used.")
|
||||
|
||||
|
@ -9,8 +9,69 @@ from flask import session
|
||||
from flask import render_template
|
||||
from flask_mail import Message
|
||||
from werkzeug.exceptions import abort
|
||||
from nanoid import generate
|
||||
|
||||
from capsulflask.auth import account_required
|
||||
|
||||
from capsulflask.db import get_model
|
||||
|
||||
bp = Blueprint("console", __name__, url_prefix="/console")
|
||||
|
||||
def makeCapsulId():
|
||||
lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10)
|
||||
return f"capsul-{lettersAndNumbers}"
|
||||
|
||||
@bp.route("/")
|
||||
@account_required
|
||||
def index():
|
||||
return render_template("console.html", vms=get_model().list_vms_for_account(session["account"]))
|
||||
|
||||
@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"])
|
||||
error = None
|
||||
|
||||
if request.method == "POST":
|
||||
size = request.form["size"]
|
||||
os = request.form["os"]
|
||||
if not size:
|
||||
error = "Size is required"
|
||||
elif size not in vm_sizes:
|
||||
error = f"Invalid size {size}"
|
||||
|
||||
if not os:
|
||||
error = "OS is required"
|
||||
elif os not in operating_systems:
|
||||
error = f"Invalid os {os}"
|
||||
|
||||
if error is None:
|
||||
id = makeCapsulId()
|
||||
db_model.create_vm(
|
||||
email=session["account"],
|
||||
id=id,
|
||||
size=size,
|
||||
os=os
|
||||
)
|
||||
current_app.config["VIRTUALIZATION_MODEL"].create(
|
||||
email = session["account"],
|
||||
id=id,
|
||||
template_image_file_name=operating_systems[os].template_image_file_name,
|
||||
vcpus=vm_sizes[size].vcpus,
|
||||
memory=vm_sizes[size].memory
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"create.html",
|
||||
ssh_public_keys=ssh_public_keys,
|
||||
operating_systems=operating_systems,
|
||||
vm_sizes=vm_sizes
|
||||
)
|
||||
|
||||
@bp.route("/billing")
|
||||
@account_required
|
||||
def faq():
|
||||
return render_template("billing.html")
|
@ -22,7 +22,7 @@ class DBModel:
|
||||
|
||||
return token
|
||||
|
||||
def consumeToken(self, token):
|
||||
def consume_token(self, token):
|
||||
self.cursor.execute("SELECT email FROM login_tokens WHERE token = %s", (token, ))
|
||||
rows = self.cursor.fetchall()
|
||||
if len(rows) > 0:
|
||||
@ -32,7 +32,52 @@ class DBModel:
|
||||
return email
|
||||
return None
|
||||
|
||||
def allVmIds(self,):
|
||||
def all_vm_ids(self,):
|
||||
self.cursor.execute("SELECT id FROM vms")
|
||||
return map(lambda x: x[0], self.cursor.fetchall())
|
||||
|
||||
def operating_systems_dict(self,):
|
||||
self.cursor.execute("SELECT id, template_image_file_name, description FROM os_images")
|
||||
|
||||
operatingSystems = dict()
|
||||
for row in self.cursor.fetchall():
|
||||
operatingSystems[row[0]] = dict(template_image_file_name=row[1], description=row[2])
|
||||
|
||||
return operatingSystems
|
||||
|
||||
def vm_sizes_dict(self,):
|
||||
self.cursor.execute("SELECT id, dollars_per_month, vcpus, memory_mb, bandwidth_gb_per_month FROM vm_sizes")
|
||||
|
||||
vmSizes = dict()
|
||||
for row in self.cursor.fetchall():
|
||||
vmSizes[row[0]] = dict(dollars_per_month=row[1], vcpus=row[2], memory_mb=row[3], bandwidth_gb_per_month=row[4])
|
||||
|
||||
return vmSizes
|
||||
|
||||
def list_ssh_public_keys_for_account(self, email):
|
||||
self.cursor.execute("SELECT name, content FROM ssh_public_keys WHERE email = %s", (email, ))
|
||||
return map(
|
||||
lambda x: dict(name=x[0], content=x[1]),
|
||||
self.cursor.fetchall()
|
||||
)
|
||||
|
||||
def list_vms_for_account(self, email):
|
||||
self.cursor.execute("""
|
||||
SELECT vms.id, vms.last_seen_ipv4, vms.last_seen_ipv6, vms.size, os_images.description, vms.created, vms.deleted
|
||||
FROM vms JOIN os_images on os_images.id = vms.os
|
||||
WHERE vms.email = %s""",
|
||||
(email, )
|
||||
)
|
||||
return 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]),
|
||||
self.cursor.fetchall()
|
||||
)
|
||||
|
||||
def create_vm(self, email, id, size, os):
|
||||
self.cursor.execute("""
|
||||
INSERT INTO vms (email, id, size, os)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
""",
|
||||
(email, id, size, os)
|
||||
)
|
||||
self.connection.commit()
|
@ -20,11 +20,10 @@ CREATE TABLE vm_sizes (
|
||||
);
|
||||
|
||||
CREATE TABLE ssh_public_keys (
|
||||
id SERIAL PRIMARY KEY,
|
||||
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
||||
name TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
UNIQUE (id, email)
|
||||
PRIMARY KEY (email, name)
|
||||
);
|
||||
|
||||
CREATE TABLE vms (
|
||||
@ -40,12 +39,12 @@ CREATE TABLE vms (
|
||||
);
|
||||
|
||||
CREATE TABLE vm_ssh_public_key (
|
||||
ssh_public_key_id INTEGER NOT NULL,
|
||||
ssh_public_key_name TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
vm_id TEXT NOT NULL,
|
||||
FOREIGN KEY (email, ssh_public_key_id) REFERENCES ssh_public_keys(email, id) ON DELETE RESTRICT,
|
||||
FOREIGN KEY (email, ssh_public_key_name) REFERENCES ssh_public_keys(email, name) ON DELETE RESTRICT,
|
||||
FOREIGN KEY (email, vm_id) REFERENCES vms(email, id) ON DELETE RESTRICT,
|
||||
PRIMARY KEY (email, vm_id, ssh_public_key_id)
|
||||
PRIMARY KEY (email, vm_id, ssh_public_key_name)
|
||||
);
|
||||
|
||||
CREATE TABLE payments (
|
||||
@ -63,11 +62,11 @@ CREATE TABLE login_tokens (
|
||||
);
|
||||
|
||||
INSERT INTO os_images (id, template_image_file_name, description)
|
||||
VALUES ('debian10', 'debian-10-genericcloud-amd64-20191117-80.qcow2', 'Debian 10 (Buster)'),
|
||||
VALUES ('alpine311', 'alpine-cloud-2020-04-18.qcow2', 'Alpine Linux 3.11'),
|
||||
('ubuntu18', 'ubuntu-18.04-minimal-cloudimg-amd64.img', 'Ubuntu 18.04 LTS (Bionic Beaver)'),
|
||||
('debian10', 'debian-10-genericcloud-amd64-20191117-80.qcow2', 'Debian 10 (Buster)'),
|
||||
('centos7', 'CentOS-7-x86_64-GenericCloud.qcow2', 'CentOS 7'),
|
||||
('centos8', 'CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2', 'CentOS 8'),
|
||||
('ubuntu18', 'ubuntu-18.04-minimal-cloudimg-amd64.img', 'Ubuntu 18.04 LTS (Bionic Beaver)'),
|
||||
('alpine311', 'alpine-cloud-2020-04-18.qcow2', 'Alpine Linux 3.11'),
|
||||
('openbsd66', 'openbsd-cloud-2020-05.qcow2', 'OpenBSD 6.6'),
|
||||
('guix110', 'guixsystem-cloud-2020-05.qcow2', 'Guix System 1.1.0');
|
||||
|
||||
|
BIN
capsulflask/static/dropdown-handle.png
Normal file
BIN
capsulflask/static/dropdown-handle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 714 B |
BIN
capsulflask/static/dropdown-handle.png~
Normal file
BIN
capsulflask/static/dropdown-handle.png~
Normal file
Binary file not shown.
After Width: | Height: | Size: 714 B |
@ -26,15 +26,14 @@ a:hover, a:active, a:visited {
|
||||
color: #b5bd68;
|
||||
}
|
||||
|
||||
nav .nav-row {
|
||||
.nav-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
nav .nav-row:last-child {
|
||||
.nav-row:last-child {
|
||||
justify-content: center;
|
||||
}
|
||||
nav .nav-row:last-child a, nav .nav-row:last-child div {
|
||||
.nav-row:last-child a, .nav-row:last-child div {
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
@ -52,37 +51,77 @@ main {
|
||||
border: 1px dashed #bdc7b8;
|
||||
padding: 1rem;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.single-content {
|
||||
margin: 2rem 0;
|
||||
.full-margin {
|
||||
margin: 3rem 0;
|
||||
}
|
||||
.half-margin {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
.third-margin {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
}
|
||||
.row.wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.row.justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.row.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
input, label {
|
||||
label.align {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
input, select, label {
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
input {
|
||||
background: none;
|
||||
input, select {
|
||||
outline: 0;
|
||||
padding: 0.25em 0.5em;
|
||||
border-radius: 0.5em;
|
||||
color: #bdc7b8;
|
||||
}
|
||||
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
/*
|
||||
re-generate the following line from the source image with:
|
||||
echo "background-image: url(data:image/png;base64,$(cat capsulflask/static/dropdown-handle.png | base64 -w 0));"
|
||||
*/
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA9hAAAPYQGoP6dpAAACfElEQVRYw+2WSWgVQRCGp3tmHmLEuKEEL3owguJBjBcD8eJJiCABT4IrOQiKC6jnoJ6CYBDiQkTxiQiC4nKIntR4E70JUQQ9KahIUAg6m1/FntBvXpYZE6PCFPxUTc90/dW19HuOU0op/4K8GnzcOMN8s8BCsbVZO8hCO1AzQN6EugJa7QCWguvgMB/4f5B8DeqO73vb0JEdwBetVYPnud3Yl0yU003egep3XbclCEInG8C3OE6cMIwc3/e383yXDWuniViDI5J2rXVTFEXpq9gO4Gu6GgSB43neOsyHbNwFpkK+AHWeU3dD3hDHsf06sQO4DZ6lUYVh6CilpEvPgTNpxxYgVmA15i3KuldObZGL8QQ8Hw2geWXbW9RWMECkv8JLEgmiQvQHeLyGw+YCMWwC98hkm5Q1Fdcd8d0POuD8LA8qE/kic+otYHQafM9zgjB8jXkIPGBzMN58o/aAExxkXiblP8ANsJ/9Q+mitr/gxSeUNOHVNBMjfUFJOM0KzJviACJvDPI5QgzOZsnJpKiLYLdNXpcBy1kF1WVOXKnZgDPKU8U8Ct6b5WWgh3q32yk38h2cAichr3upJmmmYyaQmiC4SJiW8AVmJ5Bs9DG+q2SCMjIMjkPcMx6HytHRUtPTYK69TnM6dPcHKSPNtTiK6kZsyNS7OpF/lXOsZEL6qO18u7Zpn2TXeJZe2gn5/cl8qwKzvRF12dR7InkDdkD+NI9fnTcAHD4yd8Wg9EBWzNpL+SYveaEMWJlYjqoyDBuSpGYyBmSEIX9XxJ/6zTt+CeoC2GwaTmrdCfnHor7UFH5oZqN6zd2+D/Lhv/FXbj1oKf/UllLKfy0/ATtM/c/kKrmhAAAAAElFTkSuQmCC);
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom 0.65em right 0.8em;
|
||||
background-size: 0.5em;
|
||||
}
|
||||
|
||||
input {
|
||||
background: none;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
font: calc(0.40rem + 1vmin) monospace;
|
||||
border: 0;
|
||||
@ -91,14 +130,14 @@ input[type=text] {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input[type=submit] {
|
||||
input[type=submit], select {
|
||||
font: calc(0.40rem + 1vmin) monospace;
|
||||
cursor: pointer;
|
||||
border: 1px solid #777e73;
|
||||
background: #bdc7b810;
|
||||
|
||||
background-color: #bdc7b810;
|
||||
}
|
||||
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
font-size:calc(0.40rem + 1vmin);
|
||||
margin: initial;
|
||||
|
@ -9,7 +9,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div class="nav-row">
|
||||
<div class="nav-row half-margin">
|
||||
<div>
|
||||
<a href="/"><b>Capsul</b></a>💊
|
||||
</div>
|
||||
@ -21,9 +21,15 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-row">
|
||||
<div class="nav-row half-margin">
|
||||
<a href="/faq">FAQ</a>
|
||||
<a href="/changelog">Changelog</a>
|
||||
|
||||
{% if session["account"] %}
|
||||
<a href="/console/">Console</a>
|
||||
<a href="/console/billing">Billing</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="/support">Support</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -3,9 +3,7 @@
|
||||
{% block title %}Changelog{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="hero">
|
||||
<div class="single-content"><h1>CHANGELOG</h1></div>
|
||||
</div>
|
||||
<div class="full-margin"><h1>CHANGELOG</h1></div>
|
||||
{% endblock %}
|
||||
{% block subcontent %}
|
||||
<p>
|
||||
|
49
capsulflask/templates/console.html
Normal file
49
capsulflask/templates/console.html
Normal file
@ -0,0 +1,49 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Console{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="third-margin">
|
||||
<h1>CONSOLE</h1>
|
||||
</div>
|
||||
<div class="third-margin">
|
||||
<div class="nav-row">
|
||||
<a href="/console">Capsuls</a>
|
||||
<a href="/console/ssh">SSH Public Keys</a>
|
||||
<a href="/console/billing">Billing</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="third-margin">
|
||||
{% if vms[0] is defined %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>id</th>
|
||||
<th>size</th>
|
||||
<th>ipv4</th>
|
||||
<th>os</th>
|
||||
<th>created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for vm in vms %}
|
||||
<a href="/console/{{ vm['id'] }}">
|
||||
<tr>
|
||||
<td>{{ vm["id"] }}</td>
|
||||
<td>{{ vm["size"] }}</td>
|
||||
<td>{{ vm["ipv4"] }}</td>
|
||||
<td>{{ vm["os"] }}</td>
|
||||
<td>{{ vm["created"] }}</td>
|
||||
</tr>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
You don't have any Capsuls running. <a href="/console/create">Create one</a> today!
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagesource %}/templates/console.html{% endblock %}
|
68
capsulflask/templates/create.html
Normal file
68
capsulflask/templates/create.html
Normal file
@ -0,0 +1,68 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Create{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="half-margin">
|
||||
<h1>CONSOLE - CREATE CAPSUL</h1>
|
||||
</div>
|
||||
<div class="half-margin">
|
||||
{% if ssh_public_keys[0] is defined %}
|
||||
<pre>
|
||||
CAPSUL SIZES
|
||||
============
|
||||
type monthly cpus mem ssd net*
|
||||
----- ------- ---- --- --- ---
|
||||
f1-s $5.33 1 512M 25G .5TB
|
||||
f1-m $7.16 1 1024M 25G 1TB
|
||||
f1-l $8.92 1 2048M 25G 2TB
|
||||
f1-x $16.16 2 4096M 25G 4TB
|
||||
f1-xx $29.66 4 8096M 25G 8TB
|
||||
f1-xxx $57.58 8 16G 25G 16TB
|
||||
|
||||
* net is calculated as a per-month average
|
||||
* all VMs come standard with one public IPv4 addr</pre>
|
||||
</div>
|
||||
<form method="post">
|
||||
<div class="row justify-start">
|
||||
<label class="align" for="size">Capsul Size</label>
|
||||
<select id="size" name="size">
|
||||
{% for size in vm_sizes.keys() %}<option value="{{ size }}">{{ size }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="row justify-start">
|
||||
<label class="align" for="os">Operating System</label>
|
||||
<select id="os" name="os">
|
||||
{% for os_id, os in operating_systems.items() %}
|
||||
<option value="{{ os_id }}">{{ os.description }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="row justify-end">
|
||||
<input type="submit" value="Create">
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block subcontent %}
|
||||
{% if ssh_public_keys[0] is defined %}
|
||||
<p>
|
||||
Using our services implies that you agree to our terms of service & privacy policy.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://git.cyberia.club/congress/terms-of-service/plain/README">git.cyberia.club/congress/terms-of-service</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://git.cyberia.club/congress/privacy-policy/plain/README">git.cyberia.club/congress/privacy-policy</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pagesource %}/templates/console.html{% endblock %}
|
@ -3,9 +3,7 @@
|
||||
{% block title %}FAQ{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="hero">
|
||||
<div class="single-content"><h1>Frequently Asked Questions</h1></div>
|
||||
</div>
|
||||
<div class="full-margin"><h1>Frequently Asked Questions</h1></div>
|
||||
{% endblock %}
|
||||
|
||||
{% block subcontent %}
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="hero">
|
||||
<h1>CAPSUL</h1>
|
||||
<pre>
|
||||
.-.
|
||||
@ -13,20 +12,19 @@
|
||||
\ /
|
||||
`"`
|
||||
</pre>
|
||||
<span>Simple, fast, private compute by <a href="https://cyberia.club">https://cyberia.club</a></span>
|
||||
</div>
|
||||
<span>Simple, fast, private compute by <a href="https://cyberia.club">cyberia.club</a></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block subcontent %}
|
||||
<p>
|
||||
<ul>
|
||||
<li>Simply log in with your email address </li>
|
||||
<li>Low friction: simply log in with your email address and fund your account with Credit/Debit or Cryptocurrency</li>
|
||||
<li>All root disks are backed up at no charge</li>
|
||||
<li>All storage is fast, local, and solid-state</li>
|
||||
<li>All network connections are low latency</li>
|
||||
<li>Supported by amazing volunteers from Cyberia</li>
|
||||
<li>Upfront prices, no confusing billing</li>
|
||||
<li>A Minnesota non-profit organization that will never exploit you</li>
|
||||
<li>Operated by a Minnesota non-profit organization that will never exploit you</li>
|
||||
<li>We donate a portion of our proceeds to likeminded hacker groups around the globe</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
@ -3,9 +3,7 @@
|
||||
{% block title %}check your email{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="hero">
|
||||
<div class="single-content">check your email. a login link has been sent to {{ email }}</div>
|
||||
</div>
|
||||
<div class="full-margin">Check your email. A login link has been sent to {{ email }}</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagesource %}/templates/login-landing.html{% endblock %}
|
@ -3,14 +3,16 @@
|
||||
{% block title %}login{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="hero">
|
||||
<form method="post" class="single-content">
|
||||
<label for="email">Email Address</label>
|
||||
<input type="text" name="email" id="email" required>
|
||||
<input type="submit" value="Log In">
|
||||
<div class="half-margin">
|
||||
<h1>LOGIN</h1>
|
||||
</div>
|
||||
<form method="post" class="half-margin">
|
||||
<div class="row wrap">
|
||||
<label for="email">Email Address</label>
|
||||
<input type="text" name="email" id="email" required>
|
||||
<input type="submit" value="Log In">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block pagesource %}/templates/login.html{% endblock %}
|
@ -3,15 +3,12 @@
|
||||
{% block title %}Support{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="hero">
|
||||
<div class="single-content">
|
||||
<div class="half-margin">
|
||||
<h1>SUPPORT</h1>
|
||||
</div>
|
||||
|
||||
<div class="single-content">
|
||||
<a href="mailto:support@cyberia.club?subject=Please%20halp!">Click Here to Email Support</a> (support@cyberia.club)
|
||||
<div class="half-margin">
|
||||
<a href="mailto:support@cyberia.club?subject=Please%20halp!">mailto:support@cyberia.club</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block subcontent %}
|
||||
|
@ -5,15 +5,10 @@ from flask import current_app
|
||||
from time import sleep
|
||||
from os.path import join
|
||||
from subprocess import run
|
||||
from nanoid import generate
|
||||
|
||||
from capsulflask.db import get_model
|
||||
|
||||
def makeCapsulId():
|
||||
lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10)
|
||||
return f"capsul-{lettersAndNumbers}"
|
||||
|
||||
def validateCapsulId(id):
|
||||
def validate_capsul_id(id):
|
||||
if not re.match(r"^capsul-[a-z0-9]{10}$", id):
|
||||
raise ValueError(f"vm id \"{id}\" must match \"^capsul-[a-z0-9]{{10}}$\"")
|
||||
|
||||
@ -27,10 +22,10 @@ class VirtualizationInterface:
|
||||
def get(self, id: str) -> VirtualMachine:
|
||||
pass
|
||||
|
||||
def listIds(self) -> list:
|
||||
def list_ids(self) -> list:
|
||||
pass
|
||||
|
||||
def create(self, email: str, template_file_name: str, vcpus: int, memory: int, ssh_public_keys: list) -> VirtualMachine:
|
||||
def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory: int, ssh_public_keys: list) -> VirtualMachine:
|
||||
pass
|
||||
|
||||
def destroy(self, email: str, id: str):
|
||||
@ -38,14 +33,14 @@ class VirtualizationInterface:
|
||||
|
||||
class MockVirtualization(VirtualizationInterface):
|
||||
def get(self, id):
|
||||
validateCapsulId(id)
|
||||
validate_capsul_id(id)
|
||||
return VirtualMachine(id, ipv4="1.1.1.1")
|
||||
|
||||
def listIds(self) -> list:
|
||||
return get_model().allVmIds()
|
||||
def list_ids(self) -> list:
|
||||
return get_model().all_vm_ids()
|
||||
|
||||
def create(self, email: str, template_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list):
|
||||
id = makeCapsulId()
|
||||
def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list):
|
||||
validate_capsul_id(id)
|
||||
print(f"mock create: {id} for {email}")
|
||||
sleep(5)
|
||||
return VirtualMachine(id, ipv4="1.1.1.1")
|
||||
@ -56,7 +51,7 @@ class MockVirtualization(VirtualizationInterface):
|
||||
|
||||
class ShellScriptVirtualization(VirtualizationInterface):
|
||||
|
||||
def validateCompletedProcess(self, completedProcess, email=None):
|
||||
def validate_completed_process(self, completedProcess, email=None):
|
||||
emailPart = ""
|
||||
if email != None:
|
||||
emailPart = f"for {email}"
|
||||
@ -70,9 +65,9 @@ class ShellScriptVirtualization(VirtualizationInterface):
|
||||
""")
|
||||
|
||||
def get(self, id):
|
||||
validateCapsulId(id)
|
||||
validate_capsul_id(id)
|
||||
completedProcess = run([join(current_app.root_path, 'shell_scripts/get.sh'), id], capture_output=True)
|
||||
self.validateCompletedProcess(completedProcess)
|
||||
self.validate_completed_process(completedProcess)
|
||||
lines = completedProcess.stdout.splitlines()
|
||||
|
||||
if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", lines[0]):
|
||||
@ -80,16 +75,16 @@ class ShellScriptVirtualization(VirtualizationInterface):
|
||||
|
||||
return VirtualMachine(id, ipv4=lines[0])
|
||||
|
||||
def listIds(self) -> list:
|
||||
completedProcess = run([join(current_app.root_path, 'shell_scripts/listIds.sh')], capture_output=True)
|
||||
self.validateCompletedProcess(completedProcess)
|
||||
def list_ids(self) -> list:
|
||||
completedProcess = run([join(current_app.root_path, 'shell_scripts/list_ids.sh')], capture_output=True)
|
||||
self.validate_completed_process(completedProcess)
|
||||
return completedProcess.stdout.splitlines()
|
||||
|
||||
def create(self, email: str, template_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list):
|
||||
id = makeCapsulId()
|
||||
def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list):
|
||||
validate_capsul_id(id)
|
||||
|
||||
if not re.match(r"^[a-zA-Z0-9_.-]$", template_file_name):
|
||||
raise ValueError(f"template_file_name \"{template_file_name}\" must match \"^[a-zA-Z0-9_.-]$\"")
|
||||
if not re.match(r"^[a-zA-Z0-9_.-]$", template_image_file_name):
|
||||
raise ValueError(f"template_image_file_name \"{template_image_file_name}\" must match \"^[a-zA-Z0-9_.-]$\"")
|
||||
|
||||
for ssh_public_key in ssh_public_keys:
|
||||
if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@ -]+$", ssh_public_key):
|
||||
@ -106,18 +101,18 @@ class ShellScriptVirtualization(VirtualizationInterface):
|
||||
completedProcess = run([
|
||||
join(current_app.root_path, 'shell_scripts/create.sh'),
|
||||
id,
|
||||
template_file_name,
|
||||
template_image_file_name,
|
||||
str(vcpus),
|
||||
str(memory_mb),
|
||||
ssh_keys_string
|
||||
], capture_output=True)
|
||||
|
||||
self.validateCompletedProcess(completedProcess, email)
|
||||
self.validate_completed_process(completedProcess, email)
|
||||
lines = completedProcess.stdout.splitlines()
|
||||
|
||||
vmSettings = f"""
|
||||
id={id}
|
||||
template_file_name={template_file_name}
|
||||
template_image_file_name={template_image_file_name}
|
||||
vcpus={str(vcpus)}
|
||||
memory={str(memory_mb)}
|
||||
ssh_public_keys={ssh_keys_string}
|
||||
@ -149,9 +144,9 @@ class ShellScriptVirtualization(VirtualizationInterface):
|
||||
""")
|
||||
|
||||
def destroy(self, email: str, id: str):
|
||||
validateCapsulId(id)
|
||||
validate_capsul_id(id)
|
||||
completedProcess = run([join(current_app.root_path, 'shell_scripts/destroy.sh'), id], capture_output=True)
|
||||
self.validateCompletedProcess(completedProcess, email)
|
||||
self.validate_completed_process(completedProcess, email)
|
||||
lines = completedProcess.stdout.splitlines()
|
||||
|
||||
if not lines[len(lines)-1] == "success":
|
||||
|
Loading…
Reference in New Issue
Block a user