v7.3.1.0: Refactor server-side code to use JSON-based APIs

The commit refactors the server-side modules of the Roxy-WI web interface to use JSON-based APIs for improved data exchange. Specifically, methods for user, group and SSH management have been updated to accept and return JSON data. Simultaneously, error handling is improved to give more informative responses instead of plain strings. The client-side JavaScript code is also refactored to accommodate these changes. Additionally, some minor corrections are made in the UI templates.
pull/390/head
Aidaho 2024-06-16 19:20:18 +03:00
parent 9c5bc5ca4b
commit ea8e51ee29
27 changed files with 723 additions and 707 deletions

View File

@ -785,7 +785,7 @@ def create_ssh():
token = request.headers.get('token')
login, group_id, role_id = user_sql.get_username_group_id_from_api_token(token)
try:
ssh_mod.create_ssh_cread_api(name, enable, group_id, username, password)
ssh_mod.create_ssh_cred_api(name, enable, group_id, username, password)
data = {'status': 'done'}
except Exception as e:
data = {'status': f'error: {e}'}

View File

@ -155,7 +155,7 @@ def default_values():
{'service_id': 3, 'service': 'Keepalived', 'slug': 'keepalived'},
{'service_id': 4, 'service': 'Apache', 'slug': 'apache'},
{'service_id': 5, 'service': 'HA cluster', 'slug': 'cluster'},
{'service_id': 6, 'service': 'UDP balancing', 'slug': 'udp'},
{'service_id': 6, 'service': 'UDP listener', 'slug': 'udp'},
]
try:

View File

@ -66,7 +66,7 @@ def get_config(server_ip, cfg, service='haproxy', **kwargs):
"""
config_path = ''
if service in ('keepalived', 'haproxy'):
if service in ('keepalived', 'haproxy') and not kwargs.get("waf"):
config_path = sql.get_setting(f'{service}_config_path')
elif service in ('nginx', 'apache'):
config_path = _replace_config_path_to_correct(kwargs.get('config_file_name'))

View File

@ -18,16 +18,14 @@ def select_groups(**kwargs):
return query_res
def add_group(name, description):
def add_group(name: str, description: str) -> int:
try:
last_insert = Groups.insert(name=name, description=description)
last_insert_id = last_insert.execute()
last_id = Groups.insert(name=name, description=description).execute()
except Exception as e:
out_error(e)
return False
else:
add_setting_for_new_group(last_insert_id)
return True
add_setting_for_new_group(last_id)
return last_id
def add_setting_for_new_group(group_id):

View File

@ -6,12 +6,12 @@ from app.modules.db.common import out_error
import app.modules.roxy_wi_tools as roxy_wi_tools
def add_user(user, email, password, role, activeuser, group):
def add_user(user, email, password, role, enabled, group):
if password != 'aduser':
try:
hashed_pass = roxy_wi_tools.Tools.get_hash(password)
last_id = User.insert(
username=user, email=email, password=hashed_pass, role=role, activeuser=activeuser, groups=group
username=user, email=email, password=hashed_pass, role=role, activeuser=enabled, groups=group
).execute()
except Exception as e:
out_error(e)
@ -20,7 +20,7 @@ def add_user(user, email, password, role, activeuser, group):
else:
try:
last_id = User.insert(
username=user, email=email, role=role, ldap_user=1, activeuser=activeuser, groups=group
username=user, email=email, role=role, ldap_user=1, activeuser=enabled, groups=group
).execute()
except Exception as e:
out_error(e)

View File

@ -318,5 +318,3 @@ def update_waf_metrics_enable(name, enable):
Waf.update(metrics=enable).where(Waf.server_id == server_id).execute()
except Exception as e:
out_error(e)
else:
return 'ok'

View File

@ -167,7 +167,7 @@ def keep_action_history(service: str, action: str, server_ip: str, login: str, u
hostname = ha_sql.select_cluster_name(int(server_id))
except Exception as e:
logging('Roxy-WI server', f'Cannot get info about cluster {server_ip} for history: {e}', roxywi=1)
elif service == 'UDP Listener':
elif service == 'UDP listener':
try:
server_id = int(server_ip)
listener = udp_sql.get_listener(server_id)
@ -317,3 +317,8 @@ def handle_exceptions(ex: Exception, server_ip: str, message: str, **kwargs: Any
"""
logging(server_ip, f'error: {message}: {ex}', **kwargs)
raise Exception(f'error: {message}: {ex}')
def handle_json_exceptions(ex: Exception, server_ip: str, message: str, **kwargs: Any) -> dict:
logging(server_ip, f'error: {message}: {ex}', roxywi=1, login=1, **kwargs)
return {'status': 'failed', 'error': f'{message}: {ex}'}

View File

@ -2,16 +2,15 @@ import app.modules.db.group as group_sql
import app.modules.roxywi.common as roxywi_common
def update_group(group_id: int, group_name: str, desc: str) -> str:
def update_group(group_id: int, group_name: str, desc: str) -> None:
if group_name == '':
return roxywi_common.return_error_message()
else:
try:
group_sql.update_group(group_name, desc, group_id)
roxywi_common.logging('Roxy-WI server', f'The {group_name} has been updated', roxywi=1, login=1)
return 'ok'
except Exception as e:
return f'error: {e}'
raise Exception(e)
def delete_group(group_id: int) -> str:

View File

@ -9,10 +9,9 @@ import app.modules.roxywi.common as roxywi_common
import app.modules.tools.alerting as alerting
def create_user(new_user: str, email: str, password: str, role: int, activeuser: int, group: int) -> None:
def create_user(new_user: str, email: str, password: str, role: int, enabled: int, group: int) -> int:
try:
user_id = user_sql.add_user(new_user, email, password, role, activeuser, group)
# user_sql.update_user_role(user_id, group, role)
user_id = user_sql.add_user(new_user, email, password, role, enabled, group)
roxywi_common.logging(f'a new user {new_user}', 'has been created', roxywi=1, login=1)
try:
user_sql.update_user_role(user_id, group, role)
@ -32,8 +31,10 @@ def create_user(new_user: str, email: str, password: str, role: int, activeuser:
except Exception as e:
roxywi_common.handle_exceptions(e, 'Roxy-WI server', 'Cannot create a new user', roxywi=1, login=1)
return user_id
def delete_user(user_id: int) -> str:
def delete_user(user_id: int):
if user_sql.is_user_super_admin(user_id):
count_super_admin_users = user_sql.get_super_admin_count()
if count_super_admin_users < 2:
@ -45,7 +46,6 @@ def delete_user(user_id: int) -> str:
if user_sql.delete_user(user_id):
user_sql.delete_user_groups(user_id)
roxywi_common.logging(username, ' has been deleted user ', roxywi=1, login=1)
return "ok"
def update_user(email, new_user, user_id, enabled, group_id, role_id):
@ -81,7 +81,7 @@ def get_user_services(user_id: int) -> str:
)
def change_user_services(user: str, user_id: int, user_services: str) -> str:
def change_user_services(user: str, user_id: int, user_services: dict):
services = ''
for _k, v in user_services.items():
@ -91,9 +91,8 @@ def change_user_services(user: str, user_id: int, user_services: str) -> str:
try:
user_sql.update_user_services(services=services, user_id=user_id)
except Exception as e:
return f'error: Cannot save: {e}'
raise Exception(f'error: Cannot save: {e}')
roxywi_common.logging('Roxy-WI server', f'Access to the services has been updated for user: {user}', roxywi=1, login=1)
return 'ok'
def change_user_active_group(group_id: int, user_uuid: str) -> str:

View File

