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

View File

@ -19,6 +19,9 @@
data-check-obfs-url="{{ url_for('check_obfs') }}"
data-enable-obfs-url="{{ url_for('enable_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-sni-url-template="{{ url_for('set_sni_api', sni='SNI_PLACEHOLDER') }}"
data-update-geo-url-template="{{ url_for('update_geo', country='COUNTRY_PLACEHOLDER') }}"
@ -63,22 +66,57 @@
</div>
<div class="col-md-6 mb-4">
<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-body">
<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 class="card-header p-0">
<ul class="nav nav-tabs" id="obfs-masquerade-tabs" role="tablist">
<li class="nav-item">
<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>
</li>
<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 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>
<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>