From adddc11406affaee61cbaee948a49a8105398c6b Mon Sep 17 00:00:00 2001 From: AlexShmak Date: Wed, 17 Dec 2025 14:56:01 +0300 Subject: [PATCH 1/8] feat(textbox): add template for textbox element --- front/src/static/assets/css/workspace.css | 1 + front/src/static/icons.js | 1 + front/src/static/netfront.js | 16 ++++++++++++++++ front/src/static/netfront_f.js | 21 +++++++++++++++++++++ front/src/templates/network.html | 11 +++++++++++ 5 files changed, 50 insertions(+) diff --git a/front/src/static/assets/css/workspace.css b/front/src/static/assets/css/workspace.css index acdf7de7..7ba0dde9 100644 --- a/front/src/static/assets/css/workspace.css +++ b/front/src/static/assets/css/workspace.css @@ -70,6 +70,7 @@ text-align: center; color: #222; overflow-y: auto; + margin-bottom: 10px; } .ws-menu-settings-name { diff --git a/front/src/static/icons.js b/front/src/static/icons.js index 6dd9f1cd..c8fcdf65 100644 --- a/front/src/static/icons.js +++ b/front/src/static/icons.js @@ -16,4 +16,5 @@ const DiagramIcons = { document: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAACxMAAAsTAQCanBgAAAH8SURBVGje7Zm/SyNBFMc/k12JP8ATVLjr/FGJ+AcoCNan2EnigWAtYiVaqIWoYHGNlYVicXCFhWAngojYWenJXWEhCmqws7kcaNSxMI3KZt9kdxLCzTdFwszb2c+878tjhwWn/10qYLya+girZvmHloX6AeODrJMr8vYJtpnnMlpmRtARPo/8oEVKa0Me31igVRLqF5y9Y5yswY2X6ASeUHikUMxxUeweXi3I8MnoqgM0ml120Ghy/Aw3whcsa6rfbJCkD580OiwLdmrgD5McoEkwHFYLdgDgmEn2gQRpFmkvPYDmmCn2AI8hFmkrJUAdjXzhMxlWOAWqSJEKCrZRhKOk878UtfnvwH+TDYAkSXmwrRoQK94MTNPE85sRxXc6Sgdw9GFEMVv4krJb4ADKDiArwmZqjFfOcSt5LpQBrDJg+ISY4Ixu7uMCqDHrbgD5JiwgLbNkGZhh5V2HC5MiKzNNBnBiLwMVYkE/rcYW3LHJU1wAY3w13toZWxKACrHglivjRnQtOx9LLfCMt/bMQ3wAgpZarCqkBpbpFdSAYoJfdgC66BHFNZhnoEIs2Oacx9AoxbUtgDV7GSi7BQ7AATgAB+AA/JDZDv5GWl+FnRELAzRxaHmLIdOKqsgARfJpkBwrhPKCz1VBb80aaZO+9xJIkeEmttWcnOLVCybGe0qF0Rl6AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTAyLTI5VDExOjU5OjE2KzAxOjAwq3IToAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMC0wMi0yOVQxMTo1OToxNiswMTowMNovqxwAAAAASUVORK5CYII=", phone: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAACxMAAAsTAQCanBgAAAZ6SURBVGje7dnbb1zVFQbw3zlzxtfYudhJcCAXTAIJDhA1TUWFKK2gVUW59KGgto9973/TZ9S3VkJUgARSL6gQKkECKAScxMTO1SbxbcYX8GVu5/TBJ5MZe2gcZwIPZT2Mzqy9z17fXnvtb+29Dt/L/7sE1adQVvgtWY2VxPUAQts8bNu3AiGWd1Z+BUKUKjP2+YPHhDeQ3TUJxU77k7l6AIEO+9xvyoS4ZmGaK4nQTtvN6bhhI6o2Bsh52zuW7yKANk97Tk3sRXXNC874l8W6l4Jqq4b62zFPh3v8rHasqK5LUDNsoMUmXVqFqFj2lQXFtDW01VbZ2zBfNGN2lY01AG5KxlYDjtitU4jYvCtOOWdOghbHPGXrOv0QSIx7z4m1Id4YQKjP055zSFZZglBkyWfe9J4piciAF2wzp7wOAC02GTPu9Krl/UYAm/3E7/W74oxJJbTqc9jjuiz7t68FWnSY8XcTKreYe8Z9ntCutVGHRgBC+/1Cv0+86jPzKohscczLBvzcBeeQKBn1hqFb+iByxD0eaLxcawEkWvU7YMY7/mM6nV9gyoId9jpotxEJEkWzckq3BDCruGYXVWe7Vlr1aHPdRbNV9yYq8i6Y0qUnjf4gbbkjaQQgo0Vg0eKqmC372pJQi0yNv+5QGgdhgETSYPik7jmQ+caNfLNXJFy9+28FYH2SscVhnbcMwoxDepoPIBE64I8W17EM3fqq4dwkALFJQ3bK6l5H78C4y6Ya+WqjAIo+lNexykytm2sjKMC885bW2tsYgEBsUrkuGcUWfKWSmo10pVnkxhsFeeVmAUhk/cCTtgokVgj3ax87nqYqejzuR9qptk9638m18bLRJYg84jd6LSpLZLRbEDotl5ro8mMvabWgLJDV7oppn1lqFoBAi3az3jUl0e2ovdpqCCrUJuuKk3IydjmmTWujrXgnPFBy1RuGxXaK9KmlqQRLznjNBVmP2GbPepPR+iVjhyftF9viAdGq9U202OcpB0X22CXTmC82CiBRFrvXS4oSkc2K1R2wImUZA3YpC7TabGxV+x0CqBh1Qk/1f2jOSA0rLhn2gY40nQUCXxprlLg3ygNF7xrUUjfjGdNVAHmvO14TlIFl0xbXnoo2TsVT8jJp3ozT3Jmk4yXKrhsXCIRpn4pKo1vXnQRhl+1aUDRlVixjs17tWDZlXlmo205tEsumzDUaZOMAsh7xrHsw7m0fKmhx2C/tERr1lo8tiQx43h6xMW87odBMAKGdHrMXV3wiRGS7ww4JbfWRDEK9HnVIuappIoCys/5iC2adVUbBkFftwJQhRZSd91fbJXKGGh9eNw6g5JzhNMDKKij6woiwRlN23oX0ZlVp7oFkZRGidPBYBYFQVgYVsVhSp0maDSDrgCPpEnxqWElWvyN6BaadckFJRr+jeiVyThlpHhGtvPmw36VBWHRJSauDXnJQaFjBqJLIg37roJIRS640F0As7wvzuJ7WeypmDCsKjMqJkZh1XkHZmJnGxZ87CcLTxrWiYEIJBYMmtWPJhAJKzpjWIbFsolpbaJoHZsynz5WqB+o1sVlfVam4yR4I9dqVzveaabGMHn06seianIpQj/t0Siz60nRzc0GLI37tXnzpdccta/OoF/ULXPI3H1iUddjL7he76jXvW14vgJWTbKP7XG0Nqdte+xDpFiDUZY/9ApVUE+iy2wExqWZdACqKEh06Vt2dI5u0ixVUUPK5V3Rj3mA1CP9sG/IGUyo+4xU9EjM+Xz8VF+Qs69NvsKZAEdrmAduNy6eDj7icMuENKr7oap2m7LKxqmadQRhYdtGwJzwtv6pE81PthowqI5DVkdLsYkq8We2iOk2kU4SypUYR0NgDsRH/0OeoXmfTPb5SpOoz6J+uihF50LHU4R85p6TFfj+0Q2DKSecVRfZ73A6xaScb58PGQTjnuKxfedjuujLdB950wkL65gEvplQ8azgF8LyHhIbNuKQoo98LHlIxLH87uSB23Vsu/49CJbEJp1zDhAkxyiadlhcYS4t3sUmfmha7ZvL2iKgi50ODNmlrWKqlZND19EyYS3fBGePasCyniJIh0yld59azC5K6y1VBQe4bi9Wx2eox84ZP5qpUnKS/qzVWV55qAQQ6DXjmLpfrB3TKNwKQoMezjt71DxY9Rm96Iao2Lbpss1CnjXwLWC8AFl1y+eYl7jv/aPWdf7b7Xr6X/wL6oqEHXcA//QAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMC0wMi0yOVQxMjo1NzozNiswMTowMEpUSKAAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjAtMDItMjlUMTI6NTc6MzYrMDE6MDA7CfAcAAAAAElFTkSuQmCC", folder: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAfxJREFUeJzt279rFEEYxvHPxROjxmBppYKNEAsLGwsLBWs70cI2bXrBVlQEEUv/C2ttBK0EbRIQAqKlREGMmESysdjdcyyCcLu37507X3iZ2ebleZ+dGfbHDJlMrxkk/TmcxpExc32uYia5gnXsNYgCj3GgY+2NuYgdzYpP41G38psxwEtcwgbu4vuYeVawVF1fxfM2BHZBffefNMyzlORaN/5a0ilDHKz62w1zreIe7uAM7uO20pBpYqeKEfXcfdhC8kN4q731ZBJR4AXOTcIAOIUPU1Dov+Ibzg5bKjrlI87jFk5MIH9TFrCMRTyg/REwCzxV1rw5F60kiB9VO99XA0b03oB0EVzADcwHaemKLRytLwb+PKgU+jMidpUvbbupAX2kSKfAV1zDZpCYrjiGZziu+h5SPwe8CRTVNe9Udadzvk9TYVRrXxa9fckGRAuIJhsQLSCabEC0gGiyAdECoskGRAuIJhuQ9Hv/MrQaICSKtaotUgO+RCgJYqNq9/IaEC0gmmxAtIBosgHRAqLJBkQLiCYbEC0gmmxAtIBosgHRAqLJBkQLiCYbEC0gmmyAcssY5Vb3vlDvhfw1xGvlkZmb+GS8IzOzxCKuV/1XlIemtsXv3+86tnChduUy3k+BqK5iTTnq/zo4OcBJHPZ/81M51fv0JyyT2Y/f3pw3gUxucUoAAAAASUVORK5CYII=", + textbox: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABRCAYAAAAtvqMwAAAAAXNSR0IArs4c6QAAA2NJREFUeF7tnNFx2zAMhiG3e7TZJN5AG7TeoBf3PfF7nesGTTfQBnUmiTpIqh5lJ5EdKflBE4Sk+32Xp0AA+H8ERckGC+HHVYHCNTqDCwE4TwICIABnBZzDswIIwFkB5/CsAAJwVsA5PCtg0gDKb5/3+S++OI/DI/xfkUUt1Y/dOcHjKqAVPohe3JwTfD7XNjdS3W5ixqMHUH6/FGn+xASb+TW1SLHSVoQOQDvzPzycCFmLNHczF7dneEVYfsPfZeeftcjjUqqfNaqHEsA6zPxOwGKpJY4mNhm71yvCTqrtEs1fC6B5cUzxn7Uor76KFL9etHm8QKsAB3AapNri16LTYcp25ToszYddIT45cRHLq+vOrqe/zFpI8mnKOr6f+7/fvbO77C7P+K4oMYDTe8T7w5mexcDsJoBcKAkgl9IDcQiAAERk4CbMe8B+dvAmbFglXIIMxUVcEwCikqENARiKi7gmAEQlQxsCMBQXcU0AiEqGNgRgKC7imgAQlQxtCMBQXMQ1ASAqGdoQgKG4iGsCQFQytCEAQ3ER1wSAqGRoQwCG4iKuCQBRydCGAAzFRVwTAKKSoQ0BGIqLuCYARCVDGwIwFBdxTQCISoY2BGAoLuKaABCVDG0IwFBcxPW4AYQukW7PFDKiidkMNOKN4ufpE5MyabpHHTLNSqpbqHFR0aBx1J5aS7W9SDqAqTsr153+OZMesVctqndSbVdT1y1J/sftWyKK/jm8AkKmp4EUP8NOMtCxOek9MQD/aXoYjhJAWwV9N9rQmAw3J49Nx8h8+jYbqh5hPYC2CnhOxAAwtfhxAJ6i7/uGwykpM992vlkfh8ovNrEnBuiWoKFc2qr4eGhSjizoNy9rruNBF/CxAarMzzym5ilWGgCqzCOMy3W474QmcO1n9NtlAtAiTWxPAIkF1bojAK1iie0JILGgWncEoFUssX1+APvXGeHZYUzPD2E/vxNp7tG3mKk45AVw9M481RCS+gnn321yQsgNoPPKNqlwKZ1FvVKITSAfgOkcd5n14S0jgN4jL2MnjuV1Wb/nyAcgSNZ/7qilmFrfWZefkFxeAM8QFmEHNLLD/Rb3sW80tZS79vkBnJPtDK8lAGeoBEAAzgo4h2cFEICzAs7hWQEE4KyAc3hWAAE4K+Ac/j9oMVNwgtAfYAAAAABJRU5ErkJggg==", }; \ No newline at end of file diff --git a/front/src/static/netfront.js b/front/src/static/netfront.js index d1a9f436..e7f64de0 100644 --- a/front/src/static/netfront.js +++ b/front/src/static/netfront.js @@ -98,6 +98,22 @@ $('#network_scheme').droppable({ interface: [], }); } + else if (type === 'textbox'){ + let node_id = TextboxUid(); + nodes.push( + { + data: {id: node_id, label: node_id}, + position: {x: CalculateDropOffset(ui.position.left, ui.position.top).x, y: CalculateDropOffset(ui.position.left, ui.position.top).y}, + classes: ['textbox'], + config: { + type: 'textbox', + label: node_id, + text: 'Введите текст здесь' + }, + interface: [], + }); + + } else { return; } diff --git a/front/src/static/netfront_f.js b/front/src/static/netfront_f.js index e5b10fae..f0794e42 100644 --- a/front/src/static/netfront_f.js +++ b/front/src/static/netfront_f.js @@ -22,6 +22,23 @@ const uid = function(){ return Date.now().toString(36) + Math.random().toString(36).substr(2); } +const TextboxUid = function(){ + let textbox_name = "textbox_"; + + let textbox; + for (let textbox_number = 1; textbox_number < 100; textbox_number++) { + textbox = textbox_name + textbox_number; + + let t = nodes.find(t => t.data.id === textbox); + + if (!t) { + return textbox; + } + } + + return "text_" + uid(); +} + const HostUid = function(){ let host_name = "host_"; @@ -808,6 +825,10 @@ const prepareStylesheet = function() { return label; } + if (n.config.type === 'textbox') { + return n.config.text || label; + } + $.each(n.interface, function (i) { let ip_addr = n.interface[i].ip; diff --git a/front/src/templates/network.html b/front/src/templates/network.html index c092c468..8a8608d8 100644 --- a/front/src/templates/network.html +++ b/front/src/templates/network.html @@ -120,6 +120,16 @@ +
Инструменты
+
+
+ +
+ Текстбокс +
+
+
@@ -168,6 +178,7 @@
+
{% endblock %} {% block network %} From 0d461c0de33f95928b7e216f57f514cfbbcb074e Mon Sep 17 00:00:00 2001 From: AlexShmak Date: Fri, 9 Jan 2026 23:49:11 +0300 Subject: [PATCH 2/8] feat(textbox): add temporary textbox icon (textbox.svg) --- front/src/static/config_textbox.html | 0 front/src/static/config_textbox.js | 0 front/src/static/icons.js | 4 ++-- front/src/static/images/textbox.svg | 6 ++++++ front/src/static/netfront_f.js | 4 ++++ front/src/templates/network.html | 2 +- 6 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 front/src/static/config_textbox.html create mode 100644 front/src/static/config_textbox.js create mode 100644 front/src/static/images/textbox.svg diff --git a/front/src/static/config_textbox.html b/front/src/static/config_textbox.html new file mode 100644 index 00000000..e69de29b diff --git a/front/src/static/config_textbox.js b/front/src/static/config_textbox.js new file mode 100644 index 00000000..e69de29b diff --git a/front/src/static/icons.js b/front/src/static/icons.js index c8fcdf65..606810a8 100644 --- a/front/src/static/icons.js +++ b/front/src/static/icons.js @@ -16,5 +16,5 @@ const DiagramIcons = { document: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAACxMAAAsTAQCanBgAAAH8SURBVGje7Zm/SyNBFMc/k12JP8ATVLjr/FGJ+AcoCNan2EnigWAtYiVaqIWoYHGNlYVicXCFhWAngojYWenJXWEhCmqws7kcaNSxMI3KZt9kdxLCzTdFwszb2c+878tjhwWn/10qYLya+girZvmHloX6AeODrJMr8vYJtpnnMlpmRtARPo/8oEVKa0Me31igVRLqF5y9Y5yswY2X6ASeUHikUMxxUeweXi3I8MnoqgM0ml120Ghy/Aw3whcsa6rfbJCkD580OiwLdmrgD5McoEkwHFYLdgDgmEn2gQRpFmkvPYDmmCn2AI8hFmkrJUAdjXzhMxlWOAWqSJEKCrZRhKOk878UtfnvwH+TDYAkSXmwrRoQK94MTNPE85sRxXc6Sgdw9GFEMVv4krJb4ADKDiArwmZqjFfOcSt5LpQBrDJg+ISY4Ixu7uMCqDHrbgD5JiwgLbNkGZhh5V2HC5MiKzNNBnBiLwMVYkE/rcYW3LHJU1wAY3w13toZWxKACrHglivjRnQtOx9LLfCMt/bMQ3wAgpZarCqkBpbpFdSAYoJfdgC66BHFNZhnoEIs2Oacx9AoxbUtgDV7GSi7BQ7AATgAB+AA/JDZDv5GWl+FnRELAzRxaHmLIdOKqsgARfJpkBwrhPKCz1VBb80aaZO+9xJIkeEmttWcnOLVCybGe0qF0Rl6AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTAyLTI5VDExOjU5OjE2KzAxOjAwq3IToAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMC0wMi0yOVQxMTo1OToxNiswMTowMNovqxwAAAAASUVORK5CYII=", phone: "data:image/png;base64,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", folder: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAfxJREFUeJzt279rFEEYxvHPxROjxmBppYKNEAsLGwsLBWs70cI2bXrBVlQEEUv/C2ttBK0EbRIQAqKlREGMmESysdjdcyyCcLu37507X3iZ2ebleZ+dGfbHDJlMrxkk/TmcxpExc32uYia5gnXsNYgCj3GgY+2NuYgdzYpP41G38psxwEtcwgbu4vuYeVawVF1fxfM2BHZBffefNMyzlORaN/5a0ilDHKz62w1zreIe7uAM7uO20pBpYqeKEfXcfdhC8kN4q731ZBJR4AXOTcIAOIUPU1Dov+Ibzg5bKjrlI87jFk5MIH9TFrCMRTyg/REwCzxV1rw5F60kiB9VO99XA0b03oB0EVzADcwHaemKLRytLwb+PKgU+jMidpUvbbupAX2kSKfAV1zDZpCYrjiGZziu+h5SPwe8CRTVNe9Udadzvk9TYVRrXxa9fckGRAuIJhsQLSCabEC0gGiyAdECoskGRAuIJhuQ9Hv/MrQaICSKtaotUgO+RCgJYqNq9/IaEC0gmmxAtIBosgHRAqLJBkQLiCYbEC0gmmxAtIBosgHRAqLJBkQLiCYbEC0gmmyAcssY5Vb3vlDvhfw1xGvlkZmb+GS8IzOzxCKuV/1XlIemtsXv3+86tnChduUy3k+BqK5iTTnq/zo4OcBJHPZ/81M51fv0JyyT2Y/f3pw3gUxucUoAAAAASUVORK5CYII=", - textbox: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABRCAYAAAAtvqMwAAAAAXNSR0IArs4c6QAAA2NJREFUeF7tnNFx2zAMhiG3e7TZJN5AG7TeoBf3PfF7nesGTTfQBnUmiTpIqh5lJ5EdKflBE4Sk+32Xp0AA+H8ERckGC+HHVYHCNTqDCwE4TwICIABnBZzDswIIwFkB5/CsAAJwVsA5PCtg0gDKb5/3+S++OI/DI/xfkUUt1Y/dOcHjKqAVPohe3JwTfD7XNjdS3W5ixqMHUH6/FGn+xASb+TW1SLHSVoQOQDvzPzycCFmLNHczF7dneEVYfsPfZeeftcjjUqqfNaqHEsA6zPxOwGKpJY4mNhm71yvCTqrtEs1fC6B5cUzxn7Uor76KFL9etHm8QKsAB3AapNri16LTYcp25ToszYddIT45cRHLq+vOrqe/zFpI8mnKOr6f+7/fvbO77C7P+K4oMYDTe8T7w5mexcDsJoBcKAkgl9IDcQiAAERk4CbMe8B+dvAmbFglXIIMxUVcEwCikqENARiKi7gmAEQlQxsCMBQXcU0AiEqGNgRgKC7imgAQlQxtCMBQXMQ1ASAqGdoQgKG4iGsCQFQytCEAQ3ER1wSAqGRoQwCG4iKuCQBRydCGAAzFRVwTAKKSoQ0BGIqLuCYARCVDGwIwFBdxTQCISoY2BGAoLuKaABCVDG0IwFBcxPW4AYQukW7PFDKiidkMNOKN4ufpE5MyabpHHTLNSqpbqHFR0aBx1J5aS7W9SDqAqTsr153+OZMesVctqndSbVdT1y1J/sftWyKK/jm8AkKmp4EUP8NOMtCxOek9MQD/aXoYjhJAWwV9N9rQmAw3J49Nx8h8+jYbqh5hPYC2CnhOxAAwtfhxAJ6i7/uGwykpM992vlkfh8ovNrEnBuiWoKFc2qr4eGhSjizoNy9rruNBF/CxAarMzzym5ilWGgCqzCOMy3W474QmcO1n9NtlAtAiTWxPAIkF1bojAK1iie0JILGgWncEoFUssX1+APvXGeHZYUzPD2E/vxNp7tG3mKk45AVw9M481RCS+gnn321yQsgNoPPKNqlwKZ1FvVKITSAfgOkcd5n14S0jgN4jL2MnjuV1Wb/nyAcgSNZ/7qilmFrfWZefkFxeAM8QFmEHNLLD/Rb3sW80tZS79vkBnJPtDK8lAGeoBEAAzgo4h2cFEICzAs7hWQEE4KyAc3hWAAE4K+Ac/j9oMVNwgtAfYAAAAABJRU5ErkJggg==", -}; \ No newline at end of file + textbox: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDQ4IDQ4Ij4KICA8cmVjdCB4PSI0IiB5PSI4IiB3aWR0aD0iNDAiIGhlaWdodD0iMzIiIHJ4PSI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDRmNzQiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxsaW5lIHgxPSIxMCIgeTE9IjMyIiB4Mj0iMjgiIHkyPSIzMiIgc3Ryb2tlPSIjMDA0Zjc0IiBzdHJva2Utd2lkdGg9IjIiLz4KICA8bGluZSB4MT0iMTAiIHkxPSIxNiIgeDI9IjM4IiB5Mj0iMTYiIHN0cm9rZT0iIzAwNGY3NCIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPGxpbmUgeDE9IjEwIiB5MT0iMjQiIHgyPSIzOCIgeTI9IjI0IiBzdHJva2U9IiMwMDRmNzQiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K", +}; diff --git a/front/src/static/images/textbox.svg b/front/src/static/images/textbox.svg new file mode 100644 index 00000000..65027f80 --- /dev/null +++ b/front/src/static/images/textbox.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/front/src/static/netfront_f.js b/front/src/static/netfront_f.js index f0794e42..31daeef1 100644 --- a/front/src/static/netfront_f.js +++ b/front/src/static/netfront_f.js @@ -148,6 +148,10 @@ const ActionWithInterface = function (n, i, fun) { } +const ShowTextboxConfig = function(n, shared = 0) { + return +} + const ShowHostConfig = function(n, shared = 0){ // Exit edit mode when switching to different device diff --git a/front/src/templates/network.html b/front/src/templates/network.html index 8a8608d8..58f0f0ec 100644 --- a/front/src/templates/network.html +++ b/front/src/templates/network.html @@ -123,7 +123,7 @@
Инструменты
- +
Текстбокс From c7f93a81f6e2d732a81ff430245d381f49dc6405 Mon Sep 17 00:00:00 2001 From: AlexShmak Date: Wed, 21 Jan 2026 02:22:55 +0300 Subject: [PATCH 3/8] feat(textbox): add editable content field for textbox --- front/src/app.py | 4 ++ front/src/configurators.py | 17 +++++++ front/src/miminet_host.py | 7 +++ front/src/static/config_devices.js | 71 +++++++++++++++++++++++++--- front/src/static/config_textbox.html | 18 +++++++ front/src/static/config_textbox.js | 0 front/src/static/netfront.js | 2 +- front/src/static/netfront_f.js | 52 +++++++++++++++++++- 8 files changed, 163 insertions(+), 8 deletions(-) delete mode 100644 front/src/static/config_textbox.js diff --git a/front/src/app.py b/front/src/app.py index 9a0c156b..baef1c2d 100644 --- a/front/src/app.py +++ b/front/src/app.py @@ -61,6 +61,7 @@ save_edge_config, save_host_config, save_hub_config, + save_textbox_config, save_router_config, save_server_config, save_switch_config, @@ -314,6 +315,9 @@ def get_database_uri(mode): app.add_url_rule( "/host/hub_save_config", methods=["GET", "POST"], view_func=save_hub_config ) +app.add_url_rule( + "/host/textbox_save_config", methods=["GET", "POST"], view_func=save_textbox_config +) app.add_url_rule( "/host/switch_save_config", methods=["GET", "POST"], view_func=save_switch_config ) diff --git a/front/src/configurators.py b/front/src/configurators.py index c157c245..8fb34483 100644 --- a/front/src/configurators.py +++ b/front/src/configurators.py @@ -422,6 +422,23 @@ def _configure(self): return {"message": "Конфигурация обновлена", "nodes": self._nodes} +class TextboxConfigurator(AbstractDeviceConfigurator): + def __init__(self): + super().__init__(device_type="textbox") + + def _conf_content_update(self): + label = get_data(f"config_{self._device_type}_content") + + if label: + self._device_node["config"]["label"] = label + self._device_node["data"]["label"] = self._device_node["config"]["label"] + + def _configure(self): + self._conf_prepare() + self._conf_content_update() + + return {"message": "Конфигурация обновлена", "nodes": self._nodes} + class SwitchConfigurator(AbstractDeviceConfigurator): def __init__(self): diff --git a/front/src/miminet_host.py b/front/src/miminet_host.py index fb2ae734..fc0aabc4 100755 --- a/front/src/miminet_host.py +++ b/front/src/miminet_host.py @@ -8,6 +8,7 @@ EdgeConfigurator, HostConfigurator, HubConfigurator, + TextboxConfigurator, RouterConfigurator, ServerConfigurator, SwitchConfigurator, @@ -186,6 +187,7 @@ def build_error(error_type: str, cmd: str) -> str: router = RouterConfigurator() server = ServerConfigurator() edge = EdgeConfigurator() +textbox = TextboxConfigurator() # --- Jobs --- @@ -517,6 +519,11 @@ def save_router_config(): return router.configure() +@jwt_required +def save_textbox_config(): + return textbox.configure() + + @jwt_required() def save_server_config(): return server.configure() diff --git a/front/src/static/config_devices.js b/front/src/static/config_devices.js index 52954b62..101f9605 100644 --- a/front/src/static/config_devices.js +++ b/front/src/static/config_devices.js @@ -6,6 +6,7 @@ $('#config_router').load(ExternalUrlFor("/config_router.html")); $('#config_server').load(ExternalUrlFor("/config_server.html")); $('#config_vlan').load(ExternalUrlFor("/config_vlan.html")); $('#config_vxlan').load(ExternalUrlFor("/config_vxlan.html")); +$("#config_textbox").load(ExternalUrlFor("/config_textbox.html")) const config_content_id = "#config_content"; const config_main_form_id = "#config_main_form"; @@ -103,10 +104,61 @@ const UpdateHostConfigurationForm = function(host_id) { DeleteAndSaveJob('host', UpdateHostConfiguration, data, host_id); }; -const ConfigHostForm = function(host_id){ - var form = document.getElementById('config_host_main_form_script').innerHTML; - var button = document.getElementById('config_host_save_script').innerHTML; - var banner = document.getElementById('config_host_edit_banner_script').innerHTML; +const UpdateTextboxConfigurationForm = function (textbox_id) { + let data = $("#config_textbox_main_form").serialize(); + + // Disable all input fields + $("#config_textbox_main_form :input").prop("disabled", true); + + // Set loading spinner + $("#config_textbox_main_form_submit_button").text(""); + $("#config_textbox_main_form_submit_button").append( + 'Сохранение...', + ); + + UpdateTextboxConfiguration(data, textbox_id); +}; + +const ConfigTextboxForm = function (textbox_id) { + + var form = document.getElementById( + "config_textbox_main_form_script", + ).innerHTML; + var button = document.getElementById( + "config_textbox_save_script", + ).innerHTML; + + $(config_content_id).empty(); + $(config_content_save_tag).empty(); + + document.getElementById(config_content_save_id).style.display = "block"; + + $(config_content_id).append(form); + $(config_content_save_tag).append(button); + + $("#textbox_id").val(textbox_id); + $("#net_guid").val(network_guid); + + function handleTextboxClick(event) { + event.preventDefault(); + let data = $("#config_textbox_main_form").serialize(); + UpdateTextboxConfiguration(data, textbox_id); + } + + $("#config_textbox_main_form_submit_button, #config_textbox_end_form").on( + "click", + handleTextboxClick, + ); +}; + +const ConfigHostForm = function (host_id) { + var form = document.getElementById( + "config_host_main_form_script", + ).innerHTML; + var button = document.getElementById("config_host_save_script").innerHTML; + var banner = document.getElementById( + "config_host_edit_banner_script", + ).innerHTML; // Clear all child $(config_content_id).empty(); @@ -377,8 +429,15 @@ const ConfigHubName = function (hostname) { var text = document.getElementById('config_hub_name_script').innerHTML; - $(config_hub_main_form_id).prepend((text)); - $('#config_hub_name').val(hostname); + $(config_hub_main_form_id).prepend(text); + $("#config_hub_name").val(hostname); +}; + +const ConfigTextboxContent = function(textbox_name) { + var text = document.getElementById("config_textbox_content_script").innerHTML; + + $("#config_textbox_main_form").prepend(text); + $("#config_textbox_content").val(textbox_name) } const ConfigEdgeNetworkIssues = function (edge_loss, edge_duplicate) { diff --git a/front/src/static/config_textbox.html b/front/src/static/config_textbox.html index e69de29b..32d40c9f 100644 --- a/front/src/static/config_textbox.html +++ b/front/src/static/config_textbox.html @@ -0,0 +1,18 @@ + + + + + diff --git a/front/src/static/config_textbox.js b/front/src/static/config_textbox.js deleted file mode 100644 index e69de29b..00000000 diff --git a/front/src/static/netfront.js b/front/src/static/netfront.js index e7f64de0..6cd593bf 100644 --- a/front/src/static/netfront.js +++ b/front/src/static/netfront.js @@ -108,7 +108,7 @@ $('#network_scheme').droppable({ config: { type: 'textbox', label: node_id, - text: 'Введите текст здесь' + text: "Textbox" }, interface: [], }); diff --git a/front/src/static/netfront_f.js b/front/src/static/netfront_f.js index 31daeef1..8d74178f 100644 --- a/front/src/static/netfront_f.js +++ b/front/src/static/netfront_f.js @@ -149,7 +149,17 @@ const ActionWithInterface = function (n, i, fun) { } const ShowTextboxConfig = function(n, shared = 0) { - return + + let textbox_name = n.config.label; + textbox_name = textbox_name || n.data.id + + if (shared) { + SharedConfigTextboxForm(n.data.id); + } else { + ConfigTextboxForm(n.data.id) + } + + ConfigTextboxContent(textbox_name); } const ShowHostConfig = function(n, shared = 0){ @@ -1254,6 +1264,8 @@ const DrawGraph = function() { ShowRouterConfig(n); } else if (n.config.type === 'server'){ ShowServerConfig(n); + } else if (n.config.type === 'textbox'){ + ShowTextboxConfig(n); } }); @@ -2095,6 +2107,44 @@ const UpdateHubConfiguration = function (data, hub_id) }); } +const UpdateTextboxConfiguration = function (data, textbox_id) { + SetNetworkPlayerState(-1); + + $.ajax({ + type: "POST", + url: "/host/textbox_save_config", + data: data, + success: function (data, textStatus, xhr) { + if (xhr.status === 200) { + + nodes = data.nodes; + DrawGraph(); + + let n = nodes.find((n) => n.data.id === textbox_id); + + if (!n) { + ClearConfigForm("Нет такого текстового блока"); + return; + } + + if (n.config.type === "textbox") { + ShowTextboxConfig(n); + } else { + ClearConfigForm("Нет такого текстового блока"); + return; + } + + } + }, + error: function(xhr) { + console.log("Cannot update textbox config"); + console.log(xhr); + }, + dataType: 'json' + + }); +}; + // Update Switch configuration const UpdateSwitchConfiguration = function (data, switch_id) { From 9d72897b16fadbbecd182f45cb024ae241ec6dfd Mon Sep 17 00:00:00 2001 From: AlexShmak Date: Wed, 28 Jan 2026 22:58:00 +0300 Subject: [PATCH 4/8] feat(textbox): implement resizable textbox with configurable text content --- front/src/static/icons.js | 3 +- front/src/static/netfront.js | 21 ++- front/src/static/netfront_f.js | 287 ++++++++++++++++++++++++++----- front/src/templates/network.html | 3 +- 4 files changed, 263 insertions(+), 51 deletions(-) diff --git a/front/src/static/icons.js b/front/src/static/icons.js index 606810a8..4bd3f517 100644 --- a/front/src/static/icons.js +++ b/front/src/static/icons.js @@ -16,5 +16,6 @@ const DiagramIcons = { document: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAACxMAAAsTAQCanBgAAAH8SURBVGje7Zm/SyNBFMc/k12JP8ATVLjr/FGJ+AcoCNan2EnigWAtYiVaqIWoYHGNlYVicXCFhWAngojYWenJXWEhCmqws7kcaNSxMI3KZt9kdxLCzTdFwszb2c+878tjhwWn/10qYLya+girZvmHloX6AeODrJMr8vYJtpnnMlpmRtARPo/8oEVKa0Me31igVRLqF5y9Y5yswY2X6ASeUHikUMxxUeweXi3I8MnoqgM0ml120Ghy/Aw3whcsa6rfbJCkD580OiwLdmrgD5McoEkwHFYLdgDgmEn2gQRpFmkvPYDmmCn2AI8hFmkrJUAdjXzhMxlWOAWqSJEKCrZRhKOk878UtfnvwH+TDYAkSXmwrRoQK94MTNPE85sRxXc6Sgdw9GFEMVv4krJb4ADKDiArwmZqjFfOcSt5LpQBrDJg+ISY4Ixu7uMCqDHrbgD5JiwgLbNkGZhh5V2HC5MiKzNNBnBiLwMVYkE/rcYW3LHJU1wAY3w13toZWxKACrHglivjRnQtOx9LLfCMt/bMQ3wAgpZarCqkBpbpFdSAYoJfdgC66BHFNZhnoEIs2Oacx9AoxbUtgDV7GSi7BQ7AATgAB+AA/JDZDv5GWl+FnRELAzRxaHmLIdOKqsgARfJpkBwrhPKCz1VBb80aaZO+9xJIkeEmttWcnOLVCybGe0qF0Rl6AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTAyLTI5VDExOjU5OjE2KzAxOjAwq3IToAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMC0wMi0yOVQxMTo1OToxNiswMTowMNovqxwAAAAASUVORK5CYII=", phone: "data:image/png;base64,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", folder: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAfxJREFUeJzt279rFEEYxvHPxROjxmBppYKNEAsLGwsLBWs70cI2bXrBVlQEEUv/C2ttBK0EbRIQAqKlREGMmESysdjdcyyCcLu37507X3iZ2ebleZ+dGfbHDJlMrxkk/TmcxpExc32uYia5gnXsNYgCj3GgY+2NuYgdzYpP41G38psxwEtcwgbu4vuYeVawVF1fxfM2BHZBffefNMyzlORaN/5a0ilDHKz62w1zreIe7uAM7uO20pBpYqeKEfXcfdhC8kN4q731ZBJR4AXOTcIAOIUPU1Dov+Ibzg5bKjrlI87jFk5MIH9TFrCMRTyg/REwCzxV1rw5F60kiB9VO99XA0b03oB0EVzADcwHaemKLRytLwb+PKgU+jMidpUvbbupAX2kSKfAV1zDZpCYrjiGZziu+h5SPwe8CRTVNe9Udadzvk9TYVRrXxa9fckGRAuIJhsQLSCabEC0gGiyAdECoskGRAuIJhuQ9Hv/MrQaICSKtaotUgO+RCgJYqNq9/IaEC0gmmxAtIBosgHRAqLJBkQLiCYbEC0gmmxAtIBosgHRAqLJBkQLiCYbEC0gmmyAcssY5Vb3vlDvhfw1xGvlkZmb+GS8IzOzxCKuV/1XlIemtsXv3+86tnChduUy3k+BqK5iTTnq/zo4OcBJHPZ/81M51fv0JyyT2Y/f3pw3gUxucUoAAAAASUVORK5CYII=", - textbox: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDQ4IDQ4Ij4KICA8cmVjdCB4PSI0IiB5PSI4IiB3aWR0aD0iNDAiIGhlaWdodD0iMzIiIHJ4PSI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDRmNzQiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxsaW5lIHgxPSIxMCIgeTE9IjMyIiB4Mj0iMjgiIHkyPSIzMiIgc3Ryb2tlPSIjMDA0Zjc0IiBzdHJva2Utd2lkdGg9IjIiLz4KICA8bGluZSB4MT0iMTAiIHkxPSIxNiIgeDI9IjM4IiB5Mj0iMTYiIHN0cm9rZT0iIzAwNGY3NCIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPGxpbmUgeDE9IjEwIiB5MT0iMjQiIHgyPSIzOCIgeTI9IjI0IiBzdHJva2U9IiMwMDRmNzQiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K", + // textbox: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDQ4IDQ4Ij4KICA8cmVjdCB4PSI0IiB5PSI4IiB3aWR0aD0iNDAiIGhlaWdodD0iMzIiIHJ4PSI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDRmNzQiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxsaW5lIHgxPSIxMCIgeTE9IjMyIiB4Mj0iMjgiIHkyPSIzMiIgc3Ryb2tlPSIjMDA0Zjc0IiBzdHJva2Utd2lkdGg9IjIiLz4KICA8bGluZSB4MT0iMTAiIHkxPSIxNiIgeDI9IjM4IiB5Mj0iMTYiIHN0cm9rZT0iIzAwNGY3NCIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPGxpbmUgeDE9IjEwIiB5MT0iMjQiIHgyPSIzOCIgeTI9IjI0IiBzdHJva2U9IiMwMDRmNzQiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K", + textbox: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxIDEiPjwvc3ZnPg==" }; diff --git a/front/src/static/netfront.js b/front/src/static/netfront.js index 6cd593bf..7e7293c2 100644 --- a/front/src/static/netfront.js +++ b/front/src/static/netfront.js @@ -102,16 +102,25 @@ $('#network_scheme').droppable({ let node_id = TextboxUid(); nodes.push( { - data: {id: node_id, label: node_id}, - position: {x: CalculateDropOffset(ui.position.left, ui.position.top).x, y: CalculateDropOffset(ui.position.left, ui.position.top).y}, - classes: ['textbox'], + data: { + id: node_id, + label: "Новое текстовое поле", + connectable: false, + type: 'textbox', + width: 150, + height: 80 + }, + position: { + x: CalculateDropOffset(ui.position.left, ui.position.top).x, + y: CalculateDropOffset(ui.position.left, ui.position.top).y, + }, + classes: ["textbox"], config: { type: 'textbox', - label: node_id, - text: "Textbox" + label: "Новое текстовое поле", }, interface: [], - }); + }); } else { diff --git a/front/src/static/netfront_f.js b/front/src/static/netfront_f.js index 8d74178f..a30a8b92 100644 --- a/front/src/static/netfront_f.js +++ b/front/src/static/netfront_f.js @@ -532,6 +532,10 @@ const AddEdge = function(source_id, target_id){ return; } + if (source_node.config.type === 'textbox' || target_node.config.type === 'textbox') { + return; + } + // Save the network state. SaveNetworkObject(); @@ -550,9 +554,9 @@ const AddEdge = function(source_id, target_id){ if (source_node.config.type === 'host' || source_node.config.type === 'router' || source_node.config.type === 'server'){ let iface_id = InterfaceUid(); source_node.interface.push({ - id: iface_id, - name: iface_id, - connect: edge_id, + id: iface_id, + name: iface_id, + connect: edge_id, }); } @@ -954,30 +958,40 @@ const prepareStylesheet = function() { 'source-arrow-color': 'blue' }) - .selector('.eh-ghost-edge') + .selector('node[type="packet"]') + .css({ + content: "data(label)", + "text-valign": "top", + "text-align": "center", + height: "5px", + width: "5px", + "border-opacity": "0", + "border-width": "0px", + "text-wrap": "wrap", + }) + + .selector(".hidden") + .css({ + display: "none", + }) + + .selector(".eh-preview") .css({ - 'background-color': 'blue', - 'line-color': 'blue', - 'target-arrow-color': 'blue', - 'source-arrow-color': 'blue' + "background-color": "blue", + "line-color": "blue", + "target-arrow-color": "blue", + "source-arrow-color": "blue", + "opacity": 0.5 }) - .selector('node[name]') - .css({ - 'content': 'data(name)' - }) - .selector('node[type="packet"]') - .css({ - 'content': 'data(label)', - 'text-valign': 'top', - 'text-align': 'center', - 'height': '5px', - 'width': '5px', - 'border-opacity': '0', - 'border-width': '0px', - 'text-wrap': 'wrap' - }) + .selector(".eh-ghost-edge") + .css({ + "background-color": "blue", + "line-color": "blue", + "target-arrow-color": "blue", + "source-arrow-color": "blue", + }) .selector('.hidden') .css({ @@ -1018,8 +1032,27 @@ const prepareStylesheet = function() { } } + sheet.selector('node[type="textbox"]') + .css({ + 'shape': 'rectangle', + 'background-opacity': 0, + 'border-width': 0, + 'content': 'data(label)', + 'width': function(ele) { return ele.data('width') || 100; }, + 'height': function(ele) { return ele.data('height') || 50; }, + 'text-valign': 'center', + 'text-halign': 'center', + 'text-wrap': 'wrap', + 'text-max-width': function(ele) { return ele.data('width') || 100; }, + 'color': function(ele) { return ele.data('color') || '#000000'; }, + 'font-size': function(ele) { return ele.data('fontsize') || 12; }, + 'font-weight': function(ele) { return ele.data('fontweight') || 'normal'; }, + 'font-style': function(ele) { return ele.data('fontstyle') || 'normal'; }, + 'text-decoration': function(ele) { return ele.data('textdecoration') || 'none'; } + }); + return sheet; - }; +}; const SnapNodesToGrid = function(cy_instance) { if (!cy_instance) return; @@ -1118,30 +1151,173 @@ const DrawGraph = function() { global_cy = cy; - // the default values of each option are outlined below: - let defaults = { - canConnect: function( sourceNode, targetNode ){ + if ($('#resize-style').length === 0) { + $('head').append(` + + `); + } - // whether an edge can be created between source and target - return !sourceNode.same(targetNode); // e.g. disallow loops - }, + let activeNodeId = null; - edgeParams: function( sourceNode, targetNode ){ + const hideResizeFrame = () => { + $('#resize-frame').remove(); + activeNodeId = null; + }; - // for edges between the specified source and target - // return element object to be passed to cy.add() for edge - return {}; - }, + $(document).off('mousedown.resizeHide').on('mousedown.resizeHide', function(e) { + if ($(e.target).closest('#network_scheme').length > 0) { + return; + } + + if ($(e.target).closest('.resize-frame').length > 0 || $(e.target).hasClass('resize-handle')) { + return; + } + + hideResizeFrame(); + }); - hoverDelay: 150, // time spent hovering over a target node before it is considered selected - snap: false, // when enabled, the edge can be drawn by just moving close to a target node (can be confusing on compound graphs) - snapThreshold: 50, // the target node must be less than or equal to this many pixels away from the cursor/finger - snapFrequency: 15, // the number of times per second (Hz) that snap checks done (lower is less expensive) - noEdgeEventsInDraw: true, // set events:no to edges during draws, prevents mouseouts on compounds - disableBrowserGestures: true // during an edge drawing gesture, disable browser gestures such as two-finger trackpad swipe and pinch-to-zoom + const updateResizeFrame = () => { + if (!activeNodeId) return; + const node = cy.getElementById(activeNodeId); + if (node.empty() || node.removed()) { hideResizeFrame(); return; } + + const bb = node.renderedBoundingBox({ includeLabels: false }); + const containerOffset = $(cy.container()).offset(); + + let frame = $('#resize-frame'); + if (frame.length === 0) return; + + frame.css({ + top: containerOffset.top + bb.y1, + left: containerOffset.left + bb.x1, + width: bb.w, + height: bb.h + }); }; - global_eh = cy.edgehandles(defaults); + const initResizeFrame = (node) => { + hideResizeFrame(); + activeNodeId = node.id(); + + const frame = $('
'); + $('body').append(frame); + + const handles = [ + { d: 'nw', x: 0, y: 0, c: 'nw-resize' }, { d: 'n', x: 50, y: 0, c: 'n-resize' }, + { d: 'ne', x: 100, y: 0, c: 'ne-resize' }, { d: 'e', x: 100, y: 50, c: 'e-resize' }, + { d: 'se', x: 100, y: 100, c: 'se-resize' }, { d: 's', x: 50, y: 100, c: 's-resize' }, + { d: 'sw', x: 0, y: 100, c: 'sw-resize' }, { d: 'w', x: 0, y: 50, c: 'w-resize' } + ]; + + handles.forEach(h => { + const handle = $('
'); + handle.css({ left: h.x + '%', top: h.y + '%', cursor: h.c }); + + handle.on('mousedown', function(e) { + e.stopPropagation(); + e.preventDefault(); + + const startX = e.pageX; + const startY = e.pageY; + const startW = node.data('width') || 100; + const startH = node.data('height') || 50; + + const startPos = { x: node.position().x, y: node.position().y }; + + const zoom = cy.zoom(); + + node.ungrabify(); + + $(document).on('mousemove.resizing', function(ev) { + const dx = (ev.pageX - startX) / zoom; + const dy = (ev.pageY - startY) / zoom; + + let newW = startW; + let newH = startH; + let newX = startPos.x; + let newY = startPos.y; + + // Horizontal Resize + if (h.d.includes('e')) { + newW = Math.max(30, startW + dx); + newX += (newW - startW) / 2; // Shift center right + } + if (h.d.includes('w')) { + newW = Math.max(30, startW - dx); + newX -= (newW - startW) / 2; // Shift center left + } + + // Vertical Resize + if (h.d.includes('s')) { + newH = Math.max(30, startH + dy); + newY += (newH - startH) / 2; // Shift center down + } + if (h.d.includes('n')) { + newH = Math.max(30, startH - dy); + newY -= (newH - startH) / 2; // Shift center up + } + + // Apply changes + node.data('width', newW); + node.data('height', newH); + node.position({ x: newX, y: newY }); + + updateResizeFrame(); + }); + + $(document).on('mouseup.resizing', function() { + $(document).off('.resizing'); + node.grabify(); + TakeGraphPictureAndUpdate(); + MoveNodes(); + }); + }); + + frame.append(handle); + }); + + updateResizeFrame(); + }; + + cy.on('tap', 'node[type="textbox"]', (e) => initResizeFrame(e.target)); + cy.on('tap', (e) => { if (e.target === cy) hideResizeFrame(); }); + cy.on('zoom pan position', updateResizeFrame); + + let allowEdges = function(src, tgt) { + const sNode = nodes.find(n => n.data.id === src.id()); + const tNode = nodes.find(n => n.data.id === tgt.id()); + + if (sNode && sNode.config && sNode.config.type === 'textbox') return false; + if (tNode && tNode.config && tNode.config.type === 'textbox') return false; + if (src.data('type') === 'textbox' || tgt.data('type') === 'textbox') return false; + + return !src.same(tgt); + } + + let customDefaults = { + handleNodes: 'node[type!="textbox"]', + + canConnect: (src, tgt) => allowEdges(src, tgt), + + edgeParams: (src, tgt) => allowEdges(src, tgt) ? {} : null, + + hoverDelay: 150, + snap: false, + snapThreshold: 50, + snapFrequency: 15, + noEdgeEventsInDraw: true, + disableBrowserGestures: true, + }; + + global_eh = cy.edgehandles(customDefaults); cy.minZoom(0.5); cy.maxZoom(2); @@ -1218,7 +1394,8 @@ const DrawGraph = function() { n.position.x = posX; n.position.y = posY; - MoveNodes(); + MoveNodes(); // --- RESIZE UI LOGIC END --- + TakeGraphPictureAndUpdate(); }); @@ -1272,6 +1449,7 @@ const DrawGraph = function() { // Add edge to the edges[] and then save it to the server. cy.on('ehcomplete', (event, sourceNode, targetNode, addedEdge) => { AddEdge(sourceNode._private.data.id, targetNode._private.data.id); + DrawGraph(); PostNodesEdges(); TakeGraphPictureAndUpdate(); @@ -1286,6 +1464,9 @@ const DrawGraph = function() { } if (e.keyCode == 46 && selecteed_node_id) { + if (activeNodeId === selecteed_node_id) { + hideResizeFrame(); + } // Save the network state. SaveNetworkObject(); @@ -1359,6 +1540,26 @@ const DrawGraph = function() { }); + + cy.on('tap', 'node[type="textbox"]', (e) => { + e.originalEvent.stopPropagation(); + initResizeFrame(e.target); + }); + + cy.on('tap', (e) => { + if (e.target === cy || (e.target.isNode && e.target.isNode() && e.target.data('type') !== 'textbox')) { + hideResizeFrame(); + } + }); + + cy.on('zoom pan', updateResizeFrame); + + cy.on('dragstart', (e) => { + if (e.target.id() !== activeNodeId) { + hideResizeFrame(); + } + }); + // Initialize grid initGrid(cy); } diff --git a/front/src/templates/network.html b/front/src/templates/network.html index 58f0f0ec..6792a79d 100644 --- a/front/src/templates/network.html +++ b/front/src/templates/network.html @@ -122,7 +122,8 @@
Инструменты
-
+ +
Date: Sat, 7 Feb 2026 21:10:45 +0300 Subject: [PATCH 5/8] feat(textbox): implement font style, weight, color and size configuration in textbox config form --- back/src/network_schema.py | 10 ++- back/src/network_topology.py | 4 + front/src/configurators.py | 16 ++++ front/src/static/config_devices.js | 113 +++++++++++++++++++++++- front/src/static/config_textbox.html | 47 +++++++++- front/src/static/icons.js | 1 - front/src/static/netfront.js | 16 ++-- front/src/static/netfront_f.js | 126 +++++++++++++++------------ 8 files changed, 262 insertions(+), 71 deletions(-) diff --git a/back/src/network_schema.py b/back/src/network_schema.py index e5620822..b55056df 100644 --- a/back/src/network_schema.py +++ b/back/src/network_schema.py @@ -37,6 +37,14 @@ class NodeConfig: priority: Optional[int] = None default_gw: str = "" +@dataclass +class TextboxNodeConfig(NodeConfig): + width: int = 0 + height: int = 0 + tb_fontsize: int = 0 + color: str = "" + fontweight: str = "" + fontstyle: str = "" @dataclass class NodeInterface: @@ -93,7 +101,7 @@ class Node: classes (list[str]): Node classes (e.g., ["l2_switch"]). """ - config: NodeConfig + config: NodeConfig | TextboxNodeConfig data: NodeData interface: list[NodeInterface] classes: list[str] diff --git a/back/src/network_topology.py b/back/src/network_topology.py index a0b71caf..219eba11 100644 --- a/back/src/network_topology.py +++ b/back/src/network_topology.py @@ -47,6 +47,8 @@ def __handle_node(self, node: Node): node_type: str = config.type # network device type node_id: str = node.data.id # network device name(label) + if node_type == "textbox": + return if node_type == "l2_switch": self.__handle_l2_switch(node_id, config) elif node_type in ("host", "server"): @@ -127,6 +129,8 @@ def build(self, *args, **kwargs): interfaces = [] for node in self.__network.nodes: + if node.config.type == "textbox": + continue # Caches node by ID for quick lookup later self.__id_to_node[node.data.id] = node diff --git a/front/src/configurators.py b/front/src/configurators.py index 8fb34483..05a13cb4 100644 --- a/front/src/configurators.py +++ b/front/src/configurators.py @@ -428,11 +428,27 @@ def __init__(self): def _conf_content_update(self): label = get_data(f"config_{self._device_type}_content") + fontsize = get_data("config_textbox_font_size") + font_color = get_data("config_textbox_font_color") + font_style = get_data("config_textbox_font_style") + font_weight = get_data("config_textbox_font_weight") if label: self._device_node["config"]["label"] = label self._device_node["data"]["label"] = self._device_node["config"]["label"] + if fontsize: + self._device_node["config"]["tb_fontsize"] = int(fontsize) + + if font_color: + self._device_node["config"]["color"] = font_color + + if font_style: + self._device_node["config"]["fontstyle"] = font_style + + if font_weight: + self._device_node["config"]["fontweight"] = font_weight + def _configure(self): self._conf_prepare() self._conf_content_update() diff --git a/front/src/static/config_devices.js b/front/src/static/config_devices.js index 101f9605..25a3d274 100644 --- a/front/src/static/config_devices.js +++ b/front/src/static/config_devices.js @@ -139,10 +139,9 @@ const ConfigTextboxForm = function (textbox_id) { $("#textbox_id").val(textbox_id); $("#net_guid").val(network_guid); - function handleTextboxClick(event) { + function handleTextboxClick(event) { event.preventDefault(); - let data = $("#config_textbox_main_form").serialize(); - UpdateTextboxConfiguration(data, textbox_id); + UpdateTextboxConfigurationForm(textbox_id); } $("#config_textbox_main_form_submit_button, #config_textbox_end_form").on( @@ -440,6 +439,114 @@ const ConfigTextboxContent = function(textbox_name) { $("#config_textbox_content").val(textbox_name) } +const ConfigTextboxFontColor = function(textbox_font_color) { + var colorScript = document.getElementById("config_textbox_font_color_script").innerHTML; + $("#config_textbox_main_form").prepend(colorScript); + + var input = $("#config_textbox_font_color"); + + var currentColor = textbox_font_color || "#000000"; + input.val(currentColor); + + const highlightColor = (selectedColor) => { + if (!selectedColor) return; + selectedColor = selectedColor.toLowerCase(); + + var $form = $("#config_textbox_main_form"); + var $presets = $form.find(".color-preset"); + var $customWrapper = $form.find(".custom-color-wrapper"); + + $presets.add($customWrapper).css({ + "outline": "none" + }); + + let foundPreset = false; + $presets.each(function() { + var $el = $(this); + var presetColor = $el.data("color"); + if (presetColor && presetColor.toLowerCase() === selectedColor) { + $el.css({ + "outline": "2px solid #0d6efd", + "outline-offset": "2px" + }); + foundPreset = true; + return false; + } + }); + + if (!foundPreset) { + $customWrapper.css({ + "outline": "2px solid #0d6efd", + "outline-offset": "2px" + }); + } + }; + + highlightColor(currentColor); + + $("#config_textbox_main_form").on("click", ".color-preset", function() { + var selectedColor = $(this).data("color"); + input.val(selectedColor); + highlightColor(selectedColor); + }); + + input.on("input change", function() { + highlightColor($(this).val()); + }); +} + +const ConfigTextboxFontControls = function(size, style, weight) { + if ($("#config_textbox_font_size").length === 0) { + var controls = document.getElementById("config_textbox_font_controls_script").innerHTML; + $("#config_textbox_main_form").prepend(controls); + } + + $("#config_textbox_font_size").val(size); + + var inputStyle = $("#config_textbox_font_style"); + var btnStyle = $("#btn_toggle_style"); + var inputWeight = $("#config_textbox_font_weight"); + var btnWeight = $("#btn_toggle_weight"); + + inputStyle.val(style || 'normal'); + if (style === 'italic') { + btnStyle.removeClass('btn-outline-secondary').addClass('btn-primary active'); + } else { + btnStyle.removeClass('btn-primary active').addClass('btn-outline-secondary'); + } + + inputWeight.val(weight || 'normal'); + if (weight === 'bold') { + btnWeight.removeClass('btn-outline-secondary').addClass('btn-primary active'); + } else { + btnWeight.removeClass('btn-primary active').addClass('btn-outline-secondary'); + } + + $("#config_textbox_main_form").off('click', '#btn_toggle_style').on('click', '#btn_toggle_style', function() { + var btn = $(this); + var input = $("#config_textbox_font_style"); + if (input.val() === 'italic') { + input.val('normal'); + btn.removeClass('btn-primary active').addClass('btn-outline-secondary'); + } else { + input.val('italic'); + btn.removeClass('btn-outline-secondary').addClass('btn-primary active'); + } + }); + + $("#config_textbox_main_form").off('click', '#btn_toggle_weight').on('click', '#btn_toggle_weight', function() { + var btn = $(this); + var input = $("#config_textbox_font_weight"); + if (input.val() === 'bold') { + input.val('normal'); + btn.removeClass('btn-primary active').addClass('btn-outline-secondary'); + } else { + input.val('bold'); + btn.removeClass('btn-outline-secondary').addClass('btn-primary active'); + } + }); +} + const ConfigEdgeNetworkIssues = function (edge_loss, edge_duplicate) { var text = document.getElementById('config_edge_set_network_issues_script').innerHTML; $(config_edge_main_form_id).prepend(text); diff --git a/front/src/static/config_textbox.html b/front/src/static/config_textbox.html index 32d40c9f..c606a905 100644 --- a/front/src/static/config_textbox.html +++ b/front/src/static/config_textbox.html @@ -12,7 +12,50 @@ + + + + \ No newline at end of file diff --git a/front/src/static/icons.js b/front/src/static/icons.js index 4bd3f517..cca063f9 100644 --- a/front/src/static/icons.js +++ b/front/src/static/icons.js @@ -16,6 +16,5 @@ const DiagramIcons = { document: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAACxMAAAsTAQCanBgAAAH8SURBVGje7Zm/SyNBFMc/k12JP8ATVLjr/FGJ+AcoCNan2EnigWAtYiVaqIWoYHGNlYVicXCFhWAngojYWenJXWEhCmqws7kcaNSxMI3KZt9kdxLCzTdFwszb2c+878tjhwWn/10qYLya+girZvmHloX6AeODrJMr8vYJtpnnMlpmRtARPo/8oEVKa0Me31igVRLqF5y9Y5yswY2X6ASeUHikUMxxUeweXi3I8MnoqgM0ml120Ghy/Aw3whcsa6rfbJCkD580OiwLdmrgD5McoEkwHFYLdgDgmEn2gQRpFmkvPYDmmCn2AI8hFmkrJUAdjXzhMxlWOAWqSJEKCrZRhKOk878UtfnvwH+TDYAkSXmwrRoQK94MTNPE85sRxXc6Sgdw9GFEMVv4krJb4ADKDiArwmZqjFfOcSt5LpQBrDJg+ISY4Ixu7uMCqDHrbgD5JiwgLbNkGZhh5V2HC5MiKzNNBnBiLwMVYkE/rcYW3LHJU1wAY3w13toZWxKACrHglivjRnQtOx9LLfCMt/bMQ3wAgpZarCqkBpbpFdSAYoJfdgC66BHFNZhnoEIs2Oacx9AoxbUtgDV7GSi7BQ7AATgAB+AA/JDZDv5GWl+FnRELAzRxaHmLIdOKqsgARfJpkBwrhPKCz1VBb80aaZO+9xJIkeEmttWcnOLVCybGe0qF0Rl6AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTAyLTI5VDExOjU5OjE2KzAxOjAwq3IToAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMC0wMi0yOVQxMTo1OToxNiswMTowMNovqxwAAAAASUVORK5CYII=", phone: "data:image/png;base64,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", folder: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAfxJREFUeJzt279rFEEYxvHPxROjxmBppYKNEAsLGwsLBWs70cI2bXrBVlQEEUv/C2ttBK0EbRIQAqKlREGMmESysdjdcyyCcLu37507X3iZ2ebleZ+dGfbHDJlMrxkk/TmcxpExc32uYia5gnXsNYgCj3GgY+2NuYgdzYpP41G38psxwEtcwgbu4vuYeVawVF1fxfM2BHZBffefNMyzlORaN/5a0ilDHKz62w1zreIe7uAM7uO20pBpYqeKEfXcfdhC8kN4q731ZBJR4AXOTcIAOIUPU1Dov+Ibzg5bKjrlI87jFk5MIH9TFrCMRTyg/REwCzxV1rw5F60kiB9VO99XA0b03oB0EVzADcwHaemKLRytLwb+PKgU+jMidpUvbbupAX2kSKfAV1zDZpCYrjiGZziu+h5SPwe8CRTVNe9Udadzvk9TYVRrXxa9fckGRAuIJhsQLSCabEC0gGiyAdECoskGRAuIJhuQ9Hv/MrQaICSKtaotUgO+RCgJYqNq9/IaEC0gmmxAtIBosgHRAqLJBkQLiCYbEC0gmmxAtIBosgHRAqLJBkQLiCYbEC0gmmyAcssY5Vb3vlDvhfw1xGvlkZmb+GS8IzOzxCKuV/1XlIemtsXv3+86tnChduUy3k+BqK5iTTnq/zo4OcBJHPZ/81M51fv0JyyT2Y/f3pw3gUxucUoAAAAASUVORK5CYII=", - // textbox: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDQ4IDQ4Ij4KICA8cmVjdCB4PSI0IiB5PSI4IiB3aWR0aD0iNDAiIGhlaWdodD0iMzIiIHJ4PSI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDRmNzQiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxsaW5lIHgxPSIxMCIgeTE9IjMyIiB4Mj0iMjgiIHkyPSIzMiIgc3Ryb2tlPSIjMDA0Zjc0IiBzdHJva2Utd2lkdGg9IjIiLz4KICA8bGluZSB4MT0iMTAiIHkxPSIxNiIgeDI9IjM4IiB5Mj0iMTYiIHN0cm9rZT0iIzAwNGY3NCIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPGxpbmUgeDE9IjEwIiB5MT0iMjQiIHgyPSIzOCIgeTI9IjI0IiBzdHJva2U9IiMwMDRmNzQiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K", textbox: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxIDEiPjwvc3ZnPg==" }; diff --git a/front/src/static/netfront.js b/front/src/static/netfront.js index 7e7293c2..0ab6409a 100644 --- a/front/src/static/netfront.js +++ b/front/src/static/netfront.js @@ -102,13 +102,9 @@ $('#network_scheme').droppable({ let node_id = TextboxUid(); nodes.push( { - data: { - id: node_id, - label: "Новое текстовое поле", - connectable: false, - type: 'textbox', - width: 150, - height: 80 + data: { + id: node_id, + label: "Новое текстовое поле", }, position: { x: CalculateDropOffset(ui.position.left, ui.position.top).x, @@ -118,6 +114,12 @@ $('#network_scheme').droppable({ config: { type: 'textbox', label: "Новое текстовое поле", + width: 150, + height: 80, + tb_fontsize: 12, + color: 'black', + fontweight: 'normal', + fontstyle: 'normal', }, interface: [], }); diff --git a/front/src/static/netfront_f.js b/front/src/static/netfront_f.js index a30a8b92..5a3543a4 100644 --- a/front/src/static/netfront_f.js +++ b/front/src/static/netfront_f.js @@ -151,6 +151,10 @@ const ActionWithInterface = function (n, i, fun) { const ShowTextboxConfig = function(n, shared = 0) { let textbox_name = n.config.label; + let textbox_fontsize = n.config.tb_fontsize; + let textbox_font_color = n.config.color + let textbox_font_style = n.config.fontstyle + let textbox_font_weight = n.config.fontweight textbox_name = textbox_name || n.data.id if (shared) { @@ -158,7 +162,8 @@ const ShowTextboxConfig = function(n, shared = 0) { } else { ConfigTextboxForm(n.data.id) } - + ConfigTextboxFontColor(textbox_font_color) + ConfigTextboxFontControls(textbox_fontsize, textbox_font_style, textbox_font_weight); ConfigTextboxContent(textbox_name); } @@ -975,16 +980,6 @@ const prepareStylesheet = function() { display: "none", }) - .selector(".eh-preview") - .css({ - "background-color": "blue", - "line-color": "blue", - "target-arrow-color": "blue", - "source-arrow-color": "blue", - "opacity": 0.5 - }) - - .selector(".eh-ghost-edge") .css({ "background-color": "blue", @@ -1032,23 +1027,29 @@ const prepareStylesheet = function() { } } - sheet.selector('node[type="textbox"]') + const getConfig = function(ele, property, defaultValue) { + let n = nodes.find(n => n.data.id === ele.id()); + return (n && n.config && n.config[property] !== undefined) ? n.config[property] : defaultValue; + }; + + sheet.selector('.textbox') .css({ 'shape': 'rectangle', 'background-opacity': 0, - 'border-width': 0, + 'border-width': 0, 'content': 'data(label)', - 'width': function(ele) { return ele.data('width') || 100; }, - 'height': function(ele) { return ele.data('height') || 50; }, + 'width': function (ele) { + return getConfig(ele,'width',50) + }, + 'height': function(ele) { return getConfig(ele,'height',50) }, 'text-valign': 'center', 'text-halign': 'center', - 'text-wrap': 'wrap', - 'text-max-width': function(ele) { return ele.data('width') || 100; }, - 'color': function(ele) { return ele.data('color') || '#000000'; }, - 'font-size': function(ele) { return ele.data('fontsize') || 12; }, - 'font-weight': function(ele) { return ele.data('fontweight') || 'normal'; }, - 'font-style': function(ele) { return ele.data('fontstyle') || 'normal'; }, - 'text-decoration': function(ele) { return ele.data('textdecoration') || 'none'; } + 'text-wrap': 'wrap', + 'text-max-width': function(ele) { return getConfig(ele,'width',50) }, + 'color': function(ele) { return getConfig(ele,'color','#000000') }, + 'font-size': function(ele) { return getConfig(ele,'tb_fontsize',12) }, + 'font-style': function(ele) {return getConfig(ele, 'fontstyle', 'normal')}, + 'font-weight': function(ele) {return getConfig(ele, 'fontweight', 'normal')} }); return sheet; @@ -1207,6 +1208,9 @@ const DrawGraph = function() { hideResizeFrame(); activeNodeId = node.id(); + let n = nodes.find(n => n.data.id === node.id()); + if (!n || !n.config) return; + const frame = $('
'); $('body').append(frame); @@ -1224,52 +1228,54 @@ const DrawGraph = function() { handle.on('mousedown', function(e) { e.stopPropagation(); e.preventDefault(); - + const startX = e.pageX; const startY = e.pageY; - const startW = node.data('width') || 100; - const startH = node.data('height') || 50; - - const startPos = { x: node.position().x, y: node.position().y }; - + const startW = n.config.width || 100; + const startH = n.config.height || 50; + const startPos = {x: node.position().x, y: node.position().y}; const zoom = cy.zoom(); - + node.ungrabify(); $(document).on('mousemove.resizing', function(ev) { const dx = (ev.pageX - startX) / zoom; const dy = (ev.pageY - startY) / zoom; - - let newW = startW; - let newH = startH; - let newX = startPos.x; + + let newW = startW; + let newH = startH; + let newX = startPos.x; let newY = startPos.y; // Horizontal Resize - if (h.d.includes('e')) { - newW = Math.max(30, startW + dx); + if (h.d.includes('e')) { + newW = Math.max(30, startW + dx); newX += (newW - startW) / 2; // Shift center right } - if (h.d.includes('w')) { - newW = Math.max(30, startW - dx); + if (h.d.includes('w')) { + newW = Math.max(30, startW - dx); newX -= (newW - startW) / 2; // Shift center left } // Vertical Resize - if (h.d.includes('s')) { - newH = Math.max(30, startH + dy); + if (h.d.includes('s')) { + newH = Math.max(30, startH + dy); newY += (newH - startH) / 2; // Shift center down } - if (h.d.includes('n')) { - newH = Math.max(30, startH - dy); + if (h.d.includes('n')) { + newH = Math.max(30, startH - dy); newY -= (newH - startH) / 2; // Shift center up } - // Apply changes - node.data('width', newW); - node.data('height', newH); - node.position({ x: newX, y: newY }); - + n.config.width = newW; + n.config.height = newH; + + node.style('width', newW); + node.style('height', newH); + node.style('text-max-width', newW); + + node.position({x: newX, y: newY}); + updateResizeFrame(); }); @@ -1283,11 +1289,11 @@ const DrawGraph = function() { frame.append(handle); }); - + updateResizeFrame(); }; - cy.on('tap', 'node[type="textbox"]', (e) => initResizeFrame(e.target)); + cy.on('tap', '.textbox', (e) => initResizeFrame(e.target)); cy.on('tap', (e) => { if (e.target === cy) hideResizeFrame(); }); cy.on('zoom pan position', updateResizeFrame); @@ -1297,14 +1303,12 @@ const DrawGraph = function() { if (sNode && sNode.config && sNode.config.type === 'textbox') return false; if (tNode && tNode.config && tNode.config.type === 'textbox') return false; - if (src.data('type') === 'textbox' || tgt.data('type') === 'textbox') return false; - return !src.same(tgt); + return !src.same(tgt); } let customDefaults = { - handleNodes: 'node[type!="textbox"]', - + handleNodes: '.host, .l2_switch, .l1_hub, .l3_router, .server', canConnect: (src, tgt) => allowEdges(src, tgt), edgeParams: (src, tgt) => allowEdges(src, tgt) ? {} : null, @@ -1449,7 +1453,7 @@ const DrawGraph = function() { // Add edge to the edges[] and then save it to the server. cy.on('ehcomplete', (event, sourceNode, targetNode, addedEdge) => { AddEdge(sourceNode._private.data.id, targetNode._private.data.id); - DrawGraph(); + DrawGraph(); PostNodesEdges(); TakeGraphPictureAndUpdate(); @@ -1541,19 +1545,27 @@ const DrawGraph = function() { }); - cy.on('tap', 'node[type="textbox"]', (e) => { - e.originalEvent.stopPropagation(); + cy.on('tap', '.textbox', (e) => { + e.originalEvent.stopPropagation(); initResizeFrame(e.target); }); cy.on('tap', (e) => { - if (e.target === cy || (e.target.isNode && e.target.isNode() && e.target.data('type') !== 'textbox')) { + if (e.target === cy) { hideResizeFrame(); + return; + } + + if (e.target.isNode && e.target.isNode()) { + let n = nodes.find(n => n.data.id === e.target.id()); + if (n && n.config && n.config.type !== 'textbox') { + hideResizeFrame(); + } } }); cy.on('zoom pan', updateResizeFrame); - + cy.on('dragstart', (e) => { if (e.target.id() !== activeNodeId) { hideResizeFrame(); From 9bdd3d0425bbf6414c293b49bc3dc6792bbd6769 Mon Sep 17 00:00:00 2001 From: AlexShmak Date: Tue, 10 Feb 2026 22:34:28 +0300 Subject: [PATCH 6/8] feat(textbox): rework devices and tools side panel --- back/src/network_schema.py | 2 ++ front/src/configurators.py | 1 + front/src/static/assets/css/workspace.css | 38 +++++++++++++++++++++-- front/src/static/config_textbox.html | 2 +- front/src/templates/network.html | 31 +++++++++++++++--- 5 files changed, 66 insertions(+), 8 deletions(-) diff --git a/back/src/network_schema.py b/back/src/network_schema.py index b55056df..207da45c 100644 --- a/back/src/network_schema.py +++ b/back/src/network_schema.py @@ -37,6 +37,7 @@ class NodeConfig: priority: Optional[int] = None default_gw: str = "" + @dataclass class TextboxNodeConfig(NodeConfig): width: int = 0 @@ -46,6 +47,7 @@ class TextboxNodeConfig(NodeConfig): fontweight: str = "" fontstyle: str = "" + @dataclass class NodeInterface: """ diff --git a/front/src/configurators.py b/front/src/configurators.py index 05a13cb4..927a4e6f 100644 --- a/front/src/configurators.py +++ b/front/src/configurators.py @@ -422,6 +422,7 @@ def _configure(self): return {"message": "Конфигурация обновлена", "nodes": self._nodes} + class TextboxConfigurator(AbstractDeviceConfigurator): def __init__(self): super().__init__(device_type="textbox") diff --git a/front/src/static/assets/css/workspace.css b/front/src/static/assets/css/workspace.css index 7ba0dde9..0e6dbbea 100644 --- a/front/src/static/assets/css/workspace.css +++ b/front/src/static/assets/css/workspace.css @@ -50,7 +50,7 @@ display: block; margin-bottom: 10px; padding: 10px; - width: 130px; + width: 170px; background-color: white; border-radius: 7px; box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1); @@ -63,7 +63,7 @@ display: block; max-height: calc(80vh - 140px); padding: 10px; - width: 130px; + width: 170px; background-color: white; border-radius: 7px; box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1); @@ -108,4 +108,38 @@ font-weight: 500; color: #222; } + + .side-menu-header { + position: relative; + text-align: center; + padding: 6px 28px; + } + + .side-menu-header span { + font-size: inherit; + user-select: none; + } + + .side-menu-arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + cursor: pointer; + font-size: 1.2rem; + color: #6c757d; + opacity: 0.6; + } + + .side-menu-arrow.left { + left: 6px; + } + + .side-menu-arrow.right { + right: 6px; + } + + .side-menu-arrow:hover { + color: #000; + opacity: 1; + } } \ No newline at end of file diff --git a/front/src/static/config_textbox.html b/front/src/static/config_textbox.html index c606a905..3321551d 100644 --- a/front/src/static/config_textbox.html +++ b/front/src/static/config_textbox.html @@ -13,7 +13,7 @@ diff --git a/front/src/templates/network.html b/front/src/templates/network.html index 6792a79d..a44ae9eb 100644 --- a/front/src/templates/network.html +++ b/front/src/templates/network.html @@ -79,9 +79,14 @@ {% endif %} -
-
Устройства
-
+
+
+ + Устройства + +
+ +
@@ -120,9 +125,8 @@
-
Инструменты
-
+