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.
pull/418/head
Aidaho 2025-05-14 20:31:25 +03:00
parent da34363cb4
commit 40703576af
37 changed files with 2483 additions and 901 deletions

View File

@ -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/<section_name>', 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/<section_name>', 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/<section_name>', methods=['GET', 'PUT', 'DELETE'])
bp.add_url_rule('/service/<any(haproxy):service>/list/<list_name>/<color>', view_func=HaproxyListView.as_view('list_get'), methods=['GET'])
bp.add_url_rule('/service/<any(haproxy):service>/list', view_func=HaproxyListView.as_view('list_post'), methods=['POST', 'PUT', 'DELETE'])
bp.add_url_rule('/service/<any(nginx, apache):service>/<server_id>/config/list', view_func=ServiceConfigList.as_view('config_list_ip'), methods=['GET'])

View File

@ -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()

View File

@ -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)

View File

@ -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]
)

View File

@ -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'

View File

@ -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')

View File

@ -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)

View File

@ -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}')

View File

@ -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('/<any(haproxy):service>/list/<list_name>/<color>', view_func=HaproxyListView.as_view('list_get'), methods=['GET'])
bp.add_url_rule('/<any(haproxy):service>/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/<section_name>', 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/<section_name>', 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/<color>/<int:group>')
@validate()
def get_bwlists(color: Literal['black', 'white'], group):
@ -190,7 +200,8 @@ def delete_saved_server(server_id):
@bp.route('/certs/<int:server_id>')
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/<int:server_id>/<cert_id>', 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/<int:server_id>')
@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)

View File

@ -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 -%}

View File

@ -0,0 +1,9 @@
---
- name: Create NGINX section
hosts: localhost
connection: local
become: yes
become_method: sudo
gather_facts: yes
roles:
- role: nginx_section

View File

@ -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'

View File

@ -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 %}

View File

@ -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 %}
}

View File

@ -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

View File

@ -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 $("<li>")
.append("<div>" + item.value + "<br>" + item.desc + "</div>")
.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 += '</span>'
}
new_data += '<span class="list_of_lists">'
} else {
new_data += '</span><span class="list_of_lists">'
}
j += 1
new_data += ' <a onclick="view_ssl(\'' + data[i] + '\')" title="View ' + data[i] + ' cert">' + data[i] + '</a> '
}
}
$("#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 = '<p id="new_acl_p" style="border-bottom: 1px solid #ddd; padding-bottom: 10px;">\n' +
'<b class="padding10">'+if_word+'</b>\n' +
'<select name="acl_if">\n' +
@ -1569,15 +1254,7 @@ function make_actions_for_adding_bind(section_id) {
}
});
}
function makeid(length) {
let result = '';
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let charactersLength = characters.length;
for ( let i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
function changePortCheckFromServerPort() {
$('[name=server_port]').on('input', function () {
let iNum = parseInt($($(this)).val());

110
app/static/js/add_common.js Normal file
View File

@ -0,0 +1,110 @@
$( function() {
$('#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);
}
}
});
});
$("#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 $("<li>")
.append("<div>" + item.value + "<br>" + item.desc + "</div>")
.appendTo(ul);
};
});
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 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);
}
}
}
});
}

View File

