worked version

This commit is contained in:
snegi512 2025-03-12 22:35:36 +03:00
parent 0eced29dda
commit bce42ff3ff
28 changed files with 1074 additions and 1030 deletions

89
go.sum Normal file
View File

@ -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=

View File

@ -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",

View File

@ -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"
},

View File

@ -19,6 +19,7 @@ export default function Ethernet() {
const [rawYamlContent, setRawYamlContent] = useState(''); // Состояние для хранения raw YAML
useEffect(() => {
fetchConfig();
fetchRawConfig(); // Загружаем raw YAML при старте, чтобы быть готовыми к режиму raw edit

View File

@ -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}`);
}

View File

@ -79,141 +79,8 @@ export default function IPAddressInput({ value, onChange, placeholder = "192.168
{(!isValidIPAddressCIDR(internalValue) && internalValue !== '') ?
<Message severity="error" text="Неправильный формат."/>
: <></>
}
</div>
// <InputText
// value={internalValue}
// onChange={handleChange}
// placeholder={placeholder}
// className={!isValidIPAddressCIDR(internalValue) && internalValue !== '' ? 'p-invalid' : ''} // Добавляем класс p-invalid при невалидном вводе
// />
);
};
// 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 (
// <div>
// <div className="p-d-flex p-ai-center">
// {ipParts.map((part, index) => (
// <React.Fragment key={index}>
// <InputText
// key={`ipPart-${index}`}
// value={part}
// onChange={(e) => handleIpPartChange(index, e.target.value)}
// className="p-inputtext-sm p-mr-1"
// maxLength="3"
// style={{ width: '50px', textAlign: 'center' }}
// keyfilter="pint"
// />
// {index < 3 && <span className="p-mr-1">.</span>}
// </React.Fragment>
// ))}
// <span className="p-mx-2">/</span>
// <InputNumber
// value={subnetMask}
// onValueChange={(e) => handleSubnetMaskChange(e.value)} // e.value уже число или null
// min={0}
// max={32}
// className="p-inputnumber-sm"
// style={{ width: '60px' }}
// keyfilter="pint"
// />
// </div>
// {/* Для отображения полного IP адреса (опционально) */}
// {currentFullIp && (
// <div className="p-mt-2">
// Полный IP адрес: <strong>{currentFullIp}</strong>
// </div>
// )}
// {(!isValidIpAddress(ipParts) || !isValidSubnetMask(subnetMask)) && (
// <div className="p-mt-2 p-error">
// Неверный IP адрес или маска подсети.
// </div>
// )}
// </div>
// );
// };

View File

@ -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 = <img alt="logo" src="/logic_hub.svg" height="40" className="mr-2"></img>;
return (
<div>
<Menubar model={items} start={start}/>
</div>
)
}

View File

@ -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 <div style={{color: 'red'}}>Error: {error}</div>;
}
if (!config) {
return <div>Loading configuration...</div>;
}
// 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 (
<div className="App">
<h1>Управление Wi-Fi</h1>
<div>
<h2>Управление Wi-Fi</h2>
<Divider/>
<Info/>
<Divider/>
<div style={{marginBottom: '15px', display: 'flex', alignItems: 'center', gap: '10px'}}>
<label htmlFor="dhcpToggle">DHCP:</label>
<InputSwitch
id="dhcpToggle"
checked={dhcpEnabled}
onChange={handleDhcpChange}
/>
</div>
<Divider/>
{!dhcpEnabled ? (
<>
<div style={{marginBottom: '15px'}} className={"flex flex-wrap"}>
<label htmlFor="additionalIps" className={"flex text-left align-items-center"}>IP
адрес:</label>
<IPAddressInput
value={additionalIps}
onChange={handleAdditionalIpsChange}
/>
</div>
<div style={{marginBottom: '15px'}} className={"flex flex-wrap"}>
<label htmlFor="gateway"
className={"flex text-left align-items-center"}>Gateway:</label>
<IPGatewayInput
value={gateway}
onChange={handleGatewayChange}
/>
</div>
<Divider/>
</>) : (<></>)}
<WifiNetworkSelector value={accessPoints} onChange={(e)=>{setAccessPoints(e)}} />
<Divider/>
<div className={"flex justify-content-end"}>
<RequestButton handleSubmit={handleSubmit}/>
</div>
{successMessage && <div style={{color: 'green', marginTop: '10px'}}>{successMessage}</div>}
{error && <div style={{color: 'red', marginTop: '10px'}}>Error: {error}</div>}
</div>
);
}
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: ""
};
}
}

View File

@ -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 (
<div>
<form onSubmit={handleSubmit}>
<NetworkList apiUrl={apiUrl} onConnect={handleNetworkSelect}/>
<input
type="password"
placeholder="Пароль"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Подключиться</button>
</form>
</div>
);
};
export default FormConnect;

View File

@ -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 (
<div>
<h3>Точка доступа</h3>
<input
type="text"
placeholder="SSID"
value={ssid}
onChange={(e) => setSSID(e.target.value)}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button onClick={enableHotspot}>Включить</button>
<button onClick={disableHotspot}>Отключить</button>
</div>
);
};
export default HotspotControl;

View File

@ -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 <div>Загрузка информации о сети...</div>;
}
if (error) {
return (
<div>
Ошибка при загрузке сетевой информации: {error.message}
{macAddress?.error && <p>Ошибка MAC: {macAddress.error}</p>}
{ipAddresses?.error && <p>Ошибка IP: {ipAddresses.error}</p>}
{ssid?.error && <p>Ошибка SSID: {ssid.error}</p>}
</div>
);
}
return (
<div>
{macAddress && macAddress.mac_address ? (
<p><strong>MAC-адрес:</strong> {macAddress.mac_address}</p>
) : (
macAddress && macAddress.error ? (
<p><strong>Ошибка получения MAC-адреса:</strong> {macAddress.error}</p>
) : (
<p><strong>MAC-адрес :</strong> Не удалось получить</p>
)
)}
{ssid && ssid.ssid ? (
<p><strong>SSID:</strong> {ssid.ssid}</p>
) : (
ssid && ssid.error ? (
<p><strong>Ошибка получения SSID:</strong> {ssid.error}</p>
) : (
<p><strong>SSID:</strong> Не удалось получить</p>
)
)}
{ipAddresses && ipAddresses.ip_addresses && ipAddresses.ip_addresses.length > 0 ? (
<div>
<p><strong>IP-адреса :</strong></p>
<ul>
{ipAddresses.ip_addresses.map((ip, index) => (
<li key={index}>{ip}</li>
))}
</ul>
</div>
) : (
ipAddresses && ipAddresses.error ? (
<p><strong>Ошибка получения IP-адресов:</strong> {ipAddresses.error}</p>
) : (
<p><strong>IP-адреса:</strong> Не найдены</p>
)
)}
</div>
);
};

View File

@ -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 (
<div className="network-info">
<h3>Сетевая информация:</h3>
<p>IP: {info.ip}</p>
<p>Маска: {info.netmask}</p>
<p>Шлюз: {info.gateway}</p>
<p>MAC: {info.mac}</p>
<p>Статус: {info.connected ? "Подключено" : "Отключено"}</p>
</div>
);
};
export default NetworkInfo;

View File

@ -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 (
<div>
<h3>Доступные сети:</h3>
<select onChange={(e) => onConnect(e.target.value)}>
<option>Выберите сеть</option>
{networks.map((network, index) => (
<option key={index} value={network.ssid}>
{network.ssid} ({network.security}, {network.bars})
</option>
))}
</select>
</div>
);
};
export default NetworkList;

View File

@ -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 <i className={"bi bi-reception-" + bars}></i>
if (barIcons === '') return <i className={"bi bi-reception-0"}></i>;
return barIcons;
};
const getSecurityIcon = (securityType) => {
if (securityType && securityType.toLowerCase() !== 'open') {
return '🔒'; // Locked icon for secured networks
}
return '🔓'; // Unlocked icon for open networks
};
return (
<div>
<Button onClick={handleRefreshClick} disabled={loadingNetworks}>
{loadingNetworks ? 'Обновление...' : 'Обновить список'}
</Button>
{loadingNetworks ? (
<div>Загрузка списка WiFi сетей...</div>
) : networkError ? (
<div>Ошибка загрузки сетей: {networkError.message}</div>
) : (
<ul>
{wifiNetworks.map((network, index) => (
<li key={index} onClick={() => handleNetworkClick(network.ssid)} style={{
cursor: 'pointer',
padding: '5px',
borderBottom: '1px solid #eee',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<span>
{getSecurityIcon(network.security)} {/* Security Icon */}
{network.ssid}
</span>
<span style={{
marginLeft: '10px',
fontSize: '0.8em',
color: '#777',
display: 'flex',
alignItems: 'center'
}}>
{getSignalStrengthBars(network.signal)}
</span>
</li>
))}
</ul>
)}
<div className={"flex justify-content-center mt-5"}>
<FloatLabel>
<InputText id="ssid-input" value={ssidInput} onChange={(e) => ssidChange(e.target.value)}/>
<label htmlFor="ssid-input">SSID</label>
</FloatLabel>
</div>
<div className={"flex justify-content-center mt-5"}>
<FloatLabel>
<Password type="password" id="password-input" value={passwordInput}
onChange={(e) => passwordChange(e.target.value)}
ref={passwordInputRef}
toggleMask
feedback={false}
/>
<label htmlFor="password-input">Пароль</label>
</FloatLabel>
</div>
</div>
);
};
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);
}

View File

@ -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;
}

View File

@ -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(

View File

@ -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()],
})

162
main.go
View File

@ -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)

27
models/eth_yaml.go Normal file
View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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"`
}

8
models/ssid_response.go Normal file
View File

@ -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"`
}

