feat(web): Implement full node configuration in settings UI
This commit is contained in:
@ -67,7 +67,11 @@ $(document).ready(function () {
|
||||
function isValidDomain(domain) {
|
||||
if (!domain) return false;
|
||||
const lowerDomain = domain.toLowerCase();
|
||||
return !lowerDomain.startsWith("http://") && !lowerDomain.startsWith("https://");
|
||||
if (lowerDomain.startsWith("http://") || lowerDomain.startsWith("https://")) return false;
|
||||
const ipV4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
if(ipV4Regex.test(domain)) return false;
|
||||
const domainRegex = /^(?!-)(?:[a-zA-Z\d-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+$)[a-zA-Z\d]{1,63}$/;
|
||||
return domainRegex.test(lowerDomain);
|
||||
}
|
||||
|
||||
function isValidPort(port) {
|
||||
@ -75,6 +79,12 @@ $(document).ready(function () {
|
||||
return /^[0-9]+$/.test(port) && parseInt(port) > 0 && parseInt(port) <= 65535;
|
||||
}
|
||||
|
||||
function isValidSha256Pin(pin) {
|
||||
if (!pin) return false;
|
||||
const pinRegex = /^([0-9A-F]{2}:){31}[0-9A-F]{2}$/i;
|
||||
return pinRegex.test(pin.trim());
|
||||
}
|
||||
|
||||
function isValidSubPath(subpath) {
|
||||
if (!subpath) return false;
|
||||
return /^[a-zA-Z0-9]+$/.test(subpath);
|
||||
@ -198,6 +208,14 @@ $(document).ready(function () {
|
||||
}
|
||||
} else if (id === 'decoy_path') {
|
||||
fieldValid = isValidPath(input.val());
|
||||
} else if (id === 'node_port') {
|
||||
fieldValid = (input.val().trim() === '') ? true : isValidPort(input.val());
|
||||
} else if (id === 'node_sni') {
|
||||
fieldValid = (input.val().trim() === '') ? true : isValidDomain(input.val());
|
||||
} else if (id === 'node_pin') {
|
||||
fieldValid = (input.val().trim() === '') ? true : isValidSha256Pin(input.val());
|
||||
} else if (id === 'node_obfs') {
|
||||
fieldValid = true;
|
||||
} else {
|
||||
if (input.attr('placeholder') && input.attr('placeholder').includes('Enter') && !input.attr('id').startsWith('ipv')) {
|
||||
fieldValid = input.val().trim() !== "";
|
||||
@ -265,6 +283,10 @@ $(document).ready(function () {
|
||||
const row = `<tr>
|
||||
<td>${escapeHtml(node.name)}</td>
|
||||
<td>${escapeHtml(node.ip)}</td>
|
||||
<td>${escapeHtml(node.port || 'N/A')}</td>
|
||||
<td>${escapeHtml(node.sni || 'N/A')}</td>
|
||||
<td>${escapeHtml(node.obfs || 'N/A')}</td>
|
||||
<td>${escapeHtml(node.pinSHA256 || 'N/A')}</td>
|
||||
<td>
|
||||
<button class="btn btn-xs btn-danger delete-node-btn" data-name="${escapeHtml(node.name)}">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
@ -284,18 +306,32 @@ $(document).ready(function () {
|
||||
|
||||
const name = $("#node_name").val().trim();
|
||||
const ip = $("#node_ip").val().trim();
|
||||
const port = $("#node_port").val().trim();
|
||||
const sni = $("#node_sni").val().trim();
|
||||
const obfs = $("#node_obfs").val().trim();
|
||||
const pinSHA256 = $("#node_pin").val().trim();
|
||||
|
||||
const data = { name: name, ip: ip };
|
||||
if (port) data.port = parseInt(port);
|
||||
if (sni) data.sni = sni;
|
||||
if (obfs) data.obfs = obfs;
|
||||
if (pinSHA256) data.pinSHA256 = pinSHA256;
|
||||
|
||||
confirmAction(`add the node '${name}'`, function () {
|
||||
sendRequest(
|
||||
API_URLS.addNode,
|
||||
"POST",
|
||||
{ name: name, ip: ip },
|
||||
data,
|
||||
`Node '${name}' added successfully!`,
|
||||
"#add_node_btn",
|
||||
false,
|
||||
function() {
|
||||
$("#node_name").val('');
|
||||
$("#node_ip").val('');
|
||||
$("#node_port").val('');
|
||||
$("#node_sni").val('');
|
||||
$("#node_obfs").val('');
|
||||
$("#node_pin").val('');
|
||||
$("#add_node_form .form-control").removeClass('is-invalid');
|
||||
fetchNodes();
|
||||
}
|
||||
@ -1055,4 +1091,31 @@ $(document).ready(function () {
|
||||
$(this).addClass('is-invalid');
|
||||
}
|
||||
});
|
||||
|
||||
$('#node_port').on('input', function () {
|
||||
const val = $(this).val().trim();
|
||||
if (val === '' || isValidPort(val)) {
|
||||
$(this).removeClass('is-invalid');
|
||||
} else {
|
||||
$(this).addClass('is-invalid');
|
||||
}
|
||||
});
|
||||
|
||||
$('#node_sni').on('input', function () {
|
||||
const val = $(this).val().trim();
|
||||
if (val === '' || isValidDomain(val)) {
|
||||
$(this).removeClass('is-invalid');
|
||||
} else {
|
||||
$(this).addClass('is-invalid');
|
||||
}
|
||||
});
|
||||
|
||||
$('#node_pin').on('input', function () {
|
||||
const val = $(this).val().trim();
|
||||
if (val === '' || isValidSha256Pin(val)) {
|
||||
$(this).removeClass('is-invalid');
|
||||
} else {
|
||||
$(this).addClass('is-invalid');
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -223,8 +223,12 @@
|
||||
<table class="table table-bordered table-striped" id="nodes_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Node Name</th>
|
||||
<th>IP Address / Domain</th>
|
||||
<th>Name</th>
|
||||
<th>IP/Domain</th>
|
||||
<th>Port</th>
|
||||
<th>SNI</th>
|
||||
<th>OBFS</th>
|
||||
<th>Pin SHA256</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -237,18 +241,42 @@
|
||||
|
||||
<hr>
|
||||
<h5>Add New Node</h5>
|
||||
<form id="add_node_form" class="form-inline">
|
||||
<div class="form-group mb-2 mr-sm-2">
|
||||
<label for="node_name" class="sr-only">Node Name</label>
|
||||
<input type="text" class="form-control" id="node_name" placeholder="e.g., Node-US">
|
||||
<div class="invalid-feedback">Please enter a name.</div>
|
||||
<form id="add_node_form">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="node_name">Node Name (Required)</label>
|
||||
<input type="text" class="form-control" id="node_name" placeholder="e.g., Node-US">
|
||||
<div class="invalid-feedback">Please enter a unique name.</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="node_ip">IP Address / Domain (Required)</label>
|
||||
<input type="text" class="form-control" id="node_ip" placeholder="e.g., node.example.com or 1.2.3.4">
|
||||
<div class="invalid-feedback">Please enter a valid IP or domain.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-2 mr-sm-2">
|
||||
<label for="node_ip" class="sr-only">IP Address / Domain</label>
|
||||
<input type="text" class="form-control" id="node_ip" placeholder="e.g., node.example.com">
|
||||
<div class="invalid-feedback">Please enter a valid IP or domain.</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-3">
|
||||
<label for="node_port">Port (Optional)</label>
|
||||
<input type="number" class="form-control" id="node_port" placeholder="e.g., 443">
|
||||
<div class="invalid-feedback">Please enter a valid port (1-65535).</div>
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<label for="node_obfs">OBFS (Optional)</label>
|
||||
<input type="text" class="form-control" id="node_obfs" placeholder="obfs-password">
|
||||
<div class="invalid-feedback">OBFS cannot be empty if provided.</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="node_sni">SNI (Optional)</label>
|
||||
<input type="text" class="form-control" id="node_sni" placeholder="e.g., yourdomain.com">
|
||||
<div class="invalid-feedback">Please enter a valid domain name (not an IP).</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="add_node_btn" class="btn btn-success mb-2">
|
||||
<div class="form-group">
|
||||
<label for="node_pin">Pin SHA256 (Optional)</label>
|
||||
<input type="text" class="form-control" id="node_pin" placeholder="5D:23:0E:E9:10:AB:96:E0:43...">
|
||||
<div class="invalid-feedback">Invalid SHA256 pin format.</div>
|
||||
</div>
|
||||
<button type="button" id="add_node_btn" class="btn btn-success">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
|
||||
Add Node
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user