2021-07-08 19:10:14 +00:00
import re
import sys
import json
import ipaddress
2021-12-11 01:21:07 +00:00
#import pprint
2021-07-08 19:10:14 +00:00
from datetime import datetime , timedelta
2021-12-09 23:25:44 +00:00
from flask import Blueprint , current_app , render_template , make_response , session , request , redirect , url_for , flash
2021-12-09 17:21:33 +00:00
from flask_mail import Message
2021-07-08 19:10:14 +00:00
from werkzeug . exceptions import abort
2021-07-09 19:13:28 +00:00
from nanoid import generate
2021-07-08 19:10:14 +00:00
from capsulflask . metrics import durations as metric_durations
from capsulflask . auth import admin_account_required
from capsulflask . db import get_model
2021-12-18 00:29:48 +00:00
from capsulflask . consistency import get_all_vms_from_db , get_all_vms_from_hosts , get_inconsistent_capsuls_information
2021-07-08 19:10:14 +00:00
from capsulflask . shared import my_exec_info_message
bp = Blueprint ( " admin " , __name__ , url_prefix = " /admin " )
2021-12-09 17:29:06 +00:00
@bp.route ( " / " , methods = ( " GET " , " POST " ) )
2021-07-08 19:10:14 +00:00
@admin_account_required
def index ( ) :
2021-12-05 22:47:37 +00:00
2021-12-09 23:25:44 +00:00
# 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 ( )
2021-12-11 00:57:11 +00:00
virt_vms_by_id = get_all_vms_from_hosts ( db_vms_by_id )
2021-12-11 00:58:53 +00:00
2021-12-11 01:21:07 +00:00
#current_app.logger.info(f"\n*******************1:\n{pprint.pformat(virt_vms_by_id)}\n\n\n\n")
2021-12-11 00:58:53 +00:00
2021-12-09 23:25:44 +00:00
network_display_width_px = float ( 270 )
#operations = get_model().list_all_operations()
2021-12-09 17:21:33 +00:00
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... " )
2021-12-09 17:44:30 +00:00
# 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 "
2021-12-09 17:21:33 +00:00
current_app . config [ " FLASK_MAIL_INSTANCE " ] . send (
Message (
request . form [ ' subject ' ] ,
sender = current_app . config [ " MAIL_DEFAULT_SENDER " ] ,
2021-12-09 17:44:30 +00:00
body = f " { request . form [ ' body ' ] } \n \n { suffix1 } \n { suffix2 } " ,
bcc = emails_list ,
2021-12-09 17:21:33 +00:00
)
)
current_app . logger . info ( f " sending email is done. " )
return redirect ( f " { url_for ( ' admin.index ' ) } " )
2021-12-09 23:25:44 +00:00
2022-04-11 19:42:47 +00:00
elif request . form [ ' action ' ] == " set_broadcast_message " :
get_model ( ) . set_broadcast_message ( request . form [ ' message ' ] )
current_app . config [ ' BROADCAST_BANNER_MESSAGE ' ] = request . form [ ' message ' ]
session . pop ( ' _flashes ' , None )
return redirect ( f " { url_for ( ' admin.index ' ) } " )
2021-12-09 23:25:44 +00:00
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 :
2021-12-11 01:21:07 +00:00
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 ' ] )
2021-12-09 23:25:44 +00:00
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 " )
2021-12-09 23:41:28 +00:00
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 ' ] )
2021-12-09 23:25:44 +00:00
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 " )
2021-12-11 01:44:46 +00:00
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 ' ] )
2021-12-09 23:25:44 +00:00
return redirect ( f " { url_for ( ' admin.index ' ) } " )
2021-12-09 17:21:33 +00:00
else :
return abort ( 400 , " unknown form action " )
2021-12-09 23:25:44 +00:00
# moving on from the form post action stuff...
2021-12-09 17:21:33 +00:00
# first create the hosts list w/ ip allocation visualization from the database
2021-12-05 22:47:37 +00:00
#
2021-07-08 19:10:14 +00:00
display_hosts = [ ]
2021-07-09 19:13:28 +00:00
inline_styles = [ f """
. network - display { ' { ' }
width : { network_display_width_px } px ;
{ ' } ' }
""" ]
2021-07-08 19:10:14 +00:00
2021-12-09 20:38:17 +00:00
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 )
2021-12-05 22:47:37 +00:00
2021-12-09 17:21:33 +00:00
for kv in db_hosts . items ( ) :
2021-07-09 19:13:28 +00:00
host_id = kv [ 0 ]
2021-07-08 19:10:14 +00:00
value = kv [ 1 ]
2021-07-09 19:13:28 +00:00
display_host = dict ( name = host_id , networks = value [ ' networks ' ] )
2021-07-08 19:10:14 +00:00
2021-07-09 19:13:28 +00:00
for network in display_host [ ' networks ' ] :
2021-07-12 16:29:33 +00:00
2021-07-12 19:38:56 +00:00
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 " ] ) )
2021-07-12 16:29:33 +00:00
2021-07-09 19:13:28 +00:00
network [ ' allocations ' ] = [ ]
2021-07-12 17:27:07 +00:00
network_addresses_width = float ( ( network_end_int - network_start_int ) + 1 )
2021-07-09 19:13:28 +00:00
2021-12-09 20:38:17 +00:00
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 ] :
2022-03-03 18:57:52 +00:00
if ' public_ipv4 ' not in vm or vm [ ' public_ipv4 ' ] is None :
continue
2021-12-09 20:38:17 +00:00
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 ' ] } " )
2021-07-09 19:13:28 +00:00
display_hosts . append ( display_host )
2021-12-05 22:47:37 +00:00
2021-12-18 00:29:48 +00:00
inconsistency_info = get_inconsistent_capsuls_information ( db_vms_by_id , virt_vms_by_id )
2021-12-09 17:21:33 +00:00
2021-12-05 22:47:37 +00:00
2021-07-09 19:13:28 +00:00
csp_inline_style_nonce = generate ( alphabet = " 1234567890qwertyuiopasdfghjklzxcvbnm " , size = 10 )
response_text = render_template (
" admin.html " ,
2021-12-09 17:21:33 +00:00
csrf_token = session [ " csrf-token " ] ,
2021-07-09 19:13:28 +00:00
display_hosts = display_hosts ,
network_display_width_px = network_display_width_px ,
csp_inline_style_nonce = csp_inline_style_nonce ,
2021-12-09 17:21:33 +00:00
inline_style = ' \n ' . join ( inline_styles ) ,
2021-12-18 00:29:48 +00:00
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 ' ]
2021-07-09 19:13:28 +00:00
)
response = make_response ( response_text )
2021-07-08 19:10:14 +00:00
2021-07-09 19:13:28 +00:00
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 } ' " )
2021-07-08 19:10:14 +00:00
2021-07-09 19:13:28 +00:00
return response