feat: add API endpoint to fetch IP Limiter configuration

This commit is contained in:
Whispering Wind
2025-05-23 12:16:58 +03:30
committed by GitHub
parent 5d0a9a875e
commit b5af06708d
4 changed files with 88 additions and 29 deletions

View File

@ -630,4 +630,22 @@ def config_ip_limiter(block_duration: int = None, max_ips: int = None):
cmd_args.append('') cmd_args.append('')
run_cmd(cmd_args) 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 # endregion

View File

@ -1,6 +1,6 @@
from fastapi import APIRouter, BackgroundTasks, HTTPException, UploadFile, File from fastapi import APIRouter, BackgroundTasks, HTTPException, UploadFile, File
from ..schema.config.hysteria import ConfigFile, GetPortResponse, GetSniResponse 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 from fastapi.responses import FileResponse
import shutil import shutil
import zipfile import zipfile
@ -335,6 +335,14 @@ async def config_ip_limit_api(config: IPLimitConfig):
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f'Error configuring IP Limiter: {str(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): def run_setup_decoy_background(domain: str, decoy_path: str):
"""Function to run decoy setup in the background.""" """Function to run decoy setup in the background."""

View File

@ -6,8 +6,12 @@ class DetailResponse(BaseModel):
detail: str detail: str
class IPLimitConfig(BaseModel): class IPLimitConfig(BaseModel):
block_duration: Optional[int] = None block_duration: Optional[int] = Field(None, example=60)
max_ips: Optional[int] = None 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): class SetupDecoyRequest(BaseModel):
domain: str = Field(..., description="Domain name associated with the web panel") domain: str = Field(..., description="Domain name associated with the web panel")
@ -15,4 +19,4 @@ class SetupDecoyRequest(BaseModel):
class DecoyStatusResponse(BaseModel): class DecoyStatusResponse(BaseModel):
active: bool = Field(..., description="Whether the decoy site is currently configured and active") 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")

View File

