streamprox.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. <div class="standardContainer">
  2. <div class="ui basic segment">
  3. <h2>Stream Proxy</h2>
  4. <p>Proxy traffic flow on layer 3 via TCP or UDP</p>
  5. </div>
  6. <div class="ui divider"></div>
  7. <div class="ui basic segment" style="margin-top: 0;">
  8. <h3>TCP / UDP Proxy Rules</h3>
  9. <p>A list of TCP / UDP proxy rules created on this host.</p>
  10. <div style="overflow-x: auto; ">
  11. <table id="proxyTable" class="ui celled basic unstackable table">
  12. <thead>
  13. <tr>
  14. <th>Name</th>
  15. <th>Listening Address</th>
  16. <th>Target Address</th>
  17. <th>Mode</th>
  18. <th>Timeout (s)</th>
  19. <th>Actions</th>
  20. </tr>
  21. </thead>
  22. <tbody>
  23. </tbody>
  24. </table>
  25. </div>
  26. <br>
  27. <button class="ui basic right floated button" onclick="initProxyConfigList();" title="Refresh List"><i class="ui green refresh icon"></i>Refresh</button>
  28. <br><br>
  29. </div>
  30. <div class="ui divider"></div>
  31. <div class="ui basic segment" id="addproxyConfig">
  32. <h3>Add or Edit Stream Proxy</h3>
  33. <p>Create or edit a new stream proxy instance</p>
  34. <form id="streamProxyForm" class="ui form">
  35. <div class="field" style="display:none;">
  36. <label>UUID</label>
  37. <input type="text" name="uuid">
  38. </div>
  39. <div class="field">
  40. <label>Name</label>
  41. <input type="text" name="name" placeholder="Config Name">
  42. </div>
  43. <div class="field">
  44. <label>Listening Address with Port</label>
  45. <input type="text" name="listenAddr" placeholder="">
  46. <small>Address to listen on this host. e.g. :25565 or 127.0.0.1:25565. <br>
  47. If you are using Docker, you will also need to expose this port to host network.</small>
  48. </div>
  49. <div class="field">
  50. <label>Proxy Target Address with Port</label>
  51. <input type="text" name="proxyAddr" placeholder="">
  52. <small>Server address to forward TCP / UDP package. e.g. 192.168.1.100:25565</small>
  53. </div>
  54. <div class="field">
  55. <label>Timeout (s)</label>
  56. <input type="text" name="timeout" placeholder="" value="10">
  57. <small>Connection timeout in seconds</small>
  58. </div>
  59. <Br>
  60. <div class="field">
  61. <div class="ui toggle checkbox">
  62. <input type="checkbox" tabindex="0" name="useTCP" class="hidden">
  63. <label>Enable TCP<br>
  64. <small>Forward TCP request on this listening socket</small>
  65. </label>
  66. </div>
  67. </div>
  68. <div class="field">
  69. <div class="ui toggle checkbox">
  70. <input type="checkbox" tabindex="0" name="useUDP" class="hidden">
  71. <label>Enable UDP<br>
  72. <small>Forward UDP request on this listening socket</small></label>
  73. </div>
  74. </div>
  75. <button id="addStreamProxyButton" class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
  76. <button id="editStreamProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);" style="display:none;"><i class="ui green check icon"></i> Update</button>
  77. <button class="ui basic red button" onclick="event.preventDefault(); cancelStreamProxyEdit(event);"><i class="ui red remove icon"></i> Cancel</button>
  78. </form>
  79. </div>
  80. </div>
  81. <script>
  82. let editingStreamProxyConfigUUID = ""; //The current editing TCP Proxy config UUID
  83. $("#streamProxyForm .dropdown").dropdown();
  84. $('#streamProxyForm').on('submit', function(event) {
  85. event.preventDefault();
  86. //Check if update mode
  87. if ($("#editStreamProxyButton").is(":visible")){
  88. confirmEditTCPProxyConfig(event);
  89. return;
  90. }
  91. var form = $(this);
  92. var formValid = validateTCPProxyConfig(form);
  93. if (!formValid){
  94. return;
  95. }
  96. // Send the AJAX POST request
  97. $.ajax({
  98. type: 'POST',
  99. url: '/api/streamprox/config/add',
  100. data: form.serialize(),
  101. success: function(response) {
  102. if (response.error) {
  103. msgbox(response.error, false, 6000);
  104. }else{
  105. msgbox("Config Added");
  106. }
  107. clearStreamProxyAddEditForm();
  108. initProxyConfigList();
  109. $("#addproxyConfig").slideUp("fast");
  110. },
  111. error: function() {
  112. msgbox('An error occurred while processing the request', false);
  113. }
  114. });
  115. });
  116. function clearStreamProxyAddEditForm(){
  117. $('#streamProxyForm input, #streamProxyForm select').val('');
  118. $('#streamProxyForm select').dropdown('clear');
  119. }
  120. function cancelStreamProxyEdit(event=undefined) {
  121. clearStreamProxyAddEditForm();
  122. $("#addStreamProxyButton").show();
  123. $("#editStreamProxyButton").hide();
  124. }
  125. function validateTCPProxyConfig(form){
  126. //Check if name is filled. If not, generate a random name for it
  127. var name = form.find('input[name="name"]').val()
  128. if (name == ""){
  129. let randomName = "Proxy Rule (#" + Math.round(Date.now()/1000) + ")";
  130. form.find('input[name="name"]').val(randomName);
  131. }
  132. // Validate timeout is an integer
  133. var timeout = parseInt(form.find('input[name="timeout"]').val());
  134. if (form.find('input[name="timeout"]').val() == ""){
  135. //Not set. Assign a random one to it
  136. form.find('input[name="timeout"]').val("10");
  137. timeout = 10;
  138. }
  139. if (isNaN(timeout)) {
  140. form.find('input[name="timeout"]').parent().addClass("error");
  141. msgbox('Timeout must be a valid integer', false, 5000);
  142. return false;
  143. }else{
  144. form.find('input[name="timeout"]').parent().removeClass("error");
  145. }
  146. // Validate mode is selected
  147. var mode = form.find('select[name="mode"]').val();
  148. if (mode === '') {
  149. form.find('select[name="mode"]').parent().addClass("error");
  150. msgbox('Please select a mode', false, 5000);
  151. return false;
  152. }else{
  153. form.find('select[name="mode"]').parent().removeClass("error");
  154. }
  155. return true;
  156. }
  157. function renderProxyConfigs(proxyConfigs) {
  158. var tableBody = $('#proxyTable tbody');
  159. tableBody.empty();
  160. if (proxyConfigs.length === 0) {
  161. var noResultsRow = $('<tr><td colspan="7"><i class="green check circle icon"></i>No Proxy Configs</td></tr>');
  162. tableBody.append(noResultsRow);
  163. } else {
  164. proxyConfigs.forEach(function(config) {
  165. var runningLogo = 'Stopped';
  166. var runningClass = "stopped";
  167. var startButton = `<button onclick="startStreamProx('${config.UUID}');" class="ui basic mini circular icon button" title="Start Proxy"><i class="green play icon"></i></button>`;
  168. if (config.Running){
  169. runningLogo = 'Running';
  170. startButton = `<button onclick="stopStreamProx('${config.UUID}');" class="ui basic mini circular icon button" title="Stop Proxy"><i class="red stop icon"></i></button>`;
  171. runningClass = "running"
  172. }
  173. var modeText = [];
  174. if (config.UseTCP){
  175. modeText.push("TCP")
  176. }
  177. if (config.UseUDP){
  178. modeText.push("UDP")
  179. }
  180. modeText = modeText.join(" & ")
  181. var thisConfig = encodeURIComponent(JSON.stringify(config));
  182. var row = $(`<tr class="streamproxConfig ${runningClass}" uuid="${config.UUID}" config="${thisConfig}">`);
  183. row.append($('<td>').html(`
  184. ${config.Name}
  185. <div class="statusText">${runningLogo}</div>`));
  186. row.append($('<td>').text(config.ListeningAddress));
  187. row.append($('<td>').text(config.ProxyTargetAddr));
  188. row.append($('<td>').text(modeText));
  189. row.append($('<td>').text(config.Timeout));
  190. row.append($('<td>').html(`
  191. ${startButton}
  192. <button onclick="editTCPProxyConfig('${config.UUID}');" class="ui circular basic mini icon button" title="Edit Config"><i class="edit icon"></i></button>
  193. <button onclick="deleteTCPProxyConfig('${config.UUID}');" class="ui circular red basic mini icon button" title="Delete Config"><i class="trash icon"></i></button>
  194. `));
  195. tableBody.append(row);
  196. });
  197. }
  198. }
  199. function getConfigDetailsFromDOM(configUUID){
  200. let thisConfig = null;
  201. $(".streamproxConfig").each(function(){
  202. let uuid = $(this).attr("uuid");
  203. if (configUUID == uuid){
  204. //This is the one we are looking for
  205. thisConfig = JSON.parse(decodeURIComponent($(this).attr("config")));
  206. }
  207. });
  208. return thisConfig;
  209. }
  210. function editTCPProxyConfig(configUUID){
  211. let targetConfig = getConfigDetailsFromDOM(configUUID);
  212. if (targetConfig != null){
  213. $("#addStreamProxyButton").hide();
  214. $("#editStreamProxyButton").show();
  215. $.each(targetConfig, function(key, value) {
  216. var field;
  217. if (key == "UseTCP"){
  218. let checkboxEle = $("#streamProxyForm input[name=useTCP]").parent();
  219. if (value === true){
  220. $(checkboxEle).checkbox("set checked");
  221. }else{
  222. $(checkboxEle).checkbox("set unchecked");
  223. }
  224. return;
  225. }else if (key == "UseUDP"){
  226. let checkboxEle = $("#streamProxyForm input[name=useUDP]").parent();
  227. if (value === true){
  228. $(checkboxEle).checkbox("set checked");
  229. }else{
  230. $(checkboxEle).checkbox("set unchecked");
  231. }
  232. return;
  233. }else if (key == "ListeningAddress"){
  234. field = $("#streamProxyForm input[name=listenAddr]");
  235. }else if (key == "ProxyTargetAddr"){
  236. field = $("#streamProxyForm input[name=proxyAddr]");
  237. }else if (key == "UUID"){
  238. field = $("#streamProxyForm input[name=uuid]");
  239. }else if (key == "Name"){
  240. field = $("#streamProxyForm input[name=name]");
  241. }else if (key == "Timeout"){
  242. field = $("#streamProxyForm input[name=timeout]");
  243. }
  244. if (field != undefined && field.length > 0) {
  245. field.val(value);
  246. }
  247. });
  248. editingStreamProxyConfigUUID = configUUID;
  249. $("#addproxyConfig").slideDown("fast");
  250. }else{
  251. msgbox("Unable to load target config", false);
  252. }
  253. }
  254. function confirmEditTCPProxyConfig(event){
  255. event.preventDefault();
  256. event.stopImmediatePropagation();
  257. var form = $("#streamProxyForm");
  258. var formValid = validateTCPProxyConfig(form);
  259. if (!formValid){
  260. return;
  261. }
  262. // Send the AJAX POST request
  263. $.ajax({
  264. type: 'POST',
  265. url: '/api/streamprox/config/edit',
  266. method: "POST",
  267. data: {
  268. uuid: $("#streamProxyForm input[name=uuid]").val().trim(),
  269. name: $("#streamProxyForm input[name=name]").val().trim(),
  270. listenAddr: $("#streamProxyForm input[name=listenAddr]").val().trim(),
  271. proxyAddr: $("#streamProxyForm input[name=proxyAddr]").val().trim(),
  272. useTCP: $("#streamProxyForm input[name=useTCP]")[0].checked ,
  273. useUDP: $("#streamProxyForm input[name=useUDP]")[0].checked ,
  274. timeout: parseInt($("#streamProxyForm input[name=timeout]").val().trim()),
  275. },
  276. success: function(response) {
  277. if (response.error) {
  278. msgbox(response.error, false, 6000);
  279. }else{
  280. msgbox("Config Updated");
  281. }
  282. initProxyConfigList();
  283. cancelStreamProxyEdit();
  284. },
  285. error: function() {
  286. msgbox('An error occurred while processing the request', false);
  287. }
  288. });
  289. }
  290. function deleteTCPProxyConfig(configUUID){
  291. $.ajax({
  292. url: "/api/streamprox/config/delete",
  293. method: "POST",
  294. data: {uuid: configUUID},
  295. success: function(data){
  296. if (data.error != undefined){
  297. msgbox(data.error, false, 6000);
  298. }else{
  299. msgbox("Proxy Config Removed");
  300. initProxyConfigList();
  301. }
  302. }
  303. })
  304. }
  305. //Start a TCP proxy by their config UUID
  306. function startStreamProx(configUUID){
  307. $.ajax({
  308. url: "/api/streamprox/config/start",
  309. method: "POST",
  310. data: {uuid: configUUID},
  311. success: function(data){
  312. if (data.error != undefined){
  313. msgbox(data.error, false, 6000);
  314. }else{
  315. msgbox("Service Started");
  316. initProxyConfigList();
  317. }
  318. }
  319. });
  320. }
  321. //Stop a TCP proxy by their config UUID
  322. function stopStreamProx(configUUID){
  323. $.ajax({
  324. url: "/api/streamprox/config/stop",
  325. method: "POST",
  326. data: {uuid: configUUID},
  327. success: function(data){
  328. if (data.error != undefined){
  329. msgbox(data.error, false, 6000);
  330. }else{
  331. msgbox("Service Stopped");
  332. initProxyConfigList();
  333. }
  334. }
  335. });
  336. }
  337. function initProxyConfigList(){
  338. $.ajax({
  339. type: 'GET',
  340. url: '/api/streamprox/config/list',
  341. success: function(response) {
  342. renderProxyConfigs(response);
  343. },
  344. error: function() {
  345. msgbox('Unable to load proxy configs', false);
  346. }
  347. });
  348. }
  349. initProxyConfigList();
  350. </script>
  351. </div>