From bce42ff3ffa138bc1a9652807c9899f64760e5d3 Mon Sep 17 00:00:00 2001 From: snegi512 <106768300+snegi512@users.noreply.github.com> Date: Wed, 12 Mar 2025 22:35:36 +0300 Subject: [PATCH] worked version --- go.sum | 89 ++++ interface/package-lock.json | 30 ++ interface/package.json | 2 + interface/src/components/ethernet.jsx | 1 + .../components/ethernet_components/info.jsx | 2 +- .../ethernet_components/ip_addres_input.jsx | 133 ----- interface/src/components/menu/menu.jsx | 5 +- interface/src/components/wlan.jsx | 224 ++++++++- .../src/components/wlan/form_connect.jsx | 44 -- .../src/components/wlan/hotspot_control.jsx | 49 -- interface/src/components/wlan/info.jsx | 108 ++++ .../src/components/wlan/network_info.jsx | 37 -- .../src/components/wlan/network_list.jsx | 34 -- .../components/wlan/wifi_network_selector.jsx | 165 ++++++ interface/src/components/wlan/wlan.css | 10 - interface/src/main.jsx | 1 + interface/vite.config.js | 5 +- main.go | 162 +----- models/eth_yaml.go | 27 + models/interface_IPs_response.go | 8 + models/mac_address_response.go | 8 + models/ssid_response.go | 8 + models/wifi_networks_response.go | 14 + models/wlan_yaml.go | 25 + network/eth/handler.go | 90 ++-- network/network.go | 333 ------------- network/wlan/handler.go | 469 ++++++++++++------ wifi.yaml | 21 - 28 files changed, 1074 insertions(+), 1030 deletions(-) create mode 100644 go.sum delete mode 100644 interface/src/components/wlan/form_connect.jsx delete mode 100644 interface/src/components/wlan/hotspot_control.jsx create mode 100644 interface/src/components/wlan/info.jsx delete mode 100644 interface/src/components/wlan/network_info.jsx delete mode 100644 interface/src/components/wlan/network_list.jsx create mode 100644 interface/src/components/wlan/wifi_network_selector.jsx delete mode 100644 interface/src/components/wlan/wlan.css create mode 100644 models/eth_yaml.go create mode 100644 models/interface_IPs_response.go create mode 100644 models/mac_address_response.go create mode 100644 models/ssid_response.go create mode 100644 models/wifi_networks_response.go create mode 100644 models/wlan_yaml.go delete mode 100644 network/network.go delete mode 100644 wifi.yaml diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7f08abb --- /dev/null +++ b/go.sum @@ -0,0 +1,89 @@ +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/interface/package-lock.json b/interface/package-lock.json index caa18f5..3d7565e 100644 --- a/interface/package-lock.json +++ b/interface/package-lock.json @@ -8,10 +8,12 @@ "name": "interface", "version": "0.0.0", "dependencies": { + "bootstrap-icons": "^1.11.3", "primeflex": "^4.0.0", "primeicons": "^7.0.0", "primereact": "^10.9.2", "react": "^19.0.0", + "react-bootstrap-icons": "^1.11.5", "react-dom": "^19.0.0", "sass-embedded": "^1.85.1" }, @@ -1632,6 +1634,22 @@ "dev": true, "license": "MIT" }, + "node_modules/bootstrap-icons": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3816,6 +3834,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-bootstrap-icons": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/react-bootstrap-icons/-/react-bootstrap-icons-1.11.5.tgz", + "integrity": "sha512-eOhtFJMUqw98IJcfKJsSMZkFHCeNPTTwXZAe9V9d4mT22ARmbrISxPO9GmtWWuf72zQctLeZMGodX/q6wrbYYg==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": ">=16.8.6" + } + }, "node_modules/react-dom": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", diff --git a/interface/package.json b/interface/package.json index 05a26b1..13f0687 100644 --- a/interface/package.json +++ b/interface/package.json @@ -10,10 +10,12 @@ "preview": "vite preview" }, "dependencies": { + "bootstrap-icons": "^1.11.3", "primeflex": "^4.0.0", "primeicons": "^7.0.0", "primereact": "^10.9.2", "react": "^19.0.0", + "react-bootstrap-icons": "^1.11.5", "react-dom": "^19.0.0", "sass-embedded": "^1.85.1" }, diff --git a/interface/src/components/ethernet.jsx b/interface/src/components/ethernet.jsx index d18d897..2dfb387 100644 --- a/interface/src/components/ethernet.jsx +++ b/interface/src/components/ethernet.jsx @@ -19,6 +19,7 @@ export default function Ethernet() { const [rawYamlContent, setRawYamlContent] = useState(''); // Состояние для хранения raw YAML + useEffect(() => { fetchConfig(); fetchRawConfig(); // Загружаем raw YAML при старте, чтобы быть готовыми к режиму raw edit diff --git a/interface/src/components/ethernet_components/info.jsx b/interface/src/components/ethernet_components/info.jsx index a779538..6bb83cc 100644 --- a/interface/src/components/ethernet_components/info.jsx +++ b/interface/src/components/ethernet_components/info.jsx @@ -19,7 +19,7 @@ export default function Info({update}){ const macData = await macResponse.json(); setMacAddress(macData); - const ipResponse = await fetch('/api/eth/eth0ips'); + const ipResponse = await fetch('/api/eth/ips'); if (!ipResponse.ok) { throw new Error(`Failed to fetch IP addresses: ${ipResponse.status} ${ipResponse.statusText}`); } diff --git a/interface/src/components/ethernet_components/ip_addres_input.jsx b/interface/src/components/ethernet_components/ip_addres_input.jsx index a49ccc4..30d5c24 100644 --- a/interface/src/components/ethernet_components/ip_addres_input.jsx +++ b/interface/src/components/ethernet_components/ip_addres_input.jsx @@ -79,141 +79,8 @@ export default function IPAddressInput({ value, onChange, placeholder = "192.168 {(!isValidIPAddressCIDR(internalValue) && internalValue !== '') ? : <> - } - // ); }; -// eslint-disable-next-line react/prop-types -// export default function IPAddressInput({ value, onChange }){ -// -// // Функция для разбора пропса value и установки начального состояния -// const parseIpAddressValue = useCallback((inputValue) => { -// if (inputValue) { -// const parts = inputValue.split('/'); -// const ip = parts[0]; -// const mask = parts[1] || ''; // Маска может быть не указана -// const ipPartsArray = ip.split('.'); -// return { -// ipParts: ipPartsArray.length === 4 ? ipPartsArray : ['', '', '', ''], -// subnetMask: mask, -// }; -// } else { -// return { ipParts: ['', '', '', ''], subnetMask: '' }; -// } -// }, []); -// const parsedValue = parseIpAddressValue(value) -// -// const [ipParts, setIpParts] = useState(parsedValue.ipParts); -// const [subnetMask, setSubnetMask] = useState(parsedValue.subnetMask); -// -// -// -// //Эффект для установки начального состояния или обновления при изменении пропса value -// useEffect(() => { -// console.log(value) -// const parsedValue = parseIpAddressValue(value); -// setIpParts(parsedValue.ipParts); -// setSubnetMask(parsedValue.subnetMask); -// // console.log(parsedValue, ipParts, subnetMask); -// }, [value, parseIpAddressValue]); -// -// const handleIpPartChange = useCallback((index, newValue) => { -// const newIpParts = [...ipParts]; -// newIpParts[index] = newValue; -// setIpParts(newIpParts); -// // Вызываем onChange с новым значением IP адреса при изменении части IP -// if (onChange) { -// const val = getFullIpAddress({ ipParts, subnetMask: mask }) -// console.log(val) -// onChange(val); // Передаем текущие части IP и новую маску -// } -// -// }, [ipParts, onChange]); -// const handleSubnetMaskChange = useCallback((newValue) => { -// const mask = newValue?newValue.toString():"24"; // Ensure mask is string for consistency -// setSubnetMask(mask); -// // Вызываем onChange с новым значением IP адреса при изменении маски -// -// if (onChange) { -// const val = getFullIpAddress({ ipParts, subnetMask: mask }) -// console.log(val) -// onChange(val); // Передаем текущие части IP и новую маску -// } -// -// }, [onChange, subnetMask]); -// -// const isValidIpAddress = useCallback((currentIpParts) => { -// return currentIpParts.every(part => { -// const num = parseInt(part, 10); -// return !isNaN(num) && num >= 0 && num <= 255; -// }); -// }, []); -// -// const isValidSubnetMask = useCallback((currentSubnetMask) => { -// const num = parseInt(currentSubnetMask, 10); -// return !isNaN(num) && num >= 0 && num <= 32 && currentSubnetMask !== ''; // Маска не должна быть пустой строкой для валидности -// }, []); -// -// const getFullIpAddress = useCallback(({ ipParts: currentIpParts, subnetMask: currentSubnetMask }) => { // Принимаем части IP и маску как аргументы -// const validIp = isValidIpAddress(currentIpParts); -// const validMask = isValidSubnetMask(currentSubnetMask); -// -// if (validIp && validMask) { -// return `${currentIpParts.join('.')}/${currentSubnetMask}`; -// } -// return ''; // Или другое значение по умолчанию, если IP или маска невалидны -// }, [isValidIpAddress, isValidSubnetMask]); -// -// -// const currentFullIp = getFullIpAddress({ ipParts, subnetMask }); // Получаем текущий полный IP адрес для отображения валидации -// -// return ( -//
-//
-// {ipParts.map((part, index) => ( -// -// handleIpPartChange(index, e.target.value)} -// className="p-inputtext-sm p-mr-1" -// maxLength="3" -// style={{ width: '50px', textAlign: 'center' }} -// keyfilter="pint" -// /> -// {index < 3 && .} -// -// ))} -// / -// handleSubnetMaskChange(e.value)} // e.value уже число или null -// min={0} -// max={32} -// className="p-inputnumber-sm" -// style={{ width: '60px' }} -// keyfilter="pint" -// /> -//
-// {/* Для отображения полного IP адреса (опционально) */} -// {currentFullIp && ( -//
-// Полный IP адрес: {currentFullIp} -//
-// )} -// {(!isValidIpAddress(ipParts) || !isValidSubnetMask(subnetMask)) && ( -//
-// Неверный IP адрес или маска подсети. -//
-// )} -//
-// ); -// }; diff --git a/interface/src/components/menu/menu.jsx b/interface/src/components/menu/menu.jsx index 6831479..ceafc9b 100644 --- a/interface/src/components/menu/menu.jsx +++ b/interface/src/components/menu/menu.jsx @@ -6,7 +6,7 @@ export default function NavMenu() { let items = [ { - label: 'home', + label: 'Главная', icon: 'pi pi-home', url: "/home" }, @@ -14,14 +14,11 @@ export default function NavMenu() { ]; const start = logo; - return (
) - - } diff --git a/interface/src/components/wlan.jsx b/interface/src/components/wlan.jsx index 585acba..c42b38f 100644 --- a/interface/src/components/wlan.jsx +++ b/interface/src/components/wlan.jsx @@ -1,16 +1,224 @@ -import './wlan/wlan.css'; -import FormConnect from "./wlan/form_connect.jsx"; -import HotspotControl from "./wlan/hotspot_control.jsx"; -import NetworkInfo from "./wlan/network_info.jsx"; +import {Divider} from "primereact/divider"; +import Info from "./wlan/info.jsx"; +import {useEffect, useState} from "react"; +import {InputSwitch} from "primereact/inputswitch"; +import IPAddressInput from "./ethernet_components/ip_addres_input.jsx"; +import IPGatewayInput from "./ethernet_components/ip_gateway_input.jsx"; +import RequestButton from "./ethernet_components/bt_submit.jsx"; +import WifiNetworkSelector from "./wlan/wifi_network_selector.jsx"; +import log from "eslint-plugin-react/lib/util/log.js"; + function Wlan() { - const apiUrl = '/api/wlan'; + + const [config, setConfig] = useState(null); + const [dhcpEnabled, setDhcpEnabled] = useState(true); + const [additionalIps, setAdditionalIps] = useState(''); + const [gateway, setGateway] = useState(''); + const [error, setError] = useState(null); + const [successMessage, setSuccessMessage] = useState(''); + const [accessPoints, setAccessPoints] = useState({ssid:'',password:''}); + + + useEffect(() => { + fetchConfig(); + }, []); + + useEffect(() => { + if (dhcpEnabled) { + setAdditionalIps('') + setGateway('') + } + }, [dhcpEnabled]); + + const fetchConfig = async () => { + try { + const response = await fetch('/api/wlan/netplan'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + + console.log(data) + + setDhcpEnabled(data.network.wifis.wlan0.dhcp4 === undefined || data.network.wifis.wlan0.dhcp4 === true); // По умолчанию DHCP включен, если не указано явно false + setAdditionalIps(data.network.wifis.wlan0.addresses ? data.network.wifis.wlan0.addresses[0] : ''); + setGateway(data.network.wifis.wlan0.gateway4 || ''); + setError(null); + setAccessPoints(getSsidAndPassword(data)) + + for (const dataKey in data.network.wifis.wlan0["access-points"]) { + console.log(dataKey) + } + + setConfig(data); + } catch (e) { + console.error("Could not fetch netplan config:", e); + setError("Failed to load configuration. Please check the server."); + } + }; + + const handleDhcpChange = (e) => { + setDhcpEnabled(e.target.value); + }; + + const handleAdditionalIpsChange = (e) => { + setAdditionalIps(e); + }; + + const handleGatewayChange = (e) => { + console.log(e) + setGateway(e); + }; + + + const handleSubmit = async () => { + setSuccessMessage(''); + setError(null); + + // Сохранение через форму + if (!config) return; + let updatedAddresses = []; + if (config.network.wifis.wlan0.addresses && config.network.wifis.wlan0.addresses.length > 0) { + updatedAddresses.push(config.network.wifis.wlan0.addresses[0]); + } + const parsedAdditionalIps = additionalIps.split('\n').map(ip => ip.trim()).filter(ip => ip !== ''); + updatedAddresses = updatedAddresses.concat(parsedAdditionalIps.map(ip => ip.includes('/') ? ip : `${ip}/24`)); + + const updatedConfig = { + ...config, + network: { + ...config.network, + wifis: { + wlan0: { + dhcp4: dhcpEnabled, + addresses: updatedAddresses.length > 0 && !dhcpEnabled ? updatedAddresses : undefined, + gateway4: gateway || undefined, + nameservers: config.network.wifis.wlan0.nameservers, + 'access-points':{[accessPoints.ssid]:{password:accessPoints.password}} + } + } + } + }; + + try { + const response = await fetch('/api/wlan/netplan', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(updatedConfig), + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + setSuccessMessage('Configuration updated successfully!'); + fetchConfig(); // Обновляем отображение конфигурации после успешного сохранения + } catch (e) { + console.error("Could not update netplan config:", e); + setError("Failed to save configuration. Please check the server and logs."); + } + + }; + + if (error) { + return
Error: {error}
; + } + + if (!config) { + return
Loading configuration...
; + } + + + // const yamlString = config ? `network:\n version: ${config.network.version}\n renderer: ${config.network.renderer}\n ethernets:\n eth0:\n dhcp4: ${config.network.wifis.wlan0.dhcp4 === undefined ? 'yes' : (config.network.wifis.wlan0.dhcp4 ? 'yes' : 'no')}\n addresses: ${config.network.wifis.wlan0.addresses ? JSON.stringify(config.network.wifis.wlan0.addresses).replace(/,/g, ', ') : '[]'}\n gateway4: ${config.network.wifis.wlan0.gateway4 || 'null'}\n nameservers:\n addresses: ${JSON.stringify(config.network.wifis.wlan0.nameservers.addresses).replace(/,/g, ', ')}` : ''; + return ( -
-

Управление Wi-Fi

+
+

Управление Wi-Fi

+ + + + + +
+ + +
+ + {!dhcpEnabled ? ( + <> +
+ + +
+
+ + +
+ + ) : (<>)} + + + {setAccessPoints(e)}} /> + +
+ +
+ {successMessage &&
{successMessage}
} + {error &&
Error: {error}
} + +
+ ); } -export default Wlan; \ No newline at end of file +export default Wlan; + + +function getSsidAndPassword(data) { + try { + if (!data?.network?.wifis) { + throw new Error("Отсутствует ключ 'wifis' в данных"); + } + + const wifis = data.network.wifis; + const interfaceName = Object.keys(wifis)[0]; + + if (!interfaceName || !wifis[interfaceName]?.["access-points"]) { + throw new Error("Отсутствует ключ 'access-points' в данных"); + } + + const accessPoints = wifis[interfaceName]["access-points"]; + const ssid = Object.keys(accessPoints)[0]; + + if (!ssid ) { + throw new Error("Отсутствует SSID в данных"); + } + const password = accessPoints[ssid].password||''; + + return { + ssid: ssid, + password: password + }; + } catch (error) { + console.error("Ошибка при извлечении данных:", error.message); + return { + ssid: "", + password: "" + }; + } +} \ No newline at end of file diff --git a/interface/src/components/wlan/form_connect.jsx b/interface/src/components/wlan/form_connect.jsx deleted file mode 100644 index 4e3e115..0000000 --- a/interface/src/components/wlan/form_connect.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import {useState} from 'react'; -import NetworkList from "./network_list.jsx"; - - -const FormConnect = ({apiUrl}) => { - const [ssid, setSSID] = useState(''); - const [password, setPassword] = useState(''); - - const handleSubmit = async (e) => { - e.preventDefault(); - try { - const response = await fetch(`${apiUrl}/connect`, { - method: 'POST', - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - body: `ssid=${encodeURIComponent(ssid)}&password=${encodeURIComponent(password)}`, - }); - const result = await response.json(); - alert(result.status); - } catch (error) { - alert("Ошибка подключения"); - } - }; - - const handleNetworkSelect = (selectedSSID) => { - setSSID(selectedSSID); - }; - - return ( -
-
- - setPassword(e.target.value)} - /> - - -
- ); -}; - -export default FormConnect; \ No newline at end of file diff --git a/interface/src/components/wlan/hotspot_control.jsx b/interface/src/components/wlan/hotspot_control.jsx deleted file mode 100644 index 511884f..0000000 --- a/interface/src/components/wlan/hotspot_control.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useState } from 'react'; - -const HotspotControl = ({ apiUrl }) => { - const [ssid, setSSID] = useState(''); - const [password, setPassword] = useState(''); - - const enableHotspot = async () => { - try { - const response = await fetch(`${apiUrl}/enable-hotspot`, { - method: 'POST', - body: `ssid=${ssid}&password=${password}`, - }); - alert(await response.text()); - } catch (error) { - alert("Ошибка включения точки доступа"); - } - }; - - const disableHotspot = async () => { - try { - const response = await fetch(`${apiUrl}/disable-hotspot`); - alert(await response.text()); - } catch (error) { - alert("Ошибка отключения точки доступа"); - } - }; - - return ( -
-

Точка доступа

- setSSID(e.target.value)} - /> - setPassword(e.target.value)} - /> - - -
- ); -}; - -export default HotspotControl; \ No newline at end of file diff --git a/interface/src/components/wlan/info.jsx b/interface/src/components/wlan/info.jsx new file mode 100644 index 0000000..62640df --- /dev/null +++ b/interface/src/components/wlan/info.jsx @@ -0,0 +1,108 @@ +import React, { useState, useEffect } from 'react'; + +export default function Info(){ + const [macAddress, setMacAddress] = useState(null); + const [ipAddresses, setIpAddresses] = useState(null); + const [ssid, setSsid] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchNetworkInfo = async () => { + setLoading(true); + setError(null); + + try { + const macResponse = await fetch('/api/wlan/mac'); + if (!macResponse.ok) { + throw new Error(`Failed to fetch MAC address: ${macResponse.status} ${macResponse.statusText}`); + } + const macData = await macResponse.json(); + setMacAddress(macData); + + const ipResponse = await fetch('/api/wlan/ips'); + if (!ipResponse.ok) { + throw new Error(`Failed to fetch IP addresses: ${ipResponse.status} ${ipResponse.statusText}`); + } + const ipData = await ipResponse.json(); + setIpAddresses(ipData); + + const ssidResponse = await fetch('/api/wlan/ssid'); // New fetch for SSID + // if (!ssidResponse.ok) { + // throw new Error(`Failed to fetch SSID (wlan0): ${ssidResponse.status} ${ssidResponse.statusText}`); + // } + const ssidData = await ssidResponse.json(); + setSsid(ssidData); + + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + fetchNetworkInfo(); + + // const intervalId = setInterval(() => { + // fetchNetworkInfo(); + // }, 5000); + + // return () => clearInterval(intervalId); + }, []); + + if (loading) { + return
Загрузка информации о сети...
; + } + + if (error) { + return ( +
+ Ошибка при загрузке сетевой информации: {error.message} + {macAddress?.error &&

Ошибка MAC: {macAddress.error}

} + {ipAddresses?.error &&

Ошибка IP: {ipAddresses.error}

} + {ssid?.error &&

Ошибка SSID: {ssid.error}

} +
+ ); + } + + return ( +
+ {macAddress && macAddress.mac_address ? ( +

MAC-адрес: {macAddress.mac_address}

+ ) : ( + macAddress && macAddress.error ? ( +

Ошибка получения MAC-адреса: {macAddress.error}

+ ) : ( +

MAC-адрес : Не удалось получить

+ ) + )} + {ssid && ssid.ssid ? ( +

SSID: {ssid.ssid}

+ ) : ( + ssid && ssid.error ? ( +

Ошибка получения SSID: {ssid.error}

+ ) : ( +

SSID: Не удалось получить

+ ) + )} + {ipAddresses && ipAddresses.ip_addresses && ipAddresses.ip_addresses.length > 0 ? ( +
+

IP-адреса :

+
    + {ipAddresses.ip_addresses.map((ip, index) => ( +
  • {ip}
  • + ))} +
+
+ ) : ( + ipAddresses && ipAddresses.error ? ( +

Ошибка получения IP-адресов: {ipAddresses.error}

+ ) : ( +

IP-адреса: Не найдены

+ ) + )} + + +
+ ); +}; \ No newline at end of file diff --git a/interface/src/components/wlan/network_info.jsx b/interface/src/components/wlan/network_info.jsx deleted file mode 100644 index 8681790..0000000 --- a/interface/src/components/wlan/network_info.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useState, useEffect } from 'react'; - -const NetworkInfo = ({ apiUrl }) => { - const [info, setInfo] = useState({ - ip: '-', - netmask: '-', - gateway: '-', - mac: '-', - connected: false, - }); - - useEffect(() => { - const fetchInfo = async () => { - try { - const response = await fetch(`${apiUrl}/network-info`); - const data = await response.json(); - setInfo(data); - } catch (error) { - console.error("Ошибка получения информации:", error); - } - }; - fetchInfo(); - }, [apiUrl]); - - return ( -
-

Сетевая информация:

-

IP: {info.ip}

-

Маска: {info.netmask}

-

Шлюз: {info.gateway}

-

MAC: {info.mac}

-

Статус: {info.connected ? "Подключено" : "Отключено"}

-
- ); -}; - -export default NetworkInfo; \ No newline at end of file diff --git a/interface/src/components/wlan/network_list.jsx b/interface/src/components/wlan/network_list.jsx deleted file mode 100644 index 0c1563a..0000000 --- a/interface/src/components/wlan/network_list.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useState, useEffect } from 'react'; - -const NetworkList = ({ apiUrl, onConnect }) => { - const [networks, setNetworks] = useState([]); - - useEffect(() => { - const fetchNetworks = async () => { - try { - const response = await fetch(`${apiUrl}/list-networks`); - const data = await response.json(); - setNetworks(data); - } catch (error) { - console.error("Ошибка получения списка сетей:", error); - } - }; - fetchNetworks(); - }, [apiUrl]); - - return ( -
-

Доступные сети:

- -
- ); -}; - -export default NetworkList; \ No newline at end of file diff --git a/interface/src/components/wlan/wifi_network_selector.jsx b/interface/src/components/wlan/wifi_network_selector.jsx new file mode 100644 index 0000000..48bb882 --- /dev/null +++ b/interface/src/components/wlan/wifi_network_selector.jsx @@ -0,0 +1,165 @@ +import React, {useCallback, useEffect, useRef, useState} from 'react'; +import {FloatLabel} from "primereact/floatlabel"; +import {InputText} from "primereact/inputtext"; +import {Password} from 'primereact/password'; +import {Button} from "primereact/button"; + +export default function WifiNetworkSelector({value, onChange}) { + const [wifiNetworks, setWifiNetworks] = useState([]); + const [ssidInput, setSsidInput] = useState(''); + const [passwordInput, setPasswordInput] = useState(''); + const [loadingNetworks, setLoadingNetworks] = useState(true); + const [networkError, setNetworkError] = useState(null); + const passwordInputRef = useRef(null); + const fetchWifiNetworks = useCallback(async () => { + setLoadingNetworks(true); + setNetworkError(null); + try { + const response = await fetch('/api/wlan/networks'); + if (!response.ok) { + throw new Error(`Failed to fetch WiFi networks: ${response.status} ${response.statusText}`); + } + const data = await response.json(); + setWifiNetworks(data.networks || []); + } catch (error) { + setNetworkError(error); + } finally { + setLoadingNetworks(false); + } + }, []); + + + useEffect(() => { + setSsidInput(value.ssid) + setPasswordInput(value.password) + }, [value]); + + + const ssidChange = useCallback((e) => { + setSsidInput(e); + if (onChange) { + onChange({ + password: passwordInput, + ssid: e + }) + } + }); + const passwordChange = useCallback((e) => { + setPasswordInput(e); + if (onChange) { + onChange({ + password: e, + ssid: ssidInput + }) + } + }); + + + useEffect(() => { + fetchWifiNetworks(); + }, [fetchWifiNetworks]); + + const handleNetworkClick = (ssid) => { + setSsidInput(ssid); + if (passwordInputRef.current) { + passwordInputRef.current.focus(); + } + }; + + const handleRefreshClick = () => { + fetchWifiNetworks(); + }; + + const getSignalStrengthBars = (signalLevel) => { + const level = parseInt(signalLevel, 10); + if (isNaN(level)) return 'Нет сигнала'; + + const bars = getSignalLevel(signalLevel); + let barIcons = ''; + + return + + if (barIcons === '') return ; + return barIcons; + }; + + const getSecurityIcon = (securityType) => { + if (securityType && securityType.toLowerCase() !== 'open') { + return '🔒'; // Locked icon for secured networks + } + return '🔓'; // Unlocked icon for open networks + }; + + + return ( +
+ + + {loadingNetworks ? ( +
Загрузка списка WiFi сетей...
+ ) : networkError ? ( +
Ошибка загрузки сетей: {networkError.message}
+ ) : ( +
    + {wifiNetworks.map((network, index) => ( +
  • handleNetworkClick(network.ssid)} style={{ + cursor: 'pointer', + padding: '5px', + borderBottom: '1px solid #eee', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center' + }}> + + {getSecurityIcon(network.security)} {/* Security Icon */} + {network.ssid} + + + {getSignalStrengthBars(network.signal)} + +
  • + ))} +
+ )} + +
+ + ssidChange(e.target.value)}/> + + +
+ +
+ + + passwordChange(e.target.value)} + ref={passwordInputRef} + toggleMask + feedback={false} + /> + + +
+
+ ); +}; + + +function getSignalLevel(input) { + const [numerator, denominator] = input.split('/').map(Number); + if (denominator === 0) { + throw new Error("Знаменатель не может быть равен нулю."); + } + const percentage = (numerator / denominator) * 100; + const signalLevel = Math.round((percentage / 100) * 4); + return Math.min(Math.max(signalLevel, 0), 4); +} \ No newline at end of file diff --git a/interface/src/components/wlan/wlan.css b/interface/src/components/wlan/wlan.css deleted file mode 100644 index a93b641..0000000 --- a/interface/src/components/wlan/wlan.css +++ /dev/null @@ -1,10 +0,0 @@ -.network-info { - border: 1px solid #ccc; - padding: 1rem; - margin: 1rem 0; - border-radius: 4px; -} - -.network-info p { - margin: 0.5rem 0; -} \ No newline at end of file diff --git a/interface/src/main.jsx b/interface/src/main.jsx index f85044f..ab93bcb 100644 --- a/interface/src/main.jsx +++ b/interface/src/main.jsx @@ -4,6 +4,7 @@ import "primereact/resources/themes/arya-purple/theme.css"; import 'primeicons/primeicons.css'; import 'primeflex/primeflex.css'; import 'primereact/resources/primereact.css'; +import 'bootstrap-icons/font/bootstrap-icons.css'; import App from './App.jsx' createRoot(document.getElementById('root')).render( diff --git a/interface/vite.config.js b/interface/vite.config.js index be5c81f..cada411 100644 --- a/interface/vite.config.js +++ b/interface/vite.config.js @@ -6,11 +6,12 @@ export default defineConfig({ server: { proxy: { '/api': { - target: 'http://192.168.0.102:8088', + target: 'http://192.168.0.208:8088', changeOrigin: true, - // rewrite: (path) => path.replace(/^\/api/, ''), + rewrite: (path) => path.replace(/^\/net/, ''), }, }, }, + base: '/net/', plugins: [react()], }) diff --git a/main.go b/main.go index e4eccd7..d11d105 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,10 @@ package main import ( - "encoding/json" "log" "net/http" - "network_configurator/network" "network_configurator/network/eth" "network_configurator/network/wlan" - "path/filepath" - "strings" ) func main() { @@ -16,159 +12,13 @@ func main() { http.HandleFunc("/api/eth/netplan", eth.NetplanHandler) http.HandleFunc("/api/eth/netplan/raw", eth.RawNetplanHandler) http.HandleFunc("/api/eth/mac", eth.MacAdres) - http.HandleFunc("/api/eth/eth0ips", eth.InterfaceIPs) + http.HandleFunc("/api/eth/ips", eth.InterfaceIPs) - http.HandleFunc("/api/wlan/connect", wlan.ConnectWiFi) - http.HandleFunc("/api/wlan/disconnect", wlan.DisconnectWiFi) - http.HandleFunc("/api/wlan/enable-hotspot", wlan.EnableHotspot) - http.HandleFunc("/api/wlan/disable-hotspot", wlan.DisableHotspot) - http.HandleFunc("/api/wlan/set-static-ip", wlan.SetStaticIP) - http.HandleFunc("/api/wlan/enable-dhcp", wlan.EnableDHCP) - http.HandleFunc("/api/wlan/list-networks", wlan.ListNetworks) - http.HandleFunc("/api/wlan/network-info", wlan.GetNetworkInfo) - - // Обработчик для сканирования WiFi - http.HandleFunc("/api/wifi/scan", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - network.SendJSONError(w, "Метод не разрешен", http.StatusMethodNotAllowed) - return - } - - networks, err := network.ScanWiFiNetworks() - if err != nil { - log.Printf("Ошибка сканирования WiFi: %v", err) - network.SendJSONError(w, "Ошибка сканирования WiFi", http.StatusInternalServerError) - return - } - - network.SendJSONResponse(w, networks, http.StatusOK) - }) - - // Обработчик для подключения к WiFi - http.HandleFunc("/api/wifi/connect", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - network.SendJSONError(w, "Метод не разрешен", http.StatusMethodNotAllowed) - return - } - - var requestBody map[string]string - err := json.NewDecoder(r.Body).Decode(&requestBody) - if err != nil { - network.SendJSONError(w, "Неверный запрос", http.StatusBadRequest) - return - } - - ssid := requestBody["ssid"] - password := requestBody["password"] - - if ssid == "" || password == "" { - network.SendJSONError(w, "Необходимо указать SSID и пароль", http.StatusBadRequest) - return - } - - err = network.ConnectToWiFi(ssid, password) - if err != nil { - log.Printf("Ошибка подключения к WiFi: %v", err) - network.SendJSONError(w, "Ошибка подключения к WiFi", http.StatusInternalServerError) - return - } - - network.SendJSONResponse(w, map[string]string{"status": "success", "message": "Подключение к WiFi инициировано"}, http.StatusOK) - }) - - // Обработчик для получения статуса сети - http.HandleFunc("/api/network/status", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - network.SendJSONError(w, "Метод не разрешен", http.StatusMethodNotAllowed) - return - } - - status, err := network.GetNetworkStatus() - if err != nil { - log.Printf("Ошибка получения статуса сети: %v", err) - network.SendJSONError(w, "Ошибка получения статуса сети", http.StatusInternalServerError) - return - } - - network.SendJSONResponse(w, status, http.StatusOK) - }) - - // Обработчик для настройки статического IP - http.HandleFunc("/api/network/configure", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - network.SendJSONError(w, "Метод не разрешен", http.StatusMethodNotAllowed) - return - } - - var requestBody map[string]string - err := json.NewDecoder(r.Body).Decode(&requestBody) - if err != nil { - network.SendJSONError(w, "Неверный запрос", http.StatusBadRequest) - return - } - - iface := requestBody["interface"] - ip := requestBody["ip"] - netmask := requestBody["netmask"] - gateway := requestBody["gateway"] - - if iface == "" || ip == "" || netmask == "" { // Шлюз может быть пустым - network.SendJSONError(w, "Необходимо указать интерфейс, IP и маску", http.StatusBadRequest) - return - } - - err = network.ConfigureNetworkInterface(iface, ip, netmask, gateway) - if err != nil { - log.Printf("Ошибка конфигурации сети: %v", err) - network.SendJSONError(w, "Ошибка конфигурации сети: "+err.Error(), http.StatusInternalServerError) - return - } - - network.SendJSONResponse(w, map[string]string{"status": "success", "message": "Настройки сети применены"}, http.StatusOK) - }) - - // Обработчик для получения текущего SSID подключенной WiFi сети - http.HandleFunc("/api/wifi/connected-ssid", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - network.SendJSONError(w, "Метод не разрешен", http.StatusMethodNotAllowed) - return - } - - ssid, err := network.GetConnectedWiFiSSID() - if err != nil { - log.Printf("Ошибка получения текущего SSID: %v", err) - // Не отправляем ошибку, если нет подключения, просто возвращаем пустой SSID - if strings.Contains(err.Error(), "не удалось получить SSID") { - network.SendJSONResponse(w, map[string]string{"ssid": ""}, http.StatusOK) - return - } - network.SendJSONError(w, "Ошибка получения текущего SSID", http.StatusInternalServerError) - return - } - - network.SendJSONResponse(w, map[string]string{"ssid": ssid}, http.StatusOK) - }) - - // Обработчик для отключения от WiFi сети - http.HandleFunc("/api/wifi/disconnect", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { // Лучше использовать POST для действий - network.SendJSONError(w, "Метод не разрешен", http.StatusMethodNotAllowed) - return - } - - err := network.DisconnectWiFi() - if err != nil { - log.Printf("Ошибка отключения от WiFi: %v", err) - network.SendJSONError(w, "Ошибка отключения от WiFi", http.StatusInternalServerError) - return - } - - network.SendJSONResponse(w, map[string]string{"status": "success", "message": "Отключение от WiFi инициировано"}, http.StatusOK) - }) - - // Обработчик для статики frontend (React build) - fs := http.FileServer(http.Dir(filepath.Join("frontend", "build"))) - http.Handle("/", fs) + http.HandleFunc("/api/wlan/netplan", wlan.NetplanHandler) + http.HandleFunc("/api/wlan/mac", wlan.MacAdresWlan0) + http.HandleFunc("/api/wlan/ips", wlan.InterfaceIPsWlan0) + http.HandleFunc("/api/wlan/ssid", wlan.SSIDWlan0) + http.HandleFunc("/api/wlan/networks", wlan.WifiNetworks) port := ":8088" log.Printf("Сервер запущен на порту %s", port) diff --git a/models/eth_yaml.go b/models/eth_yaml.go new file mode 100644 index 0000000..6d0eda8 --- /dev/null +++ b/models/eth_yaml.go @@ -0,0 +1,27 @@ +package models + +type Network struct { + Version int `yaml:"version" json:"version"` + Renderer string `yaml:"renderer" json:"renderer"` + Ethernets Ethernets `yaml:"ethernets" json:"ethernets"` +} + +type Ethernets struct { + Eth0 Eth0Config `yaml:"eth0" json:"eth0"` +} + +type Eth0Config struct { + DHCP4 *bool `yaml:"dhcp4,omitempty" json:"dhcp4"` + Addresses []string `yaml:"addresses,omitempty" json:"addresses,omitempty"` + Gateway4 string `yaml:"gateway4,omitempty" json:"gateway4,omitempty"` + Nameservers Nameservers `yaml:"nameservers,omitempty" json:"nameservers"` +} + +type Nameservers struct { + Addresses []string `yaml:"addresses,omitempty" json:"addresses,omitempty"` +} + +// NetplanConfig структура для представления YAML файла +type NetplanConfig struct { + Network Network `yaml:"network" json:"network"` +} diff --git a/models/interface_IPs_response.go b/models/interface_IPs_response.go new file mode 100644 index 0000000..d7fe615 --- /dev/null +++ b/models/interface_IPs_response.go @@ -0,0 +1,8 @@ +package models + +// InterfaceIPsResponse структура для JSON ответа со списком IP адресов интерфейса +type InterfaceIPsResponse struct { + Interface string `json:"interface"` + IPAddresses []string `json:"ip_addresses"` + Error string `json:"error,omitempty"` +} diff --git a/models/mac_address_response.go b/models/mac_address_response.go new file mode 100644 index 0000000..e18baef --- /dev/null +++ b/models/mac_address_response.go @@ -0,0 +1,8 @@ +package models + +// MacAddressResponse структура для JSON ответа с MAC адресом +type MacAddressResponse struct { + MacAddress string `json:"mac_address"` + Interface string `json:"interface"` + Error string `json:"error,omitempty"` +} diff --git a/models/ssid_response.go b/models/ssid_response.go new file mode 100644 index 0000000..6132d79 --- /dev/null +++ b/models/ssid_response.go @@ -0,0 +1,8 @@ +package models + +// SSIDResponse структура для JSON ответа с SSID +type SSIDResponse struct { + SSID string `json:"ssid"` + Interface string `json:"interface"` + Error string `json:"error,omitempty"` +} diff --git a/models/wifi_networks_response.go b/models/wifi_networks_response.go new file mode 100644 index 0000000..4e2e46c --- /dev/null +++ b/models/wifi_networks_response.go @@ -0,0 +1,14 @@ +package models + +// NetworkInfo структура для представления информации о WiFi сети (включая уровень сигнала и тип безопасности) +type NetworkInfo struct { + SSID string `json:"ssid"` + Signal string `json:"signal,omitempty"` + Security string `json:"security,omitempty"` +} + +// WifiNetworksResponse структура для JSON ответа со списком WiFi сетей +type WifiNetworksResponse struct { + Networks []NetworkInfo `json:"networks"` + Error string `json:"error,omitempty"` +} diff --git a/models/wlan_yaml.go b/models/wlan_yaml.go new file mode 100644 index 0000000..3192281 --- /dev/null +++ b/models/wlan_yaml.go @@ -0,0 +1,25 @@ +package models + +type NetplanConfigWlan struct { + Network NetworkWlan `yaml:"network" json:"network"` +} + +type NetworkWlan struct { + Version int `yaml:"version" json:"version"` + Renderer string `yaml:"renderer" json:"renderer"` + Wifis map[string]Wifi `yaml:"wifis" json:"wifis"` +} + +type Wifi struct { + Dhcp4 bool `yaml:"dhcp4" json:"dhcp4"` + Addresses []string `yaml:"addresses,omitempty" json:"addresses,omitempty"` + Gateway4 string `yaml:"gateway4,omitempty" json:"gateway4,omitempty"` + Nameservers struct { + Addresses []string `yaml:"addresses,omitempty" json:"addresses,omitempty"` + } `yaml:"nameservers,omitempty" json:"nameservers,omitempty"` + AccessPoints map[string]AccessPoint `yaml:"access-points" json:"access-points"` +} + +type AccessPoint struct { + Password string `yaml:"password,omitempty" json:"password,omitempty"` +} diff --git a/network/eth/handler.go b/network/eth/handler.go index 72c96f6..a9896bb 100644 --- a/network/eth/handler.go +++ b/network/eth/handler.go @@ -8,37 +8,12 @@ import ( "log" "net" "net/http" + "network_configurator/models" "os" "os/exec" ) -// NetplanConfig структура для представления YAML файла -type NetplanConfig struct { - Network Network `yaml:"network" json:"network"` -} - -type Network struct { - Version int `yaml:"version" json:"version"` - Renderer string `yaml:"renderer" json:"renderer"` - Ethernets Ethernets `yaml:"ethernets" json:"ethernets"` -} - -type Ethernets struct { - Eth0 Eth0Config `yaml:"eth0" json:"eth0"` -} - -type Eth0Config struct { - DHCP4 *bool `yaml:"dhcp4,omitempty" json:"dhcp4"` // Используем указатель, чтобы различать "false" и отсутствие ключа - Addresses []string `yaml:"addresses,omitempty" json:"addresses"` - Gateway4 string `yaml:"gateway4,omitempty" json:"gateway4,omitempty"` - Nameservers Nameservers `yaml:"nameservers,omitempty" json:"nameservers"` -} - -type Nameservers struct { - Addresses []string `yaml:"addresses,omitempty" json:"addresses"` -} - -const netplanFilePath = "/etc/netplan/eth.yaml" // Путь к файлу netplan (для примера, лучше использовать /etc/netplan/eth.yaml) +const netplanFilePath = "/etc/netplan/eth.yaml" func NetplanHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { @@ -63,7 +38,7 @@ func getNetplanConfig(w http.ResponseWriter, r *http.Request) { } func updateNetplanConfig(w http.ResponseWriter, r *http.Request) { - var updatedConfig NetplanConfig + var updatedConfig models.NetplanConfig err := json.NewDecoder(r.Body).Decode(&updatedConfig) if err != nil { http.Error(w, fmt.Sprintf("Failed to decode request body: %v", err), http.StatusBadRequest) @@ -80,13 +55,13 @@ func updateNetplanConfig(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Netplan configuration updated successfully")) } -func readNetplanConfig() (*NetplanConfig, error) { +func readNetplanConfig() (*models.NetplanConfig, error) { yamlFile, err := os.ReadFile(netplanFilePath) if err != nil { return nil, fmt.Errorf("failed to read netplan file: %w", err) } - var config NetplanConfig + var config models.NetplanConfig err = yaml.Unmarshal(yamlFile, &config) if err != nil { return nil, fmt.Errorf("failed to unmarshal yaml: %w", err) @@ -95,7 +70,7 @@ func readNetplanConfig() (*NetplanConfig, error) { return &config, nil } -func writeNetplanConfig(config NetplanConfig) error { +func writeNetplanConfig(config models.NetplanConfig) error { savedConfig, err := readNetplanConfig() if err != nil { return fmt.Errorf("failed to read eth yaml: %w", err) @@ -178,18 +153,37 @@ func updateRawNetplanConfig(w http.ResponseWriter, r *http.Request) { } func readRawNetplanConfig() (string, error) { + yamlFile, err := os.ReadFile(netplanFilePath) if err != nil { return "", fmt.Errorf("failed to read netplan file: %w", err) } + return string(yamlFile), nil } func writeRawNetplanConfig(content string) error { - err := os.WriteFile(netplanFilePath, []byte(content), 0644) + + savedConfig, err := readRawNetplanConfig() + if err != nil { + return fmt.Errorf("failed to read eth yaml: %w", err) + } + + err = os.WriteFile(netplanFilePath, []byte(content), 0644) if err != nil { return fmt.Errorf("failed to write netplan file: %w", err) } + + cmd := exec.Command("netplan", "apply") + output, err := cmd.CombinedOutput() + if err != nil { + err = os.WriteFile(netplanFilePath, []byte(savedConfig), 0644) + if err != nil { + return fmt.Errorf("failed to write netplan file: %w", err) + } + + return fmt.Errorf("Ошибка при выполнении команды: %s\n Вывод команды: %s\n", err, output) + } return nil } @@ -227,18 +221,11 @@ func init() { } } -// MacAddressResponse структура для JSON ответа -type MacAddressResponse struct { - MacAddress string `json:"mac_address"` - Interface string `json:"interface"` - Error string `json:"error,omitempty"` -} - // MacAdres HTTP handler для получения MAC адреса интерфейса eth0 func MacAdres(w http.ResponseWriter, r *http.Request) { interfaces, err := net.Interfaces() if err != nil { - response := MacAddressResponse{ + response := models.MacAddressResponse{ Error: fmt.Sprintf("Failed to get network interfaces: %v", err), } w.Header().Set("Content-Type", "application/json") @@ -256,7 +243,7 @@ func MacAdres(w http.ResponseWriter, r *http.Request) { } if eth0Interface == nil { - response := MacAddressResponse{ + response := models.MacAddressResponse{ Error: "Interface eth0 not found", Interface: "eth0", } @@ -268,7 +255,7 @@ func MacAdres(w http.ResponseWriter, r *http.Request) { macAddress := eth0Interface.HardwareAddr.String() - response := MacAddressResponse{ + response := models.MacAddressResponse{ MacAddress: macAddress, Interface: "eth0", } @@ -278,18 +265,11 @@ func MacAdres(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(response) } -// InterfaceIPsResponse структура для JSON ответа со списком IP адресов интерфейса -type InterfaceIPsResponse struct { - Interface string `json:"interface"` - IPAddresses []string `json:"ip_addresses"` - Error string `json:"error,omitempty"` -} - // InterfaceIPs HTTP handler для получения списка IP адресов интерфейса eth0 func InterfaceIPs(w http.ResponseWriter, r *http.Request) { interfaces, err := net.Interfaces() if err != nil { - response := InterfaceIPsResponse{ + response := models.InterfaceIPsResponse{ Error: fmt.Sprintf("Failed to get network interfaces: %v", err), } w.Header().Set("Content-Type", "application/json") @@ -307,7 +287,7 @@ func InterfaceIPs(w http.ResponseWriter, r *http.Request) { } if eth0Interface == nil { - response := InterfaceIPsResponse{ + response := models.InterfaceIPsResponse{ Interface: "eth0", Error: "Interface eth0 not found", } @@ -319,7 +299,7 @@ func InterfaceIPs(w http.ResponseWriter, r *http.Request) { addrs, err := eth0Interface.Addrs() if err != nil { - response := InterfaceIPsResponse{ + response := models.InterfaceIPsResponse{ Interface: "eth0", Error: fmt.Sprintf("Failed to get addresses for eth0: %v", err), } @@ -331,14 +311,14 @@ func InterfaceIPs(w http.ResponseWriter, r *http.Request) { ipAddresses := make([]string, 0) for _, addr := range addrs { - if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { // Проверяем, что это IPNet и не loopback - if ipnet.IP.To4() != nil || ipnet.IP.To16() != nil { // Проверяем, что это IPv4 или IPv6 + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil || ipnet.IP.To16() != nil { ipAddresses = append(ipAddresses, ipnet.IP.String()) } } } - response := InterfaceIPsResponse{ + response := models.InterfaceIPsResponse{ Interface: "eth0", IPAddresses: ipAddresses, } diff --git a/network/network.go b/network/network.go deleted file mode 100644 index 5386dad..0000000 --- a/network/network.go +++ /dev/null @@ -1,333 +0,0 @@ -package network - -import ( - "bufio" - "encoding/json" - "fmt" - "net" - "net/http" - "os" - "os/exec" - "regexp" - "strconv" - "strings" -) - -// NetworkInterface представляет информацию о сетевом интерфейсе -type NetworkInterface struct { - Name string `json:"name"` - IP string `json:"ip"` - Netmask string `json:"netmask"` - Gateway string `json:"gateway"` -} - -// WiFiNetwork представляет информацию о WiFi сети -type WiFiNetwork struct { - SSID string `json:"ssid"` - Signal int `json:"signal"` // Пример, можно добавить больше информации - Security string `json:"security"` // Пример, можно добавить больше информации -} - -// ErrorResponse для JSON ответов с ошибками -type ErrorResponse struct { - Error string `json:"error"` -} - -// ScanWiFiNetworks сканирует доступные WiFi сети -func ScanWiFiNetworks() ([]WiFiNetwork, error) { - networks, err := ScanWiFiNetworksImpl() // Реализация зависит от ОС - if err != nil { - return nil, err - } - return networks, nil -} - -// ConnectToWiFi подключается к WiFi сети -func ConnectToWiFi(ssid, password string) error { - return ConnectToWiFiImpl(ssid, password) // Реализация зависит от ОС -} - -// GetNetworkStatus получает статус сетевых интерфейсов (wlan0, eth0) -func GetNetworkStatus() ([]NetworkInterface, error) { - interfaces, err := getNetworkStatusImpl() // Реализация зависит от ОС - if err != nil { - return nil, err - } - return interfaces, nil -} - -// ConfigureNetworkInterface настраивает статический IP для интерфейса -func ConfigureNetworkInterface(iface, ip, netmask, gateway string) error { - return configureNetworkInterfaceImpl(iface, ip, netmask, gateway) // Реализация зависит от ОС -} - -// Функция для выполнения команд shell -func executeCommand(command string, args ...string) (string, error) { - cmd := exec.Command(command, args...) - output, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("ошибка выполнения команды: %s %s, вывод: %s, ошибка: %v", command, strings.Join(args, " "), string(output), err) - } - return string(output), nil -} - -// Функция для отправки JSON ответа -func SendJSONResponse(w http.ResponseWriter, data interface{}, statusCode int) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(statusCode) - json.NewEncoder(w).Encode(data) -} - -// Функция для отправки JSON ошибки -func SendJSONError(w http.ResponseWriter, message string, statusCode int) { - SendJSONResponse(w, ErrorResponse{Error: message}, statusCode) -} - -// GetConnectedWiFiSSID получает SSID текущей подключенной WiFi сети -func GetConnectedWiFiSSID() (string, error) { - ssid, err := getConnectedWiFiSSIDImpl() // Реализация зависит от ОС - if err != nil { - return "", err - } - return ssid, nil -} - -// DisconnectWiFi отключается от текущей WiFi сети -func DisconnectWiFi() error { - return disconnectWiFiImpl() // Реализация зависит от ОС -} - -// ScanWiFiNetworksImpl реализация для Linux -func ScanWiFiNetworksImpl() ([]WiFiNetwork, error) { - output, err := executeCommand("sudo", "iwlist", "wlan0", "scan") - if err != nil { - return nil, fmt.Errorf("ошибка сканирования WiFi: %v", err) - } - - networks := []WiFiNetwork{} - scanner := bufio.NewScanner(strings.NewReader(output)) - currentNetwork := WiFiNetwork{} - reSSID := regexp.MustCompile(`ESSID:"([^"]*)"`) - reSignal := regexp.MustCompile(`level=(-?\d+)`) - reEncryption := regexp.MustCompile(`IE: .*?(WPA|WPA2|WEP).*`) - - for scanner.Scan() { - line := scanner.Text() - if strings.Contains(line, "ESSID:") { - matches := reSSID.FindStringSubmatch(line) - if len(matches) > 1 { - currentNetwork.SSID = matches[1] - } - } - if strings.Contains(line, "Signal level=") { - matches := reSignal.FindStringSubmatch(line) - if len(matches) > 1 { - signalLevel, _ := strconv.Atoi(matches[1]) - currentNetwork.Signal = signalLevel - } - } - if strings.Contains(line, "IE:") && reEncryption.MatchString(line) { - matches := reEncryption.FindStringSubmatch(line) - if len(matches) > 1 { - currentNetwork.Security = matches[1] - } else { - currentNetwork.Security = "Open" // Если не найдено, считаем открытой - } - } - if currentNetwork.SSID != "" { - networks = append(networks, currentNetwork) - currentNetwork = WiFiNetwork{} // Сброс для следующей сети - } - } - - return networks, nil -} - -// ConnectToWiFiImpl реализация для Linux -func ConnectToWiFiImpl(ssid, password string) error { - // Создаем файл конфигурации wpa_supplicant.conf - conf := fmt.Sprintf(`ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev -update_config=1 -country=RU - -network={ - ssid="%s" - psk="%s" -} -`, ssid, password) - - err := os.WriteFile("/etc/wpa_supplicant/wpa_supplicant.conf", []byte(conf), 0600) - if err != nil { - return fmt.Errorf("ошибка записи в wpa_supplicant.conf: %v", err) - } - - // Перезапускаем wpa_supplicant - _, err = executeCommand("sudo", "wpa_cli", "-i", "wlan0", "reconfigure") - if err != nil { - // Попробуем перезапустить службу wpa_supplicant, если wpa_cli не сработал - _, errRestartService := executeCommand("sudo", "systemctl", "restart", "wpa_supplicant") - if errRestartService != nil { - return fmt.Errorf("ошибка перезапуска wpa_supplicant: %v, также ошибка wpa_cli: %v", errRestartService, err) - } - } - - // Переподключаем интерфейс wlan0 - _, err = executeCommand("sudo", "ifdown", "wlan0") - if err != nil { - fmt.Printf("Предупреждение: ошибка ifdown wlan0: %v\n", err) // Не критическая ошибка - } - _, err = executeCommand("sudo", "ifup", "wlan0") - if err != nil { - return fmt.Errorf("ошибка ifup wlan0: %v", err) - } - - return nil -} - -// getNetworkStatusImpl реализация для Linux -func getNetworkStatusImpl() ([]NetworkInterface, error) { - interfaces := []NetworkInterface{} - - // Получаем статус для eth0 - eth0Status, err := getInterfaceStatus("eth0") - if err == nil { - interfaces = append(interfaces, eth0Status) - } - - // Получаем статус для wlan0 - wlan0Status, err := getInterfaceStatus("wlan0") - if err == nil { - interfaces = append(interfaces, wlan0Status) - } - - return interfaces, nil -} - -func getInterfaceStatus(ifaceName string) (NetworkInterface, error) { - ifconfigOutput, err := executeCommand("ifconfig", ifaceName) - if err != nil { - return NetworkInterface{}, fmt.Errorf("ошибка ifconfig %s: %v", ifaceName, err) - } - - ip, netmask, err := parseIfconfigOutput(ifconfigOutput) - if err != nil && !strings.Contains(err.Error(), "interface is down") { // Игнорируем ошибку, если интерфейс выключен - fmt.Printf("Предупреждение: ошибка парсинга ifconfig %s: %v\n", ifaceName, err) - return NetworkInterface{Name: ifaceName}, nil // Возвращаем интерфейс без IP и маски, но без критической ошибки - } - - gateway, err := getDefaultGateway(ifaceName) - if err != nil { - fmt.Printf("Предупреждение: ошибка получения шлюза для %s: %v\n", ifaceName, err) - gateway = "" // Шлюз может быть не задан - } - - return NetworkInterface{ - Name: ifaceName, - IP: ip, - Netmask: netmask, - Gateway: gateway, - }, nil -} - -func parseIfconfigOutput(output string) (ip, netmask string, err error) { - reIP := regexp.MustCompile(`inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`) - reNetmask := regexp.MustCompile(`netmask (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`) - - ipMatches := reIP.FindStringSubmatch(output) - if len(ipMatches) > 1 { - ip = ipMatches[1] - } else { - return "", "", fmt.Errorf("IP адрес не найден или интерфейс выключен") // Изменено сообщение об ошибке - } - - netmaskMatches := reNetmask.FindStringSubmatch(output) - if len(netmaskMatches) > 1 { - netmask = netmaskMatches[1] - } else { - netmask = "" // Маска может быть не найдена, не критично - } - - return ip, netmask, nil -} - -func getDefaultGateway(ifaceName string) (string, error) { - routeOutput, err := executeCommand("route", "-n") - if err != nil { - return "", fmt.Errorf("ошибка route -n: %v", err) - } - - scanner := bufio.NewScanner(strings.NewReader(routeOutput)) - for scanner.Scan() { - line := scanner.Text() - fields := strings.Fields(line) - if len(fields) >= 4 && fields[0] == "0.0.0.0" && fields[5] == ifaceName { - return fields[1], nil // Шлюз находится во втором поле - } - } - return "", fmt.Errorf("шлюз по умолчанию для интерфейса %s не найден", ifaceName) -} - -// configureNetworkInterfaceImpl реализация для Linux -func configureNetworkInterfaceImpl(iface, ip, netmask, gateway string) error { - // Проверяем валидность IP и маски - if net.ParseIP(ip) == nil { - return fmt.Errorf("неверный формат IP адреса: %s", ip) - } - if net.ParseIP(netmask) == nil { - return fmt.Errorf("неверный формат маски подсети: %s", netmask) - } - - // Настраиваем IP и маску - _, err := executeCommand("sudo", "ifconfig", iface, ip, "netmask", netmask) - if err != nil { - return fmt.Errorf("ошибка настройки IP и маски: %v", err) - } - - // Настраиваем шлюз, только если он указан - if gateway != "" { - if net.ParseIP(gateway) == nil { - return fmt.Errorf("неверный формат шлюза: %s", gateway) - } - _, err = executeCommand("sudo", "route", "del", "default", "gw", "0.0.0.0", "dev", iface) // Удаляем старый шлюз, если есть - if err != nil && !strings.Contains(err.Error(), "No such process") && !strings.Contains(err.Error(), "not in table") { // Игнорируем ошибки, если шлюз не был установлен - fmt.Printf("Предупреждение: ошибка удаления старого шлюза: %v\n", err) - } - _, err = executeCommand("sudo", "route", "add", "default", "gw", gateway, "dev", iface) - if err != nil { - return fmt.Errorf("ошибка настройки шлюза: %v", err) - } - } else { - // Если шлюз не указан, удаляем шлюз по умолчанию для этого интерфейса - _, err = executeCommand("sudo", "route", "del", "default", "gw", "0.0.0.0", "dev", iface) - if err != nil && !strings.Contains(err.Error(), "No such process") && !strings.Contains(err.Error(), "not in table") { - fmt.Printf("Предупреждение: ошибка удаления шлюза по умолчанию: %v\n", err) - } - } - - return nil -} - -// getConnectedWiFiSSIDImpl реализация для Linux -func getConnectedWiFiSSIDImpl() (string, error) { - output, err := executeCommand("iwconfig", "wlan0") - if err != nil { - return "", fmt.Errorf("ошибка выполнения iwconfig wlan0: %v", err) - } - - reSSID := regexp.MustCompile(`ESSID:"([^"]*)"`) - matches := reSSID.FindStringSubmatch(output) - if len(matches) > 1 { - return matches[1], nil - } - return "", fmt.Errorf("не удалось получить SSID из вывода iwconfig, возможно, не подключено к WiFi") -} - -// disconnectWiFiImpl реализация для Linux -func disconnectWiFiImpl() error { - // Отключаем интерфейс wlan0 - _, err := executeCommand("sudo", "ifdown", "wlan0") - if err != nil { - return fmt.Errorf("ошибка ifdown wlan0: %v", err) - } - return nil -} diff --git a/network/wlan/handler.go b/network/wlan/handler.go index 6740c7c..af97114 100644 --- a/network/wlan/handler.go +++ b/network/wlan/handler.go @@ -3,167 +3,350 @@ package wlan import ( "encoding/json" "fmt" + "gopkg.in/yaml.v3" + "log" "net" "net/http" + "network_configurator/models" + "os" "os/exec" - "regexp" "strings" ) -type ApiResponse struct { - Status string `json:"status"` - Error string `json:"error,omitempty"` -} +const netplanFilePath = "/etc/netplan/wlan.yaml" -type NetworkInfo struct { - IP string `json:"ip"` - Netmask string `json:"netmask"` - Gateway string `json:"gateway"` - MAC string `json:"mac"` - Connected bool `json:"connected"` -} - -// Подключение к Wi-Fi -func ConnectWiFi(w http.ResponseWriter, r *http.Request) { - ssid := r.FormValue("ssid") - password := r.FormValue("password") - - cmd := exec.Command("nmcli", "dev", "wifi", "connect", ssid, "password", password) - err := cmd.Run() +// MacAdresWlan0 HTTP handler для получения MAC адреса интерфейса wlan0 +func MacAdresWlan0(w http.ResponseWriter, r *http.Request) { + interfaces, err := net.Interfaces() if err != nil { - http.Error(w, "Ошибка подключения", http.StatusInternalServerError) - return - } - json.NewEncoder(w).Encode(ApiResponse{Status: "OK"}) -} - -// Отключение Wi-Fi -func DisconnectWiFi(w http.ResponseWriter, r *http.Request) { - cmd := exec.Command("nmcli", "dev", "disconnect", "wlan0") - err := cmd.Run() - if err != nil { - http.Error(w, "Ошибка отключения", http.StatusInternalServerError) - return - } - json.NewEncoder(w).Encode(ApiResponse{Status: "OK"}) -} - -// Включение точки доступа -func EnableHotspot(w http.ResponseWriter, r *http.Request) { - ssid := r.FormValue("ssid") - password := r.FormValue("password") - - cmd := exec.Command("nmcli", "dev", "wifi", "hotspot", "ifname", "wlan0", "ssid", ssid, "password", password) - err := cmd.Run() - if err != nil { - http.Error(w, "Ошибка включения точки доступа", http.StatusInternalServerError) - return - } - json.NewEncoder(w).Encode(ApiResponse{Status: "OK"}) -} - -// Отключение точки доступа -func DisableHotspot(w http.ResponseWriter, r *http.Request) { - cmd := exec.Command("nmcli", "con", "delete", "Wi-Fi Hotspot") - err := cmd.Run() - if err != nil { - http.Error(w, "Ошибка отключения точки доступа", http.StatusInternalServerError) - return - } - json.NewEncoder(w).Encode(ApiResponse{Status: "OK"}) -} - -// Установка статического IP -func SetStaticIP(w http.ResponseWriter, r *http.Request) { - ip := r.FormValue("ip") - gateway := r.FormValue("gateway") - dns := r.FormValue("dns") - - cmd := exec.Command("sh", "-c", fmt.Sprintf( - "nmcli con modify wlan0 ipv4.method manual ipv4.addresses %s ipv4.gateway %s ipv4.dns %s && nmcli con up wlan0", - ip, gateway, dns, - )) - err := cmd.Run() - if err != nil { - http.Error(w, "Ошибка установки статического IP", http.StatusInternalServerError) - return - } - json.NewEncoder(w).Encode(ApiResponse{Status: "OK"}) -} - -// Включение DHCP -func EnableDHCP(w http.ResponseWriter, r *http.Request) { - cmd := exec.Command("nmcli", "con", "modify", "wlan0", "ipv4.method", "auto") - err := cmd.Run() - if err != nil { - http.Error(w, "Ошибка включения DHCP", http.StatusInternalServerError) - return - } - json.NewEncoder(w).Encode(ApiResponse{Status: "OK"}) -} - -// Получение списка сетей -func ListNetworks(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - cmd := exec.Command("nmcli", "--fields", "SSID,SECURITY,SIGNAL,BARS", "-t", "-f", "SSID,SECURITY,SIGNAL,BARS", "dev", "wifi", "list") - out, err := cmd.Output() - if err != nil { - http.Error(w, "Ошибка получения списка сетей", http.StatusInternalServerError) - return - } - networks := parseNetworks(string(out)) - json.NewEncoder(w).Encode(networks) -} - -func parseNetworks(output string) []map[string]string { - lines := strings.Split(output, "\n")[1:] - networks := make([]map[string]string, 0) - for _, line := range lines { - if line == "" { - continue + response := models.MacAddressResponse{ + Error: fmt.Sprintf("Failed to get network interfaces: %v", err), } - fields := strings.Split(line, ":") - network := make(map[string]string) - network["ssid"] = fields[0] - network["security"] = fields[1] - network["signal"] = fields[2] - network["bars"] = fields[3] - networks = append(networks, network) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(response) + return } - return networks + + var wlan0Interface *net.Interface + for _, iface := range interfaces { + if iface.Name == "wlan0" { + wlan0Interface = &iface + break + } + } + + if wlan0Interface == nil { + response := models.MacAddressResponse{ + Error: "Interface wlan0 not found", + Interface: "wlan0", + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(response) + return + } + + macAddress := wlan0Interface.HardwareAddr.String() + + response := models.MacAddressResponse{ + MacAddress: macAddress, + Interface: "wlan0", + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) } -// Информация о текущем соединении -func GetNetworkInfo(w http.ResponseWriter, r *http.Request) { - info := NetworkInfo{Connected: false} - - // MAC - outMac, _ := exec.Command("ip", "link", "show", "wlan0").Output() - macRegex := regexp.MustCompile(`link/ether ([0-9a-f:]+)`) - if match := macRegex.FindStringSubmatch(string(outMac)); len(match) > 1 { - info.MAC = match[1] +// InterfaceIPsWlan0 HTTP handler для получения списка IP адресов интерфейса wlan0 +func InterfaceIPsWlan0(w http.ResponseWriter, r *http.Request) { + interfaces, err := net.Interfaces() + if err != nil { + response := models.InterfaceIPsResponse{ + Error: fmt.Sprintf("Failed to get network interfaces: %v", err), + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(response) + return } - // IP и маска - outIP, _ := exec.Command("ip", "-4", "addr", "show", "wlan0").Output() - ipRegex := regexp.MustCompile(`inet ([0-9.]+)/[0-9]+`) - if matchIP := ipRegex.FindStringSubmatch(string(outIP)); len(matchIP) > 1 { - info.IP = matchIP[1] + var wlan0Interface *net.Interface + for _, iface := range interfaces { + if iface.Name == "wlan0" { + wlan0Interface = &iface + break + } } - netmaskRegex := regexp.MustCompile(`inet [0-9.]+/[0-9]+`) - if matchNetmask := netmaskRegex.FindStringSubmatch(string(outIP)); len(matchNetmask) > 0 { - _, ipNet, _ := net.ParseCIDR(matchNetmask[0]) - info.Netmask = ipNet.Mask.String() + if wlan0Interface == nil { + response := models.InterfaceIPsResponse{ + Interface: "wlan0", + Error: "Interface wlan0 not found", + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(response) + return } - // Шлюз - outGateway, _ := exec.Command("ip", "route", "show", "default").Output() - gatewayRegex := regexp.MustCompile(`default via ([0-9.]+)`) - if matchGateway := gatewayRegex.FindStringSubmatch(string(outGateway)); len(matchGateway) > 1 { - info.Gateway = matchGateway[1] + addrs, err := wlan0Interface.Addrs() + if err != nil { + response := models.InterfaceIPsResponse{ + Interface: "wlan0", + Error: fmt.Sprintf("Failed to get addresses for wlan0: %v", err), + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(response) + return } - info.Connected = info.IP != "" && info.Gateway != "" - json.NewEncoder(w).Encode(info) + ipAddresses := make([]string, 0) + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil || ipnet.IP.To16() != nil { + ipAddresses = append(ipAddresses, ipnet.IP.String()) + } + } + } + + response := models.InterfaceIPsResponse{ + Interface: "wlan0", + IPAddresses: ipAddresses, + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) +} + +// SSIDWlan0 HTTP handler для получения SSID интерфейса wlan0 +func SSIDWlan0(w http.ResponseWriter, r *http.Request) { + // Command to get SSID (Linux - iwgetid, macOS - airport, Windows - needs different approach) + cmd := "iwgetid" // For Linux - you might need to install 'wireless-tools' package + out, err := exec.Command(cmd, "wlan0", "-r").Output() + if err != nil { + response := models.SSIDResponse{ + Interface: "wlan0", + Error: fmt.Sprintf("Failed to get SSID using %s: %v", cmd, err), + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(response) + return + } + + ssid := strings.TrimSpace(string(out)) + + if ssid == "" { + response := models.SSIDResponse{ + Interface: "wlan0", + Error: "SSID is empty or not connected", + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(response) + return + } + + response := models.SSIDResponse{ + Interface: "wlan0", + SSID: ssid, + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) +} + +func createTestNetplanFile() { + initialYaml := ` +network: + version: 2 + renderer: networkd + wifis: + wlan0: + dhcp4: yes +` + err := os.WriteFile(netplanFilePath, []byte(initialYaml), 0644) + if err != nil { + log.Fatalf("Failed to create test netplan file: %v", err) + } + fmt.Println("Test netplan file created at:", netplanFilePath) +} + +func init() { + if _, err := os.Stat(netplanFilePath); os.IsNotExist(err) { + createTestNetplanFile() + } +} + +func NetplanHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + getNetplanConfig(w, r) + case http.MethodPut: + updateNetplanConfig(w, r) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func getNetplanConfig(w http.ResponseWriter, r *http.Request) { + config, err := readNetplanConfig() + if err != nil { + http.Error(w, fmt.Sprintf("Failed to read config: %v", err), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(config) +} + +func updateNetplanConfig(w http.ResponseWriter, r *http.Request) { + var updatedConfig models.NetplanConfigWlan + err := json.NewDecoder(r.Body).Decode(&updatedConfig) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to decode request body: %v", err), http.StatusBadRequest) + return + } + + err = writeNetplanConfig(updatedConfig) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to write config: %v", err), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Netplan configuration updated successfully")) +} + +func readNetplanConfig() (*models.NetplanConfigWlan, error) { + yamlFile, err := os.ReadFile(netplanFilePath) + if err != nil { + return nil, fmt.Errorf("failed to read netplan file: %w", err) + } + + var config models.NetplanConfigWlan + err = yaml.Unmarshal(yamlFile, &config) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal yaml: %w", err) + } + + return &config, nil +} + +func writeNetplanConfig(config models.NetplanConfigWlan) error { + savedConfig, err := readNetplanConfig() + if err != nil { + return fmt.Errorf("failed to read eth yaml: %w", err) + } + + yamlData, err := yaml.Marshal(config) + if err != nil { + return fmt.Errorf("failed to marshal yaml: %w", err) + } + + err = os.WriteFile(netplanFilePath, yamlData, 0644) + if err != nil { + return fmt.Errorf("failed to write netplan file: %w", err) + } + + cmd := exec.Command("netplan", "apply") + output, err := cmd.CombinedOutput() + if err != nil { + + yamlData, err := yaml.Marshal(*savedConfig) + if err != nil { + return fmt.Errorf("failed to marshal yaml: %w", err) + } + + err = os.WriteFile(netplanFilePath, yamlData, 0644) + if err != nil { + return fmt.Errorf("failed to write netplan file: %w", err) + } + + return fmt.Errorf("Ошибка при выполнении команды: %s\n Вывод команды: %s\n", err, output) + } + + return nil +} + +// WifiNetworks HTTP handler для получения списка видимых WiFi сетей с уровнем сигнала и типом безопасности +func WifiNetworks(w http.ResponseWriter, r *http.Request) { + interfaceName := "wlan0" // Жестко задан интерфейс wlan0 для сканирования + cmd := "iwlist" // Using 'iwlist' command to scan WiFi networks (Linux specific) + out, err := exec.Command(cmd, interfaceName, "scan").Output() + if err != nil { + response := models.WifiNetworksResponse{ + Error: fmt.Sprintf("Failed to scan WiFi networks using %s: %v", cmd, err), + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(response) + return + } + + output := string(out) + lines := strings.Split(output, "\n") + networks := make([]models.NetworkInfo, 0) + seenSSIDs := make(map[string]bool) + + var currentNetworkInfo models.NetworkInfo + var signal string + for _, line := range lines { + line = strings.TrimSpace(line) + + if strings.Contains(line, "Signal level=") { + signalLevel := strings.SplitN(line, "=", 2)[1] + signalLevel = strings.Fields(signalLevel)[0] + signal = signalLevel + } + + if strings.Contains(line, "ESSID:") { + ssid := strings.SplitN(line, ":", 2)[1] + ssid = strings.Trim(ssid, "\"") + if !seenSSIDs[ssid] { + if currentNetworkInfo.SSID != "" { + networks = append(networks, currentNetworkInfo) + } + currentNetworkInfo = models.NetworkInfo{SSID: ssid, Signal: signal} + seenSSIDs[ssid] = true + } + } else if strings.Contains(line, "Encryption key:") && strings.Contains(line, "off") { + currentNetworkInfo.Security = "Open" // No encryption, Open network + } else if strings.Contains(line, "IE:") { // Information Element - for security type + if strings.Contains(line, "WPA2") { + currentNetworkInfo.Security = "WPA2" + } else if strings.Contains(line, "WPA") { + currentNetworkInfo.Security = "WPA" + } else if strings.Contains(line, "WEP") { + currentNetworkInfo.Security = "WEP" + } else { + if currentNetworkInfo.Security == "" { // If no specific WPA/WEP found and not marked as Open + currentNetworkInfo.Security = "Secured" // Generic secured if IE present but type not identified + } + } + } + } + if currentNetworkInfo.SSID != "" { + networks = append(networks, currentNetworkInfo) + } + + // If Security is still empty after all checks, and it's not Open, assume it's some form of Secured + for i := range networks { + if networks[i].Security == "" && networks[i].SSID != "" { + networks[i].Security = "Secured" // Default to Secured if type not determined by IE and not explicitly Open + } + } + + response := models.WifiNetworksResponse{ + Networks: networks, + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) } diff --git a/wifi.yaml b/wifi.yaml deleted file mode 100644 index 5b7aacd..0000000 --- a/wifi.yaml +++ /dev/null @@ -1,21 +0,0 @@ -network: - version: 2 - renderer: networkd - wifis: - wlan0: - mode: ap - access-points: - my-ap: - password: "your_strong_password" - security: wpa2-personal - dhcp4: yes - dhcp4-subnet: 192.168.10.0/24 - bridges: - br0: - interfaces: [ eth0, wlan0 ] - dhcp4: yes # Получить IP-адрес для моста от DHCP-сервера в проводной сети - gateway4: default # Использовать шлюз, полученный по DHCP - nameservers: - addresses: [ 8.8.8.8, 8.8.4.4 ] # Пример DNS-серверов - parameters: - forward-delay: 0 # Ускорить процесс моста \ No newline at end of file