@ -71,7 +71,7 @@ def waf_overview(serv, waf_service) -> str:
return render_template('ajax/overviewWaf.html', service_status=servers_sorted, role=role, waf_service=waf_service, lang=lang)
def change_waf_mode(waf_mode: str, server_hostname: str, service: str) -> str:
def change_waf_mode(waf_mode: str, server_hostname: str, service: str):
serv = server_sql.select_server_by_name(server_hostname)
if service == 'haproxy':
@ -88,10 +88,8 @@ def change_waf_mode(waf_mode: str, server_hostname: str, service: str) -> str:
roxywi_common.logging(serv, f'Has been changed WAF mod to {waf_mode}', roxywi=1, login=1)
return 'ok'
def switch_waf_rule(serv: str, enable: int, rule_id: int) -> str:
def switch_waf_rule(serv: str, enable: int, rule_id: int):
haproxy_path = sql.get_setting('haproxy_dir')
rule_file = waf_sql.select_waf_rule_by_id(rule_id)
conf_file_path = haproxy_path + '/waf/modsecurity.conf'
@ -111,13 +109,13 @@ def switch_waf_rule(serv: str, enable: int, rule_id: int) -> str:
pass
waf_sql.update_enable_waf_rules(rule_id, serv, enable)
return server_mod.ssh_command(serv, cmd)
server_mod.ssh_command(serv, cmd)
def create_waf_rule(serv, service) -> str:
new_waf_rule = common.checkAjaxInput(request.form.get('new_waf_rule'))
new_rule_desc = common.checkAjaxInput(request.form.get('new_rule_description'))
rule_file = common.checkAjaxInput(request.form.get('new_rule_file'))
def create_waf_rule(serv: str, service: str, json_data: dict) -> int:
new_waf_rule = common.checkAjaxInput(json_data['new_waf_rule'])
new_rule_desc = common.checkAjaxInput(json_data['new_rule_description'])
rule_file = common.checkAjaxInput(json_data['new_rule_file'])
rule_file = f'{rule_file}.conf'
waf_path = ''
@ -131,7 +129,7 @@ def create_waf_rule(serv, service) -> str:
cmd = f"sudo echo Include {rule_file_path} >> {conf_file_path} && sudo touch {rule_file_path}"
server_mod.ssh_command(serv, cmd)
waf_sql.insert_new_waf_rule(new_waf_rule, rule_file, new_rule_desc, service, serv)
last_id = waf_sql.insert_new_waf_rule(new_waf_rule, rule_file, new_rule_desc, service, serv)
try:
roxywi_common.logging('WAF', f' A new rule has been created {rule_file} on the server {serv}',
@ -139,4 +137,4 @@ def create_waf_rule(serv, service) -> str:
except Exception:
pass
return 'ok'
return last_id

View File

@ -63,15 +63,13 @@ def ssh_connect(server_ip):
return ssh
def create_ssh_cred() -> str:
name = common.checkAjaxInput(request.form.get('new_ssh'))
enable = common.checkAjaxInput(request.form.get('ssh_enable'))
group = common.checkAjaxInput(request.form.get('new_group'))
def create_ssh_cred(json_data: dict) -> dict:
name = common.checkAjaxInput(json_data['ssh'])
enable = int(json_data['enabled'])
group = common.checkAjaxInput(json_data['group'])
group_name = group_sql.get_group_name_by_id(group)
username = common.checkAjaxInput(request.form.get('ssh_user'))
password = common.checkAjaxInput(request.form.get('ssh_pass'))
page = common.checkAjaxInput(request.form.get('page'))
page = page.split("#")[0]
username = common.checkAjaxInput(json_data['user'])
password = common.checkAjaxInput(json_data['pass'])
lang = roxywi_common.get_user_lang_for_flask()
name = f'{name}_{group_name}'
@ -83,16 +81,17 @@ def create_ssh_cred() -> str:
if username is None or name is None:
return error_mess
else:
try:
cred_sql.insert_new_ssh(name, enable, group, username, password)
except Exception as e:
roxywi_common.handle_exceptions(e, 'Roxy-WI server', 'Cannot create new SSH credentials', roxywi=1, login=1)
roxywi_common.logging('Roxy-WI server', f'New SSH credentials {name} has been created', roxywi=1, login=1)
return render_template('ajax/new_ssh.html', groups=group_sql.select_groups(), sshs=cred_sql.select_ssh(name=name), page=page, lang=lang)
try:
last_id = cred_sql.insert_new_ssh(name, enable, group, username, password)
except Exception as e:
roxywi_common.handle_exceptions(e, 'Roxy-WI server', 'Cannot create new SSH credentials', roxywi=1, login=1)
roxywi_common.logging('Roxy-WI server', f'New SSH credentials {name} has been created', roxywi=1, login=1)
rendered_template = render_template('ajax/new_ssh.html', groups=group_sql.select_groups(), sshs=cred_sql.select_ssh(name=name), lang=lang)
return {'id': last_id, 'template': rendered_template}
def create_ssh_cread_api(name: str, enable: str, group: str, username: str, password: str) -> bool:
def create_ssh_cred_api(name: str, enable: str, group: str, username: str, password: str) -> bool:
group_name = group_sql.get_group_name_by_id(group)
name = common.checkAjaxInput(name)
name = f'{name}_{group_name}'
@ -117,7 +116,7 @@ def create_ssh_cread_api(name: str, enable: str, group: str, username: str, pass
roxywi_common.handle_exceptions(e, 'Roxy-WI server', f'Cannot create SSH credentials {name}', roxywi=1)
def upload_ssh_key(name: str, user_group: str, key: str, passphrase: str) -> str:
def upload_ssh_key(name: str, user_group: int, key: str, passphrase: str) -> str:
if '..' in name:
raise Exception('error: nice try')
@ -172,13 +171,13 @@ def upload_ssh_key(name: str, user_group: str, key: str, passphrase: str) -> str
return f'success: SSH key has been saved into: {ssh_keys}'
def update_ssh_key() -> str:
ssh_id = common.checkAjaxInput(request.form.get('id'))
name = common.checkAjaxInput(request.form.get('name'))
enable = common.checkAjaxInput(request.form.get('ssh_enable'))
group = common.checkAjaxInput(request.form.get('group'))
username = common.checkAjaxInput(request.form.get('ssh_user'))
password = common.checkAjaxInput(request.form.get('ssh_pass'))
def update_ssh_key(json_data: dict):
ssh_id = int(json_data['id'])
name = common.checkAjaxInput(json_data['name'])
enable = common.checkAjaxInput(json_data['ssh_enable'])
group = int(json_data['group'])
username = common.checkAjaxInput(json_data['ssh_user'])
password = common.checkAjaxInput(json_data['ssh_pass'])
new_ssh_key_name = ''
ssh_key_name = ''
ssh_enable = 0
@ -206,8 +205,6 @@ def update_ssh_key() -> str:
cred_sql.update_ssh(ssh_id, name, enable, group, username, password)
roxywi_common.logging('Roxy-WI server', f'The SSH credentials {name} has been updated ', roxywi=1, login=1)
return 'ok'
def delete_ssh_key(ssh_id) -> str:
lib_path = get_config.get_config_var('main', 'lib_path')

View File

@ -160,11 +160,11 @@ def service_history(service, server_ip):
history = ''
server_ip = common.checkAjaxInput(server_ip)
if service in ('haproxy', 'nginx', 'keepalived', 'apache', 'cluster'):
if service in ('haproxy', 'nginx', 'keepalived', 'apache', 'cluster', 'udp'):
service_desc = service_sql.select_service(service)
if not roxywi_auth.is_access_permit_to_service(service_desc.slug):
abort(403, f'You do not have needed permissions to access to {service_desc.slug.title()} service')
if service == 'cluster':
if service in ('cluster', 'udp'):
server_id = server_ip
else:
server_id = server_sql.select_server_id_by_ip(server_ip)

View File

@ -1,6 +1,6 @@
import json
from flask import render_template, request, g
from flask import render_template, request, g, jsonify, abort
from flask_login import login_required
from app.routes.server import bp
@ -187,67 +187,80 @@ def delete_server(server_id):
return server_mod.delete_server(server_id)
@bp.route('/group/create', methods=['POST'])
@bp.route('/group', methods=['POST', 'PUT', 'DELETE'])
def create_group():
roxywi_auth.page_for_admin()
newgroup = common.checkAjaxInput(request.form.get('groupname'))
desc = common.checkAjaxInput(request.form.get('newdesc'))
if newgroup == '':
return error_mess
else:
json_data = request.get_json()
if request.method == 'POST':
name = json_data.get('name')
desc = json_data.get('desc')
if name == '':
return error_mess
try:
if group_sql.add_group(newgroup, desc):
roxywi_common.logging('Roxy-WI server', f'A new group {newgroup} has been created', roxywi=1, login=1)
return render_template('ajax/new_group.html', groups=group_sql.select_groups(group=newgroup))
last_id = group_sql.add_group(name, desc)
roxywi_common.logging('Roxy-WI server', f'A new group {name} has been created', roxywi=1, login=1)
return jsonify({
'status': 'created',
'id': last_id,
'data': render_template('ajax/new_group.html', groups=group_sql.select_groups(group=name))}
)
except Exception as e:
return str(e)
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server', 'Cannot create a new group')
elif request.method == 'PUT':
name = json_data.get('name')
desc = json_data.get('desc')
group_id = json_data.get('group_id')
try:
group_mod.update_group(group_id, name, desc)
return jsonify({'status': 'updated'})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server', f'Cannot update group {name}')
elif request.method == 'DELETE':
group_id = json_data.get('group_id')
try:
group_mod.delete_group(group_id)
return jsonify({'status': 'deleted'})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server', f'Cannot delete {group_id}')
else:
abort(405)
@bp.route('/group/update', methods=['POST'])
def update_group():
roxywi_auth.page_for_admin()
name = common.checkAjaxInput(request.form.get('updategroup'))
desc = common.checkAjaxInput(request.form.get('descript'))
group_id = common.checkAjaxInput(request.form.get('id'))
return group_mod.update_group(group_id, name, desc)
@bp.route('/group/delete/<int:group_id>')
def delete_group(group_id):
roxywi_auth.page_for_admin()
return group_mod.delete_group(group_id)
@bp.route('/ssh/create', methods=['POST'])
@bp.route('/ssh', methods=['POST', 'PUT', 'DELETE', 'PATCH'])
@get_user_params()
def create_ssh():
roxywi_auth.page_for_admin(level=2)
return ssh_mod.create_ssh_cred()
json_data = request.get_json()
if request.method == 'POST':
try:
data = ssh_mod.create_ssh_cred(json_data)
return jsonify({'status': 'created', 'id': data['id'], 'data': data['template']})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server', f'Cannot create SSH')
elif request.method == 'PUT':
try:
ssh_mod.update_ssh_key(json_data)
return jsonify({'status': 'updated'})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server', f'Cannot update SSH')
elif request.method == 'DELETE':
ssh_id = int(json_data.get('id'))
try:
ssh_mod.delete_ssh_key(ssh_id)
return jsonify({'status': 'deleted'})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server', f'Cannot delete SSH')
elif request.method == 'PATCH':
user_group = roxywi_common.get_user_group()
name = common.checkAjaxInput(json_data['name'])
passphrase = common.checkAjaxInput(json_data['pass'])
key = json_data['ssh_cert']
@bp.route('/ssh/delete/<int:ssh_id>')
def delete_ssh(ssh_id):
roxywi_auth.page_for_admin(level=2)
return ssh_mod.delete_ssh_key(ssh_id)
@bp.route('/ssh/update', methods=['POST'])
def update_ssh():
roxywi_auth.page_for_admin(level=2)
return ssh_mod.update_ssh_key()
@bp.route('/ssh/upload', methods=['POST'])
def upload_ssh_key():
user_group = roxywi_common.get_user_group()
name = common.checkAjaxInput(request.form.get('name'))
passphrase = common.checkAjaxInput(request.form.get('pass'))
key = request.form.get('ssh_cert')
try:
return ssh_mod.upload_ssh_key(name, user_group, key, passphrase)
except Exception as e:
return str(e)
try:
saved_path = ssh_mod.upload_ssh_key(name, user_group, key, passphrase)
return jsonify({'status': 'uploaded', 'message': saved_path})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server', f'Cannot upload ssh')
@bp.app_template_filter('string_to_dict')

View File

@ -38,16 +38,21 @@ def listener_funct(service):
elif request.method == 'POST':
json_data = request.get_json()
json_data['group_id'] = g.user_params['group_id']
listener_name = json_data['new-listener-name']
try:
listener_id = udp_mod.create_listener(json_data)
roxywi_common.logging(listener_id, f'UDP listener {listener_name} has been created', roxywi=1, keep_history=1, login=1, service='UDP listener')
return jsonify({'status': 'created', 'listener_id': listener_id})
except Exception as e:
return jsonify({'status': 'failed', 'error': str(e)})
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server','Cannot create UDP listener')
elif request.method == 'PUT':
json_data = request.get_json()
json_data['group_id'] = g.user_params['group_id']
listener_name = json_data['new-listener-name']
listener_id = json_data['listener_id']
try:
udp_mod.update_listener(json_data)
roxywi_common.logging(listener_id, f'UDP listener {listener_name} has been updated', roxywi=1, keep_history=1, login=1, service='UDP listener')
return jsonify({'status': 'updated'}), 201
except Exception as e:
return jsonify({'status': 'failed', 'error': str(e)})
@ -57,13 +62,14 @@ def listener_funct(service):
try:
inv, server_ips = service_mod.generate_udp_inv(listener_id, 'uninstall')
service_mod.run_ansible(inv, server_ips, f'udp'), 201
roxywi_common.logging(listener_id, f'UDP listener has been deleted {listener_id}', roxywi=1, keep_history=1, login=1, service='UDP listener')
except Exception as e:
return jsonify({'status': 'failed', 'error': str(e)})
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server',f'Cannot create inventory for UDP listener deleting {listener_id}')
try:
udp_sql.delete_listener(listener_id)
return jsonify({'status': 'deleted'}), 201
except Exception as e:
return jsonify({'status': 'failed', 'error': str(e)})
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server',f'Cannot delete UDP listener {listener_id}')
@bp.get('/<service>/listener/<int:listener_id>')
@ -100,6 +106,7 @@ def get_listener_settings(service, listener_id):
def action_with_listener(service, listener_id, action):
try:
udp_mod.listener_actions(listener_id, action, g.user_params['group_id'])
roxywi_common.logging(listener_id, f'UDP listener {listener_id} has been {action}ed', roxywi=1, keep_history=1, login=1, service='UDP listener')
return jsonify({'status': 'done'})
except Exception as e:
return jsonify({'status': 'failed', 'error': str(e)})
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server',f'Cannot {action} listener')

View File

@ -1,6 +1,6 @@
import json
from flask import render_template, request
from flask import render_template, request, jsonify, g, abort
from flask_login import login_required
from app.routes.user import bp
@ -21,70 +21,56 @@ def before_request():
pass
@bp.post('/create')
@bp.route('', methods=['POST', 'PUT', 'DELETE'])
@get_user_params()
def create_user():
roxywi_auth.page_for_admin(level=2)
email = common.checkAjaxInput(request.form.get('newemail'))
password = common.checkAjaxInput(request.form.get('newpassword'))
role = common.checkAjaxInput(request.form.get('newrole'))
new_user = common.checkAjaxInput(request.form.get('newusername'))
page = common.checkAjaxInput(request.form.get('page'))
activeuser = common.checkAjaxInput(request.form.get('activeuser'))
group = common.checkAjaxInput(request.form.get('newgroupuser'))
lang = roxywi_common.get_user_lang_for_flask()
json_data = request.get_json()
if not roxywi_common.check_user_group_for_flask():
return 'error: Wrong group'
if page == 'servers':
if roxywi_auth.is_admin(level=2, role_id=role):
roxywi_common.logging(new_user, ' tried to privilege escalation: user creation', roxywi=1, login=1)
return 'error: Wrong role'
try:
roxywi_user.create_user(new_user, email, password, role, activeuser, group)
except Exception as e:
return str(e)
else:
return render_template(
'ajax/new_user.html', users=user_sql.select_users(user=new_user), groups=group_sql.select_groups(), page=page,
roles=sql.select_roles(), adding=1, lang=lang
)
@bp.post('/update')
def update_user():
roxywi_auth.page_for_admin(level=2)
email = request.form.get('email')
new_user = request.form.get('updateuser')
user_id = request.form.get('id')
enabled = request.form.get('activeuser')
group_id = int(request.form.get('usergroup'))
if roxywi_common.check_user_group_for_flask():
if request.form.get('role'):
role_id = int(request.form.get('role'))
if roxywi_auth.is_admin(level=role_id):
roxywi_user.update_user(email, new_user, user_id, enabled, group_id, role_id)
else:
roxywi_common.logging(new_user, ' tried to privilege escalation', roxywi=1, login=1)
return 'error: dalsd'
if request.method == 'POST':
email = common.checkAjaxInput(json_data['email'])
password = common.checkAjaxInput(json_data['password'])
role = int(json_data['role'])
new_user = common.checkAjaxInput(json_data['username'])
enabled = int(json_data['enabled'])
group_id = int(json_data['user_group'])
lang = roxywi_common.get_user_lang_for_flask()
current_user_role_id = g.user_params['role']
if not roxywi_common.check_user_group_for_flask():
return roxywi_common.handle_json_exceptions('Wrong group', 'Roxy-WI server', '')
if current_user_role_id < role:
return roxywi_common.handle_json_exceptions('Wrong role', 'Roxy-WI server', '')
try:
user_id = roxywi_user.create_user(new_user, email, password, role, enabled, group_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server', 'Cannot create a new user')
else:
return jsonify({'status': 'created', 'id': user_id, 'data': render_template(
'ajax/new_user.html', users=user_sql.select_users(user=new_user), groups=group_sql.select_groups(),
roles=sql.select_roles(), adding=1, lang=lang
)})
elif request.method == 'PUT':
user_id = int(json_data['user_id'])
user_name = common.checkAjaxInput(json_data['username'])
email = common.checkAjaxInput(json_data['email'])
enabled = int(json_data['enabled'])
if roxywi_common.check_user_group_for_flask():
try:
user_sql.update_user_from_admin_area(new_user, email, user_id, enabled)
user_sql.update_user_from_admin_area(user_name, email, user_id, enabled)
except Exception as e:
return f'error: Cannot update user: {e}'
roxywi_common.logging(new_user, ' has been updated user ', roxywi=1, login=1)
return 'ok'
@bp.route('/delete/<int:user_id>')
def delete_user(user_id):
roxywi_auth.page_for_admin(level=2)
try:
return roxywi_user.delete_user(user_id)
except Exception as e:
return f'error: {e}'
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server', 'Cannot update user')
roxywi_common.logging(user_name, ' has been updated user ', roxywi=1, login=1)
return jsonify({'status': 'updated'})
elif request.method == 'DELETE':
roxywi_auth.page_for_admin(level=2)
user_id = int(json_data['user_id'])
try:
roxywi_user.delete_user(user_id)
return jsonify({'status': 'deleted'})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server', f'Cannot delete the user {user_id}')
else:
abort(405)
@bp.route('/ldap/<username>')
@ -108,10 +94,14 @@ def show_user_services(user_id):
if request.method == 'GET':
return roxywi_user.get_user_services(user_id)
else:
user = common.checkAjaxInput(request.form.get('changeUserServicesUser'))
user_services = json.loads(request.form.get('jsonDatas'))
return roxywi_user.change_user_services(user, user_id, user_services)
json_data = request.get_json()
user = json_data['username']
user_services = json_data['services']
try:
roxywi_user.change_user_services(user, user_id, user_services)
return jsonify({'status': 'updated'})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Roxy-WI server', 'Cannot change user services')
@bp.route('/group', methods=['GET', 'PUT'])

View File

@ -1,6 +1,6 @@
import os
from flask import render_template, request, g, abort
from flask import render_template, request, g, abort, jsonify
from flask_login import login_required
from app.routes.waf import bp
@ -87,6 +87,8 @@ def waf_rules(service, server_ip):
@bp.route('/<service>/<server_ip>/rule/<int:rule_id>')
@get_user_params()
def waf_rule_edit(service, server_ip, rule_id):
if service not in ('haproxy', 'nginx'):
abort(404)
roxywi_auth.page_for_admin(level=2)
if not roxywi_auth.is_access_permit_to_service(service):
abort(403, f'You do not have needed permissions to access to {service.title()} service')
@ -100,17 +102,19 @@ def waf_rule_edit(service, server_ip, rule_id):
get_date = roxy_wi_tools.GetDate(sql.get_setting('time_zone'))
waf_rule_file = waf_sql.select_waf_rule_by_id(rule_id)
configs_dir = sql.get_setting('tmp_config_path')
cfg = f"{configs_dir}{server_ip}-{get_date.return_date('config')}-{waf_rule_file}"
error = config_mod.get_config(server_ip, cfg, waf=service, waf_rule_file=waf_rule_file)
try:
cfg = f"{configs_dir}{server_ip}-{get_date.return_date('config')}-{waf_rule_file}"
config_mod.get_config(server_ip, cfg, waf=service, waf_rule_file=waf_rule_file)
except Exception as e:
pass
config_file_name = common.return_nice_path(config_path) + 'waf/rules/' + waf_rule_file
config_read = ''
try:
conf = open(cfg, "r")
config_read = conf.read()
conf.close()
except IOError:
print('Cannot read imported config file')
return f'error: Cannot read imported config file: {e}'
kwargs = {
'title': 'Edit a WAF rule',
@ -147,8 +151,8 @@ def waf_save_config(service, server_ip, rule_id):
try:
with open(cfg, "a") as conf:
conf.write(config)
except IOError:
print("error: Cannot read imported config file")
except IOError as e:
return f"error: Cannot read imported config file: {e}"
stderr = config_mod.master_slave_upload_and_restart(server_ip, cfg, save, 'waf', oldcfg=oldcfg, config_file_name=config_file_name)
@ -169,32 +173,46 @@ def waf_save_config(service, server_ip, rule_id):
def enable_rule(server_ip, rule_id, enable):
server_ip = common.is_ip_or_dns(server_ip)
return roxy_waf.switch_waf_rule(server_ip, enable, rule_id)
try:
roxy_waf.switch_waf_rule(server_ip, enable, rule_id)
return jsonify({'status': 'updated'})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, server_ip, f'Cannot enable WAF rule {rule_id} on server {server_ip}')
@bp.route('/<service>/<server_ip>/rule/create', methods=['POST'])
def create_rule(service, server_ip):
if service not in ('haproxy', 'nginx'):
return 'error: Wrong service'
server_ip = common.is_ip_or_dns(server_ip)
json_data = request.get_json()
if service not in ('haproxy', 'nginx'):
return roxywi_common.handle_json_exceptions('Wrong service', server_ip, '')
return roxy_waf.create_waf_rule(server_ip, service)
try:
last_id = roxy_waf.create_waf_rule(server_ip, service, json_data)
return jsonify({'status': 'created', 'id': last_id})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, server_ip, 'Cannot create WAF rule')
@bp.route('/<service>/mode/<server_name>/<waf_mode>')
def change_waf_mode(service, server_name, waf_mode):
if service not in ('haproxy', 'nginx'):
return 'error: Wrong service'
return roxywi_common.handle_json_exceptions('Wrong service', server_name, '')
server_name = common.checkAjaxInput(server_name)
waf_mode = common.checkAjaxInput(waf_mode)
return roxy_waf.change_waf_mode(waf_mode, server_name, service)
try:
roxy_waf.change_waf_mode(waf_mode, server_name, service)
return jsonify({'status': 'updated'})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, server_name, 'Cannot change WAF mode')
@bp.route('/overview/<service>/<server_ip>')
def overview_waf(service, server_ip):
if service not in ('haproxy', 'nginx'):
abort(404)
server_ip = common.is_ip_or_dns(server_ip)
if service not in ('haproxy', 'nginx'):
@ -206,4 +224,8 @@ def overview_waf(service, server_ip):
@bp.route('/metric/enable/<int:enable>/<server_name>')
def enable_metric(enable, server_name):
server_name = common.checkAjaxInput(server_name)
return waf_sql.update_waf_metrics_enable(server_name, enable)
try:
waf_sql.update_waf_metrics_enable(server_name, enable)
return jsonify({'status': 'updated'})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, server_name, 'Cannot enable WAF metrics')

