Aidaho 2024-08-02 13:00:17 +03:00
parent 8f50e1b8bf
commit 178116b9c1
12 changed files with 1341 additions and 202 deletions

View File

@ -0,0 +1,255 @@
import re
from annotated_types import Gt, Le
from typing import Optional, Annotated, Union, Literal, Any, Dict, List
from shlex import quote
from pydantic_core import CoreSchema, core_schema
from pydantic import BaseModel, field_validator, StringConstraints, IPvAnyAddress, AnyUrl, root_validator, GetCoreSchemaHandler
DomainName = Annotated[str, StringConstraints(pattern=r"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$")]
class EscapedString(str):
pattern = re.compile('[&;|$`]')
@classmethod
def validate(cls, field_value, info) -> str:
if isinstance(field_value, str):
if cls.pattern.search(field_value):
return re.sub(cls.pattern, '', field_value)
elif field_value == '':
return field_value
else:
return quote(field_value.rstrip())
@classmethod
def __get_pydantic_core_schema__(
cls, source_type: Any, handler: GetCoreSchemaHandler
) -> CoreSchema:
return core_schema.chain_schema(
[
core_schema.with_info_plain_validator_function(
function=cls.validate,
)
]
)
class BaseResponse(BaseModel):
status: str = 'Ok'
class IdResponse(BaseResponse):
id: int
class IdDataResponse(IdResponse):
data: str
class DataResponse(BaseModel):
data: Union[list, dict]
class DataStrResponse(BaseModel):
data: str
class ErrorResponse(BaseModel):
status: str = 'failed'
error: Union[str, list]
class UdpBackendConfig(BaseModel):
backend_ip: Union[IPvAnyAddress, DomainName]
port: Annotated[int, Gt(1), Le(65535)]
weight: Annotated[int, Gt(0), Le(51)]
class UdpListenerRequest(BaseModel):
name: EscapedString
cluster_id: Optional[int] = None
server_id: Optional[int] = None
vip: Optional[str] = None
port: Annotated[int, Gt(1), Le(65535)]
group_id: int
config: List[UdpBackendConfig]
description: Optional[EscapedString] = None
lb_algo: Literal['rr', 'wrr', 'lc', 'wlc', 'sh', 'dh', 'wlc', 'lblc']
check_enabled: Optional[bool] = 1
reconfigure: Optional[bool] = 0
class UserPost(BaseModel):
username: EscapedString
password: str
email: EscapedString
enabled: Optional[bool] = 1
user_group: int
role: Annotated[int, Gt(0), Le(4)] = 4
class UserPut(BaseModel):
username: EscapedString
email: EscapedString
enabled: Optional[bool] = 1
class AddUserToGroup(BaseModel):
role_id: Annotated[int, Gt(0), Le(4)]
class ServerRequest(BaseModel):
hostname: EscapedString
ip: Union[IPvAnyAddress, DomainName]
enabled: Optional[bool] = 1
type_ip: Optional[bool] = 0
cred_id: int
description: Optional[EscapedString] = None
group_id: Optional[int] = None
protected: Optional[bool] = 0
master: Optional[int] = None
port: Annotated[int, Gt(1), Le(65535)] = 22
haproxy: Optional[bool] = 0
nginx: Optional[bool] = 0
apache: Optional[bool] = 0
firewall_enable: Optional[bool] = 0
scan_server: Optional[bool] = 1
class GroupQuery(BaseModel):
group_id: Optional[int] = None
class GroupRequest(BaseModel):
name: EscapedString
description: Optional[EscapedString] = None
class CredRequest(BaseModel):
name: EscapedString
username: EscapedString
password: Optional[EscapedString] = None
key_enabled: Optional[bool] = 1
group_id: Optional[int] = None
class CredUploadRequest(BaseModel):
private_key: str
passphrase: Optional[EscapedString] = None
class HAClusterServer(BaseModel):
eth: EscapedString
id: int
ip: Union[IPvAnyAddress, DomainName]
name: EscapedString
master: Optional[bool] = 1
class HAClusterService(BaseModel):
enabled: Optional[bool] = 0
docker: Optional[bool] = 0
class HAClusterVIP(BaseModel):
name: EscapedString
use_src: Optional[bool] = 1
vip: IPvAnyAddress
return_master: Optional[bool] = 1
virt_server: Optional[bool] = 1
router_id: Optional[int] = None
servers: Dict[int, HAClusterServer]
class HAClusterRequest(BaseModel):
name: EscapedString
description: Optional[EscapedString] = None
return_master: Optional[bool] = 1
servers: List[HAClusterServer]
services: Dict[str, HAClusterService]
syn_flood: Optional[bool] = 1
use_src: Optional[bool] = 1
vip: IPvAnyAddress
virt_server: Optional[bool] = 1
class ConfigFileNameQuery(BaseModel):
file_name: Optional[str] = None
version: Optional[str] = None
class ConfigRequest(BaseModel):
action: Literal['save', 'test', 'reload', 'restart']
file_name: Optional[str] = None
config_local_path: Optional[str] = None
config: str
class LoginRequest(BaseModel):
login: EscapedString
password: EscapedString
class ChannelRequest(BaseModel):
token: EscapedString
channel: EscapedString
group_id: Optional[int] = None
class ServerInstall(BaseModel):
ip: Union[IPvAnyAddress, DomainName]
master: Optional[bool] = 0
name: EscapedString
class ServiceInstall(BaseModel):
cluster_id: Optional[int] = None
servers: List[ServerInstall]
services: Dict[str, HAClusterService]
checker: Optional[bool] = 0
metrics: Optional[bool] = 0
auto_start: Optional[bool] = 0
syn_flood: Optional[bool] = 0
class Checker(BaseModel):
server_id: int
checker: Optional[bool] = 0
metrics: Optional[bool] = 0
auto_start: Optional[bool] = 0
service: Optional[bool] = 1
telegram_id: Optional[int] = 0
slack_id: Optional[int] = 0
pd_id: Optional[int] = 0
mm_id: Optional[int] = 0
email: Optional[int] = 1
service_alert: Optional[int] = 1
backend_alert: Optional[int] = 1
maxconn_alert: Optional[int] = 1
class SettingsRequest(BaseModel):
param: EscapedString
value: EscapedString
class BackupRequest(BaseModel):
cred_id: int
server: Union[IPvAnyAddress, DomainName]
rserver: Optional[Union[IPvAnyAddress, DomainName]] = None
description: Optional[EscapedString] = None
rpath: Optional[EscapedString] = None
type: Optional[EscapedString] = None
time: Optional[EscapedString] = None
class S3BackupRequest(BaseModel):
server: Union[IPvAnyAddress, DomainName]
s3_server: Optional[Union[IPvAnyAddress, DomainName]] = None
bucket: EscapedString
secret_key: Optional[EscapedString] = None
access_key: Optional[EscapedString] = None
time: Optional[EscapedString] = None
description: Optional[EscapedString] = None

