SSH keys CRUD working
This commit is contained in:
parent
18e6a1b141
commit
452f236c6b
@ -1,3 +1,4 @@
|
|||||||
|
import re
|
||||||
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
|
||||||
@ -24,7 +25,62 @@ def makeCapsulId():
|
|||||||
@bp.route("/")
|
@bp.route("/")
|
||||||
@account_required
|
@account_required
|
||||||
def index():
|
def index():
|
||||||
return render_template("console.html", vms=get_model().list_vms_for_account(session["account"]))
|
return render_template("capsuls.html", vms=get_model().list_vms_for_account(session["account"]))
|
||||||
|
|
||||||
|
@bp.route("/ssh", methods=("GET", "POST"))
|
||||||
|
@account_required
|
||||||
|
def ssh_public_keys():
|
||||||
|
db_model = get_model()
|
||||||
|
error = None
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
method = request.form["method"]
|
||||||
|
|
||||||
|
name = request.form["name"]
|
||||||
|
if not name or len(name.strip()) < 1:
|
||||||
|
error = "Name is required"
|
||||||
|
elif not re.match(r"^[0-9A-Za-z_ -]+$", name):
|
||||||
|
error = "Name must match \"^[0-9A-Za-z_ -]+$\""
|
||||||
|
|
||||||
|
if method == "POST":
|
||||||
|
content = request.form["content"]
|
||||||
|
if not content or len(content.strip()) < 1:
|
||||||
|
error = "Content is required"
|
||||||
|
else:
|
||||||
|
content = content.replace("\r", "").replace("\n", "")
|
||||||
|
if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@ -]+$", content):
|
||||||
|
error = "Content must match \"^(ssh|ecdsa)-[0-9A-Za-z+/_=@ -]+$\""
|
||||||
|
|
||||||
|
if db_model.ssh_public_key_name_exists(session["account"], name):
|
||||||
|
error = "A key with that name already exists"
|
||||||
|
|
||||||
|
if error is None:
|
||||||
|
db_model.create_ssh_public_key(session["account"], name, content)
|
||||||
|
|
||||||
|
elif method == "DELETE":
|
||||||
|
|
||||||
|
if error is None:
|
||||||
|
db_model.delete_ssh_public_key(session["account"], name)
|
||||||
|
|
||||||
|
if error:
|
||||||
|
flash(error)
|
||||||
|
|
||||||
|
# keys_list=
|
||||||
|
# for key in keys_list:
|
||||||
|
# if len(key['content']) > 40:
|
||||||
|
# print(key['content'])
|
||||||
|
# print(f"{key['content'][:20]}...{key['content'][len(key['content'])-20:]}")
|
||||||
|
# key["content"] =
|
||||||
|
|
||||||
|
# return
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"ssh-public-keys.html",
|
||||||
|
ssh_public_keys=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"])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@bp.route("/create", methods=("GET", "POST"))
|
@bp.route("/create", methods=("GET", "POST"))
|
||||||
@account_required
|
@account_required
|
||||||
@ -64,8 +120,11 @@ def create():
|
|||||||
memory=vm_sizes[size].memory
|
memory=vm_sizes[size].memory
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if error:
|
||||||
|
flash(error)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"create.html",
|
"create-capsul.html",
|
||||||
ssh_public_keys=ssh_public_keys,
|
ssh_public_keys=ssh_public_keys,
|
||||||
operating_systems=operating_systems,
|
operating_systems=operating_systems,
|
||||||
vm_sizes=vm_sizes
|
vm_sizes=vm_sizes
|
||||||
|
@ -55,12 +55,29 @@ class DBModel:
|
|||||||
return vmSizes
|
return vmSizes
|
||||||
|
|
||||||
def list_ssh_public_keys_for_account(self, email):
|
def list_ssh_public_keys_for_account(self, email):
|
||||||
self.cursor.execute("SELECT name, content FROM ssh_public_keys WHERE email = %s", (email, ))
|
self.cursor.execute("SELECT name, content, created FROM ssh_public_keys WHERE email = %s", (email, ))
|
||||||
return map(
|
return map(
|
||||||
lambda x: dict(name=x[0], content=x[1]),
|
lambda x: dict(name=x[0], content=x[1], created=x[2]),
|
||||||
self.cursor.fetchall()
|
self.cursor.fetchall()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def ssh_public_key_name_exists(self, email, name):
|
||||||
|
self.cursor.execute( "SELECT name FROM ssh_public_keys where email = %s AND name = %s", (email, name) )
|
||||||
|
return len(self.cursor.fetchall()) > 0
|
||||||
|
|
||||||
|
def create_ssh_public_key(self, email, name, content):
|
||||||
|
self.cursor.execute("""
|
||||||
|
INSERT INTO ssh_public_keys (email, name, content)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
""",
|
||||||
|
(email, name, content)
|
||||||
|
)
|
||||||
|
self.connection.commit()
|
||||||
|
|
||||||
|
def delete_ssh_public_key(self, email, name):
|
||||||
|
self.cursor.execute( "DELETE FROM ssh_public_keys where email = %s AND name = %s", (email, name) )
|
||||||
|
self.connection.commit()
|
||||||
|
|
||||||
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, os_images.description, vms.created, vms.deleted
|
SELECT vms.id, vms.last_seen_ipv4, vms.last_seen_ipv6, vms.size, os_images.description, vms.created, vms.deleted
|
||||||
|
@ -23,6 +23,7 @@ CREATE TABLE ssh_public_keys (
|
|||||||
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
|
created TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
PRIMARY KEY (email, name)
|
PRIMARY KEY (email, name)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -42,8 +43,8 @@ CREATE TABLE vm_ssh_public_key (
|
|||||||
ssh_public_key_name TEXT NOT NULL,
|
ssh_public_key_name TEXT NOT NULL,
|
||||||
email TEXT NOT NULL,
|
email TEXT NOT NULL,
|
||||||
vm_id TEXT NOT NULL,
|
vm_id TEXT NOT NULL,
|
||||||
FOREIGN KEY (email, ssh_public_key_name) REFERENCES ssh_public_keys(email, name) ON DELETE RESTRICT,
|
FOREIGN KEY (email, ssh_public_key_name) REFERENCES ssh_public_keys(email, name) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (email, vm_id) REFERENCES vms(email, id) ON DELETE RESTRICT,
|
FOREIGN KEY (email, vm_id) REFERENCES vms(email, id) ON DELETE CASCADE,
|
||||||
PRIMARY KEY (email, vm_id, ssh_public_key_name)
|
PRIMARY KEY (email, vm_id, ssh_public_key_name)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -78,4 +79,7 @@ VALUES ('f1-s', 5.33, 512, 1, 500),
|
|||||||
('f1-xx', 29.66, 8192, 4, 8000),
|
('f1-xx', 29.66, 8192, 4, 8000),
|
||||||
('f1-xxx', 57.58, 16384, 8, 16000);
|
('f1-xxx', 57.58, 16384, 8, 16000);
|
||||||
|
|
||||||
|
INSERT INTO accounts (email)
|
||||||
|
VALUES ('forest.n.johnson@gmail.com');
|
||||||
|
|
||||||
UPDATE schemaversion SET version = 2;
|
UPDATE schemaversion SET version = 2;
|
@ -93,11 +93,11 @@ label.align {
|
|||||||
min-width: 10em;
|
min-width: 10em;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select, label {
|
input, textarea, select, label {
|
||||||
margin: 0.5em;
|
margin: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select {
|
input, select, textarea {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
padding: 0.25em 0.5em;
|
padding: 0.25em 0.5em;
|
||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
@ -118,7 +118,7 @@ select {
|
|||||||
background-size: 0.5em;
|
background-size: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input, textarea {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,18 +126,31 @@ input[type=text] {
|
|||||||
font: calc(0.40rem + 1vmin) monospace;
|
font: calc(0.40rem + 1vmin) monospace;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px solid #777e73;
|
border-bottom: 1px solid #777e73;
|
||||||
min-width: 20em;
|
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=submit], select {
|
input[type=text], textarea {
|
||||||
|
min-width: 20em;
|
||||||
|
}
|
||||||
|
input[type=text].expand, textarea.expand {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
height: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=submit], select, textarea {
|
||||||
font: calc(0.40rem + 1vmin) monospace;
|
font: calc(0.40rem + 1vmin) monospace;
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid #777e73;
|
border: 1px solid #777e73;
|
||||||
background-color: #bdc7b810;
|
background-color: #bdc7b810;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
input[type=submit], select {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5 {
|
h1, h2, h3, h4, h5 {
|
||||||
font-size:calc(0.40rem + 1vmin);
|
font-size:calc(0.40rem + 1vmin);
|
||||||
margin: initial;
|
margin: initial;
|
||||||
@ -156,6 +169,13 @@ ul li {
|
|||||||
border: 1px solid #777e73;
|
border: 1px solid #777e73;
|
||||||
background: #bdc7b810;
|
background: #bdc7b810;
|
||||||
}
|
}
|
||||||
|
.break-word {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dim {
|
||||||
|
color: #777e73bb;
|
||||||
|
}
|
||||||
|
|
||||||
footer, p {
|
footer, p {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@ -172,3 +192,7 @@ footer {
|
|||||||
font-size: 1.8em;
|
font-size: 1.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.smalltext {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
|
|
||||||
{% if session["account"] %}
|
{% if session["account"] %}
|
||||||
<a href="/console/">Console</a>
|
<a href="/console/">Console</a>
|
||||||
<a href="/console/billing">Billing</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<a href="/support">Support</a>
|
<a href="/support">Support</a>
|
||||||
|
37
capsulflask/templates/capsuls.html
Normal file
37
capsulflask/templates/capsuls.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{% extends 'console-base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Console{% endblock %}
|
||||||
|
{% block consoletitle %}Console{% endblock %}
|
||||||
|
|
||||||
|
{% block consolecontent %}
|
||||||
|
{% 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 %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block pagesource %}/templates/capsuls.html{% endblock %}
|
17
capsulflask/templates/console-base.html
Normal file
17
capsulflask/templates/console-base.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="third-margin">
|
||||||
|
<h1>{% block consoletitle %}{% endblock %}</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">
|
||||||
|
{% block consolecontent %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -1,49 +0,0 @@
|
|||||||
{% 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 %}
|
|
@ -1,12 +1,10 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'console-base.html' %}
|
||||||
|
|
||||||
{% block title %}Create{% endblock %}
|
{% block title %}Create{% endblock %}
|
||||||
|
{% block consoletitle %}Console - Create Capsul{% endblock %}
|
||||||
|
|
||||||
|
{% block consolecontent %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="half-margin">
|
|
||||||
<h1>CONSOLE - CREATE CAPSUL</h1>
|
|
||||||
</div>
|
|
||||||
<div class="half-margin">
|
|
||||||
{% if ssh_public_keys[0] is defined %}
|
{% if ssh_public_keys[0] is defined %}
|
||||||
<pre>
|
<pre>
|
||||||
CAPSUL SIZES
|
CAPSUL SIZES
|
||||||
@ -22,7 +20,7 @@ f1-xxx $57.58 8 16G 25G 16TB
|
|||||||
|
|
||||||
* net is calculated as a per-month average
|
* net is calculated as a per-month average
|
||||||
* all VMs come standard with one public IPv4 addr</pre>
|
* all VMs come standard with one public IPv4 addr</pre>
|
||||||
</div>
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div class="row justify-start">
|
<div class="row justify-start">
|
||||||
<label class="align" for="size">Capsul Size</label>
|
<label class="align" for="size">Capsul Size</label>
|
||||||
@ -65,4 +63,4 @@ f1-xxx $57.58 8 16G 25G 16TB
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block pagesource %}/templates/console.html{% endblock %}
|
{% block pagesource %}/templates/create-capsul.html{% endblock %}
|
@ -141,5 +141,5 @@ $ doas su -</pre>
|
|||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block pagesource %}/templates/changelog.html{% endblock %}
|
{% block pagesource %}/templates/faq.html{% endblock %}
|
||||||
|
|
||||||
|
55
capsulflask/templates/ssh-public-keys.html
Normal file
55
capsulflask/templates/ssh-public-keys.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{% extends 'console-base.html' %}
|
||||||
|
|
||||||
|
{% block title %}SSH Public Keys{% endblock %}
|
||||||
|
{% block consoletitle %}Console - SSH Public Keys{% endblock %}
|
||||||
|
|
||||||
|
{% block consolecontent %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if ssh_public_keys[0] is defined %} <hr/> {% endif %}
|
||||||
|
|
||||||
|
{% for ssh_public_key in ssh_public_keys %}
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="method" value="DELETE"></input>
|
||||||
|
<input type="hidden" name="name" value="{{ ssh_public_key['name'] }}"></input>
|
||||||
|
<div class="row">
|
||||||
|
<span class="code">{{ ssh_public_key['name'] }}</span>
|
||||||
|
<span class="dim">{{ ssh_public_key['content'] }}</span>
|
||||||
|
<input type="submit" value="Delete">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if ssh_public_keys[0] is defined %} <hr/> {% endif %}
|
||||||
|
|
||||||
|
<div class="third-margin">
|
||||||
|
<h1>UPLOAD A NEW SSH KEY</h1>
|
||||||
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="method" value="POST"></input>
|
||||||
|
<div class="row justify-start">
|
||||||
|
<label class="align" for="name">Name</label>
|
||||||
|
<input type="text" id="name" name="name"></input>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-start">
|
||||||
|
<label class="align" for="content">Content</label>
|
||||||
|
<textarea class="expand" id="content" name="content"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="smalltext">
|
||||||
|
<p>Paste the contents of your SSH public key file here.
|
||||||
|
( Something like <span class='code'>~/.ssh/id_rsa.pub</span> )
|
||||||
|
</p><p>
|
||||||
|
The contents of this file should look similar to
|
||||||
|
<span class='code'>ssh-rsa AAAAC3NzaC1l...Yqv== me@my-computer</span>
|
||||||
|
</p><p>
|
||||||
|
If you wish, you can <a href="https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key">
|
||||||
|
generate a new SSH key pair using the ssh-keygen utility</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-end">
|
||||||
|
<input type="submit" value="Upload">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block pagesource %}/templates/ssh-public-keys.html{% endblock %}
|
Loading…
Reference in New Issue
Block a user