View File

@ -0,0 +1,144 @@
$( function() {
$('#add-group-button').click(function () {
addGroupDialog.dialog('open');
});
let group_tabel_title = $("#group-add-table-overview").attr('title');
let addGroupDialog = $("#group-add-table").dialog({
autoOpen: false,
resizable: false,
height: "auto",
width: 600,
modal: true,
title: group_tabel_title,
show: {
effect: "fade",
duration: 200
},
hide: {
effect: "fade",
duration: 200
},
buttons: {
"Add": function () {
addGroup(this);
},
Cancel: function () {
$(this).dialog("close");
clearTips();
}
}
});
$("#ajax-group input").change(function () {
let id = $(this).attr('id').split('-');
updateGroup(id[1])
});
});
function addGroup(dialog_id) {
toastr.clear();
let valid = true;
let allFields = $([]).add($('#new-group-add'));
allFields.removeClass("ui-state-error");
valid = valid && checkLength($('#new-group-add'), "new group name", 1);
if (valid) {
let jsonData = {
'name': $('#new-group-add').val(),
'desc': $('#new-desc').val()
}
$.ajax({
url: "/app/server/group",
type: 'POST',
data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data.status === 'failed') {
toastr.error(data);
} else {
let id = data.id;
$('select:regex(id, group)').append('<option value=' + id + '>' + $('#new-group-add').val() + '</option>').selectmenu("refresh");
common_ajax_action_after_success(dialog_id, 'newgroup', 'ajax-group', data.data);
}
}
});
}
}
function updateGroup(id) {
toastr.clear();
let jsonData = {
"name": $('#name-' + id).val(),
"desc": $('#descript-' + id).val(),
"group_id": id
}
$.ajax({
url: "/app/server/group",
type: "PUT",
data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data.status === 'failed') {
toastr.error(data);
} else {
toastr.clear();
$("#group-" + id).addClass("update", 1000);
setTimeout(function () {
$("#group-" + id).removeClass("update");
}, 2500);
$('select:regex(id, group) option[value=' + id + ']').remove();
$('select:regex(id, group)').append('<option value=' + id + '>' + $('#name-' + id).val() + '</option>').selectmenu("refresh");
}
}
});
}
function confirmDeleteGroup(id) {
$( "#dialog-confirm" ).dialog({
resizable: false,
height: "auto",
width: 400,
modal: true,
title: delete_word+ " " +$('#name-'+id).val() + "?",
buttons: [{
text: delete_word,
click: function() {
$(this).dialog("close");
removeGroup(id);
}
}, {
text: cancel_word,
click: function () {
$(this).dialog("close");
}
}]
});
}
function removeGroup(id) {
$("#group-" + id).css("background-color", "#f2dede");
$.ajax({
url: "/app/server/group",
type: 'DELETE',
data: JSON.stringify({'group_id': id}),
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data.status === 'failed') {
toastr.error(data.error);
} else {
$("#group-" + id).remove();
$('select:regex(id, group) option[value=' + id + ']').remove();
$('select:regex(id, group)').selectmenu("refresh");
}
}
});
}
function getGroupNameById(group_id) {
let group_name = ''
$.ajax({
url: "/app/user/group/name/" + group_id,
async: false,
success: function (data) {
if (data.indexOf('error:') != '-1') {
toastr.error(data);
} else {
group_name = data;
}
}
});
return group_name;
}

