599 lines
21 KiB
Python
599 lines
21 KiB
Python
import os
|
|
import json
|
|
import requests
|
|
import subprocess
|
|
from flask import Flask , redirect , render_template, request, Response, stream_with_context, url_for, make_response, flash
|
|
from werkzeug.utils import secure_filename
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
from flask_login import LoginManager , UserMixin , login_required ,login_user, logout_user,current_user
|
|
from werkzeug.security import generate_password_hash, check_password_hash
|
|
|
|
from config import *
|
|
from functions.fhost import *
|
|
from functions.flxc import *
|
|
from functions.fiso import *
|
|
from functions.fvm import *
|
|
from functions.fvnc import *
|
|
from functions.fbackup import *
|
|
from functions.fpool import *
|
|
|
|
app = Flask(__name__,static_url_path='',static_folder='static',template_folder='templates')
|
|
|
|
###########################
|
|
## DROPZONE ##
|
|
###########################
|
|
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024 * 1024
|
|
app.config['UPLOAD_EXTENSIONS'] = ['.iso']
|
|
app.config['UPLOAD_PATH'] = iso_path
|
|
|
|
############################
|
|
## LOGIN ##
|
|
############################
|
|
|
|
#Database (see base.sql for creation user table)
|
|
app.config['SQLALCHEMY_DATABASE_URI']='sqlite:///'+os.path.join(path,'db.db')
|
|
app.config['SECRET_KEY']='619619'
|
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=True
|
|
db = SQLAlchemy(app)
|
|
|
|
#Login init (login_view => default login page)
|
|
login_manager = LoginManager()
|
|
login_manager.login_view = "/login"
|
|
login_manager.init_app(app)
|
|
|
|
#Database search
|
|
class User(UserMixin,db.Model):
|
|
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
|
|
username = db.Column(db.String(200))
|
|
email = db.Column(db.String(200))
|
|
password = db.Column(db.String(200))
|
|
|
|
@login_manager.user_loader
|
|
def get(id):
|
|
return User.query.get(id)
|
|
|
|
#Pass encrypt
|
|
def encrypt(password):
|
|
hashed_pass=generate_password_hash(password)
|
|
return hashed_pass
|
|
|
|
#Page for login
|
|
@app.route('/login',methods=['GET'])
|
|
def get_login():
|
|
return render_template('login.html')
|
|
|
|
#Page for user creation (secured)
|
|
@app.route('/signup',methods=['GET'])
|
|
@login_required
|
|
def get_signup():
|
|
return render_template('signup.html')
|
|
|
|
#Page for login, adding user and logout
|
|
@app.route('/login',methods=['POST'])
|
|
def login_post():
|
|
email = request.form['email']
|
|
password = request.form['password']
|
|
user = User.query.filter_by(email=email).first()
|
|
if user and check_password_hash(user.password, password):
|
|
login_user(user)
|
|
return redirect('/')
|
|
return redirect('/')
|
|
|
|
@app.route('/signup',methods=['POST'])
|
|
@login_required
|
|
def signup_post():
|
|
username = request.form['username']
|
|
email = request.form['email']
|
|
password = encrypt(request.form['password'])
|
|
user = User(username=username,email=email,password=password)
|
|
try:
|
|
db.session.add(user)
|
|
db.session.commit()
|
|
flash(user+' added', category='success')
|
|
except Exception as e:
|
|
flash('Error on adding user :'+str(e), category='danger')
|
|
return redirect('param')
|
|
|
|
@app.route('/logout',methods=['GET'])
|
|
def logout():
|
|
logout_user()
|
|
return redirect('/login')
|
|
|
|
############################
|
|
## INDEX ##
|
|
############################
|
|
|
|
@app.route('/',methods=['GET'])
|
|
@login_required
|
|
def get_home():
|
|
lxc_up = len(get_lxc_activ())
|
|
lxc_down = len(get_lxc_inactiv())
|
|
vm_up = len(get_vm_activ())
|
|
vm_down = len(get_vm_inactiv())
|
|
full = get_host_full()
|
|
return render_template('index.html', host=Host(), full = full, lxc_up=lxc_up, lxc_down=lxc_down, vm_up=vm_up, vm_down=vm_down)
|
|
|
|
def get_ressources():
|
|
while True:
|
|
full = get_host_full()
|
|
json_data = json.dumps(
|
|
{
|
|
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
"cpu_percent": full['cpu']['percent'],
|
|
"mem_percent": full['mem']['percent'],
|
|
"swap_percent": full['swap']['percent'],
|
|
"cpu_time_user": full['cpu']['time_user'],
|
|
"cpu_time_nice": full['cpu']['time_nice'],
|
|
"cpu_time_system": full['cpu']['time_system'],
|
|
"cpu_time_idle": full['cpu']['time_idle'],
|
|
"cpu_time_iowait": full['cpu']['time_iowait'],
|
|
"cpu_time_irq": full['cpu']['time_irq'],
|
|
"cpu_time_softirq": full['cpu']['time_softirq'],
|
|
"cpu_time_steal": full['cpu']['time_steal'],
|
|
"cpu_time_guest": full['cpu']['time_guest'],
|
|
"cpu_time_guest_nice": full['cpu']['time_nice'],
|
|
"mem_total": full['mem']['total'],
|
|
"mem_available": full['mem']['available'],
|
|
"mem_used": full['mem']['used'],
|
|
"mem_free": full['mem']['free'],
|
|
"mem_active": full['mem']['active'],
|
|
"mem_inactive": full['mem']['inactive'],
|
|
"mem_buffers": full['mem']['buffers'],
|
|
"mem_cached": full['mem']['cached'],
|
|
"mem_shared": full['mem']['shared'],
|
|
"mem_slab": full['mem']['slab'],
|
|
"swap_total": full['swap']['total'],
|
|
"swap_used": full['swap']['used'],
|
|
"swap_free": full['swap']['free'],
|
|
"swap_sin": full['swap']['sin'],
|
|
"swap_sout": full['swap']['sout'],
|
|
}
|
|
)
|
|
yield f"data:{json_data}\n\n"
|
|
time.sleep(1)
|
|
|
|
@app.route('/chart-ressources/')
|
|
def chart_data() -> Response:
|
|
response = Response(stream_with_context(get_ressources()), mimetype="text/event-stream")
|
|
response.headers["Cache-Control"] = "no-cache"
|
|
response.headers["X-Accel-Buffering"] = "no"
|
|
return response
|
|
|
|
############################
|
|
## POOL ##
|
|
############################
|
|
|
|
@app.route('/pool',methods=['GET'])
|
|
@login_required
|
|
def get_pool():
|
|
return render_template('pool.html',list_pool_full = get_full_pool())
|
|
|
|
@app.route('/del_volume',methods=['POST'])
|
|
@login_required
|
|
def delvol():
|
|
pool_name = request.form['pool_name']
|
|
volume_name = request.form['volume_name']
|
|
try:
|
|
del_pool_vol(pool_name,volume_name)
|
|
flash(volume_name+' deleted', category='success')
|
|
except Exception as e:
|
|
flash('Error on deleting '+volume_name+':'+str(e), category='danger')
|
|
return redirect(url_for('get_pool'))
|
|
|
|
############################
|
|
## VNC ##
|
|
############################
|
|
|
|
@app.route('/vnc',methods=['GET','POST',"DELETE"])
|
|
@login_required
|
|
def vnc_connect():
|
|
if request.method=='GET':
|
|
resp = requests.get('http://localhost:6080/vnc.html')
|
|
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
|
|
headers = [(name, value) for (name, value) in resp.raw.headers.items() if name.lower() not in excluded_headers]
|
|
response = Response(resp.content, resp.status_code, headers)
|
|
return response
|
|
elif request.method=='POST':
|
|
resp = requests.post('http://localhost:6080/vnc.html',json=request.get_json())
|
|
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
|
|
headers = [(name, value) for (name, value) in resp.raw.headers.items() if name.lower() not in excluded_headers]
|
|
response = Response(resp.content, resp.status_code, headers)
|
|
return response
|
|
elif request.method=='DELETE':
|
|
resp = requests.delete('http://localhost:6080/vnc.html').content
|
|
response = Response(resp.content, resp.status_code, headers)
|
|
return response
|
|
|
|
@app.route('/console_lxc',methods=['GET','POST','DELETE'])
|
|
@login_required
|
|
def consolelxc():
|
|
lxc_name = request.form['lxc_name']
|
|
try:
|
|
pyxterm_connect(path,lxc_name)
|
|
time.sleep(2)
|
|
return render_template('terminal.html')
|
|
except Exception as e:
|
|
flash('Error starting Terminal :'+str(e), category='danger')
|
|
return redirect(url_for('state'))
|
|
|
|
@app.route('/ter',methods=['GET','POST',"DELETE"])
|
|
@login_required
|
|
def ter_connect():
|
|
if request.method=='GET':
|
|
resp = requests.get('http://localhost:5008/')
|
|
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
|
|
headers = [(name, value) for (name, value) in resp.raw.headers.items() if name.lower() not in excluded_headers]
|
|
response = Response(resp.content, resp.status_code, headers)
|
|
return response
|
|
elif request.method=='POST':
|
|
resp = requests.post('http://localhost:5008/',json=request.get_json())
|
|
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
|
|
headers = [(name, value) for (name, value) in resp.raw.headers.items() if name.lower() not in excluded_headers]
|
|
response = Response(resp.content, resp.status_code, headers)
|
|
return response
|
|
elif request.method=='DELETE':
|
|
resp = requests.delete('http://localhost:5008/').content
|
|
response = Response(resp.content, resp.status_code, headers)
|
|
return response
|
|
|
|
|
|
@app.route('/console_vm',methods=['POST'])
|
|
@login_required
|
|
def consolevm():
|
|
vm_name = request.form['vm_name']
|
|
try:
|
|
socket_connect(vm_name)
|
|
return render_template('vnc.html')
|
|
except Exception as e:
|
|
flash('Error starting VNC :'+str(e), category='danger')
|
|
return redirect(url_for('state'))
|
|
|
|
############################
|
|
## PARAMETERS ##
|
|
############################
|
|
def get_users():
|
|
users = User.query.all()
|
|
list_users=[]
|
|
for uni in users:
|
|
single=[]
|
|
single.append(uni.username)
|
|
single.append(uni.email)
|
|
list_users.append(single)
|
|
return list_users
|
|
|
|
|
|
@app.route('/param',methods=['GET'])
|
|
@login_required
|
|
def param():
|
|
return render_template('param.html',users_list = get_users())
|
|
|
|
@app.route('/deluser',methods=['POST'])
|
|
@login_required
|
|
def deluser():
|
|
username = request.form['username']
|
|
email = request.form['email']
|
|
try:
|
|
User.query.filter_by(username=username,email=email).delete()
|
|
db.session.commit()
|
|
flash(username+' deleted', category='success')
|
|
except Exception as e:
|
|
flash('Error on deleting '+username+':'+str(e), category='danger')
|
|
return redirect('param')
|
|
|
|
|
|
############################
|
|
## ISO ##
|
|
############################
|
|
|
|
@app.route('/iso',methods=['GET'])
|
|
@login_required
|
|
def iso():
|
|
return render_template('iso.html', list_iso = get_iso_list(), list_vm = get_vm_list(), list_iso_mount = get_iso_list())
|
|
|
|
@app.route('/deliso',methods=['POST'])
|
|
@login_required
|
|
def delete_iso():
|
|
file=request.form['fichier']
|
|
path=os.path.join(iso_path, file)
|
|
try:
|
|
os.remove(path)
|
|
flash(file+' deleted', category='success')
|
|
except Exception as e:
|
|
flash('Error on deleting '+file+':'+str(e), category='danger')
|
|
return render_template('iso.html', list_iso = get_iso_list(), list_vm = get_vm_list(), list_iso_mount = get_iso_list())
|
|
|
|
@app.route('/mountiso', methods=['POST'])
|
|
@login_required
|
|
def mountiso():
|
|
vm_name=request.form['vm']
|
|
if request.form['iso'] == '':
|
|
iso_name = ''
|
|
else:
|
|
iso_name=iso_path+request.form['iso']
|
|
try:
|
|
mount_iso(vm_name,iso_name)
|
|
flash(iso_name+' mounted', category='success')
|
|
except Exception as e:
|
|
flash('Error on mounting '+iso_name+':'+str(e), category='danger')
|
|
return redirect(url_for('state'))
|
|
|
|
@app.route('/ejectiso', methods = ['POST'])
|
|
@login_required
|
|
def eject_iso():
|
|
vm_name=request.form['ejectisovm']
|
|
state = check_iso_is_mounted(vm_name)
|
|
while state != 0:
|
|
iso_file='';
|
|
mount_iso(vm_name,iso_name)
|
|
state = check_iso_is_mounted(vm_name)
|
|
|
|
@app.route('/iso', methods=['POST'])
|
|
@login_required
|
|
def upload_files():
|
|
uploaded_file = request.files['file']
|
|
filename = secure_filename(uploaded_file.filename)
|
|
if filename != '':
|
|
file_ext = os.path.splitext(filename)[1]
|
|
if file_ext not in app.config['UPLOAD_EXTENSIONS']:
|
|
return "Invalid image", 400
|
|
uploaded_file.save(os.path.join(app.config['UPLOAD_PATH'], filename))
|
|
return '', 204
|
|
|
|
############################
|
|
## BACKUP ##
|
|
############################
|
|
|
|
@app.route('/backup')
|
|
@login_required
|
|
def backup():
|
|
full_snap_lxc=[]
|
|
full_snap_vm=[]
|
|
for lxc_name in get_lxc_list():
|
|
if get_snap_list_lxc(lxc_name):
|
|
full_snap_lxc.append(get_snap_list_lxc(lxc_name))
|
|
else:
|
|
full_snap_lxc.append('')
|
|
for vm_name in get_vm_list():
|
|
if get_snap_list_vm(vm_name):
|
|
full_snap_vm.append(get_snap_list_vm(vm_name))
|
|
else:
|
|
full_snap_vm.append('')
|
|
list_snap_lxc = zip(get_lxc_list(), full_snap_lxc)
|
|
list_snap_vm = zip(get_vm_list(), full_snap_vm)
|
|
return render_template('backup.html',list_snap_lxc=list_snap_lxc,list_snap_vm=list_snap_vm)
|
|
|
|
@app.route('/snaplxc', methods = ['POST'])
|
|
@login_required
|
|
def snaplxc():
|
|
lxc_name=request.form['snap']
|
|
try:
|
|
create_snap_lxc(lxc_name)
|
|
flash('Snapshot '+lxc_name+' done', category='success')
|
|
except Exception as e:
|
|
flash('Error on snapshoting '+lxc_name+':'+str(e), category='danger')
|
|
return redirect(url_for('backup'))
|
|
|
|
@app.route('/del_snap_lxc', methods = ['POST'])
|
|
@login_required
|
|
def delsnaplxc():
|
|
lxc_name=request.form['lxc_name']
|
|
item=request.form['item']
|
|
try:
|
|
del_snap_lxc(lxc_name,item)
|
|
flash(item+' deleted', category='success')
|
|
except Exception as e:
|
|
flash('Error on deleting '+item+':'+str(e), category='danger')
|
|
return redirect(url_for('backup'))
|
|
|
|
@app.route('/rest_snap_lxc', methods = ['POST'])
|
|
@login_required
|
|
def restsnap_lxc():
|
|
lxc_name=request.form['lxc_name']
|
|
item=request.form['item']
|
|
try:
|
|
rest_snap_lxc(lxc_name,item)
|
|
flash('Restore '+item+' done', category='success')
|
|
except Exception as e:
|
|
flash('Error restoring '+item+':'+str(e), category='danger')
|
|
return redirect(url_for('backup'))
|
|
|
|
###LAPIN
|
|
@app.route('/snapvm', methods = ['POST'])
|
|
@login_required
|
|
def snapvm():
|
|
vm_name=request.form['snap']
|
|
try:
|
|
create_snap_vm(vm_name)
|
|
flash('Snapshot '+vm_name+' done', category='success')
|
|
except Exception as e:
|
|
flash('Error on snapshoting '+vm_name+':'+str(e), category='danger')
|
|
return redirect(url_for('backup'))
|
|
|
|
@app.route('/del_snap_vm', methods = ['POST'])
|
|
@login_required
|
|
def delsnapvm():
|
|
vm_name=request.form['vm_name']
|
|
item=request.form['item']
|
|
try:
|
|
del_snap_vm(vm_name,item)
|
|
flash(item+' deleted', category='success')
|
|
except Exception as e:
|
|
flash('Error on deleting '+item+':'+str(e), category='danger')
|
|
return redirect(url_for('backup'))
|
|
"""
|
|
@app.route('/rest_snap_vm', methods = ['POST'])
|
|
@login_required
|
|
def restsnap_lxc():
|
|
vm_name=request.form['lxc_name']
|
|
item=request.form['item']
|
|
try:
|
|
rest_snap_lxc(lxc_name,item)
|
|
flash('Restore '+item+' done', category='success')
|
|
except Exception as e:
|
|
flash('Error restoring '+item+':'+str(e), category='danger')
|
|
return redirect(url_for('backup'))
|
|
"""
|
|
##PINE LA
|
|
|
|
############################
|
|
## BUILD ##
|
|
############################
|
|
|
|
@app.route('/build',methods=['GET'])
|
|
@login_required
|
|
def build_lxc():
|
|
return render_template('build.html', list_lxc_os=list_distrib(), list_profiles=get_os_profile_list(), list_net=conn.listNetworks(),list_iso = get_iso_list())
|
|
|
|
@app.route("/creation", methods=['POST'])
|
|
@login_required
|
|
def create_ct():
|
|
lxc_ip = request.form['ip']
|
|
try:
|
|
create_lxc(request.form['nom'],request.form['os'])
|
|
flash(request.form['nom']+' created', category='success')
|
|
except Exception as e:
|
|
flash('Error on creating '+request.form['nom']+':'+str(e), category='danger')
|
|
if lxc_ip:
|
|
set_lxc_ip(lxc_ip)
|
|
return redirect(url_for('state'))
|
|
|
|
@app.route('/creationvm', methods=['POST'])
|
|
@login_required
|
|
def create_VM():
|
|
#VNC Version
|
|
nom = request.form['vm_name']
|
|
ram = request.form['ram']
|
|
cpu = request.form['cpu']
|
|
ose = request.form['os']
|
|
iso = request.form['iso']
|
|
iso = iso_path+iso
|
|
net = request.form['net']
|
|
disk = request.form['disk']
|
|
opt = ''
|
|
ostype = 'linux'
|
|
|
|
####################################
|
|
## WINDOWS ##
|
|
####################################
|
|
if ose.startswith('win'):
|
|
try:
|
|
cmd = "qemu-img create -f qcow2 "+str(virtuo_path)+str(nom)+".qcow2 "+str(disk)+"G"
|
|
subprocess.call(cmd, shell=True)
|
|
except Exception as e:
|
|
flash('Error on creating '+str(nom)+':'+str(e),category='danger')
|
|
return redirect(url_for('state'))
|
|
opt = " --disk path="+virtuo_path+virtuo_file+",device=cdrom"
|
|
ostype = 'windows'
|
|
diskpath = " --disk path="+str(virtuo_path)+str(nom)+".qcow2,size="+str(disk)+",bus=virtio"
|
|
creationcmd='--name '+str(nom)+' --ram '+str(ram)+' --disk path='+str(virtuo_path)+str(nom)+'.qcow2,bus=virtio,format=qcow2 --vcpus '+str(cpu)+' --os-type '+str(ostype)+' --os-variant '+str(ose)+' --network network:bridged --graphics vnc,listen=0.0.0.0 --noautoconsole --console pty,target_type=serial '+opt+' --cdrom '+str(iso)+' --force --debug '
|
|
else:
|
|
creationcmd='--name '+str(nom)+' --ram '+str(ram)+' --disk pool=default,size='+str(disk)+',bus=virtio,format=qcow2 --vcpus '+str(cpu)+' --os-type '+str(ostype)+' --os-variant '+str(ose)+' --network network:bridged --graphics vnc,listen=0.0.0.0 --noautoconsole --console pty,target_type=serial '+opt+' --cdrom '+str(iso)+' --force --debug '
|
|
try:
|
|
os.system('virt-install '+creationcmd+' ')
|
|
flash(str(nom)+' created', category='success')
|
|
except Exception as e:
|
|
flash('Error on creating '+str(nom)+':'+str(e),category='danger')
|
|
return redirect(url_for('state'))
|
|
|
|
|
|
############################
|
|
## STATE ##
|
|
############################
|
|
@app.route('/state',methods=['GET'])
|
|
@login_required
|
|
def state():
|
|
ips_lxc=[]
|
|
for lxc_name in get_lxc_activ():
|
|
ips_lxc.append(get_lxc_ip(lxc_name))
|
|
activ_lxc=zip(get_lxc_activ(),ips_lxc)
|
|
state_act_vm = []
|
|
ips_vm = []
|
|
for vm_name in get_vm_activ():
|
|
state_act_vm.append(check_iso_is_mounted(vm_name))
|
|
ips_vm.append(get_vm_ips(vm_name))
|
|
activ_vm=zip(get_vm_activ(),state_act_vm,ips_vm)
|
|
state_inact_vm = []
|
|
for vm_name in get_vm_inactiv():
|
|
state_inact_vm.append(check_iso_is_mounted(vm_name))
|
|
inactiv_vm=zip(get_vm_inactiv(),state_inact_vm)
|
|
return render_template('state.html', activ_vm=activ_vm, inactiv_vm=inactiv_vm,activ_lxc=activ_lxc,inactiv_lxc=get_lxc_inactiv())
|
|
|
|
############################
|
|
## LXC ##
|
|
############################
|
|
@app.route('/start_lxc',methods=['POST'])
|
|
@login_required
|
|
def startct():
|
|
lxc_name = request.form['start']
|
|
try:
|
|
start_lxc(lxc_name)
|
|
flash(lxc_name+' started', category='success')
|
|
except Exception as e:
|
|
flash('Error starting '+lxc_name+':'+str(e), category='danger')
|
|
return redirect(url_for('state'))
|
|
|
|
@app.route('/stop_lxc',methods=['POST'])
|
|
@login_required
|
|
def stopct():
|
|
lxc_name = request.form['stop']
|
|
try:
|
|
stop_lxc(lxc_name)
|
|
flash(lxc_name+' stoped', category='success')
|
|
except Exception as e:
|
|
flash('Error stoping '+lxc_name+':'+str(e), category='danger')
|
|
return redirect(url_for('state'))
|
|
|
|
@app.route('/destroy_lxc',methods=['POST'])
|
|
@login_required
|
|
def destroyct():
|
|
lxc_name = request.form['destroy']
|
|
try:
|
|
destroy_lxc(lxc_name)
|
|
flash(lxc_name+' destroyed', category='success')
|
|
except Exception as e:
|
|
flash('Error destroying '+lxc_name+':'+str(e), category='danger')
|
|
return redirect(url_for('state'))
|
|
|
|
############################
|
|
## VM ##
|
|
############################
|
|
@app.route('/start_vm',methods=['POST'])
|
|
@login_required
|
|
def startvm():
|
|
vm_name = request.form['start']
|
|
try:
|
|
start_vm(vm_name)
|
|
flash(vm_name+' started', category='success')
|
|
except Exception as e:
|
|
flash('Error starting '+vm_name+':'+str(e), category='danger')
|
|
return redirect(url_for('state'))
|
|
|
|
@app.route('/stop_vm',methods=['POST'])
|
|
@login_required
|
|
def stopvm():
|
|
vm_name = request.form['stop']
|
|
try:
|
|
stop_vm(vm_name)
|
|
flash(vm_name+' stoped', category='success')
|
|
except Exception as e:
|
|
flash('Error stoping '+vm_name+':'+str(e), category='danger')
|
|
return redirect(url_for('state'))
|
|
|
|
@app.route('/destroy_vm',methods=['POST'])
|
|
@login_required
|
|
def destroyvm():
|
|
vm_name = request.form['destroy']
|
|
try:
|
|
destroy_vm(vm_name)
|
|
flash(vm_name+' destroyed', category='success')
|
|
except Exception as e:
|
|
flash('Error destroying '+vm_name+':'+str(e), category='danger')
|
|
return redirect(url_for('state'))
|
|
|
|
############################
|
|
## LOGIN ##
|
|
############################
|
|
#Self certificate page
|
|
if __name__=='__main__':
|
|
app.run(host=flask_config.host, port=flask_config.port, threaded=flask_config.thread, debug=flask_config.debug, ssl_context='adhoc')
|