View File

@ -0,0 +1,33 @@
class RoxywiGroupMismatch(Exception):
""" Raised when not superAdmin tris update resource not from its group. """
def __init__(self):
super(RoxywiGroupMismatch, self).__init__('Group ID does not match')
class RoxywiGroupNotFound(Exception):
""" Raised when a group not found. """
def __init__(self):
super(RoxywiGroupNotFound, self).__init__('Group not found')
class RoxywiResourceNotFound(Exception):
""" This class represents an exception raised when a resource is not found. """
def __init__(self, message='Resource not found'):
super(RoxywiResourceNotFound, self).__init__(message)
class RoxywiCheckLimits(Exception):
""" This class represents an exception raised when a check limit is exceeded. """
def __init__(self, message='You have reached limit for Free plan'):
super(RoxywiCheckLimits, self).__init__(message)
class RoxywiValidationError(Exception):
""" This class represents an exception raised when a validation error occurs. """
def __init__(self, message='Validation error'):
super(RoxywiValidationError, self).__init__(message)

View File

@ -3,7 +3,6 @@ from flask_jwt_extended import jwt_required
from app.routes.channel import bp
from app.middleware import get_user_params
import app.modules.common.common as common
import app.modules.tools.alerting as alerting
import app.modules.roxywi.common as roxywi_common
from app.views.channel.views import ChannelView
@ -42,17 +41,6 @@ def load_channels():
return f'{e}'
# @bp.route('/check/<int:channel_id>/<receiver_name>')
# def check_receiver(channel_id, receiver_name):
# receiver_name = common.checkAjaxInput(receiver_name)
#
# try:
# alerting.check_receiver(channel_id, receiver_name)
# return jsonify({'status': 'success'})
# except Exception as e:
# return roxywi_common.handle_json_exceptions(e, f'Cannot send message via {receiver_name}')
@bp.post('/check')
def check_sender():
json_data = request.get_json()

View File

@ -136,45 +136,6 @@ def config(service, serv, edit, config_file_name, new):
return render_template('config.html', **kwargs)
# @bp.route('/<service>/<server_ip>/save', methods=['POST'])
# @check_services
# @get_user_params()
# def save_config(service, server_ip):
# roxywi_common.check_is_server_in_group(server_ip)
# config_file = request.form.get('config')
# oldcfg = request.form.get('oldconfig')
# save = request.form.get('save')
# config_file_name = request.form.get('config_file_name')
#
# try:
# cfg = config_mod.return_cfg(service, server_ip, config_file_name)
# except Exception as e:
# return f'error: Cannot get config {e}'
#
# try:
# with open(cfg, "a") as conf:
# conf.write(config_file)
# except IOError as e:
# return f"error: Cannot read imported config file: {e}", 200
#
# try:
# if service == 'keepalived':
# stderr = config_mod.upload_and_restart(server_ip, cfg, save, service, oldcfg=oldcfg)
# else:
# stderr = config_mod.master_slave_upload_and_restart(server_ip, cfg, save, service, oldcfg=oldcfg,
# config_file_name=config_file_name)
# except Exception as e:
# return f'error: {e}', 200
#
# if save != 'test':
# config_mod.diff_config(oldcfg, cfg)
#
# if stderr:
# return stderr, 200
#
# return
@bp.route('/versions/<service>', defaults={'server_ip': None}, methods=['GET', 'POST'])
@bp.route('/versions/<service>/<server_ip>', methods=['GET', 'POST'])
@check_services

