zoraxy/src/web/components/tcpprox.html
Toby Chui e980bc847b Updated a lot of stuffs
+ Added comments for whitelist
+ Added automatic cert pick for multi-host certs (SNI)
+ Renamed .crt to .pem for cert store
+ Added best-fit selection for wildcard matching rules
+ Added x-proxy-by header
+ Added X-real-Ip header
+ Added Development Mode (Cache-Control: no-store)
+ Updated utm timeout to 10 seconds instead of 90
2024-02-16 15:44:09 +08:00

431 lines
18 KiB
HTML

<div class="standardContainer">
<div class="ui basic segment">
<h2>TCP Proxy</h2>
<p>Proxy traffic flow on layer 3 via TCP/IP</p>
</div>
<div class="ui divider"></div>
<div class="ui basic segment" style="margin-top: 0;">
<h4>TCP Proxy Rules</h4>
<p>A list of TCP proxy rules created on this host. To enable them, use the toggle button on the right.</p>
<div style="overflow-x: auto; min-height: 400px;">
<table id="proxyTable" class="ui celled unstackable table">
<thead>
<tr>
<th>Name</th>
<th>Port/Addr A</th>
<th>Port/Addr B</th>
<th>Mode</th>
<th>Timeout (s)</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<button class="ui basic right floated button" onclick="initProxyConfigList();" title="Refresh List"><i class="ui green refresh icon"></i>Refresh</button>
<br><br>
</div>
<div class="ui divider"></div>
<div class="ui basic segment" id="addproxyConfig">
<h4>Add or Edit TCP Proxy</h4>
<p>Create or edit a new proxy instance</p>
<form id="tcpProxyForm" class="ui form">
<div class="field" style="display:none;">
<label>UUID</label>
<input type="text" name="uuid">
</div>
<div class="field">
<label>Name</label>
<input type="text" name="name" placeholder="Config Name">
</div>
<div class="field">
<label>Port A</label>
<input type="text" name="porta" placeholder="First address or port">
</div>
<div class="field">
<label>Port B</label>
<input type="text" name="portb" placeholder="Second address or port">
</div>
<div class="field">
<label>Timeout (s)</label>
<input type="text" name="timeout" placeholder="Timeout (s)">
</div>
<div class="field">
<label>Mode</label>
<select name="mode" class="ui dropdown">
<option value="">Select Mode</option>
<option value="listen">Listen</option>
<option value="transport">Transport</option>
<option value="starter">Starter</option>
</select>
</div>
<button id="addTcpProxyButton" class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
<button id="editTcpProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);" style="display:none;"><i class="ui green check icon"></i> Update</button>
<button class="ui basic red button" onclick="event.preventDefault(); cancelTCPProxyEdit(event);"><i class="ui red remove icon"></i> Cancel</button>
<div class="ui basic inverted segment" style="background: var(--theme_background_inverted); border-radius: 0.6em;">
<p>TCP Proxy support the following TCP sockets proxy modes</p>
<table class="ui celled padded inverted basic table">
<thead>
<tr><th class="single line">Mode</th>
<th>Public-IP</th>
<th>Concurrent Access</th>
<th>Flow Diagram</th>
</tr></thead>
<tbody>
<tr>
<td>
<h4 class="ui center aligned inverted header">Transport</h4>
</td>
<td class="single line">
Server: <i class="ui green check icon"></i><br>
A: <i class="ui remove icon"></i><br>
B: <i class="ui green check icon"></i> (or same LAN)<br>
</td>
<td>
<i class="ui green check icon"></i>
</td>
<td>Port A (e.g. 25565) <i class="arrow right icon"></i> Server<br>
Server <i class="arrow right icon"></i> Port B (e.g. 192.168.0.2:25565)<br>
<small>Traffic from Port A will be forward to Port B's (IP if provided and) Port</small>
</td>
</tr>
<tr>
<td>
<h4 class="ui center aligned inverted header">Listen</h4>
</td>
<td class="single line">
Server: <i class="ui green check icon"></i><br>
A: <i class="ui remove icon"></i><br>
B: <i class="ui remove icon"></i><br>
</td>
<td>
<i class="ui red times icon"></i>
</td>
<td>Port A (e.g. 8080) <i class="arrow right icon"></i> Server<br>
Port B (e.g. 8081) <i class="arrow right icon"></i> Server<br>
<small>Server will act as a bridge to proxy traffic between Port A and B</small>
</td>
</tr>
<tr>
<td>
<h4 class="ui center aligned inverted header">Starter</h4>
</td>
<td class="single line">
Server: <i class="ui times icon"></i><br>
A: <i class="ui green check icon"></i><br>
B: <i class="ui green check icon"></i><br>
</td>
<td>
<i class="ui red times icon"></i>
</td>
<td>Server <i class="arrow right icon"></i> Port A (e.g. remote.local.:8080) <br>
Server <i class="arrow right icon"></i> Port B (e.g. recv.local.:8081) <br>
<small>Port A and B will be actively bridged</small>
</td>
</tr>
</tbody>
</table>
</div>
</form>
</div>
</div>
<script>
let editingTCPProxyConfigUUID = ""; //The current editing TCP Proxy config UUID
$("#tcpProxyForm .dropdown").dropdown();
$('#tcpProxyForm').on('submit', function(event) {
event.preventDefault();
//Check if update mode
if ($("#editTcpProxyButton").is(":visible")){
confirmEditTCPProxyConfig(event);
return;
}
var form = $(this);
var formValid = validateTCPProxyConfig(form);
if (!formValid){
return;
}
// Send the AJAX POST request
$.ajax({
type: 'POST',
url: '/api/tcpprox/config/add',
data: form.serialize(),
success: function(response) {
if (response.error) {
msgbox(response.error, false, 6000);
}else{
msgbox("Config Added");
}
clearTCPProxyAddEditForm();
initProxyConfigList();
$("#addproxyConfig").slideUp("fast");
},
error: function() {
msgbox('An error occurred while processing the request', false);
}
});
});
function clearTCPProxyAddEditForm(){
$('#tcpProxyForm input, #tcpProxyForm select').val('');
$('#tcpProxyForm select').dropdown('clear');
}
function cancelTCPProxyEdit(event=undefined) {
clearTCPProxyAddEditForm();
$("#addTcpProxyButton").show();
$("#editTcpProxyButton").hide();
}
function validateTCPProxyConfig(form){
//Check if name is filled. If not, generate a random name for it
var name = form.find('input[name="name"]').val()
if (name == ""){
let randomName = "Proxy Rule (#" + Math.round(Date.now()/1000) + ")";
form.find('input[name="name"]').val(randomName);
}
// Validate timeout is an integer
var timeout = parseInt(form.find('input[name="timeout"]').val());
if (form.find('input[name="timeout"]').val() == ""){
//Not set. Assign a random one to it
form.find('input[name="timeout"]').val("10");
timeout = 10;
}
if (isNaN(timeout)) {
form.find('input[name="timeout"]').parent().addClass("error");
msgbox('Timeout must be a valid integer', false, 5000);
return false;
}else{
form.find('input[name="timeout"]').parent().removeClass("error");
}
// Validate mode is selected
var mode = form.find('select[name="mode"]').val();
if (mode === '') {
form.find('select[name="mode"]').parent().addClass("error");
msgbox('Please select a mode', false, 5000);
return false;
}else{
form.find('select[name="mode"]').parent().removeClass("error");
}
return true;
}
function renderProxyConfigs(proxyConfigs) {
var tableBody = $('#proxyTable tbody');
tableBody.empty();
if (proxyConfigs.length === 0) {
var noResultsRow = $('<tr><td colspan="7"><i class="green check circle icon"></i>No Proxy Configs</td></tr>');
tableBody.append(noResultsRow);
} else {
proxyConfigs.forEach(function(config) {
var runningLogo = 'Stopped';
var runningClass = "stopped";
var startButton = `<button onclick="startTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="green play icon"></i> Start Proxy</button>`;
if (config.Running){
runningLogo = 'Running';
startButton = `<button onclick="stopTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="red stop icon"></i> Stop Proxy</button>`;
runningClass = "running"
}
var modeText = "Unknown";
if (config.Mode == 0){
modeText = "Listen";
}else if (config.Mode == 1){
modeText = "Transport";
}else if (config.Mode == 2){
modeText = "Starter";
}
var thisConfig = encodeURIComponent(JSON.stringify(config));
var row = $(`<tr class="tcproxConfig ${runningClass}" uuid="${config.UUID}" config="${thisConfig}">`);
row.append($('<td>').html(`
${config.Name}
<div class="statusText">${runningLogo}</div>`));
row.append($('<td>').text(config.PortA));
row.append($('<td>').text(config.PortB));
row.append($('<td>').text(modeText));
row.append($('<td>').text(config.Timeout));
row.append($('<td>').html(`
<div class="ui basic vertical fluid tiny buttons">
<button class="ui button" onclick="validateProxyConfig('${config.UUID}', this);" title="Validate Config"><i class="teal question circle outline icon"></i> CXN Test</button>
${startButton}
<button onclick="editTCPProxyConfig('${config.UUID}');" class="ui button" title="Edit Config"><i class="edit icon"></i> Edit </button>
<button onclick="deleteTCPProxyConfig('${config.UUID}');" class="ui red basic button" title="Delete Config"><i class="trash icon"></i> Remove</button>
</div>
`));
tableBody.append(row);
});
}
}
function getConfigDetailsFromDOM(configUUID){
let thisConfig = null;
$(".tcproxConfig").each(function(){
let uuid = $(this).attr("uuid");
if (configUUID == uuid){
//This is the one we are looking for
thisConfig = JSON.parse(decodeURIComponent($(this).attr("config")));
}
});
return thisConfig;
}
function validateProxyConfig(configUUID, btn){
$(btn).html(`<i class="ui loading spinner icon"></i>`);
$.ajax({
url: "/api/tcpprox/config/validate",
data: {uuid: configUUID},
success: function(data){
if (data.error != undefined){
let errormsg = data.error.charAt(0).toUpperCase() + data.error.slice(1);
$(btn).html(`<i class="red times icon"></i> ${errormsg}`);
msgbox(data.error, false, 6000);
}else{
$(btn).html(`<i class="green check icon"></i> Config Valid`);
msgbox("Config Check Passed");
}
}
})
}
function editTCPProxyConfig(configUUID){
let targetConfig = getConfigDetailsFromDOM(configUUID);
if (targetConfig != null){
$("#addTcpProxyButton").hide();
$("#editTcpProxyButton").show();
$.each(targetConfig, function(key, value) {
var field = $("#tcpProxyForm").find('[name="' + key.toLowerCase() + '"]');
if (field.length > 0) {
if (field.is('input')) {
field.val(value);
}else if (field.is('select')){
if (key.toLowerCase() == "mode"){
if (value == 0){
value = "listen";
}else if (value == 1){
value = "transport";
}else if (value == 2){
value = "starter";
}
}
$(field).dropdown("set selected", value);
}
}
});
editingTCPProxyConfigUUID = configUUID;
$("#addproxyConfig").slideDown("fast");
}else{
msgbox("Unable to load target config", false);
}
}
function confirmEditTCPProxyConfig(event){
event.preventDefault();
event.stopImmediatePropagation();
var form = $("#tcpProxyForm");
var formValid = validateTCPProxyConfig(form);
if (!formValid){
return;
}
// Send the AJAX POST request
$.ajax({
type: 'POST',
url: '/api/tcpprox/config/edit',
data: form.serialize(),
success: function(response) {
if (response.error) {
msgbox(response.error, false, 6000);
}else{
msgbox("Config Updated");
}
initProxyConfigList();
cancelTCPProxyEdit();
},
error: function() {
msgbox('An error occurred while processing the request', false);
}
});
}
function deleteTCPProxyConfig(configUUID){
$.ajax({
url: "/api/tcpprox/config/delete",
method: "POST",
data: {uuid: configUUID},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false, 6000);
}else{
msgbox("Proxy Config Removed");
initProxyConfigList();
}
}
})
}
//Start a TCP proxy by their config UUID
function startTcpProx(configUUID){
$.ajax({
url: "/api/tcpprox/config/start",
method: "POST",
data: {uuid: configUUID},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false, 6000);
}else{
msgbox("Service Started");
initProxyConfigList();
}
}
});
}
//Stop a TCP proxy by their config UUID
function stopTcpProx(configUUID){
$.ajax({
url: "/api/tcpprox/config/stop",
method: "POST",
data: {uuid: configUUID},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false, 6000);
}else{
msgbox("Service Stopped");
initProxyConfigList();
}
}
});
}
function initProxyConfigList(){
$.ajax({
type: 'GET',
url: '/api/tcpprox/config/list',
success: function(response) {
renderProxyConfigs(response);
},
error: function() {
msgbox('Unable to load proxy configs', false);
}
});
}
initProxyConfigList();
</script>
</div>