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(`
NormalSub service is running. You can stop it or configure its subpath.
`); + if ($normalForm.find(".alert-info").length === 0) { + $normalForm.prepend(`
NormalSub service is running. You can stop it or configure its subpath.
`); } $normalSubConfigTabLi.show(); fetchNormalSubPath(); @@ -544,7 +546,7 @@ $normalFormGroups.show(); $normalStartBtn.show(); $normalStopBtn.hide(); - $("#normal_sub_service_form .alert-info").remove(); + $normalForm.find(".alert-info").remove(); $normalSubConfigTabLi.hide(); if ($('#normal-sub-config-link-tab').hasClass('active')) { $('#normal-tab').tab('show'); @@ -553,31 +555,40 @@ $("#normal_subpath_input").removeClass('is-invalid'); } } else if (service === "hysteria_iplimit") { + const $ipLimitServiceForm = $("#ip_limit_service_form"); + const $configTabLi = $(".ip-limit-config-tab-li"); if (isRunning) { $("#ip_limit_start").hide(); $("#ip_limit_stop").show(); - $(".ip-limit-config-tab-li").show(); - // TODO: Fetch IP Limit Config and populate fields + $configTabLi.show(); + fetchIpLimitConfig(); + if ($ipLimitServiceForm.find(".alert-info").length === 0) { + $ipLimitServiceForm.prepend(`
IP-Limit service is running. You can stop it if needed.
`); + } } else { $("#ip_limit_start").show(); $("#ip_limit_stop").hide(); - $(".ip-limit-config-tab-li").hide(); + $configTabLi.hide(); $('#ip-limit-service-tab').tab('show'); - // TODO: Clear IP Limit Config fields + $ipLimitServiceForm.find(".alert-info").remove(); + $("#block_duration").val(""); + $("#max_ips").val(""); + $("#block_duration, #max_ips").removeClass('is-invalid'); } } else { + const $formSelector = $(targetSelector); if (isRunning) { - $(formSelector + " .form-group").hide(); - $(formSelector + " .btn-success").hide(); - $(formSelector + " .btn-danger").show(); - if ($(formSelector + " .alert-info").length === 0) { - $(formSelector).prepend(`
Service is running. You can stop it if needed.
`); + $formSelector.find(".form-group").hide(); + $formSelector.find(".btn-success").hide(); + $formSelector.find(".btn-danger").show(); + if ($formSelector.find(".alert-info").length === 0) { + $formSelector.prepend(`
Service is running. You can stop it if needed.
`); } } else { - $(formSelector + " .form-group").show(); - $(formSelector + " .btn-success").show(); - $(formSelector + " .btn-danger").hide(); - $(formSelector + " .alert-info").remove(); + $formSelector.find(".form-group").show(); + $formSelector.find(".btn-success").show(); + $formSelector.find(".btn-danger").hide(); + $formSelector.find(".alert-info").remove(); } } }); @@ -596,7 +607,24 @@ error: function (xhr, status, error) { console.error("Failed to fetch NormalSub subpath:", error, xhr.responseText); $("#normal_subpath_input").val(""); - // Swal.fire("Error!", "Could not fetch NormalSub subpath.", "error"); // Avoid too many popups during init + } + }); + } + + function fetchIpLimitConfig() { + $.ajax({ + url: "{{ url_for('get_ip_limit_config_api') }}", + type: "GET", + success: function (data) { + $("#block_duration").val(data.block_duration || ""); + $("#max_ips").val(data.max_ips || ""); + if (data.block_duration) $("#block_duration").removeClass('is-invalid'); + if (data.max_ips) $("#max_ips").removeClass('is-invalid'); + }, + error: function (xhr, status, error) { + console.error("Failed to fetch IP Limit config:", error, xhr.responseText); + $("#block_duration").val(""); + $("#max_ips").val(""); } }); } @@ -883,7 +911,7 @@ } function configIPLimit() { - if (!validateForm('ip_limit_config_form')) return; // Ensure correct form ID + if (!validateForm('ip_limit_config_form')) return; const blockDuration = $("#block_duration").val(); const maxIps = $("#max_ips").val(); confirmAction("save the IP Limit configuration", function () { @@ -893,7 +921,8 @@ { block_duration: parseInt(blockDuration), max_ips: parseInt(maxIps) }, "IP Limit configuration saved successfully!", "#ip_limit_change_config", - false + false, + fetchIpLimitConfig ); }); } @@ -977,8 +1006,8 @@ $(this).removeClass('is-invalid'); } else if ($(this).val().trim() !== "") { $(this).addClass('is-invalid'); - } else { - $(this).removeClass('is-invalid'); + } else { + $(this).addClass('is-invalid'); } });