From 40703576af848ff6dace064bece4779243467bfe Mon Sep 17 00:00:00 2001 From: Aidaho Date: Wed, 14 May 2025 20:31:25 +0300 Subject: [PATCH] v8.2.0: Refactor and modularize Nginx proxy addition logic Redesigned Nginx proxy addition by removing unused templates, consolidating scripts, and introducing new modular template structures. Improvements include dynamic JavaScript handlers, enhanced UI flow, better SSL and upstream management, and new functionality for headers and location-specific configurations. Cleaned up obsolete code and adjusted database version updating logic. --- app/api/routes/routes.py | 5 + app/create_db.py | 69 +- app/modules/db/add.py | 74 +- app/modules/db/db_model.py | 30 +- app/modules/roxywi/class_models.py | 83 +- app/modules/server/server.py | 9 + app/modules/server/ssh_connection.py | 5 + app/modules/service/installation.py | 34 +- app/routes/add/routes.py | 97 +- .../haproxy_section/templates/section.j2 | 6 +- app/scripts/ansible/roles/nginx_section.yml | 9 + .../roles/nginx_section/tasks/main.yml | 19 + .../nginx_section/templates/proxy_pass.j2 | 170 ++++ .../roles/nginx_section/templates/upstream.j2 | 10 + .../ansible/roles/nginx_section/vars/main.yml | 72 ++ app/static/js/add.js | 487 ++-------- app/static/js/add_common.js | 110 +++ app/static/js/add_nginx.js | 342 ++++--- app/static/js/edit_config.js | 241 ++++- app/static/js/script.js | 116 ++- app/static/js/variables.js | 5 + app/templates/add.html | 53 +- app/templates/add_nginx.html | 77 +- app/templates/ajax/config_show.html | 11 + .../ajax/config_show_add_nginx_sections.html | 9 + app/templates/include/add/servers.html | 46 + .../include/add_nginx/add_nginx_proxy.html | 62 ++ .../{ => add_nginx}/add_nginx_servers.html | 8 +- .../include/add_nginx/proxy_pass.html | 123 +++ app/templates/include/add_nginx/upstream.html | 57 ++ app/templates/include/add_nginx_proxy.html | 32 - app/templates/languages/en.html | 5 + app/templates/languages/fr.html | 5 + app/templates/languages/pt-br.html | 5 + app/templates/languages/ru.html | 5 + app/views/service/haproxy_section_views.py | 10 +- app/views/service/nginx_section_views.py | 883 ++++++++++++++++++ 37 files changed, 2483 insertions(+), 901 deletions(-) create mode 100644 app/scripts/ansible/roles/nginx_section.yml create mode 100644 app/scripts/ansible/roles/nginx_section/tasks/main.yml create mode 100644 app/scripts/ansible/roles/nginx_section/templates/proxy_pass.j2 create mode 100644 app/scripts/ansible/roles/nginx_section/templates/upstream.j2 create mode 100644 app/scripts/ansible/roles/nginx_section/vars/main.yml create mode 100644 app/static/js/add_common.js create mode 100644 app/templates/ajax/config_show_add_nginx_sections.html create mode 100644 app/templates/include/add/servers.html create mode 100644 app/templates/include/add_nginx/add_nginx_proxy.html rename app/templates/include/{ => add_nginx}/add_nginx_servers.html (98%) create mode 100644 app/templates/include/add_nginx/proxy_pass.html create mode 100644 app/templates/include/add_nginx/upstream.html delete mode 100644 app/templates/include/add_nginx_proxy.html create mode 100644 app/views/service/nginx_section_views.py diff --git a/app/api/routes/routes.py b/app/api/routes/routes.py index cd18fe68..73d4f173 100644 --- a/app/api/routes/routes.py +++ b/app/api/routes/routes.py @@ -12,6 +12,7 @@ from app.views.service.views import (ServiceView, ServiceActionView, ServiceBack ServiceConfigVersionsView, ServiceConfigList) from app.views.service.haproxy_section_views import ListenSectionView, UserListSectionView, PeersSectionView, \ GlobalSectionView, DefaultsSectionView +from app.views.service.nginx_section_views import UpstreamSectionView, ProxyPassSectionView from app.views.service.lets_encrypt_views import LetsEncryptsView, LetsEncryptView from app.views.service.haproxy_lists_views import HaproxyListView from app.views.ha.views import HAView, HAVIPView, HAVIPsView @@ -78,6 +79,10 @@ register_api_id_ip(PeersSectionView, 'haproxy_peers_post', '/section/peers', met register_api_id_ip(PeersSectionView, 'haproxy_peers', '/section/peers/', methods=['GET', 'PUT', 'DELETE']) register_api_id_ip(GlobalSectionView, 'haproxy_global', '/section/global', methods=['GET', 'PUT']) register_api_id_ip(DefaultsSectionView, 'haproxy_defaults', '/section/defaults', methods=['GET', 'PUT']) +register_api_id_ip(UpstreamSectionView, 'nginx_upstream_post_p', '/section/upstream', methods=['POST']) +register_api_id_ip(UpstreamSectionView, 'nginx_upstream_post', '/section/upstream/', methods=['GET', 'PUT', 'DELETE']) +register_api_id_ip(ProxyPassSectionView, 'nginx_proxy_pass_post_p', '/section/proxy_pass', methods=['POST']) +register_api_id_ip(ProxyPassSectionView, 'nginx_proxy_pass_post', '/section/proxy_pass/', methods=['GET', 'PUT', 'DELETE']) bp.add_url_rule('/service//list//', view_func=HaproxyListView.as_view('list_get'), methods=['GET']) bp.add_url_rule('/service//list', view_func=HaproxyListView.as_view('list_post'), methods=['POST', 'PUT', 'DELETE']) bp.add_url_rule('/service///config/list', view_func=ServiceConfigList.as_view('config_list_ip'), methods=['GET']) diff --git a/app/create_db.py b/app/create_db.py index e37011a9..e56aa79b 100644 --- a/app/create_db.py +++ b/app/create_db.py @@ -465,70 +465,6 @@ def update_db_v_4_3_0(): print("An error occurred:", e) -def update_db_v_7_2_0(): - try: - if mysql_enable: - migrate( - migrator.add_column('smon_ping_check', 'interval', IntegerField(default=120)), - migrator.add_column('smon_http_check', 'interval', IntegerField(default=120)), - migrator.add_column('smon_tcp_check', 'interval', IntegerField(default=120)), - migrator.add_column('smon_dns_check', 'interval', IntegerField(default=120)), - migrator.add_column('smon_ping_check', 'agent_id', IntegerField(default=1)), - migrator.add_column('smon_http_check', 'agent_id', IntegerField(default=1)), - migrator.add_column('smon_tcp_check', 'agent_id', IntegerField(default=1)), - migrator.add_column('smon_dns_check', 'agent_id', IntegerField(default=1)) - ) - else: - migrate( - migrator.add_column('smon_ping_check', 'interval', IntegerField(constraints=[SQL('DEFAULT 120')])), - migrator.add_column('smon_http_check', 'interval', IntegerField(constraints=[SQL('DEFAULT 120')])), - migrator.add_column('smon_tcp_check', 'interval', IntegerField(constraints=[SQL('DEFAULT 120')])), - migrator.add_column('smon_dns_check', 'interval', IntegerField(constraints=[SQL('DEFAULT 120')])), - migrator.add_column('smon_ping_check', 'agent_id', IntegerField(constraints=[SQL('DEFAULT 1')])), - migrator.add_column('smon_http_check', 'agent_id', IntegerField(constraints=[SQL('DEFAULT 1')])), - migrator.add_column('smon_tcp_check', 'agent_id', IntegerField(constraints=[SQL('DEFAULT 1')])), - migrator.add_column('smon_dns_check', 'agent_id', IntegerField(constraints=[SQL('DEFAULT 1')])) - ) - except Exception as e: - if e.args[0] == 'duplicate column name: agent_id' or str(e) == '(1060, "Duplicate column name \'agent_id\'")': - print('Updating... DB has been updated to version 7.2.0') - elif e.args[0] == 'duplicate column name: interval' or str(e) == '(1060, "Duplicate column name \'interval\'")': - print('Updating... DB has been updated to version 7.2.0') - else: - print("An error occurred:", e) - - -def update_db_v_7_2_0_1(): - try: - Setting.delete().where(Setting.param == 'smon_check_interval').execute() - Setting.delete().where((Setting.param == 'smon_keep_history_range') & (Setting.section == 'monitoring')).execute() - Setting.delete().where((Setting.param == 'smon_ssl_expire_warning_alert') & (Setting.section == 'monitoring')).execute() - Setting.delete().where((Setting.param == 'smon_ssl_expire_critical_alert') & (Setting.section == 'monitoring')).execute() - except Exception as e: - print("An error occurred:", e) - else: - print("Updating... DB has been updated to version 7.2.0-1") - - -def update_db_v_7_2_3(): - try: - if mysql_enable: - migrate( - migrator.add_column('checker_setting', 'mm_id', IntegerField(default=0)), - migrator.add_column('smon', 'mm_channel_id', IntegerField(default=0)), - ) - else: - migrate( - migrator.add_column('checker_setting', 'mm_id', IntegerField(constraints=[SQL('DEFAULT 0')])), - migrator.add_column('smon', 'mm_channel_id', IntegerField(constraints=[SQL('DEFAULT 0')])), - ) - except Exception as e: - if e.args[0] == 'duplicate column name: mm_id' or str(e) == '(1060, "Duplicate column name \'mm_id\'")': - print('Updating... DB has been updated to version 7.2.3') - else: - print("An error occurred:", e) - - def update_db_v_7_3_1(): try: if mysql_enable: @@ -723,7 +659,7 @@ def update_db_v_8_1_6(): def update_ver(): try: - Version.update(version='8.1.8').execute() + Version.update(version='8.2.0').execute() except Exception: print('Cannot update version') @@ -741,9 +677,6 @@ def update_all(): if check_ver() is None: update_db_v_3_4_5_22() update_db_v_4_3_0() - update_db_v_7_2_0() - update_db_v_7_2_0_1() - update_db_v_7_2_3() update_db_v_7_3_1() update_db_v_7_4() update_db_v_8() diff --git a/app/modules/db/add.py b/app/modules/db/add.py index 34771170..9c1686fb 100644 --- a/app/modules/db/add.py +++ b/app/modules/db/add.py @@ -1,10 +1,14 @@ -from typing import Union +from typing import Union, Literal -from app.modules.db.db_model import SavedServer, Option, HaproxySection +from app.modules.db.db_model import SavedServer, Option, HaproxySection, NginxSection from app.modules.db.common import out_error -from app.modules.roxywi.class_models import HaproxyConfigRequest, HaproxyGlobalRequest, HaproxyDefaultsRequest +from app.modules.roxywi.class_models import HaproxyConfigRequest, HaproxyGlobalRequest, HaproxyDefaultsRequest, NginxUpstreamRequest from app.modules.roxywi.exception import RoxywiResourceNotFound +SectionModel = { + 'haproxy': HaproxySection, + 'nginx': NginxSection, +} def update_saved_server(server, description, saved_id): try: @@ -93,9 +97,16 @@ def select_saved_servers(**kwargs): return query_res -def insert_new_section(server_id: int, section_type: str, section_name: str, body: HaproxyConfigRequest): +def insert_new_section( + server_id: int, + section_type: str, + section_name: str, + body: Union[HaproxyConfigRequest, NginxUpstreamRequest], + service: Literal['haproxy', 'nginx'] = 'haproxy' +): + model = SectionModel[service] try: - return (HaproxySection.insert( + return (model.insert( server_id=server_id, type=section_type, name=section_name, @@ -105,7 +116,12 @@ def insert_new_section(server_id: int, section_type: str, section_name: str, bod out_error(e) -def insert_or_update_new_section(server_id: int, section_type: str, section_name: str, body: Union[HaproxyGlobalRequest, HaproxyDefaultsRequest]): +def insert_or_update_new_section( + server_id: int, + section_type: str, + section_name: str, + body: Union[HaproxyGlobalRequest, HaproxyDefaultsRequest] +): try: return (HaproxySection.insert( server_id=server_id, @@ -117,40 +133,54 @@ def insert_or_update_new_section(server_id: int, section_type: str, section_name out_error(e) -def update_section(server_id: int, section_type: str, section_name: str, body: HaproxyConfigRequest): +def update_section( + server_id: int, + section_type: str, + section_name: str, + body: Union[HaproxyConfigRequest, NginxUpstreamRequest], + service: Literal['haproxy', 'nginx'] = 'haproxy' +): + model = SectionModel[service] try: - HaproxySection.update( + model.update( config=body.model_dump(mode='json') ).where( - (HaproxySection.server_id == server_id) & (HaproxySection.type == section_type) & (HaproxySection.name == section_name) + (model.server_id == server_id) & (model.type == section_type) & (model.name == section_name) ).execute() - except HaproxySection.DoesNotExist: + except model.DoesNotExist: raise RoxywiResourceNotFound except Exception as e: out_error(e) -def get_section(server_id: int, section_type: str, section_name: str) -> HaproxySection: +def get_section( + server_id: int, + section_type: str, + section_name: str, + service: Literal['haproxy', 'nginx'] = 'haproxy' +) -> Union[HaproxySection, NginxSection]: + model = SectionModel[service] try: - return HaproxySection.get( - (HaproxySection.server_id == server_id) - & (HaproxySection.type == section_type) - & (HaproxySection.name == section_name) + return model.get( + (model.server_id == server_id) + & (model.type == section_type) + & (model.name == section_name) ) - except HaproxySection.DoesNotExist: + except model.DoesNotExist: raise RoxywiResourceNotFound except Exception as e: out_error(e) -def delete_section(server_id: int, section_type: str, section_name: str): +def delete_section(server_id: int, section_type: str, section_name: str, service: Literal['haproxy', 'nginx'] = 'haproxy') -> None: + model = SectionModel[service] try: - HaproxySection.delete().where( - (HaproxySection.server_id == server_id) - & (HaproxySection.type == section_type) - & (HaproxySection.name == section_name) + model.delete().where( + (model.server_id == server_id) + & (model.type == section_type) + & (model.name == section_name) ).execute() - except HaproxySection.DoesNotExist: + except model.DoesNotExist: raise RoxywiResourceNotFound except Exception as e: out_error(e) diff --git a/app/modules/db/db_model.py b/app/modules/db/db_model.py index cbf8656b..517e55d1 100644 --- a/app/modules/db/db_model.py +++ b/app/modules/db/db_model.py @@ -1,5 +1,6 @@ from datetime import datetime +from peewee import ForeignKeyField from playhouse.migrate import * from playhouse.shortcuts import ReconnectMixin from playhouse.sqlite_ext import SqliteExtDatabase @@ -775,6 +776,18 @@ class HaproxySection(BaseModel): constraints = [SQL('UNIQUE (server_id, type, name)')] +class NginxSection(BaseModel): + id = AutoField + server_id = ForeignKeyField(Server, on_delete='Cascade') + type = CharField() + name = CharField() + config = JSONField() + + class Meta: + table_name = 'nginx_sections' + constraints = [SQL('UNIQUE (server_id, type, name)')] + + class LetsEncrypt(BaseModel): id = AutoField server_id = ForeignKeyField(Server, null=True, on_delete='SET NULL') @@ -789,6 +802,21 @@ class LetsEncrypt(BaseModel): table_name = 'lets_encrypt' +class InstallationTasks(BaseModel): + id = AutoField + service_name = CharField() + status = CharField(default='created') + error = CharField(null=True) + start_date = DateTimeField(default=datetime.now) + finish_date = DateTimeField(default=datetime.now) + group_id = ForeignKeyField(Groups, null=True, on_delete='SET NULL') + user_id = ForeignKeyField(User, null=True, on_delete='SET NULL') + server_ids = JSONField(null=True) + + class Meta: + table_name = 'installation_tasks' + + def create_tables(): conn = connect() with conn: @@ -799,5 +827,5 @@ def create_tables(): NginxMetrics, SystemInfo, Services, UserName, GitSetting, CheckerSetting, ApacheMetrics, WafNginx, ServiceStatus, KeepaliveRestart, PD, SmonHistory, SmonAgent, SmonTcpCheck, SmonHttpCheck, SmonPingCheck, SmonDnsCheck, S3Backup, SmonStatusPage, SmonStatusPageCheck, HaCluster, HaClusterSlave, HaClusterVip, HaClusterVirt, HaClusterService, - HaClusterRouter, MM, UDPBalancer, HaproxySection, LetsEncrypt] + HaClusterRouter, MM, UDPBalancer, HaproxySection, LetsEncrypt, NginxSection, InstallationTasks] ) diff --git a/app/modules/roxywi/class_models.py b/app/modules/roxywi/class_models.py index 3774e6f4..3088c679 100644 --- a/app/modules/roxywi/class_models.py +++ b/app/modules/roxywi/class_models.py @@ -4,7 +4,8 @@ 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, Base64Str, StringConstraints, IPvAnyAddress, GetCoreSchemaHandler, AnyUrl, root_validator, EmailStr +from pydantic import BaseModel, Base64Str, StringConstraints, IPvAnyAddress, GetCoreSchemaHandler, AnyUrl, \ + root_validator, EmailStr, model_validator 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]$")] WildcardDomainName = 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]$")] @@ -547,3 +548,83 @@ class ListRequest(BaseModel): class IpRequest(BaseModel): ip: Union[IPvAnyAddress, DomainName] + + +class NginxBackendServer(BaseModel): + server: Union[IPvAnyAddress, DomainName] + port: Annotated[int, Gt(1), Le(65535)] + max_fails: int + fail_timeout: int + + +class NginxUpstreamRequest(BaseModel): + name: EscapedString + balance: Optional[Literal['ip_hash', 'least_conn', 'random', 'round_robin']] + keepalive: Optional[int] = 32 + backend_servers: List[NginxBackendServer] + type: Literal['upstream'] = 'upstream' + action: Optional[Literal['save', 'test', 'reload', 'restart']] = "save" + + @model_validator(mode='before') + @classmethod + def backend_server_cannot_be_empty(cls, values): + if 'backend_servers' in values: + if len(values['backend_servers']) == 0: + raise ValueError('Backend servers cannot be empty') + return values + + +class NginxHeaders(BaseModel): + path: Literal['proxy-header', 'header'] + name: str + method: Literal['add', 'hide'] + value: Optional[str] = None + + +class NginxHeaderRequest(BaseModel): + action: Optional[Literal['add_header', 'proxy_set_header', 'proxy_hide_header']] = None + name: Optional[str] = None + value: Optional[str] = None + + +class NginxLocationRequest(BaseModel): + location: str = '/' + proxy_connect_timeout: Optional[int] = 60 + proxy_read_timeout: Optional[int] = 60 + proxy_send_timeout: Optional[int] = 60 + headers: Optional[List[NginxHeaderRequest]] = None + upstream: str + + @model_validator(mode='before') + @classmethod + def location_cannot_be_empty(cls, values): + if 'location' in values: + if values['location'] == '': + raise ValueError('Location cannot be empty') + if not values['location'].startswith('/'): + raise ValueError('Location must start with /') + return values + + @model_validator(mode='before') + @classmethod + def upstream_cannot_be_empty(cls, values): + if 'upstream' in values: + if values['upstream'] == '': + raise ValueError('Upstream cannot be empty') + return values + + +class NginxProxyPassRequest(BaseModel): + locations: List[NginxLocationRequest] + name: EscapedString + port: Annotated[int, Gt(1), Le(65535)] + type: Literal['proxy_pass'] = 'proxy_pass' + scheme: Literal['http', 'https'] = 'http' + ssl_crt: Optional[str] = None + ssl_key: Optional[str] = None + ssl_offloading: Optional[bool] = False + action: Optional[Literal['save', 'test', 'reload', 'restart']] = 'save' + compression: bool = False + compression_level: Annotated[int, Gt(0), Le(10)] = 6 + compression_min_length: Optional[int] = 1024 + compression_types: Optional[str] = 'text/plain text/css application/json application/javascript text/xml' diff --git a/app/modules/server/server.py b/app/modules/server/server.py index 2dc75ab7..e1dd0764 100644 --- a/app/modules/server/server.py +++ b/app/modules/server/server.py @@ -108,6 +108,15 @@ def get_remote_files(server_ip: str, config_dir: str, file_format: str): return config_files +def get_remote_upstream_files(server_ip: str): + service_config_dir = sql.get_setting('nginx_dir') + config_dir = common.return_nice_path(service_config_dir) + command = f'sudo ls {config_dir}/conf.d/upstream*.conf|awk -F"/" \'{{print $NF}}\'' + config_files = ssh_command(server_ip, command) + config_files = config_files.replace('upstream_', '').replace('.conf', '') + return config_files + + def get_system_info(server_ip: str) -> None: if server_ip == '': raise Exception('IP cannot be empty') diff --git a/app/modules/server/ssh_connection.py b/app/modules/server/ssh_connection.py index c4b2930f..32a9e007 100644 --- a/app/modules/server/ssh_connection.py +++ b/app/modules/server/ssh_connection.py @@ -99,6 +99,11 @@ class SshConnection: except Exception as e: raise paramiko.SSHException(str(e)) + def remove_sftp(self, full_path): + sftp = self.ssh.open_sftp() + sftp.remove(full_path) + sftp.close() + def generate(self, command): with self as ssh_something: stdin, stdout, stderr = ssh_something.ssh.exec_command(command) diff --git a/app/modules/service/installation.py b/app/modules/service/installation.py index 0914a727..387cce4f 100644 --- a/app/modules/service/installation.py +++ b/app/modules/service/installation.py @@ -1,7 +1,7 @@ import os import json import random -from typing import Union +from typing import Union, Literal from packaging import version import ansible @@ -160,14 +160,14 @@ def generate_haproxy_inv(json_data: ServiceInstall, installed_service: str) -> o return inv, server_ips -def generate_haproxy_section_inv(json_data: dict, cfg: str) -> dict: +def generate_section_inv(json_data: dict, cfg: str, service: Literal['haproxy', 'nginx']) -> dict: cert_path = sql.get_setting('cert_path') - haproxy_dir = sql.get_setting('haproxy_dir') + service_dir = sql.get_setting(f'{service}_dir') inv = {"server": {"hosts": {}}} inv['server']['hosts']['localhost'] = { "config": json_data, "cert_path": cert_path, - "haproxy_dir": haproxy_dir, + "service_dir": service_dir, "cfg": cfg, "action": 'create' } @@ -175,7 +175,7 @@ def generate_haproxy_section_inv(json_data: dict, cfg: str) -> dict: return inv -def generate_haproxy_section_inv_for_del(cfg: str, section_type: str, section_name: str) -> dict: +def generate_section_inv_for_del(cfg: str, section_type: str, section_name: str) -> dict: config = {'type': section_type, 'name': section_name} inv = {"server": {"hosts": {}}} inv['server']['hosts']['localhost'] = { @@ -306,18 +306,18 @@ def run_ansible(inv: dict, server_ips: list, ansible_role: str) -> dict: invent.write(str(inv)) except Exception as e: server_mod.stop_ssh_agent(agent_pid) - roxywi_common.handle_exceptions(e, 'Roxy-WI server', 'Cannot save inventory file', roxywi=1) + roxywi_common.handle_exceptions(e, 'Roxy-WI server', 'Cannot save inventory file') try: result = ansible_runner.run(**kwargs) except Exception as e: server_mod.stop_ssh_agent(agent_pid) - roxywi_common.handle_exceptions(e, 'Roxy-WI server', 'Cannot run Ansible', roxywi=1) + roxywi_common.handle_exceptions(e, 'Roxy-WI server', 'Cannot run Ansible') try: server_mod.stop_ssh_agent(agent_pid) except Exception as e: - roxywi_common.logging('Roxy-WI server', f'error: Cannot stop SSH agent {e}', roxywi=1) + roxywi_common.logging('Roxy-WI server', f'error: Cannot stop SSH agent {e}') os.remove(inventory) @@ -366,12 +366,12 @@ def run_ansible_locally(inv: dict, ansible_role: str) -> dict: with open(inventory, 'a') as invent: invent.write(str(inv)) except Exception as e: - roxywi_common.handle_exceptions(e, 'Roxy-WI server', 'Cannot save inventory file', roxywi=1) + roxywi_common.handle_exceptions(e, 'Roxy-WI server', 'Cannot save inventory file') try: result = ansible_runner.run(**kwargs) except Exception as e: - roxywi_common.handle_exceptions(e, 'Roxy-WI server', 'Cannot run Ansible', roxywi=1) + roxywi_common.handle_exceptions(e, 'Roxy-WI server', 'Cannot run Ansible') os.remove(inventory) @@ -395,14 +395,14 @@ def service_actions_after_install(server_ips: str, service: str, json_data) -> N try: update_functions[service](server_ip) except Exception as e: - roxywi_common.handle_exceptions(e, 'Roxy-WI server', f'Cannot activate {service} on server {server_ip}', roxywi=1) + roxywi_common.handle_exceptions(e, 'Roxy-WI server', f'Cannot activate {service} on server {server_ip}') if service != 'keepalived': is_docker = json_data['services'][service]['docker'] - - if is_docker and service != 'keepalived': - service_sql.insert_or_update_service_setting(server_id, service, 'dockerized', '1') service_sql.insert_or_update_service_setting(server_id, service, 'restart', '1') - + if is_docker: + service_sql.insert_or_update_service_setting(server_id, service, 'dockerized', '1') + else: + service_sql.insert_or_update_service_setting(server_id, service, 'dockerized', '0') if service == 'haproxy': try: _create_default_config_in_db(server_id) @@ -476,8 +476,8 @@ def _install_ansible_collections(): except Exception as e: roxywi_common.handle_exceptions(e, 'Roxy-WI server', - f'Cannot install as collection. {trouble_link}', - roxywi=1) + f'Cannot install as collection. {trouble_link}' + ) else: if exit_code != 0: raise Exception(f'error: Ansible collection installation was not successful: {exit_code}. {trouble_link}') diff --git a/app/routes/add/routes.py b/app/routes/add/routes.py index 0f207288..236680f9 100644 --- a/app/routes/add/routes.py +++ b/app/routes/add/routes.py @@ -12,12 +12,14 @@ import app.modules.db.add as add_sql import app.modules.db.server as server_sql from app.middleware import check_services, get_user_params import app.modules.config.add as add_mod +import app.modules.server.server as server_mod import app.modules.common.common as common import app.modules.roxywi.auth as roxywi_auth import app.modules.roxywi.common as roxywi_common import app.modules.roxy_wi_tools as roxy_wi_tools from app.views.service.haproxy_section_views import (GlobalSectionView, DefaultsSectionView, ListenSectionView, UserListSectionView, PeersSectionView) +from app.views.service.nginx_section_views import UpstreamSectionView, ProxyPassSectionView from app.views.service.haproxy_lists_views import HaproxyListView get_config = roxy_wi_tools.GetConfigVar() @@ -40,6 +42,10 @@ register_api_id_ip(GlobalSectionView, 'haproxy_global_a', '/section/global', met register_api_id_ip(DefaultsSectionView, 'haproxy_defaults_a', '/section/defaults', methods=['GET', 'PUT']) bp.add_url_rule('//list//', view_func=HaproxyListView.as_view('list_get'), methods=['GET']) bp.add_url_rule('//list', view_func=HaproxyListView.as_view('list_post'), methods=['POST', 'DELETE']) +register_api_id_ip(UpstreamSectionView, 'nginx_section_upstream_post_a', '/section/upstream', methods=['POST']) +register_api_id_ip(UpstreamSectionView, 'nginx_section_upstream_post', '/section/upstream/', methods=['GET', 'PUT', 'DELETE']) +register_api_id_ip(ProxyPassSectionView, 'nginx_section_proxy_pass_post_a', '/section/proxy_pass', methods=['POST']) +register_api_id_ip(ProxyPassSectionView, 'nginx_section_proxy_pass_post', '/section/proxy_pass/', methods=['GET', 'PUT', 'DELETE']) @bp.before_request @@ -58,13 +64,15 @@ def add(service): :param service: Service name for service in what will be add :return: Template with Add page or redirect to the index if no needed permission """ + user_subscription = roxywi_common.return_user_subscription() roxywi_auth.page_for_admin(level=3) kwargs = { - 'h2': 1, 'add': request.form.get('add'), 'conf_add': request.form.get('conf'), 'lang': g.user_params['lang'], - 'all_servers': roxywi_common.get_dick_permit() + 'all_servers': roxywi_common.get_dick_permit(), + 'user_subscription': user_subscription, + 'saved_servers': add_sql.select_saved_servers() } if service == 'haproxy': @@ -74,18 +82,13 @@ def add(service): list_dir = lib_path + "/lists" white_dir = lib_path + "/lists/" + user_group + "/white" black_dir = lib_path + "/lists/" + user_group + "/black" + dirs = (list_dir, white_dir, black_dir) - if not os.path.exists(list_dir): - os.makedirs(list_dir) - if not os.path.exists(list_dir + "/" + user_group): - os.makedirs(list_dir + "/" + user_group) - if not os.path.exists(white_dir): - os.makedirs(white_dir) - if not os.path.exists(black_dir): - os.makedirs(black_dir) + for dir_to_create in dirs: + if not os.path.exists(dir_to_create): + os.makedirs(dir_to_create) kwargs.setdefault('options', add_sql.select_options()) - kwargs.setdefault('saved_servers', add_sql.select_saved_servers()) kwargs.setdefault('white_lists', roxywi_common.get_files(folder=white_dir, file_format="lst")) kwargs.setdefault('black_lists', roxywi_common.get_files(folder=black_dir, file_format="lst")) kwargs.setdefault('maps', roxywi_common.get_files(folder=f'{lib_path}/maps/{user_group}', file_format="map")) @@ -99,11 +102,18 @@ def add(service): @bp.route('/haproxy/get_section_html') @get_user_params() -def get_section_html(): +def get_haproxy_section_html(): lang = g.user_params['lang'] return render_template('ajax/config_show_add_sections.html', lang=lang) +@bp.route('/nginx/get_section_html') +@get_user_params() +def get_nginx_section_html(): + lang = g.user_params['lang'] + return render_template('ajax/config_show_add_nginx_sections.html', lang=lang) + + @bp.route('/haproxy/bwlists//') @validate() def get_bwlists(color: Literal['black', 'white'], group): @@ -190,7 +200,8 @@ def delete_saved_server(server_id): @bp.route('/certs/') def get_certs(server_id: int): server_ip = server_sql.get_server(server_id).ip - return add_mod.get_ssl_certs(server_ip) + cert_type = request.args.get('cert_type') + return add_mod.get_ssl_certs(server_ip, cert_type) @bp.route('/cert//', methods=['DELETE', 'GET']) @@ -207,7 +218,7 @@ def get_cert(server_id: int, cert_id: EscapedString): @validate(body=SSLCertUploadRequest) def upload_cert(body: SSLCertUploadRequest): try: - data = add_mod.upload_ssl_cert(body.server_ip, body.name, body.cert.replace("'", "")) + data = add_mod.upload_ssl_cert(body.server_ip, body.name, body.cert.replace("'", ""), body.cert_type) return jsonify(data), 201 except Exception as e: return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot upload SSL certificate') @@ -243,58 +254,8 @@ def create_map(): return add_mod.edit_map(map_name, group) -@bp.post('/nginx/upstream') +@bp.route('/get/upstreams/') @get_user_params() -def add_nginx_upstream(): - roxywi_auth.page_for_admin(level=3) - - server_ip = common.is_ip_or_dns(request.form.get('serv')) - new_upstream = request.form.get('upstream') - balance = request.form.get("balance") - config_add = '' - servers_split = '' - generate = request.form.get('generateconfig') - - if balance == 'round_robin' or balance is None: - balance = '' - else: - balance = f' {balance};\n' - - if new_upstream != '': - config_add = f'upstream {new_upstream} {{\n' - config_add += balance - config_name = f'upstream_{new_upstream}' - - if request.form.get('keepalive') != '': - config_add += f' keepalive {request.form.get("keepalive")};\n' - - if request.form.get('servers') is not None: - servers = request.form.getlist('servers') - server_port = request.form.getlist('server_port') - max_fails = request.form.getlist('max_fails') - fail_timeout = request.form.getlist('fail_timeout') - i = 0 - for server in servers: - if server == '': - continue - try: - max_fails_val = f'max_fails={max_fails[i]}' - except Exception: - max_fails_val = 'max_fails=1' - - try: - fail_timeout_val = f'fail_timeout={fail_timeout[i]}' - except Exception: - fail_timeout_val = 'fail_timeout=1' - - servers_split += f" server {server}:{server_port[i]} {max_fails_val} {fail_timeout_val}s; \n" - i += 1 - config_add += f'{servers_split} }}\n' - - if generate: - return config_add - else: - try: - return add_mod.save_nginx_config(config_add, server_ip, config_name) - except Exception as e: - return str(e) +def get_upstreams(server_id: int): + server_ip = server_sql.get_server(server_id).ip + return server_mod.get_remote_upstream_files(server_ip) diff --git a/app/scripts/ansible/roles/haproxy_section/templates/section.j2 b/app/scripts/ansible/roles/haproxy_section/templates/section.j2 index f3e27415..4bf5eaf9 100644 --- a/app/scripts/ansible/roles/haproxy_section/templates/section.j2 +++ b/app/scripts/ansible/roles/haproxy_section/templates/section.j2 @@ -27,12 +27,12 @@ {% endif %} {% if config.whitelist and config.whitelist != 'None' -%} - acl white_list_{{ config.whitelist }} src -f {{ haproxy_dir }}/white/{{ config.whitelist }} + acl white_list_{{ config.whitelist }} src -f {{ service_dir }}/white/{{ config.whitelist }} tcp-request content accept if white_list_{{ config.whitelist }} tcp-request content reject {% endif %} {% if config.blacklist and config.blacklist != 'None' -%} - tcp-request connection reject if { src -f {{ haproxy_dir }}/black/{{ config.blacklist }} } + tcp-request connection reject if { src -f {{ service_dir }}/black/{{ config.blacklist }} } {% endif %} {% if config.acls != 'None' -%} @@ -81,7 +81,7 @@ {% endif -%} {% if config.waf -%} - filter spoe engine modsecurity config {{ haproxy_dir }}/waf.conf + filter spoe engine modsecurity config {{ service_dir }}/waf.conf http-request deny if { var(txn.modsec.code) -m int gt 0 } {% endif -%} diff --git a/app/scripts/ansible/roles/nginx_section.yml b/app/scripts/ansible/roles/nginx_section.yml new file mode 100644 index 00000000..61ed8a40 --- /dev/null +++ b/app/scripts/ansible/roles/nginx_section.yml @@ -0,0 +1,9 @@ +--- +- name: Create NGINX section + hosts: localhost + connection: local + become: yes + become_method: sudo + gather_facts: yes + roles: + - role: nginx_section diff --git a/app/scripts/ansible/roles/nginx_section/tasks/main.yml b/app/scripts/ansible/roles/nginx_section/tasks/main.yml new file mode 100644 index 00000000..44416624 --- /dev/null +++ b/app/scripts/ansible/roles/nginx_section/tasks/main.yml @@ -0,0 +1,19 @@ +--- +- name: Create section + when: action == 'create' + block: + + - name: Generate Nginx config + template: + src: proxy_pass.j2 + dest: "{{ cfg }}" + owner: root + group: root + mode: '0644' + when: config.type == 'proxy_pass' + + - name: Generate upstream config + template: + src: upstream.j2 + dest: "{{ cfg }}" + when: config.type == 'upstream' diff --git a/app/scripts/ansible/roles/nginx_section/templates/proxy_pass.j2 b/app/scripts/ansible/roles/nginx_section/templates/proxy_pass.j2 new file mode 100644 index 00000000..249786f6 --- /dev/null +++ b/app/scripts/ansible/roles/nginx_section/templates/proxy_pass.j2 @@ -0,0 +1,170 @@ +# Roxy-WI MANAGED do not edit it directly +{% if config.scheme == 'https' %} +server { + listen {{ config.port }} ssl{% if nginx_proxy.http2 %} http2{% endif %}; + ssl_certificate {{ config.ssl_crt }}; + ssl_certificate_key {{ config.ssl_key }}; + {% if nginx_proxy.security.hsts %} +add_header Strict-Transport-Security "max-age={{ nginx_proxy.security.hsts_max_age }}; includeSubDomains" always; + {% endif %} +{% else %} +server { + listen {{ config.port }}; +{% endif %} + + server_name {{ config.name }}; + + access_log /var/log/nginx/{{ config.name }}_access.log main buffer=16k flush=1m; + error_log /var/log/nginx/{{ config.name }}_error.log; + + {% if nginx_proxy.access_control.global_whitelist.enabled %} + # ACCESS CONTROL: Global rules + allow {% for ip in nginx_proxy.access_control.global_whitelist.ips %}{{ ip }} {% endfor %}; + + deny all; + {% elif nginx_proxy.access_control.global_blacklist.enabled %} + # ACCESS CONTROL: Global rules + deny {% for ip in nginx_proxy.access_control.global_blacklist.ips %}{{ ip }}; {% endfor %} + allow all; + {% endif -%} + + + {% if nginx_proxy.security.hide_server_tokens %} +server_tokens off; + {% endif %} + + {% if nginx_proxy.security.security_headers %} +# Security headers + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Content-Security-Policy "{{ nginx_proxy.security.content_security_policy }}" always; + {% endif %} + + {% if nginx_proxy.rate_limit.enabled %} + # Rate limiting + limit_req_zone $binary_remote_addr zone={{ nginx_proxy.rate_limit.zone.split()[0] }}:{{ nginx_proxy.rate_limit.zone.split()[1] }}; + {% endif %} + + # Proxy rules + {% for location in config.locations %} +location {{ location.location }} { + {% if nginx_proxy.rate_limit.enabled %} + limit_req zone={{ nginx_proxy.rate_limit.zone.split()[0] }} burst={{ nginx_proxy.rate_limit.burst }}; + {% endif %} + + {%- if nginx_proxy.access_control.location_whitelist.enabled %} + # ACCESS CONTROL: Location-specific whitelist + {% set path = '/' %} + {% if path in nginx_proxy.access_control.location_whitelist.apply_to or nginx_proxy.access_control.location_whitelist.apply_to | length == 0 %} + {% for ip in nginx_proxy.access_control.location_whitelist.ips %}allow {{ ip }}; {% endfor %} + deny all; + {% endif %} + {% endif %} + + proxy_pass http://{{ location.upstream }}; + + # Headers + {% for header in location.headers -%} + {{ header.action }} {{ header.name }} {{ header.value }}; + {% endfor -%} + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Timeouts + proxy_connect_timeout {{ location.proxy_connect_timeout }}; + proxy_read_timeout {{ location.proxy_read_timeout }}; + proxy_send_timeout {{ location.proxy_send_timeout }}; + + {% if nginx_proxy.websocket.enabled %} + # WebSocket + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + {% endif %} + + {% if nginx_proxy.caching.enabled %} + # Caching + proxy_cache {{ nginx_proxy.caching.zones[0].name }}; + proxy_cache_key "$scheme$request_method$host$request_uri"; + proxy_cache_valid 200 302 10m; + proxy_cache_valid 404 1m; + add_header X-Proxy-Cache $upstream_cache_status; + {% endif %} +} + {% endfor %} + {% if nginx_proxy.static_files.enabled %} + # Static files + location {{ nginx_proxy.static_files.url_path }} { + # ACCESS CONTROL: Location-specific whitelist + {% if nginx_proxy.access_control.location_whitelist.enabled %} + {% set path = nginx_proxy.static_files.url_path %} + {% if path in nginx_proxy.access_control.location_whitelist.apply_to or nginx_proxy.access_control.location_whitelist.apply_to | length == 0 %} + allow {% for ip in nginx_proxy.access_control.location_whitelist.ips %}{{ ip }}; {% endfor %} + deny all; + {% endif %} + {% endif %} + + alias {{ nginx_proxy.static_files.path }}/; + expires 30d; + access_log off; + } + {% endif %} + + {% if nginx_proxy.error_pages.enabled %} + # Custom error pages + {% for code in nginx_proxy.error_pages.codes %} + error_page {{ code }} /error_{{ code }}.html; + {% endfor %} + + location ~ ^/error_ { + internal; + root {{ nginx_proxy.error_pages.path }}; + } + {% endif %} + + {% if config.compression %} +# Gzip compression + gzip on; + gzip_types {{ config.compression_types }}; + gzip_min_length {{ config.compression_min_length }}; + gzip_comp_level {{ config.compression_level }}; + gzip_proxied any; + {% endif %} + + {% if nginx_proxy.security.hide_backend_headers %} +# Hide backend headers + proxy_hide_header X-Powered-By; + proxy_hide_header Server; + {% endif -%} +} + +{% if nginx_proxy.caching.enabled %} +# Cache zones +proxy_cache_path {{ nginx_proxy.caching.zones[0].path }} + levels={{ nginx_proxy.caching.zones[0].levels }} + keys_zone={{ nginx_proxy.caching.zones[0].name }}:{{ nginx_proxy.caching.zones[0].size }} + inactive={{ nginx_proxy.caching.zones[0].inactive }}; +{% endif %} + +{% if config.ssl_offloading -%} +# HTTP to HTTPS redirect +server { + listen 80; + server_name {{ config.name }}; + + {% if nginx_proxy.access_control.global_whitelist.enabled %} +# ACCESS CONTROL: Apply global rules to redirect server + allow {% for ip in nginx_proxy.access_control.global_whitelist.ips %}{{ ip }}; {% endfor %} + deny all; + {% elif nginx_proxy.access_control.global_blacklist.enabled %} + deny {% for ip in nginx_proxy.access_control.global_blacklist.ips %}{{ ip }}; {% endfor %} + allow all; + {% endif %} + + return 301 https://$host$request_uri; +} +{% endif %} diff --git a/app/scripts/ansible/roles/nginx_section/templates/upstream.j2 b/app/scripts/ansible/roles/nginx_section/templates/upstream.j2 new file mode 100644 index 00000000..e426c10f --- /dev/null +++ b/app/scripts/ansible/roles/nginx_section/templates/upstream.j2 @@ -0,0 +1,10 @@ +# Roxy-WI MANAGED do not edit it directly +upstream {{ config.name }} { + keepalive {{ config.keepalive }}; + {% if config.balance != 'round_robin' -%} + {{ config.balance }}; + {% endif -%} + {% for server in config.backend_servers %} +server {{ server.server }}:{{ server.port }} max_fails={{ server.max_fails }} fail_timeout={{ server.fail_timeout }}; + {% endfor %} +} diff --git a/app/scripts/ansible/roles/nginx_section/vars/main.yml b/app/scripts/ansible/roles/nginx_section/vars/main.yml new file mode 100644 index 00000000..c97b8910 --- /dev/null +++ b/app/scripts/ansible/roles/nginx_section/vars/main.yml @@ -0,0 +1,72 @@ +--- +# Main server configuration +nginx_proxy: + http2: false # Enable HTTP/2 (requires SSL) + + # Static files configuration + static_files: + enabled: false # Enable static file handling + path: "/var/www/static" # Physical path to static files + url_path: "/static/" # URL prefix for static files + + # Security configurations + security: + hide_server_tokens: true # Hide Nginx version in headers + hide_backend_headers: true # Hide backend server headers (e.g., X-Powered-By) + security_headers: true # Enable security headers (X-Content-Type-Options, etc.) + hsts: true # Enable HTTP Strict Transport Security + hsts_max_age: 15768000 # HSTS duration in seconds (6 months) + content_security_policy: "default-src 'self'" # Content Security Policy rules + + # Rate limiting configuration + rate_limit: + enabled: false # Enable rate limiting + zone: "ratelimit 10m rate=100r/s" # Rate limit zone (10MB memory, 100 req/sec) + burst: 50 # Maximum burst capacity + + # Caching configuration + caching: + enabled: false # Enable response caching + zones: + - name: "api_cache" # Cache zone name + path: "/var/cache/nginx/api" # Disk path for cache + size: "10m" # Memory zone size + inactive: "60m" # Cache retention time for inactive entries + levels: "1:2" # Directory structure levels + + websocket: + enabled: false + + # Custom error pages + error_pages: + enabled: false # Enable custom error pages + codes: [502, 503, 504] # HTTP status codes to handle + path: "/var/www/errors" # Directory containing error HTML files + + # IP access control + access_control: + global_whitelist: + enabled: false # Global allow-list mode + ips: ["192.168.1.0/24", "127.0.0.1"] # Allowed IPs/CIDR ranges + global_blacklist: + enabled: false # Global deny-list mode + ips: ["203.0.113.0/24"] # Blocked IPs/CIDR ranges + location_whitelist: + enabled: false # Path-specific allow-list + ips: ["172.16.0.0/16"] # Allowed IPs for specific paths + apply_to: ["/admin", "/api"] # Protected paths + + + # TCP/UDP proxy configuration + tcp_proxies: + - name: "mysql_proxy" # Proxy name + enabled: false # Enable this proxy + listen_port: 3306 # Listening port + protocol: "tcp" # Protocol (tcp/udp) + upstream_servers: + - { ip: "10.0.1.1", port: 3306, weight: 3 } # Backend servers + ssl_enabled: true # Enable SSL termination + ssl_cert: "/etc/ssl/certs/mysql.crt" # SSL certificate + ssl_key: "/etc/ssl/private/mysql.key" # SSL private key + proxy_timeout: 3600s # Connection timeout + proxy_connect_timeout: 60s # Backend connect timeout diff --git a/app/static/js/add.js b/app/static/js/add.js index f151d2a4..e1f05ba7 100644 --- a/app/static/js/add.js +++ b/app/static/js/add.js @@ -1,23 +1,11 @@ -window.onload = function() { - var cur_url = window.location.href.split('/').pop(); - let activeTabIdx = $('#tabs').tabs('option','active'); - if (cur_url.split('#')[1] === 'ssl') { - if (activeTabIdx === 4) { - getLes(); - } - } -} $( function() { $("#tabs ul li").click(function () { - let activeTab = $(this).find("a").attr("href"); - let activeTabClass = activeTab.replace('#', ''); - $('.menu li ul li').each(function () { - activeSubMenu($(this), activeTabClass) - }); - if (activeTab === '#ssl') { - getLes(); - } - }); + let activeTab = $(this).find("a").attr("href"); + let activeTabClass = activeTab.replace('#', ''); + $('.menu li ul li').each(function () { + activeSubMenu($(this), activeTabClass) + }); + }); $("#listen-mode-select").on('selectmenuchange', function () { if ($("#listen-mode-select option:selected").val() === "tcp") { $("#https-listen-span").hide("fast"); @@ -325,52 +313,6 @@ $( function() { } }); }); - $("#servers_table input").change(function () { - let id = $(this).attr('id').split('-'); - updateSavedServer(id[2]) - - }); - $('[name=servers]').autocomplete({ - source: "/add/server/get/" + $('#group_id').val(), - autoFocus: true, - minLength: 1, - select: function (event, ui) { - $(this).append(ui.item.value + " "); - $(this).next().focus(); - } - }) - .autocomplete("instance")._renderItem = function (ul, item) { - return $("
  • ") - .append("
    " + item.value + "
    " + item.desc + "
    ") - .appendTo(ul); - }; - $('#add-saved-server-button').click(function () { - if ($('#saved-server-add-table').css('display', 'none')) { - $('#saved-server-add-table').show("blind", "fast"); - } - }); - $('#add-saved-server-new').click(function () { - $.ajax({ - url: "/add/server", - data: JSON.stringify({ - server: $('#new-saved-servers').val(), - description: $('#new-saved-servers-description').val() - }), - type: "POST", - contentType: "application/json; charset=utf-8", - success: function (data) { - if (data.status === 'failed') { - toastr.error(data); - } else { - $("#servers_table").append(data.data); - setTimeout(function () { - $(".newsavedserver").removeClass("update"); - }, 2500); - $.getScript(overview); - } - } - }); - }); $(":regex(id, template)").click(function () { if ($(':regex(id, template)').is(':checked')) { $(".prefix").show("fast"); @@ -394,181 +336,79 @@ $( function() { $("[name=port_check_text]").show("fast"); } }); - let cur_url = window.location.href.split('/').pop(); - cur_url = cur_url.split('/'); - if (cur_url[0] == "add") { - $("#cache").checkboxradio("disable"); - $("#waf").checkboxradio("disable"); - $("#serv").on('selectmenuchange', function () { - change_select_acceleration(""); - change_select_waf(""); - }); - $("#cache2").checkboxradio("disable"); - $("#waf2").checkboxradio("disable"); - $("#serv2").on('selectmenuchange', function () { - change_select_acceleration("2"); - change_select_waf("2"); - }); - $("#cache3").checkboxradio("disable"); - $("#serv3").on('selectmenuchange', function () { - change_select_acceleration("3"); - }); - $('#compression').on("click", function () { - if ($('#compression').is(':checked')) { - $("#cache").checkboxradio("disable"); - $("#cache").prop('checked', false); - } else { - change_select_acceleration(""); - } - }); - $('#compression2').on("click", function () { - if ($('#compression2').is(':checked')) { - $("#cache2").checkboxradio("disable"); - $("#cache2").prop('checked', false); - } else { - change_select_acceleration('2'); - } - }); - $('#compression3').on("click", function () { - if ($('#compression3').is(':checked')) { - $("#cache3").checkboxradio("disable"); - $("#cache3").prop('checked', false); - } else { - change_select_acceleration('3'); - } - }); - $('#cache').on("click", function () { - if ($('#cache').is(':checked')) { - $("#compression").checkboxradio("disable"); - $("#compression").prop('checked', false); - } else { - $("#compression").checkboxradio("enable"); - } - }); - $('#cache2').on("click", function () { - if ($('#cache2').is(':checked')) { - $("#compression2").checkboxradio("disable"); - $("#compression2").prop('checked', false); - } else { - $("#compression2").checkboxradio("enable"); - } - }); - $('#cache3').on("click", function () { - if ($('#cache3').is(':checked')) { - $("#compression3").checkboxradio("disable"); - $("#compression3").prop('checked', false); - } else { - $("#compression3").checkboxradio("enable"); - } - }); - $("#add1").on("click", function () { - $('.menu li ul li').each(function () { - $(this).find('a').css('padding-left', '20px') - $(this).find('a').css('border-left', '0px solid #5D9CEB'); - $(this).find('a').css('background-color', '#48505A'); - $(this).children("#add1").css('padding-left', '30px'); - $(this).children("#add1").css('border-left', '4px solid #5D9CEB'); - $(this).children("#add1").css('background-color', 'var(--right-menu-blue-rolor)'); - }); - $("#tabs").tabs("option", "active", 0); - }); - $("#add3").on("click", function () { - $('.menu li ul li').each(function () { - $(this).find('a').css('padding-left', '20px') - $(this).find('a').css('border-left', '0px solid #5D9CEB'); - $(this).find('a').css('background-color', '#48505A'); - $(this).children("#add3").css('padding-left', '30px'); - $(this).children("#add3").css('border-left', '4px solid #5D9CEB'); - $(this).children("#add3").css('background-color', 'var(--right-menu-blue-rolor)'); - }); - getLes(); - $("#tabs").tabs("option", "active", 4); - }); - $("#add4").on("click", function () { - $("#tabs").tabs("option", "active", 5); - }); - $("#add5").on("click", function () { - $("#tabs").tabs("option", "active", 6); - }); - $("#add6").on("click", function () { - $("#tabs").tabs("option", "active", 7); - $("#userlist_serv").selectmenu("open"); - }); - $("#add7").on("click", function () { - $('.menu li ul li').each(function () { - $(this).find('a').css('padding-left', '20px') - $(this).find('a').css('border-left', '0px solid #5D9CEB'); - $(this).find('a').css('background-color', '#48505A'); - $(this).children("#add7").css('padding-left', '30px'); - $(this).children("#add7").css('border-left', '4px solid #5D9CEB'); - $(this).children("#add7").css('background-color', 'var(--right-menu-blue-rolor)'); - }); - $("#tabs").tabs("option", "active", 9); - }); - } - $("#ssl_key_upload").click(function () { - if (!checkIsServerFiled('#serv4')) return false; - if (!checkIsServerFiled('#ssl_name', 'Enter the Certificate name')) return false; - if (!checkIsServerFiled('#ssl_cert', 'Paste the contents of the certificate file')) return false; - let jsonData = { - server_ip: $('#serv4').val(), - cert: $('#ssl_cert').val(), - name: $('#ssl_name').val() - } - $.ajax({ - url: "/add/cert/add", - data: JSON.stringify(jsonData), - contentType: "application/json; charset=utf-8", - type: "POST", - success: function (data) { - if (data.error === 'failed') { - toastr.error(data.error); - } else { - for (let i = 0; i < data.length; i++) { - if (data[i]) { - if (data[i].indexOf('error: ') != '-1' || data[i].indexOf('Errno') != '-1') { - toastr.error(data[i]); - } else { - toastr.success(data[i]); - } - } - } - } - } - }); + $("#cache").checkboxradio("disable"); + $("#waf").checkboxradio("disable"); + $("#serv").on('selectmenuchange', function () { + change_select_acceleration(""); + change_select_waf(""); }); - $('#ssl_key_view').click(function () { - if (!checkIsServerFiled('#serv5')) return false; - $.ajax({ - url: "/add/certs/" + $('#serv5').val(), - success: function (data) { - if (data.indexOf('error:') != '-1') { - toastr.error(data); - } else { - let i; - let new_data = ""; - data = data.split("\n"); - let j = 1 - for (i = 0; i < data.length; i++) { - data[i] = data[i].replace(/\s+/g, ' '); - if (data[i] != '') { - if (j % 2) { - if (j != 0) { - new_data += '' - } - new_data += '' - } else { - new_data += '' - - } - j += 1 - new_data += ' ' + data[i] + ' ' - } - } - $("#ajax-show-ssl").html(new_data); - } - } - }); + $("#cache2").checkboxradio("disable"); + $("#waf2").checkboxradio("disable"); + $("#serv2").on('selectmenuchange', function () { + change_select_acceleration("2"); + change_select_waf("2"); + }); + $("#cache3").checkboxradio("disable"); + $("#serv3").on('selectmenuchange', function () { + change_select_acceleration("3"); + }); + $('#compression').on("click", function () { + if ($('#compression').is(':checked')) { + $("#cache").checkboxradio("disable"); + $("#cache").prop('checked', false); + } else { + change_select_acceleration(""); + } + }); + $('#compression2').on("click", function () { + if ($('#compression2').is(':checked')) { + $("#cache2").checkboxradio("disable"); + $("#cache2").prop('checked', false); + } else { + change_select_acceleration('2'); + } + }); + $('#compression3').on("click", function () { + if ($('#compression3').is(':checked')) { + $("#cache3").checkboxradio("disable"); + $("#cache3").prop('checked', false); + } else { + change_select_acceleration('3'); + } + }); + $('#cache').on("click", function () { + if ($('#cache').is(':checked')) { + $("#compression").checkboxradio("disable"); + $("#compression").prop('checked', false); + } else { + $("#compression").checkboxradio("enable"); + } + }); + $('#cache2').on("click", function () { + if ($('#cache2').is(':checked')) { + $("#compression2").checkboxradio("disable"); + $("#compression2").prop('checked', false); + } else { + $("#compression2").checkboxradio("enable"); + } + }); + $('#cache3').on("click", function () { + if ($('#cache3').is(':checked')) { + $("#compression3").checkboxradio("disable"); + $("#compression3").prop('checked', false); + } else { + $("#compression3").checkboxradio("enable"); + } + }); + $("#add4").on("click", function () { + $("#tabs").tabs("option", "active", 4); + }); + $("#add5").on("click", function () { + $("#tabs").tabs("option", "active", 5); + }); + $("#add6").on("click", function () { + $("#tabs").tabs("option", "active", 6); + $("#userlist_serv").selectmenu("open"); }); $('[name=add-server-input]').click(function () { $("[name=add_servers]").append(add_server_var); @@ -691,7 +531,7 @@ $( function() { let server = $("#add-" + section_type + " select[name='server'] option:selected"); if (!checkIsServerFiled("#add-" + section_type + " select[name='server'] option:selected")) return false; $.ajax({ - url: "/add/certs/" + server.val(), + url: "/add/certs/" + server.val() + "?cert_type=pem", success: function (data) { data = data.replace(/\s+/g, ' '); response(data.split(" ")); @@ -810,7 +650,7 @@ $( function() { } }); $("#options-" + section_type + "-show").click(function () { - if ($("#options-"+section_type+"-show").is(':checked')) { + if ($("#options-" + section_type + "-show").is(':checked')) { $("#options-" + section_type + "-show-div").show("fast"); } else { $("#options-" + section_type + "-show-div").hide("fast"); @@ -957,7 +797,7 @@ function updateOptions(id) { if (data.indexOf('error:') != '-1') { toastr.error(data); } else { - $("#option-" + id).addClass("update", 1000); + $("#option-" + id).Class("update", 1000); setTimeout(function () { $("#option-" + id).removeClass("update"); }, 2500); @@ -965,159 +805,6 @@ function updateOptions(id) { } }); } -function confirmDeleteSavedServer(id) { - $("#dialog-confirm").dialog({ - resizable: false, - height: "auto", - width: 400, - modal: true, - title: delete_word + " " + $('#servers-ip-' + id).val() + "?", - buttons: [{ - text: delete_word, - click: function () { - $(this).dialog("close"); - removeSavedServer(id); - } - }, { - text: cancel_word, - click: function () { - $(this).dialog("close"); - } - }] - }); -} -function removeSavedServer(id) { - $("#servers-saved-" + id).css("background-color", "#f2dede"); - $.ajax({ - url: "/add/server/" + id, - type: "DELETE", - contentType: "application/json; charset=utf-8", - statusCode: { - 204: function (xhr) { - $("#servers-saved-" + id).remove(); - }, - 404: function (xhr) { - $("#servers-saved-" + id).remove(); - } - }, - success: function (data) { - if (data) { - if (data.status === "failed") { - toastr.error(data); - } - } - } - }); -} -function updateSavedServer(id) { - toastr.clear(); - $.ajax( { - url: "/add/server/" + id, - type: "PUT", - data: JSON.stringify({"server": $('#servers-ip-'+id).val(), description: $('#servers-desc-'+id).val(),}), - contentType: "application/json; charset=utf-8", - success: function( data ) { - if (data.status === 'failed') { - toastr.error(data.error); - } else { - $("#servers-saved-"+id).addClass( "update", 1000 ); - setTimeout(function() { - $( "#servers-saved-"+id ).removeClass( "update" ); - }, 2500 ); - } - } - } ); -} -function view_ssl(id) { - let raw_word = translate_div.attr('data-raw'); - if(!checkIsServerFiled('#serv5')) return false; - $.ajax( { - url: "/add/cert/" + $('#serv5').val() + '/' + id, - success: function( data ) { - if (data.indexOf('error: ') != '-1') { - toastr.error(data); - } else { - $('#dialog-confirm-body').text(data); - $( "#dialog-confirm-cert" ).dialog({ - resizable: false, - height: "auto", - width: 670, - modal: true, - title: "Certificate from "+$('#serv5').val()+", name: "+id, - buttons: [{ - text: cancel_word, - click: function () { - $(this).dialog("close"); - } - }, { - text: raw_word, - click: function () { - showRawSSL(id); - } - }, { - text: delete_word, - click: function () { - $(this).dialog("close"); - confirmDeleting("SSL cert", id, $(this), ""); - } - }] - }); - } - } - } ); -} -function showRawSSL(id) { - $.ajax({ - url: "/add/cert/get/raw/" + $('#serv5').val() + "/" + id, - success: function (data) { - if (data.indexOf('error: ') != '-1') { - toastr.error(data); - } else { - $('#dialog-confirm-body').text(data); - $("#dialog-confirm-cert").dialog({ - resizable: false, - height: "auto", - width: 670, - modal: true, - title: "Certificate from " + $('#serv5').val() + ", name: " + id, - buttons: [{ - text: cancel_word, - click: function () { - $(this).dialog("close"); - } - }, { - text: "Human readable", - click: function () { - view_ssl(id); - } - }, { - text: delete_word, - click: function () { - $(this).dialog("close"); - confirmDeleting("SSL cert", id, $(this), ""); - } - }] - }); - } - } - }); -} -function deleteSsl(id) { - if (!checkIsServerFiled('#serv5')) return false; - $.ajax({ - url: "/add/cert/" + $("#serv5").val() + "/" + id, - type: "DELETE", - success: function (data) { - if (data.indexOf('error: ') != '-1') { - toastr.error(data); - } else { - toastr.clear(); - toastr.success('SSL cert ' + id + ' has been deleted'); - $("#ssl_key_view").trigger("click"); - } - } - }); -} function change_select_acceleration(id) { $.ajax({ url: "/service/haproxy/" + $('#serv' + id + ' option:selected').val() + "/status", @@ -1465,8 +1152,6 @@ function deleteId(id) { } var if_word = translate_div.attr('data-if-title'); var then_word = translate_div.attr('data-then'); -var value_word = translate_div.attr('data-value'); -var name_word = translate_div.attr('data-name'); var acl_option = '

    \n' + ''+if_word+'\n' + ': ' + - ' ' + - 'max_fails: ' + - ' fail_timeout: s' - $('[name=add-server-input]').click(function() { - $("[name=add_servers]").append(add_server_var); - changePortCheckFromServerPort(); + $('[name=add-server-input]').click(function () { + $("[name=add_servers]").append(add_server_nginx_var); }); - $('.advance-show-button').click(function() { + $('.advance-show-button').click(function () { $('.advance').fadeIn(); $('.advance-show-button').css('display', 'none'); $('.advance-hide-button').css('display', 'block'); return false; }); - $('.advance-hide-button').click(function() { + $('.advance-hide-button').click(function () { $('.advance').fadeOut(); $('.advance-show-button').css('display', 'block'); $('.advance-hide-button').css('display', 'none'); return false; }); + $("#scheme").on('selectmenuchange', function () { + if ($("#scheme option:selected").val() === "http") { + $('#hide-scheme').hide(); + } else { + $('#hide-scheme').show(); + } + }); + $("#compression").click(function () { + if ($("#compression").is(':checked')) { + $('#compression-options').show(); + } else { + $('#compression-options').hide(); + } + }); + $("#show_header").on("click", function () { + $("#header_div").show(); + $("#add_header").show(); + $("#show_header").hide(); + }); + $("#add_header").click(function () { + make_actions_for_adding_header('#header_div'); + }); + for (let section_type of ['ssl_key', 'ssl_crt']) { + let cert_type = section_type.split('_')[1]; + $("#" + section_type).autocomplete({ + source: function (request, response) { + let server = $("#add-proxy_pass select[name='server'] option:selected"); + if (!checkIsServerFiled("#add-proxy_pass select[name='server'] option:selected")) return false; + $.ajax({ + url: "/add/certs/" + server.val() + "?cert_type=" + cert_type, + success: function (data) { + data = data.replace(/\s+/g, ' '); + response(data.split(" ")); + } + }); + }, + autoFocus: true, + minLength: -1 + }); + } + $("#proxy_pass-upstream").autocomplete({ + source: function (request, response) { + let server = $("#add-proxy_pass select[name='server'] option:selected"); + if (!checkIsServerFiled("#add-proxy_pass select[name='server'] option:selected")) return false; + $.ajax({ + url: "/add/get/upstreams/" + server.val(), + success: function (data) { + data = data.replace(/\s+/g, ' '); + response(data.split(" ")); + } + }); + }, + autoFocus: true, + minLength: -1 + }); + $("#add5").on("click", function () { + $("#tabs").tabs("option", "active", 3); + }); }); +var header_option = '

    \n' + + '' + + '\t'+name_word+'' + + '\t' + + '\t'+value_word+'' + + '\t' + + '\t' + + '

    ' +function make_actions_for_adding_header(section_id) { + let random_id = makeid(3); + $(section_id).append(header_option); + $('#new_header_minus').attr('onclick', 'deleteId(\''+random_id+'\')'); + $('#new_header_minus').attr('id', ''); + $('#new_header_p').attr('id', random_id); + $('#new_header_minus').attr('id', ''); + $.getScript(awesome); + $( "select" ).selectmenu(); + $('[name=headers_method]').selectmenu({width: 180}); +} +function deleteId(id) { + $('#' + id).remove(); +} function resetProxySettings() { - $('[name=upstream]').val(''); + $('[name=name]').val(''); $('input:checkbox').prop("checked", false); $('[name=check-servers]').prop("checked", true); $('input:checkbox').checkboxradio("refresh"); $('.advance-show').fadeIn(); $('.advance').fadeOut(); - $('[name=mode').val('http'); $('select').selectmenu('refresh'); $("#path-cert-listen").attr('required', false); $("#path-cert-frontend").attr('required', false); } -function checkIsServerFiled(select_id, message = 'Select a server first') { - if ($(select_id).val() == null || $(select_id).val() == '') { - toastr.warning(message); - return false; - } - return true; -} -function generateConfig(form_name) { - let frm = $('#' + form_name); - if (form_name == 'add-upstream') { - serv = '#serv' - name_id = '#name' - } - if (!checkIsServerFiled(serv)) return false; - if (!checkIsServerFiled(name_id, 'The name cannot be empty')) return false; - let input = $("") - .attr("name", "generateconfig").val("1").attr("type", "hidden").attr("id", "generateconfig"); - $('#' + form_name + ' input[name=acl_then_value]').each(function () { - if (!$(this).val()) { - $(this).val('IsEmptY') - } - }); - $('#' + form_name + ' input[name=ip]').each(function () { - if (!$(this).val()) { - $(this).val('IsEmptY') - } - }); - $('#' + form_name + ' input[name=port]').each(function () { - if (!$(this).val()) { - $(this).val('IsEmptY') - } - }); - frm.append(input); - let generated_title = translate_div.attr('data-generated_config'); - $.ajax({ - url: frm.attr('action'), - data: frm.serialize(), - type: frm.attr('method'), - success: function (data) { - if (data.indexOf('error: ') != '-1' || data.indexOf('Fatal') != '-1') { - toastr.clear(); - toastr.error(data); - } else { - $('#dialog-confirm-body').text(data); - $("#dialog-confirm-cert").dialog({ - resizable: false, - height: "auto", - width: 650, - modal: true, - title: generated_title, - buttons: { - Ok: function () { - $(this).dialog("close"); - } - } - }); - } - } - }); - $("#generateconfig").remove(); - $('#' + form_name + ' input[name=ip]').each(function () { - if ($(this).val() == 'IsEmptY') { - $(this).val('') - } - }); - $('#' + form_name + ' input[name=port]').each(function () { - if ($(this).val() == 'IsEmptY') { - $(this).val('') - } - }); -} -function addProxy(form_name) { +function addProxy(form_name, generate = false) { let frm = $('#'+form_name); - if (form_name == 'add-upstream') { - serv = '#serv' - name_id = '#name' + let serv = '#serv1'; + let name_id = ''; + if (form_name === 'add-upstream') { + serv = '#serv2' + name_id = '#upstream-name' + } else if (form_name === 'add-proxy_pass') { + serv = '#serv1' + name_id = '#proxy_pass' } if(!checkIsServerFiled(serv)) return false; if(!checkIsServerFiled(name_id, 'The name cannot be empty')) return false; - $('#'+form_name +' input[name=ip]').each(function(){ - if ($(this).val().length === 0){ - $(this).val('IsEmptY') - } - }); - $('#'+form_name +' input[name=port]').each(function(){ - if ($(this).val().length === 0){ - $(this).val('IsEmptY') - } - }); + let json_data = getNginxFormData(frm, form_name); + let section_type = form_name.split('-')[1] + let q_generate = ''; + if (generate) { + q_generate = '?generate=1'; + } $.ajax({ - url: frm.attr('action'), - data: frm.serialize(), + url: '/add/nginx/' + $(serv).val() + '/section/' + section_type + q_generate, + data: JSON.stringify(json_data), type: frm.attr('method'), + contentType: "application/json; charset=utf-8", success: function( data ) { - data = data.replace(/\n/g, "
    "); - if (data.indexOf('error: ') != '-1' || data.indexOf('Fatal') != '-1') { - returnNiceCheckingConfig(data); - } else if (data == '') { + if (data.status === 'failed') { + toastr.error(data.error) + } else if (data === '') { toastr.clear(); - toastr.error('error: Proxy cannot be empty'); + toastr.error('error: Something went wrong. Check configuration'); } else { - toastr.clear(); - returnNiceCheckingConfig(data); - toastr.info('Section has been added. Do not forget to restart the server'); - resetProxySettings(); + if (generate) { + $('#dialog-confirm-body').text(data.data); + let generated_title = translate_div.attr('data-generated_config'); + $("#dialog-confirm-cert").dialog({ + resizable: false, + height: "auto", + width: 650, + modal: true, + title: generated_title, + buttons: { + Ok: function () { + $(this).dialog("close"); + } + } + }); + } else { + toastr.clear(); + data.data = data.data.replace(/\n/g, "
    "); + if (returnNiceCheckingConfig(data.data) === 0) { + toastr.info('Section has been added. Do not forget to restart the server'); + resetProxySettings(); + } + } } } }); - $('#'+form_name +' input[name=ip]').each(function(){ - if ($(this).val() == 'IsEmptY'){ - $(this).val('') +} +function getNginxFormData($form, form_name) { + let section_type = form_name.split('-')[1] + let unindexed_array = $form.serializeArray(); + let indexed_array = {}; + indexed_array['locations'] = []; + indexed_array['backend_servers'] = []; + let headers = []; + + $.map(unindexed_array, function (n, i) { + if (n['name'] === 'location') { + let location = $('input[name="location"]').val(); + let proxy_connect_timeout = $('input[name="proxy_connect_timeout"]').val(); + let proxy_read_timeout = $('input[name="proxy_read_timeout"]').val(); + let proxy_send_timeout = $('input[name="proxy_send_timeout"]').val(); + let upstream = $('input[name="upstream"]').val(); + $('#header_div p').each(function () { + let action = $(this).children().children('select[name="headers_res"] option:selected').val(); + let name = $(this).children('input[name="header_name"]').val(); + let value = $(this).children('input[name="header_value"]').val(); + if (action === '------') { + return; + } + if (name === '') { + return; + } + let header = {action, name, value}; + headers.push(header); + }); + let location_config = {location, proxy_connect_timeout, proxy_read_timeout, proxy_send_timeout, headers, upstream}; + indexed_array['locations'].push(location_config) + } else if (n['name'] === 'ssl_offloading') { + if ($('input[name="ssl_offloading"]').is(':checked')) { + indexed_array['ssl_offloading'] = true; + } else { + indexed_array['ssl_offloading'] = false; + } + } else { + indexed_array[n['name']] = n['value']; } }); - $('#'+form_name +' input[name=port]').each(function(){ - if ($(this).val() == 'IsEmptY'){ - $(this).val('') + $('#'+form_name+' span[name="add_servers"] p').each(function (){ + let server = $(this).children("input[name='servers']").val(); + if (server === undefined || server === '') { + return; } + let port = $(this).children("input[name='server_port']").val(); + let max_fails = $(this).children("input[name='max_fails']").val(); + let fail_timeout = $(this).children("input[name='fail_timeout']").val(); + let test_var = {server, port, max_fails, fail_timeout}; + indexed_array['backend_servers'].push(test_var); }); -} \ No newline at end of file + let elementsForDelete = [ + 'servers', 'server_port', 'max_fails', 'fail_timeout', 'proxy_connect_timeout', 'proxy_read_timeout', 'proxy_send_timeout', + 'headers_res', 'header_name', 'header_value', 'upstream', 'server' + ] + for (let element of elementsForDelete) { + delete indexed_array[element] + } + return indexed_array; +} +function createSsl(TabId) { + $('[name=port]').val('443'); + $("#tabs").tabs("option", "active", TabId); + $("#hide-scheme").show("fast"); + $('#scheme').val('https'); + $('#ssl_offloading').prop("checked", true); + $('input:checkbox').checkboxradio("refresh"); + $("#ssl_key").attr('required', true); + if (TabId === 1) { + TabId = ''; + } + $("#serv" + TabId).selectmenu("open"); + $("#scheme").selectmenu("refresh"); + history.pushState('Add proxy pass', 'Add proxy pass', 'nginx#proxypass') +} diff --git a/app/static/js/edit_config.js b/app/static/js/edit_config.js index 950e83be..1caae3e7 100644 --- a/app/static/js/edit_config.js +++ b/app/static/js/edit_config.js @@ -284,9 +284,9 @@ function openSection(section) { } }); } -function delete_section(section_type, section_name, server_id) { +function delete_section(section_type, section_name, server_id, service) { $.ajax({ - url: '/add/haproxy/' + server_id + '/section/' + section_type + '/' + section_name, + url: '/add/' + service + '/' + server_id + '/section/' + section_type + '/' + section_name, contentType: "application/json; charset=utf-8", method: "DELETE", statusCode: { @@ -415,6 +415,7 @@ function editProxy(form_name, dialog_id, generate=false) { toastr.info('Section has been updated. Do not forget to restart the server'); let ip = $('select[name=serv]').val(); localStorage.setItem('restart', ip); + $('#edit-section').remove(); showConfig(); $(dialog_id).dialog( "close" ); } @@ -647,25 +648,219 @@ function getFormData($form, form_name) { } return indexed_array; } -function confirmDeleteSection(section_type, section_name, serv_val, dialog_id) { - $( "#dialog-confirm" ).dialog({ - resizable: false, - height: "auto", - width: 400, - modal: true, - title: delete_word + " " + section_name + "?", - buttons: [{ - text: delete_word, - click: function () { - $(this).dialog("close"); - delete_section(section_type, section_name, serv_val); - dialog_id.dialog("close"); - } - }, { - text: cancel_word, - click: function () { - $(this).dialog("close"); - } - }] - }); +function confirmDeleteSection(section_type, section_name, serv_val, dialog_id, service='haproxy') { + $("#dialog-confirm").dialog({ + resizable: false, + height: "auto", + width: 400, + modal: true, + title: delete_word + " " + section_name + "?", + buttons: [{ + text: delete_word, + click: function () { + $(this).dialog("close"); + delete_section(section_type, section_name, serv_val, service); + dialog_id.dialog("close"); + } + }, { + text: cancel_word, + click: function () { + $(this).dialog("close"); + } + }] + }); +} +function openNginxSection(section) { + let section_type = section.split('_')[0]; + let section_name = section.split('_')[1]; + if (section_type === 'proxy-pass') { + section_type = 'proxy_pass'; + } + let url = '/add/nginx/' + $('#serv').val() + '/section/' + section_type + '/' + section_name; + clearEditNginxSection(); + $.ajax({ + url: url, + contentType: "application/json; charset=utf-8", + statusCode: { + 404: function (xhr) { + window.open('/config/nginx/' + $('#serv').val() + '/edit/' + $('#config_file_name').val(), '_blank').focus(); + } + }, + async: false, + success: function (data) { + $('.advance-show-button').click(function () { + $('.advance').fadeIn(); + $('.advance-show-button').css('display', 'none'); + $('.advance-hide-button').css('display', 'block'); + return false; + }); + $('.advance-hide-button').click(function () { + $('.advance').fadeOut(); + $('.advance-show-button').css('display', 'block'); + $('.advance-hide-button').css('display', 'none'); + return false; + }); + let section_id = '#add-' + section_type; + $.getScript(awesome); + $('#edit-' + section_type).show(); + $('#edit-' + section_type + ' caption').hide(); + $('#' + section_type + '-add-buttons').hide(); + let i = 0; + Object.keys(data.config).forEach(function (key) { + if ($(section_id + ' *[name="' + key + '"]').prop("tagName") === 'SELECT') { + $(section_id + ' select[name="' + key + '"]').val(data.config[key]).change(); + } else if ($(section_id + ' *[name="' + key + '"]').prop("tagName") === 'TEXTAREA') { + $(section_id + ' select[name="' + key + '"]').val(data.config['option']).change(); + } else { + if ($(section_id + ' *[name="' + key + '"]').prop('type') === 'checkbox') { + if (data.config[key]) { + $(section_id + ' input[name="' + key + '"]').prop("checked", true); + } + } else { + $(section_id + ' input[name="' + key + '"]').val(data.config[key]); + } + } + }); + if (section_type === 'upstream') { + if (data.config.backend_servers) { + if (data.config.backend_servers.length > 3) { + for (let i = 2; i < data.config.backend_servers.length; i++) { + $("[name=add_servers]").append(add_server_nginx_var); + } + } + i = 0; + if (data.config.backend_servers.length > 0) { + for (let bind of data.config.backend_servers) { + $(section_id + ' input[name="servers"]').get(i).value = bind.server; + $(section_id + ' input[name="server_port"]').get(i).value = bind.port; + $(section_id + ' input[name="max_fails"]').get(i).value = bind.max_fails; + $(section_id + ' input[name="fail_timeout"]').get(i).value = bind.fail_timeout; + i++; + } + } + } + } + if (section_type === 'proxy_pass') { + for (let location of data.locations) { + if (location.headers) { + if (location.length > 0) { + i = 0; + $("#add_header").on("click", function () { + $("#header_div").show(); + $("#add_header").show(); + $("#add_header").hide(); + }); + $('#add_header').trigger('click'); + for (let header of data.config.headers) { + make_actions_for_adding_header('#header_div') + let headers_res_id = $(section_id + ' select[name="headers_res"]').get(i).id; + $('#' + headers_res_id).val(header.path).change(); + $(section_id + ' input[name="header_name"]').get(i).value = header.name; + $(section_id + ' input[name="header_value"]').get(i).value = header.value; + i++; + } + } + } + $('#proxy_connect_timeout').val(location.proxy_connect_timeout); + $('#proxy_read_timeout').val(location.proxy_read_timeout); + $('#proxy_send_timeout').val(location.proxy_send_timeout); + $('#proxy_pass-upstream').val(location.upstream); + } + if (data.compression) { + $('#compression').prop("checked", true); + $("#compression-options").show("fast"); + $('#compression_types').val(data.compression_types); + $('#compression_min_length').val(data.compression_min_length); + $('#compression_level').val(data.compression_level); + } else { + $('#compression').prop("checked", false); + $("#compression-options").hide("fast"); + } + if (data.scheme === 'https') { + $('#scheme').val('https'); + $('#hide-scheme').show(); + $("#scheme").selectmenu(); + $("#scheme").selectmenu('refresh'); + } + } + $(section_id + ' select[name="server"]').selectmenu(); + $(section_id + ' select[name="server"]').selectmenu('refresh'); + $("input[type=checkbox]").checkboxradio(); + $("input[type=checkbox]").checkboxradio('refresh'); + $(section_id + ' select[name="server"]').val(data.server_id).change(); + $(section_id + ' select[name="server"]').selectmenu('disable').parent().parent().hide(); + $(section_id + ' input[name="name"]').prop("readonly", true).parent().parent().hide(); + let buttons = [{ + text: edit_word, + click: function () { + editNginxProxy('add-' + section_type, $(this)); + } + }, { + text: delete_word, + click: function () { + confirmDeleteSection(section_type, section_name, $('#serv').val(), $(this), 'nginx'); + } + }, { + text: cancel_word, + click: function () { + $(this).dialog("close"); + $('#edit-' + section_type).hide(); + } + }] + $("#edit-section").dialog({ + resizable: false, + height: "auto", + width: 1100, + modal: true, + title: edit_word, + close: function () { + $('#edit-' + section_type).hide(); + }, + buttons: buttons + }); + } + }); +} +function clearEditNginxSection() { + $('#edit-section').empty(); + $.ajax({ + url: "/add/nginx/get_section_html", + method: "GET", + async: false, + success: function(data) { + $('#edit-section').html(data); + $.getScript('/static/js/add_nginx.js'); + }, + }) +} +function editNginxProxy(form_name, dialog_id, generate=false) { + let frm = $('#'+form_name); + let name_id = '#' +form_name + ' input[name="name"]'; + if(!checkIsServerFiled(name_id, 'The name cannot be empty')) return false; + let json_data = getNginxFormData(frm, form_name); + let section_type = form_name.split('-')[1] + let url = '/add/nginx/' + $('#serv').val() + '/section/' + section_type + '/' + $(name_id).val(); + $.ajax({ + url: url, + data: JSON.stringify(json_data), + type: 'PUT', + contentType: "application/json; charset=utf-8", + success: function( data ) { + if (data.status === 'failed') { + toastr.error(data.error) + } else if (data === '') { + toastr.clear(); + toastr.error('error: Something went wrong. Check configuration'); + } else { + toastr.clear(); + data.data = data.data.replace(/\n/g, "
    "); + if (returnNiceCheckingConfig(data.data) === 0) { + toastr.info('Section has been updated. Do not forget to restart the server'); + showConfig(); + $('#edit-section').remove(); + $(dialog_id).dialog( "close" ); + } + } + } + }); } diff --git a/app/static/js/script.js b/app/static/js/script.js index d27d620f..093ffd4c 100644 --- a/app/static/js/script.js +++ b/app/static/js/script.js @@ -703,7 +703,7 @@ $( function() { }); $(".installmon").on("click", function () { $('.menu li ul li').each(function () { - activeSubMenu($(this), 'instalmon'); + activeSubMenu($(this), 'installmon'); }); $("#tabs").tabs("option", "active", 1); }); @@ -845,41 +845,41 @@ async function ban() { $("input[type=submit], button").button('enable'); $('#ban_10').hide(); } -function replace_text(id_textarea, text_var) { - var str = $(id_textarea).val(); - var len = str.length; - var len_var = text_var.length; - var beg = str.indexOf(text_var); - var end = beg + len_var - var text_val = str.substring(0, beg) + str.substring(end, len); - $(id_textarea).text(text_val); -} -function createHistroy() { +// function replace_text(id_textarea, text_var) { +// var str = $(id_textarea).val(); +// var len = str.length; +// var len_var = text_var.length; +// var beg = str.indexOf(text_var); +// var end = beg + len_var +// var text_val = str.substring(0, beg) + str.substring(end, len); +// $(id_textarea).text(text_val); +// } +function createHistory() { if(localStorage.getItem('history') === null) { - var get_history_array = ['login', 'login','login']; + let get_history_array = ['login', 'login','login']; localStorage.setItem('history', JSON.stringify(get_history_array)); } } -function listHistroy() { - var browse_history = JSON.parse(localStorage.getItem('history')); - var history_link = ''; - var title = [] - var link_text = [] - var cur_path = window.location.pathname; +function listHistory() { + let browse_history = JSON.parse(localStorage.getItem('history')); + let history_link = ''; + let title = [] + let link_text = [] + let cur_path = window.location.pathname; for(let i = 0; i < browse_history.length; i++){ - if (i == 0) { + if (i === 0) { browse_history[0] = browse_history[1]; } - if (i == 1) { + if (i === 1) { browse_history[1] = browse_history[2] } - if (i == 2) { + if (i === 2) { browse_history[2] = cur_path } $( function() { $('.menu li ul li').each(function () { - var link1 = $(this).find('a').attr('href'); - if (browse_history[i].replace(/\/$/, "") == link1) { + let link1 = $(this).find('a').attr('href'); + if (browse_history[i].replace(/\/$/, "") === link1) { title[i] = $(this).find('a').attr('title'); link_text[i] = $(this).find('a').text(); history_link = '
  • '+link_text[i]+'
  • ' @@ -890,8 +890,8 @@ function listHistroy() { } localStorage.setItem('history', JSON.stringify(browse_history)); } -createHistroy(); -listHistroy(); +createHistory(); +listHistory(); function changeCurrentGroupF(user_id) { $.ajax({ @@ -909,14 +909,14 @@ function changeCurrentGroupF(user_id) { }); } function updateTips( t ) { - var tips = $( ".validateTips" ); + let tips = $( ".validateTips" ); tips.text( t ).addClass( "alert-warning" ); tips.text( t ).addClass( "alert-one-row" ); } function clearTips() { - var tips = $( ".validateTips" ); + let tips = $( ".validateTips" ); tips.html('Fields marked "*" are required').removeClass( "alert-warning" ); - allFields = $( [] ).add( $('#new-server-add') ).add( $('#new-ip') ).add( $('#new-port')).add( $('#new-username') ).add( $('#new-password') ) + let allFields = $( [] ).add( $('#new-server-add') ).add( $('#new-ip') ).add( $('#new-port')).add( $('#new-username') ).add( $('#new-password') ) allFields.removeClass( "ui-state-error" ); } function checkLength( o, n, min ) { @@ -1382,3 +1382,63 @@ function makeid(length) { } return result; } +const INSTALLATION_TASKS_KEY = 'installationTasks'; +function getInstallationTasksFromSessionStorage() { + const tasks = sessionStorage.getItem(INSTALLATION_TASKS_KEY); + return tasks ? JSON.parse(tasks) : []; +} +function addItemToSessionStorageInstallTask(taskId) { + if (!sessionStorage.getItem(INSTALLATION_TASKS_KEY)) { + sessionStorage.setItem(INSTALLATION_TASKS_KEY, JSON.stringify([])); // Создаем пустой массив + } + let tasks = getInstallationTasksFromSessionStorage(); + + tasks.push(taskId); + + sessionStorage.setItem(INSTALLATION_TASKS_KEY, JSON.stringify(tasks)); +} +function removeItemFromSessionStorage(taskId) { + let tasks = getInstallationTasksFromSessionStorage(); + + tasks = tasks.filter(item => item !== taskId); + + sessionStorage.setItem(INSTALLATION_TASKS_KEY, JSON.stringify(tasks)); + } + +function checkInstallationTask() { + let tasks = getInstallationTasksFromSessionStorage(); // Извлекаем список + if (tasks && tasks.length > 0) { + tasks.forEach(item => { + checkInstallationStatus(item); + }); + } else { + console.log('No tasks'); + clearInterval(checkInstallationTaskInterval); + } +} +function runInstallationTaskCheck(tasks_ids) { + toastr.info('Installation started. You can continue to use the system while it is installing'); + tasks_ids.forEach(item => { + addItemToSessionStorageInstallTask(item); + setTimeout(function () { + setInterval(checkInstallationTask, 3000); + }, 5000); + }); +} +function checkInstallationStatus(taskId) { + NProgress.configure({showSpinner: false}); + $.ajax({ + url: "/install/task-status/" + taskId, + success: function (data) { + if (data.status === 'completed') { + toastr.success('Installation completed for ' + data.service_name); + removeItemFromSessionStorage(taskId); + } else if (data.status === 'failed') { + toastr.error('Cannot install ' + data.service_name + '. Error: ' + data.error); + removeItemFromSessionStorage(taskId); + } + } + }); + NProgress.configure({showSpinner: true}); +} +let checkInstallationTaskInterval = setInterval(checkInstallationTask, 3000); diff --git a/app/static/js/variables.js b/app/static/js/variables.js index a9701627..0eed9cc0 100644 --- a/app/static/js/variables.js +++ b/app/static/js/variables.js @@ -40,3 +40,8 @@ const add_userlist_var = '

    : ' + '

    ' +const add_server_nginx_var = '

    : ' + + ' ' + + 'max_fails: ' + + ' fail_timeout: s ' + + '

    ' diff --git a/app/templates/add.html b/app/templates/add.html index 7e961593..86983729 100644 --- a/app/templates/add.html +++ b/app/templates/add.html @@ -16,8 +16,11 @@ {% set header_params = {'add-header': 'add-header', 'set-header': 'set-header', 'del-header': 'del-header'} %} {% set if_values = {'1':'Host name starts with','2':'Host name ends with','3':'Path starts with','4':'Path ends with', '6': 'Src ip'} %} {% set force_close = {'0':'Off','1':'Server only','2':'Force close','3':'Pretend keep alive'} %} - +{% if user_subscription.user_status == 0 %} + {% include 'include/no_sub.html' %} +{% else %} +
      @@ -100,52 +103,7 @@
    - - - - - - - {% for s in saved_servers %} - - {% if s.groups|string() == g.user_params['group_id']|string() or group|string() == '1' %} - - - - {% endif %} - - {% endfor %} -
    {{lang.words.server|title()}}{{lang.words.desc|title()}}
    - - - - - -
    -
    + {{lang.words.add|title()}} -

    - - - - - - - - - - - - -
    -
    - {{lang.add_page.desc.servers}} -
    + {% include 'include/add/servers.html' %}
    {% include 'include/add/userlist.html' %} @@ -299,4 +257,5 @@ for (var i = 0; i <= serv_ports.length; i++) { 'This can be done easily in HAProxy by adding the keyword backup on the server line. If multiple backup servers are configured, only the first active one is used.">backup'); } +{% endif %} {% endblock %} diff --git a/app/templates/add_nginx.html b/app/templates/add_nginx.html index dae944a1..a48bfa47 100644 --- a/app/templates/add_nginx.html +++ b/app/templates/add_nginx.html @@ -3,75 +3,30 @@ {% block h2 %}{{lang.menu_links.add_proxy.title}}{% endblock %} {% block content %} {% from 'include/input_macros.html' import input, checkbox, select %} -{% set balance_params = dict() %} {% set balance_params = {'ip_hash':'ip_hash','least_conn':'least_conn','random':'random', 'round_robin': 'round-robin'} %} + +{% if user_subscription.user_status == 0 %} + {% include 'include/no_sub.html' %} +{% else %}
      - {% include 'include/add_nginx_proxy.html' %} -
      -
      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

      {{lang.words.add|title()}} upstream

      {{lang.words.select|title()}} {{lang.words.w_a}} {{lang.words.server}}: - {{ select('serv', values=g.user_params['servers'], is_servers='true') }} -
      {{lang.words.note|title()}}: {{lang.phrases.master_slave}}
      -
      - {{lang.add_nginx_page.desc.upstream_desc1}} -

      - {{lang.add_nginx_page.desc.upstream_desc2}} -

      - {{lang.add_nginx_page.desc.upstream_desc3}} -
      {{lang.words.name|title()}}: - {{ input('name', name='upstream', title="Name upstream", placeholder="backend_servers", required='required') }} -
      {{lang.words.balance|title()}}: - {{ select('balance', values=balance_params, selected='round-robin', required='required', class='force_close') }} -
      Keepalive: - {{ input('name', name='keepalive', title=lang.add_nginx_page.desc.keepalive, placeholder="32") }} -
      {{lang.words.servers|title()}}: - {% include 'include/add_nginx_servers.html' %} -
      - - -
      - {{lang.words.add|title()}} - - {{lang.words.generate|title()}} {{lang.words.config}} -
      -
      + {% include 'include/add_nginx/add_nginx_proxy.html' %} +
      + {% include 'include/add_nginx/proxy_pass.html' %}
      +
      + {% include 'include/add_nginx/upstream.html' %} +
      +
      + {% include 'include/add/servers.html' %} +
      +{% include 'include/del_confirm.html' %} +{% endif %} {% endblock %} diff --git a/app/templates/ajax/config_show.html b/app/templates/ajax/config_show.html index 08487956..54b2ee43 100644 --- a/app/templates/ajax/config_show.html +++ b/app/templates/ajax/config_show.html @@ -6,7 +6,11 @@ {% if role <= 3 %} {% if not is_serv_protected or role <= 2 %} {% if not configver %} + {% if service != 'nginx' %} {{lang.words.edit|title()}} + {% else %} + {{lang.words.edit|title()}} + {% endif %} {% endif %} {% if service == 'haproxy' %} {{lang.words.add|title()}} @@ -76,6 +80,12 @@
      {% continue %} {% endif %} + {%- if "upstream {" in line -%} +
      + {{ line }} +
      + {% continue %} + {% endif %} {% if "listen " in line or "location" in line or "server_name" in line or "}" in line %} {% if "#" not in line %} @@ -379,6 +389,7 @@ {%- endfor -%}
      +
      {% if configver %}
      {% if role <= 3 %} diff --git a/app/templates/ajax/config_show_add_nginx_sections.html b/app/templates/ajax/config_show_add_nginx_sections.html new file mode 100644 index 00000000..952f5530 --- /dev/null +++ b/app/templates/ajax/config_show_add_nginx_sections.html @@ -0,0 +1,9 @@ +{% import 'languages/'+lang|default('en')+'.html' as lang %} +{% from 'include/input_macros.html' import input, checkbox, select %} +{% set balance_params = {'ip_hash':'ip_hash','least_conn':'least_conn','random':'random', 'round_robin': 'round-robin'} %} + + diff --git a/app/templates/include/add/servers.html b/app/templates/include/add/servers.html new file mode 100644 index 00000000..d6c266c2 --- /dev/null +++ b/app/templates/include/add/servers.html @@ -0,0 +1,46 @@ + + + + + + +{% for s in saved_servers %} + + {% if s.groups|string() == g.user_params['group_id']|string() or group|string() == '1' %} + + + + {% endif %} + +{% endfor %} +
      {{lang.words.server|title()}}{{lang.words.desc|title()}}
      + + + + + +
      +
      + {{lang.words.add|title()}} +

      + + + + + + + + + + + + +
      +
      + {{lang.add_page.desc.servers}} +
      diff --git a/app/templates/include/add_nginx/add_nginx_proxy.html b/app/templates/include/add_nginx/add_nginx_proxy.html new file mode 100644 index 00000000..e9968ffc --- /dev/null +++ b/app/templates/include/add_nginx/add_nginx_proxy.html @@ -0,0 +1,62 @@ +{% if add %} +
      +
      + X +
      +

      {{ add }} {{lang.add_page.desc.was_success_added}}

      + {{ conf_add }} +
      + +{% endif %} +
      +
      +
      +
      +
      + {{lang.words.create|title()}} HTTP proxy pass +
      +
      + {{lang.add_page.desc.create_nginx_proxy}} +
      +
      +
      +
      +
      +
      + {{lang.words.create|title()}} HTTPS proxy pass +
      +
      + {{lang.add_page.desc.create_nginx_ssl_proxy}} {{lang.words.uploaded}} {{lang.words.w_a}} PEM {{lang.words.cert}} +
      +
      +
      +
      +
      +
      +
      +
      + {{lang.words.create|title()}} {{lang.words.upstream|title()}} +
      +
      + {{lang.add_nginx_page.desc.upstream_desc1}} +
      +
      +
      +
      +
      +
      + {{lang.words.create|title()}} {{lang.words.w_a}} {{lang.words.server}} {{lang.words.template}} +
      +
      + {{lang.add_page.desc.server_temp}} +
      +
      +
      +
      +
      +
      +
      diff --git a/app/templates/include/add_nginx_servers.html b/app/templates/include/add_nginx/add_nginx_servers.html similarity index 98% rename from app/templates/include/add_nginx_servers.html rename to app/templates/include/add_nginx/add_nginx_servers.html index 73f0e114..41cce15a 100644 --- a/app/templates/include/add_nginx_servers.html +++ b/app/templates/include/add_nginx/add_nginx_servers.html @@ -4,20 +4,24 @@ } +

      : max_fails: fail_timeout: s -
      +

      +

      : max_fails: fail_timeout: s -
      +

      +

      : max_fails: fail_timeout: s +

      diff --git a/app/templates/include/add_nginx/proxy_pass.html b/app/templates/include/add_nginx/proxy_pass.html new file mode 100644 index 00000000..8c1cf300 --- /dev/null +++ b/app/templates/include/add_nginx/proxy_pass.html @@ -0,0 +1,123 @@ +{% set header_res = {'add_header': 'add_header', 'proxy_set_header': 'proxy_set_header', 'proxy_hide_header': 'proxy_hide_header'} %} + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

      {{lang.words.add|title()}} proxy pass

      {{lang.words.select|title()}} {{lang.words.w_a}} {{lang.words.server}}: + {{ select('serv1', name='server', values=g.user_params['servers'], is_servers='true', by_id='true') }} +
      {{lang.words.note|title()}}: {{lang.phrases.master_slave}}
      +
      + Proxy pass is a Nginx directive that forwards incoming client requests to a specified backend server (like Apache, Node.js, or a microservice) and relays the response back to the client. It supports protocols like HTTP, HTTPS, WebSocket, and gRPC, making it versatile for reverse proxying. Key parameters include: +
      +
      +Header control: Uses proxy_set_header to forward client IP (X-Real-IP) or protocol (X-Forwarded-Proto). +
      +
      +Timeouts: Timeouts (proxy_connect_timeout). +
      +
      +(Summary: It’s the backbone of Nginx reverse proxying, handling routing, balancing, and security.) +
      {{lang.words.name|title()}}: + {{ input('proxy_pass', name='name', title="Domain name or IP", placeholder="example.com", required='required') }} +
      + HTTP {{lang.words.scheme|title()}}: + + {% set scheme_params={'http': 'HTTP', 'https': 'HTTPS'} %} + {{ select('scheme', values=scheme_params, selected='http', required='required', class='force_close') }} +
      +
      + +
      + {{lang.words.port|title()}}: + + {{ input('port', name='port', title='Port to bind', placeholder="80", required='required', type='number', style='width: 40px;') }} +
      Location: +

      + {{ input('location', name='location', title="Location", value="/", required='required') }} +
      +
      + Proxy connect timeout: + {{ input('proxy_connect_timeout', name='proxy_connect_timeout', title='Proxy connect timeout', value="60", type='number', style='width: 40px;') }} +
      + Proxy read timeout: + {{ input('proxy_read_timeout', name='proxy_read_timeout', title='Proxy read timeout', value="60", type='number', style='width: 40px;') }} +
      + Proxy send timeout: + {{ input('proxy_send_timeout', name='proxy_send_timeout', title='Proxy send timeout', value="60", type='number', style='width: 40px;') }}

      + {{lang.words.headers|title()}}: +

      + + + +
      + Upstream: {{ input('proxy_pass-upstream', name='upstream', placeholder='upstream_config') }} +

      +
      {{ lang.words.compression|title() }}: + {{ checkbox('compression', title=lang.add_page.desc.http_compression, value='true', desc=lang.words.compression|title()) }} + +
      + {{lang.words.add|title()}} + + {{lang.words.generate|title()}} {{lang.words.config}} +
      +
      + \ No newline at end of file diff --git a/app/templates/include/add_nginx/upstream.html b/app/templates/include/add_nginx/upstream.html new file mode 100644 index 00000000..113fadb6 --- /dev/null +++ b/app/templates/include/add_nginx/upstream.html @@ -0,0 +1,57 @@ +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

      {{lang.words.add|title()}} upstream

      {{lang.words.select|title()}} {{lang.words.w_a}} {{lang.words.server}}: + {{ select('serv2', name='server', values=g.user_params['servers'], is_servers='true', by_id='true') }} +
      {{lang.words.note|title()}}: {{lang.phrases.master_slave}}
      +
      + {{lang.add_nginx_page.desc.upstream_desc1}} +

      + {{lang.add_nginx_page.desc.upstream_desc2}} +

      + {{lang.add_nginx_page.desc.upstream_desc3}} +
      {{lang.words.name|title()}}: + {{ input('upstream-name', name='name', title="Name upstream", placeholder="backend_servers", required='required') }} +
      {{lang.words.balance|title()}}: + {{ select('balance', values=balance_params, selected='round_robin', required='required', class='force_close') }} +
      Keepalive: + {{ input('keepalive', name='keepalive', title=lang.add_nginx_page.desc.keepalive, value="32") }} +
      {{lang.words.servers|title()}}: + {% include 'include/add_nginx/add_nginx_servers.html' %} +
      + + +
      + {{lang.words.add|title()}} + + {{lang.words.generate|title()}} {{lang.words.config}} +
      +
      diff --git a/app/templates/include/add_nginx_proxy.html b/app/templates/include/add_nginx_proxy.html deleted file mode 100644 index ee224ccc..00000000 --- a/app/templates/include/add_nginx_proxy.html +++ /dev/null @@ -1,32 +0,0 @@ -{% if add %} -
      -
      - X -
      -

      {{ add }} {{lang.add_page.desc.was_success_added}}

      - {{ conf_add }} -
      - -{% endif %} -
      -
      -
      -
      -
      - {{lang.words.create|title()}} {{lang.words.upstream|title()}} -
      -
      - {{lang.add_nginx_page.desc.upstream_desc1}} -
      -
      -
      -
      -
      -
      -
      -
      -
      diff --git a/app/templates/languages/en.html b/app/templates/languages/en.html index 2cec9006..28da3f09 100644 --- a/app/templates/languages/en.html +++ b/app/templates/languages/en.html @@ -405,6 +405,8 @@ "server_temp": "Create, edit and delete servers. And after use them as autocomplete in the 'Add' sections", "use_add": "And use it in the 'Add' sections", "comma_separated": "You can specify several, separated by a comma or a space", + "create_nginx_ssl_proxy": "Create HTTPS Proxy with the SSL termination on NGINX and SSL offload. NGINX will send to backends HTTP traffic. You need have", + "create_nginx_proxy": "Proxy pass in Nginx forwards client requests to a specified backend server (like Node.js or Python) and returns the response to the client. It’s the core directive for reverse proxying, enabling load balancing, SSL termination, and routing traffic to different services.", }, "buttons": { "disable_ssl_check": "Disable SSL check", @@ -962,5 +964,8 @@ "theme": "theme", "dark": "dark", "light": "light", + "types": "types", + "min": "minimum", + "length": "length", } %} diff --git a/app/templates/languages/fr.html b/app/templates/languages/fr.html index 4504da21..9896aaaf 100644 --- a/app/templates/languages/fr.html +++ b/app/templates/languages/fr.html @@ -405,6 +405,8 @@ "server_temp": "Créer, modifier et supprimer des serveurs. Et par la suite, utilisez-les en tant que liste déroulante dans les sections 'Ajouter'", "use_add": "Et utilisez-le dans les sections 'Ajouter'.", "comma_separated": "Vous pouvez en spécifier plusieurs, séparés par une virgule ou un espace", + "create_nginx_ssl_proxy": "Créez un proxy HTTPS avec terminaison SSL sur NGINX et déchargement SSL. NGINX enverra le trafic HTTP aux backends. Vous devez disposer dee", + "create_nginx_proxy": "Dans Nginx, la commande proxy transmet les requêtes client à un serveur back-end spécifique (comme Node.js ou Python) et renvoie la réponse au client. Il s'agit de la directive principale pour le proxy inverse, permettant l'équilibrage de charge, la terminaison SSL et le routage du trafic vers différents services.", }, "buttons": { "disable_ssl_check": "Désactiver la vérification SSL", @@ -962,5 +964,8 @@ "theme": "thème", "dark": "sombre", "light": "léger", + "types": "types", + "min": "minimum", + "length": "longueur", } %} diff --git a/app/templates/languages/pt-br.html b/app/templates/languages/pt-br.html index 759afbea..0f1746e1 100644 --- a/app/templates/languages/pt-br.html +++ b/app/templates/languages/pt-br.html @@ -405,6 +405,8 @@ "server_temp": "Crie, edite e exclua servidores com determinados parâmetros. Usá-los como preenchimento automático nas seções 'Adicionar'", "use_add": "Usa isso as seções 'Adicionar'", "comma_separated": "Você pode especificar vários, separados por uma vírgula ou um espaço", + "create_nginx_ssl_proxy": "Crie um proxy HTTPS com terminação SSL no NGINX e descarregamento SSL. O NGINX enviará tráfego HTTP para os backends. Você precisa terw", + "create_nginx_proxy": "A passagem de proxy no Nginx encaminha solicitações do cliente para um servidor backend especificado (como Node.js ou Python) e retorna a resposta ao cliente. É a diretiva principal para proxy reverso, permitindo balanceamento de carga, terminação SSL e roteamento de tráfego para diferentes serviços.", }, "buttons": { "disable_ssl_check": "Ativar a verificação de SSL", @@ -962,5 +964,8 @@ "theme": "thema", "dark": "escuro", "light": "claro", + "types": "tipos", + "min": "mínima", + "length": "comprimento", } %} diff --git a/app/templates/languages/ru.html b/app/templates/languages/ru.html index d9320235..aef2517c 100644 --- a/app/templates/languages/ru.html +++ b/app/templates/languages/ru.html @@ -405,6 +405,8 @@ "server_temp": "Создание, редактирование и удаление серверов. И после использовать их как автозаполнение в разделах «Добавить прокси»", "use_add": "И использовать их в разделах «Добавить прокси»", "comma_separated": "Можно указать несколько, разделенных запятой, либо пробелом", + "create_nginx_ssl_proxy": "Создайте HTTPS-прокси с терминацией SSL на NGINX и разгрузкой SSL. NGINX будет отправлять на серверные части HTTP-трафик. Вам нужно иметь", + "create_nginx_proxy": "Прокси-передача в Nginx перенаправляет клиентские запросы на указанный бэкенд-сервер (например, Node.js или Python) и возвращает ответ клиенту. Это основная директива для обратного проксирования, включающая балансировку нагрузки, терминацию SSL и маршрутизацию трафика на различные сервисы.", }, "buttons": { "disable_ssl_check": "Отключить проверку SSL", @@ -962,5 +964,8 @@ "theme": "тема", "dark": "темная", "light": "светлая", + "types": "типы", + "min": "минимальная", + "length": "длина", } %} diff --git a/app/views/service/haproxy_section_views.py b/app/views/service/haproxy_section_views.py index 5638abe8..adcefe57 100644 --- a/app/views/service/haproxy_section_views.py +++ b/app/views/service/haproxy_section_views.py @@ -64,7 +64,7 @@ class HaproxySectionView(MethodView): if query.generate: cfg = '/tmp/haproxy-generated-config.cfg' os.system(f'touch {cfg}') - inv = service_mod.generate_haproxy_section_inv(body.model_dump(mode='json'), cfg) + inv = service_mod.generate_section_inv(body.model_dump(mode='json'), cfg, service) try: output = service_mod.run_ansible_locally(inv, 'haproxy_section') @@ -153,15 +153,17 @@ class HaproxySectionView(MethodView): def _edit_config(service, server: Server, body: HaproxyConfigRequest, action: Literal['create', 'delete'], **kwargs) -> str: cfg = config_common.generate_config_path(service, server.ip) if action == 'create': - inv = service_mod.generate_haproxy_section_inv(body.model_dump(mode='json'), cfg) + inv = service_mod.generate_section_inv(body.model_dump(mode='json'), cfg, service) else: - inv = service_mod.generate_haproxy_section_inv_for_del(cfg, kwargs.get('section_type'), kwargs.get('section_name')) + inv = service_mod.generate_section_inv_for_del(cfg, kwargs.get('section_type'), kwargs.get('section_name')) try: config_mod.get_config(server.ip, cfg, service=service) except Exception as e: raise e + os.system(f'cp {cfg} {cfg}.old') + try: output = service_mod.run_ansible_locally(inv, 'haproxy_section') except Exception as e: @@ -176,7 +178,7 @@ class HaproxySectionView(MethodView): else: action = 'save' - output = config_mod.master_slave_upload_and_restart(server.ip, cfg, action, 'haproxy') + output = config_mod.master_slave_upload_and_restart(server.ip, cfg, action, 'haproxy', oldcfg=f'{cfg}.old') return output diff --git a/app/views/service/nginx_section_views.py b/app/views/service/nginx_section_views.py new file mode 100644 index 00000000..9605a8c2 --- /dev/null +++ b/app/views/service/nginx_section_views.py @@ -0,0 +1,883 @@ +import os +from typing import Union, Literal + +from flask.views import MethodView +from flask_pydantic import validate +from flask import jsonify, g +from flask_jwt_extended import jwt_required +from playhouse.shortcuts import model_to_dict + +import app.modules.db.sql as sql +import app.modules.db.add as add_sql +import app.modules.db.server as server_sql +import app.modules.server.ssh as mod_ssh +import app.modules.config.config as config_mod +import app.modules.config.common as config_common +import app.modules.service.installation as service_mod +import app.modules.roxywi.common as roxywi_common +from app.middleware import get_user_params, page_for_admin, check_group, check_services +from app.modules.db.db_model import Server +from app.modules.roxywi.class_models import BaseResponse, DataStrResponse, NginxUpstreamRequest, IdDataStrResponse, \ + ErrorResponse, GenerateConfigRequest, NginxProxyPassRequest +from app.modules.common.common_classes import SupportClass + + +class NginxSectionView(MethodView): + methods = ['GET', 'POST', 'PUT', 'DELETE'] + decorators = [jwt_required(), get_user_params(), check_services, page_for_admin(level=3), check_group()] + + @staticmethod + def get(service: Literal['nginx'], section_type: str, section_name: str, server_id: Union[int, str]): + try: + server_id = SupportClass().return_server_ip_or_id(server_id) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, '') + + try: + server_sql.get_server_with_group(server_id, g.user_params['group_id']) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot find a server') + + try: + section = add_sql.get_section(server_id, section_type, section_name, service) + output = {'server_id': section.server_id.server_id, **model_to_dict(section, recurse=False), + 'id': f'{server_id}-{section_name}'} + output.update(section.config) + return jsonify(output) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get NGINX section') + + def post(self, + service: Literal['nginx'], + section_type: str, + server_id: Union[int, str], + body: Union[NginxUpstreamRequest, NginxProxyPassRequest], + query: GenerateConfigRequest + ): + try: + server_id = SupportClass().return_server_ip_or_id(server_id) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, '') + + try: + server = server_sql.get_server_with_group(server_id, g.user_params['group_id']) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot find a server') + if query.generate: + cfg = '/tmp/nginx-generated-config.conf' + os.system(f'touch {cfg}') + inv = service_mod.generate_section_inv(body.model_dump(mode='json'), cfg, service) + + try: + output = service_mod.run_ansible_locally(inv, 'nginx_section') + if len(output['failures']) > 0 or len(output['dark']) > 0: + raise Exception('Cannot create NGINX section. Check Apache error log') + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, f'Cannot create NGINX section: {e}') + try: + with open(cfg, 'r') as file: + conf = file.read() + except Exception as e: + raise Exception(f'error: Cannot read config file: {e}') + try: + os.remove(cfg) + except Exception: + pass + return DataStrResponse(data=conf).model_dump(mode='json'), 200 + + try: + output = self._edit_config(service, server, body, 'create') + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create NGINX section') + + if 'Fatal' in output or 'error' in output: + return ErrorResponse(error=output).model_dump(mode='json'), 500 + + try: + add_sql.insert_new_section(server_id, section_type, body.name, body, service) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot add NGINX section') + + return IdDataStrResponse(data=output, id=f'{server_id}-{body.name}').model_dump(mode='json'), 201 + + def put(self, + service: Literal['nginx'], + section_type: str, + section_name: str, + server_id: Union[int, str], + body: Union[NginxUpstreamRequest, NginxProxyPassRequest] + ): + try: + server_id = SupportClass().return_server_ip_or_id(server_id) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, '') + + try: + server = server_sql.get_server_with_group(server_id, g.user_params['group_id']) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot find a server') + + try: + output = self._edit_config(service, server, body, 'create') + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create HAProxy section') + + if 'Fatal' in output or 'error' in output: + return ErrorResponse(error=output).model_dump(mode='json'), 500 + else: + try: + add_sql.update_section(server_id, section_type, section_name, body, service) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot update NGINX section') + + return DataStrResponse(data=output).model_dump(mode='json'), 201 + + def delete(self, service: Literal['nginx'], section_type: str, section_name: str, server_id: Union[int, str]): + try: + server_id = SupportClass().return_server_ip_or_id(server_id) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, '') + + try: + server = server_sql.get_server_with_group(server_id, g.user_params['group_id']) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot find a server') + + try: + config_file_name = self._create_config_path(service, section_type, section_name) + with mod_ssh.ssh_connect(server.ip) as ssh: + ssh.remove_sftp(config_file_name) + add_sql.delete_section(server_id, section_type, section_name, service) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete NGINX section') + + return BaseResponse().model_dump(mode='json'), 204 + + @staticmethod + def _create_config_path(service: str, config_type: str, name: str) -> str: + service_dir = sql.get_setting(f'{service}_dir') + if config_type == 'upstream': + config_file_name = f'{service_dir}/conf.d/upstream_{name}.conf' + else: + config_file_name = f'{service_dir}/sites-enabled/proxy-pass_{name}.conf' + return config_file_name + + def _edit_config(self, service, server: Server, body: NginxUpstreamRequest, action: Literal['create', 'delete'], **kwargs) -> str: + cfg = config_common.generate_config_path(service, server.ip) + print('cfg', cfg) + config_file_name = self._create_config_path(service, body.type, body.name) + + if action == 'create': + inv = service_mod.generate_section_inv(body.model_dump(mode='json'), cfg, service) + else: + inv = service_mod.generate_section_inv_for_del(cfg, kwargs.get('section_type'), kwargs.get('section_name')) + + try: + config_mod.get_config(server.ip, cfg, service=service, config_file_name=config_file_name) + except Exception as e: + raise e + + os.system(f'mv {cfg} {cfg}.old') + + try: + output = service_mod.run_ansible_locally(inv, 'nginx_section') + except Exception as e: + raise e + + if len(output['failures']) > 0 or len(output['dark']) > 0: + raise Exception('Cannot create NGINX section. Check Apache error log') + + if body: + if body.action: + action = str(body.action) + else: + action = 'save' + + output = config_mod.master_slave_upload_and_restart(server.ip, cfg, action, service, config_file_name=config_file_name, oldcfg=f'{cfg}.old') + + return output + + +class UpstreamSectionView(NginxSectionView): + methods = ['GET', 'POST', 'PUT', 'DELETE'] + decorators = [jwt_required(), get_user_params(), check_services, page_for_admin(level=3), check_group()] + + def __init__(self): + self.section_type = 'upstream' + + @validate() + def get(self, service: Literal['nginx'], section_name: str, server_id: Union[int, str]): + """ + NginxUpstreamView API + + This is the NginxUpstreamView API where you can get configurations of NGINX sections. + + --- + tags: + - NGINX upstream section + parameters: + - name: service + in: path + type: string + required: true + enum: + - nginx + description: The service to which this section belongs. + - name: server_id + in: path + type: string + required: true + description: Server ID or IP address + - name: section_name + in: path + type: string + required: true + description: The name of the section to fetch. + responses: + 200: + description: NGINX upstream configuration. + schema: + type: object + properties: + config: + type: object + properties: + backend_servers: + type: array + items: + type: object + properties: + server: + type: string + port: + type: integer + max_fails: + type: integer + fail_timeout: + type: integer + name: + type: string + balance: + type: string + keepalive: + type: integer + id: + type: string + name: + type: string + server_id: + type: integer + type: + type: string + 400: + description: Invalid parameters. + 404: + description: Section not found. + 500: + description: Internal server error. + """ + return super().get(service, self.section_type, section_name, server_id) + + @validate(body=NginxUpstreamRequest, query=GenerateConfigRequest) + def post(self, + service: Literal['nginx'], + server_id: Union[int, str], + body: NginxUpstreamRequest, + query: GenerateConfigRequest + ): + """ + NginxUpstreamView API + + This is the NginxUpstreamView API where you can create NGINX sections. + + --- + tags: + - NGINX upstream section + parameters: + - name: service + in: path + type: string + required: true + enum: + - nginx + description: The service to which this section belongs. + - name: server_id + in: path + type: string + required: true + description: Server ID or IP address + - name: body + in: body + required: true + schema: + type: object + properties: + backend_servers: + type: array + items: + type: object + properties: + server: + type: string + port: + type: integer + max_fails: + type: integer + fail_timeout: + type: integer + name: + type: string + description: The name of the upstream + balance: + type: string + description: Could be 'round_robin', 'ip_hash', 'least_conn' or 'random' + default: round_robin + keepalive: + type: integer + default: 32 + responses: + 200: + description: NGINX section successfully created. + 400: + description: Invalid parameters. + 500: + description: Internal server error. + """ + return super().post(service, self.section_type, server_id, body, query) + + @validate(body=NginxUpstreamRequest) + def put(self, + service: Literal['nginx'], + server_id: Union[int, str], + section_name: str, + body: NginxUpstreamRequest, + query: GenerateConfigRequest + ): + """ + This is the NginxUpstreamView API where you can update the NGINX sections. + + --- + tags: + - NGINX upstream section + parameters: + - name: service + in: path + type: string + required: true + enum: + - nginx + description: The service to which this section belongs. + - name: server_id + in: path + type: string + required: true + description: Server ID or IP address + - name: section_name + in: path + type: string + required: true + description: The name of section to update. + - name: body + in: body + required: true + schema: + type: object + properties: + backend_servers: + type: array + items: + type: object + properties: + server: + type: string + port: + type: integer + max_fails: + type: integer + fail_timeout: + type: integer + name: + type: string + description: The name of the upstream to update. + balance: + type: string + description: Could be 'round_robin', 'ip_hash', 'least_conn' or 'random' + default: round_robin + keepalive: + type: integer + default: 32 + responses: + 200: + description: NGINX section successfully created. + 400: + description: Invalid parameters. + 500: + description: Internal server error. + """ + return super().put(service, self.section_type, section_name, server_id, body) + + @validate() + def delete(self, service: Literal['nginx'], section_name: str, server_id: Union[int, str]): + """ + NginxUpstreamView sections API + + This is the NginxUpstreamView API where you can delete configurations of NGINX sections. + + --- + tags: + - NGINX upstream section + parameters: + - name: service + in: path + type: string + required: true + enum: + - nginx + description: The service to which this section belongs. + - name: server_id + in: path + type: string + required: true + description: Server ID or IP address + - name: section_name + in: path + type: string + required: true + description: The name of the section to fetch. + responses: + 204: + description: NGINX section configuration. + """ + return super().delete(service, self.section_type, section_name, server_id) + + +class ProxyPassSectionView(NginxSectionView): + methods = ['GET', 'POST', 'PUT', 'DELETE'] + decorators = [jwt_required(), get_user_params(), check_services, page_for_admin(level=3), check_group()] + + def __init__(self): + self.section_type = 'proxy_pass' + + @validate() + def get(self, service: Literal['nginx'], section_name: str, server_id: Union[int, str]): + """ + NginxProxyPassView API + + This is the NginxProxyPassView API where you can get configurations of NGINX sections. + + --- + tags: + - NGINX proxy_pass section + parameters: + - name: service + in: path + type: string + required: true + enum: + - nginx + description: The service to which this section belongs. + - name: server_id + in: path + type: string + required: true + description: Server ID or IP address + - name: section_name + in: path + type: string + required: true + description: The name of the section to fetch. + responses: + 200: + description: Proxy Pass Section details retrieved successfully. + schema: + type: object + properties: + compression: + type: boolean + description: Indicates whether compression is enabled. + compression_level: + type: integer + description: Specifies the compression level (from 1 to 9). + compression_min_length: + type: integer + description: Minimum response size in bytes for compression to apply. + compression_types: + type: string + description: MIME types (space-separated) that compression applies to. + id: + type: string + description: Unique ID of the proxy pass section. + locations: + type: array + items: + type: object + properties: + location: + type: string + description: Path of the location block (e.g., `/`). + headers: + type: array + description: List of headers for the location. + items: + type: object + proxy_connect_timeout: + type: integer + description: Timeout value for connecting to the upstream server (in seconds). + proxy_read_timeout: + type: integer + description: Timeout for reading data from the upstream server (in seconds). + proxy_send_timeout: + type: integer + description: Timeout for sending data to the upstream server (in seconds). + upstream: + type: string + description: Name of the upstream server. + name: + type: string + description: Name of the proxy pass configuration. + port: + type: integer + description: Port on which this proxy pass runs. + scheme: + type: string + enum: + - http + - https + description: Protocol used by the proxy. + server_id: + type: integer + description: ID of the associated server. + ssl_crt: + type: string + description: SSL certificate file name. + ssl_key: + type: string + description: SSL private key file name. + ssl_offloading: + type: boolean + description: Indicates whether SSL offloading is enabled. + type: + type: string + enum: + - proxy_pass + description: Section type (e.g., "proxy_pass"). + 400: + description: Invalid parameters. + 404: + description: Section not found. + 500: + description: Internal server error. + """ + return super().get(service, self.section_type, section_name, server_id) + + @validate(body=NginxProxyPassRequest, query=GenerateConfigRequest) + def post(self, + service: Literal['nginx'], + server_id: Union[int, str], + body: NginxProxyPassRequest, + query: GenerateConfigRequest + ): + """ + NginxProxyPassView API + + This is the NginxProxyPassView API where you can create NGINX sections. + + --- + tags: + - NGINX proxy_pass section + parameters: + - name: service + in: path + type: string + required: true + enum: + - nginx + description: The service to which this section belongs. + - name: server_id + in: path + type: string + required: true + description: Server ID or IP address + - name: body + in: body + required: true + schema: + type: object + required: + - locations + - name + - scheme + - port + properties: + locations: + type: array + description: List of locations associated with this proxy pass section. + items: + type: object + required: + - location + - proxy_connect_timeout + - proxy_read_timeout + - proxy_send_timeout + - upstream + properties: + location: + type: string + description: Path of the location block (e.g., `/`). + default: / + proxy_connect_timeout: + type: string + description: Timeout value for connecting to the upstream server (in seconds). + default: 60 + proxy_read_timeout: + type: string + description: Timeout for reading data from the upstream server (in seconds). + default: 60 + proxy_send_timeout: + type: string + description: Timeout for sending data to the upstream server (in seconds). + default: 60 + headers: + type: array + description: List of headers for the location (currently empty). + items: + type: object + required: + - action + - name + properties: + action: + type: string + description: Action to perform on the header (e.g., "add_header"). + enum: + - add_header + - proxy_set_header + - proxy_hide_header + name: + type: string + description: Name of the header. + example: X-Real-IP + value: + type: string + description: Value of the header. + upstream: + type: string + description: Name of the upstream server. + name: + type: string + description: Domain name or IP of the proxy pass section. + scheme: + type: string + enum: + - http + - https + description: Scheme (protocol) for the proxy pass section. + port: + type: integer + description: Port number on which the proxy pass section operates. + ssl_offloading: + type: boolean + description: Indicates whether SSL offloading is enabled. + default: false + ssl_key: + type: string + description: SSL private key file name. Need if scheme is https. + ssl_crt: + type: string + description: SSL certificate file name. Need if scheme is https + compression_types: + type: string + description: Space-separated list of MIME types to be compressed. + default: text/plain text/css application/json application/javascript text/xml + compression_min_length: + type: string + description: Minimum response size in bytes for compression to apply. + default: 1024 + compression_level: + type: string + description: The compression level (e.g., 1 to 9). + default: 6 + responses: + 200: + description: NGINX section successfully created. + 400: + description: Invalid parameters. + 500: + description: Internal server error. + """ + return super().post(service, self.section_type, server_id, body, query) + + @validate(body=NginxProxyPassRequest) + def put(self, + service: Literal['nginx'], + server_id: Union[int, str], + section_name: str, + body: NginxProxyPassRequest, + query: GenerateConfigRequest + ): + """ + This is the NginxProxyPassView API where you can update the NGINX sections. + + --- + tags: + - NGINX proxy_pass section + parameters: + - name: service + in: path + type: string + required: true + enum: + - nginx + description: The service to which this section belongs. + - name: server_id + in: path + type: string + required: true + description: Server ID or IP address + - name: section_name + in: path + type: string + required: true + description: The name of section to update. + - name: body + in: body + required: true + schema: + type: object + required: + - locations + - name + - scheme + - port + properties: + locations: + type: array + description: List of locations associated with this proxy pass section. + items: + type: object + required: + - location + - proxy_connect_timeout + - proxy_read_timeout + - proxy_send_timeout + - upstream + properties: + location: + type: string + description: Path of the location block (e.g., `/`). + default: / + proxy_connect_timeout: + type: string + description: Timeout value for connecting to the upstream server (in seconds). + default: 60 + proxy_read_timeout: + type: string + description: Timeout for reading data from the upstream server (in seconds). + default: 60 + proxy_send_timeout: + type: string + description: Timeout for sending data to the upstream server (in seconds). + default: 60 + headers: + type: array + description: List of headers for the location (currently empty). + items: + type: object + required: + - action + - name + properties: + action: + type: string + description: Action to perform on the header (e.g., "add_header"). + enum: + - add_header + - proxy_set_header + - proxy_hide_header + name: + type: string + description: Name of the header. + example: X-Real-IP + value: + type: string + description: Value of the header. + upstream: + type: string + description: Name of the upstream server. + name: + type: string + description: Domain name or IP of the proxy pass section. + scheme: + type: string + enum: + - http + - https + description: Scheme (protocol) for the proxy pass section. + port: + type: integer + description: Port number on which the proxy pass section operates. + ssl_offloading: + type: boolean + description: Indicates whether SSL offloading is enabled. + default: false + ssl_key: + type: string + description: SSL private key file name. Need if scheme is https. + ssl_crt: + type: string + description: SSL certificate file name. Need if scheme is https + compression_types: + type: string + description: Space-separated list of MIME types to be compressed. + default: text/plain text/css application/json application/javascript text/xml + compression_min_length: + type: string + description: Minimum response size in bytes for compression to apply. + default: 1024 + compression_level: + type: string + description: The compression level (e.g., 1 to 9). + default: 6 + responses: + 200: + description: NGINX section successfully created. + 400: + description: Invalid parameters. + 500: + description: Internal server error. + """ + return super().put(service, self.section_type, section_name, server_id, body) + + @validate() + def delete(self, service: Literal['nginx'], section_name: str, server_id: Union[int, str]): + """ + NginxProxyPassView sections API + + This is the NginxProxyPassView API where you can delete configurations of NGINX sections. + + --- + tags: + - NGINX proxy_pass section + parameters: + - name: service + in: path + type: string + required: true + enum: + - nginx + description: The service to which this section belongs. + - name: server_id + in: path + type: string + required: true + description: Server ID or IP address + - name: section_name + in: path + type: string + required: true + description: The name of the section to fetch. + responses: + 204: + description: NGINX section configuration. + """ + return super().delete(service, self.section_type, section_name, server_id)