@ -451,7 +451,9 @@
} else if (id === 'decoy_path') { } else if (id === 'decoy_path') {
fieldValid = isValidPath(input.val()); fieldValid = isValidPath(input.val());
} else { } 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) { if (!fieldValid) {
@ -521,22 +523,22 @@
}; };
Object.keys(servicesMap).forEach(service => { Object.keys(servicesMap).forEach(service => {
let formSelector = servicesMap[service]; let targetSelector = servicesMap[service];
let isRunning = data[service]; let isRunning = data[service];
if (service === "hysteria_normal_sub") { 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 $normalStartBtn = $("#normal_start");
const $normalStopBtn = $("#normal_stop"); const $normalStopBtn = $("#normal_stop");
const $normalAlert = $("#normal_sub_service_form .alert-info");
const $normalSubConfigTabLi = $(".normal-sub-config-tab-li"); const $normalSubConfigTabLi = $(".normal-sub-config-tab-li");
if (isRunning) { if (isRunning) {
$normalFormGroups.hide(); $normalFormGroups.hide();
$normalStartBtn.hide(); $normalStartBtn.hide();
$normalStopBtn.show(); $normalStopBtn.show();
if ($normalAlert.length === 0) { if ($normalForm.find(".alert-info").length === 0) {
$("#normal_sub_service_form").prepend(`<div class='alert alert-info'>NormalSub service is running. You can stop it or configure its subpath.</div>`); $normalForm.prepend(`<div class='alert alert-info'>NormalSub service is running. You can stop it or configure its subpath.</div>`);
} }
$normalSubConfigTabLi.show(); $normalSubConfigTabLi.show();
fetchNormalSubPath(); fetchNormalSubPath();
@ -544,7 +546,7 @@
$normalFormGroups.show(); $normalFormGroups.show();
$normalStartBtn.show(); $normalStartBtn.show();
$normalStopBtn.hide(); $normalStopBtn.hide();
$("#normal_sub_service_form .alert-info").remove(); $normalForm.find(".alert-info").remove();
$normalSubConfigTabLi.hide(); $normalSubConfigTabLi.hide();
if ($('#normal-sub-config-link-tab').hasClass('active')) { if ($('#normal-sub-config-link-tab').hasClass('active')) {
$('#normal-tab').tab('show'); $('#normal-tab').tab('show');
@ -553,31 +555,40 @@
$("#normal_subpath_input").removeClass('is-invalid'); $("#normal_subpath_input").removeClass('is-invalid');
} }
} else if (service === "hysteria_iplimit") { } else if (service === "hysteria_iplimit") {
const $ipLimitServiceForm = $("#ip_limit_service_form");
const $configTabLi = $(".ip-limit-config-tab-li");
if (isRunning) { if (isRunning) {
$("#ip_limit_start").hide(); $("#ip_limit_start").hide();
$("#ip_limit_stop").show(); $("#ip_limit_stop").show();
$(".ip-limit-config-tab-li").show(); $configTabLi.show();
// TODO: Fetch IP Limit Config and populate fields fetchIpLimitConfig();
if ($ipLimitServiceForm.find(".alert-info").length === 0) {
$ipLimitServiceForm.prepend(`<div class='alert alert-info'>IP-Limit service is running. You can stop it if needed.</div>`);
}
} else { } else {
$("#ip_limit_start").show(); $("#ip_limit_start").show();
$("#ip_limit_stop").hide(); $("#ip_limit_stop").hide();
$(".ip-limit-config-tab-li").hide(); $configTabLi.hide();
$('#ip-limit-service-tab').tab('show'); $('#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 { } else {
const $formSelector = $(targetSelector);
if (isRunning) { if (isRunning) {
$(formSelector + " .form-group").hide(); $formSelector.find(".form-group").hide();
$(formSelector + " .btn-success").hide(); $formSelector.find(".btn-success").hide();
$(formSelector + " .btn-danger").show(); $formSelector.find(".btn-danger").show();
if ($(formSelector + " .alert-info").length === 0) { if ($formSelector.find(".alert-info").length === 0) {
$(formSelector).prepend(`<div class='alert alert-info'>Service is running. You can stop it if needed.</div>`); $formSelector.prepend(`<div class='alert alert-info'>Service is running. You can stop it if needed.</div>`);
} }
} else { } else {
$(formSelector + " .form-group").show(); $formSelector.find(".form-group").show();
$(formSelector + " .btn-success").show(); $formSelector.find(".btn-success").show();
$(formSelector + " .btn-danger").hide(); $formSelector.find(".btn-danger").hide();
$(formSelector + " .alert-info").remove(); $formSelector.find(".alert-info").remove();
} }
} }
}); });
@ -596,7 +607,24 @@
error: function (xhr, status, error) { error: function (xhr, status, error) {
console.error("Failed to fetch NormalSub subpath:", error, xhr.responseText); console.error("Failed to fetch NormalSub subpath:", error, xhr.responseText);
$("#normal_subpath_input").val(""); $("#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() { 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 blockDuration = $("#block_duration").val();
const maxIps = $("#max_ips").val(); const maxIps = $("#max_ips").val();
confirmAction("save the IP Limit configuration", function () { confirmAction("save the IP Limit configuration", function () {
@ -893,7 +921,8 @@
{ block_duration: parseInt(blockDuration), max_ips: parseInt(maxIps) }, { block_duration: parseInt(blockDuration), max_ips: parseInt(maxIps) },
"IP Limit configuration saved successfully!", "IP Limit configuration saved successfully!",
"#ip_limit_change_config", "#ip_limit_change_config",
false false,
fetchIpLimitConfig
); );
}); });
} }
@ -977,8 +1006,8 @@
$(this).removeClass('is-invalid'); $(this).removeClass('is-invalid');
} else if ($(this).val().trim() !== "") { } else if ($(this).val().trim() !== "") {
$(this).addClass('is-invalid'); $(this).addClass('is-invalid');
} else { } else {
$(this).removeClass('is-invalid'); $(this).addClass('is-invalid');
} }
}); });