ogcp/ogcp/views.py

2841 lines
102 KiB
Python

# Copyright (C) 2020-2021 Soleta Networks <info@soleta.eu>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the
# Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
from flask import (
g, render_template, url_for, flash, redirect, request, jsonify, make_response
)
from ogcp.forms.action_forms import (
WOLForm, SetupForm, ClientDetailsForm, ImageDetailsForm, HardwareForm,
SessionForm, ImageRestoreForm, ImageCreateForm, SoftwareForm, BootModeForm,
RoomForm, DeleteRoomForm, CenterForm, DeleteCenterForm, OgliveForm,
GenericForm, SelectClientForm, ImageUpdateForm, ImportClientsForm,
ServerForm, DeleteRepositoryForm, RepoForm, FolderForm
)
from flask_login import (
current_user, LoginManager,
login_user, logout_user,
login_required
)
from pathlib import Path
from ogcp.models import User
from ogcp.forms.auth import LoginForm, UserForm, DeleteUserForm
from ogcp.og_server import OGServer, servers
from flask_babel import lazy_gettext as _l
from flask_babel import gettext, _
from ogcp import app, ogcp_cfg_path
import requests
import datetime
import hashlib
import json
import os
import re
from collections import deque
FS_CODES = {
0: 'DISK',
1: 'EMPTY',
2: 'CACHE',
6: 'EXT4',
9: 'FAT32',
13: 'NTFS',
18: 'EXFAT',
19: 'LINUX-SWAP'
}
PART_TYPE_CODES = {
0: 'EMPTY',
1: 'DISK',
5: 'EXTENDED',
7: 'NTFS',
11: 'FAT32',
23: 'HNTFS',
27: 'HFAT32',
39: 'HNTFS-WINRE',
130: 'LINUX-SWAP',
131: 'LINUX',
142: 'LINUX-LVM',
202: 'CACHE',
218: 'DATA',
253: 'LINUX-RAID',
1792: 'NTFS',
9984: 'WIN-RECOV',
33280: 'LINUX-SWAP',
33536: 'LINUX',
36352: 'LINUX-LVM',
51712: 'CACHE',
61184: 'EFI',
64768: 'LINUX-RAID',
65535: 'UNKNOWN'
}
PART_SCHEME_CODES = {
0: 'EMPTY',
1: 'MSDOS',
2: 'GPT'
}
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
class ServerError(Exception):
pass
class ServerErrorCode(Exception):
pass
def ogserver_down(view):
flash(_('Cannot talk to ogserver. Is ogserver down?'), category='error')
return redirect(url_for(view))
def ogserver_error(view):
flash(_('ogserver replied with a bad HTTP status code'), category='error')
return redirect(url_for(view))
def validate_elements(elements, min_len=1, max_len=float('inf')):
valid = True
if len(elements) < min_len:
flash(_('Please, select at least {} element(s)').format(min_len),
category='error')
valid = not valid
elif len(elements) > max_len:
flash(_('No more than {} element(s) can be selected for the given action').format(max_len),
category='error')
valid = not valid
return valid
def parse_elements(checkboxes_dict):
unwanted_elements = ['csrf_token', 'scope-server', 'scope-center',
'scope-room', 'image-server', 'repos-server', 'folder']
elements = set()
for key, elements_list in checkboxes_dict.items():
if key not in unwanted_elements:
elements.update(elements_list.split(' '))
return elements
def get_client_setup(ip):
server = get_server_from_clients([ip])
payload = {'clients': [ip]}
r = server.post('/refresh', payload)
if not r:
raise ServerError
if r.status_code != requests.codes.ok:
raise ServerErrorCode
payload = {'client': [ip]}
r = server.get('/client/setup', payload)
if not r:
raise ServerError
if r.status_code != requests.codes.ok:
raise ServerErrorCode
db_partitions = r.json()['partitions']
for partition in db_partitions:
if partition['partition'] == 0:
partition['code'] = PART_SCHEME_CODES.get(partition['code'], 'MSDOS')
else:
partition['code'] = PART_TYPE_CODES.get(partition['code'], 'EMPTY')
partition['filesystem'] = FS_CODES.get(partition['filesystem'], 'EMPTY')
return db_partitions
def get_clients(state_filter=None):
responses = multi_request('get', '/clients')
clients_list = []
for r in responses:
clients_list = clients_list + r['json']['clients']
clients = {}
clients['clients'] = clients_list
if state_filter:
return filter(clients.items(), lambda c: c.state == state_filter)
return clients
def get_repository(repository_id, server):
try:
repositories = get_repositories(server)
except ServerError:
raise ServerError
except ServerErrorCode:
raise ServerErrorCode
[repository] = [repository for repository in repositories
if repository['id'] == repository_id]
return repository
def get_repositories(server):
r = server.get('/repositories')
if not r:
raise ServerError
if r.status_code != requests.codes.ok:
raise ServerErrorCode
repositories = r.json()['repositories']
return repositories
def parse_scopes_from_tree(tree, scope_type):
scopes = []
for scope in tree['scope']:
if scope['type'] == scope_type:
if 'name' in tree:
scope['parent'] = tree['name']
scopes.append(scope)
else:
scopes += parse_scopes_from_tree(scope, scope_type)
return scopes
def add_state_and_ips(scope, clients, ips):
scope['selected'] = False
if 'ip' in scope:
filtered_client = filter(lambda x: x['addr']==scope['ip'], clients)
client = next(filtered_client, False)
if client:
scope['state'] = client['state']
scope['link'] = client.get('speed')
scope['last_cmd'] = {}
scope['last_cmd']['result'] = client.get('last_cmd').get('result')
else:
scope['state'] = 'off'
scope['ip'] = [scope['ip']]
scope['selected'] = set(scope['ip']).issubset(ips)
else:
scope['ip'] = []
for child in scope['scope']:
scope['ip'] += add_state_and_ips(child, clients, ips)
scope['selected'] = (len(scope['ip']) < 0 and
set(scope['ip']).issubset(ips))
return scope['ip']
def get_allowed_scopes(scopes, allowed_scopes):
for scope in scopes.get('scope'):
if scope.get('name') in current_user.scopes:
allowed_scopes.append(scope)
else:
get_allowed_scopes(scope, allowed_scopes)
def multi_request(method, uri, payload=None):
responses = []
for server in servers:
response = {}
if method == 'get':
r = server.get(uri, payload)
elif method == 'post':
r = server.post(uri, payload)
else:
raise Exception('Invalid method, use get or post')
if not r:
continue
response['server'] = server
response['json'] = r.json()
responses.append(response)
return responses
def get_server_from_clients(selected_clients):
server = None
responses = multi_request('get', '/scopes')
for r in responses:
server_clients = [c['ip'] for c in parse_scopes_from_tree(r['json'],
'computer')]
if all(client in server_clients for client in selected_clients):
server = r['server']
break
if not server:
raise Exception('Selected clients not found in any server')
return server
def get_server_from_ip_port(str_ip_port):
ip, port = str_ip_port.split(':')
for s in servers:
if s.ip == ip and s.port == int(port):
return s
raise Exception('Server with address ' + str_ip_port + 'is not configured')
def sort_scopes_recursive(ls_scopes):
for scope in ls_scopes:
if len(scope['scope']) != 0:
sorted_ls = sort_scopes_recursive(scope['scope'])
scope['scope'] = sorted_ls
return sorted(ls_scopes, key=lambda s: s['name'].lower())
def sort_scopes(scopes):
all_scopes={}
ls = sort_scopes_recursive(scopes['scope'])
all_scopes['scope'] = ls
return all_scopes
def get_scopes(ips=set()):
list_scopes = []
responses = multi_request('get', '/scopes')
for r in responses:
scopes = r['json']
server_scope = {}
server_scope['name'] = r['server'].name
server_scope['type'] = "server"
server_scope['server_ip_port'] = (r['server'].ip + ":" +
str(r['server'].port))
server_scope.update(scopes)
list_scopes.append(server_scope)
all_scopes = {'scope': list_scopes}
all_scopes = sort_scopes(all_scopes)
if current_user.scopes:
allowed_scopes = []
get_allowed_scopes(all_scopes, allowed_scopes)
all_scopes = {'scope': allowed_scopes}
clients = get_clients()
add_state_and_ips(all_scopes, clients['clients'], ips)
return all_scopes, clients
def hash_password(pwd):
sha = hashlib.sha512()
sha.update(pwd.encode())
pwd_hash = sha.hexdigest()
return pwd_hash
def authenticate_user(username, pwd):
for user in app.config['USERS']:
if user.get("USER") == username:
if user.get("PASS") == pwd:
return user
else:
flash(_('Incorrect password'))
return None
flash(_('Incorrect user name'))
return None
def get_user(username):
for user in app.config['USERS']:
if user.get("USER") == username:
return user
return None
intervals = (
(_l('days'), 86400), # 60 * 60 * 24
(_l('hours'), 3600), # 60 * 60
(_l('minutes'), 60),
)
def display_time(seconds):
result = []
for name, count in intervals:
value = seconds // count
if value:
seconds -= value * count
result.append("{} {}".format(value, name))
return ', '.join(result)
@login_manager.user_loader
def load_user(username):
user_dict = get_user(username)
if not user_dict:
return None
user = User(username, user_dict.get('SCOPES'), user_dict.get('ADMIN'))
return user
@app.errorhandler(404)
def page_not_found(error):
return render_template('error.html', message=error), 404
@app.errorhandler(500)
def server_error(error):
return render_template('error.html', message=error), 500
def image_modified_date_from_str(image):
return datetime.datetime.strptime(image['modified'], '%a %b %d %H:%M:%S %Y')
@app.route('/')
def index():
if not current_user.is_authenticated:
return redirect(url_for('login'))
images_response = multi_request('get', '/images')
dashboard_servers = {}
for i in images_response:
server_name = i['server'].name
server_id = i['server'].id
images = i['json']['images']
images.sort(key=image_modified_date_from_str, reverse=True)
disk = i['json']['disk']
if server_name not in dashboard_servers:
dashboard_servers[server_id] = {'name': server_name}
dashboard_servers[server_id]['images'] = images
dashboard_servers[server_id]['disk'] = disk
oglive_list = multi_request('get', '/oglive/list')
for i in oglive_list:
server_id = i['server'].id
dashboard_servers[server_id]['oglive_list'] = i['json']
all_stats = multi_request('get', '/stats')
for server in servers:
active = False
stat = None
for i in all_stats:
if i['server'].id == server.id:
active = True
stat = i
break
if active:
server_id = stat['server'].id
dashboard_servers[server_id]['online'] = True
stats = stat['json']
dashboard_servers[server_id]['stats'] = stats
timestamp = datetime.datetime.fromtimestamp(stats.get('time').get('now'))
now = timestamp.strftime('%Y-%m-%d %H:%M:%S')
boot = display_time(stats.get('time').get('boot'))
start = display_time(stats.get('time').get('start'))
time_dict = {'now': now, 'boot': boot, 'start': start}
dashboard_servers[server_id]['time_dict'] = time_dict
else:
dashboard_servers[server.id] = {}
dashboard_servers[server.id]['online'] = False
dashboard_servers[server.id]['name'] = server.name
dashboard_servers[server.id]['time_dict'] = {'now': None, 'boot': 'Offline', 'start': None}
dashboard_servers[server.id]['clients'] = []
dashboard_servers[server.id]['images'] = []
dashboard_servers[server.id]['disk'] = {'total': 0, 'free':0}
dashboard_servers[server.id]['stats'] = {'memory': {'free': 0, 'size':0}, 'swap': ''}
dashboard_servers[server.id]['oglive_list'] = []
clients_response = multi_request('get', '/clients')
for i in clients_response:
server_id = i['server'].id
dashboard_servers[server_id]['clients'] = i['json']['clients']
now = timestamp.strftime('%Y-%m-%d %H:%M:%S')
return render_template('dashboard.html', servers=dashboard_servers, now=now, colsize="6")
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
form_user = request.form['user']
pwd = request.form['pwd']
pwd_hash = hash_password(pwd)
user_dict = authenticate_user(form_user, pwd_hash)
if not user_dict:
return render_template('auth/login.html', form=form)
user = User(form_user, user_dict.get('SCOPES'), user_dict.get('ADMIN'))
login_user(user)
return redirect(url_for('index'))
return render_template('auth/login.html', form=LoginForm())
@app.route("/logout")
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/scopes/status')
@login_required
def scopes_status():
scopes, _clients = get_scopes()
return jsonify(scopes)
@app.route('/client/mac', methods=['GET'])
@login_required
def get_client_mac():
ip = parse_elements(request.args.to_dict())
payload = {'client': list(ip)}
server = get_server_from_clients(list(ip))
resp = server.get('/client/info', payload)
client_info = resp.json()
mac = client_info.get('mac')
pretty_mac = (':'.join(mac[i:i+2] for i in range(0, 12, 2))).upper()
return jsonify(pretty_mac)
@app.route('/scopes/')
@login_required
def scopes():
scopes, clients = get_scopes()
return render_template('scopes.html', scopes=scopes, clients=clients)
@app.route('/action/poweroff', methods=['GET', 'POST'])
@login_required
def action_poweroff():
form = GenericForm(request.form)
if request.method == 'POST':
ips = form.ips.data.split(' ')
if not validate_elements(ips):
return redirect(url_for('commands'))
payload = {'clients': ips}
server = get_server_from_clients(ips)
r = server.post('/poweroff', payload)
if r.status_code != requests.codes.ok:
flash(_('ogServer: error powering off client'),
category='error')
else:
flash(_('Client powered off successfully'),
category='info')
return redirect(url_for('commands'))
else:
ips = parse_elements(request.args.to_dict())
form.ips.data = " ".join(ips)
if validate_elements(ips):
scopes, clients = get_scopes(set(ips))
selected_clients = list(get_selected_clients(scopes['scope']).items())
return render_template('actions/poweroff.html', form=form,
selected_clients=selected_clients,
scopes=scopes)
else:
return redirect(url_for('commands'))
@app.route('/action/wol', methods=['GET', 'POST'])
@login_required
def action_wol():
form = WOLForm(request.form)
if request.method == 'POST' and form.validate():
wol_type = form.wol_type.data
ips = form.ips.data.split(' ')
payload = {'type': wol_type, 'clients': ips}
server = get_server_from_clients(ips)
server.post('/wol', payload)
flash(_('Wake On Lan request sent successfully'), category='info')
return redirect(url_for('commands'))
else:
ips = parse_elements(request.args.to_dict())
form.ips.data = " ".join(ips)
if validate_elements(ips, min_len=1):
scopes, clients = get_scopes(set(ips))
selected_clients = list(get_selected_clients(scopes['scope']).items())
return render_template('actions/wol.html', form=form,
selected_clients=selected_clients,
scopes=scopes)
else:
return redirect(url_for('commands'))
@app.route('/action/setup/select', methods=['GET'])
@login_required
def action_setup_select():
args = request.args.copy()
ips = parse_elements(args.to_dict())
if not validate_elements(ips):
return redirect(url_for('commands'))
if len(ips) == 1:
ip = list(ips)[0]
return redirect(url_for('action_setup_show', ip=ip))
form = SelectClientForm()
form.ips.data = " ".join(ips)
server = get_server_from_clients(list(ips))
client_choices = []
for ip in ips:
r = server.get('/client/info', payload={'client': [ip]})
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
client_name = r.json()['name']
client_choices.append((ip, f"{client_name} ({ip})"))
form.selected_client.choices = client_choices
scopes, unused = get_scopes(ips)
selected_clients = list(get_selected_clients(scopes['scope']).items())
return render_template('actions/select_client.html',
selected_clients=selected_clients,
form=form, scopes=scopes)
@app.route('/action/setup', methods=['GET'])
@login_required
def action_setup_show():
args = request.args.copy()
default_disk = 1
selected_disk = int(args.pop('disk', default_disk))
if args.get('ip'):
ips = {args['ip']}
ips_str = base_client = args['ip']
else:
ips_str = args['ips']
ips = set(args['ips'].split(' '))
base_client = args['selected_client']
try:
db_partitions = get_client_setup(base_client)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
if not db_partitions:
flash(_('Partition information is not available. Boot client in ogLive mode to obtain it'), category='error')
return redirect(url_for('commands'))
filtered_partitions = [p for p in db_partitions
if p.get('disk') == selected_disk]
disk_partition = 0
disks = [d.get('disk') for d in db_partitions
if d.get('partition') == disk_partition]
form = SetupForm()
form.ips.data = ips_str
form.disk.data = selected_disk
# If partition table is empty, set MSDOS
form.disk_type.data = filtered_partitions[0]['code'] or 1
disk_size = filtered_partitions[0]['size'] // 1024
# Make form.partition length equal to (filtered_partitions - 1) length
diff = len(filtered_partitions) - 1 - len(form.partitions)
[form.partitions.append_entry() for unused in range(diff)]
for partition, db_part in zip(form.partitions, filtered_partitions[1:]):
partition.partition.data = str(db_part['partition'])
partition.part_type.data = db_part['code']
partition.fs.data = db_part['filesystem']
partition.size.data = db_part['size'] // 1024
scopes, _clients = get_scopes(ips)
return render_template('actions/setup.html',
selected_disk=selected_disk,
disks=disks,
form=form,
disk_size=disk_size,
ips=ips_str,
base_client=base_client,
scopes=scopes)
@app.route('/action/setup', methods=['POST'])
@login_required
def action_setup_modify():
form = SetupForm(request.form)
if form.validate():
cache_count = 0
for partition in form.partitions:
if partition.part_type.data == 'CACHE':
cache_count += 1
if cache_count == 0:
flash(_(f'Missing cache partition'), category='error')
return redirect(url_for('commands'))
if cache_count > 1:
flash(_(f'More than one cache partition is not supported'), category='error')
return redirect(url_for('commands'))
ips = form.ips.data.split(' ')
payload = {'clients': ips,
'disk': str(form.disk.data),
'type': str(form.disk_type.data),
'cache': str(0),
'cache_size': str(0),
'partition_setup': []}
partition_index = 0
for partition in form.partitions:
partition_index += 1
partition_setup = {'partition': str(partition_index),
'code': str(partition.part_type.data),
'filesystem': str(partition.fs.data),
'size': str(partition.size.data * 1024),
'format': '1'}
payload['partition_setup'].append(partition_setup)
if partition.part_type.data == 'CACHE':
payload['cache'] = '1'
payload['cache_size'] = str(partition.size.data * 1024)
for partition_index in range(len(form.partitions) + 1, 5):
empty_part = {
'partition': str(partition_index),
'code': 'EMPTY',
'filesystem': 'EMPTY',
'size': '0',
'format': '1',
}
payload['partition_setup'].append(empty_part)
server = get_server_from_clients(list(ips))
r = server.post('/setup', payload=payload)
if r.status_code == requests.codes.ok:
return redirect(url_for('commands'))
flash(_(f'Invalid setup form'), category='error')
return redirect(url_for('commands'))
def search_image(images_list, image_id):
for image in images_list:
if image['id'] == image_id:
return image
return False
def get_images_grouped_by_repos_from_server(server):
r = server.get('/images')
if not r:
raise ServerError
if r.status_code != requests.codes.ok:
raise ServerErrorCode
images = r.json()['images']
repos={}
for image in images:
repo_id=image['repo_id']
if repo_id not in repos:
repos[repo_id] = [image]
else:
repos[repo_id].append(image)
return repos
def get_clients_repo(server, ips):
repo_id=None
for ip in ips:
r = server.get('/client/info', payload={'client': [ip]})
if not r:
raise ServerError
if r.status_code != requests.codes.ok:
raise ServerErrorCode
repo_id_aux = r.json()['repo_id']
if repo_id is None:
repo_id = repo_id_aux
elif repo_id_aux != repo_id:
return None
return repo_id
@app.route('/action/image/restore', methods=['GET', 'POST'])
@login_required
def action_image_restore():
form = ImageRestoreForm(request.form)
if request.method == 'POST':
ips = form.ips.data.split(' ')
disk, partition, part_code, part_size = form.partition.data.split(' ')
part_type = PART_TYPE_CODES.get(int(part_code), 'UNKNOWN')
invalid_part_types = ['EMPTY', 'LINUX-SWAP', 'CACHE', 'EFI', 'WIN-RECOV']
if part_type in invalid_part_types:
flash(_(f'Cannot restore image on partition type {part_type}'), category='error')
return redirect(url_for('commands'))
image_id = form.image.data
server = get_server_from_clients(ips)
r = server.get('/images')
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
images_list = r.json()['images']
image = search_image(images_list, int(image_id))
if not image:
flash(_(f'Image to restore was not found'), category='error')
return redirect(url_for('commands'))
image_datasize = int(image['datasize'])
part_size = int(part_size) * 1024
if image_datasize > part_size:
flash(_(f'The image size is bigger than the target partition'), category='error')
return redirect(url_for('commands'))
try:
repository = get_repository(image['repo_id'], server)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
payload = {'disk': disk,
'partition': partition,
'name': image['name'],
'repository': repository['ip'],
'clients': ips,
'type': form.method.data,
'profile': str(image['software_id']),
'id': str(image['id'])}
server.post('/image/restore', payload)
if r.status_code == requests.codes.ok:
flash(_(f'Image restore command sent sucessfully'), category='info')
else:
flash(_(f'There was a problem sending the image restore command'), category='error')
return redirect(url_for('commands'))
else:
ips = parse_elements(request.args.to_dict())
if not validate_elements(ips):
return redirect(url_for('commands'))
form.ips.data = ' '.join(ips)
server = get_server_from_clients(ips)
try:
repo_id = get_clients_repo(server, ips)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
if repo_id is None:
flash(_(f'Computers have different repos assigned'), category='error')
return redirect(url_for('commands'))
try:
images = get_images_grouped_by_repos_from_server(server)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
if repo_id not in images:
flash(_(f'Computer(s) assigned to a repo with no images'), category='error')
return redirect(url_for('commands'))
for image in images[repo_id]:
form.image.choices.append((image['id'], image['name']))
part_choices = []
for ip in ips:
r = server.get('/client/setup', payload={'client': [ip]})
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
partitions = r.json()['partitions']
parts = []
for partition in partitions:
disk_id = partition['disk']
part_id = partition['partition']
if part_id == 0: # This is the disk data, not a partition.
continue
part_code = partition['code']
filesystem = partition['filesystem']
part_size = partition['size']
choice_value = (disk_id, part_id, part_code, filesystem, part_size)
parts.append(choice_value)
if not part_choices: # Use first computer as reference part setup conf
part_choices = [part for part in parts]
elif part_choices != parts:
flash(_(f'Computers have different partition setup'), category='error')
return redirect(url_for('commands'))
form.partition.choices = [
(f"{disk_id} {part_id} {part_code} {part_size}",
f"Disk {disk_id} | Partition {part_id} "
f"| {PART_TYPE_CODES.get(part_code, 'UNKNOWN')} "
f"{FS_CODES.get(filesystem, 'UNKNOWN')}")
for disk_id, part_id, part_code, filesystem, part_size in part_choices ]
scopes, clients = get_scopes(set(ips))
selected_clients = list(get_selected_clients(scopes['scope']).items())
return render_template('actions/image_restore.html', form=form,
selected_clients=selected_clients,
scopes=scopes)
@app.route('/action/hardware', methods=['GET', 'POST'])
@login_required
def action_hardware():
form = HardwareForm(request.form)
if request.method == 'POST':
ips = form.ips.data.split(' ')
server = get_server_from_clients(ips)
r = server.post('/hardware', payload={'clients': ips})
if r.status_code == requests.codes.ok:
flash(_(f'Hardware inventory command has been sent'), category='info')
else:
flash(_(f'There was a problem sending the hardware inventory command'), category='error')
return redirect(url_for('commands'))
else:
ips = parse_elements(request.args.to_dict())
scopes, _clients = get_scopes(ips)
if not validate_elements(ips, max_len=1):
return redirect(url_for('commands'))
form.ips.data = ' '.join(ips)
server = get_server_from_clients(ips)
r = server.get('/hardware', payload={'client': list(ips)})
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
hardware = r.json()['hardware']
return render_template('actions/hardware.html', form=form,
hardware=hardware, scopes=scopes)
@app.route('/action/software', methods=['GET', 'POST'])
@login_required
def action_software():
form = SoftwareForm(request.form)
if request.method == 'POST':
ips = form.ips.data.split(' ')
disk, partition = form.os.data.split(' ')
server = get_server_from_clients(ips)
if form.view.data:
r = server.get('/software', payload={'client': ips,
'disk': int(disk),
'partition': int(partition)})
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
software = r.json()['software']
scopes, clients = get_scopes(set(ips))
return render_template('actions/software_list.html',
software=software, form=form, scopes=scopes)
elif form.update.data:
r = server.post('/software', payload={'clients': ips,
'disk': disk,
'partition': partition})
if r.status_code == requests.codes.ok:
flash(_('Software profile request sent successfully'), category='info')
else:
flash(_('Error processing software profile request: ({})').format(r.status), category='error')
else:
flash(_('Error processing software profile form'), category='error')
return redirect(url_for('commands'))
else:
ips = parse_elements(request.args.to_dict())
scopes, clients = get_scopes(set(ips))
if not validate_elements(ips, max_len=1):
return redirect(url_for('commands'))
form.ips.data = ' '.join(ips)
server = get_server_from_clients(ips)
r = server.get('/client/setup', payload={'client': list(ips)})
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
if not r.json()['partitions']:
flash(_('Software inventory is not available. Boot client in ogLive mode to obtain it'), category='error')
return redirect(url_for('commands'))
for part in r.json()['partitions'][1:]:
form.os.choices.append(
(f"{part.get('disk')} {part.get('partition')}",
f"Disk {part.get('disk')} | Partition {part.get('partition')} "
f"| {PART_TYPE_CODES.get(part.get('code'), 'UNKNOWN')} "
f"{FS_CODES.get(part.get('filesystem'), 'UNKNOWN')}")
)
return render_template('actions/software.html', form=form, scopes=scopes)
@app.route('/action/session', methods=['GET', 'POST'])
@login_required
def action_session():
form = SessionForm(request.form)
if request.method == 'POST':
ips = form.ips.data.split(' ')
disk, partition = form.os.data.split(' ')
server = get_server_from_clients(list(ips))
r = server.post('/session', payload={'clients': ips,
'disk': str(disk),
'partition': str(partition)})
if r.status_code == requests.codes.ok:
return redirect(url_for('commands'))
return make_response("400 Bad Request", 400)
else:
ips = parse_elements(request.args.to_dict())
if not validate_elements(ips, max_len=1):
return redirect(url_for('commands'))
server = get_server_from_clients(list(ips))
form.ips.data = ' '.join(ips)
r = server.get('/session', payload={'client': list(ips)})
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
sessions = r.json()['sessions']
if not sessions:
flash(_('ogServer returned an empty session list'),
category='error')
return redirect(url_for('commands'))
for os in sessions:
choice = (f"{os['disk']} {os['partition']}",
f"OS: {os['name']} (Disk:{os['disk']}, Partition:{os['partition']})")
form.os.choices.append(choice)
scopes, clients = get_scopes(set(ips))
selected_clients = list(get_selected_clients(scopes['scope']).items())
return render_template('actions/session.html', form=form,
selected_clients=selected_clients,
scopes=scopes)
@app.route('/action/client/info', methods=['GET'])
@login_required
def action_client_info():
form = ClientDetailsForm()
ips = parse_elements(request.args.to_dict())
if not validate_elements(ips, max_len=1):
return redirect(url_for('commands'))
server = get_server_from_clients(list(ips))
payload = {'client': list(ips)}
r = server.get('/client/info', payload)
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
db_client = r.json()
form.name.data = db_client['name']
form.name.render_kw = {'readonly': True}
form.ip.data = db_client['ip']
form.ip.render_kw = {'readonly': True}
form.mac.data = db_client['mac']
form.mac.render_kw = {'readonly': True}
form.serial_number.data = db_client['serial_number']
form.serial_number.render_kw = {'readonly': True}
form.livedir.choices = [(db_client['livedir'], db_client['livedir'])]
form.livedir.render_kw = {'readonly': True}
form.remote.data = db_client['remote']
form.remote.render_kw = {'readonly': True}
form.maintenance.data = db_client['maintenance']
form.maintenance.render_kw = {'readonly': True}
try:
repositories = get_repositories(server)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
form.repo.choices = [(repo["id"], repo["name"]) for repo in repositories
if db_client['repo_id'] == repo["id"]]
form.repo.render_kw = {'readonly': True}
form.room.data = db_client['room']
form.room.render_kw = {'readonly': True}
form.boot.choices = [(db_client['boot'], db_client['boot'])]
form.boot.render_kw = {'readonly': True}
r = server.get('/scopes')
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
rooms = parse_scopes_from_tree(r.json(), 'room')
rooms = [(room['id'], room['name']) for room in rooms
if room['id'] == int(db_client['room'])]
form.room.choices = list(rooms)
form.submit.render_kw = {"style": "visibility:hidden;"}
r = server.get('/images')
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
images = r.json()['images']
ip = list(ips)[0]
try:
setup = get_client_setup(ip)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
if setup and setup[0].get('code') == 'MSDOS':
setup[0]['code'] = 'MBR'
for entry in setup:
if images and entry['image'] != 0:
image = next((img for img in images if img['id'] == entry['image']), None)
if image:
entry['image'] = image['name']
else:
entry['image'] = ""
else:
entry['image'] = ""
scopes, clients = get_scopes(set(ips))
return render_template('actions/client_details.html', form=form,
parent="commands.html", scopes=scopes, setup=setup)
@app.route('/action/client/update', methods=['GET', 'POST'])
@login_required
def action_client_update():
form = ClientDetailsForm(request.form)
if request.method == 'POST':
payload = {"ip": form.ip.data,
"serial_number": form.serial_number.data,
"netdriver": "generic",
"maintenance": form.maintenance.data,
"netiface": "eth0",
"repo_id": int(form.repo.data),
"netmask": "0",
"remote": form.remote.data,
"room": int(form.room.data),
"name": form.name.data,
"boot": form.boot.data,
"mac": form.mac.data }
server = get_server_from_ip_port(form.server.data)
r = server.post('/client/update', payload)
if r.status_code != requests.codes.ok:
flash(_('ogServer: error updating client'),
category='error')
else:
flash(_('Client updated successfully'), category='info')
return redirect(url_for("scopes"))
else:
ips = parse_elements(request.args.to_dict())
if not validate_elements(ips, max_len=1):
flash(_('Please, select one client'), category='error')
return redirect(url_for('scopes'))
server = get_server_from_clients(list(ips))
scopes, clients = get_scopes()
payload = {'client': list(ips)}
r = server.get('/client/info', payload)
if not r:
return ogserver_down('scopes')
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
db_client = r.json()
form.mac.render_kw = {'readonly': True}
form.server.data = "{0}:{1}".format(server.ip, server.port)
form.ip.data = db_client['ip']
form.ip.render_kw = {'readonly': True}
form.name.data = db_client['name']
form.mac.data = db_client['mac']
form.serial_number.data = db_client['serial_number']
form.livedir.render_kw = {'readonly': True}
form.remote.data = db_client['remote']
form.maintenance.data = db_client['maintenance']
current_mode = db_client['boot']
r = server.get('/mode')
if not r:
return ogserver_down('scopes')
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
available_modes = [(current_mode, current_mode)]
available_modes.extend([(mode, mode) for mode in r.json()['modes']
if mode != current_mode])
form.boot.choices = list(available_modes)
form.boot.render_kw = {'readonly': True}
r = server.get('/scopes')
if not r:
return ogserver_down('scopes')
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
room_id = db_client['room']
rooms = parse_scopes_from_tree(r.json(), 'room')
rooms = [(room['id'], room['name']) for room in rooms
if room['id'] == int(room_id)]
form.room.choices = list(rooms)
form.room.render_kw = {'readonly': True}
try:
repositories = get_repositories(server)
except ServerError:
return ogserver_down('scopes')
except ServerErrorCode:
return ogserver_error('scopes')
form.repo.choices = [(repo["id"], repo["name"]) for repo in repositories
if db_client['repo_id'] == repo["id"]]
form.repo.choices.extend([(repo["id"], repo["name"]) for repo in repositories
if db_client['repo_id'] != repo["id"]])
ip = list(ips)[0]
try:
setup = get_client_setup(ip)
except ServerError:
return ogserver_down('scopes')
except ServerErrorCode:
return ogserver_error('scopes')
if setup and setup[0].get('code') == 'MSDOS':
setup[0]['code'] = 'MBR'
r = server.get('/images')
if not r:
return ogserver_down('scopes')
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
images = r.json()['images']
for entry in setup:
if images and entry['image'] != 0:
image = next((img for img in images if img['id'] == entry['image']), None)
if image:
entry['image'] = image['name']
else:
entry['image'] = ""
else:
entry['image'] = ""
form.submit.render_kw = {"formaction": url_for('action_client_update')}
return render_template('actions/client_details.html', form=form,
parent="scopes.html", scopes=scopes, setup=setup)
def find_folder(folder_id, scopes):
scopes = deque([scopes['scope']])
while scopes:
scope_ls = scopes.popleft()
if not scope_ls:
continue
else:
for scope in scope_ls:
if scope['type'] == 'folder' and scope['id'] == folder_id:
return scope
else:
scopes.append(scope['scope'])
return None
@app.route('/action/folder/delete', methods=['GET', 'POST'])
@login_required
def action_folder_delete():
form = FolderForm(request.form)
if request.method == 'POST':
payload = {"id": int(form.folder_id.data)}
server = get_server_from_ip_port(form.server.data)
r = server.post('/folder/delete', payload)
if r.status_code != requests.codes.ok:
flash(_('ogServer: error deleting folder'),
category='error')
else:
flash(_('Folder deleted successfully'), category='info')
return redirect(url_for("scopes"))
else:
params = request.args.to_dict()
folder_id = params.get('folder')
if not folder_id:
flash(_('Please, select a folder'), category='error')
return redirect(url_for('scopes'))
form.folder_id.data = folder_id
form.server.data = params['scope-server']
form.submit.render_kw = {"formaction": url_for('action_folder_delete')}
scopes, unused = get_scopes()
ancestors, children = get_scope_context(int(folder_id), 'folder', scopes)
del form.name
return render_template('actions/folder_delete.html', form=form,
parent="scopes.html", scopes=scopes, ancestors=ancestors, children=children)
@app.route('/action/folder/update', methods=['GET','POST'])
def action_folder_update():
form = FolderForm(request.form)
if request.method == 'POST':
payload = {'name': form.name.data,
'id': int(form.folder_id.data)}
server = get_server_from_ip_port(form.server.data)
r = server.post('/folder/update', payload)
if r.status_code != requests.codes.ok:
flash(_('ogServer: error updating folder'),
category='error')
else:
flash(_('Folder updated successfully'), category='info')
return redirect(url_for("scopes"))
else:
params = request.args.to_dict()
folder_id = int(params.get('folder'))
if not folder_id:
flash(_('Please, select a folder to modify'), category='error')
return redirect(url_for('scopes'))
scopes, clients = get_scopes()
folder = find_element_scope(folder_id, 'folder', scopes)
form.server.data = params['scope-server']
form.name.data = folder['name']
form.folder_id.data = folder_id
form.submit.render_kw = {"formaction": url_for('action_folder_update')}
return render_template('actions/folder_update.html', form=form,
parent="scopes.html", scopes=scopes)
@app.route('/action/folder/add', methods=['GET'])
@login_required
def action_folder_add():
form = FolderForm()
params = request.args.to_dict()
room = params.get('scope-room')
center = params.get('scope-center')
if room and center:
flash(_('Please, select either a room or a center'), category='error')
return redirect(url_for('scopes'))
if not room and not center:
flash(_('Please, select a room or a center'), category='error')
return redirect(url_for('scopes'))
if params.get('folder'):
flash(_('Error: A folder has been selected. Please, select a room or a center'), category='error')
return redirect(url_for('scopes'))
form.server.data = params['scope-server']
form.room.data = room
form.center.data = center
form.submit.render_kw = {"formaction": url_for('action_folder_add_post')}
scopes, unused = get_scopes()
return render_template('actions/folder_add.html', form=form,
parent="scopes.html", scopes=scopes)
@app.route('/action/folder/add', methods=['POST'])
def action_folder_add_post():
form = FolderForm(request.form)
payload = {"name": form.name.data}
if form.center.data:
payload["center"] = int(form.center.data)
if form.room.data:
payload["room"] = int(form.room.data)
server = get_server_from_ip_port(form.server.data)
r = server.post('/folder/add', payload)
if r.status_code != requests.codes.ok:
flash(_('ogServer: error adding folder'),
category='error')
else:
flash(_('Folder added successfully'), category='info')
return redirect(url_for("scopes"))
@app.route('/action/client/add', methods=['GET', 'POST'])
@login_required
def action_client_add():
form = ClientDetailsForm(request.form)
if request.method == 'POST':
payload = {"boot": form.boot.data,
"ip": form.ip.data,
"livedir": form.livedir.data,
"mac": form.mac.data,
"maintenance": form.maintenance.data,
"name": form.name.data,
"netdriver": "generic",
"netiface": "eth0",
"netmask": "0",
"remote": form.remote.data,
"repo_id": int(form.repo.data),
"room": int(form.room.data),
"serial_number": form.serial_number.data,
"folder_id": int(form.folder_id.data) }
server = get_server_from_ip_port(form.server.data)
r = server.post('/client/add', payload)
if r.status_code != requests.codes.ok:
flash(_('ogServer: error adding client'),
category='error')
else:
flash(_('Client added successfully'), category='info')
return redirect(url_for("scopes"))
else:
params = request.args.to_dict()
if not params.get('scope-room'):
flash(_('Please, select a room or a folder'), category='error')
return redirect(url_for('scopes'))
form.server.data = params['scope-server']
server = get_server_from_ip_port(params['scope-server'])
r = server.get('/mode')
if not r:
return ogserver_down('scopes')
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
available_modes = [(mode, mode) for mode in r.json()['modes'] if mode == 'pxe']
available_modes.extend([(mode, mode) for mode in r.json()['modes'] if mode != 'pxe'])
form.boot.choices = list(available_modes)
form.mac.render_kw = {'placeholder': 'aabbccddeeaa'}
r = server.get('/scopes')
if not r:
return ogserver_down('scopes')
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
room_id = params['scope-room']
rooms = parse_scopes_from_tree(r.json(), 'room')
rooms = [(room['id'], room['name']) for room in rooms
if room['id'] == int(room_id)]
form.room.choices = list(rooms)
form.room.render_kw = {'readonly': True}
try:
repositories = get_repositories(server)
except ServerError:
return ogserver_down('scopes')
except ServerErrorCode:
return ogserver_error('scopes')
form.repo.choices = [(repo["id"], repo["name"]) for repo in repositories]
if params.get('folder'):
form.folder_id.data = params['folder']
else:
form.folder_id.data = 0
form.submit.render_kw = {"formaction": url_for('action_client_add')}
scopes, clients = get_scopes()
return render_template('actions/client_add.html', form=form,
parent="scopes.html", scopes=scopes)
PLACEHOLDER_TEXT = '''host example1 { hardware ethernet 94:c6:91:a6:25:1a; fixed-address 10.141.10.100; }
host example2 { hardware ethernet 94:c6:91:a6:25:1b; fixed-address 10.141.10.101; }
host example3 { hardware ethernet 94:c6:91:a6:25:1c; fixed-address 10.141.10.102; }
host example4 { hardware ethernet 94:c6:91:a6:25:1d; fixed-address 10.141.10.103; }
host example5 { hardware ethernet 94:c6:91:a6:25:1e; fixed-address 10.141.10.104; }
host example6 { hardware ethernet 94:c6:91:a6:25:1f; fixed-address 10.141.10.105; }
...'''
@app.route('/action/clients/import', methods=['GET'])
@login_required
def action_clients_import_get():
params = request.args.to_dict()
if not params.get('scope-room'):
flash(_('Please, select one room'), category='error')
return redirect(url_for('scopes'))
form = ImportClientsForm()
form.server.data = params['scope-server']
server = get_server_from_ip_port(params['scope-server'])
r = server.get('/scopes')
if not r:
return ogserver_down('scopes')
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
rooms = parse_scopes_from_tree(r.json(), 'room')
selected_room_id = params['scope-room']
selected_room = [(room['id'], room['name'] + " (" + room['parent'] + ")")
for room in rooms if room['id'] == int(selected_room_id)]
form.room.choices = selected_room
form.room.render_kw = {'readonly': True}
try:
repositories = get_repositories(server)
except ServerError:
return ogserver_down('scopes')
except ServerErrorCode:
return ogserver_error('scopes')
form.repo.choices = [(repo["id"], repo["name"]) for repo in repositories]
form.dhcpd_conf.render_kw = {'placeholder': PLACEHOLDER_TEXT}
scopes, _clients = get_scopes()
return render_template('actions/import_clients.html', form=form,
scopes=scopes)
OG_REGEX_DHCPD_CONF = (r'(?:\s*host\s*)'
r'([\w.-]*)'
r'(?:\s*{[ \n\r]*)'
r'(?:\s*hardware *ethernet *)'
r'((?:[0-9A-Fa-f]{2}[:-]){5}(?:[0-9A-Fa-f]{2}))'
r'(?:\s*;)'
r'(?:\s*fixed-address *)'
r'(\d+\.\d+\.\d+\.\d+)'
r'(?:\s*;)(?:\s*[^}]*})')
OG_CLIENT_DEFAULT_BOOT = "pxe"
OG_CLIENT_DEFAULT_LIVEDIR = "ogLive"
OG_CLIENT_DEFAULT_MAINTENANCE = False
OG_CLIENT_DEFAULT_NETDRIVER = "generic"
OG_CLIENT_DEFAULT_NETIFACE = "eth0"
OG_CLIENT_DEFAULT_REMOTE = False
@app.route('/action/clients/import', methods=['POST'])
@login_required
def action_clients_import_post():
form = ImportClientsForm(request.form)
server = get_server_from_ip_port(form.server.data)
clients = re.findall(OG_REGEX_DHCPD_CONF, form.dhcpd_conf.data)
if not clients:
flash(_('No clients found. Check the dhcpd.conf file.'),
category='error')
return redirect(url_for('scopes'))
payload = {'boot': OG_CLIENT_DEFAULT_BOOT,
'livedir': OG_CLIENT_DEFAULT_LIVEDIR,
'maintenance': OG_CLIENT_DEFAULT_MAINTENANCE,
'netdriver': OG_CLIENT_DEFAULT_NETDRIVER,
'netiface': OG_CLIENT_DEFAULT_NETIFACE,
'netmask': "0",
'remote': OG_CLIENT_DEFAULT_REMOTE,
"repo_id": int(form.repo.data),
'room': int(form.room.data)}
for client in clients:
payload['name'] = client[0]
payload['mac'] = client[1].replace(':', '')
payload['ip'] = client[2]
resp = server.post('/client/add', payload)
if resp.status_code != requests.codes.ok:
flash(_('ogServer: error adding client {}').format(client[0]),
category='error')
return redirect(url_for('scopes'))
flash(_('Clients imported successfully'), category='info')
return redirect(url_for('scopes'))
def get_selected_clients(scopes):
selected_clients = dict()
for scope in scopes:
scope_type = scope.get('type')
selected = scope.get('selected')
if ((scope_type == 'computer') and selected):
name_id = scope.get('name') + '_' + str(scope.get('id'))
selected_clients[name_id] = scope.get('ip')[0]
else:
selected_clients.update(get_selected_clients(scope['scope']))
return selected_clients
@app.route('/action/client/delete', methods=['GET', 'POST'])
@login_required
def action_client_delete():
form = GenericForm(request.form)
if request.method == 'POST':
ips = form.ips.data.split(' ')
if not validate_elements(ips):
return redirect(url_for('scopes'))
payload = {'clients': ips}
server = get_server_from_clients(ips)
r = server.post('/client/delete', payload)
if r.status_code != requests.codes.ok:
flash(_('ogServer: error deleting client'),
category='error')
else:
flash(_('Client deleted successfully'),
category='info')
return redirect(url_for('scopes'))
else:
ips = parse_elements(request.args.to_dict())
form.ips.data = " ".join(ips)
if validate_elements(ips):
scopes, clients = get_scopes(set(ips))
selected_clients = list(get_selected_clients(scopes['scope']).items())
return render_template('actions/delete_client.html', form=form,
selected_clients=selected_clients,
scopes=scopes)
else:
return redirect(url_for('scopes'))
def get_clients_modes(ips, server):
modes = {}
for ip in ips:
r = server.get('/client/info', payload={"client": [ip]})
if not r:
raise ServerError
if r.status_code != requests.codes.ok:
raise ServerErrorCode
resp = r.json()
current_boot = resp['boot']
client_name = resp['name']
if current_boot not in modes:
modes[current_boot] = [client_name]
else:
modes[current_boot].append(client_name)
return modes
@app.route('/action/mode', methods=['GET', 'POST'])
@login_required
def action_mode():
form = BootModeForm(request.form)
if request.method == 'POST':
ips = form.ips.data.split(' ')
payload = { 'clients': ips, 'mode': form.boot.data }
print(payload)
server = get_server_from_clients(ips)
r = server.post('/mode', payload)
if r.status_code == requests.codes.ok:
flash(_('Client set boot mode request sent successfully'), category='info')
else:
flash(_('Ogserver replied with status code not ok'), category='error')
return redirect(url_for('commands'))
else:
ips = parse_elements(request.args.to_dict())
form.ips.data = " ".join(ips)
if not validate_elements(ips):
return redirect(url_for('commands'))
server = get_server_from_clients(ips)
try:
modes_set = get_clients_modes(ips, server)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
r = server.get('/mode')
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
most_used_mode = max(modes_set, key=lambda m: len(modes_set[m]))
available_modes = []
if most_used_mode in r.json()['modes']:
available_modes.append((most_used_mode, most_used_mode))
available_modes.extend([(mode, mode) for mode in r.json()['modes'] if mode != most_used_mode])
if not available_modes:
flash(_('no boot templates are available in the server'),
category='error')
return redirect(url_for('commands'))
form.boot.choices = list(available_modes)
form.ok.render_kw = { 'formaction': url_for('action_mode') }
scopes, clients = get_scopes(set(ips))
selected_clients = list(get_selected_clients(scopes['scope']).items())
return render_template('actions/mode.html', form=form, scopes=scopes,
selected_clients=selected_clients,
clients=clients, modes_set=modes_set)
def get_clients_oglive(ips, server):
oglives = {}
for ip in ips:
r = server.get('/client/info', payload={"client": [ip]})
if not r:
raise ServerError
if r.status_code != requests.codes.ok:
raise ServerErrorCode
resp = r.json()
oglive = resp['livedir']
client_name = resp['name']
if oglive not in oglives:
oglives[oglive] = [client_name]
else:
oglives[oglive].append(client_name)
return oglives
@app.route('/action/oglive', methods=['GET', 'POST'])
@login_required
def action_oglive():
form = OgliveForm(request.form)
if request.method == 'POST':
ips = form.ips.data.split(' ')
payload = {'clients': ips, 'name': form.oglive.data}
server = get_server_from_clients(ips)
r = server.post('/oglive/set', payload)
if r.status_code == requests.codes.ok:
flash(_('Client set ogLive request sent successfully'),
category='info')
else:
flash(_('Ogserver replied with status code not ok'),
category='error')
return redirect(url_for('commands'))
else:
ips = parse_elements(request.args.to_dict())
form.ips.data = " ".join(ips)
if not validate_elements(ips):
return redirect(url_for('commands'))
server = get_server_from_clients(list(ips))
try:
oglives_set = get_clients_oglive(ips, server)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
r = server.get('/oglive/list')
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
most_used_oglive = max(oglives_set, key=lambda l: len(oglives_set[l]))
available_oglives = [(oglive.get('directory'), oglive.get('directory'))
for oglive in r.json()['oglive']
if oglive.get('directory') == most_used_oglive]
if not available_oglives:
available_oglives.append(('default', 'default'))
available_oglives.extend([(oglive.get('directory'), oglive.get('directory'))
for oglive in r.json()['oglive']
if oglive.get('directory') != most_used_oglive])
form.oglive.choices = list(available_oglives)
form.ok.render_kw = {'formaction': url_for('action_oglive')}
scopes, clients = get_scopes(set(ips))
selected_clients = list(get_selected_clients(scopes['scope']).items())
return render_template('actions/oglive.html', oglives_set=oglives_set, form=form, scopes=scopes,
selected_clients=selected_clients)
@app.route('/action/image/create', methods=['GET', 'POST'])
@login_required
def action_image_create():
form = ImageCreateForm(request.form)
if request.method == 'POST':
ip = form.ip.data
server = get_server_from_clients([ip])
r = server.get('/client/info', payload={"client": [ip]})
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
image_name = form.name.data.strip()
if ' ' in image_name:
flash(_('No spaces allowed in image names'), category='error')
return redirect(url_for('commands'))
disk, partition, code = form.os.data.split(' ')
payload = {"clients": [ip],
"disk": disk,
"partition": partition,
"code": code,
"name": image_name,
"repository_id": int(form.repository.data),
"id": "0", # This is ignored by the server.
"description": form.description.data,
"group_id": 0, # Default group.
"center_id": r.json()["center"]}
r = server.post('/image/create', payload)
if r.status_code == requests.codes.ok:
return redirect(url_for('commands'))
return make_response("400 Bad Request", 400)
else:
ips = parse_elements(request.args.to_dict())
form.ip.data = " ".join(ips)
if not validate_elements(ips, max_len=1):
return redirect(url_for('commands'))
server = get_server_from_clients(ips)
r = server.get('/client/setup', payload={'client': list(ips)})
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
for part in r.json()['partitions'][1:]:
form.os.choices.append(
(f"{part.get('disk')} {part.get('partition')} {part.get('code')}",
f"Disk {part.get('disk')} | Partition {part.get('partition')} "
f"| {PART_TYPE_CODES.get(part.get('code'), 'UNKNOWN')} "
f"{FS_CODES.get(part.get('filesystem'), 'UNKNOWN')}")
)
r = server.get('/client/info', payload={'client': list(ips)})
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
client_repo_id = r.json()['repo_id']
try:
repositories = get_repositories(server)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
form.repository.choices = [ (repo['id'], repo['name']) for repo in repositories
if client_repo_id == repo['id']]
form.repository.render_kw = {'readonly': True}
scopes, clients = get_scopes(set(ips))
return render_template('actions/image_create.html', form=form,
scopes=scopes)
@app.route('/action/image/update', methods=['GET', 'POST'])
@login_required
def action_image_update():
form = ImageUpdateForm(request.form)
if request.method == 'POST':
ip = form.ip.data
disk, partition, code = form.os.data.split(' ')
image_id = form.image.data
server = get_server_from_clients([ip])
r = server.get('/images')
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
images_list = r.json()['images']
image = search_image(images_list, int(image_id))
if not image:
flash(_('Image to restore was not found'), category='error')
return redirect(url_for('commands'))
try:
repository = get_repository(image['repo_id'], server)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
payload = {'clients': [ip],
'disk': disk,
'partition': partition,
'code': code,
'name': image['name'],
'repository': repository['ip'],
'id': str(image['id']),
'backup': form.backup.data,
# Dummy parameters, not used by ogServer on image update.
'group_id': 0,
'center_id': 0}
r = server.post('/image/update', payload)
if r.status_code == requests.codes.ok:
flash(_('Image update command sent sucessfully'), category='info')
else:
flash(_('There was a problem sending the image update command'),
category='error')
return redirect(url_for('commands'))
ips = parse_elements(request.args.to_dict())
if not validate_elements(ips, max_len=1):
return redirect(url_for('commands'))
form.ip.data = ' '.join(ips)
server = get_server_from_clients(ips)
r = server.get('/client/info', payload={'client': list(ips)})
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
repo_id = r.json()['repo_id']
try:
images = get_images_grouped_by_repos_from_server(server)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
if repo_id not in images:
flash(_('Computer is assigned to a repo with no images'),
category='error')
return redirect(url_for('commands'))
for image in images[repo_id]:
form.image.choices.append((image['id'], image['name']))
r = server.get('/client/setup', payload={'client': list(ips)})
if not r:
return ogserver_down('commands')
if r.status_code != requests.codes.ok:
return ogserver_error('commands')
for part in r.json()['partitions'][1:]:
form.os.choices.append(
(f"{part.get('disk')} {part.get('partition')} {part.get('code')}",
f"Disk {part.get('disk')} | Partition {part.get('partition')} "
f"| {PART_TYPE_CODES.get(part.get('code'), 'UNKNOWN')} "
f"{FS_CODES.get(part.get('filesystem'), 'UNKNOWN')}")
)
scopes, _clients = get_scopes(set(ips))
selected_clients = list(get_selected_clients(scopes['scope']).items())
return render_template('actions/image_update.html', form=form,
selected_clients=selected_clients,
scopes=scopes)
@app.route('/action/reboot', methods=['GET', 'POST'])
@login_required
def action_reboot():
form = GenericForm(request.form)
if request.method == 'POST':
ips = form.ips.data.split(' ')
if not validate_elements(ips):
return redirect(url_for('commands'))
payload = {'clients': ips}
server = get_server_from_clients(ips)
r = server.post('/reboot', payload)
if r.status_code != requests.codes.ok:
flash(_('ogServer: error rebooting client'),
category='error')
else:
flash(_('Client rebooted successfully'),
category='info')
return redirect(url_for('commands'))
else:
ips = parse_elements(request.args.to_dict())
form.ips.data = " ".join(ips)
if validate_elements(ips):
scopes, clients = get_scopes(set(ips))
selected_clients = list(get_selected_clients(scopes['scope']).items())
return render_template('actions/reboot.html', form=form,
selected_clients=selected_clients,
scopes=scopes)
else:
return redirect(url_for('commands'))
@app.route('/action/refresh', methods=['POST'])
@login_required
def action_refresh():
ips = parse_elements(request.form.to_dict())
if not validate_elements(ips):
return redirect(url_for('commands'))
server = get_server_from_clients(list(ips))
payload = {'clients': list(ips)}
r = server.post('/refresh', payload)
if r.status_code != requests.codes.ok:
flash(_('OgServer replied with a non ok status code'), category='error')
else:
flash(_('Refresh request processed successfully'), category='info')
return redirect(url_for('commands'))
@app.route('/action/center/add', methods=['GET', 'POST'])
@login_required
def action_center_add():
form = CenterForm(request.form)
if request.method == 'POST':
payload = {"name": form.name.data,
"comment": form.comment.data}
server = get_server_from_ip_port(form.server.data)
r = server.post('/center/add', payload)
if r.status_code != requests.codes.ok:
flash(_('Server replied with error code when adding the center'),
category='error')
else:
flash(_('Center added successfully'), category='info')
return redirect(url_for("scopes"))
else:
server_choices = [(server.ip + ':' + str(server.port), server.name)
for server in servers]
form.server.choices = server_choices
scopes, clients = get_scopes()
return render_template('actions/add_center.html', form=form,
scopes=scopes)
@app.route('/action/center/update', methods=['GET', 'POST'])
@login_required
def action_center_update():
form = CenterForm(request.form)
if request.method == 'POST':
payload = {"id": int(form.center.data),
"name": form.name.data,
"comment": form.comment.data}
server = get_server_from_ip_port(form.server.data)
r = server.post('/center/update', payload)
if r.status_code != requests.codes.ok:
flash(_('Server replied with error code when updating the center'),
category='error')
else:
flash(_('Center updated successfully'), category='info')
return redirect(url_for("scopes"))
else:
params = request.args.to_dict()
server = params.get('scope-server')
center = params.get('scope-center')
if not server:
flash(_('Internal error: server was not sent as request argument'),
category='error')
return redirect(url_for("scopes"))
if not center:
flash(_('Please, select one center'), category='error')
return redirect(url_for("scopes"))
server = get_server_from_ip_port(server)
form.server.choices = [(server.ip + ':' + str(server.port), server.name)]
form.server.render_kw = {'readonly': True}
form.center.data = center
payload = {"id": int(center)}
r = server.get('/center/info', payload)
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
form.comment.data = r.json()['comment']
form.name.data = r.json()['name']
scopes, clients = get_scopes()
form.submit.render_kw = {"formaction": url_for('action_center_update')}
return render_template('actions/center_update.html', form=form,
scopes=scopes)
def get_scope_context_rec(elem_id, elem_type, scopes, ancestors):
if not scopes:
return ([], None)
res = None
for s in scopes:
if s['type'] == elem_type and int(s['id']) == elem_id:
ancestors.append(s['name'])
return (ancestors, s)
ancestors_tmp = list(ancestors)
ancestors_tmp.append(s['name'])
ancestors_tmp, elem = get_scope_context_rec(elem_id, elem_type, s['scope'], ancestors_tmp)
if elem:
res = (ancestors_tmp, elem)
break
if res:
return res
else:
return ([], None)
def find_element_scope(elem_id, elem_type, scopes):
unused, elem = get_scope_context_rec(elem_id, elem_type, scopes['scope'], [])
return elem
def get_scope_context(elem_id, elem_type, scopes):
ancestors, elem = get_scope_context_rec(elem_id, elem_type, scopes['scope'], [])
children = {}
for c in elem['scope']:
if c['type'] not in children:
children[c['type']] = []
children[c['type']].append(c['name'])
return (ancestors, children)
@app.route('/action/center/delete', methods=['GET', 'POST'])
@login_required
def action_center_delete():
form = DeleteCenterForm(request.form)
if request.method == 'POST':
server = get_server_from_ip_port(form.server.data)
payload = {"id": form.center.data}
r = server.post('/center/delete', payload)
if r.status_code != requests.codes.ok:
flash(_('Server replied with error code when deleting the center'),
category='error')
else:
flash(_('Center deleted successfully'), category='info')
return redirect(url_for("scopes"))
else:
params = request.args.to_dict()
if not params.get('scope-center'):
flash(_('Please, select one center'), category='error')
return redirect(url_for('scopes'))
server = get_server_from_ip_port(params['scope-server'])
r = server.get('/scopes')
if not r:
return ogserver_down('scopes')
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
form.center.data = params['scope-center']
form.server.data = params['scope-server']
scopes, clients = get_scopes()
ancestors, children = get_scope_context(int(params['scope-center']), 'center', scopes)
return render_template('actions/delete_center.html', form=form,
scopes=scopes, ancestors=ancestors, children=children)
@app.route('/action/room/add', methods=['GET', 'POST'])
@login_required
def action_room_add():
form = RoomForm(request.form)
if request.method == 'POST':
server = get_server_from_ip_port(form.server.data)
payload = {"center": int(form.center.data),
"name": form.name.data,
"netmask": form.netmask.data,
"gateway": form.gateway.data,
"folder_id": int(form.folder_id.data)}
r = server.post('/room/add', payload)
if r.status_code != requests.codes.ok:
flash(_('Server replied with error code when adding the room'), category='error')
else:
flash(_('Room added successfully'), category='info')
return redirect(url_for("scopes"))
else:
params = request.args.to_dict()
if not params.get('scope-center'):
flash(_('Please, select a center or a folder'), category='error')
return redirect(url_for('scopes'))
server = get_server_from_ip_port(params['scope-server'])
r = server.get('/scopes')
if not r:
return ogserver_down('scopes')
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
selected_center_id = params['scope-center']
centers = parse_scopes_from_tree(r.json(), 'center')
selected_center = [(center['id'], center['name']) for center in centers
if center['id'] == int(selected_center_id)]
form.center.choices = selected_center
form.center.render_kw = {'readonly': True}
form.server.data = params['scope-server']
if params.get('folder'):
form.folder_id.data = params['folder']
else:
form.folder_id.data = 0
scopes, clients = get_scopes()
return render_template('actions/add_room.html', form=form,
scopes=scopes)
@app.route('/action/room/update', methods=['GET', 'POST'])
@login_required
def action_room_update():
form = RoomForm(request.form)
if request.method == 'POST':
server = get_server_from_ip_port(form.server.data)
payload = {"name": form.name.data,
"netmask": form.netmask.data,
"gateway": form.gateway.data,
"id": int(form.room.data)}
r = server.post('/room/update', payload)
if r.status_code != requests.codes.ok:
flash(_('Server replied with error code when updating the room'), category='error')
else:
flash(_('Room updated successfully'), category='info')
return redirect(url_for("scopes"))
else:
params = request.args.to_dict()
room_id = params.get('scope-room')
if not room_id:
flash(_('Please, select a room to update'), category='error')
return redirect(url_for('scopes'))
server = get_server_from_ip_port(params['scope-server'])
del form.center
form.server.data = params['scope-server']
form.room.data = room_id
payload = {"id": int(room_id)}
r = server.get('/room/info', payload)
if not r:
return ogserver_down('scopes')
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
form.name.data = r.json()['name']
form.gateway.data = r.json()['gateway']
form.netmask.data = r.json()['netmask']
form.submit.render_kw = {"formaction": url_for('action_room_update')}
scopes, clients = get_scopes()
return render_template('actions/room_update.html', form=form,
scopes=scopes)
@app.route('/action/room/delete', methods=['GET', 'POST'])
@login_required
def action_room_delete():
form = DeleteRoomForm(request.form)
if request.method == 'POST':
payload = {"id": form.room.data}
server = get_server_from_ip_port(form.server.data)
r = server.post('/room/delete', payload)
if r.status_code != requests.codes.ok:
flash(_('Server replied with error code when deleting the room'),
category='error')
else:
flash(_('Room deleted successfully'), category='info')
return redirect(url_for("scopes"))
else:
params = request.args.to_dict()
if not params.get('scope-room'):
flash(_('Please, select one room'), category='error')
return redirect(url_for('scopes'))
server = get_server_from_ip_port(params['scope-server'])
r = server.get('/scopes')
if not r:
return ogserver_down('scopes')
if r.status_code != requests.codes.ok:
return ogserver_error('scopes')
form.room.data = params['scope-room']
form.room.render_kw = {'readonly': True}
form.server.data = params['scope-server']
scopes, clients = get_scopes()
ancestors, children = get_scope_context(int(params['scope-room']), 'room', scopes)
return render_template('actions/delete_room.html', form=form,
scopes=scopes, ancestors=ancestors, children=children)
@app.route('/commands/', methods=['GET'])
@login_required
def commands():
scopes, clients = get_scopes()
return render_template('commands.html', scopes=scopes, clients=clients)
def get_images_grouped_by_repos():
responses = multi_request('get', '/images')
servers=[]
for resp in responses:
server={}
server['server'] = resp['server']
images=resp['json']['images']
try:
all_repos=get_repositories(resp['server'])
except ServerError:
continue
except ServerErrorCode:
continue
repos={}
for image in images:
repo_id=image['repo_id']
repo_data={}
if repo_id not in repos:
image_repo = [repo['name'] for repo in all_repos
if repo_id == repo['id']]
repos[repo_id] = {}
if image_repo:
repos[repo_id]['name'] = image_repo[0]
else:
repos[repo_id]['name'] = 'unknown'
repos[repo_id]['images'] = [image]
else:
repos[repo_id]['images'].append(image)
server['repos'] = repos
servers.append(server)
return servers
@app.route('/images/', methods=['GET'])
@login_required
def images():
responses = get_images_grouped_by_repos()
return render_template('images.html', responses=responses)
@app.route('/repos/', methods=['GET'])
@login_required
def manage_repos():
responses = multi_request('get', '/repositories')
return render_template('repos.html', repos_resp=responses)
@app.route('/action/repo/add', methods=['POST', 'GET'])
@login_required
def action_repo_add():
form = RepoForm(request.form)
if request.method == 'POST':
if not form.validate():
flash(form.errors, category='error')
return redirect(url_for('manage_repos'))
payload = {"name": form.name.data,
"ip": form.ip.data,
"center": 1}
server = get_server_from_ip_port(form.server.data)
r = server.post('/repository/add', payload)
if r.status_code != requests.codes.ok:
flash(_('ogServer: error adding repo'),
category='error')
else:
flash(_('Repo added successfully'), category='info')
return redirect(url_for("manage_repos"))
else:
params = request.args.to_dict()
if not params.get('repos-server'):
flash(_('Please, select a server'), category='error')
return redirect(url_for('manage_repos'))
form.server.data = params['repos-server']
responses = multi_request('get', '/repositories')
return render_template('actions/repos_add.html', form=form,
repos_resp=responses)
@app.route('/action/repo/update', methods=['GET', 'POST'])
@login_required
def action_repo_update():
form = RepoForm(request.form)
if request.method == 'POST':
server = get_server_from_ip_port(form.server.data)
payload = { 'repo_id': int(form.repo_id.data),
'name': form.name.data,
'ip': form.ip.data,
'center': 1}
r = server.post('/repository/update', payload)
if r.status_code != requests.codes.ok:
flash(_('ogServer: error updating repo'),
category='error')
else:
flash(_('Repo updated successfully'),
category='info')
return redirect(url_for('manage_repos'))
else:
params = request.args.to_dict()
repos = parse_elements(params)
if not validate_elements(repos, max_len=1):
return redirect(url_for('manage_repos'))
repo_id = repos.pop()
server_ip_port = params.get('repos-server')
if not server_ip_port:
flash(_('Please, select a server'), category='error')
return redirect(url_for('manage_repos'))
server = get_server_from_ip_port(server_ip_port)
try:
repository = get_repository(int(repo_id), server)
except ServerError:
return ogserver_down('manage_repos')
except ServerErrorCode:
return ogserver_error('manage_repos')
form.server.data = server_ip_port
form.repo_id.data = repo_id
form.name.data = repository['name']
form.ip.data = repository['ip']
responses = multi_request('get', '/repositories')
return render_template('actions/repos_update.html', form=form,
repos_resp=responses)
@app.route('/action/repo/delete', methods=['GET', 'POST'])
@login_required
def action_repo_delete():
form = RepoForm(request.form)
if request.method == 'POST':
server = get_server_from_ip_port(form.server.data)
payload = { 'id': form.repo_id.data }
r = server.post('/repository/delete', payload)
if r.status_code != requests.codes.ok:
flash(_('ogServer: error deleting repo'),
category='error')
else:
flash(_('Repo deleted successfully'),
category='info')
return redirect(url_for('manage_repos'))
else:
params = request.args.to_dict()
repos = parse_elements(params)
print(repos)
if not validate_elements(repos, max_len=1):
return redirect(url_for('manage_repos'))
repo_id = repos.pop()
if not repo_id:
flash(_('Please, select a repo'), category='error')
return redirect(url_for('manage_repos'))
repo_id = int(repo_id)
server_ip_port = params.get('repos-server')
if not server_ip_port:
flash(_('Please, select a server'), category='error')
return redirect(url_for('manage_repos'))
server = get_server_from_ip_port(server_ip_port)
try:
repository = get_repository(repo_id, server)
except ServerError:
return ogserver_down('manage_repos')
except ServerErrorCode:
return ogserver_error('manage_repos')
form.server.data = server_ip_port
form.repo_id.data = repo_id
form.name.data = repository['name']
form.name.render_kw = {'readonly': True}
form.ip.data = repository['ip']
form.ip.render_kw = {'readonly': True}
responses = multi_request('get', '/repositories')
return render_template('actions/delete_repo.html', form=form,
repos_resp=responses)
@app.route('/action/repo/info', methods=['GET'])
@login_required
def action_repo_info():
form = RepoForm()
params = request.args.to_dict()
repos = parse_elements(params)
if not validate_elements(repos, max_len=1):
return redirect(url_for('manage_repos'))
repo_id = repos.pop()
if not repo_id:
flash(_('Please, select a repo'), category='error')
return redirect(url_for('manage_repos'))
repo_id = int(repo_id)
server_ip_port = params.get('repos-server')
if not server_ip_port:
flash(_('Please, select a server'), category='error')
return redirect(url_for('manage_repos'))
server = get_server_from_ip_port(server_ip_port)
try:
repository = get_repository(repo_id, server)
except ServerError:
return ogserver_down('manage_repos')
except ServerErrorCode:
return ogserver_error('manage_repos')
form.name.data = repository['name']
form.name.render_kw = {'readonly': True}
form.ip.data = repository['ip']
form.ip.render_kw = {'readonly': True}
form.submit.render_kw = {"style": "visibility:hidden;"}
responses = multi_request('get', '/repositories')
return render_template('actions/repo_details.html', form=form,
repos_resp=responses)
@app.route('/servers/', methods=['GET'])
@login_required
def manage_servers():
return render_template('servers.html', servers=servers)
@app.route('/server/add', methods=['GET'])
@login_required
def server_add_get():
form = ServerForm()
return render_template('actions/add_server.html', form=form,
servers=servers)
@app.route('/server/add', methods=['POST'])
@login_required
def server_add_post():
form = ServerForm(request.form)
if not form.validate():
flash(form.errors, category='error')
return redirect(url_for('servers'))
ip_port_str = form.ip.data + ":" + form.port.data
try:
get_server_from_ip_port(ip_port_str)
flash(_('Server {} already exists').format(ip_port_str),
category='error')
return redirect(url_for('manage_servers'))
except Exception:
return save_server(form)
@app.route('/server/delete', methods=['GET'])
@login_required
def server_delete_get():
params = request.args.to_dict()
try:
selected_server = get_server_from_ip_port(params['selected-server'])
except KeyError:
flash(_('Please, select one server'), category='error')
return redirect(url_for('manage_servers'))
form = ServerForm()
form.name.data = selected_server.name
form.name.render_kw = {'readonly': True}
form.ip.data = selected_server.ip
form.ip.render_kw = {'readonly': True}
form.port.data = selected_server.port
form.port.render_kw = {'readonly': True}
form.api_token.data = selected_server.api_token
form.api_token.render_kw = {'readonly': True}
return render_template('actions/delete_server.html', form=form,
servers=servers)
@app.route('/server/delete', methods=['POST'])
@login_required
def server_delete_post():
form = ServerForm(request.form)
if not form.validate():
flash(form.errors, category='error')
return redirect(url_for('manage_servers'))
ip_port_str = form.ip.data + ":" + form.port.data
try:
server = get_server_from_ip_port(ip_port_str)
return delete_server(server)
except Exception:
flash(_('Server {} do not exists').format(ip_port_str),
category='error')
return redirect(url_for('manage_servers'))
@app.route('/users/', methods=['GET'])
@login_required
def users():
users = app.config['USERS']
return render_template('users.html', users=users)
def get_available_centers():
responses = multi_request('get', '/scopes')
available_centers = list()
for resp in responses:
centers = parse_scopes_from_tree(resp['json'], 'center')
centers = [(center['name'], center['name']) for center in centers]
available_centers.extend(centers)
return available_centers
def get_available_scopes():
responses = multi_request('get', '/scopes')
available_scopes = list()
for resp in responses:
servers = parse_scopes_from_tree(resp['json'], 'server')
servers = [(server['name'], server['name']) for server in servers]
available_scopes.extend(servers)
centers = parse_scopes_from_tree(resp['json'], 'center')
centers = [(center['name'], center['name']) for center in centers]
available_scopes.extend(centers)
rooms = parse_scopes_from_tree(resp['json'], 'room')
rooms = [(room['name'], room['name']) for room in rooms]
available_scopes.extend(rooms)
return available_scopes
def save_server(form):
server_dict = {
'NAME': form.name.data,
'IP': form.ip.data,
'PORT': int(form.port.data),
'API_TOKEN': form.api_token.data,
}
server_obj = OGServer(form.name.data,
form.ip.data,
int(form.port.data),
form.api_token.data)
filename = os.path.join(app.root_path, ogcp_cfg_path)
with open(filename, 'r+') as file:
config = json.load(file)
try:
config['SERVERS'].append(server_dict)
except KeyError:
config['SERVERS'] = list()
config['SERVERS'].append(server_dict)
file.seek(0)
json.dump(config, file, indent='\t')
file.truncate()
servers.append(server_obj)
return redirect(url_for('manage_servers'))
def delete_server(server):
server_dict = {
'NAME': server.name,
'IP': server.ip,
'PORT': int(server.port),
'API_TOKEN': server.api_token,
}
filename = os.path.join(app.root_path, ogcp_cfg_path)
with open(filename, 'r+') as file:
config = json.load(file)
try:
config['SERVERS'].remove(server_dict)
except (KeyError, ValueError):
config.pop('IP')
config.pop('PORT')
config.pop('API_TOKEN')
file.seek(0)
json.dump(config, file, indent='\t')
file.truncate()
servers.remove(server)
return redirect(url_for('manage_servers'))
def save_user(form):
username = form.username.data
pwd_hash = hash_password(form.pwd.data)
pwd_hash_confirm = hash_password(form.pwd_confirm.data)
if not pwd_hash == pwd_hash_confirm:
flash(_('Passwords do not match'), category='error')
return redirect(url_for('users'))
admin = form.admin.data
scopes = form.scopes.data
user = {
'USER': username,
'PASS': pwd_hash,
'ADMIN': admin,
'SCOPES': scopes,
}
filename = os.path.join(app.root_path, ogcp_cfg_path)
with open(filename, 'r+') as file:
config = json.load(file)
config['USERS'].append(user)
file.seek(0)
json.dump(config, file, indent='\t')
file.truncate()
app.config['USERS'].append(user)
return redirect(url_for('users'))
def delete_user(username):
user = get_user(username)
filename = os.path.join(app.root_path, ogcp_cfg_path)
with open(filename, 'r+') as file:
config = json.load(file)
config['USERS'].remove(user)
file.seek(0)
json.dump(config, file, indent='\t')
file.truncate()
app.config['USERS'].remove(user)
return redirect(url_for('users'))
@app.route('/user/add', methods=['GET'])
@login_required
def user_add_get():
form = UserForm()
form.scopes.choices = get_available_centers()
return render_template('auth/add_user.html', form=form)
@app.route('/user/add', methods=['POST'])
@login_required
def user_add_post():
form = UserForm(request.form)
form.scopes.choices = get_available_scopes()
if not form.validate():
flash(form.errors, category='error')
return redirect(url_for('users'))
if get_user(form.username.data):
flash(_('This username already exists'), category='error')
return redirect(url_for('users'))
return save_user(form)
@app.route('/user/edit', methods=['GET'])
@login_required
def user_edit_get():
username_set = parse_elements(request.args.to_dict())
if not validate_elements(username_set, max_len=1):
return redirect(url_for('users'))
username = username_set.pop()
user = get_user(username)
if not user:
flash(_('User {} do not exists').format(username), category='error')
return redirect(url_for('users'))
form = UserForm()
form.username.data = user.get('USER')
form.username.render_kw = {'readonly': True}
form.admin.data = user.get('ADMIN')
form.scopes.data = user.get('SCOPES')
form.scopes.choices = get_available_centers()
return render_template('auth/edit_user.html', form=form)
@app.route('/user/edit', methods=['POST'])
@login_required
def user_edit_post():
form = UserForm(request.form)
form.scopes.choices = get_available_scopes()
if not form.validate():
flash(form.errors, category='error')
return redirect(url_for('users'))
username = form.username.data
if not get_user(username):
flash(_('User {} do not exists').format(username), category='error')
return redirect(url_for('users'))
delete_user(username)
return save_user(form)
@app.route('/user/delete', methods=['GET'])
@login_required
def user_delete_get():
username_set = parse_elements(request.args.to_dict())
if not validate_elements(username_set, max_len=1):
return redirect(url_for('users'))
username = username_set.pop()
user = get_user(username)
if not user:
flash(_('User {} do not exists').format(username), category='error')
return redirect(url_for('users'))
form = DeleteUserForm()
form.username.data = user.get('USER')
return render_template('auth/delete_user.html', form=form)
@app.route('/action/image/list', methods=['GET'])
@login_required
def action_image_list():
params = request.args.to_dict()
ids = parse_elements(params)
ids = [int(id) for id in ids if id.isdigit()]
if not ids:
flash(_('Please, select one more images to be listed'), category='error')
return redirect(url_for('images'))
server = get_server_from_ip_port(params['image-server'])
try:
responses = get_images_grouped_by_repos()
servers = []
for server in responses:
repos = []
for unused, repo in server['repos'].items():
images=[]
for img in repo['images']:
if int(img['id']) in ids:
images.append(img)
if images:
repos.append((repo['name'], images))
if repos:
s={}
s['name'] = server['server'].name
s['repos'] = repos
servers.append(s)
except ServerError:
return ogserver_down('images')
except ServerErrorCode:
return ogserver_error('images')
return render_template('actions/list_images.html',
servers=servers, responses=responses)
@app.route('/user/delete', methods=['POST'])
@login_required
def user_delete_post():
form = DeleteUserForm(request.form)
if not form.validate():
flash(form.errors, category='error')
return redirect(url_for('users'))
username = form.username.data
if not get_user(username):
flash(_('User {} do not exists').format(username), category='error')
return redirect(url_for('users'))
delete_user(username)
flash(_('User {} deleted').format(username), category='info')
return redirect(url_for('users'))
@app.route('/action/image/info', methods=['GET'])
@login_required
def action_image_info():
form = ImageDetailsForm()
params = request.args.to_dict()
ids = parse_elements(params)
if not validate_elements(ids, max_len=1):
return redirect(url_for('images'))
id = ids.pop()
server = get_server_from_ip_port(params['image-server'])
r = server.get('/images')
if not r:
return ogserver_down('images')
if r.status_code != requests.codes.ok:
return ogserver_error('images')
images = r.json()['images']
image = next(img for img in images if img['id'] == int(id))
form.name.data = image['name']
# Bytes to Mebibytes
form.size.data = image['size'] / 1024 ** 2
form.datasize.data = image['datasize'] / 1024 ** 2
form.modified.data = image['modified']
form.permissions.data = image['permissions']
form.software_id.data = image['software_id']
form.description.data = image['description']
try:
responses = get_images_grouped_by_repos()
except ServerError:
return ogserver_down('images')
except ServerErrorCode:
return ogserver_error('images')
return render_template('actions/image_details.html', form=form,
responses=responses)
@app.route('/action/image/delete', methods=['GET', 'POST'])
@login_required
def action_image_delete():
form = GenericForm(request.form)
if request.method == 'POST':
ids = form.ids.data.split(' ')
if not validate_elements(ids, max_len=1):
return redirect(url_for('images'))
id = ids.pop()
payload = {'image': id}
server = get_server_from_ip_port(form.server.data)
r = server.post('/image/delete', payload)
if r.status_code != requests.codes.ok:
flash(_('OgServer replied with a non ok status code'), category='error')
else:
flash(_('Image deletion request sent successfully'), category='info')
return redirect(url_for('images'))
else:
params = request.args.to_dict()
images = [(name, imgid) for name, imgid in params.items()
if name != 'csrf_token' and name != 'image-server']
if not validate_elements(images, max_len=1):
return redirect(url_for('images'))
image_name, image_id = images[0]
server = get_server_from_ip_port(params['image-server'])
try:
responses = get_images_grouped_by_repos()
except ServerError:
return ogserver_down('images')
except ServerErrorCode:
return ogserver_error('images')
form.ids.data = image_id
form.server.data = params['image-server']
if not validate_elements(images, max_len=1):
flash(_('Please select one image to delete'), category='error')
return redirect(url_for('images'))
return render_template('actions/delete_image.html', form=form,
image_name=image_name.split('_', 1)[0], image_id=image_id,
responses=responses)
@app.route('/action/log', methods=['GET'])
@login_required
def action_legacy_log():
ips = parse_elements(request.args.to_dict())
if not validate_elements(ips, max_len=1):
return redirect(url_for('commands'))
ip = ips.pop()
log_file = Path("/opt/opengnsys/log/clients/" + str(ip) + ".log")
if not os.access(log_file, os.R_OK):
flash(_('No log available for this client yet'), category='error')
return redirect(url_for('commands'))
log = log_file.read_text()
if log:
scopes, clients = get_scopes(set(ips))
return render_template('actions/legacy/log.html', log=log,
scopes=scopes)
else:
return redirect(url_for('commands'))
@app.route('/action/rt-log', methods=['GET'])
@login_required
def action_legacy_rt_log():
ips = parse_elements(request.args.to_dict())
if not validate_elements(ips, max_len=1):
return redirect(url_for('commands'))
ip = ips.pop()
scheme = "http://"
rt_log_path = "/cgi-bin/httpd-log.sh"
rt_log_url = scheme + ip + rt_log_path
return redirect(rt_log_url)