mirror of https://git.48k.eu/ogcp
4130 lines
144 KiB
Python
4130 lines
144 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, CacheForm,
|
|
ClientMoveForm, RunScriptForm, ImageConfigForm, ImageFetchForm,
|
|
ServerConfigurationForm, SetRepoForm
|
|
)
|
|
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, EditUserForm
|
|
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
|
|
from wtforms import StringField
|
|
import unicodedata
|
|
import ipaddress
|
|
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',
|
|
239: 'EFI',
|
|
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'
|
|
}
|
|
|
|
def get_invalid_image_partition_types():
|
|
return ['EMPTY', 'LINUX-SWAP', 'CACHE', 'EFI']
|
|
|
|
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 split_csv(s):
|
|
return re.split(r'\s*,\s*', s)
|
|
|
|
def normalize_mac(mac):
|
|
return mac.replace(':', '').replace('-', '').replace('.', '').lower()
|
|
|
|
def is_valid_normalized_mac(mac):
|
|
if len(mac) != 12:
|
|
return False
|
|
if not all(c in '0123456789abcdef' for c in mac):
|
|
return False
|
|
return True
|
|
|
|
def prettify_mac(mac):
|
|
normalized_mac = normalize_mac(mac)
|
|
|
|
if not is_valid_normalized_mac(normalized_mac):
|
|
return mac
|
|
return (':'.join(normalized_mac[i:i+2] for i in range(0, 12, 2))).lower()
|
|
|
|
def is_valid_ip(ip):
|
|
try:
|
|
ipaddress.ip_address(ip)
|
|
except:
|
|
return False
|
|
return True
|
|
|
|
def remove_accents(text):
|
|
normalized_text = unicodedata.normalize('NFD', text)
|
|
return ''.join(c for c in normalized_text if unicodedata.category(c) != 'Mn')
|
|
|
|
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 client_setup_add_image_names(server, setup_data):
|
|
r = server.get('/images')
|
|
if not r:
|
|
raise ServerError
|
|
if r.status_code != requests.codes.ok:
|
|
raise ServerErrorCode
|
|
|
|
images = r.json()['images']
|
|
|
|
for disk, partitions in setup_data.items():
|
|
for p in partitions:
|
|
if images and p['image'] != 0:
|
|
image = next((img for img in images if img['id'] == p['image']), None)
|
|
if image:
|
|
p['image'] = image['name']
|
|
else:
|
|
p['image'] = ""
|
|
else:
|
|
p['image'] = ""
|
|
|
|
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']
|
|
res = {}
|
|
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')
|
|
|
|
disk = partition.get('disk')
|
|
if disk in res:
|
|
res[disk].append(partition)
|
|
else:
|
|
res[disk] = [partition]
|
|
|
|
return res
|
|
|
|
@app.route('/client/setup', methods=['GET'])
|
|
@login_required
|
|
def get_client_setup_json():
|
|
ip = parse_elements(request.args.to_dict())
|
|
setup = get_client_setup(ip)
|
|
return jsonify(setup)
|
|
|
|
def get_clients(state_filter=None):
|
|
responses = multi_request('get', '/clients')
|
|
clients_list = []
|
|
for r in responses:
|
|
req_clients = r['json']['clients']
|
|
for client in req_clients:
|
|
client['server'] = r['server']
|
|
clients_list = clients_list + req_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 get_all_repositories():
|
|
data = multi_request('get', '/repositories')
|
|
for item in data:
|
|
repositories = item['json']['repositories']
|
|
sorted_repositories = sorted(repositories, key=lambda repo: repo['name'])
|
|
item['json']['repositories'] = sorted_repositories
|
|
return data
|
|
|
|
|
|
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 remove_disabled_scopes(scopes):
|
|
scope_list = scopes.get('scope')[:]
|
|
for scope in scope_list:
|
|
if scope.get('type') == 'center':
|
|
if str(scope.get('id')) in current_user.scopes:
|
|
continue
|
|
|
|
scopes.get('scope').remove(scope)
|
|
else:
|
|
remove_disabled_scopes(scope)
|
|
|
|
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
|
|
if r.status_code != requests.codes.ok:
|
|
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 not current_user.admin and current_user.scopes:
|
|
remove_disabled_scopes(all_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 get_cache_info(clients_info, storage_data, images_data, client_images):
|
|
for client_info in clients_info:
|
|
ip = client_info['ip']
|
|
cache_size = client_info['cache_size'] * 1024
|
|
used_cache = client_info['used_cache']
|
|
free_cache = client_info['free_cache']
|
|
for image_info in client_info['images']:
|
|
image_name = image_info['name']
|
|
checksum = image_info['checksum']
|
|
img_identifier = f'{image_name}.{checksum}'
|
|
|
|
if ip in client_images:
|
|
client_images[ip].append(img_identifier)
|
|
else:
|
|
client_images[ip] = [img_identifier]
|
|
|
|
if img_identifier in images_data:
|
|
images_data[img_identifier]['clients'].append(ip)
|
|
else:
|
|
images_data[img_identifier] = {
|
|
'clients': [ip],
|
|
'size': int(image_info['size']),
|
|
'name': image_name,
|
|
'checksum': checksum
|
|
}
|
|
|
|
storage_data[ip] = {'used': used_cache,
|
|
'free': free_cache}
|
|
|
|
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'),
|
|
user_dict.get('PERMISSIONS', {}))
|
|
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:
|
|
timestamp = datetime.datetime.today()
|
|
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'),
|
|
user_dict.get('PERMISSIONS', {}))
|
|
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 = prettify_mac(mac)
|
|
return jsonify(pretty_mac)
|
|
|
|
|
|
def get_server_data_from_scopes(scopes, clients):
|
|
servers_data = {}
|
|
|
|
ip_to_name = {ip: c['name'] for c in parse_scopes_from_tree(scopes, 'computer') for ip in c['ip']}
|
|
|
|
for client in clients['clients']:
|
|
if not client['addr'] in ip_to_name:
|
|
continue
|
|
|
|
client['name'] = ip_to_name[client['addr']]
|
|
server_id = client['server'].id
|
|
if server_id not in servers_data:
|
|
servers_data[server_id] = {'clients': []}
|
|
|
|
for server in servers:
|
|
if server.id == server_id:
|
|
servers_data[server_id]['name'] = server.name
|
|
|
|
servers_data[server_id]['clients'].append(client)
|
|
|
|
for server_id in servers_data:
|
|
servers_data[server_id]['clients'].sort(key=lambda x: x['name'])
|
|
|
|
return servers_data
|
|
|
|
@app.route('/client/list', methods=['GET'])
|
|
@login_required
|
|
def client_list():
|
|
scopes, clients = get_scopes()
|
|
servers_data = get_server_data_from_scopes(scopes, clients)
|
|
|
|
selected_clients = list(get_selected_clients(scopes['scope']).items())
|
|
|
|
return render_template('client_list.html', servers_data=servers_data,
|
|
selected_clients=selected_clients,
|
|
scopes=scopes)
|
|
|
|
|
|
@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 not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('commands')
|
|
|
|
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'))
|
|
|
|
|
|
def get_common_disk_data(ips):
|
|
disk_data = {}
|
|
|
|
for ip in ips:
|
|
setup_data = get_client_setup(ip)
|
|
|
|
for disk, partitions in setup_data.items():
|
|
for p in partitions:
|
|
if p.get('partition') != 0:
|
|
continue
|
|
if disk in disk_data:
|
|
disk_data[disk]['common_size'] = min(disk_data[disk]['common_size'], p.get('size'))
|
|
disk_data[disk]['inventory'].setdefault(p.get('size'), []).append(ip)
|
|
else:
|
|
disk_data[disk] = {
|
|
'common_size': p.get('size'),
|
|
'excluded': [],
|
|
'inventory': {p.get('size'): [ip]}
|
|
}
|
|
break
|
|
|
|
for disk_id in disk_data:
|
|
disk_ips = []
|
|
for disk_size in disk_data[disk_id]['inventory']:
|
|
disk_ips.extend(disk_data[disk_id]['inventory'][disk_size])
|
|
|
|
disk_data[disk_id]['excluded'] = [ip for ip in ips if ip not in disk_ips]
|
|
|
|
return disk_data
|
|
|
|
@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
|
|
|
|
common_disk_data = get_common_disk_data(ips)
|
|
|
|
scopes, unused = get_scopes(ips)
|
|
selected_clients = list(get_selected_clients(scopes['scope']).items())
|
|
|
|
return render_template('actions/select_client.html',
|
|
common_disk_data=common_disk_data,
|
|
selected_clients=selected_clients,
|
|
form=form, scopes=scopes)
|
|
|
|
|
|
@app.route('/action/setup', methods=['GET'])
|
|
@login_required
|
|
def action_setup_show():
|
|
args = request.args.copy()
|
|
disk_size = None
|
|
|
|
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:
|
|
setup_data = get_client_setup(base_client)
|
|
except ServerError:
|
|
return ogserver_down('commands')
|
|
except ServerErrorCode:
|
|
return ogserver_error('commands')
|
|
|
|
if not setup_data:
|
|
flash(_('Partition information is not available. Boot client in ogLive mode to obtain it'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
selected_disk = 1
|
|
common_disk_data = get_common_disk_data(ips)
|
|
|
|
# Use common disk size
|
|
for disk in setup_data:
|
|
setup_data[disk][0]['size'] = common_disk_data[disk]['common_size']
|
|
|
|
excluded_client_disks = {}
|
|
for disk in common_disk_data:
|
|
excluded_client_disks[disk] = common_disk_data[disk]['excluded']
|
|
|
|
form = SetupForm()
|
|
form.ips.data = ips_str
|
|
|
|
form.disk.choices = [(disk, disk) for disk in setup_data]
|
|
form.disk.data = selected_disk
|
|
# If partition table is empty, set MSDOS
|
|
form.disk_type.data = setup_data[selected_disk][0]['code'] or 1
|
|
|
|
scopes, _clients = get_scopes(ips)
|
|
return render_template('actions/setup.html',
|
|
excluded_client_disks=excluded_client_disks,
|
|
selected_disk=selected_disk,
|
|
setup_data=setup_data,
|
|
disk_form=form,
|
|
scopes=scopes)
|
|
|
|
@app.route('/action/setup', methods=['POST'])
|
|
@login_required
|
|
def action_setup_modify():
|
|
form = SetupForm(request.form)
|
|
MIN_EFI_SIZE = 500
|
|
if form.validate():
|
|
cache_count = 0
|
|
for partition in form.partitions:
|
|
if partition.part_type.data == 'CACHE':
|
|
cache_count += 1
|
|
|
|
if partition.part_type.data == 'EFI' and partition.fs.data != 'FAT32':
|
|
flash(_('The EFI partition requires a FAT32 filesystem'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
if partition.part_type.data == 'EFI' and partition.size.data < MIN_EFI_SIZE:
|
|
flash(_(f'The EFI partition requires a size of {MIN_EFI_SIZE}MiB or higher'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
if partition.size.data <= 0:
|
|
flash(_('Partitions can\'t have a size of zero or lower'), 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 not r:
|
|
return ogserver_down('commands')
|
|
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 filter_images_allowed_in_center(server, images, center_id):
|
|
res = []
|
|
for image in images:
|
|
r = server.get('/image/restrict', {'image': image['id']})
|
|
if not r:
|
|
raise ServerError
|
|
if r.status_code != requests.codes.ok:
|
|
raise ServerErrorCode
|
|
allowed_scopes = r.json().get('scopes')
|
|
if not allowed_scopes or center_id in allowed_scopes:
|
|
res.append(image)
|
|
return res
|
|
|
|
def get_images_from_repo(server, repo_id):
|
|
r = server.get('/images')
|
|
if not r:
|
|
raise ServerError
|
|
if r.status_code != requests.codes.ok:
|
|
raise ServerErrorCode
|
|
images = r.json()['images']
|
|
res=[]
|
|
for image in images:
|
|
if image['repo_id'] == repo_id:
|
|
res.append(image)
|
|
return res
|
|
|
|
def get_image_key(image):
|
|
return image['name']
|
|
|
|
def sort_images(images):
|
|
images.sort(key=get_image_key, reverse=False)
|
|
|
|
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
|
|
|
|
def image_fits_in_cache(server, clients_info, image):
|
|
image_size = int(image.get('size', '0'))
|
|
image_checksum = image.get('checksum', '')
|
|
|
|
if not (image_size and image_checksum):
|
|
return True
|
|
|
|
err_report = ""
|
|
|
|
for client_info in clients_info:
|
|
ip = client_info['ip']
|
|
cache_size = client_info['cache_size'] * 1024
|
|
has_image = False;
|
|
|
|
for image_info in client_info['images']:
|
|
if image_info['checksum'] == image_checksum:
|
|
has_image = True
|
|
|
|
if has_image:
|
|
continue
|
|
|
|
free_cache = client_info['free_cache']
|
|
used_cache = client_info['used_cache']
|
|
if used_cache == 0 and free_cache == 0:
|
|
err_report += f'{ip} has no cache partition<br>'
|
|
continue
|
|
|
|
if free_cache < image_size:
|
|
missing_cache = image_size - free_cache
|
|
err_report += f'{ip} requires {(missing_cache / (1024 ** 3)):.3f} more free GiB<br>'
|
|
|
|
if err_report:
|
|
flash(f'{err_report}', category='error')
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
class PartitionCollection:
|
|
def __init__(self):
|
|
self.partition_list = []
|
|
self.clients = []
|
|
self.empty_scheme = False
|
|
|
|
def __len__(self):
|
|
return len(self.partition_list)
|
|
|
|
def has_empty_scheme(self):
|
|
return self.empty_scheme
|
|
|
|
def register_partition_setup(self, partitions, client):
|
|
idx = None
|
|
self.empty_scheme = self.empty_scheme or not partitions
|
|
for index, p in enumerate(self.partition_list):
|
|
if p == partitions:
|
|
idx = index
|
|
self.clients[idx].append(client)
|
|
break
|
|
else:
|
|
idx = len(self.partition_list)
|
|
self.partition_list.append(partitions)
|
|
self.clients.append([client])
|
|
|
|
return idx
|
|
|
|
def get_partition_setup(self, idx):
|
|
return self.partition_list[idx]
|
|
|
|
def get_clients(self, idx):
|
|
return self.clients[idx]
|
|
|
|
@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_size, has_cache = form.partition.data.split(' ')
|
|
|
|
requires_cache = form.method.data == 'TIPTORRENT' or form.method.data == 'UNICAST'
|
|
if has_cache == 'False' and requires_cache:
|
|
flash(_(f'Cannot restore image using {form.method.data} on a client without cache'), 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 and image_datasize > part_size:
|
|
flash(_(f'The image size is bigger than the target partition'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
r = server.get('/cache/list', payload={'clients': ips})
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('commands')
|
|
|
|
clients_info = r.json()['clients']
|
|
|
|
if requires_cache and not image_fits_in_cache(server, clients_info, image):
|
|
return redirect(url_for('commands'))
|
|
|
|
payload = {'disk': disk,
|
|
'partition': partition,
|
|
'name': image['name'],
|
|
'repository_id': image['repo_id'],
|
|
'clients': ips,
|
|
'type': form.method.data,
|
|
'profile': str(image['software_id']),
|
|
'id': str(image['id'])}
|
|
server.post('/image/restore', payload)
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
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:
|
|
params = request.args.to_dict()
|
|
center_id = int(params.get('scope-center'))
|
|
ips = parse_elements(params)
|
|
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_from_repo(server, repo_id)
|
|
|
|
if not images:
|
|
flash(_(f'Computer(s) assigned to a repo with no images'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
images = filter_images_allowed_in_center(server, images, center_id)
|
|
except ServerError:
|
|
return ogserver_down('commands')
|
|
except ServerErrorCode:
|
|
return ogserver_error('commands')
|
|
|
|
sort_images(images)
|
|
|
|
for image in images:
|
|
form.image.choices.append((image['id'], image['name']))
|
|
|
|
invalid_part_types = get_invalid_image_partition_types()
|
|
|
|
part_collection = PartitionCollection()
|
|
|
|
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')
|
|
|
|
has_cache = False
|
|
|
|
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
|
|
if disk_id != 1:
|
|
continue
|
|
part_code = partition['code']
|
|
part_type = PART_TYPE_CODES.get(int(part_code), 'UNKNOWN')
|
|
|
|
if part_type == 'CACHE':
|
|
has_cache = True
|
|
|
|
if part_type in invalid_part_types:
|
|
continue
|
|
|
|
filesystem = partition['filesystem']
|
|
fs_type = FS_CODES.get(filesystem, 'UNKNOWN')
|
|
part_size = partition['size']
|
|
|
|
parts.append((disk_id, part_id, part_type, fs_type, part_size))
|
|
|
|
part_collection.register_partition_setup(parts, ip)
|
|
|
|
scopes, clients = get_scopes(set(ips))
|
|
selected_clients = list(get_selected_clients(scopes['scope']).items())
|
|
|
|
if len(part_collection) > 1 or part_collection.has_empty_scheme():
|
|
return render_template('actions/partition_report.html',
|
|
selected_clients=selected_clients,
|
|
part_data=part_collection,
|
|
scopes=scopes)
|
|
|
|
reference_patitioning = part_collection.get_partition_setup(0)
|
|
|
|
if not reference_patitioning:
|
|
flash(_(f'No valid partition found'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
for disk_id, part_id, part_type, fs_type, part_size in reference_patitioning:
|
|
form.partition.choices.append(
|
|
(f"{disk_id} {part_id} {part_size} {has_cache}",
|
|
f"Disk {disk_id} | Partition {part_id} "
|
|
f"| {part_type} "
|
|
f"{fs_type}")
|
|
)
|
|
|
|
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 not r:
|
|
return ogserver_down('commands')
|
|
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 not r:
|
|
return ogserver_down('commands')
|
|
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, os_name = form.os.data.split(' ', 2)
|
|
server = get_server_from_clients(list(ips))
|
|
|
|
r = server.get('/session', payload={'clients': 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'))
|
|
|
|
valid_ips = []
|
|
excluded_ips = []
|
|
for client_sessions in sessions:
|
|
ip = client_sessions['addr']
|
|
os_found = False
|
|
for p_data in client_sessions['partitions']:
|
|
if p_data['disk'] == int(disk) and p_data['partition'] == int(partition) and p_data['name'] == os_name:
|
|
os_found = True
|
|
break
|
|
if os_found:
|
|
valid_ips.append(ip)
|
|
else:
|
|
excluded_ips.append(ip)
|
|
|
|
r = server.post('/session', payload={'clients': valid_ips,
|
|
'disk': str(disk),
|
|
'partition': str(partition)})
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code == requests.codes.ok:
|
|
if excluded_ips:
|
|
flash('The following clients didn\'t match the boot configuration: ' + str(excluded_ips))
|
|
return redirect(url_for('commands'))
|
|
return make_response("400 Bad Request", 400)
|
|
else:
|
|
ips = parse_elements(request.args.to_dict())
|
|
ips_list = list(ips)
|
|
if not validate_elements(ips):
|
|
return redirect(url_for('commands'))
|
|
|
|
server = get_server_from_clients(ips_list)
|
|
form.ips.data = ' '.join(ips_list)
|
|
|
|
r = server.get('/session', payload={'clients': ips_list})
|
|
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'))
|
|
|
|
os_groups = {}
|
|
for client_sessions in sessions:
|
|
ip = client_sessions['addr']
|
|
for p_data in client_sessions['partitions']:
|
|
if p_data['name'] == 'unknown':
|
|
continue
|
|
|
|
item_key = f"{p_data['disk']} {p_data['partition']} {p_data['name']}"
|
|
|
|
if item_key in os_groups:
|
|
os_groups[item_key].append(ip)
|
|
else:
|
|
os_groups[item_key] = [ip]
|
|
|
|
choice = (item_key,
|
|
f"{p_data['name']} (Disk:{p_data['disk']}, Partition:{p_data['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, os_groups=os_groups)
|
|
|
|
@app.route('/action/cache', methods=['GET', 'POST'])
|
|
@login_required
|
|
def action_client_cache():
|
|
form = CacheForm(request.form)
|
|
if request.method == 'POST':
|
|
ips = form.ips.data.split(' ')
|
|
server = get_server_from_clients(list(ips))
|
|
|
|
client_list = []
|
|
image_list = []
|
|
for entry in form.images.entries:
|
|
if not entry.selected.data:
|
|
continue
|
|
|
|
image_list.append(entry.image_name.data)
|
|
for client_ip in entry.clients.data.split(' '):
|
|
if not client_ip in client_list:
|
|
client_list.append(client_ip)
|
|
|
|
if not image_list:
|
|
flash(_(f'No selected images to delete'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
r = server.post('/cache/delete',
|
|
payload={'clients': client_list,
|
|
'images': image_list})
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code == requests.codes.ok:
|
|
flash(_('Cache delete request sent successfully'), category='info')
|
|
else:
|
|
flash(_(f'Invalid cache delete form'), category='error')
|
|
return redirect(url_for('commands'))
|
|
else:
|
|
ips = parse_elements(request.args.to_dict())
|
|
ips_list = list(ips)
|
|
if not validate_elements(ips):
|
|
return redirect(url_for('commands'))
|
|
|
|
form.ips.data = ' '.join(ips_list)
|
|
|
|
server = get_server_from_clients(ips_list)
|
|
r = server.get('/cache/list', payload={'clients': ips_list})
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('commands')
|
|
|
|
clients_info = r.json()['clients']
|
|
|
|
if not clients_info:
|
|
flash(_('ogServer returned an empty client list'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
storage_data = {}
|
|
images_data = {}
|
|
client_images = {}
|
|
|
|
get_cache_info(clients_info, storage_data, images_data, client_images)
|
|
|
|
for img_identifier in images_data:
|
|
image_data = images_data[img_identifier]
|
|
checkbox_entry = form.images.append_entry()
|
|
checkbox_entry.selected.label.text = img_identifier
|
|
checkbox_entry.image_name.data = image_data['name']
|
|
checkbox_entry.clients.data = ' '.join(image_data['clients'])
|
|
|
|
scopes, clients = get_scopes(set(ips))
|
|
selected_clients = list(get_selected_clients(scopes['scope']).items())
|
|
return render_template('actions/cache.html', form=form,
|
|
selected_clients=selected_clients,
|
|
scopes=scopes, images_data=images_data,
|
|
storage_data=storage_data,
|
|
client_images=client_images)
|
|
|
|
@app.route('/action/cache/fetch', methods=['GET', 'POST'])
|
|
@login_required
|
|
def action_image_fetch():
|
|
form = ImageFetchForm(request.form)
|
|
if request.method == 'POST':
|
|
ips = form.ips.data.split(' ')
|
|
server = get_server_from_clients(list(ips))
|
|
|
|
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 fetch was not found'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
r = server.get('/cache/list', payload={'clients': ips})
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('commands')
|
|
|
|
clients_info = r.json()['clients']
|
|
|
|
if not image_fits_in_cache(server, clients_info, image):
|
|
return redirect(url_for('commands'))
|
|
|
|
payload = {'clients': ips,
|
|
'repository_id': image['repo_id'],
|
|
'type': form.method.data,
|
|
'image': image['name']}
|
|
|
|
r = server.post('/cache/fetch', payload=payload)
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code == requests.codes.ok:
|
|
flash(_('Cache fetch request sent successfully'), category='info')
|
|
else:
|
|
flash(_(f'Invalid cache fetch form'), category='error')
|
|
return redirect(url_for('commands'))
|
|
else:
|
|
params = request.args.to_dict()
|
|
ips = parse_elements(params)
|
|
center_id = int(params.get('scope-center'))
|
|
|
|
if not validate_elements(ips):
|
|
return redirect(url_for('commands'))
|
|
|
|
ips_list = list(ips)
|
|
form.ips.data = ' '.join(ips_list)
|
|
|
|
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_from_repo(server, repo_id)
|
|
|
|
if not images:
|
|
flash(_(f'Computer(s) assigned to a repo with no images'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
images = filter_images_allowed_in_center(server, images, center_id)
|
|
except ServerError:
|
|
return ogserver_down('commands')
|
|
except ServerErrorCode:
|
|
return ogserver_error('commands')
|
|
|
|
sort_images(images)
|
|
|
|
for image in images:
|
|
form.image.choices.append((image['id'], image['name']))
|
|
|
|
scopes, clients = get_scopes(set(ips))
|
|
selected_clients = list(get_selected_clients(scopes['scope']).items())
|
|
return render_template('actions/cache_fetch.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 = prettify_mac(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;"}
|
|
|
|
ip = list(ips)[0]
|
|
try:
|
|
setup_data = get_client_setup(ip)
|
|
client_setup_add_image_names(server, setup_data)
|
|
except ServerError:
|
|
return ogserver_down('commands')
|
|
except ServerErrorCode:
|
|
return ogserver_error('commands')
|
|
|
|
disk_form = SetupForm()
|
|
selected_disk = 1
|
|
disk_form.disk.choices = [(disk, disk) for disk in setup_data]
|
|
disk_form.disk.data = selected_disk
|
|
|
|
r = server.get('/cache/list', payload={'clients': [ip]})
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('commands')
|
|
|
|
clients_info = r.json()['clients']
|
|
|
|
if not clients_info:
|
|
flash(_('ogServer returned an empty client list'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
storage_data = {}
|
|
images_data = {}
|
|
client_images = {}
|
|
|
|
get_cache_info(clients_info, storage_data, images_data, client_images)
|
|
|
|
r = server.get('/efi', payload={'clients': [ip]})
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('commands')
|
|
|
|
efi_data = r.json()['clients'][0]
|
|
|
|
scopes, clients = get_scopes(set(ips))
|
|
|
|
return render_template('actions/client_details.html', form=form,
|
|
parent="commands.html", scopes=scopes,
|
|
setup_data=setup_data,
|
|
disk_form=disk_form,
|
|
selected_disk=selected_disk,
|
|
images_data=images_data,
|
|
storage_data=storage_data,
|
|
client_images=client_images,
|
|
efi_data=efi_data)
|
|
|
|
@app.route('/action/client/update', methods=['GET', 'POST'])
|
|
@login_required
|
|
def action_client_update():
|
|
form = ClientDetailsForm(request.form)
|
|
if request.method == 'POST':
|
|
|
|
if not is_valid_ip(form.ip.data):
|
|
flash(_('Invalid IP address'), category='error')
|
|
return redirect(url_for("scopes"))
|
|
|
|
mac_address = normalize_mac(form.mac.data)
|
|
|
|
if not is_valid_normalized_mac(mac_address):
|
|
flash(_('Invalid MAC address'), category='error')
|
|
return redirect(url_for("scopes"))
|
|
|
|
payload = {"ip": form.ip.data,
|
|
"id": int(form.client_id.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": mac_address }
|
|
server = get_server_from_ip_port(form.server.data)
|
|
r = server.post('/client/update', payload)
|
|
if not r:
|
|
return ogserver_down('scopes')
|
|
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):
|
|
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.server.data = "{0}:{1}".format(server.ip, server.port)
|
|
form.client_id.data = db_client['id']
|
|
form.ip.data = db_client['ip']
|
|
form.name.data = db_client['name']
|
|
form.mac.data = prettify_mac(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_data = get_client_setup(ip)
|
|
client_setup_add_image_names(server, setup_data)
|
|
except ServerError:
|
|
return ogserver_down('scopes')
|
|
except ServerErrorCode:
|
|
return ogserver_error('scopes')
|
|
|
|
disk_form = SetupForm()
|
|
selected_disk = 1
|
|
disk_form.disk.choices = [(disk, disk) for disk in setup_data]
|
|
disk_form.disk.data = selected_disk
|
|
|
|
form.submit.render_kw = {"formaction": url_for('action_client_update')}
|
|
|
|
return render_template('actions/client_details.html', form=form,
|
|
setup_data=setup_data,
|
|
disk_form=disk_form,
|
|
selected_disk=selected_disk,
|
|
parent="scopes.html", scopes=scopes)
|
|
|
|
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 not r:
|
|
return ogserver_down('scopes')
|
|
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 not r:
|
|
return ogserver_down('scopes')
|
|
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()
|
|
|
|
if not 'folder' in params:
|
|
flash(_('Please, select a folder to modify'), category='error')
|
|
return redirect(url_for('scopes'))
|
|
|
|
folder_id = int(params.get('folder'))
|
|
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 not r:
|
|
return ogserver_down('scopes')
|
|
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':
|
|
|
|
if not form.validate():
|
|
flash(form.errors, category='error')
|
|
return redirect(url_for('scopes'))
|
|
|
|
client_name = form.name.data
|
|
|
|
if not client_name:
|
|
flash(_('Invalid empty client name'), category='error')
|
|
return redirect(url_for("scopes"))
|
|
|
|
if not is_valid_ip(form.ip.data):
|
|
flash(_('Invalid IP address'), category='error')
|
|
return redirect(url_for("scopes"))
|
|
|
|
mac_address = normalize_mac(form.mac.data)
|
|
|
|
if not is_valid_normalized_mac(mac_address):
|
|
flash(_('Invalid MAC address'), category='error')
|
|
return redirect(url_for("scopes"))
|
|
|
|
payload = {"boot": form.boot.data,
|
|
"ip": form.ip.data,
|
|
"livedir": form.livedir.data,
|
|
"mac": mac_address,
|
|
"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 not r:
|
|
return ogserver_down('scopes')
|
|
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': 'aa:bb:cc:dd:ee:aa'}
|
|
|
|
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)
|
|
|
|
def build_client_move_choices(scopes, choices, path):
|
|
for scope in scopes.get('scope'):
|
|
new_path = f'{path}{scope.get("name")}/'
|
|
|
|
if scope.get('type') == 'room':
|
|
choices.append((f'{scope["id"]} 0', new_path))
|
|
|
|
for room_child in scope.get('scope'):
|
|
if room_child.get('type') == 'folder':
|
|
choices.append(
|
|
(f'{scope["id"]} {room_child["id"]}',
|
|
f'{new_path}{room_child.get("name")}/')
|
|
)
|
|
else:
|
|
build_client_move_choices(scope, choices, new_path)
|
|
|
|
def get_client_move_choices(scopes):
|
|
choices = []
|
|
build_client_move_choices(scopes, choices, '')
|
|
return choices
|
|
|
|
@app.route('/action/client/move', methods=['GET', 'POST'])
|
|
@login_required
|
|
def action_client_move():
|
|
form = ClientMoveForm(request.form)
|
|
if request.method == 'POST':
|
|
ips = form.ips.data.split(' ')
|
|
if not validate_elements(ips):
|
|
return redirect(url_for('scopes'))
|
|
|
|
room_id, folder_id = form.scopes.data.split(' ')
|
|
|
|
payload = {"clients": ips,
|
|
"room": int(room_id),
|
|
"folder_id": int(folder_id)}
|
|
server = get_server_from_clients(ips)
|
|
r = server.post('/client/move', payload)
|
|
if not r:
|
|
return ogserver_down('scopes')
|
|
if r.status_code != requests.codes.ok:
|
|
flash(_('ogServer: error moving client'),
|
|
category='error')
|
|
else:
|
|
flash(_('Client moved successfully'),
|
|
category='info')
|
|
return redirect(url_for('scopes'))
|
|
else:
|
|
ips = parse_elements(request.args.to_dict())
|
|
form.ips.data = " ".join(ips)
|
|
|
|
if not validate_elements(ips):
|
|
return redirect(url_for('scopes'))
|
|
|
|
scopes, clients = get_scopes(set(ips))
|
|
form.scopes.choices = get_client_move_choices(scopes)
|
|
selected_clients = list(get_selected_clients(scopes['scope']).items())
|
|
|
|
return render_template('actions/client_move.html', form=form,
|
|
selected_clients=selected_clients,
|
|
scopes=scopes)
|
|
|
|
|
|
PLACEHOLDER_CLIENT_IMPORT_TEXT = '''client_name1,94:c6:91:a6:25:1a,10.141.10.100
|
|
client_name2,94:c6:91:a6:25:1b,10.141.10.101'''
|
|
|
|
@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.client_conf.render_kw = {'placeholder': PLACEHOLDER_CLIENT_IMPORT_TEXT}
|
|
|
|
scopes, _clients = get_scopes()
|
|
return render_template('actions/import_clients.html', form=form,
|
|
scopes=scopes)
|
|
|
|
|
|
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 = []
|
|
for client_data in form.client_conf.data.splitlines():
|
|
client_values = split_csv(client_data)
|
|
|
|
if len(client_values) != 3 or any(not v for v in client_values):
|
|
flash(_('Each client requires 3 values'), category='error')
|
|
return redirect(url_for('scopes'))
|
|
|
|
client_name = client_values[0]
|
|
|
|
client_mac = client_values[1]
|
|
normalized_mac = normalize_mac(client_mac)
|
|
|
|
if not is_valid_normalized_mac(normalized_mac):
|
|
flash(_(f'Invalid MAC address {client_mac}'), category='error')
|
|
return redirect(url_for("scopes"))
|
|
|
|
client_ip = client_values[2]
|
|
|
|
if not is_valid_ip(client_ip):
|
|
flash(_(f'Invalid IP address {client_ip}'), category='error')
|
|
return redirect(url_for("scopes"))
|
|
|
|
clients.append({
|
|
'name': client_name,
|
|
'mac': normalized_mac,
|
|
'ip': client_ip
|
|
})
|
|
|
|
if not clients:
|
|
flash(_('No client configuration found'), 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:
|
|
for key, value in client.items():
|
|
payload[key] = value
|
|
resp = server.post('/client/add', payload)
|
|
|
|
if resp.status_code != requests.codes.ok:
|
|
flash(_('ogServer: error adding client {}').format(client['name']),
|
|
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 not r:
|
|
return ogserver_down('scopes')
|
|
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'))
|
|
|
|
@app.route('/action/script/run', methods=['GET', 'POST'])
|
|
@login_required
|
|
def action_run_script():
|
|
form = RunScriptForm(request.form)
|
|
if request.method == 'POST':
|
|
ips = form.ips.data.split(' ')
|
|
if not validate_elements(ips):
|
|
return redirect(url_for('commands'))
|
|
|
|
arguments = form.arguments.data.split(' ')
|
|
cmd_elems = [form.script.data] + arguments
|
|
|
|
payload = {
|
|
'clients': ips,
|
|
'run': ';|\n\r'.join(cmd_elems),
|
|
'echo': True
|
|
}
|
|
server = get_server_from_clients(ips)
|
|
r = server.post('/shell/run', payload)
|
|
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('commands')
|
|
|
|
flash(_('Script run sent successfully'), category='info')
|
|
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)
|
|
different_setups = False
|
|
reference_patitioning = None
|
|
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'][1:]
|
|
if not reference_patitioning:
|
|
reference_patitioning = partitions
|
|
elif reference_patitioning != partitions:
|
|
different_setups = True
|
|
|
|
if different_setups:
|
|
flash(_('Some clients don\'t have same configuration'), category='info')
|
|
|
|
r = server.get('/shell/list')
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('commands')
|
|
|
|
scripts = r.json()['scripts']
|
|
|
|
for script in scripts:
|
|
form.script.choices.append((script, script))
|
|
|
|
scopes, clients = get_scopes(set(ips))
|
|
selected_clients = list(get_selected_clients(scopes['scope']).items())
|
|
return render_template('actions/script_run.html', form=form,
|
|
selected_clients=selected_clients,
|
|
scopes=scopes)
|
|
|
|
@app.route('/action/script/output', methods=['GET'])
|
|
@login_required
|
|
def action_script_display_output():
|
|
ips = parse_elements(request.args.to_dict())
|
|
|
|
if not validate_elements(ips):
|
|
return redirect(url_for('commands'))
|
|
|
|
server = get_server_from_clients(ips)
|
|
|
|
r = server.get('/shell/output', payload={'clients': list(ips)})
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('commands')
|
|
|
|
client_data = r.json()['clients']
|
|
|
|
for client in client_data:
|
|
if not 'output' in client:
|
|
client['output'] = _('No output')
|
|
|
|
if not 'retcode' in client:
|
|
client['retcode'] = 0
|
|
|
|
if not 'cmd' in client:
|
|
client['cmd'] = ['Null']
|
|
|
|
if not 'tstamp' in client:
|
|
client['tstamp'] = _('No time available')
|
|
else:
|
|
timestamp_utc = datetime.datetime.utcfromtimestamp(client['tstamp'])
|
|
client['tstamp'] = timestamp_utc.strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
client_data.sort(key=lambda x:x['tstamp'], reverse=True)
|
|
|
|
scopes, clients = get_scopes(set(ips))
|
|
selected_clients = list(get_selected_clients(scopes['scope']).items())
|
|
return render_template('actions/script_output.html',
|
|
selected_clients=selected_clients,
|
|
scopes=scopes, client_data=client_data)
|
|
|
|
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 }
|
|
|
|
server = get_server_from_clients(ips)
|
|
r = server.post('/mode', payload)
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
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')
|
|
|
|
mode_descriptions = {
|
|
'pxe': 'ogLive (pxe)',
|
|
'11': 'Disk 1 Partition 1 (11)',
|
|
'19pxeADMIN': 'ogLive Debug (19pxeADMIN)',
|
|
'12': 'Disk 1 Partition 2 (12)',
|
|
'13': 'Disk 1 Partition 3 (13)',
|
|
'memtest': 'Memory Test (memtest)',
|
|
}
|
|
|
|
excluded_modes = ['00unknown', '10']
|
|
|
|
most_used_mode = max(modes_set, key=lambda m: len(modes_set[m]))
|
|
available_modes = []
|
|
if most_used_mode in r.json()['modes']:
|
|
most_used_t = (most_used_mode, mode_descriptions.get(most_used_mode, most_used_mode))
|
|
available_modes.append(most_used_t)
|
|
|
|
for mode in r.json()['modes']:
|
|
if mode != most_used_mode and mode not in excluded_modes:
|
|
available_modes.append((mode, mode_descriptions.get(mode, 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']
|
|
if oglive not in oglives:
|
|
oglives[oglive] = [ip]
|
|
else:
|
|
oglives[oglive].append(ip)
|
|
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 not r:
|
|
return ogserver_down('commands')
|
|
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')
|
|
|
|
oglive_list = r.json()['oglive']
|
|
|
|
available_oglives = []
|
|
for oglive in oglive_list:
|
|
available_oglives.append((oglive.get('directory'), oglive.get('directory')))
|
|
|
|
default_idx = int(r.json()['default'])
|
|
default_oglive_dir = oglive_list[default_idx].get('directory')
|
|
available_oglives.append(('default', f'ogLive ({default_oglive_dir})'))
|
|
|
|
form.oglive.choices = 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)
|
|
|
|
|
|
def get_clients_repo_dictionary(ips, server, repositories):
|
|
repos = {}
|
|
repo_id_to_name = {repo["id"]: repo["name"] for repo in repositories}
|
|
|
|
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()
|
|
repo_name = repo_id_to_name[resp['repo_id']]
|
|
if repo_name not in repos:
|
|
repos[repo_name] = [ip]
|
|
else:
|
|
repos[repo_name].append(ip)
|
|
return repos
|
|
|
|
@app.route('/action/repo/set', methods=['GET', 'POST'])
|
|
@login_required
|
|
def action_repo_set():
|
|
form = SetRepoForm(request.form)
|
|
if request.method == 'POST':
|
|
ips = form.ips.data.split(' ')
|
|
payload = {'clients': ips, 'id': int(form.repo.data)}
|
|
server = get_server_from_clients(ips)
|
|
r = server.post('/client/repo', payload)
|
|
if not r:
|
|
return ogserver_down('commands')
|
|
if r.status_code == requests.codes.ok:
|
|
flash(_('Repo 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:
|
|
repositories = get_repositories(server)
|
|
repos_set = get_clients_repo_dictionary(ips, server, repositories)
|
|
except ServerError:
|
|
return ogserver_down('commands')
|
|
except ServerErrorCode:
|
|
return ogserver_error('commands')
|
|
|
|
form.repo.choices = [(repo["id"], repo["name"]) for repo in repositories]
|
|
|
|
scopes, clients = get_scopes(set(ips))
|
|
selected_clients = list(get_selected_clients(scopes['scope']).items())
|
|
return render_template('actions/repo_set.html', repos_set=repos_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 = remove_accents(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 not r:
|
|
return ogserver_down('commands')
|
|
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')
|
|
|
|
invalid_part_types = get_invalid_image_partition_types()
|
|
|
|
for part in r.json()['partitions'][1:]:
|
|
part_type = PART_TYPE_CODES.get(int(part.get('code')), 'UNKNOWN')
|
|
|
|
if part_type in invalid_part_types:
|
|
continue
|
|
|
|
if part.get('disk') != 1:
|
|
continue
|
|
|
|
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))
|
|
selected_clients = list(get_selected_clients(scopes['scope']).items())
|
|
|
|
return render_template('actions/image_create.html', form=form,
|
|
scopes=scopes,
|
|
selected_clients=selected_clients)
|
|
|
|
|
|
@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_id': int(repository['id']),
|
|
'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 not r:
|
|
return ogserver_down('commands')
|
|
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'))
|
|
|
|
params = request.args.to_dict()
|
|
center_id = int(params.get('scope-center'))
|
|
ips = parse_elements(params)
|
|
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_from_repo(server, repo_id)
|
|
|
|
if not images:
|
|
flash(_(f'Computer(s) assigned to a repo with no images'), category='error')
|
|
return redirect(url_for('commands'))
|
|
|
|
images = filter_images_allowed_in_center(server, images, center_id)
|
|
except ServerError:
|
|
return ogserver_down('commands')
|
|
except ServerErrorCode:
|
|
return ogserver_error('commands')
|
|
|
|
sort_images(images)
|
|
|
|
for image in images:
|
|
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')
|
|
|
|
invalid_part_types = get_invalid_image_partition_types()
|
|
|
|
part_content = {}
|
|
for part in r.json()['partitions'][1:]:
|
|
part_type = PART_TYPE_CODES.get(int(part.get('code')), 'UNKNOWN')
|
|
|
|
if part_type in invalid_part_types:
|
|
continue
|
|
|
|
if part.get('disk') != 1:
|
|
continue
|
|
|
|
partition_value = f"{part.get('disk')} {part.get('partition')} {part.get('code')}"
|
|
partition_text = 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')}"
|
|
|
|
form.os.choices.append(
|
|
(partition_value,
|
|
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')}")
|
|
)
|
|
|
|
if part['image']:
|
|
for image in images:
|
|
if image['id'] == part['image']:
|
|
part_content[partition_value] = part['image']
|
|
break
|
|
|
|
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, part_content=part_content)
|
|
|
|
|
|
@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 not r:
|
|
return ogserver_down('commands')
|
|
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 not r:
|
|
return ogserver_down('commands')
|
|
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 not r:
|
|
return ogserver_down('scopes')
|
|
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 not r:
|
|
return ogserver_down('scopes')
|
|
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 center:
|
|
flash(_('Please, select one center'), category='error')
|
|
return redirect(url_for("scopes"))
|
|
if not server:
|
|
flash(_('Internal error: server was not sent as request argument'),
|
|
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 not r:
|
|
return ogserver_down('scopes')
|
|
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)
|
|
|
|
@app.route('/action/center/info', methods=['GET'])
|
|
@login_required
|
|
def action_center_info():
|
|
form = CenterForm(request.form)
|
|
params = request.args.to_dict()
|
|
server = params.get('scope-server')
|
|
center = params.get('scope-center')
|
|
if not center:
|
|
flash(_('Please, select one center'), category='error')
|
|
return redirect(url_for("scopes"))
|
|
if not server:
|
|
flash(_('Internal error: server was not sent as request argument'),
|
|
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
|
|
form.center.render_kw = {'readonly': True}
|
|
|
|
payload = {"id": int(center)}
|
|
r = server.get('/center/info', payload)
|
|
if not r:
|
|
return ogserver_down('scopes')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('scopes')
|
|
|
|
form.comment.data = r.json()['comment']
|
|
form.comment.render_kw = {'readonly': True}
|
|
form.name.data = r.json()['name']
|
|
form.name.render_kw = {'readonly': True}
|
|
form.submit.render_kw = {'readonly': True, 'hidden': True}
|
|
|
|
scopes, clients = get_scopes()
|
|
return render_template('actions/center_details.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 not r:
|
|
return ogserver_down('scopes')
|
|
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 not r:
|
|
return ogserver_down('scopes')
|
|
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 not r:
|
|
return ogserver_down('scopes')
|
|
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/info', methods=['GET'])
|
|
@login_required
|
|
def action_room_info():
|
|
form = RoomForm(request.form)
|
|
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
|
|
|
|
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.name.render_kw = {'readonly': True}
|
|
form.gateway.data = r.json()['gateway']
|
|
form.gateway.render_kw = {'readonly': True}
|
|
form.netmask.data = r.json()['netmask']
|
|
form.netmask.render_kw = {'readonly': True}
|
|
|
|
form.submit.render_kw = {'readonly': True, 'hidden': True}
|
|
|
|
scopes, clients = get_scopes()
|
|
return render_template('actions/room_details.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 not r:
|
|
return ogserver_down('scopes')
|
|
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_sorted_repos(repos):
|
|
return dict(sorted(repos.items(), key=lambda item: item[1]['name']))
|
|
|
|
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={}
|
|
|
|
sort_images(images)
|
|
|
|
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'] = _get_sorted_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 = get_all_repositories()
|
|
return render_template('repos.html', repos_resp=responses)
|
|
|
|
def repo_addr_is_valid(form):
|
|
invalid_ips = []
|
|
empty_ips = 0
|
|
ip_count = 0
|
|
for ip in form.addr:
|
|
ip_count += 1
|
|
ip = ip.data.strip()
|
|
if not ip:
|
|
empty_ips += 1
|
|
continue
|
|
if not is_valid_ip(ip):
|
|
invalid_ips.append('"' + ip + '"')
|
|
|
|
res = True
|
|
if empty_ips > 0:
|
|
res = False
|
|
flash(_(f'{empty_ips} addresses are invalid'), category='error')
|
|
|
|
if invalid_ips:
|
|
res = False
|
|
flash(_(f'The following addresses are invalid: {" ".join(invalid_ips)}'), category='error')
|
|
|
|
MAX_IP_COUNT = 128
|
|
if ip_count > MAX_IP_COUNT:
|
|
res = False
|
|
flash(_(f'More than {MAX_IP_COUNT} addresses is not supported'), category='error')
|
|
|
|
return res
|
|
|
|
@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'))
|
|
|
|
if not repo_addr_is_valid(form):
|
|
return redirect(url_for('manage_repos'))
|
|
|
|
addr = [ip.data.strip() for ip in form.addr]
|
|
|
|
payload = {"name": form.name.data,
|
|
"addr": addr,
|
|
"center": 1}
|
|
server = get_server_from_ip_port(form.server.data)
|
|
r = server.post('/repository/add', payload)
|
|
if not r:
|
|
return ogserver_down('manage_repos')
|
|
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']
|
|
form.addr.append_entry('')
|
|
|
|
responses = get_all_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)
|
|
|
|
if not repo_addr_is_valid(form):
|
|
return redirect(url_for('manage_repos'))
|
|
|
|
addr = [ip.data.strip() for ip in form.addr]
|
|
|
|
payload = { 'id': int(form.repo_id.data),
|
|
'name': form.name.data,
|
|
'addr': addr,
|
|
'center': 1}
|
|
r = server.post('/repository/update', payload)
|
|
if not r:
|
|
return ogserver_down('manage_repos')
|
|
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']
|
|
for addr in repository['addr']:
|
|
form.addr.append_entry(addr)
|
|
|
|
responses = get_all_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': int(form.repo_id.data) }
|
|
r = server.post('/repository/delete', payload)
|
|
if not r:
|
|
return ogserver_down('manage_repos')
|
|
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)
|
|
|
|
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.name.data = repository['name']
|
|
for addr in repository['addr']:
|
|
form.addr.append_entry(addr)
|
|
for field in form.addr:
|
|
field.render_kw = {'readonly': True}
|
|
responses = get_all_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}
|
|
for addr in repository['addr']:
|
|
form.addr.append_entry(addr)
|
|
for field in form.addr:
|
|
field.render_kw = {'readonly': True}
|
|
form.submit.render_kw = {"style": "visibility:hidden;"}
|
|
responses = get_all_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/update', methods=['GET'])
|
|
@login_required
|
|
def server_update_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'))
|
|
|
|
r = selected_server.get('/server')
|
|
if not r:
|
|
return ogserver_down('manage_servers')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('manage_servers')
|
|
|
|
form = ServerConfigurationForm()
|
|
server_config = r.json()['servers']
|
|
for c in server_config:
|
|
form.addr.append_entry(c['address'])
|
|
|
|
form.server_addr.data = selected_server.ip + ":" + str(selected_server.port)
|
|
|
|
return render_template('actions/server_update.html', form=form,
|
|
servers=servers)
|
|
|
|
@app.route('/server/update', methods=['POST'])
|
|
@login_required
|
|
def server_update_post():
|
|
form = ServerConfigurationForm(request.form)
|
|
try:
|
|
server = get_server_from_ip_port(form.server_addr.data)
|
|
except Exception:
|
|
flash(_('Server {} does not exist').format(form.server_addr.data),
|
|
category='error')
|
|
return redirect(url_for('manage_servers'))
|
|
|
|
addr_list = [ip.data.strip() for ip in form.addr]
|
|
invalid_ips = []
|
|
for ip in addr_list:
|
|
if not is_valid_ip(ip):
|
|
invalid_ips.append('"' + ip + '"')
|
|
|
|
if invalid_ips:
|
|
flash(_(f'The following addresses are invalid: {" ".join(invalid_ips)}'), category='error')
|
|
return redirect(url_for('manage_servers'))
|
|
|
|
r = server.get('/server')
|
|
if not r:
|
|
return ogserver_down('manage_servers')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('manage_servers')
|
|
server_config = r.json()['servers']
|
|
|
|
# Remove
|
|
for c in server_config:
|
|
if c['address'] in addr_list:
|
|
continue
|
|
|
|
payload = {'id': c['id']}
|
|
rd = server.delete('/server', payload=payload)
|
|
if not rd:
|
|
return ogserver_down('manage_servers')
|
|
if rd.status_code != requests.codes.ok:
|
|
return ogserver_error('manage_servers')
|
|
|
|
# Add
|
|
for ip in addr_list:
|
|
found = False
|
|
for c in server_config:
|
|
if ip == c['address']:
|
|
found = True
|
|
break
|
|
|
|
if found:
|
|
continue
|
|
|
|
payload = {'address': ip}
|
|
ra = server.post('/server', payload=payload)
|
|
if not ra:
|
|
return ogserver_down('manage_servers')
|
|
if ra.status_code != requests.codes.ok:
|
|
return ogserver_error('manage_servers')
|
|
|
|
flash(_('Server update request sent successfully'), category='info')
|
|
return redirect(url_for('manage_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('manage_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 {} does not exist').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['id'], center['name']) for center in centers]
|
|
available_centers.extend(centers)
|
|
return available_centers
|
|
|
|
def get_center_choices():
|
|
responses = multi_request('get', '/scopes')
|
|
available_scopes = list()
|
|
for resp in responses:
|
|
centers = parse_scopes_from_tree(resp['json'], 'center')
|
|
centers = [(str(center['id']), center['name']) for center in centers]
|
|
available_scopes.extend(centers)
|
|
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, preserve_pwd):
|
|
username = form.username.data
|
|
|
|
if preserve_pwd:
|
|
pwd_hash = form.pwd.data
|
|
else:
|
|
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 False
|
|
|
|
admin = form.admin.data
|
|
scopes = form.scopes.data
|
|
|
|
user = {
|
|
'USER': username,
|
|
'PASS': pwd_hash,
|
|
'ADMIN': admin,
|
|
'SCOPES': scopes,
|
|
'PERMISSIONS': {
|
|
'CLIENT': {
|
|
'ADD': form.client_permissions.add.data,
|
|
'UPDATE': form.client_permissions.update.data,
|
|
'DELETE': form.client_permissions.delete.data,
|
|
},
|
|
'CENTER': {
|
|
'ADD': form.center_permissions.add.data,
|
|
'UPDATE': form.center_permissions.update.data,
|
|
'DELETE': form.center_permissions.delete.data,
|
|
},
|
|
'ROOM': {
|
|
'ADD': form.room_permissions.add.data,
|
|
'UPDATE': form.room_permissions.update.data,
|
|
'DELETE': form.room_permissions.delete.data,
|
|
},
|
|
'FOLDER': {
|
|
'ADD': form.folder_permissions.add.data,
|
|
'UPDATE': form.folder_permissions.update.data,
|
|
'DELETE': form.folder_permissions.delete.data,
|
|
},
|
|
'IMAGE': {
|
|
'ADD': form.image_permissions.add.data,
|
|
'UPDATE': form.image_permissions.update.data,
|
|
'DELETE': form.image_permissions.delete.data,
|
|
},
|
|
'REPOSITORY': {
|
|
'ADD': form.repository_permissions.add.data,
|
|
'UPDATE': form.repository_permissions.update.data,
|
|
'DELETE': form.repository_permissions.delete.data,
|
|
},
|
|
},
|
|
}
|
|
|
|
filename = os.path.join(app.root_path, ogcp_cfg_path)
|
|
with open(filename, 'r+') as file:
|
|
config = json.load(file)
|
|
|
|
old_user = get_user(username)
|
|
|
|
if old_user:
|
|
config['USERS'].remove(old_user)
|
|
config['USERS'].append(user)
|
|
|
|
file.seek(0)
|
|
json.dump(config, file, indent='\t')
|
|
file.truncate()
|
|
|
|
if old_user:
|
|
app.config['USERS'].remove(old_user)
|
|
app.config['USERS'].append(user)
|
|
|
|
return True
|
|
|
|
|
|
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_center_choices()
|
|
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'))
|
|
|
|
if save_user(form, preserve_pwd=False):
|
|
flash(_('User created successfully'), category='info')
|
|
return redirect(url_for('users'))
|
|
|
|
|
|
@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 {} does not exist').format(username), category='error')
|
|
return redirect(url_for('users'))
|
|
|
|
form = EditUserForm()
|
|
form.username.data = user.get('USER')
|
|
form.username.render_kw = {'readonly': True}
|
|
form.admin.data = user.get('ADMIN')
|
|
form.scopes.data = user.get('SCOPES')
|
|
|
|
if 'PERMISSIONS' in user:
|
|
permissions = user.get('PERMISSIONS')
|
|
|
|
def get_permission(target, action):
|
|
if not target in permissions:
|
|
return True
|
|
return permissions[target].get(action, True)
|
|
|
|
form.client_permissions.add.data = get_permission('CLIENT', 'ADD')
|
|
form.client_permissions.update.data = get_permission('CLIENT', 'UPDATE')
|
|
form.client_permissions.delete.data = get_permission('CLIENT', 'DELETE')
|
|
form.center_permissions.add.data = get_permission('CENTER', 'ADD')
|
|
form.center_permissions.update.data = get_permission('CENTER', 'UPDATE')
|
|
form.center_permissions.delete.data = get_permission('CENTER', 'DELETE')
|
|
form.room_permissions.add.data = get_permission('ROOM', 'ADD')
|
|
form.room_permissions.update.data = get_permission('ROOM', 'UPDATE')
|
|
form.room_permissions.delete.data = get_permission('ROOM', 'DELETE')
|
|
form.folder_permissions.add.data = get_permission('FOLDER', 'ADD')
|
|
form.folder_permissions.update.data = get_permission('FOLDER', 'UPDATE')
|
|
form.folder_permissions.delete.data = get_permission('FOLDER', 'DELETE')
|
|
form.image_permissions.add.data = get_permission('IMAGE', 'ADD')
|
|
form.image_permissions.update.data = get_permission('IMAGE', 'UPDATE')
|
|
form.image_permissions.delete.data = get_permission('IMAGE', 'DELETE')
|
|
form.repository_permissions.add.data = get_permission('REPOSITORY', 'ADD')
|
|
form.repository_permissions.update.data = get_permission('REPOSITORY', 'UPDATE')
|
|
form.repository_permissions.delete.data = get_permission('REPOSITORY', 'DELETE')
|
|
|
|
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 = EditUserForm(request.form)
|
|
form.scopes.choices = get_center_choices()
|
|
if not form.validate():
|
|
flash(form.errors, category='error')
|
|
return redirect(url_for('users'))
|
|
|
|
username = form.username.data
|
|
old_user_data = get_user(username)
|
|
if not old_user_data:
|
|
flash(_('User {} does not exist').format(username), category='error')
|
|
return redirect(url_for('users'))
|
|
|
|
preserve_pwd = (not form.pwd.data and not form.pwd_confirm.data)
|
|
if preserve_pwd:
|
|
form.pwd.data = old_user_data.get("PASS")
|
|
|
|
if save_user(form, preserve_pwd):
|
|
flash(_('User edited successfully'), category='info')
|
|
return redirect(url_for('users'))
|
|
|
|
|
|
@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 {} does not exist').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 {} does not exist').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']
|
|
|
|
checksum = image.get('checksum', _('Unknown'))
|
|
if not checksum:
|
|
checksum = _('Unknown')
|
|
|
|
form.checksum.data = checksum
|
|
|
|
try:
|
|
responses = get_images_grouped_by_repos()
|
|
except ServerError:
|
|
return ogserver_down('images')
|
|
except ServerErrorCode:
|
|
return ogserver_error('images')
|
|
|
|
r = server.get('/image/restrict', {'image': image['id']})
|
|
if not r:
|
|
raise ServerError
|
|
if r.status_code != requests.codes.ok:
|
|
raise ServerErrorCode
|
|
|
|
form.scopes.choices = get_available_centers()
|
|
form.scopes.data = [str(scope) for scope in r.json().get('scopes')]
|
|
|
|
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):
|
|
return redirect(url_for('images'))
|
|
|
|
server = get_server_from_ip_port(form.server.data)
|
|
|
|
for id in ids:
|
|
payload = {'image': id}
|
|
r = server.post('/image/delete', payload)
|
|
if not r:
|
|
return ogserver_down('images')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('images')
|
|
|
|
flash(_('Image deletion request sent successfully'), category='info')
|
|
return redirect(url_for('images'))
|
|
else:
|
|
params = request.args.to_dict()
|
|
image_ids = [imgid for name, imgid in params.items()
|
|
if name != 'csrf_token' and name != 'image-server']
|
|
|
|
if not validate_elements(image_ids):
|
|
return redirect(url_for('images'))
|
|
|
|
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 = ' '.join(image_ids)
|
|
form.server.data = params['image-server']
|
|
|
|
return render_template('actions/delete_image.html', form=form,
|
|
image_ids=image_ids,
|
|
responses=responses)
|
|
|
|
@app.route('/action/image/config', methods=['GET', 'POST'])
|
|
@login_required
|
|
def action_image_config():
|
|
form = ImageConfigForm(request.form)
|
|
if request.method == 'POST':
|
|
image_id = int(form.image_id.data)
|
|
server = get_server_from_ip_port(form.server.data)
|
|
|
|
scope_list = [int(scope) for scope in form.scopes.data]
|
|
|
|
payload = {'image': image_id, 'scopes': scope_list}
|
|
|
|
r = server.post('/image/restrict', payload)
|
|
if not r:
|
|
return ogserver_down('images')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('images')
|
|
|
|
flash(_('Image updated 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]
|
|
image_name=image_name.split('_', 1)[0]
|
|
server = get_server_from_ip_port(params['image-server'])
|
|
|
|
form.image_id.data = image_id
|
|
form.name.data = image_name
|
|
|
|
r = server.get('/image/restrict', {'image': int(image_id)})
|
|
if not r:
|
|
return ogserver_down('images')
|
|
if r.status_code != requests.codes.ok:
|
|
return ogserver_error('images')
|
|
|
|
form.server.data = params['image-server']
|
|
form.scopes.choices = get_available_centers()
|
|
form.scopes.data = [str(scope) for scope in r.json().get('scopes')]
|
|
|
|
try:
|
|
responses = get_images_grouped_by_repos()
|
|
except ServerError:
|
|
return ogserver_down('images')
|
|
except ServerErrorCode:
|
|
return ogserver_error('images')
|
|
|
|
return render_template('actions/image_config.html', form=form,
|
|
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)
|
|
|