@@ -273,6 +278,53 @@
+
+
+
+
@@ -479,6 +531,27 @@
fetchDecoyStatus();
fetchObfsStatus();
fetchNodes();
+ fetchExtraConfigs();
+
+ function escapeHtml(text) {
+ var map = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ };
+ if (text === null || typeof text === 'undefined') {
+ return '';
+ }
+ return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
+ }
+
+ function isValidURI(uri) {
+ if (!uri) return false;
+ const lowerUri = uri.toLowerCase();
+ return lowerUri.startsWith("vmess://") || lowerUri.startsWith("vless://") || lowerUri.startsWith("ss://") || lowerUri.startsWith("trojan://");
+ }
function isValidPath(path) {
if (!path) return false;
@@ -566,7 +639,16 @@
if (Array.isArray(detail)) {
errorMessage = detail.map(err => `Error in '${err.loc[1]}': ${err.msg}`).join('\n');
} else if (typeof detail === 'string') {
- errorMessage = detail;
+ let userMessage = detail;
+ const failMarker = 'failed with exit code';
+ const markerIndex = detail.indexOf(failMarker);
+ if (markerIndex > -1) {
+ const colonIndex = detail.indexOf(':', markerIndex);
+ if (colonIndex > -1) {
+ userMessage = detail.substring(colonIndex + 1).trim();
+ }
+ }
+ errorMessage = userMessage;
}
}
Swal.fire("Error!", errorMessage, "error");
@@ -598,8 +680,10 @@
fieldValid = (input.val().trim() === '') ? true : isValidIPorDomain(input.val());
} else if (id === 'node_ip') {
fieldValid = isValidIPorDomain(input.val());
- } else if (id === 'node_name') {
+ } else if (id === 'node_name' || id === 'extra_config_name') {
fieldValid = input.val().trim() !== "";
+ } else if (id === 'extra_config_uri') {
+ fieldValid = isValidURI(input.val());
} else if (id === 'block_duration' || id === 'max_ips') {
fieldValid = isValidPositiveNumber(input.val());
} else if (id === 'decoy_path') {
@@ -691,10 +775,10 @@
$("#no_nodes_message").hide();
nodes.forEach(node => {
const row = `
- | ${node.name} |
- ${node.ip} |
+ ${escapeHtml(node.name)} |
+ ${escapeHtml(node.ip)} |
- |
@@ -745,6 +829,84 @@
});
}
+ function fetchExtraConfigs() {
+ $.ajax({
+ url: "{{ url_for('get_all_extra_configs') }}",
+ type: "GET",
+ success: function (configs) {
+ renderExtraConfigs(configs);
+ },
+ error: function(xhr) {
+ Swal.fire("Error!", "Failed to fetch extra configurations.", "error");
+ console.error("Error fetching extra configs:", xhr.responseText);
+ }
+ });
+ }
+
+ function renderExtraConfigs(configs) {
+ const tableBody = $("#extra_configs_table tbody");
+ tableBody.empty();
+
+ if (configs && configs.length > 0) {
+ $("#extra_configs_table").show();
+ $("#no_extra_configs_message").hide();
+ configs.forEach(config => {
+ const shortUri = config.uri.length > 50 ? config.uri.substring(0, 50) + '...' : config.uri;
+ const row = `
+ | ${escapeHtml(config.name)} |
+ ${escapeHtml(shortUri)} |
+
+
+ |
+
`;
+ tableBody.append(row);
+ });
+ } else {
+ $("#extra_configs_table").hide();
+ $("#no_extra_configs_message").show();
+ }
+ }
+
+ function addExtraConfig() {
+ if (!validateForm('add_extra_config_form')) return;
+
+ const name = $("#extra_config_name").val().trim();
+ const uri = $("#extra_config_uri").val().trim();
+
+ confirmAction(`add the configuration '${name}'`, function () {
+ sendRequest(
+ "{{ url_for('add_extra_config') }}",
+ "POST",
+ { name: name, uri: uri },
+ `Configuration '${name}' added successfully!`,
+ "#add_extra_config_btn",
+ false,
+ function() {
+ $("#extra_config_name").val('');
+ $("#extra_config_uri").val('');
+ $("#add_extra_config_form .form-control").removeClass('is-invalid');
+ fetchExtraConfigs();
+ }
+ );
+ });
+ }
+
+ function deleteExtraConfig(configName) {
+ confirmAction(`delete the configuration '${configName}'`, function () {
+ sendRequest(
+ "{{ url_for('delete_extra_config') }}",
+ "POST",
+ { name: configName },
+ `Configuration '${configName}' deleted successfully!`,
+ null,
+ false,
+ fetchExtraConfigs
+ );
+ });
+ }
+
function updateServiceUI(data) {
const servicesMap = {
"hysteria_telegram_bot": "#telegram_form",
@@ -1339,6 +1501,11 @@
const nodeName = $(this).data("name");
deleteNode(nodeName);
});
+ $("#add_extra_config_btn").on("click", addExtraConfig);
+ $("#extra_configs_table").on("click", ".delete-extra-config-btn", function() {
+ const configName = $(this).data("name");
+ deleteExtraConfig(configName);
+ });
$('#normal_domain, #sni_domain, #decoy_domain').on('input', function () {
if (isValidDomain($(this).val())) {
@@ -1381,7 +1548,7 @@
}
});
- $('#node_name').on('input', function() {
+ $('#node_name, #extra_config_name').on('input', function() {
if ($(this).val().trim() !== "") {
$(this).removeClass('is-invalid');
} else {
@@ -1389,6 +1556,14 @@
}
});
+ $('#extra_config_uri').on('input', function () {
+ if (isValidURI($(this).val())) {
+ $(this).removeClass('is-invalid');
+ } else if ($(this).val().trim() !== "") {
+ $(this).addClass('is-invalid');
+ }
+ });
+
$('#telegram_api_token, #telegram_admin_id').on('input', function () {
if ($(this).val().trim() !== "") {
$(this).removeClass('is-invalid');
@@ -1418,4 +1593,4 @@
});
-{% endblock %}
+{% endblock %}
\ No newline at end of file