@ -1,161 +1,263 @@
$( function() {
$(".redirectUpstream").on("click", function () {
resetProxySettings();
$("#tabs").tabs("option", "active", 1);
$("#serv").selectmenu("open");
});
$( "#serv" ).on('selectmenuchange',function() {
$(".redirectUpstream").on("click", function () {
resetProxySettings();
$("#tabs").tabs("option", "active", 2);
$("#serv2").selectmenu("open");
});
$(".redirectProxyPass").on("click", function () {
resetProxySettings();
$("#tabs").tabs("option", "active", 1);
$("#serv1").selectmenu("open");
});
$("#create-ssl-proxy_pass").on("click", function () {
resetProxySettings();
createSsl(1);
});
$("#serv2").on('selectmenuchange', function () {
$('#name').focus();
});
let add_server_let = '<br /><input name="servers" title="Backend IP" size=14 placeholder="xxx.xxx.xxx.xxx" class="form-control second-server" style="margin: 2px 0 4px 0;">: ' +
'<input name="server_port" required title="Backend port" size=3 placeholder="yyy" class="form-control second-server add_server_number" type="number"> ' +
'max_fails: <input name="max_fails" required title="By default, the number of unsuccessful attempts is set to 1" size=5 value="1" class="form-control add_server_number" type="number">' +
' fail_timeout: <input name="fail_timeout" required title="By default, the number of unsuccessful attempts is set to 1" size=5 value="1" class="form-control add_server_number" type="number">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 = '<p style="border-bottom: 1px solid #ddd; padding-bottom: 10px;" id="new_header_p">\n' +
'<select name="headers_res">' +
'<option value="------">------</option>' +
'<option value="add_header">add_header</option>' +
'<option value="proxy_set_header">proxy_set_header</option>' +
'<option value="proxy_hide_header">proxy_hide_header</option>' +
'</select>' +
'\t<b class="padding10">'+name_word+'</b>' +
'\t<input name="header_name" class="form-control">' +
'\t<b class="padding10">'+value_word+'</b>' +
'\t<input name="header_value" class="form-control">' +
'\t<span class="minus minus-style" id="new_header_minus" title="Delete this header"></span>' +
'</p>'
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 = $("<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, "<br>");
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, "<br>");
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);
});
}
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')
}

View File

@ -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, "<br>");
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" );
}
}
}
});
}

View File

@ -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 = '<li><a href="'+browse_history[i]+'" title="'+title[i]+'">'+link_text[i]+'</a></li>'
@ -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 "<span class="need-field">*</span>" 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);

View File

@ -40,3 +40,8 @@ const add_userlist_var = '<p><input name="userlist-user" title="User name" place
const add_peer_var = '<p><input name="servers_name" required title="Peer name" size=14 placeholder="haproxyN" class="form-control">: ' +
'<input name="servers" title="Backend IP" size=14 placeholder="xxx.xxx.xxx.xxx" class="form-control second-server">: ' +
'<input name="server_port" required title="Backend port" size=3 placeholder="yyy" class="form-control second-server add_server_number" type="number"></p>'
const add_server_nginx_var = '<p><input name="servers" title="Backend IP" size=14 placeholder="xxx.xxx.xxx.xxx" class="form-control second-server" style="margin: 2px 0 4px 0;">: ' +
'<input name="server_port" required title="Backend port" size=3 placeholder="yyy" class="form-control second-server add_server_number" type="number"> ' +
'max_fails: <input name="max_fails" required title="By default, the number of unsuccessful attempts is set to 1" size=5 value="1" class="form-control add_server_number" type="number">' +
' fail_timeout: <input name="fail_timeout" required title="By default, the number of unsuccessful attempts is set to 1" size=5 value="1" class="form-control add_server_number" type="number">s ' +
' <span class="minus minus-style" id="" title="Remove the server" onclick="$(this).parent().remove()"></span></p>'

View File

@ -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 %}
<script src="/static/js/add.js"></script>
<script src="/static/js/add_common.js"></script>
<script src="/static/js/edit_config.js"></script>
<div id="tabs">
<ul>
@ -100,52 +103,7 @@
</div>
<div id="add-servers">
<table class="overview" id="servers_table">
<tr class="overviewHead">
<td class="padding10 first-collumn">{{lang.words.server|title()}}</td>
<td class="padding10 first-collumn">{{lang.words.desc|title()}}</td>
<td></td>
</tr>
{% for s in saved_servers %}
<tr id="servers-saved-{{ s.id }}" class="{{ loop.cycle('odd', 'even') }}">
{% if s.groups|string() == g.user_params['group_id']|string() or group|string() == '1' %}
<td class="padding10 first-collumn">
<input type="text" id="servers-ip-{{ s.id }}" value="{{ s.server }}" size="15" class="form-control">
</td>
<td class="padding10 first-collumn" style="width: 77%;">
<input type="text" id="servers-desc-{{ s.id }}" value="{{ s.description }}" size="50" class="form-control">
</td>
<td>
<a class="delete" onclick="confirmDeleteSavedServer({{ s.id }})" title="{{lang.words.delete|title()}} {{lang.words.server}} {{s.server}}" style="cursor: pointer;"></a>
</td>
{% endif %}
</tr>
{% endfor %}
</table>
<br /><span class="add-button" title="{{lang.words.add|title()}} {{lang.words.server}}" id="add-saved-server-button">+ {{lang.words.add|title()}}</span>
<br /><br />
<table class="overview" id="saved-server-add-table" style="display: none;">
<tr class="overviewHead">
<td class="padding10 first-collumn">{{lang.words.server|title()}}</td>
<td>{{lang.words.desc|title()}}</td>
<td></td>
</tr>
<tr>
<td class="padding10 first-collumn">
{{ input('new-saved-servers', size='15') }}
</td>
<td style="width: 77%;">
{{ input('new-saved-servers-description', size='50') }}
</td>
<td>
<span class="add-admin" id="add-saved-server-new" title="{{lang.words.add|title()}} {{lang.words.new|title()}} {{lang.words.server|title()}}" style="cursor: pointer;"></span>
</td>
</tr>
</table>
<div id="ajax-servers"></div>
<div class="add-note alert addName alert-info" style="width: inherit; margin-right: 15px;">
{{lang.add_page.desc.servers}}
</div>
{% include 'include/add/servers.html' %}
</div>
<div id="userlist">
{% 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</label><input type="checkbox" name="backup" value="1" id="' + uniqId + '">');
}
</script>
{% endif %}
{% endblock %}

