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 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 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") # moving on from the form post stuff... # first create the hosts list w/ ip allocation visualization from the database # db_hosts = get_model().list_hosts_with_networks(None) db_vms_by_id = get_all_vms_from_db() 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_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) # Now creating the capsul consistency / running status ui # virt_vms_by_id = get_all_vms_from_hosts() # current_app.logger.info(pprint.pformat(db_vms_by_id)) # current_app.logger.info("\n\n\n\n") # current_app.logger.info(pprint.pformat(virt_vms_by_id)) # current_app.logger.info("\n\n\n\n") virt_vm_id_by_ipv4 = dict() for vm_id, virt_vm in virt_vms_by_id.items(): if 'public_ipv4' in virt_vm and virt_vm['public_ipv4'] != "": virt_vm_id_by_ipv4[virt_vm['public_ipv4']] = vm_id db_vm_id_by_ipv4 = dict() for vm_id, db_vm in db_vms_by_id.items(): if 'public_ipv4' in db_vm and db_vm['public_ipv4'] != "": db_vm_id_by_ipv4[db_vm['public_ipv4']] = vm_id in_db_but_not_in_virt = [] state_not_equal_to_desired_state = [] has_no_desired_ip_address = [] has_not_aquired_ip_address_yet = [] stole_someone_elses_ip_and_own_ip_avaliable = [] stole_someone_elses_ip_but_own_ip_also_stolen = [] has_wrong_ip = [] for vm_id, db_vm in db_vms_by_id.items(): if vm_id not in virt_vms_by_id: in_db_but_not_in_virt.append(db_vm) elif virt_vms_by_id[vm_id]['state'] != db_vm["desired_state"]: db_vm["state"] = virt_vms_by_id[vm_id]['state'] state_not_equal_to_desired_state.append(db_vm) elif 'public_ipv4' not in db_vm or db_vm["public_ipv4"] == "": has_no_desired_ip_address.append(db_vm) elif db_vm["desired_state"] == "running" and 'public_ipv4' not in virt_vms_by_id[vm_id] or virt_vms_by_id[vm_id]['public_ipv4'] == "": has_not_aquired_ip_address_yet.append(db_vm) elif db_vm["desired_state"] == "running" and virt_vms_by_id[vm_id]['public_ipv4'] != db_vm["public_ipv4"]: db_vm["desired_ipv4"] = db_vm["public_ipv4"] db_vm["current_ipv4"] = virt_vms_by_id[vm_id]['public_ipv4'] if virt_vms_by_id[vm_id]['public_ipv4'] in db_vm_id_by_ipv4: if db_vm["public_ipv4"] not in virt_vm_id_by_ipv4: stole_someone_elses_ip_and_own_ip_avaliable.append(db_vm) else: stole_someone_elses_ip_but_own_ip_also_stolen.append(db_vm) has_wrong_ip.append(db_vm) # 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), in_db_but_not_in_virt=in_db_but_not_in_virt, state_not_equal_to_desired_state=state_not_equal_to_desired_state, has_no_desired_ip_address=has_no_desired_ip_address, has_not_aquired_ip_address_yet=has_not_aquired_ip_address_yet, stole_someone_elses_ip_and_own_ip_avaliable=stole_someone_elses_ip_and_own_ip_avaliable, stole_someone_elses_ip_but_own_ip_also_stolen=stole_someone_elses_ip_but_own_ip_also_stolen, has_wrong_ip=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