diff --git a/app/modules/config/config.py b/app/modules/config/config.py index 92863571..3af779c3 100644 --- a/app/modules/config/config.py +++ b/app/modules/config/config.py @@ -460,7 +460,7 @@ def compare_config(service: str, left: str, right: str) -> str: return render_template('ajax/compare.html', stdout=output, lang=lang) -def show_config(server_ip: str, service: str, config_file_name: str, configver: str, claims: dict) -> str: +def show_config(server_ip: str, service: str, config_file_name: str, configver: str, claims: dict, edit_section: str) -> str: """ Get and display the configuration file for a given server. @@ -516,7 +516,8 @@ def show_config(server_ip: str, service: str, config_file_name: str, configver: 'is_serv_protected': server_sql.is_serv_protected(server_ip), 'is_restart': service_sql.select_service_setting(server.server_id, service, 'restart'), 'lang': roxywi_common.get_user_lang_for_flask(), - 'hostname': server.hostname + 'hostname': server.hostname, + 'edit_section': edit_section } return render_template('ajax/config_show.html', **kwargs) diff --git a/app/modules/roxywi/class_models.py b/app/modules/roxywi/class_models.py index 634506c0..abbb1fe4 100644 --- a/app/modules/roxywi/class_models.py +++ b/app/modules/roxywi/class_models.py @@ -209,6 +209,13 @@ class ConfigRequest(BaseModel): config_local_path: Optional[str] = None config: str + @root_validator(skip_on_failure=True) + @classmethod + def decode_config(cls, values): + if 'config' in values: + values['config'] = values['config'].encode('utf-8') + return values + class LoginRequest(BaseModel): login: EscapedString @@ -407,6 +414,13 @@ class HaproxyServersCheck(BaseModel): inter: Optional[int] = 2000 +class HaproxyServersTemplate(BaseModel): + prefix: int + count: int + servers: Union[IPvAnyAddress, DomainName] + port: Annotated[int, Gt(1), Le(65535)] + + class HaproxyCircuitBreaking(BaseModel): observe: Literal['layer7', 'layer4'] error_limit: int @@ -415,7 +429,7 @@ class HaproxyCircuitBreaking(BaseModel): class HaproxyConfigRequest(BaseModel): balance: Optional[Literal['roundrobin', 'source', 'leastconn', 'first', 'rdp-cookie', 'uri', 'uri whole', 'static-rr']] = None - mode: Literal['tcp', 'http'] = 'http' + mode: Literal['tcp', 'http', 'log'] = 'http' type: Literal['listen', 'frontend', 'backend'] name: EscapedString option: Optional[str] = None @@ -425,6 +439,7 @@ class HaproxyConfigRequest(BaseModel): headers: Optional[List[HaproxyHeaders]] = None acls: Optional[List[HaproxyAcls]] = None backend_servers: Optional[List[HaproxyBackendServer]] = None + servers_template: Optional[HaproxyServersTemplate] = None blacklist: Optional[str] = '' whitelist: Optional[str] = '' ssl: Optional[HaproxySSL] = None diff --git a/app/modules/roxywi/logs.py b/app/modules/roxywi/logs.py index 09c274e5..c9981266 100644 --- a/app/modules/roxywi/logs.py +++ b/app/modules/roxywi/logs.py @@ -89,7 +89,7 @@ def show_roxy_log( if syslog_server is None or syslog_server == '': raise Exception('error: Syslog server is enabled, but there is no IP for syslog server') - if waf: + if waf and service == 'haproxy': local_path_logs = '/var/log/waf.log' commands = "sudo cat %s |tail -%s %s %s" % (local_path_logs, rows, grep_act, exgrep_act) diff --git a/app/modules/server/server.py b/app/modules/server/server.py index 2da52f4d..2dc75ab7 100644 --- a/app/modules/server/server.py +++ b/app/modules/server/server.py @@ -56,7 +56,7 @@ def ssh_command(server_ip: str, commands: str, **kwargs): def subprocess_execute(cmd): import subprocess - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True, errors='backslashreplace') stdout, stderr = p.communicate() output = stdout.splitlines() return output, stderr diff --git a/app/routes/config/routes.py b/app/routes/config/routes.py index 84657c50..109a55cc 100644 --- a/app/routes/config/routes.py +++ b/app/routes/config/routes.py @@ -39,9 +39,10 @@ def show_config(service): configver = request.json.get('configver') server_ip = request.json.get('serv') claims = get_jwt() + edit_section = request.json.get('edit_section') try: - data = config_mod.show_config(server_ip, service, config_file_name, configver, claims) + data = config_mod.show_config(server_ip, service, config_file_name, configver, claims, edit_section) return DataStrResponse(data=data).model_dump(mode='json'), 200 except Exception as e: return roxywi_common.handler_exceptions_for_json_data(e, '') @@ -114,8 +115,9 @@ def config(service, serv, edit, config_file_name, new): pass try: - conf = open(cfg, "r") + conf = open(cfg, "rb") config_read = conf.read() + config_read = config_read.decode('utf-8') conf.close() except IOError as e: return f'Cannot read imported config file {e}', 200 diff --git a/app/routes/logs/routes.py b/app/routes/logs/routes.py index 6a62765e..40341852 100644 --- a/app/routes/logs/routes.py +++ b/app/routes/logs/routes.py @@ -41,7 +41,6 @@ def logs_internal(): selects.append(['roxy-wi.access.log', 'access.log']) kwargs = { - 'autorefresh': 1, 'selects': selects, 'serv': log_file, 'lang': g.user_params['lang'] @@ -63,6 +62,8 @@ def logs(service, waf): # hour1 = request.args.get('hour1') # minute1 = request.args.get('minute1') log_file = request.args.get('file') + service_desc = service_sql.select_service(service) + service_name = service_desc.service if rows is None: rows = 10 @@ -70,17 +71,14 @@ def logs(service, waf): grep = '' if service in ('haproxy', 'nginx', 'keepalived', 'apache') and not waf: - service_desc = service_sql.select_service(service) - service_name = service_desc.service servers = roxywi_common.get_dick_permit(service=service_desc.slug) elif waf: service_name = 'WAF' - servers = roxywi_common.get_dick_permit(haproxy=1) + servers = roxywi_common.get_dick_permit(service=service_desc.slug) else: return redirect(url_for('index')) kwargs = { - 'autorefresh': 1, 'servers': servers, 'serv': serv, 'service': service, diff --git a/app/scripts/ansible/roles/haproxy_section/templates/section.j2 b/app/scripts/ansible/roles/haproxy_section/templates/section.j2 index e9030cdc..65623a9b 100644 --- a/app/scripts/ansible/roles/haproxy_section/templates/section.j2 +++ b/app/scripts/ansible/roles/haproxy_section/templates/section.j2 @@ -130,13 +130,17 @@ default-server observe {{ config.circuit_breaking.observe }} error-limit {{ config.circuit_breaking.error_limit }} on-error {{ config.circuit_breaking.on_error }} {% endif -%} - {% if config.backend_servers != 'None' -%} + {% if config.backend_servers != 'None' and config.servers_template == 'None' -%} {% for backend in config.backend_servers -%} server {{ backend.server }} {{ backend.server }}:{{ backend.port }} port {{ backend.port_check }} {{ check_option }} {{ ssl_check_option }} maxconn {{ backend.maxconn }}{% if backend.send_proxy %} send-proxy{% endif %}{% if backend.backup %} backup {% endif %} {% endfor -%} {% endif -%} + {% if config.servers_template != 'None' -%} + server-template {{ config.servers_template.prefix }} {{ config.servers_template.count }} {{ config.servers_template.servers }}: {{ config.servers_template.port }} {{ check_option }} + {% endif -%} + {% if config.backends and config.backends != 'None' -%} use_backend {{ config.backends }} {% endif %} diff --git a/app/static/js/overview.js b/app/static/js/overview.js index c07a1bfa..42c913e7 100644 --- a/app/static/js/overview.js +++ b/app/static/js/overview.js @@ -40,7 +40,7 @@ function overviewHapserverBackends(serv, hostname, service) { $("#top-" + hostname).empty(); for (let i in data.data) { if (service === 'haproxy') { - div = `${data.data[i]} ` + div = `${data.data[i]} ` } else if (service === 'nginx' || service === 'apache') { div = `${data.data[i]}`; } else { diff --git a/app/static/js/script.js b/app/static/js/script.js index 722069d7..158243c6 100644 --- a/app/static/js/script.js +++ b/app/static/js/script.js @@ -154,7 +154,7 @@ function showLog() { toastr.warning('Select a log file first') return false; } else { - file = file_from_get; + file = findGetParameter('file'); } } if ((file === undefined || file === null) && waf === '') { @@ -174,9 +174,9 @@ function showLog() { if (service === 'None') { service = 'haproxy'; } - if (waf && waf != 'haproxy' && waf != 'nginx' && waf != 'apache' && waf != 'keepalived') { - url = "/logs/" + service + "/waf/" + serv + "/" + rows; - waf = 1; + if (waf && waf != 'haproxy' && waf != 'apache' && waf != 'keepalived') { + file = findGetParameter('file'); + url = "/logs/" + service + "/waf/" + serv + "/" + rows + '?file_from_get=' + file; } $.ajax( { url: url, @@ -300,6 +300,7 @@ function showCompareConfigs() { }); } function showConfig() { + let edit_section = ''; let service = $('#service').val(); let config_file = $('#config_file_name').val() let config_file_name = encodeURI(config_file); @@ -315,10 +316,16 @@ function showConfig() { } } clearAllAjaxFields(); + + if (service === 'haproxy') { + edit_section = findGetParameter('section'); + let edit_section_uri = '?section=' + edit_section; + } let json_data = { "serv": $("#serv").val(), "service": service, - "config_file_name": config_file_name + "config_file_name": config_file_name, + "edit_section": edit_section } $.ajax({ url: "/config/" + service + "/show", @@ -332,7 +339,7 @@ function showConfig() { toastr.clear(); $("#ajax").html(data.data); $.getScript(configShow); - window.history.pushState("Show config", "Show config", "/config/" + service + "/" + $("#serv").val() + "/show/" + config_file_name); + window.history.pushState("Show config", "Show config", "/config/" + service + "/" + $("#serv").val() + "/show/" + config_file_name + edit_section_uri); } } }); diff --git a/app/templates/ajax/config_show.html b/app/templates/ajax/config_show.html index eb40a3d2..5a0e814a 100644 --- a/app/templates/ajax/config_show.html +++ b/app/templates/ajax/config_show.html @@ -331,6 +331,16 @@