Skip to content
This repository was archived by the owner on Jul 8, 2026. It is now read-only.

Commit 07e8915

Browse files
committed
feat(ui): add masquerade controls and status to hysteria settings
1 parent 224a374 commit 07e8915

2 files changed

Lines changed: 174 additions & 121 deletions

File tree

core/scripts/webpanel/assets/js/hysteria_settings.js

Lines changed: 122 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ $(document).ready(function () {
77
checkObfs: contentSection.dataset.checkObfsUrl,
88
enableObfs: contentSection.dataset.enableObfsUrl,
99
disableObfs: contentSection.dataset.disableObfsUrl,
10+
checkMasquerade: contentSection.dataset.checkMasqueradeUrl,
11+
enableMasquerade: contentSection.dataset.enableMasqueradeUrl,
12+
disableMasquerade: contentSection.dataset.disableMasqueradeUrl,
1013
setPortTemplate: contentSection.dataset.setPortUrlTemplate,
1114
setSniTemplate: contentSection.dataset.setSniUrlTemplate,
1215
updateGeoTemplate: contentSection.dataset.updateGeoUrlTemplate
@@ -23,10 +26,10 @@ $(document).ready(function () {
2326
return /^[0-9]+$/.test(port) && parseInt(port) > 0 && parseInt(port) <= 65535;
2427
}
2528

26-
function confirmAction(actionName, callback) {
29+
function confirmAction(title, text, callback) {
2730
Swal.fire({
28-
title: `Are you sure?`,
29-
text: `Do you really want to ${actionName}?`,
31+
title: title,
32+
text: text,
3033
icon: "warning",
3134
showCancelButton: true,
3235
confirmButtonColor: "#3085d6",
@@ -53,13 +56,12 @@ $(document).ready(function () {
5356
}
5457
},
5558
success: function (response) {
56-
Swal.fire("Success!", successMessage, "success").then(() => {
57-
if (showReload) {
59+
const message = typeof response.detail === 'string' ? response.detail : successMessage;
60+
Swal.fire("Success!", message, "success").then(() => {
61+
if (showReload && !postSuccessCallback) {
5862
location.reload();
59-
} else {
60-
if (postSuccessCallback) {
61-
postSuccessCallback(response);
62-
}
63+
} else if (postSuccessCallback) {
64+
postSuccessCallback(response);
6365
}
6466
});
6567
},
@@ -83,7 +85,6 @@ $(document).ready(function () {
8385
}
8486
}
8587
Swal.fire("Error!", errorMessage, "error");
86-
console.error("AJAX Error:", status, error, xhr.responseText);
8788
},
8889
complete: function() {
8990
if (buttonSelector) {
@@ -118,95 +119,122 @@ $(document).ready(function () {
118119
}
119120

120121
function initUI() {
121-
$.ajax({
122-
url: API_URLS.getPort,
123-
type: "GET",
124-
success: function (data) {
125-
$("#hysteria_port").val(data.port || "");
126-
},
127-
error: function (xhr, status, error) {
128-
console.error("Failed to fetch port:", error, xhr.responseText);
129-
}
130-
});
131-
132-
$.ajax({
133-
url: API_URLS.getSni,
134-
type: "GET",
135-
success: function (data) {
136-
$("#sni_domain").val(data.sni || "");
137-
},
138-
error: function (xhr, status, error) {
139-
console.error("Failed to fetch SNI domain:", error, xhr.responseText);
140-
}
141-
});
122+
$.get(API_URLS.getPort, data => $("#hysteria_port").val(data.port || ""));
123+
$.get(API_URLS.getSni, data => $("#sni_domain").val(data.sni || ""));
142124
}
143125

144-
function fetchObfsStatus() {
145-
$.ajax({
146-
url: API_URLS.checkObfs,
147-
type: "GET",
148-
success: function (data) {
149-
updateObfsUI(data.obfs);
150-
},
151-
error: function (xhr, status, error) {
152-
$("#obfs_status_message").html('<span class="text-danger">Failed to fetch OBFS status.</span>');
153-
console.error("Failed to fetch OBFS status:", error, xhr.responseText);
154-
$("#obfs_enable_btn").hide();
155-
$("#obfs_disable_btn").hide();
156-
}
126+
function fetchAllStatuses() {
127+
Promise.all([
128+
$.ajax({ url: API_URLS.checkObfs, type: "GET" }),
129+
$.ajax({ url: API_URLS.checkMasquerade, type: "GET" })
130+
]).then(([obfsResponse, masqueradeResponse]) => {
131+
const obfsStatus = obfsResponse.obfs;
132+
const masqueradeStatus = masqueradeResponse.status;
133+
134+
const isObfsActive = obfsStatus === "OBFS is active.";
135+
const isMasqueradeActive = masqueradeStatus === "Enabled";
136+
137+
updateObfsUI(obfsStatus, isMasqueradeActive);
138+
updateMasqueradeUI(masqueradeStatus, isObfsActive);
139+
140+
}).catch(error => {
141+
console.error("Failed to fetch statuses:", error);
142+
$("#obfs_status_message").html('<span class="text-danger">Failed to fetch status.</span>');
143+
$("#masquerade_status_message").html('<span class="text-danger">Failed to fetch status.</span>');
157144
});
158145
}
146+
147+
function updateObfsUI(statusMessage, isMasqueradeEnabled) {
148+
const container = $("#obfs_status_container");
149+
const msgElement = $("#obfs_status_message");
150+
const enableBtn = $("#obfs_enable_btn");
151+
const disableBtn = $("#obfs_disable_btn");
159152

160-
function updateObfsUI(statusMessage) {
161-
$("#obfs_status_message").text(statusMessage);
162-
if (statusMessage === "OBFS is active.") {
163-
$("#obfs_enable_btn").hide();
164-
$("#obfs_disable_btn").show();
165-
$("#obfs_status_container").removeClass("border-danger border-warning alert-danger alert-warning").addClass("border-success alert-success");
153+
container.removeClass("border-success alert-success border-warning alert-warning border-danger alert-danger border-info alert-info");
154+
enableBtn.hide();
155+
disableBtn.hide();
156+
157+
if (isMasqueradeEnabled) {
158+
msgElement.text("Cannot be managed while Masquerade is active.");
159+
container.addClass("border-info alert-info");
160+
} else if (statusMessage === "OBFS is active.") {
161+
msgElement.text(statusMessage);
162+
disableBtn.show();
163+
container.addClass("border-success alert-success");
166164
} else if (statusMessage === "OBFS is not active.") {
167-
$("#obfs_enable_btn").show();
168-
$("#obfs_disable_btn").hide();
169-
$("#obfs_status_container").removeClass("border-success border-danger alert-success alert-danger").addClass("border-warning alert-warning");
165+
msgElement.text(statusMessage);
166+
enableBtn.show();
167+
container.addClass("border-warning alert-warning");
170168
} else {
171-
$("#obfs_enable_btn").hide();
172-
$("#obfs_disable_btn").hide();
173-
$("#obfs_status_container").removeClass("border-success border-warning alert-success alert-warning").addClass("border-danger alert-danger");
169+
msgElement.html(`<span class="text-danger">${statusMessage}</span>`);
170+
container.addClass("border-danger alert-danger");
171+
}
172+
}
173+
174+
function updateMasqueradeUI(statusMessage, isObfsEnabled) {
175+
const container = $("#masquerade_status_container");
176+
const msgElement = $("#masquerade_status_message");
177+
const enableBtn = $("#masquerade_enable_btn");
178+
const disableBtn = $("#masquerade_disable_btn");
179+
180+
container.removeClass("border-success alert-success border-warning alert-warning border-danger alert-danger border-info alert-info");
181+
enableBtn.hide();
182+
disableBtn.hide();
183+
184+
if (isObfsEnabled) {
185+
msgElement.text("Cannot be managed while OBFS is active.");
186+
container.addClass("border-info alert-info");
187+
} else if (statusMessage === "Enabled") {
188+
msgElement.text(statusMessage);
189+
disableBtn.show();
190+
container.addClass("border-success alert-success");
191+
} else if (statusMessage === "Disabled") {
192+
msgElement.text(statusMessage);
193+
enableBtn.show();
194+
container.addClass("border-warning alert-warning");
195+
} else {
196+
msgElement.html(`<span class="text-danger">${statusMessage}</span>`);
197+
container.addClass("border-danger alert-danger");
174198
}
175199
}
176200

177201
function enableObfs() {
178-
confirmAction("enable OBFS", function () {
179-
sendRequest(
180-
API_URLS.enableObfs,
181-
"GET",
182-
null,
183-
"OBFS enabled successfully!",
184-
"#obfs_enable_btn",
185-
false,
186-
fetchObfsStatus
187-
);
188-
});
202+
confirmAction(
203+
"Enable OBFS?",
204+
"This will require all users to update their configuration files to reconnect.",
205+
() => sendRequest(API_URLS.enableObfs, "GET", null, "OBFS enabled successfully!", "#obfs_enable_btn", false, fetchAllStatuses)
206+
);
189207
}
190208

191209
function disableObfs() {
192-
confirmAction("disable OBFS", function () {
193-
sendRequest(
194-
API_URLS.disableObfs,
195-
"GET",
196-
null,
197-
"OBFS disabled successfully!",
198-
"#obfs_disable_btn",
199-
false,
200-
fetchObfsStatus
201-
);
202-
});
210+
confirmAction(
211+
"Disable OBFS?",
212+
"This will disconnect all current users. They must update their configurations to reconnect.",
213+
() => sendRequest(API_URLS.disableObfs, "GET", null, "OBFS disabled successfully!", "#obfs_disable_btn", false, fetchAllStatuses)
214+
);
215+
}
216+
217+
function enableMasquerade() {
218+
confirmAction(
219+
"Enable Masquerade?",
220+
"This will enable the string masquerade mode.",
221+
() => sendRequest(API_URLS.enableMasquerade, "GET", null, "Masquerade enabled successfully!", "#masquerade_enable_btn", false, fetchAllStatuses)
222+
);
223+
}
224+
225+
function disableMasquerade() {
226+
confirmAction(
227+
"Disable Masquerade?",
228+
"This will disable the masquerade feature.",
229+
() => sendRequest(API_URLS.disableMasquerade, "GET", null, "Masquerade disabled successfully!", "#masquerade_disable_btn", false, fetchAllStatuses)
230+
);
203231
}
204232

205233
function changePort() {
206234
if (!validateForm('port_form')) return;
207235
const port = $("#hysteria_port").val();
208236
const url = API_URLS.setPortTemplate.replace("PORT_PLACEHOLDER", port);
209-
confirmAction("change the port", function () {
237+
confirmAction("Are you sure?", "Do you really want to change the port?", () => {
210238
sendRequest(url, "GET", null, "Port changed successfully!", "#port_change");
211239
});
212240
}
@@ -215,7 +243,7 @@ $(document).ready(function () {
215243
if (!validateForm('sni_form')) return;
216244
const domain = $("#sni_domain").val();
217245
const url = API_URLS.setSniTemplate.replace("SNI_PLACEHOLDER", domain);
218-
confirmAction("change the SNI", function () {
246+
confirmAction("Are you sure?", "Do you really want to change the SNI?", () => {
219247
sendRequest(url, "GET", null, "SNI changed successfully!", "#sni_change");
220248
});
221249
}
@@ -225,42 +253,29 @@ $(document).ready(function () {
225253
const buttonId = `#geo_update_${country}`;
226254
const url = API_URLS.updateGeoTemplate.replace('COUNTRY_PLACEHOLDER', country);
227255

228-
confirmAction(`update the Geo files for ${countryName}`, function () {
229-
sendRequest(
230-
url,
231-
"GET",
232-
null,
233-
`Geo files for ${countryName} updated successfully!`,
234-
buttonId,
235-
false,
236-
null
237-
);
238-
});
256+
confirmAction(
257+
"Update Geo Files?",
258+
`Do you really want to update the Geo files for ${countryName}?`,
259+
() => sendRequest(url, "GET", null, `Geo files for ${countryName} updated successfully!`, buttonId, false, null)
260+
);
239261
}
240262

241263
initUI();
242-
fetchObfsStatus();
264+
fetchAllStatuses();
243265

244266
$("#port_change").on("click", changePort);
245267
$("#sni_change").on("click", changeSNI);
246268
$("#obfs_enable_btn").on("click", enableObfs);
247269
$("#obfs_disable_btn").on("click", disableObfs);
248-
$("#geo_update_iran").on("click", function() { updateGeo('iran'); });
249-
$("#geo_update_china").on("click", function() { updateGeo('china'); });
250-
$("#geo_update_russia").on("click", function() { updateGeo('russia'); });
251-
252-
$('#sni_domain').on('input', function () {
253-
if (isValidDomain($(this).val())) {
254-
$(this).removeClass('is-invalid');
255-
} else if ($(this).val().trim() !== "") {
256-
$(this).addClass('is-invalid');
257-
} else {
258-
$(this).removeClass('is-invalid');
259-
}
260-
});
270+
$("#masquerade_enable_btn").on("click", enableMasquerade);
271+
$("#masquerade_disable_btn").on("click", disableMasquerade);
272+
$("#geo_update_iran").on("click", () => updateGeo('iran'));
273+
$("#geo_update_china").on("click", () => updateGeo('china'));
274+
$("#geo_update_russia").on("click", () => updateGeo('russia'));
261275

262-
$('#hysteria_port').on('input', function () {
263-
if (isValidPort($(this).val())) {
276+
$('#sni_domain, #hysteria_port').on('input', function () {
277+
const validator = $(this).attr('id') === 'sni_domain' ? isValidDomain : isValidPort;
278+
if (validator($(this).val())) {
264279
$(this).removeClass('is-invalid');
265280
} else if ($(this).val().trim() !== "") {
266281
$(this).addClass('is-invalid');

core/scripts/webpanel/templates/hysteria_settings.html

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ <h1 class='m-0'>Hysteria Settings</h1>
1919
data-check-obfs-url="{{ url_for('check_obfs') }}"
2020
data-enable-obfs-url="{{ url_for('enable_obfs') }}"
2121
data-disable-obfs-url="{{ url_for('disable_obfs') }}"
22+
data-check-masquerade-url="{{ url_for('check_masquerade') }}"
23+
data-enable-masquerade-url="{{ url_for('enable_masquerade') }}"
24+
data-disable-masquerade-url="{{ url_for('disable_masquerade') }}"
2225
data-set-port-url-template="{{ url_for('set_port_api', port='PORT_PLACEHOLDER') }}"
2326
data-set-sni-url-template="{{ url_for('set_sni_api', sni='SNI_PLACEHOLDER') }}"
2427
data-update-geo-url-template="{{ url_for('update_geo', country='COUNTRY_PLACEHOLDER') }}"
@@ -63,22 +66,57 @@ <h1 class='m-0'>Hysteria Settings</h1>
6366
</div>
6467
<div class="col-md-6 mb-4">
6568
<div class="card card-outline card-secondary h-100">
66-
<div class="card-header"><h3 class="card-title"><i class="fas fa-user-secret"></i> OBFS</h3></div>
67-
<div class="card-body">
68-
<div class="mb-3">
69-
<h5>OBFS Status</h5>
70-
<div id="obfs_status_container" class="p-3 border rounded">
71-
<span id="obfs_status_message">Loading OBFS status...</span>
69+
<div class="card-header p-0">
70+
<ul class="nav nav-tabs" id="obfs-masquerade-tabs" role="tablist">
71+
<li class="nav-item">
72+
<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>
73+
</li>
74+
<li class="nav-item">
75+
<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>
76+
</li>
77+
</ul>
78+
</div>
79+
<div class="card-body tab-content" id="obfs-masquerade-tabs-content">
80+
<div class="tab-pane fade show active" id="obfs-content" role="tabpanel" aria-labelledby="obfs-tab">
81+
<div class="mb-3">
82+
<h5>OBFS Status</h5>
83+
<div id="obfs_status_container" class="p-3 border rounded">
84+
<span id="obfs_status_message">Loading OBFS status...</span>
85+
</div>
86+
</div>
87+
<div class="alert alert-warning" role="alert">
88+
<i class="fas fa-exclamation-triangle"></i>
89+
<strong>Warning:</strong> <small>Changing this setting requires users to update their client configurations.</small>
90+
</div>
91+
<button id="obfs_enable_btn" type='button' class='btn btn-success' style="display: none;">
92+
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
93+
Enable OBFS
94+
</button>
95+
<button id="obfs_disable_btn" type='button' class='btn btn-danger' style="display: none;">
96+
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
97+
Disable OBFS
98+
</button>
99+
</div>
100+
<div class="tab-pane fade" id="masquerade-content" role="tabpanel" aria-labelledby="masquerade-tab">
101+
<div class="mb-3">
102+
<h5>Masquerade Status</h5>
103+
<div id="masquerade_status_container" class="p-3 border rounded">
104+
<span id="masquerade_status_message">Loading Masquerade status...</span>
105+
</div>
106+
</div>
107+
<div class="alert alert-info" role="alert">
108+
<i class="fas fa-info-circle"></i>
109+
<strong>Note:</strong> <small>Masquerade cannot be active at the same time as OBFS.</small>
72110
</div>
111+
<button id="masquerade_enable_btn" type='button' class='btn btn-success' style="display: none;">
112+
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
113+
Enable Masquerade
114+
</button>
115+
<button id="masquerade_disable_btn" type='button' class='btn btn-danger' style="display: none;">
116+
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
117+
Disable Masquerade
118+
</button>
73119
</div>
74-
<button id="obfs_enable_btn" type='button' class='btn btn-success' style="display: none;">
75-
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
76-
Enable OBFS
77-
</button>
78-
<button id="obfs_disable_btn" type='button' class='btn btn-danger' style="display: none;">
79-
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
80-
Disable OBFS
81-
</button>
82120
</div>
83121
</div>
84122
</div>

0 commit comments

Comments
 (0)