View File

@ -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'} %}
<script src="/static/js/add_nginx.js"></script>
<script src="/static/js/add_common.js"></script>
{% if user_subscription.user_status == 0 %}
{% include 'include/no_sub.html' %}
{% else %}
<div id="tabs">
<ul>
<li><a href="#create" title="{{lang.words.add|title()}} {{lang.words.proxy}}: {{lang.words.add|title()}} {{lang.words.proxy}} - Roxy-WI">{{lang.words.add|title()}} {{lang.words.proxy}}</a></li>
<li><a href="#proxypass" title="{{lang.words.add|title()}} {{lang.words.proxy}}: {{lang.words.add|title()}} Proxy pass - Roxy-WI">{{lang.words.add|title()}} Proxy pass</a></li>
<li><a href="#upstream" title="{{lang.words.add|title()}} {{lang.words.proxy}}: {{lang.words.create|title()}} {{lang.words.upstream|title()}} - Roxy-WI">{{lang.words.upstream|title()}}</a></li>
<li><a href="#add-servers" title="{{lang.words.add|title()}} {{lang.words.proxy}}: {{lang.words.servers|title()}} {{lang.words.templates}} - Roxy-WI">{{lang.words.servers|title()}}</a></li>
</ul>
<ul id='browse_histroy'></ul>
{% include 'include/add_nginx_proxy.html' %}
<div id="upstream">
<form name="add-upstream" id="add-upstream" action="/add/nginx/upstream" method="post">
<table class="add-table">
<caption><h3>{{lang.words.add|title()}} upstream</h3></caption>
<tr>
<td class="addName">{{lang.words.select|title()}} {{lang.words.w_a}} {{lang.words.server}}: </td>
<td class="addOption">
{{ select('serv', values=g.user_params['servers'], is_servers='true') }}
<div class="tooltip tooltipTop"><b>{{lang.words.note|title()}}:</b> {{lang.phrases.master_slave}}</div>
</td>
<td rowspan="5" class="add-note addName alert-info">
{{lang.add_nginx_page.desc.upstream_desc1}}
<br /><br />
{{lang.add_nginx_page.desc.upstream_desc2}}
<br /><br />
{{lang.add_nginx_page.desc.upstream_desc3}}
</td>
</tr>
<tr>
<td class="addName">{{lang.words.name|title()}}:</td>
<td class="addOption">
{{ input('name', name='upstream', title="Name upstream", placeholder="backend_servers", required='required') }}
</td>
</tr>
<tr class="advance">
<td class="addName">{{lang.words.balance|title()}}: </td>
<td class="addOption">
{{ select('balance', values=balance_params, selected='round-robin', required='required', class='force_close') }}
</td>
</tr>
<tr class="advance">
<td class="addName"><span title="{{lang.add_nginx_page.desc.keepalive}}" data-help="{{lang.add_nginx_page.desc.keepalive}}">Keepalive:</span></td>
<td class="addOption">
{{ input('name', name='keepalive', title=lang.add_nginx_page.desc.keepalive, placeholder="32") }}
</td>
</tr>
<tr>
<td class="addName">{{lang.words.servers|title()}}:</td>
<td class="addOption">
{% include 'include/add_nginx_servers.html' %}
</td>
</tr>
<tr class="advance-show">
<td class="addOption" colspan="2">
<button title="{{lang.add_page.buttons.show_full_settings}}" class="row-down advance-show-button">{{lang.words.show|title()}} {{lang.words.advanced}} {{lang.words.settings}}</button>
<button title="{{lang.add_page.buttons.hide_full_settings}}" class="row-up advance-hide-button" style="display: none">{{lang.words.hide|title()}} {{lang.words.advanced}} {{lang.words.settings}}</button>
</td>
</tr>
<tr>
<td class="addButton">
<a class="ui-button ui-widget ui-corner-all" title="{{lang.words.add|title()}} {{lang.words.upstream|title()}}" onclick="addProxy('add-upstream')">{{lang.words.add|title()}}</a>
</td>
<td class="addButton">
<a class="ui-button ui-widget ui-corner-all" title="{{lang.words.generate|title()}} {{lang.words.and}} {{lang.words.display}} {{lang.words.config}}" onclick="generateConfig('add-upstream')">{{lang.words.generate|title()}} {{lang.words.config}}</a>
</td>
</tr>
</table>
</form>
{% include 'include/add_nginx/add_nginx_proxy.html' %}
<div id="proxypass">
{% include 'include/add_nginx/proxy_pass.html' %}
</div>
<div id="upstream">
{% include 'include/add_nginx/upstream.html' %}
</div>
<div id="add-servers">
{% include 'include/add/servers.html' %}
</div>
<div id="dialog-confirm-cert-edit" title="View certificate " style="display: none;">
<span><b>Note:</b> Each new address must be specified from a new line</span>
<textarea id="edit_lists" style="width: 100%" rows=20></textarea>
@ -81,6 +36,7 @@
</div>
<input type="hidden" id="group_id" value="{{ g.user_params['group_id'] }}">
</div>
{% include 'include/del_confirm.html' %}
<script>
$( function() {
if (window.matchMedia('(max-width: 1280px)').matches || window.matchMedia('(max-width: 1024px)').matches || window.matchMedia('(max-width: 667px)').matches) {
@ -90,4 +46,5 @@ $( function() {
}
});
</script>
{% endif %}
{% endblock %}

View File

@ -6,7 +6,11 @@
{% if role <= 3 %}
{% if not is_serv_protected or role <= 2 %}
{% if not configver %}
{% if service != 'nginx' %}
<a class="ui-button ui-widget ui-corner-all" title="Edit this run config" id="edit_link" href="/config/{{service}}/{{serv}}/edit/{{config_file_name}}">{{lang.words.edit|title()}}</a>
{% else %}
<a class="ui-button ui-widget ui-corner-all" title="Edit this run config" id="edit_link" onclick="openNginxSection('{{config_file_name.split("92")[-1].split(".conf")[0]}}')">{{lang.words.edit|title()}}</a>
{% endif %}
{% endif %}
{% if service == 'haproxy' %}
<a class="ui-button ui-widget ui-corner-all" title="{{lang.words.add|title()}} {{lang.words.proxy}}" href="/add/haproxy#proxy">{{lang.words.add|title()}}</a>
@ -76,6 +80,12 @@
</span><div>
{% continue %}
{% endif %}
{%- if "upstream {" in line -%}
</div>
<span class="param">{{ line }}
</span><div>
{% 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 -%}
</div>
</div>
</div>
{% if configver %}
<br>
{% if role <= 3 %}

View File

@ -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'} %}
<div id="edit-proxy_pass" style="display: none;">
{% include 'include/add_nginx/proxy_pass.html' %}
</div>
<div id="edit-upstream" style="display: none;">
{% include 'include/add_nginx/upstream.html' %}
</div>

View File

@ -0,0 +1,46 @@
<table class="overview" id="servers_table">
<tr class="overviewHead">
<td class="padding10 first-collumn">{{lang.words.server|title()}}</td>
<td class="padding10 first-collumn">{{lang.words.desc|title()}}</td>
<td></td>
</tr>
{% for s in saved_servers %}
<tr id="servers-saved-{{ s.id }}" class="{{ loop.cycle('odd', 'even') }}">
{% if s.groups|string() == g.user_params['group_id']|string() or group|string() == '1' %}
<td class="padding10 first-collumn">
<input type="text" id="servers-ip-{{ s.id }}" value="{{ s.server }}" size="15" class="form-control">
</td>
<td class="padding10 first-collumn" style="width: 77%;">
<input type="text" id="servers-desc-{{ s.id }}" value="{{ s.description }}" size="50" class="form-control">
</td>
<td>
<a class="delete" onclick="confirmDeleteSavedServer({{ s.id }})" title="{{lang.words.delete|title()}} {{lang.words.server}} {{s.server}}" style="cursor: pointer;"></a>
</td>
{% endif %}
</tr>
{% endfor %}
</table>
<br /><span class="add-button" title="{{lang.words.add|title()}} {{lang.words.server}}" id="add-saved-server-button">+ {{lang.words.add|title()}}</span>
<br /><br />
<table class="overview" id="saved-server-add-table" style="display: none;">
<tr class="overviewHead">
<td class="padding10 first-collumn">{{lang.words.server|title()}}</td>
<td>{{lang.words.desc|title()}}</td>
<td></td>
</tr>
<tr>
<td class="padding10 first-collumn">
{{ input('new-saved-servers', size='15') }}
</td>
<td style="width: 77%;">
{{ input('new-saved-servers-description', size='50') }}
</td>
<td>
<span class="add-admin" id="add-saved-server-new" title="{{lang.words.add|title()}} {{lang.words.new|title()}} {{lang.words.server|title()}}" style="cursor: pointer;"></span>
</td>
</tr>
</table>
<div id="ajax-servers"></div>
<div class="add-note alert addName alert-info" style="width: inherit; margin-right: 15px;">
{{lang.add_page.desc.servers}}
</div>

View File

@ -0,0 +1,62 @@
{% if add %}
<div class="alert alert-success" style="position: absolute;top: 45px;left: 5px;">
<div id="close">
<span title="Close" style="cursor: pointer; float: right;">X</span>
</div>
<h3>{{ add }} {{lang.add_page.desc.was_success_added}}</h3>
{{ conf_add }}
</div>
<script>
$('#close').click(function(){
$('.alert-success').remove();
});
</script>
{% endif %}
<div id="create" style="margin-top: 20px;">
<div id="left-collumn">
<div class="div-pannel">
<div class="div-server add-proxy-listen-head">
<div class="server-name">
<span title="{{lang.words.create|title()}} Proxy pass" class="redirectProxyPass span-link">{{lang.words.create|title()}} HTTP proxy pass</span>
</div>
<div class="server-desc add_proxy">
{{lang.add_page.desc.create_nginx_proxy}}
</div>
</div>
</div>
<div class="div-pannel">
<div class="div-server add-proxy-listen-head">
<div class="server-name">
<span title="{{lang.words.create|title()}} Proxy pass" class="redirectProxyPass span-link" id="create-ssl-proxy_pass">{{lang.words.create|title()}} HTTPS proxy pass</span>
</div>
<div class="server-desc add_proxy">
{{lang.add_page.desc.create_nginx_ssl_proxy}} <a href="{{ url_for('service.ssl_service', service='nginx') }}" target="_blank">{{lang.words.uploaded}} {{lang.words.w_a}} PEM {{lang.words.cert}}</a>
</div>
</div>
</div>
</div>
<div id="middle-collumn">
<div class="div-pannel">
<div class="div-server add-proxy-frontend-head">
<div class="server-name">
<span title="{{lang.words.create|title()}} {{lang.words.upstream|title()}}" class="redirectUpstream span-link">{{lang.words.create|title()}} {{lang.words.upstream|title()}}</span>
</div>
<div class="server-desc add_proxy">
{{lang.add_nginx_page.desc.upstream_desc1}}
</div>
</div>
</div>
<div class="div-pannel">
<div class="div-server add-proxy-frontend-head">
<div class="server-name">
<span title="{{lang.words.create|title()}} {{lang.words.w_a}} {{lang.words.server}} {{lang.words.template}}" class="span-link" id="add5">{{lang.words.create|title()}} {{lang.words.w_a}} {{lang.words.server}} {{lang.words.template}}</span>
</div>
<div class="server-desc add_proxy">
{{lang.add_page.desc.server_temp}}
</div>
</div>
</div>
</div>
<div id="right-collumn">
</div>
</div>

View File

@ -4,20 +4,24 @@
}
</style>
<span name="add_servers">
<p>
<input name="servers" required title="{{lang.words.backend|title()}} IP" size=14 placeholder="xxx.xxx.xxx.xxx" class="form-control">:
<input name="server_port" required title="{{lang.words.backend|title()}} {{lang.words.port}}" size=8 placeholder="yyy" class="form-control add_server_number" type="number">
<span name="max_fails">max_fails:</span> <input name="max_fails" required title="" data-help="{{lang.add_nginx_page.desc.max_fails}}" size=8 class="form-control add_server_number" value="1" type="number">
<span name="fail_timeout">fail_timeout:</span> <input name="fail_timeout" required size=8 value="1" class="form-control add_server_number" type="number" title="" data-help="{{lang.add_nginx_page.desc.fail_timeout}}">s
<br />
</p>
<p>
<input name="servers" title="{{lang.words.backend|title()}} IP" size=14 placeholder="xxx.xxx.xxx.xxx" class="form-control second-server"><span class="second-server">:</span>
<input name="server_port" title="{{lang.words.backend|title()}} {{lang.words.port}}" size=8 placeholder="yyy" class="form-control second-server add_server_number" type="number">
<span name="max_fails">max_fails:</span> <input name="max_fails" required title="" data-help="{{lang.add_nginx_page.desc.max_fails}}" size=8 class="form-control add_server_number" value="1" type="number">
<span name="fail_timeout">fail_timeout:</span> <input name="fail_timeout" required size=8 value="1" class="form-control add_server_number" type="number" title="" data-help="{{lang.add_nginx_page.desc.fail_timeout}}">s
<br />
</p>
<p>
<input name="servers" title="{{lang.words.backend|title()}} IP" size=14 placeholder="xxx.xxx.xxx.xxx" class="form-control second-server"><span class="second-server">:</span>
<input name="server_port" title="{{lang.words.backend|title()}} {{lang.words.port}}" size=3 placeholder="yyy" class="form-control second-server add_server_number" type="number">
<span name="max_fails">max_fails:</span> <input name="max_fails" required size=8 class="form-control add_server_number" value="1" type="number" title="" data-help="{{lang.add_nginx_page.desc.max_fails}}">
<span name="fail_timeout">fail_timeout:</span> <input name="fail_timeout" required size=8 value="1" class="form-control add_server_number" type="number" title="" data-help="{{lang.add_nginx_page.desc.fail_timeout}}">s
</p>
</span>
<span>
<a class="link add-server backend_server" name="add-server-input" title="{{lang.words.add|title()}} upstream" style="cursor: pointer;"></a>

View File

@ -0,0 +1,123 @@
{% set header_res = {'add_header': 'add_header', 'proxy_set_header': 'proxy_set_header', 'proxy_hide_header': 'proxy_hide_header'} %}
<style>
.proxy-timeout {
width: 140px;
display: inline-block;
}
</style>
<form name="add-proxy_pass" id="add-proxy_pass" action="/add/nginx/proxy_pass" method="post">
<table class="add-table">
<caption><h3>{{lang.words.add|title()}} proxy pass</h3></caption>
<tr>
<td class="addName">{{lang.words.select|title()}} {{lang.words.w_a}} {{lang.words.server}}: </td>
<td class="addOption">
{{ select('serv1', name='server', values=g.user_params['servers'], is_servers='true', by_id='true') }}
<div class="tooltip tooltipTop"><b>{{lang.words.note|title()}}:</b> {{lang.phrases.master_slave}}</div>
</td>
<td rowspan="5" class="add-note addName alert-info">
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:
<br />
<br />
Header control: Uses proxy_set_header to forward client IP (X-Real-IP) or protocol (X-Forwarded-Proto).
<br />
<br />
Timeouts: Timeouts (proxy_connect_timeout).
<br />
<br />
(Summary: Its the backbone of Nginx reverse proxying, handling routing, balancing, and security.)
</td>
</tr>
<tr>
<td class="addName">{{lang.words.name|title()}}:</td>
<td class="addOption">
{{ input('proxy_pass', name='name', title="Domain name or IP", placeholder="example.com", required='required') }}
</td>
</tr>
<tr>
<td class="addName">
HTTP {{lang.words.scheme|title()}}:
</td>
<td class="addOption">
{% set scheme_params={'http': 'HTTP', 'https': 'HTTPS'} %}
{{ select('scheme', values=scheme_params, selected='http', required='required', class='force_close') }}
<br>
<br>
<div id="hide-scheme" style="display: none;">
{{ checkbox('ssl_offloading', title=lang.add_page.desc.http_https, desc='HTTP->HTTPS') }}<br>
<span class="tooltip tooltipTop">{{lang.words.enter2|title()}} {{lang.words.name}} {{lang.words.of}} {{lang.words.file2}}, {{lang.add_page.desc.press_down}}:</span><br />
{{ input('ssl_key', placeholder='cert.key') }}
{{ input('ssl_crt', placeholder='cert.crt') }}
</div>
</td>
</tr>
<tr>
<td class="addName">
{{lang.words.port|title()}}:
</td>
<td class="addOption">
{{ input('port', name='port', title='Port to bind', placeholder="80", required='required', type='number', style='width: 40px;') }}
</td>
</tr>
<tr>
<td class="addName">Location:</td>
<td class="addOption nginx-location">
<p>
{{ input('location', name='location', title="Location", value="/", required='required') }}
<br>
<br>
<span class="proxy-timeout">Proxy connect timeout:</span>
{{ input('proxy_connect_timeout', name='proxy_connect_timeout', title='Proxy connect timeout', value="60", type='number', style='width: 40px;') }}
<br>
<span class="proxy-timeout">Proxy read timeout:</span>
{{ input('proxy_read_timeout', name='proxy_read_timeout', title='Proxy read timeout', value="60", type='number', style='width: 40px;') }}
<br>
<span class="proxy-timeout">Proxy send timeout:</span>
{{ input('proxy_send_timeout', name='proxy_send_timeout', title='Proxy send timeout', value="60", type='number', style='width: 40px;') }}<br><br>
{{lang.words.headers|title()}}: <span title="{{lang.words.add|title()}} {{lang.words.headers}}" id="show_header" class="link add-server"></span>
<div id="header_div" style="display: none;">
<p style="border-bottom: 1px solid #ddd; padding-bottom: 10px;" id="header_p">
{{ select('headers_res', name='headers_res', values=header_res, first='------', class='force_close') }}
<b class="padding10">{{lang.words.name}}</b>
{{ input('header_name', name="header_name") }}
<b class="padding10">{{lang.words.value}}</b>
{{ input('header_value', name="header_value") }}
<span class="minus minus-style" onclick="deleteId('header_p')" title="{{lang.words.delete|title()}}"></span>
</p>
</div>
<span>
<a class="link add-server" id="add_header" title="{{lang.words.add|title()}} {{lang.words.headers}}" style="display: none;"></a>
</span>
<br>
Upstream: {{ input('proxy_pass-upstream', name='upstream', placeholder='upstream_config') }}
</p>
</td>
</tr>
<tr>
<td class="addName">{{ lang.words.compression|title() }}:</td>
<td class="addOption">
{{ checkbox('compression', title=lang.add_page.desc.http_compression, value='true', desc=lang.words.compression|title()) }}
<div id="compression-options" style="display: none;">
<span class="proxy-timeout">{{ lang.words.types|title() }}:</span>
{{ input('compression_types', value='text/plain text/css application/json application/javascript text/xml', style='width: 250px;') }}<br>
<span class="proxy-timeout">{{ lang.words.min|title() }} {{ lang.words.length }}:</span>
{{ input('compression_min_length', value='1024', type='number', style='width: 50px;') }}<br>
<span class="proxy-timeout">{{ lang.words.level|title() }}:</span>
{{ input('compression_level', value='6', type='number', style='width: 50px;') }}
</div>
</td>
</tr>
<tr id="proxy_pass-add-buttons">
<td class="addButton">
<a class="ui-button ui-widget ui-corner-all" title="{{lang.words.add|title()}} proxy pass" onclick="addProxy('add-proxy_pass')">{{lang.words.add|title()}}</a>
</td>
<td class="addButton">
<a class="ui-button ui-widget ui-corner-all" title="{{lang.words.generate|title()}} {{lang.words.and}} {{lang.words.display}} {{lang.words.config}}" onclick="addProxy('add-proxy_pass', generate=true)">{{lang.words.generate|title()}} {{lang.words.config}}</a>
</td>
</tr>
</table>
</form>
<script>
$("#scheme" ).selectmenu({
width: 100
});
</script>

View File

@ -0,0 +1,57 @@
<form name="add-upstream" id="add-upstream" action="/add/nginx/upstream" method="post">
<table class="add-table">
<caption><h3>{{lang.words.add|title()}} upstream</h3></caption>
<tr>
<td class="addName">{{lang.words.select|title()}} {{lang.words.w_a}} {{lang.words.server}}: </td>
<td class="addOption">
{{ select('serv2', name='server', values=g.user_params['servers'], is_servers='true', by_id='true') }}
<div class="tooltip tooltipTop"><b>{{lang.words.note|title()}}:</b> {{lang.phrases.master_slave}}</div>
</td>
<td rowspan="5" class="add-note addName alert-info">
{{lang.add_nginx_page.desc.upstream_desc1}}
<br /><br />
{{lang.add_nginx_page.desc.upstream_desc2}}
<br /><br />
{{lang.add_nginx_page.desc.upstream_desc3}}
</td>
</tr>
<tr>
<td class="addName">{{lang.words.name|title()}}:</td>
<td class="addOption">
{{ input('upstream-name', name='name', title="Name upstream", placeholder="backend_servers", required='required') }}
</td>
</tr>
<tr class="advance">
<td class="addName">{{lang.words.balance|title()}}: </td>
<td class="addOption">
{{ select('balance', values=balance_params, selected='round_robin', required='required', class='force_close') }}
</td>
</tr>
<tr class="advance">
<td class="addName"><span title="{{lang.add_nginx_page.desc.keepalive}}" data-help="{{lang.add_nginx_page.desc.keepalive}}">Keepalive:</span></td>
<td class="addOption">
{{ input('keepalive', name='keepalive', title=lang.add_nginx_page.desc.keepalive, value="32") }}
</td>
</tr>
<tr>
<td class="addName">{{lang.words.servers|title()}}:</td>
<td class="addOption">
{% include 'include/add_nginx/add_nginx_servers.html' %}
</td>
</tr>
<tr class="advance-show">
<td class="addOption" colspan="2">
<button title="{{lang.add_page.buttons.show_full_settings}}" class="row-down advance-show-button">{{lang.words.show|title()}} {{lang.words.advanced}} {{lang.words.settings}}</button>
<button title="{{lang.add_page.buttons.hide_full_settings}}" class="row-up advance-hide-button" style="display: none">{{lang.words.hide|title()}} {{lang.words.advanced}} {{lang.words.settings}}</button>
</td>
</tr>
<tr id="upstream-add-buttons">
<td class="addButton">
<a class="ui-button ui-widget ui-corner-all" title="{{lang.words.add|title()}} {{lang.words.upstream|title()}}" onclick="addProxy('add-upstream')">{{lang.words.add|title()}}</a>
</td>
<td class="addButton">
<a class="ui-button ui-widget ui-corner-all" title="{{lang.words.generate|title()}} {{lang.words.and}} {{lang.words.display}} {{lang.words.config}}" onclick="addProxy('add-upstream', generate=true)">{{lang.words.generate|title()}} {{lang.words.config}}</a>
</td>
</tr>
</table>
</form>

View File

@ -1,32 +0,0 @@
{% if add %}
<div class="alert alert-success" style="position: absolute;top: 45px;left: 5px;">
<div id="close">
<span title="Close" style="cursor: pointer; float: right;">X</span>
</div>
<h3>{{ add }} {{lang.add_page.desc.was_success_added}}</h3>
{{ conf_add }}
</div>
<script>
$('#close').click(function(){
$('.alert-success').remove();
});
</script>
{% endif %}
<div id="create" style="margin-top: 20px;">
<div id="left-collumn">
<div class="div-pannel">
<div class="div-server add-proxy-listen-head">
<div class="server-name">
<span title="{{lang.words.create|title()}} {{lang.words.upstream|title()}}" class="redirectUpstream span-link">{{lang.words.create|title()}} {{lang.words.upstream|title()}}</span>
</div>
<div class="server-desc add_proxy">
{{lang.add_nginx_page.desc.upstream_desc1}}
</div>
</div>
</div>
</div>
<div id="middle-collumn">
</div>
<div id="right-collumn">
</div>
</div>

View File

@ -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. Its 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",
}
%}

View File

@ -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",
}
%}

View File

@ -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",
}
%}

View File

@ -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": "длина",
}
%}

View File

@ -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

View File

@ -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)