You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
8.8 KiB
209 lines
8.8 KiB
import re |
|
import sys |
|
import json |
|
import ipaddress |
|
#import pprint |
|
from datetime import datetime, timedelta |
|
from flask import Blueprint, current_app, render_template, make_response, session, request, redirect, url_for, flash |
|
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 admin_account_required |
|
from capsulflask.db import get_model |
|
from capsulflask.consistency import get_all_vms_from_db, get_all_vms_from_hosts, get_inconsistent_capsuls_information |
|
from capsulflask.shared import my_exec_info_message |
|
|
|
bp = Blueprint("admin", __name__, url_prefix="/admin") |
|
|
|
@bp.route("/", methods=("GET", "POST")) |
|
@admin_account_required |
|
def index(): |
|
|
|
# these are always required to display the page anyways, so might as well |
|
# grab them right off the bat as they are used inside the POST handler as well. |
|
|
|
db_hosts = get_model().list_hosts_with_networks(None) |
|
db_vms_by_id = get_all_vms_from_db() |
|
virt_vms_by_id = get_all_vms_from_hosts(db_vms_by_id) |
|
|
|
#current_app.logger.info(f"\n*******************1:\n{pprint.pformat(virt_vms_by_id)}\n\n\n\n") |
|
|
|
network_display_width_px = float(270) |
|
#operations = get_model().list_all_operations() |
|
|
|
if request.method == "POST": |
|
if "csrf-token" not in request.form or request.form['csrf-token'] != session['csrf-token']: |
|
return abort(418, f"u want tea") |
|
|
|
if 'action' not in request.form: |
|
return abort(400, "action is required") |
|
|
|
if request.form['action'] == "megaphone": |
|
emails_list = get_model().all_accounts_with_active_vms() |
|
current_app.logger.info(f"sending '{request.form['subject']}' email to {len(emails_list)} users...") |
|
# for email in emails_list: |
|
# current_app.logger.info(email) |
|
|
|
suffix1 = "This email was sent by the Capsul Admin Megaphone." |
|
suffix2 = "If you have any questions DO NOT REPLY TO THIS EMAIL, direct your inquiry to support@cyberia.club" |
|
|
|
current_app.config["FLASK_MAIL_INSTANCE"].send( |
|
Message( |
|
request.form['subject'], |
|
sender=current_app.config["MAIL_DEFAULT_SENDER"], |
|
body=f"{request.form['body']}\n\n{suffix1}\n{suffix2}", |
|
bcc=emails_list, |
|
) |
|
) |
|
current_app.logger.info(f"sending email is done.") |
|
return redirect(f"{url_for('admin.index')}") |
|
|
|
elif request.form['action'] == "start_or_stop": |
|
if 'id' not in request.form: |
|
return abort(400, "id is required") |
|
if 'desired_state' not in request.form: |
|
return abort(400, "desired_state is required") |
|
id = request.form['id'] |
|
if id not in db_vms_by_id or id not in virt_vms_by_id: |
|
return abort(404, "vm with that id was not found") |
|
|
|
virt_vm = virt_vms_by_id[id] |
|
db_vm = db_vms_by_id[id] |
|
|
|
try: |
|
if request.form['desired_state'] == "running": |
|
if 'macs' in virt_vm and len(virt_vm['macs'].keys()) > 0: |
|
current_app.config["HUB_MODEL"].net_set_dhcp(email=session['account'], host_id=virt_vm['host'], network_name=virt_vm['network_name'], macs=list(virt_vm['macs'].keys()), remove_ipv4=None, add_ipv4=db_vm['public_ipv4']) |
|
|
|
current_app.config["HUB_MODEL"].vm_state_command(email=session['account'], id=id, command="start") |
|
elif request.form['desired_state'] == "shut off": |
|
current_app.config["HUB_MODEL"].vm_state_command(email=session['account'], id=id, command="force-stop") |
|
else: |
|
return abort(400, "desired_state must be either 'running' or 'shut off'") |
|
except: |
|
flash(f"""error during start_or_stop of {id}: {my_exec_info_message(sys.exc_info())}""") |
|
|
|
return redirect(f"{url_for('admin.index')}") |
|
|
|
elif request.form['action'] == "dhcp_reset": |
|
if 'id' not in request.form: |
|
return abort(400, "id is required") |
|
|
|
id = request.form['id'] |
|
if id not in db_vms_by_id or id not in virt_vms_by_id: |
|
return abort(404, "vm with that id was not found") |
|
|
|
virt_vm = virt_vms_by_id[id] |
|
db_vm = db_vms_by_id[id] |
|
|
|
try: |
|
current_app.config["HUB_MODEL"].vm_state_command(email=session['account'], id=id, command="force-stop") |
|
current_app.config["HUB_MODEL"].net_set_dhcp(email=session['account'], host_id=virt_vm['host'], network_name=virt_vm['network_name'], macs=list(virt_vm['macs'].keys()), remove_ipv4=virt_vm['public_ipv4'], add_ipv4=db_vm['public_ipv4']) |
|
current_app.config["HUB_MODEL"].vm_state_command(email=session['account'], id=id, command="start") |
|
except: |
|
flash(f"""error during dhcp_reset of {id}: {my_exec_info_message(sys.exc_info())}""") |
|
|
|
return redirect(f"{url_for('admin.index')}") |
|
|
|
elif request.form['action'] == "stop_and_expire": |
|
if 'id' not in request.form: |
|
return abort(400, "id is required") |
|
|
|
id = request.form['id'] |
|
if id not in db_vms_by_id or id not in virt_vms_by_id: |
|
return abort(404, "vm with that id was not found") |
|
|
|
virt_vm = virt_vms_by_id[id] |
|
#db_vm = db_vms_by_id[id] |
|
|
|
current_app.config["HUB_MODEL"].vm_state_command(email=session['account'], id=id, command="force-stop") |
|
current_app.config["HUB_MODEL"].net_set_dhcp(email=session['account'], host_id=virt_vm['host'], network_name=virt_vm['network_name'], macs=list(virt_vm['macs'].keys()), add_ipv4=None, remove_ipv4=virt_vm['public_ipv4']) |
|
|
|
return redirect(f"{url_for('admin.index')}") |
|
|
|
else: |
|
return abort(400, "unknown form action") |
|
|
|
|
|
|
|
# moving on from the form post action stuff... |
|
# first create the hosts list w/ ip allocation visualization from the database |
|
# |
|
|
|
display_hosts = [] |
|
inline_styles = [f""" |
|
.network-display {'{'} |
|
width: {network_display_width_px}px; |
|
{'}'} |
|
"""] |
|
|
|
db_vms_by_host_network = dict() |
|
for vm in db_vms_by_id.values(): |
|
host_network_key = f"{vm['host']}_{vm['network_name']}" |
|
if host_network_key not in db_vms_by_host_network: |
|
db_vms_by_host_network[host_network_key] = [] |
|
db_vms_by_host_network[host_network_key].append(vm) |
|
|
|
|
|
for kv in db_hosts.items(): |
|
host_id = kv[0] |
|
value = kv[1] |
|
display_host = dict(name=host_id, networks=value['networks']) |
|
|
|
for network in display_host['networks']: |
|
|
|
network_start_int = int(ipaddress.ip_address(network["public_ipv4_first_usable_ip"])) |
|
network_end_int = int(ipaddress.ip_address(network["public_ipv4_last_usable_ip"])) |
|
|
|
network['allocations'] = [] |
|
network_addresses_width = float((network_end_int-network_start_int)+1) |
|
|
|
host_network_key = f"{host_id}_{network['network_name']}" |
|
if host_network_key in db_vms_by_host_network: |
|
for vm in db_vms_by_host_network[host_network_key]: |
|
ip_address_int = int(ipaddress.ip_address(vm['public_ipv4'])) |
|
if network_start_int <= ip_address_int and ip_address_int <= network_end_int: |
|
allocation = f"{host_id}_{network['network_name']}_{len(network['allocations'])}" |
|
inline_styles.append( |
|
f""" |
|
.{allocation} {'{'} |
|
left: {(float(ip_address_int-network_start_int)/network_addresses_width)*network_display_width_px}px; |
|
width: {network_display_width_px/network_addresses_width}px; |
|
{'}'} |
|
""" |
|
) |
|
network['allocations'].append(allocation) |
|
else: |
|
current_app.logger.warning(f"/admin: capsul {vm['id']} has public_ipv4 {vm['public_ipv4']} which is out of range for its host network {host_id} {network['network_name']} {network['public_ipv4_cidr_block']}") |
|
|
|
display_hosts.append(display_host) |
|
|
|
|
|
inconsistency_info = get_inconsistent_capsuls_information(db_vms_by_id, virt_vms_by_id) |
|
|
|
|
|
csp_inline_style_nonce = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10) |
|
response_text = render_template( |
|
"admin.html", |
|
csrf_token=session["csrf-token"], |
|
display_hosts=display_hosts, |
|
network_display_width_px=network_display_width_px, |
|
csp_inline_style_nonce=csp_inline_style_nonce, |
|
inline_style='\n'.join(inline_styles), |
|
in_db_but_not_in_virt=inconsistency_info['in_db_but_not_in_virt'], |
|
state_not_equal_to_desired_state=inconsistency_info['state_not_equal_to_desired_state'], |
|
has_no_desired_ip_address=inconsistency_info['has_no_desired_ip_address'], |
|
has_not_aquired_ip_address_yet=inconsistency_info['has_not_aquired_ip_address_yet'], |
|
stole_someone_elses_ip_and_own_ip_avaliable=inconsistency_info['stole_someone_elses_ip_and_own_ip_avaliable'], |
|
stole_someone_elses_ip_but_own_ip_also_stolen=inconsistency_info['stole_someone_elses_ip_but_own_ip_also_stolen'], |
|
has_wrong_ip=inconsistency_info['has_wrong_ip'] |
|
) |
|
|
|
response = make_response(response_text) |
|
|
|
response.headers.set('Content-Type', 'text/html') |
|
response.headers.set('Content-Security-Policy', f"default-src 'self'; style-src 'self' 'nonce-{csp_inline_style_nonce}'") |
|
|
|
return response
|
|
|