View File

@ -9,20 +9,9 @@ import app.modules.db.service as service_sql
import app.modules.server.server as server_mod
import app.modules.roxywi.common as roxywi_common
import app.modules.service.keepalived as keepalived
from app.views.ha.views import HAView, HAVIPView, HAVIPsView
from app.views.ha.views import HAView
# def register_api(view, endpoint, url, pk='listener_id', pk_type='int'):
# view_func = view.as_view(endpoint)
# bp.add_url_rule(url, view_func=view_func, methods=['GET'], defaults={pk: None})
# bp.add_url_rule(url, view_func=view_func, methods=['POST'])
# bp.add_url_rule(f'{url}/<{pk_type}:{pk}>', view_func=view_func, methods=['GET', 'PUT', 'DELETE'])
# register_api(HAView, 'ha_cluster', '/<service>', 'cluster_id')
# bp.add_url_rule('/<service>/<int:cluster_id>/vip/<int:router_id>', view_func=HAVIPView.as_view('ha_vip_g'), methods=['GET'])
bp.add_url_rule('/<service>', view_func=HAView.as_view('ha_cluster'), methods=['GET'], defaults={'cluster_id': None})
# bp.add_url_rule('/<service>/<int:router_id>/vip', view_func=HAVIPView.as_view('ha_vip_d'), methods=['DELETE'])
# bp.add_url_rule('/<service>/<int:cluster_id>/vips', view_func=HAVIPsView.as_view('ha_vips'), methods=['GET'])
@bp.before_request

View File

@ -45,16 +45,6 @@ def install_monitoring():
return render_template('install.html', **kwargs)
# @bp.post('/<service>')
# @check_services
# def install_service(service):
# json_data = request.get_json()
# try:
# return service_mod.install_service(service, json_data)
# except Exception as e:
# return jsonify({'status': 'failed', 'error': f'{e}'})
@bp.post('/exporter/<exporter>')
def install_exporter(exporter):
json_data = request.get_json()
@ -141,14 +131,3 @@ def check_geoip(service, server_ip):
service_dir = common.return_nice_path(sql.get_setting(f'{service}_dir'))
cmd = f"ls {service_dir}geoip/"
return server_mod.ssh_command(server_ip, cmd)
#
# @bp.post('/udp')
# def install_udp():
# json_data = request.get_json()
# listener_id = int(json_data['listener_id'])
# try:
# inv, server_ips = service_mod.generate_udp_inv(listener_id, 'install')
# return service_mod.run_ansible(inv, server_ips, 'udp'), 201
# except Exception as e:
# return jsonify({'status': 'failed', 'error': f'Cannot create listener: {e}'})

View File

@ -156,24 +156,6 @@ def load_backup():
return render_template('include/admin_backup.html', **kwargs)
# @bp.post('/s3backup/create')
# @bp.post('/s3backup/delete')
# def create_s3_backup():
# server = common.is_ip_or_dns(request.form.get('s3_backup_server'))
# s3_server = common.checkAjaxInput(request.form.get('s3_server'))
# bucket = common.checkAjaxInput(request.form.get('s3_bucket'))
# secret_key = common.checkAjaxInput(request.form.get('s3_secret_key'))
# access_key = common.checkAjaxInput(request.form.get('s3_access_key'))
# time = common.checkAjaxInput(request.form.get('time'))
# deljob = common.checkAjaxInput(request.form.get('dels3job'))
# description = common.checkAjaxInput(request.form.get('description'))
#
# try:
# return backup_mod.s3_backup(server, s3_server, bucket, secret_key, access_key, time, deljob, description)
# except Exception as e:
# return str(e)
@bp.route('/git', methods=['DELETE', 'POST'])
def create_git_backup():
json_data = request.get_json()

View File

@ -185,24 +185,6 @@ def services(service, serv):
return render_template('service.html', **kwargs)
# @bp.post('/action/<service>/check-service')
# def check_service(service):
# claims = get_jwt()
# server_ip = common.checkAjaxInput(request.form.get('server_ip'))
#
# try:
# return service_action.check_service(server_ip, claims['user_id'], service)
# except Exception:
# return 'logout'
#
#
# @bp.route('/action/<service>/<server_ip>/<action>', methods=['GET'])
# def action_service(service, server_ip, action):
# server_ip = common.is_ip_or_dns(server_ip)
#
# return service_action.common_action(server_ip, action, service)
@bp.route('/<service>/<server_ip>/last-edit')
@check_services
def last_edit(service, server_ip):
@ -277,27 +259,12 @@ def show_keepalived_become_master():
server_ip = common.is_ip_or_dns(request.form.get('keepalivedBecameMaster'))
return roxy_overview.keepalived_became_master(server_ip)
#
#
# @bp.route('/<service>/backends/<server_ip>')
# @cache.cached()
# def show_service_backends(service, server_ip):
# server_ip = common.is_ip_or_dns(server_ip)
#
# return service_common.overview_backends(server_ip, service)
@bp.route('/position/<int:server_id>/<int:pos>')
def change_pos(server_id, pos):
return server_sql.update_server_pos(pos, server_id)
#
# @bp.route('/haproxy/version/<server_ip>')
# def get_haproxy_v(server_ip):
# server_ip = common.is_ip_or_dns(server_ip)
#
# return service_common.check_haproxy_version(server_ip)
@bp.route('/settings/<service>/<int:server_id>')
@check_services

