155 lines
5.7 KiB
Python
155 lines
5.7 KiB
Python
import re
|
|
import sys
|
|
import json
|
|
import ipaddress
|
|
from datetime import datetime, timedelta
|
|
from flask import Blueprint, current_app, render_template, make_response, session, request, redirect, url_for
|
|
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.shared import my_exec_info_message
|
|
|
|
bp = Blueprint("admin", __name__, url_prefix="/admin")
|
|
|
|
@bp.route("/", methods=("GET", "POST"))
|
|
@admin_account_required
|
|
def index():
|
|
|
|
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')}")
|
|
else:
|
|
return abort(400, "unknown form action")
|
|
|
|
|
|
# first create the hosts list w/ ip allocation visualization from the database
|
|
#
|
|
|
|
db_hosts = get_model().list_hosts_with_networks(None)
|
|
db_vms_by_host_and_network = get_model().non_deleted_vms_by_host_and_network(None)
|
|
network_display_width_px = float(270)
|
|
#operations = get_model().list_all_operations()
|
|
|
|
display_hosts = []
|
|
inline_styles = [f"""
|
|
.network-display {'{'}
|
|
width: {network_display_width_px}px;
|
|
{'}'}
|
|
"""]
|
|
|
|
db_vm_by_id = dict()
|
|
|
|
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)
|
|
|
|
if host_id in db_vms_by_host_and_network:
|
|
if network['network_name'] in db_vms_by_host_and_network[host_id]:
|
|
for vm in db_vms_by_host_and_network[host_id][network['network_name']]:
|
|
vm['network_name'] = network['network_name']
|
|
vm['virtual_bridge_name'] = network['virtual_bridge_name']
|
|
vm['host'] = host_id
|
|
db_vm_by_id[vm['id']] = vm
|
|
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)
|
|
|
|
|
|
# Now creating the capsuls running status ui
|
|
#
|
|
|
|
virt_vms_by_host_and_network = current_app.config["HUB_MODEL"].get_all_by_host_and_network()
|
|
|
|
# virt_vms_dict = dict()
|
|
# for vm in virt_vms:
|
|
# virt_vms_dict[vm["id"]] = vm["state"]
|
|
|
|
# in_db_but_not_in_virt = []
|
|
# needs_to_be_started = []
|
|
# needs_to_be_started_missing_ipv4 = []
|
|
|
|
# for vm in db_vms:
|
|
# if vm["id"] not in virt_vms_dict:
|
|
# in_db_but_not_in_virt.append(vm["id"])
|
|
# elif vm["desired_state"] == "running" and virt_vms_dict[vm["id"]] != "running":
|
|
# if vm["id"] in db_vm_by_id:
|
|
# needs_to_be_started.append(db_vm_by_id[vm["id"]])
|
|
# else:
|
|
# needs_to_be_started_missing_ipv4.append(vm["id"])
|
|
# elif vm["ipv4"] != current_ipv4
|
|
|
|
# current_app.logger.info(f"list_of_networks: {json.dumps(list_of_networks)}")
|
|
|
|
|
|
csp_inline_style_nonce = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10)
|
|
response_text = render_template(
|
|
"admin.html",
|
|
csrf_token=session["csrf-token"],
|
|
display_hosts=display_hosts,
|
|
# in_db_but_not_in_virt=in_db_but_not_in_virt,
|
|
# needs_to_be_started=needs_to_be_started,
|
|
# needs_to_be_started_missing_ipv4=needs_to_be_started_missing_ipv4,
|
|
network_display_width_px=network_display_width_px,
|
|
csp_inline_style_nonce=csp_inline_style_nonce,
|
|
inline_style='\n'.join(inline_styles),
|
|
|
|
db_vms_by_host_and_network=json.dumps(db_vms_by_host_and_network),
|
|
virt_vms_by_host_and_network=json.dumps(virt_vms_by_host_and_network),
|
|
)
|
|
|
|
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
|