feat(ui): add masquerade controls and status to hysteria settings

This commit is contained in:
ReturnFI
2025-12-02 15:49:23 +00:00
parent 224a37421b
commit 07e8915492
2 changed files with 174 additions and 121 deletions

View File

@ -7,6 +7,9 @@ $(document).ready(function () {
checkObfs: contentSection.dataset.checkObfsUrl, checkObfs: contentSection.dataset.checkObfsUrl,
enableObfs: contentSection.dataset.enableObfsUrl, enableObfs: contentSection.dataset.enableObfsUrl,
disableObfs: contentSection.dataset.disableObfsUrl, disableObfs: contentSection.dataset.disableObfsUrl,
checkMasquerade: contentSection.dataset.checkMasqueradeUrl,
enableMasquerade: contentSection.dataset.enableMasqueradeUrl,
disableMasquerade: contentSection.dataset.disableMasqueradeUrl,
setPortTemplate: contentSection.dataset.setPortUrlTemplate, setPortTemplate: contentSection.dataset.setPortUrlTemplate,
setSniTemplate: contentSection.dataset.setSniUrlTemplate, setSniTemplate: contentSection.dataset.setSniUrlTemplate,
updateGeoTemplate: contentSection.dataset.updateGeoUrlTemplate updateGeoTemplate: contentSection.dataset.updateGeoUrlTemplate
@ -23,10 +26,10 @@ $(document).ready(function () {
return /^[0-9]+$/.test(port) && parseInt(port) > 0 && parseInt(port) <= 65535; return /^[0-9]+$/.test(port) && parseInt(port) > 0 && parseInt(port) <= 65535;
} }
function confirmAction(actionName, callback) { function confirmAction(title, text, callback) {
Swal.fire({ Swal.fire({
title: `Are you sure?`, title: title,
text: `Do you really want to ${actionName}?`, text: text,
icon: "warning", icon: "warning",
showCancelButton: true, showCancelButton: true,
confirmButtonColor: "#3085d6", confirmButtonColor: "#3085d6",
@ -53,13 +56,12 @@ $(document).ready(function () {
} }
}, },
success: function (response) { success: function (response) {
Swal.fire("Success!", successMessage, "success").then(() => { const message = typeof response.detail === 'string' ? response.detail : successMessage;
if (showReload) { Swal.fire("Success!", message, "success").then(() => {
if (showReload && !postSuccessCallback) {
location.reload(); location.reload();
} else { } else if (postSuccessCallback) {
if (postSuccessCallback) { postSuccessCallback(response);
postSuccessCallback(response);
}
} }
}); });
}, },
@ -83,7 +85,6 @@ $(document).ready(function () {
} }
} }
Swal.fire("Error!", errorMessage, "error"); Swal.fire("Error!", errorMessage, "error");
console.error("AJAX Error:", status, error, xhr.responseText);
}, },
complete: function() { complete: function() {
if (buttonSelector) { if (buttonSelector) {
@ -118,95 +119,122 @@ $(document).ready(function () {
} }
function initUI() { function initUI() {
$.ajax({ $.get(API_URLS.getPort, data => $("#hysteria_port").val(data.port || ""));
url: API_URLS.getPort, $.get(API_URLS.getSni, data => $("#sni_domain").val(data.sni || ""));
type: "GET",
success: function (data) {
$("#hysteria_port").val(data.port || "");
},
error: function (xhr, status, error) {
console.error("Failed to fetch port:", error, xhr.responseText);
}
});
$.ajax({
url: API_URLS.getSni,
type: "GET",
success: function (data) {
$("#sni_domain").val(data.sni || "");
},
error: function (xhr, status, error) {
console.error("Failed to fetch SNI domain:", error, xhr.responseText);
}
});
} }
function fetchObfsStatus() { function fetchAllStatuses() {
$.ajax({ Promise.all([
url: API_URLS.checkObfs, $.ajax({ url: API_URLS.checkObfs, type: "GET" }),
type: "GET", $.ajax({ url: API_URLS.checkMasquerade, type: "GET" })
success: function (data) { ]).then(([obfsResponse, masqueradeResponse]) => {
updateObfsUI(data.obfs); const obfsStatus = obfsResponse.obfs;
}, const masqueradeStatus = masqueradeResponse.status;
error: function (xhr, status, error) {
$("#obfs_status_message").html('<span class="text-danger">Failed to fetch OBFS status.</span>'); const isObfsActive = obfsStatus === "OBFS is active.";
console.error("Failed to fetch OBFS status:", error, xhr.responseText); const isMasqueradeActive = masqueradeStatus === "Enabled";
$("#obfs_enable_btn").hide();
$("#obfs_disable_btn").hide(); updateObfsUI(obfsStatus, isMasqueradeActive);
} updateMasqueradeUI(masqueradeStatus, isObfsActive);
}).catch(error => {
console.error("Failed to fetch statuses:", error);
$("#obfs_status_message").html('<span class="text-danger">Failed to fetch status.</span>');
$("#masquerade_status_message").html('<span class="text-danger">Failed to fetch status.</span>');
}); });
} }
function updateObfsUI(statusMessage, isMasqueradeEnabled) {
const container = $("#obfs_status_container");
const msgElement = $("#obfs_status_message");
const enableBtn = $("#obfs_enable_btn");
const disableBtn = $("#obfs_disable_btn");
function updateObfsUI(statusMessage) { container.removeClass("border-success alert-success border-warning alert-warning border-danger alert-danger border-info alert-info");
$("#obfs_status_message").text(statusMessage); enableBtn.hide();
if (statusMessage === "OBFS is active.") { disableBtn.hide();
$("#obfs_enable_btn").hide();
$("#obfs_disable_btn").show(); if (isMasqueradeEnabled) {
$("#obfs_status_container").removeClass("border-danger border-warning alert-danger alert-warning").addClass("border-success alert-success"); msgElement.text("Cannot be managed while Masquerade is active.");
container.addClass("border-info alert-info");
} else if (statusMessage === "OBFS is active.") {
msgElement.text(statusMessage);
disableBtn.show();
container.addClass("border-success alert-success");
} else if (statusMessage === "OBFS is not active.") { } else if (statusMessage === "OBFS is not active.") {
$("#obfs_enable_btn").show(); msgElement.text(statusMessage);
$("#obfs_disable_btn").hide(); enableBtn.show();
$("#obfs_status_container").removeClass("border-success border-danger alert-success alert-danger").addClass("border-warning alert-warning"); container.addClass("border-warning alert-warning");
} else { } else {
$("#obfs_enable_btn").hide(); msgElement.html(`<span class="text-danger">${statusMessage}</span>`);
$("#obfs_disable_btn").hide(); container.addClass("border-danger alert-danger");
$("#obfs_status_container").removeClass("border-success border-warning alert-success alert-warning").addClass("border-danger alert-danger"); }
}
function updateMasqueradeUI(statusMessage, isObfsEnabled) {
const container = $("#masquerade_status_container");
const msgElement = $("#masquerade_status_message");
const enableBtn = $("#masquerade_enable_btn");
const disableBtn = $("#masquerade_disable_btn");
container.removeClass("border-success alert-success border-warning alert-warning border-danger alert-danger border-info alert-info");
enableBtn.hide();
disableBtn.hide();
if (isObfsEnabled) {
msgElement.text("Cannot be managed while OBFS is active.");
container.addClass("border-info alert-info");
} else if (statusMessage === "Enabled") {
msgElement.text(statusMessage);
disableBtn.show();
container.addClass("border-success alert-success");
} else if (statusMessage === "Disabled") {
msgElement.text(statusMessage);
enableBtn.show();
container.addClass("border-warning alert-warning");
} else {
msgElement.html(`<span class="text-danger">${statusMessage}</span>`);
container.addClass("border-danger alert-danger");
} }
} }
function enableObfs() { function enableObfs() {
confirmAction("enable OBFS", function () { confirmAction(
sendRequest( "Enable OBFS?",
API_URLS.enableObfs, "This will require all users to update their configuration files to reconnect.",
"GET", () => sendRequest(API_URLS.enableObfs, "GET", null, "OBFS enabled successfully!", "#obfs_enable_btn", false, fetchAllStatuses)
null, );
"OBFS enabled successfully!",
"#obfs_enable_btn",
false,
fetchObfsStatus
);
});
} }
function disableObfs() { function disableObfs() {
confirmAction("disable OBFS", function () { confirmAction(
sendRequest( "Disable OBFS?",
API_URLS.disableObfs, "This will disconnect all current users. They must update their configurations to reconnect.",
"GET", () => sendRequest(API_URLS.disableObfs, "GET", null, "OBFS disabled successfully!", "#obfs_disable_btn", false, fetchAllStatuses)
null, );
"OBFS disabled successfully!", }
"#obfs_disable_btn",
false, function enableMasquerade() {
fetchObfsStatus confirmAction(
); "Enable Masquerade?",
}); "This will enable the string masquerade mode.",
() => sendRequest(API_URLS.enableMasquerade, "GET", null, "Masquerade enabled successfully!", "#masquerade_enable_btn", false, fetchAllStatuses)
);
}
function disableMasquerade() {
confirmAction(
"Disable Masquerade?",
"This will disable the masquerade feature.",
() => sendRequest(API_URLS.disableMasquerade, "GET", null, "Masquerade disabled successfully!", "#masquerade_disable_btn", false, fetchAllStatuses)
);
} }
function changePort() { function changePort() {
if (!validateForm('port_form')) return; if (!validateForm('port_form')) return;
const port = $("#hysteria_port").val(); const port = $("#hysteria_port").val();
const url = API_URLS.setPortTemplate.replace("PORT_PLACEHOLDER", port); const url = API_URLS.setPortTemplate.replace("PORT_PLACEHOLDER", port);
confirmAction("change the port", function () { confirmAction("Are you sure?", "Do you really want to change the port?", () => {
sendRequest(url, "GET", null, "Port changed successfully!", "#port_change"); sendRequest(url, "GET", null, "Port changed successfully!", "#port_change");
}); });
} }
@ -215,7 +243,7 @@ $(document).ready(function () {
if (!validateForm('sni_form')) return; if (!validateForm('sni_form')) return;
const domain = $("#sni_domain").val(); const domain = $("#sni_domain").val();
const url = API_URLS.setSniTemplate.replace("SNI_PLACEHOLDER", domain); const url = API_URLS.setSniTemplate.replace("SNI_PLACEHOLDER", domain);
confirmAction("change the SNI", function () { confirmAction("Are you sure?", "Do you really want to change the SNI?", () => {
sendRequest(url, "GET", null, "SNI changed successfully!", "#sni_change"); sendRequest(url, "GET", null, "SNI changed successfully!", "#sni_change");
}); });
} }
@ -225,42 +253,29 @@ $(document).ready(function () {
const buttonId = `#geo_update_${country}`; const buttonId = `#geo_update_${country}`;
const url = API_URLS.updateGeoTemplate.replace('COUNTRY_PLACEHOLDER', country); const url = API_URLS.updateGeoTemplate.replace('COUNTRY_PLACEHOLDER', country);
confirmAction(`update the Geo files for ${countryName}`, function () { confirmAction(
sendRequest( "Update Geo Files?",
url, `Do you really want to update the Geo files for ${countryName}?`,
"GET", () => sendRequest(url, "GET", null, `Geo files for ${countryName} updated successfully!`, buttonId, false, null)
null, );
`Geo files for ${countryName} updated successfully!`,
buttonId,
false,
null
);
});
} }
initUI(); initUI();
fetchObfsStatus(); fetchAllStatuses();
$("#port_change").on("click", changePort); $("#port_change").on("click", changePort);
$("#sni_change").on("click", changeSNI); $("#sni_change").on("click", changeSNI);
$("#obfs_enable_btn").on("click", enableObfs); $("#obfs_enable_btn").on("click", enableObfs);
$("#obfs_disable_btn").on("click", disableObfs); $("#obfs_disable_btn").on("click", disableObfs);
$("#geo_update_iran").on("click", function() { updateGeo('iran'); }); $("#masquerade_enable_btn").on("click", enableMasquerade);
$("#geo_update_china").on("click", function() { updateGeo('china'); }); $("#masquerade_disable_btn").on("click", disableMasquerade);
$("#geo_update_russia").on("click", function() { updateGeo('russia'); }); $("#geo_update_iran").on("click", () => updateGeo('iran'));
$("#geo_update_china").on("click", () => updateGeo('china'));
$("#geo_update_russia").on("click", () => updateGeo('russia'));
$('#sni_domain').on('input', function () { $('#sni_domain, #hysteria_port').on('input', function () {
if (isValidDomain($(this).val())) { const validator = $(this).attr('id') === 'sni_domain' ? isValidDomain : isValidPort;
$(this).removeClass('is-invalid'); if (validator($(this).val())) {
} else if ($(this).val().trim() !== "") {
$(this).addClass('is-invalid');
} else {
$(this).removeClass('is-invalid');
}
});
$('#hysteria_port').on('input', function () {
if (isValidPort($(this).val())) {
$(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');

View File

@ -19,6 +19,9 @@
data-check-obfs-url="{{ url_for('check_obfs') }}" data-check-obfs-url="{{ url_for('check_obfs') }}"
data-enable-obfs-url="{{ url_for('enable_obfs') }}" data-enable-obfs-url="{{ url_for('enable_obfs') }}"
data-disable-obfs-url="{{ url_for('disable_obfs') }}" data-disable-obfs-url="{{ url_for('disable_obfs') }}"
data-check-masquerade-url="{{ url_for('check_masquerade') }}"
data-enable-masquerade-url="{{ url_for('enable_masquerade') }}"
data-disable-masquerade-url="{{ url_for('disable_masquerade') }}"
data-set-port-url-template="{{ url_for('set_port_api', port='PORT_PLACEHOLDER') }}" data-set-port-url-template="{{ url_for('set_port_api', port='PORT_PLACEHOLDER') }}"
data-set-sni-url-template="{{ url_for('set_sni_api', sni='SNI_PLACEHOLDER') }}" data-set-sni-url-template="{{ url_for('set_sni_api', sni='SNI_PLACEHOLDER') }}"
data-update-geo-url-template="{{ url_for('update_geo', country='COUNTRY_PLACEHOLDER') }}" data-update-geo-url-template="{{ url_for('update_geo', country='COUNTRY_PLACEHOLDER') }}"
@ -63,22 +66,57 @@
</div> </div>
<div class="col-md-6 mb-4"> <div class="col-md-6 mb-4">
<div class="card card-outline card-secondary h-100"> <div class="card card-outline card-secondary h-100">
<div class="card-header"><h3 class="card-title"><i class="fas fa-user-secret"></i> OBFS</h3></div> <div class="card-header p-0">
<div class="card-body"> <ul class="nav nav-tabs" id="obfs-masquerade-tabs" role="tablist">
<div class="mb-3"> <li class="nav-item">
<h5>OBFS Status</h5> <a class="nav-link active" id="obfs-tab" data-toggle="tab" href="#obfs-content" role="tab" aria-controls="obfs-content" aria-selected="true"><i class="fas fa-user-secret"></i> OBFS</a>
<div id="obfs_status_container" class="p-3 border rounded"> </li>
<span id="obfs_status_message">Loading OBFS status...</span> <li class="nav-item">
<a class="nav-link" id="masquerade-tab" data-toggle="tab" href="#masquerade-content" role="tab" aria-controls="masquerade-content" aria-selected="false"><i class="fas fa-theater-masks"></i> Masquerade</a>
</li>
</ul>
</div>
<div class="card-body tab-content" id="obfs-masquerade-tabs-content">
<div class="tab-pane fade show active" id="obfs-content" role="tabpanel" aria-labelledby="obfs-tab">
<div class="mb-3">
<h5>OBFS Status</h5>
<div id="obfs_status_container" class="p-3 border rounded">
<span id="obfs_status_message">Loading OBFS status...</span>
</div>
</div> </div>
<div class="alert alert-warning" role="alert">
<i class="fas fa-exclamation-triangle"></i>
<strong>Warning:</strong> <small>Changing this setting requires users to update their client configurations.</small>
</div>
<button id="obfs_enable_btn" type='button' class='btn btn-success' style="display: none;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
Enable OBFS
</button>
<button id="obfs_disable_btn" type='button' class='btn btn-danger' style="display: none;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
Disable OBFS
</button>
</div>
<div class="tab-pane fade" id="masquerade-content" role="tabpanel" aria-labelledby="masquerade-tab">
<div class="mb-3">
<h5>Masquerade Status</h5>
<div id="masquerade_status_container" class="p-3 border rounded">
<span id="masquerade_status_message">Loading Masquerade status...</span>
</div>
</div>
<div class="alert alert-info" role="alert">
<i class="fas fa-info-circle"></i>
<strong>Note:</strong> <small>Masquerade cannot be active at the same time as OBFS.</small>
</div>
<button id="masquerade_enable_btn" type='button' class='btn btn-success' style="display: none;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
Enable Masquerade
</button>
<button id="masquerade_disable_btn" type='button' class='btn btn-danger' style="display: none;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
Disable Masquerade
</button>
</div> </div>
<button id="obfs_enable_btn" type='button' class='btn btn-success' style="display: none;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
Enable OBFS
</button>
<button id="obfs_disable_btn" type='button' class='btn btn-danger' style="display: none;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
Disable OBFS
</button>
</div> </div>
</div> </div>
</div> </div>