View File

@ -34,56 +34,6 @@ def before_request():
pass
# @bp.route('', methods=['POST', 'PUT', 'DELETE'])
# @get_user_params()
# def create_user():
# roxywi_auth.page_for_admin(level=2)
# json_data = request.get_json()
#
# 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, '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(user_name, email, user_id, enabled)
# except Exception as e:
# return roxywi_common.handle_json_exceptions(e, '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, f'Cannot delete the user {user_id}')
@bp.route('/ldap/<username>')
def get_ldap_email(username):
roxywi_auth.page_for_admin(level=2)
@ -140,31 +90,14 @@ def get_current_group():
group_id = claims['group']
user_id = claims['user_id']
if request.method == 'GET':
# uuid = common.checkAjaxInput(request.cookies.get('uuid'))
# group = common.checkAjaxInput(request.cookies.get('group'))
return roxywi_user.get_user_active_group(group_id, user_id)
elif request.method == 'PUT':
# group_id = common.checkAjaxInput(request.form.get('group'))
# user_uuid = common.checkAjaxInput(request.form.get('uuid'))
return roxywi_user.change_user_active_group(group_id, user_id)
# @bp.route('/groups/<int:user_id>')
# def show_user_groups_and_roles(user_id):
# lang = roxywi_common.get_user_lang_for_flask()
#
# return roxywi_user.show_user_groups_and_roles(user_id, lang)
@bp.post('/groups/save')
def change_user_groups_and_roles():
user = common.checkAjaxInput(request.form.get('changeUserGroupsUser'))
groups_and_roles = json.loads(request.form.get('jsonDatas'))
return roxywi_user.save_user_group_and_role(user, groups_and_roles)
# @bp.route('/group/name/<int:group_id>')
# def get_group_name_by_id(group_id):
# return group_sql.get_group_name_by_id(group_id)

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="SwaggerUI" />
<title>SwaggerUI</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js" crossorigin></script>
<script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-standalone-preset.js" crossorigin></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: '{{ url_for('api.spec') }}',
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
layout: "StandaloneLayout",
});
};
</script>
</body>
</html>

476
app/views/udp/views.py Normal file
View File