View File

@ -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"`
}

25
models/wlan_yaml.go Normal file
View File

@ -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"`
}

View File

@ -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,
}

View File

@ -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
}

View File

@ -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
response := models.MacAddressResponse{
Error: fmt.Sprintf("Failed to get network interfaces: %v", err),
}
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)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(response)
return
}
networks := parseNetworks(string(out))
json.NewEncoder(w).Encode(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 parseNetworks(output string) []map[string]string {
lines := strings.Split(output, "\n")[1:]
networks := make([]map[string]string, 0)
// 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
}
var wlan0Interface *net.Interface
for _, iface := range interfaces {
if iface.Name == "wlan0" {
wlan0Interface = &iface
break
}
}
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
}
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
}
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 {
if line == "" {
continue
line = strings.TrimSpace(line)
if strings.Contains(line, "Signal level=") {
signalLevel := strings.SplitN(line, "=", 2)[1]
signalLevel = strings.Fields(signalLevel)[0]
signal = signalLevel
}
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)
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)
}
return networks
}
// Информация о текущем соединении
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]
}
// 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]
}
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()
}
// Шлюз
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]
}
info.Connected = info.IP != "" && info.Gateway != ""
json.NewEncoder(w).Encode(info)
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)
}

View File

@ -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 # Ускорить процесс моста