238
app/static/js/admin/ssh.js Normal file
View File

@ -0,0 +1,238 @@
$( function() {
$('#add-ssh-button').click(function () {
addCredsDialog.dialog('open');
});
let ssh_tabel_title = $("#ssh-add-table-overview").attr('title');
let addCredsDialog = $("#ssh-add-table").dialog({
autoOpen: false,
resizable: false,
height: "auto",
width: 600,
modal: true,
title: ssh_tabel_title,
show: {
effect: "fade",
duration: 200
},
hide: {
effect: "fade",
duration: 200
},
buttons: [{
text: add_word,
click: function () {
addCreds(this);
}
}, {
text: cancel_word,
click: function () {
$(this).dialog("close");
clearTips();
}
}]
});
$( "#ssh_enable_table input" ).change(function() {
let id = $(this).attr('id').split('-');
updateSSH(id[1])
sshKeyEnableShow(id[1])
});
$( "#ssh_enable_table select" ).on('selectmenuchange',function() {
let id = $(this).attr('id').split('-');
updateSSH(id[1])
sshKeyEnableShow(id[1])
});
$('#new-ssh_enable').click(function() {
if ($('#new-ssh_enable').is(':checked')) {
$('#ssh_pass').css('display', 'none');
} else {
$('#ssh_pass').css('display', 'block');
}
});
if ($('#new-ssh_enable').is(':checked')) {
$('#ssh_pass').css('display', 'none');
} else {
$('#ssh_pass').css('display', 'block');
}
});
function addCreds(dialog_id) {
toastr.clear();
let ssh_add_div = $('#new-ssh-add');
let ssh_enable = 0;
if ($('#new-ssh_enable').is(':checked')) {
ssh_enable = '1';
}
let valid = true;
let allFields = $([]).add(ssh_add_div).add($('#ssh_user'))
allFields.removeClass("ui-state-error");
valid = valid && checkLength(ssh_add_div, "Name", 1);
valid = valid && checkLength($('#ssh_user'), "Credentials", 1);
if (valid) {
let jsonData = {
"ssh": ssh_add_div.val(),
"group": $('#new-sshgroup').val(),
"user": $('#ssh_user').val(),
"pass": $('#ssh_pass').val(),
"enabled": ssh_enable,
}
$.ajax({
url: "/app/server/ssh",
type: "POST",
data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data.status === 'failed') {
toastr.error(data.error);
} else {
let group_name = getGroupNameById($('#new-sshgroup').val());
let id = data.id;
common_ajax_action_after_success(dialog_id, 'ssh-table-' + id, 'ssh_enable_table', data.data);
$('select:regex(id, credentials)').append('<option value=' + id + '>' + ssh_add_div.val() + '</option>').selectmenu("refresh");
$('select:regex(id, ssh-key-name)').append('<option value=' + ssh_add_div.val() + '_' + group_name + '>' + ssh_add_div.val() + '_' + group_name + '</option>').selectmenu("refresh");
$("input[type=submit], button").button();
$("input[type=checkbox]").checkboxradio();
$("select").selectmenu();
}
}
});
}
}
function sshKeyEnableShow(id) {
$('#ssh_enable-' + id).click(function () {
if ($('#ssh_enable-' + id).is(':checked')) {
$('#ssh_pass-' + id).css('display', 'none');
} else {
$('#ssh_pass-' + id).css('display', 'block');
}
});
if ($('#ssh_enable-' + id).is(':checked')) {
$('#ssh_pass-' + id).css('display', 'none');
} else {
$('#ssh_pass-' + id).css('display', 'block');
}
}
function updateSSH(id) {
toastr.clear();
let ssh_enable = 0;
let ssh_name_val = $('#ssh_name-' + id).val();
if ($('#ssh_enable-' + id).is(':checked')) {
ssh_enable = '1';
}
let group = $('#sshgroup-' + id).val();
console.log('group' + group)
if (group === undefined || group === null) {
group = $('#new-sshgroup').val();
}
let jsonData = {
"name": ssh_name_val,
"group": group,
"ssh_enable": ssh_enable,
"ssh_user": $('#ssh_user-' + id).val(),
"ssh_pass": $('#ssh_pass-' + id).val(),
"id": id
}
$.ajax({
url: "/app/server/ssh",
data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8",
type: "PUT",
success: function (data) {
if (data.status === 'failed') {
toastr.error(data.error);
} else {
toastr.clear();
$("#ssh-table-" + id).addClass("update", 1000);
setTimeout(function () {
$("#ssh-table-" + id).removeClass("update");
}, 2500);
$('select:regex(id, credentials) option[value=' + id + ']').remove();
$('select:regex(id, ssh-key-name) option[value=' + ssh_name_val + ']').remove();
$('select:regex(id, credentials)').append('<option value=' + id + '>' + ssh_name_val + '</option>').selectmenu("refresh");
$('select:regex(id, ssh-key-name)').append('<option value=' + ssh_name_val + '>' + ssh_name_val + '</option>').selectmenu("refresh");
}
}
});
}
function confirmDeleteSsh(id) {
$( "#dialog-confirm" ).dialog({
resizable: false,
height: "auto",
width: 400,
modal: true,
title: delete_word +" " + $('#ssh_name-' + id).val() + "?",
buttons: [{
text: delete_word,
click: function () {
$(this).dialog("close");
removeSsh(id);
}
},{
text: cancel_word,
click: function () {
$(this).dialog("close");
}
}]
});
}
function removeSsh(id) {
$("#ssh-table-" + id).css("background-color", "#f2dede");
$.ajax({
url: "/app/server/ssh",
type: "DELETE",
data: JSON.stringify({"id": id}),
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data.status === 'failed') {
toastr.error(data.error);
} else {
$("#ssh-table-" + id).remove();
$('select:regex(id, credentials) option[value=' + id + ']').remove();
$('select:regex(id, credentials)').selectmenu("refresh");
}
}
});
}
function uploadSsh() {
toastr.clear();
if ($("#ssh-key-name option:selected").val() == "------" || $('#ssh_cert').val() == '') {
toastr.error('All fields must be completed');
} else {
let jsonData = {
"ssh_cert": $('#ssh_cert').val(),
"name": $('#ssh-key-name').val(),
"pass": $('#ssh-key-pass').val()
}
$.ajax({
url: "/app/server/ssh",
type: "PATCH",
data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data.status === 'failed') {
toastr.error(data.error);
} else if (data.status === 'uploaded') {
toastr.clear();
toastr.success(data.message)
} else {
toastr.error('Something wrong, check and try again');
}
}
});
}
}
function checkSshConnect(ip) {
$.ajax({
url: "/app/server/check/ssh/" + ip,
success: function (data) {
if (data.indexOf('error:') != '-1') {
toastr.error(data)
} else if (data.indexOf('failed') != '-1') {
toastr.error(data)
} else if (data.indexOf('Errno') != '-1') {
toastr.error(data)
} else {
toastr.clear();
toastr.success('Connect is accepted');
}
}
});
}