@ -0,0 +1,476 @@
from flask import render_template, g, jsonify
from flask.views import MethodView
from flask_pydantic import validate
from flask_jwt_extended import jwt_required
from playhouse.shortcuts import model_to_dict
import app.modules.roxywi.auth as roxywi_auth
import app.modules.roxywi.common as roxywi_common
import app.modules.common.common as common
import app.modules.db.udp as udp_sql
import app.modules.db.ha_cluster as ha_sql
import app.modules.db.server as server_sql
import app.modules.service.udp as udp_mod
import app.modules.service.installation as service_mod
from app.middleware import get_user_params, check_services, page_for_admin, check_group
from app.modules.common.common_classes import SupportClass
from app.modules.roxywi.class_models import BaseResponse, IdResponse, UdpListenerRequest, GroupQuery
class UDPListener(MethodView):
method_decorators = ["GET", "POST", "PUT", "DELETE"]
decorators = [jwt_required(), get_user_params(), check_services, check_group()]
def __init__(self, is_api=True):
self.is_api = is_api
def get(self, service: str, listener_id: int):
"""
Get information about a specific UDP listener.
---
tags:
- UDP listener
parameters:
- name: service
in: path
type: string
required: true
description: 'Can be only "udp"'
- name: listener_id
in: path
type: integer
required: true
description: The listener's identifier
responses:
200:
description: Listener configuration returned successfully
schema:
type: object
properties:
check_enabled:
type: integer
cluster_id:
type: string
config:
type: array
items:
type: object
properties:
backend_ip:
type: string
description: The IP address of the backend server
port:
type: integer
description: Port number on which the backend server listens for requests
weight:
type: integer
description: Weight assigned to the backend server
delay_before_retry:
type: integer
delay_loop:
type: integer
description:
type: string
group_id:
type: integer
id:
type: integer
lb_algo:
type: string
name:
type: string
port:
type: integer
retry:
type: integer
server_id:
type: integer
vip:
type: string
default:
description: Unexpected error
"""
if self.is_api:
if listener_id:
try:
listener_config = udp_mod.get_listener_config(listener_id)
listener_config['status'] = udp_mod.check_is_listener_active(listener_id)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Listener not found')
return jsonify(listener_config)
else:
if not listener_id:
kwargs = {
'listeners': udp_sql.select_listeners(g.user_params['group_id']),
'lang': g.user_params['lang'],
'clusters': ha_sql.select_clusters(g.user_params['group_id']),
'is_needed_tool': common.is_tool('ansible'),
'user_subscription': roxywi_common.return_user_subscription()
}
return render_template('udp/listeners.html', **kwargs)
else:
listener = udp_sql.get_listener(listener_id)
cluster = dict()
server = dict()
if listener.cluster_id:
cluster = ha_sql.select_cluster(listener.cluster_id)
elif listener.server_id:
server = server_sql.get_server_by_id(listener.server_id)
kwargs = {
'clusters': cluster,
'listener': listener,
'server': server,
'lang': g.user_params['lang'],
}
return render_template('udp/listener.html', **kwargs)
@validate(body=UdpListenerRequest)
def post(self, service: str, body: UdpListenerRequest):
"""
This endpoint allows to create a new UDP listener.
---
tags:
- UDP listener
parameters:
- in: 'path'
name: 'service'
description: 'Can be only "udp"'
required: true
schema:
type: 'string'
- in: body
name: body
required: true
schema:
required:
- config
- name
- port
- lb_algo
- vip
type: object
properties:
config:
type: array
items:
type: object
properties:
backend_ip:
type: string
description: The IP address of the backend server
port:
type: integer
description: Port number on which the backend server listens for requests
weight:
type: integer
description: Weight assigned to the backend server
name:
type: string
cluster_id:
type: int
description: Cluster ID where the UDP listener is located. Must be determined if server_id empty
server_id:
type: int
description: Standalone mode. Server ID where the UDP listener is located. Must be determined if cluster_id empty
group_id:
type: string
port:
type: string
lb_algo:
type: string
description: "'rr': 'Round robin', 'wrr': 'Weighted Round Robin', 'lc': 'Least Connection', 'wlc': 'Weighted Least Connection', 'sh': 'Source Hashing', 'dh': 'Destination Hashing', 'lblc': 'Locality-Based Least Connection'"
schema:
enum: [rr, wrr, lc, wlc, sh, dh, wlc, lblc]
description:
type: string
vip:
type: string
description: IP address of the UDP listener binding, if Standalone mode. VIP address of the UDP listener binding, if HA Cluster mode
reconfigure:
type: boolean
description: If 1, reconfigure UDP listener. If 0, just save UDP listener without configuration on servers
responses:
201:
description: UDP listener created successfully
400:
description: Invalid request data
default:
description: Unexpected error
"""
roxywi_auth.page_for_admin(level=3)
try:
listener_id = udp_sql.insert_listener(**body.model_dump(mode='json', exclude={'reconfigure'}))
roxywi_common.logging(listener_id, f'UDP listener {body.name} has been created', keep_history=1,
roxywi=1, service='UDP Listener')
if body.reconfigure:
self._reconfigure(listener_id, 'install')
return IdResponse(id=listener_id).model_dump(mode='json')
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot create UDP listener')
@validate(body=UdpListenerRequest)
def put(self, service: str, listener_id: int, body: UdpListenerRequest):
"""
This endpoint allows to update a UDP listener.
---
tags:
- UDP listener
parameters:
- in: 'path'
name: 'service'
description: 'Can be only "udp"'
required: true
schema:
type: 'string'
- in: 'path'
name: 'listener_id'
description: 'ID of the UDP listener'
required: true
schema:
type: 'integer'
- in: body
name: body
required: true
schema:
required:
- config
- name
- port
- lb_algo
- vip
type: object
properties:
config:
type: array
items:
type: object
properties:
backend_ip:
type: string
description: The IP address of the backend server
port:
type: integer
description: Port number on which the backend server listens for requests
weight:
type: integer
description: Weight assigned to the backend server
name:
type: string
cluster_id:
type: int
description: Cluster ID where the UDP listener is located. Must be determined if server_id empty
server_id:
type: int
description: Standalone mode. Server ID where the UDP listener is located. Must be determined if cluster_id empty
group_id:
type: string
port:
type: string
lb_algo:
type: string
description: "'rr': 'Round robin', 'wrr': 'Weighted Round Robin', 'lc': 'Least Connection', 'wlc': 'Weighted Least Connection', 'sh': 'Source Hashing', 'dh': 'Destination Hashing', 'lblc': 'Locality-Based Least Connection'"
schema:
enum: [rr, wrr, lc, wlc, sh, dh, wlc, lblc]
description:
type: string
vip:
type: string
description: IP address of the UDP listener binding, if Standalone mode. VIP address of the UDP listener binding, if HA Cluster mode
reconfigure:
type: boolean
description: If 1, reconfigure UDP listener. If 0, just save UDP listener without configuration on servers
responses:
201:
description: UDP listener created successfully
400:
description: Invalid request data
default:
description: Unexpected error
"""
roxywi_auth.page_for_admin(level=3)
try:
udp_sql.update_listener(listener_id, **body.model_dump(mode='json', exclude={'reconfigure'}))
roxywi_common.logging(listener_id, f'UDP listener {body.name} has been updated', keep_history=1,
roxywi=1, service='UDP Listener')
if body.reconfigure:
self._reconfigure(listener_id, 'install')
return BaseResponse().model_dump(mode='json')
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot update UDP listener')
def delete(self, service: str, listener_id: int):
"""
Delete a UDP listener
---
tags:
- UDP listener
parameters:
- in: 'path'
name: 'service'
description: 'Can be only "udp"'
required: true
schema:
type: 'string'
- in: 'path'
name: 'listener_id'
description: 'ID of the UDP listener'
required: true
schema:
type: 'integer'
responses:
204:
description: UDP listener deletion successful
"""
roxywi_auth.page_for_admin(level=3)
try:
self._reconfigure(listener_id, 'uninstall')
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 roxywi_common.handle_json_exceptions(e,f'Cannot create inventory for UDP listener deleting {listener_id}')
try:
udp_sql.delete_listener(listener_id)
return BaseResponse().model_dump(mode='json'), 204
except Exception as e:
return roxywi_common.handle_json_exceptions(e,f'Cannot delete UDP listener {listener_id}')
@staticmethod
def _reconfigure(listener_id, action):
try:
inv, server_ips = service_mod.generate_udp_inv(listener_id, action)
except Exception as e:
raise Exception(e)
try:
output = service_mod.run_ansible(inv, server_ips, 'udp')
if len(output['failures']) > 0 or len(output['dark']) > 0:
raise Exception(f'Cannot {action} UDP listener. Check Apache error log')
except Exception as e:
raise Exception(f'Cannot {action} UDP listener: {e}')
class UDPListeners(MethodView):
method_decorators = ["GET"]
decorators = [jwt_required(), get_user_params(), check_services, check_group()]
@validate(query=GroupQuery)
def get(self, service: str, query: GroupQuery):
"""
Get information about a specific UDP listener.
---
tags:
- UDP listener
parameters:
- name: service
in: path
type: string
required: true
description: 'Can be only "udp"'
- name: group_id
in: query
type: integer
required: false
description: The group's identifier. Only accessible by superAdmin role.
responses:
200:
description: Listener configuration returned successfully
schema:
type: array
items:
type: object
properties:
check_enabled:
type: integer
cluster_id:
type: string
config:
type: array
items:
type: object
properties:
backend_ip:
type: string
description: The IP address of the backend server
port:
type: integer
description: Port number on which the backend server listens for requests
weight:
type: integer
description: Weight assigned to the backend server
delay_before_retry:
type: integer
delay_loop:
type: integer
description:
type: string
group_id:
type: integer
id:
type: integer
lb_algo:
type: string
name:
type: string
port:
type: integer
retry:
type: integer
server_id:
type: integer
vip:
type: string
default:
description: Unexpected error
"""
try:
group_id = SupportClass.return_group_id(query)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, f'Cannot get UDP listeners')
try:
listeners = udp_sql.select_listeners(group_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, f'Cannot get UDP listeners')
return jsonify([model_to_dict(listener, recurse=False) for listener in listeners])
class UDPListenerActionView(MethodView):
methods = ['GET']
decorators = [jwt_required(), get_user_params(), check_services, page_for_admin(level=3), check_group()]
@staticmethod
def get(service: str, listener_id: int, action: str):
"""
This endpoint performs a specified action on a certain UDP listener.
---
tags:
- UDP listener
parameters:
- in: path
name: service
required: true
description: Can be only "udp"
- in: path
name: listener_id
schema:
type: integer
required: true
description: The ID af the UDP listener
- in: path
name: action
schema:
type: string
enum: [start, stop, reload, restart]
required: true
description: The action to be performed on the service (start, stop, reload, restart)
responses:
200:
description: Successful operation
default:
description: Unexpected error
"""
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 BaseResponse().model_dump(mode='json')
except Exception as e:
return roxywi_common.handle_json_exceptions(e, f'Cannot {action} listener')

