forked from 3wordchant/capsul-flask
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 flash
|
||||
from flask import current_app
|
||||
@ -24,7 +25,62 @@ def makeCapsulId():
|
||||
@bp.route("/")
|
||||
@account_required
|
||||
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"))
|
||||
@account_required
|
||||
@ -64,8 +120,11 @@ def create():
|
||||
memory=vm_sizes[size].memory
|
||||
)
|
||||
|
||||
if error:
|
||||
flash(error)
|
||||
|
||||
return render_template(
|
||||
"create.html",
|
||||
"create-capsul.html",
|
||||
ssh_public_keys=ssh_public_keys,
|
||||
operating_systems=operating_systems,
|
||||
vm_sizes=vm_sizes
|
||||
|
@ -55,12 +55,29 @@ class DBModel:
|
||||
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, ))
|
||||
self.cursor.execute("SELECT name, content, created FROM ssh_public_keys WHERE email = %s", (email, ))
|
||||
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()
|
||||
)
|
||||
|
||||
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):
|
||||
self.cursor.execute("""
|
||||
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,
|
||||
name TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (email, name)
|
||||
);
|
||||
|
||||
@ -42,8 +43,8 @@ CREATE TABLE vm_ssh_public_key (
|
||||
ssh_public_key_name TEXT NOT NULL,
|
||||
email 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, vm_id) REFERENCES vms(email, id) 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 CASCADE,
|
||||
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-xxx', 57.58, 16384, 8, 16000);
|
||||
|
||||
INSERT INTO accounts (email)
|
||||
VALUES ('forest.n.johnson@gmail.com');
|
||||
|
||||
UPDATE schemaversion SET version = 2;
|
@ -93,11 +93,11 @@ label.align {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
input, select, label {
|
||||
input, textarea, select, label {
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
input, select {
|
||||
input, select, textarea {
|
||||
outline: 0;
|
||||
padding: 0.25em 0.5em;
|
||||
border-radius: 0.5em;
|
||||
@ -118,7 +118,7 @@ select {
|
||||
background-size: 0.5em;
|
||||
}
|
||||
|
||||
input {
|
||||
input, textarea {
|
||||
background: none;
|
||||
}
|
||||
|
||||
@ -126,18 +126,31 @@ input[type=text] {
|
||||
font: calc(0.40rem + 1vmin) monospace;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #777e73;
|
||||
min-width: 20em;
|
||||
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;
|
||||
cursor: pointer;
|
||||
border: 1px solid #777e73;
|
||||
background-color: #bdc7b810;
|
||||
}
|
||||
|
||||
|
||||
|
||||
input[type=submit], select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
font-size:calc(0.40rem + 1vmin);
|
||||
margin: initial;
|
||||
@ -156,6 +169,13 @@ ul li {
|
||||
border: 1px solid #777e73;
|
||||
background: #bdc7b810;
|
||||
}
|
||||
.break-word {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.dim {
|
||||
color: #777e73bb;
|
||||
}
|
||||
|
||||
footer, p {
|
||||
text-align: left;
|
||||
@ -172,3 +192,7 @@ footer {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.smalltext {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,6 @@
|
||||
|
||||
{% if session["account"] %}
|
||||
<a href="/console/">Console</a>
|
||||
<a href="/console/billing">Billing</a>
|
||||
{% endif %}
|
||||
|
||||
<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 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 %}
|
||||
<pre>
|
||||
CAPSUL SIZES
|
||||
@ -22,7 +20,7 @@ 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>
|
||||
@ -65,4 +63,4 @@ f1-xxx $57.58 8 16G 25G 16TB
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pagesource %}/templates/console.html{% endblock %}
|
||||
{% block pagesource %}/templates/create-capsul.html{% endblock %}
|
@ -141,5 +141,5 @@ $ doas su -</pre>
|
||||
|
||||
{% 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