View File

@ -1,4 +1,4 @@
let cur_url = window.location.href.split('/app/').pop();
var cur_url = window.location.href.split('/app/').pop();
cur_url = cur_url.split('/');
function showHapservers(serv, hostnamea, service) {
let i;
@ -213,9 +213,9 @@ $( function() {
if (cur_url[0] != 'portscanner') {
try {
let service_name = id[2]
var service_name = id[2]
} catch (err) {
let service_name = 'haproxy'
var service_name = 'haproxy'
}
updateHapWIServer(id[1], service_name)
@ -282,8 +282,7 @@ function updateHapWIServer(id, service_name) {
name: $('#server-name-' + id).val(),
metrics: metrics,
alert_en: alert_en,
active: active,
token: $('#token').val()
active: active
},
type: "POST",
success: function (data) {

View File

@ -416,7 +416,6 @@ function clearUdpVip() {
}
function confirmUdpBalancerAction(action, listener_id) {
let action_word = translate_div.attr('data-'+action);
console.log('#litener-name-'+listener_id);
let l_name = $('#listener-name-'+listener_id).text();
$( "#dialog-confirm" ).dialog({
resizable: false,

View File

@ -1,35 +1,6 @@
var cur_url = window.location.href.split('/app/').pop();
cur_url = cur_url.split('/');
$( function() {
$('#add-group-button').click(function() {
addGroupDialog.dialog('open');
});
let group_tabel_title = $( "#group-add-table-overview" ).attr('title');
let addGroupDialog = $( "#group-add-table" ).dialog({
autoOpen: false,
resizable: false,
height: "auto",
width: 600,
modal: true,
title: group_tabel_title,
show: {
effect: "fade",
duration: 200
},
hide: {
effect: "fade",
duration: 200
},
buttons: {
"Add": function() {
addGroup(this);
},
Cancel: function() {
$( this ).dialog( "close" );
clearTips();
}
}
});
$('#add-user-button').click(function() {
addUserDialog.dialog('open');
});
@ -94,38 +65,6 @@ $( function() {
}
}]
});
$('#add-ssh-button').click(function() {
addCredsDialog.dialog('open');
});
let ssh_tabel_title = $( "#ssh-add-table-overview" ).attr('title');
let addCredsDialog = $( "#ssh-add-table" ).dialog({
autoOpen: false,
resizable: false,
height: "auto",
width: 600,
modal: true,
title: ssh_tabel_title,
show: {
effect: "fade",
duration: 200
},
hide: {
effect: "fade",
duration: 200
},
buttons: [{
text: add_word,
click: function () {
addCreds(this);
}
}, {
text: cancel_word,
click: function () {
$(this).dialog("close");
clearTips();
}
}]
});
$( "#ajax-users input" ).change(function() {
let id = $(this).attr('id').split('-');
updateUser(id[1])
@ -134,10 +73,6 @@ $( function() {
let id = $(this).attr('id').split('-');
updateUser(id[1])
});
$( "#ajax-group input" ).change(function() {
let id = $(this).attr('id').split('-');
updateGroup(id[1])
});
$( "#ajax-servers input" ).change(function() {
let id = $(this).attr('id').split('-');
updateServer(id[1])
@ -146,28 +81,6 @@ $( function() {
let id = $(this).attr('id').split('-');
updateServer(id[1])
});
$( "#ssh_enable_table input" ).change(function() {
let id = $(this).attr('id').split('-');
updateSSH(id[1])
sshKeyEnableShow(id[1])
});
$( "#ssh_enable_table select" ).on('selectmenuchange',function() {
let id = $(this).attr('id').split('-');
updateSSH(id[1])
sshKeyEnableShow(id[1])
});
$('#new-ssh_enable').click(function() {
if ($('#new-ssh_enable').is(':checked')) {
$('#ssh_pass').css('display', 'none');
} else {
$('#ssh_pass').css('display', 'block');
}
});
if ($('#new-ssh_enable').is(':checked')) {
$('#ssh_pass').css('display', 'none');
} else {
$('#ssh_pass').css('display', 'block');
}
$( "#scan_server" ).change(function() {
if ($('#scan_server').is(':checked')) {
$('.services_for_scan').hide();
@ -176,27 +89,28 @@ $( function() {
}
});
$('#search_ldap_user').click(function() {
let valid = true;
toastr.clear();
allFields = $([]).add($('#new-username'));
let username_div = $('#new-username')
let valid = true;
let allFields = $([]).add(username_div);
allFields.removeClass("ui-state-error");
valid = valid && checkLength($('#new-username'), "user name", 1);
user = $('#new-username').val()
valid = valid && checkLength(username_div, "user name", 1);
let user = username_div.val()
if (valid) {
$.ajax({
url: "/app/user/ldap/" + $('#new-username').val(),
url: "/app/user/ldap/" + user,
success: function (data) {
data = data.replace(/\s+/g, ' ');
if (data.indexOf('error:') != '-1') {
toastr.error(data);
$('#new-email').val('');
$('#new-password').attr('readonly', false);
$('#new-password').val('');
username_div.attr('readonly', false);
username_div.val('');
} else {
let json = $.parseJSON(data);
toastr.clear();
if (!$('#new-username').val().includes('@')) {
$('#new-username').val(user + '@' + json[1]);
if (!user.includes('@')) {
username_div.val(user + '@' + json[1]);
}
$('#new-email').val(json[0]);
$('#new-password').val('aduser');
@ -209,7 +123,6 @@ $( function() {
});
$("#tabs ul li").click(function() {
let activeTab = $(this).find("a").attr("href");
console.log(activeTab);
let activeTabClass = activeTab.replace('#', '');
$('.menu li ul li').each(function () {
$(this).find('a').css('border-left', '0px solid var(--right-menu-blue-rolor)');
@ -252,65 +165,35 @@ window.onload = function() {
function addUser(dialog_id) {
let valid = true;
toastr.clear();
allFields = $([]).add($('#new-username')).add($('#new-password')).add($('#new-email'))
let allFields = $([]).add($('#new-username')).add($('#new-password')).add($('#new-email'))
allFields.removeClass("ui-state-error");
valid = valid && checkLength($('#new-username'), "user name", 1);
valid = valid && checkLength($('#new-password'), "password", 1);
valid = valid && checkLength($('#new-email'), "Email", 1);
let activeuser = 0;
let enabled = 0;
if ($('#activeuser').is(':checked')) {
activeuser = '1';
enabled = '1';
}
if (valid) {
let jsonData = {
"username": $('#new-username').val(),
"password": $('#new-password').val(),
"email": $('#new-email').val(),
"role": $('#new-role').val(),
"enabled": enabled,
"user_group": $('#new-group').val(),
}
$.ajax({
url: "/app/user/create",
data: {
newusername: $('#new-username').val(),
newpassword: $('#new-password').val(),
newemail: $('#new-email').val(),
newrole: $('#new-role').val(),
activeuser: activeuser,
page: cur_url[0].split('#')[0],
newgroupuser: $('#new-group').val(),
token: $('#token').val()
},
url: "/app/user",
type: "POST",
data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8",
success: function (data) {
data = data.replace(/\s+/g, ' ');
if (data.indexOf('error:') != '-1') {
toastr.error(data);
if (data.status === 'failed') {
toastr.error(data.error);
} else {
let getId = new RegExp('[0-9]+');
let id = data.match(getId);
common_ajax_action_after_success(dialog_id, 'user-' + id, 'ajax-users', data);
}
}
});
}
}
function addGroup(dialog_id) {
toastr.clear();
let valid = true;
allFields = $([]).add($('#new-group-add'));
allFields.removeClass("ui-state-error");
valid = valid && checkLength($('#new-group-add'), "new group name", 1);
if (valid) {
$.ajax({
url: "/app/server/group/create",
data: {
groupname: $('#new-group-add').val(),
newdesc: $('#new-desc').val(),
token: $('#token').val()
},
type: "POST",
success: function (data) {
if (data.indexOf('error:') != '-1') {
toastr.error(data);
} else {
let getId = new RegExp('[0-9]+');
let id = data.match(getId);
$('select:regex(id, group)').append('<option value=' + id + '>' + $('#new-group-add').val() + '</option>').selectmenu("refresh");
common_ajax_action_after_success(dialog_id, 'newgroup', 'ajax-group', data);
let user_id = data.id;
common_ajax_action_after_success(dialog_id, 'user-' + user_id, 'ajax-users', data.data);
}
}
});
@ -386,8 +269,7 @@ function addServer(dialog_id) {
slave: $('#slavefor').val(),
cred: cred,
page: cur_url[0].split('#')[0],
desc: $('#desc').val(),
token: $('#token').val()
desc: $('#desc').val()
},
type: "POST",
success: function (data) {
@ -426,8 +308,7 @@ function after_server_creating(servername, newip, scan_server) {
act: 'after_adding',
servername: servername,
newip: newip,
scan_server: scan_server,
token: $('#token').val()
scan_server: scan_server
},
type: "POST",
success: function (data) {
@ -440,80 +321,6 @@ function after_server_creating(servername, newip, scan_server) {
}
});
}
function addCreds(dialog_id) {
toastr.clear();
let ssh_enable = 0;
if ($('#new-ssh_enable').is(':checked')) {
ssh_enable = '1';
}
let valid = true;
allFields = $([]).add($('#new-ssh-add')).add($('#ssh_user'))
allFields.removeClass("ui-state-error");
valid = valid && checkLength($('#new-ssh-add'), "Name", 1);
valid = valid && checkLength($('#ssh_user'), "Credentials", 1);
if (valid) {
$.ajax({
url: "/app/server/ssh/create",
data: {
new_ssh: $('#new-ssh-add').val(),
new_group: $('#new-sshgroup').val(),
ssh_user: $('#ssh_user').val(),
ssh_pass: $('#ssh_pass').val(),
ssh_enable: ssh_enable,
page: cur_url[0].split('#')[0],
token: $('#token').val()
},
type: "POST",
success: function (data) {
if (data.indexOf('error:') != '-1') {
toastr.error(data);
} else {
let group_name = getGroupNameById($('#new-sshgroup').val());
let getId = new RegExp('ssh-table-[0-9]+');
let id = data.match(getId) + '';
id = id.split('-').pop();
common_ajax_action_after_success(dialog_id, 'ssh-table-' + id, 'ssh_enable_table', data);
$('select:regex(id, credentials)').append('<option value=' + id + '>' + $('#new-ssh-add').val() + '</option>').selectmenu("refresh");
$('select:regex(id, ssh-key-name)').append('<option value=' + $('#new-ssh-add').val() + '_' + group_name + '>' + $('#new-ssh-add').val() + '_' + group_name + '</option>').selectmenu("refresh");
$("input[type=submit], button").button();
$("input[type=checkbox]").checkboxradio();
$("select").selectmenu();
}
}
});
}
}
function getGroupNameById(group_id) {
let group_name = ''
$.ajax({
url: "/app/user/group/name/" + group_id,
async: false,
success: function (data) {
if (data.indexOf('error:') != '-1') {
toastr.error(data);
} else {
group_name = data;
}
}
});
return group_name;
}
function sshKeyEnableShow(id) {
$('#ssh_enable-'+id).click(function() {
if ($('#ssh_enable-'+id).is(':checked')) {
$('#ssh_pass-'+id).css('display', 'none');
} else {
$('#ssh_pass-'+id).css('display', 'block');
}
});
if ($('#ssh_enable-'+id).is(':checked')) {
$('#ssh_pass-'+id).css('display', 'none');
} else {
$('#ssh_pass-'+id).css('display', 'block');
}
}
function confirmDeleteUser(id) {
$( "#dialog-confirm" ).dialog({
resizable: false,
@ -535,27 +342,7 @@ function confirmDeleteUser(id) {
}]
});
}
function confirmDeleteGroup(id) {
$( "#dialog-confirm" ).dialog({
resizable: false,
height: "auto",
width: 400,
modal: true,
title: delete_word+ " " +$('#name-'+id).val() + "?",
buttons: [{
text: delete_word,
click: function() {
$(this).dialog("close");
removeGroup(id);
}
}, {
text: cancel_word,
click: function () {
$(this).dialog("close");
}
}]
});
}
function confirmDeleteServer(id) {
$( "#dialog-confirm" ).dialog({
resizable: false,
@ -577,27 +364,6 @@ function confirmDeleteServer(id) {
}]
});
}
function confirmDeleteSsh(id) {
$( "#dialog-confirm" ).dialog({
resizable: false,
height: "auto",
width: 400,
modal: true,
title: delete_word +" " + $('#ssh_name-' + id).val() + "?",
buttons: [{
text: delete_word,
click: function () {
$(this).dialog("close");
removeSsh(id);
}
},{
text: cancel_word,
click: function () {
$(this).dialog("close");
}
}]
});
}
function cloneServer(id) {
$( "#add-server-button" ).trigger( "click" );
if ($('#enable-'+id).is(':checked')) {
@ -640,14 +406,15 @@ function cloneServer(id) {
function removeUser(id) {
$("#user-" + id).css("background-color", "#f2dede");
$.ajax({
url: "/app/user/delete/" + id,
url: "/app/user",
data: JSON.stringify({'user_id': id}),
contentType: "application/json; charset=utf-8",
type: "DELETE",
success: function (data) {
data = data.replace(/\s+/g, ' ');
if (data == "ok") {
if (data.status === 'failed') {
toastr.error(data.error);
} else {
$("#user-" + id).remove();
} else if (data.indexOf('error:') != '-1' || data.indexOf('unique') != '-1') {
toastr.error(data);
}
}
});
@ -670,70 +437,27 @@ function removeServer(id) {
}
});
}
function removeGroup(id) {
$("#group-" + id).css("background-color", "#f2dede");
$.ajax({
url: "/app/server/group/delete/" + id,
success: function (data) {
data = data.replace(/\s+/g, ' ');
if (data == "ok") {
$("#group-" + id).remove();
$('select:regex(id, group) option[value=' + id + ']').remove();
$('select:regex(id, group)').selectmenu("refresh");
} else if (data.indexOf('error:') != '-1' || data.indexOf('unique') != '-1') {
toastr.error(data);
}
}
});
}
function removeSsh(id) {
$("#ssh-table-"+id).css("background-color", "#f2dede");
$.ajax( {
url: "/app/server/ssh/delete/" + id,
success: function( data ) {
data = data.replace(/\s+/g,' ');
if(data == "ok") {
$("#ssh-table-"+id).remove();
$('select:regex(id, credentials) option[value='+id+']').remove();
$('select:regex(id, credentials)').selectmenu("refresh");
} else if (data.indexOf('error:') != '-1' || data.indexOf('unique') != '-1') {
toastr.error(data);
}
}
} );
}
function updateUser(id) {
toastr.remove();
cur_url[0] = cur_url[0].split('#')[0]
let usergroup = Cookies.get('group');
let role = $('#role-' + id).val();
let activeuser = 0;
let enabled = 0;
if ($('#activeuser-' + id).is(':checked')) {
activeuser = '1';
}
if (role == null && role !== undefined) {
toastr.warning('You cannot edit superAdmin user');
return false;
enabled = '1';
}
toastr.remove();
let jsonData = {
"username": $('#login-' + id).val(),
"email": $('#email-' + id).val(),
"enabled": enabled,
"user_id": id
}
$.ajax({
url: "/app/user/update",
data: {
updateuser: $('#login-' + id).val(),
email: $('#email-' + id).val(),
role: role,
usergroup: usergroup,
activeuser: activeuser,
id: id,
token: $('#token').val()
},
type: "POST",
url: "/app/user",
type: "PUT",
data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8",
success: function (data) {
data = data.replace(/\s+/g, ' ');
if (data.indexOf('error:') != '-1' || data.indexOf('unique') != '-1') {
toastr.error(data);
if (data.status === 'failed') {
toastr.error(data.error);
} else {
toastr.remove();
$("#user-" + id).addClass("update", 1000);
@ -744,33 +468,6 @@ function updateUser(id) {
}
});
}
function updateGroup(id) {
toastr.clear();
$.ajax({
url: "/app/server/group/update",
data: {
updategroup: $('#name-' + id).val(),
descript: $('#descript-' + id).val(),
id: id,
token: $('#token').val()
},
type: "POST",
success: function (data) {
data = data.replace(/\s+/g, ' ');
if (data.indexOf('error:') != '-1' || data.indexOf('unique') != '-1') {
toastr.error(data);
} else {
toastr.clear();
$("#group-" + id).addClass("update", 1000);
setTimeout(function () {
$("#group-" + id).removeClass("update");
}, 2500);
$('select:regex(id, group) option[value=' + id + ']').remove();
$('select:regex(id, group)').append('<option value=' + id + '>' + $('#name-' + id).val() + '</option>').selectmenu("refresh");
}
}
});
}
function updateServer(id) {
toastr.clear();
let typeip = 0;
@ -806,8 +503,7 @@ function updateServer(id) {
cred: $('#credentials-' + id + ' option:selected').val(),
id: id,
desc: $('#desc-' + id).val(),
protected: protected_serv,
token: $('#token').val()
protected: protected_serv
},
type: "POST",
success: function (data) {
@ -824,74 +520,6 @@ function updateServer(id) {
}
});
}
function uploadSsh() {
toastr.clear();
if ($("#ssh-key-name option:selected").val() == "------" || $('#ssh_cert').val() == '') {
toastr.error('All fields must be completed');
} else {
$.ajax({
url: "/app/server/ssh/upload",
data: {
ssh_cert: $('#ssh_cert').val(),
name: $('#ssh-key-name').val(),
pass: $('#ssh-key-pass').val(),
token: $('#token').val()
},
type: "POST",
success: function (data) {
data = data.replace(/\s+/g, ' ');
if (data.indexOf('danger') != '-1' || data.indexOf('unique') != '-1' || data.indexOf('error:') != '-1') {
toastr.error(data);
} else if (data.indexOf('success') != '-1') {
toastr.clear();
toastr.success(data)
} else {
toastr.error('Something wrong, check and try again');
}
}
});
}
}
function updateSSH(id) {
toastr.clear();
let ssh_enable = 0;
if ($('#ssh_enable-' + id).is(':checked')) {
ssh_enable = '1';
}
let group = $('#sshgroup-' + id).val();
if (cur_url[0].indexOf('servers') != '-1') {
group = $('#new-server-group-add').val();
}
$.ajax({
url: "/app/server/ssh/update",
data: {
name: $('#ssh_name-' + id).val(),
group: group,
ssh_enable: ssh_enable,
ssh_user: $('#ssh_user-' + id).val(),
ssh_pass: $('#ssh_pass-' + id).val(),
id: id,
token: $('#token').val()
},
type: "POST",
success: function (data) {
data = data.replace(/\s+/g, ' ');
if (data.indexOf('error:') != '-1' || data.indexOf('unique') != '-1') {
toastr.error(data);
} else {
toastr.clear();
$("#ssh-table-" + id).addClass("update", 1000);
setTimeout(function () {
$("#ssh-table-" + id).removeClass("update");
}, 2500);
$('select:regex(id, credentials) option[value=' + id + ']').remove();
$('select:regex(id, ssh-key-name) option[value=' + $('#ssh_name-' + id).val() + ']').remove();
$('select:regex(id, credentials)').append('<option value=' + id + '>' + $('#ssh_name-' + id).val() + '</option>').selectmenu("refresh");
$('select:regex(id, ssh-key-name)').append('<option value=' + $('#ssh_name-' + id).val() + '>' + $('#ssh_name-' + id).val() + '</option>').selectmenu("refresh");
}
}
});
}
function showApacheLog(serv) {
let rows = $('#rows').val();
let grep = $('#grep').val();
@ -911,8 +539,7 @@ function showApacheLog(serv) {
hour: hour,
minute: minute,
hour1: hour1,
minute1: minute1,
token: $('#token').val()
minute1: minute1
},
type: "POST",
success: function( data ) {
@ -920,24 +547,6 @@ function showApacheLog(serv) {
}
} );
}
function checkSshConnect(ip) {
$.ajax({
url: "/app/server/check/ssh/" + ip,
success: function (data) {
if (data.indexOf('error:') != '-1') {
toastr.error(data)
} else if (data.indexOf('failed') != '-1') {
toastr.error(data)
} else if (data.indexOf('Errno') != '-1') {
toastr.error(data)
} else {
toastr.clear();
toastr.success('Connect is accepted');
}
}
});
}
function openChangeUserPasswordDialog(id) {
changeUserPasswordDialog(id);
}
@ -993,8 +602,7 @@ function changeUserPassword(id, d) {
url: "/app/user/password",
data: {
updatepassowrd: pass,
id: id,
token: $('#token').val()
id: id
},
type: "POST",
success: function (data) {
@ -1024,7 +632,6 @@ function changeUserServiceDialog(id) {
}
$.ajax({
url: "/app/user/services/" + id,
success: function (data) {
if (data.indexOf('danger') != '-1') {
toastr.error(data);
@ -1056,21 +663,20 @@ function changeUserServiceDialog(id) {
}
function changeUserServices(user_id) {
let jsonData = {};
jsonData[user_id] = {};
jsonData['services'] = {};
jsonData['services'][user_id] = {};
jsonData['username'] = $('#login-'+user_id).val();
$('#checked_services tbody tr').each(function () {
let this_id = $(this).attr('id').split('-')[1];
jsonData[user_id][this_id] = {}
jsonData['services'][user_id][this_id] = {}
});
$.ajax( {
url: "/app/user/services/" + user_id,
data: {
jsonDatas: JSON.stringify(jsonData),
changeUserServicesUser: $('#login-'+user_id).val(),
token: $('#token').val()
},
data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8",
type: "POST",
success: function( data ) {
if (data.indexOf('error:') != '-1' || data.indexOf('Failed') != '-1') {
if (data.status === 'failed') {
toastr.error(data);
} else {
$("#user-" + user_id).addClass("update", 1000);
@ -1223,8 +829,7 @@ function removeOpenVpnProfile(id) {
$.ajax({
url: "/app/admin/openvpn/delete",
data: {
openvpndel: id,
token: $('#token').val()
openvpndel: id
},
type: "POST",
success: function (data) {
@ -1246,8 +851,7 @@ function uploadOvpn() {
url: "/app/admin/openvpn/upload",
data: {
uploadovpn: $('#ovpn_upload_file').val(),
ovpnname: $('#ovpn_upload_name').val(),
token: $('#token').val()
ovpnname: $('#ovpn_upload_name').val()
},
type: "POST",
success: function (data) {
@ -1423,10 +1027,6 @@ function serverIsUp(server_ip, server_id) {
if (cur_url.split('#')[1] == 'servers') {
$.ajax({
url: "/app/server/check/server/" + server_ip,
// data: {
// token: $('#token').val()
// },
// type: "POST",
success: function (data) {
data = data.replace(/^\s+|\s+$/g, '');
if (data.indexOf('up') != '-1') {
@ -1533,8 +1133,7 @@ function saveGroupsAndRoles(user_id) {
url: "/app/user/groups/save",
data: {
changeUserGroupsUser: $('#login-' + user_id).val(),
jsonDatas: JSON.stringify(jsonData),
token: $('#token').val()
jsonDatas: JSON.stringify(jsonData)
},
type: "POST",
success: function (data) {
@ -1554,10 +1153,6 @@ function openChangeServerServiceDialog(server_id) {
let hostname = $('#hostname-' + server_id).val();
$.ajax({
url: "/app/server/services/" + server_id,
// data: {
// token: $('#token').val()
// },
// type: "GET",
success: function (data) {
$("#groups-roles").html(data);
$("#groups-roles").dialog({
@ -1623,7 +1218,6 @@ function changeServerServices(server_id) {
data: {
jsonDatas: JSON.stringify(jsonData),
changeServerServicesServer: $('#hostname-' + server_id).val(),
token: $('#token').val()
},
type: "POST",
success: function (data) {

View File

@ -37,11 +37,16 @@ function metrics_waf(name) {
name = name.split('metrics')[1]
$.ajax({
url: "/app/waf/metric/enable/" + enable + "/" + name,
contentType: "application/json; charset=utf-8",
success: function (data) {
showOverviewWaf(ip, hostnamea);
setTimeout(function () {
$("#" + name).parent().parent().removeClass("update");
}, 2500);
if (data.status === 'failed') {
toastr.error(data.error);
} else {
showOverviewWaf(ip, hostnamea);
setTimeout(function () {
$("#" + name).parent().parent().removeClass("update");
}, 2500);
}
}
});
}
@ -71,17 +76,22 @@ function changeWafMode(id) {
let service = cur_url[1];
$.ajax({
url: "/app/waf/" + service + "/mode/" + server_hostname + "/" + waf_mode,
contentType: "application/json; charset=utf-8",
success: function (data) {
toastr.info('Do not forget restart WAF service');
$('#' + server_hostname + '-select-line').addClass("update", 1000);
setTimeout(function () {
$('#' + server_hostname + '-select-line').removeClass("update");
}, 2500);
if (data.status === 'failed') {
toastr.error(data.error);
} else {
toastr.info('Do not forget restart WAF service');
$('#' + server_hostname + '-select-line').addClass("update", 1000);
setTimeout(function () {
$('#' + server_hostname + '-select-line').removeClass("update");
}, 2500);
}
}
});
}
$( function() {
$( "#waf_rules input" ).change(function() {
$("#waf_rules input").change(function () {
let id = $(this).attr('id').split('-');
waf_rules_en(id[1])
});
@ -94,9 +104,10 @@ function waf_rules_en(id) {
let serv = cur_url[2];
$.ajax({
url: "/app/waf/" + serv + "/rule/" + id + "/" + enable,
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data.indexOf('sed:') != '-1' || data.indexOf('error: ') != '-1') {
toastr.error(data);
if (data.status === 'failed') {
toastr.error(data.error);
} else {
toastr.info('Do not forget restart WAF service');
$('#rule-' + id).addClass("update", 1000);
@ -143,21 +154,21 @@ function addNewConfig() {
new_rule_description = escapeHtml(new_rule_description);
new_rule_file = escapeHtml(new_rule_file);
serv = escapeHtml(serv);
jsonData = {
"new_waf_rule": new_rule_name,
"new_rule_description": new_rule_description,
"new_rule_file": new_rule_file
}
$.ajax({
url: "/app/waf/" + service + "/" + serv + "/rule/create",
data: {
new_waf_rule: new_rule_name,
new_rule_description: new_rule_description,
new_rule_file: new_rule_file
},
data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8",
type: "POST",
success: function (data) {
if (data.indexOf('error:') != '-1') {
toastr.error(data);
if (data.status === 'failed') {
toastr.error(data.error);
} else {
let getId = new RegExp('[0-9]+');
let id = data.match(getId) + '';
window.location.replace('waf.py?service=' + service + '&waf_rule_id=' + id + '&serv=' + serv);
window.location.replace("/app/waf/" + service + "/" + serv + "/rule/" + data.id);
}
}
});

View File

@ -3,7 +3,9 @@
{% block h2 %}{{lang.words.admin_area|title()}}{% endblock %}
{% block content %}
{% from 'include/input_macros.html' import input, select, copy_to_clipboard, checkbox %}
<script src="/app/static/js/users.js"></script>
<script src="{{ url_for('static', filename='js/admin/group.js') }}"></script>
<script src="{{ url_for('static', filename='js/admin/ssh.js') }}"></script>
<script src="{{ url_for('static', filename='js/users.js') }}"></script>
<script src="{{ url_for('static', filename='js/backup.js') }}"></script>
<script src="/app/static/js/fontawesome.min.js"></script>
{% include 'include/del_confirm.html' %}
@ -182,6 +184,7 @@
{% include 'include/intro/admin.html' %}
{% else %}
{% include 'include/intro/servers.html' %}
<input id="new-sshgroup" type="hidden" value="{{ g.user_params['group_id'] }}">
{% endif %}
{% include 'include/intro/js_script.html' %}
{% endblock %}

View File

@ -11,7 +11,7 @@
<label for="ssh_enable-{{ssh.id}}">{{lang.words.enable|title()}} SSH {{lang.words.key}}</label><input type="checkbox" id="ssh_enable-{{ssh.id}}">
{% endif %}
</td>
{% if page != "servers" %}
{% if g.user_params['role'] == 1 %}
<td>
<select id="sshgroup-{{ssh.id}}" name="sshgroup-{{ssh.id}}">
{% for group in groups %}
@ -26,7 +26,7 @@
{% endif %}
<td style="padding-top: 15px;">
<p>
<input type="text" id="ssh_user-{{ssh.id}}" class="form-control" value="{{ssh.username}}">
<input type="text" id="ssh_user-{{ssh.id}}" class="form-control" value="{{ssh.username.replace("'", "")}}">
</p>
{% if ssh.enable == 1 %}
<input type="password" id="ssh_pass-{{ssh.id}}" class="form-control" placeholder="*****" style="display: none;">
@ -36,7 +36,7 @@
<br>
</td>
<td>
<a class="delete" onclick="confirmDeleteSsh({{ssh.id}})" title="{{lang.words.delete|title()}} {{ssh.name}}" style="cursor: pointer;"></a>
<a class="delete" onclick="confirmDeleteSsh({{ssh.id}})" title="{{lang.words.delete|title()}} {{ssh.name}}" style="cursor: pointer;"></a>
</td>
</tr>
{% endfor %}

View File

@ -14,7 +14,7 @@
'smon': 'SMON',
}
%}
<script defer src="/app/static/js/admin_settings.js"></script>
<script defer src="/app/static/js/admin/admin_settings.js"></script>
<table id="settings">
<tbody>
{% set section = namespace(section='') %}

View File

@ -1,10 +1,12 @@
<table id="ssh_enable_table" class="overview">
<tr class="overviewHead" style="width: 50%;">
<td class="padding10 first-collumn" style="width: 15%;" class="help_cursor">
<span title="It's just name alias. This alias will be use in 'Servers' page for choose credentials">{{lang.words.name|title()}}<span>
<td class="padding10 first-collumn help_cursor" style="width: 15%;">
<span title="It's just name alias. This alias will be use in 'Servers' page for choose credentials">{{lang.words.name|title()}}</span>
</td>
<td class="first-collumn" style="width: 25%;" class="help_cursor" id="ssh-key-enabled-td">
<span title="If it is enabled, the key will be used, if turned off - the password. Do not forget to download the keys to all servers or install the sudo without a password">SSH {{lang.words.key}}</span>
<td class="first-collumn help_cursor" style="width: 25%;" id="ssh-key-enabled-td">
<span title="If it is enabled, the key will be used, if turned off - the password. Do not forget to download the keys to all servers or install the sudo without a password">
SSH {{lang.words.key}}
</span>
</td>
{% if g.user_params['role'] == 1 %}
<td style="width: 25%;">{{lang.words.group|title()}}</td>