548
app/views/user/views.py Normal file
View File

@ -0,0 +1,548 @@
from typing import Union
from flask.views import MethodView
from flask_pydantic import validate
from flask import render_template, jsonify, g
from flask_jwt_extended import jwt_required
from flask_jwt_extended import set_access_cookies
from playhouse.shortcuts import model_to_dict
import app.modules.db.sql as sql
import app.modules.db.user as user_sql
import app.modules.db.group as group_sql
import app.modules.roxywi.user as roxywi_user
import app.modules.roxywi.auth as roxywi_auth
import app.modules.roxywi.common as roxywi_common
from app.modules.db.db_model import User as User_DB
from app.modules.roxywi.class_models import UserPost, UserPut, IdResponse, IdDataResponse, BaseResponse, AddUserToGroup
from app.modules.roxywi.exception import RoxywiResourceNotFound
from app.middleware import get_user_params, page_for_admin, check_group
class UserView(MethodView):
methods = ["GET", "POST", "PUT", "DELETE"]
decorators = [jwt_required(), get_user_params(), page_for_admin(level=2), check_group()]
def __init__(self, is_api=False):
"""
Initialize UserView instance
---
parameters:
- name: is_api
in: path
type: boolean
description: is api
"""
self.is_api = is_api
def get(self, user_id: int):
"""
Get User information by ID
---
tags:
- 'User'
parameters:
- in: 'path'
name: 'user_id'
description: 'ID of the User to retrieve'
required: true
type: 'integer'
responses:
'200':
description: 'Successful Operation'
schema:
type: 'object'
id: 'User'
properties:
user_group_id:
type: 'object'
properties:
description:
type: 'string'
description: 'Group description'
group_id:
type: 'integer'
description: 'Group ID'
name:
type: 'string'
description: 'Group name'
user_id:
type: 'object'
properties:
email:
type: 'string'
description: 'User email'
enabled:
type: 'integer'
description: 'User activation status'
last_login_date:
type: 'string'
format: 'date-time'
description: 'User last login date'
last_login_ip:
type: 'string'
description: 'User last login IP'
ldap_user:
type: 'integer'
description: 'Is User a LDAP user'
role:
type: 'string'
description: 'User role'
user_id:
type: 'integer'
description: 'User ID'
username:
type: 'string'
description: 'Username'
user_role_id:
type: 'integer'
description: 'User role ID'
'404':
description: 'User not found'
schema:
id: 'NotFound'
properties:
message:
type: 'string'
description: 'Error message'
"""
users_list = []
try:
users = user_sql.select_user_groups_with_names(user_id)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get user')
try:
roxywi_common.is_user_has_access_to_its_group(user_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot find user'), 404
for user in users:
users_list.append(model_to_dict(user, exclude={User_DB.group_id, User_DB.password, User_DB.user_services}))
return jsonify(users_list)
@validate(body=UserPost)
def post(self, body: UserPost) -> Union[dict, tuple]:
"""
Create a new user
---
tags:
- User
parameters:
- name: body
in: body
schema:
id: NewUser
required:
- email
- password
- role
- username
- enabled
- user_group
properties:
email:
type: string
description: The email of the user
password:
type: string
description: The password of the user
role:
type: integer
description: The role of the user
username:
type: string
description: The username of the user
enabled:
type: integer
description: 'Enable status (1 for enabled)'
group_id:
type: integer
description: The ID of the user's group
responses:
200:
description: user created
schema:
id: CreateUserResponse
properties:
status:
type: string
description: The status of the user creation
id:
type: integer
description: The ID of the created user
"""
if g.user_params['role'] > body.role:
return roxywi_common.handle_json_exceptions('Wrong role', 'Cannot create user')
try:
user_id = roxywi_user.create_user(body.username, body.email, body.password, body.role, body.enabled, body.user_group)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot create a new user')
else:
if self.is_api:
return IdResponse(id=user_id), 201
else:
lang = roxywi_common.get_user_lang_for_flask()
data = render_template(
'ajax/new_user.html', users=user_sql.select_users(user=body.username), groups=group_sql.select_groups(),
roles=sql.select_roles(), adding=1, lang=lang
)
return IdDataResponse(id=user_id, data=data), 201
@validate(body=UserPut)
def put(self, user_id: int, body: UserPut) -> Union[dict, tuple]:
"""
Update User Information
---
tags:
- User
description: Update the information of a user based on the provided user ID.
parameters:
- in: 'path'
name: 'user_id'
description: 'ID of the User to retrieve'
required: true
type: 'integer'
- in: body
name: body
schema:
id: UserUpdate
required:
- id
- username
- email
- enabled
properties:
email:
type: string
description: The email of the user
username:
type: string
description: The username of the user
enabled:
type: integer
description: 'Enable status (1 for enabled)'
responses:
400:
description: Invalid request
201:
description: User information update successful
"""
try:
_ = user_sql.get_user_id(user_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot get user'), 404
try:
user_sql.update_user_from_admin_area(user_id, **body.model_dump(mode='json'))
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot update user')
roxywi_common.logging(body.username, 'has been updated user', roxywi=1, login=1)
return BaseResponse(), 201
@validate()
def delete(self, user_id: int):
"""
Delete a User
---
tags:
- User
Description: Delete a user based on the provided user ID.
parameters:
- in: 'path'
name: 'user_id'
description: 'User ID to delete'
required: true
type: 'integer'
responses:
204:
description: User deletion successful
400:
description: Invalid request
404:
description: User not found
"""
try:
roxywi_common.is_user_has_access_to_its_group(user_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot find user'), 404
try:
user = user_sql.get_user_id(user_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot get user'), 404
if g.user_params['role'] > int(user.role):
return roxywi_common.handle_json_exceptions('Wrong role', 'Cannot delete user'), 404
try:
roxywi_user.delete_user(user_id)
return BaseResponse().model_dump(mode='json'), 204
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete the user')
class UserGroupView(MethodView):
methods = ["GET", "POST", "PUT", "DELETE", "PATCH"]
decorators = [jwt_required(), get_user_params(), page_for_admin(level=4), check_group()]
def get(self, user_id: int):
"""
Fetch a specific User Group.
---
tags:
- User group
parameters:
- in: 'path'
name: 'user_id'
description: 'User ID to get'
required: true
type: 'integer'
responses:
200:
description: A list of user's groups
schema:
type: array
items:
id: UserGet
properties:
user_group_id:
type: integer
description: User group ID
example: 1
user_role_id:
type: integer
description: User role ID
example: 1
"""
try:
users = user_sql.select_user_groups_with_names(user_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot get group')
json_data = []
for user in users:
json_data.append(model_to_dict(user, exclude=[User_DB.password, User_DB.user_services]))
return jsonify(json_data)
@validate(body=AddUserToGroup)
def post(self, user_id: int, group_id: int, body: AddUserToGroup):
"""
Add a User to a specific Group
---
tags:
- 'User group'
parameters:
- in: 'path'
name: 'user_id'
description: 'ID of the User to be added'
required: true
type: 'integer'
- in: 'path'
name: 'group_id'
description: 'ID of the Group which will have a new user'
required: true
type: 'integer'
- in: body
name: role_id
required: true
schema:
properties:
role_id:
type: integer
description: A role inside the group
responses:
'201':
description: 'User successfully added to the group'
'404':
description: 'User or Group not found'
"""
page_for_admin(level=2)
try:
self._check_is_user_and_group(user_id, group_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot get user or group'), 404
try:
user_sql.update_user_role(user_id, group_id, body.role_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot add user to group'), 500
else:
return BaseResponse().model_dump(mode='json'), 201
@validate(body=AddUserToGroup)
def put(self, user_id: int, group_id: int, body: AddUserToGroup):
"""
Update a User to a specific Group
---
tags:
- 'User group'
parameters:
- in: 'path'
name: 'user_id'
description: 'ID of the User to be added'
required: true
type: 'integer'
- in: 'path'
name: 'group_id'
description: 'ID of the Group where updating the user'
required: true
type: 'integer'
- in: body
name: role_id
required: true
schema:
properties:
role_id:
type: integer
description: A role inside the group
responses:
'201':
description: 'User successfully added to the group'
'404':
description: 'User or Group not found'
"""
page_for_admin(level=2)
try:
self._check_is_user_and_group(user_id, group_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot get user or group'), 404
try:
user_sql.delete_user_from_group(group_id, user_id)
user_sql.update_user_role(user_id, group_id, body.role_id)
return BaseResponse().model_dump(mode='json'), 201
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot delete user')
def patch(self, user_id: int, group_id: int):
"""
Assign a User to a specific Group
---
tags:
- 'User group'
parameters:
- in: 'path'
name: 'user_id'
description: 'ID of the User to be assigned to the group'
required: true
type: 'integer'
- in: 'path'
name: 'group_id'
description: 'ID of the Group which the user will be assigned to'
required: true
type: 'integer'
responses:
201:
description: 'User successfully assigned to the group'
404:
description: 'User or Group not found'
schema:
id: 'NotFound'
properties:
error:
type: 'string'
description: 'Error message'
"""
try:
self._check_is_user_and_group(user_id, group_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot get user or group'), 404
user_param = {"user": user_id, "group": group_id}
access_token = roxywi_auth.create_jwt_token(user_param)
response = jsonify({'status': 'Ok'})
set_access_cookies(response, access_token)
try:
user_sql.update_user_current_groups(group_id, user_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot update user or group'), 500
return response
def delete(self, user_id: int, group_id: int):
"""
Delete a User from a specific Group
---
tags:
- 'User group'
parameters:
- in: 'path'
name: 'user_id'
description: 'ID of the User to be deleted'
required: true
type: 'integer'
- in: 'path'
name: 'group_id'
description: 'ID of the Group from which user will be deleted'
required: true
type: 'integer'
responses:
'204':
description: 'User successfully deleted'
'404':
description: 'User or Group not found'
"""
page_for_admin(level=2)
try:
self._check_is_user_and_group(user_id, group_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot get user or group'), 404
try:
user_sql.delete_user_from_group(group_id, user_id)
return BaseResponse().model_dump(mode='json'), 204
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot delete user')
@staticmethod
def _check_is_user_and_group(user_id: int, group_id: int):
try:
_ = user_sql.get_user_id(user_id)
groups = group_sql.get_group_name_by_id(group_id)
if len(groups) == 0:
raise RoxywiResourceNotFound
except Exception as e:
raise e
class UserRoles(MethodView):
methods = ['GET']
decorators = [jwt_required(), get_user_params(), page_for_admin()]
@staticmethod
def get():
"""
User Roles
---
tags:
- User Roles
summary: Get All User Role Information
description: This method is used to retrieve all available user roles along with their descriptions and corresponding role_ids.
produces:
- application/json
responses:
200:
description: User Roles Returned
schema:
type: array
items:
type: object
properties:
name:
type: string
description: The name of the user role.
example: "superAdmin"
role_id:
type: integer
description: The ID of the user role.
example: 1
description:
type: string
description: The description of the user role.
example: "Has the highest level of administrative..."
"""
try:
roles = sql.select_roles()
except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot get roles')
roles_list = []
for role in roles:
roles_list.append(model_to_dict(role))
return jsonify(roles_list)