From b5af06708d2e428647b1af48fcbdc1a8b07323df Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Fri, 23 May 2025 12:16:58 +0330 Subject: [PATCH] feat: add API endpoint to fetch IP Limiter configuration --- core/cli_api.py | 18 +++++ .../routers/api/v1/config/hysteria.py | 10 ++- .../routers/api/v1/schema/response.py | 10 ++- core/scripts/webpanel/templates/settings.html | 79 +++++++++++++------ 4 files changed, 88 insertions(+), 29 deletions(-) diff --git a/core/cli_api.py b/core/cli_api.py index ad7a8e2..d491288 100644 --- a/core/cli_api.py +++ b/core/cli_api.py @@ -630,4 +630,22 @@ def config_ip_limiter(block_duration: int = None, max_ips: int = None): cmd_args.append('') run_cmd(cmd_args) + +def get_ip_limiter_config() -> dict[str, int | None]: + '''Retrieves the current IP Limiter configuration from .configs.env.''' + try: + if not os.path.exists(CONFIG_ENV_FILE): + return {"block_duration": None, "max_ips": None} + + env_vars = dotenv_values(CONFIG_ENV_FILE) + block_duration_str = env_vars.get('BLOCK_DURATION') + max_ips_str = env_vars.get('MAX_IPS') + + block_duration = int(block_duration_str) if block_duration_str and block_duration_str.isdigit() else None + max_ips = int(max_ips_str) if max_ips_str and max_ips_str.isdigit() else None + + return {"block_duration": block_duration, "max_ips": max_ips} + except Exception as e: + print(f"Error reading IP Limiter config from .configs.env: {e}") + return {"block_duration": None, "max_ips": None} # endregion diff --git a/core/scripts/webpanel/routers/api/v1/config/hysteria.py b/core/scripts/webpanel/routers/api/v1/config/hysteria.py index 20535ea..2f08f59 100644 --- a/core/scripts/webpanel/routers/api/v1/config/hysteria.py +++ b/core/scripts/webpanel/routers/api/v1/config/hysteria.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, BackgroundTasks, HTTPException, UploadFile, File from ..schema.config.hysteria import ConfigFile, GetPortResponse, GetSniResponse -from ..schema.response import DetailResponse, IPLimitConfig, SetupDecoyRequest, DecoyStatusResponse +from ..schema.response import DetailResponse, IPLimitConfig, SetupDecoyRequest, DecoyStatusResponse, IPLimitConfigResponse from fastapi.responses import FileResponse import shutil import zipfile @@ -335,6 +335,14 @@ async def config_ip_limit_api(config: IPLimitConfig): except Exception as e: raise HTTPException(status_code=400, detail=f'Error configuring IP Limiter: {str(e)}') +@router.get('/ip-limit/config', response_model=IPLimitConfigResponse, summary='Get IP Limiter Configuration') +async def get_ip_limit_config_api(): + """Retrieves the current IP Limiter configuration.""" + try: + config = cli_api.get_ip_limiter_config() + return IPLimitConfigResponse(**config) + except Exception as e: + raise HTTPException(status_code=500, detail=f'Error retrieving IP Limiter configuration: {str(e)}') def run_setup_decoy_background(domain: str, decoy_path: str): """Function to run decoy setup in the background.""" diff --git a/core/scripts/webpanel/routers/api/v1/schema/response.py b/core/scripts/webpanel/routers/api/v1/schema/response.py index 22ebbd2..acacc20 100644 --- a/core/scripts/webpanel/routers/api/v1/schema/response.py +++ b/core/scripts/webpanel/routers/api/v1/schema/response.py @@ -6,8 +6,12 @@ class DetailResponse(BaseModel): detail: str class IPLimitConfig(BaseModel): - block_duration: Optional[int] = None - max_ips: Optional[int] = None + block_duration: Optional[int] = Field(None, example=60) + max_ips: Optional[int] = Field(None, example=1) + +class IPLimitConfigResponse(BaseModel): + block_duration: Optional[int] = Field(None, description="Current block duration in seconds for IP Limiter") + max_ips: Optional[int] = Field(None, description="Current maximum IPs per user for IP Limiter") class SetupDecoyRequest(BaseModel): domain: str = Field(..., description="Domain name associated with the web panel") @@ -15,4 +19,4 @@ class SetupDecoyRequest(BaseModel): class DecoyStatusResponse(BaseModel): active: bool = Field(..., description="Whether the decoy site is currently configured and active") - path: Optional[str] = Field(None, description="The configured path for the decoy site, if active") + path: Optional[str] = Field(None, description="The configured path for the decoy site, if active") \ No newline at end of file diff --git a/core/scripts/webpanel/templates/settings.html b/core/scripts/webpanel/templates/settings.html index 5f0d733..c4c5c58 100644 --- a/core/scripts/webpanel/templates/settings.html +++ b/core/scripts/webpanel/templates/settings.html @@ -451,7 +451,9 @@ } else if (id === 'decoy_path') { fieldValid = isValidPath(input.val()); } else { - fieldValid = input.val().trim() !== ""; + if (input.attr('placeholder') && input.attr('placeholder').includes('Enter') && !input.attr('id').startsWith('ipv')) { + fieldValid = input.val().trim() !== ""; + } } if (!fieldValid) { @@ -521,22 +523,22 @@ }; Object.keys(servicesMap).forEach(service => { - let formSelector = servicesMap[service]; + let targetSelector = servicesMap[service]; let isRunning = data[service]; if (service === "hysteria_normal_sub") { - const $normalFormGroups = $("#normal_sub_service_form .form-group"); + const $normalForm = $("#normal_sub_service_form"); + const $normalFormGroups = $normalForm.find(".form-group"); const $normalStartBtn = $("#normal_start"); const $normalStopBtn = $("#normal_stop"); - const $normalAlert = $("#normal_sub_service_form .alert-info"); const $normalSubConfigTabLi = $(".normal-sub-config-tab-li"); if (isRunning) { $normalFormGroups.hide(); $normalStartBtn.hide(); $normalStopBtn.show(); - if ($normalAlert.length === 0) { - $("#normal_sub_service_form").prepend(`