save after change

This commit is contained in:
snegi512 2025-03-11 14:07:01 +03:00
commit 0eced29dda
33 changed files with 7988 additions and 0 deletions

9
.idea/network_configurator.iml generated Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

34
go.mod Normal file
View File

@ -0,0 +1,34 @@
module network_configurator
go 1.23.0
require github.com/gin-gonic/gin v1.10.0
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

24
interface/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

8
interface/README.md Normal file
View File

@ -0,0 +1,8 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

View File

@ -0,0 +1,38 @@
import js from '@eslint/js'
import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
settings: { react: { version: '18.3' } },
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]

14
interface/index.html Normal file
View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="preload" href="/locales">
<link rel="icon" type="image/svg+xml" href="/logic_hub.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LogicHUB Assistant</title>
</head>
<body style="margin: 0">
<div id="root" style="margin: 0"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

5125
interface/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
interface/package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "interface",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"primeflex": "^4.0.0",
"primeicons": "^7.0.0",
"primereact": "^10.9.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"sass-embedded": "^1.85.1"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.19.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.18",
"globals": "^15.14.0",
"vite": "^6.1.0"
}
}

View File

@ -0,0 +1,396 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
<style type="text/css">
.st0{fill:#A0232B;}
.st1{fill:#FFFFFF;}
</style>
<g>
<g>
<path class="st0" d="M1000,999c-333.33,0-666.67,0-1000,0C0,666.42,0,333.84,0,1.26c333.33,0,666.67,0,1000,0
C1000,333.84,1000,666.42,1000,999z M960.99,500.38c0-150.72,0-301.43,0-452.15c0-1.33,0.01-2.66-0.01-3.99
c0-0.53-0.04-1.06-0.07-1.59c-0.03-0.53-0.49-1.03-0.99-1.04c-1.86-0.05-3.73-0.11-5.59-0.11c-302.25,0-604.5,0-906.75,0
c-1.33,0-2.67,0-4,0.01c-0.4,0-0.8,0.03-1.2,0.06c-0.66,0.05-1.31,0.63-1.32,1.25C41.02,44.4,41,45.99,41,47.59
c0,301.97,0,603.93,0,905.9c0,1.2,0,2.39,0.01,3.59c0,0.4,0.04,0.8,0.07,1.19c0.03,0.39,0.66,1.01,1.08,1.02
c1.33,0.04,2.66,0.09,3.99,0.09c303.18,0,606.37,0,909.55,0c1.2,0,2.4,0,3.6-0.03c1.3-0.03,1.66-0.38,1.68-1.65
c0.03-1.46,0.02-2.93,0.02-4.39C960.99,802.34,960.99,651.36,960.99,500.38z"/>
<path class="st1" d="M960.99,500.38c0,150.98,0,301.97,0,452.95c0,1.46,0.01,2.93-0.02,4.39c-0.02,1.27-0.38,1.62-1.68,1.65
c-1.2,0.03-2.4,0.03-3.6,0.03c-303.18,0-606.37,0-909.55,0c-1.33,0-2.66-0.05-3.99-0.09c-0.42-0.01-1.05-0.63-1.08-1.02
c-0.03-0.4-0.07-0.8-0.07-1.19c-0.01-1.2-0.01-2.39-0.01-3.59c0-301.97,0-603.93,0-905.9c0-1.6,0.03-3.19,0.07-4.79
c0.02-0.62,0.66-1.21,1.32-1.25c0.4-0.03,0.8-0.05,1.2-0.06c1.33-0.01,2.67-0.01,4-0.01c302.25,0,604.5,0,906.75,0
c1.86,0,3.73,0.06,5.59,0.11c0.5,0.01,0.95,0.52,0.99,1.04c0.03,0.53,0.07,1.06,0.07,1.59c0.01,1.33,0.01,2.66,0.01,3.99
C960.99,198.94,960.99,349.66,960.99,500.38z M257.46,707.97c-1.62,0-2.95,0-4.28,0c-9.2,0-18.4-0.02-27.6,0.02
c-1.98,0.01-3.99-0.36-5.93,0.32c-0.56,0.2-0.57,0.46-0.57,6.18c0,17.16,0,34.32,0,51.48c0,1.33-0.02,2.66-0.13,3.98
c-0.03,0.33-0.35,0.72-0.82,0.74c-1.2,0.05-2.39,0.12-3.59,0.13c-9.07,0.01-18.13,0.04-27.2,0c-6.12-0.03-5.26,0.81-5.26-5.29
c-0.02-65.71-0.01-131.42-0.01-197.12c0-1.06-0.01-2.13,0.03-3.19c0.01-0.39,0.09-0.8,0.23-1.16c0.08-0.22,0.32-0.5,0.53-0.53
c0.78-0.12,1.57-0.21,2.36-0.21c10.27-0.01,20.53-0.01,30.8,0c0.66,0,1.33,0.06,1.99,0.1c0.48,0.03,0.97,0.55,0.99,1.06
c0.04,1.33,0.08,2.66,0.09,3.98c0,32.72,0,65.44,0,98.16c0,1.33,0.01,2.66,0.03,3.99c0,0.26,0.06,0.54,0.18,0.76
c0.11,0.21,0.34,0.47,0.54,0.49c1.19,0.1,2.38,0.2,3.57,0.2c10.4,0.01,20.8,0.01,31.2,0.01c17.46,0,34.93,0,52.39,0.01
c1.33,0,2.66,0.07,3.99,0.12c0.5,0.02,0.96,0.53,0.97,1.07c0.03,1.33,0.06,2.66,0.06,3.99c0,32.72,0,65.44,0,98.16
c0,1.2-0.01,2.39,0.02,3.59c0.01,0.39,0.06,0.8,0.19,1.17c0.08,0.22,0.33,0.51,0.54,0.53c0.92,0.11,1.85,0.19,2.78,0.19
c16.66,0.01,33.33,0.01,49.99,0.01c0.67,0,1.33-0.01,2-0.04c0.91-0.04,1.39-0.54,1.41-1.54c0.03-1.33,0.03-2.66,0.03-3.99
c0-72.09,0-144.19,0-216.28c0-0.67,0-1.33,0-2c-0.01-3.88,0.21-3.64-3.71-3.64c-16.13-0.01-32.26-0.02-48.39,0.01
c-5.52,0.01-4.84-0.62-4.85,4.92c-0.02,17.29-0.01,34.58-0.01,51.88c0,1.33-0.01,2.66-0.12,3.98c-0.02,0.3-0.4,0.67-0.71,0.82
c-0.34,0.16-0.78,0.14-1.18,0.14c-8.13,0-16.26,0-24.4-0.01c-0.39,0-0.77-0.15-1.29-0.26c-0.33-1.73-2.17-2.57-2.59-4.33
c-0.08-0.34-0.53-0.58-0.78-0.89c-1.11-1.38-2.94-1.76-4.18-3c-0.4-0.4-1.32-0.22-1.86-0.54c-1.33-0.79-2.75-0.88-4.23-0.83
c-0.93,0.03-1.87-0.02-2.8,0.02c-0.52,0.02-1.09,0.06-1.55,0.28c-1.52,0.73-3.4,0.73-4.55,2.24c-0.14,0.19-0.48,0.23-0.7,0.36
c-1.68,1.01-2.05,1.38-3.04,3c-0.27,0.45-0.48,0.98-0.86,1.32c-0.85,0.77-1.25,1.69-1.25,2.81c-0.92,1.42-0.92,3-0.86,4.6
c0.02,0.66,0.05,1.33,0,1.99c-0.15,1.8,1.16,3.22,1.28,4.94c1.13,0.98,1.56,2.45,2.48,3.56c0.58,0.7,1.21,1.41,2.14,1.75
c0.49,0.18,0.99,0.46,1.34,0.82c0.79,0.81,1.8,0.98,2.82,1.33c1.14,0.38,2.19,0.88,3.43,0.82c1.2-0.06,2.4-0.02,3.6-0.02
c0.67,0,1.3-0.13,1.93-0.41c1.42-0.63,3.1-0.74,4.17-2.11c0.22-0.28,0.75-0.3,1.05-0.54c0.82-0.67,1.59-1.39,2.38-2.09
c0.4-1.61,1.94-2.51,2.49-4.03c0.16-0.43,0.57-0.57,0.94-0.6c1.2-0.07,2.4-0.09,3.59-0.09c9.47-0.01,18.93,0,28.4,0
c1.07,0,2.13,0.01,3.2-0.02c1.06-0.04,1.69-0.22,1.74-1.66c0.04-1.2,0.03-2.39,0.03-3.59c0-17.03,0-34.05,0-51.08
c0-1.2-0.01-2.39,0.02-3.59c0.01-0.39,0.11-0.78,0.18-1.17c0.09-0.44,0.47-0.68,0.83-0.74c0.65-0.1,1.33-0.1,1.99-0.1
c10.27,0,20.53-0.01,30.8,0c0.66,0,1.33,0.04,1.99,0.08c0.52,0.03,1.06,0.5,1.08,0.98c0.05,1.19,0.11,2.39,0.11,3.58
c0.01,66.11,0,132.22,0,198.32c0,1.06-0.01,2.13-0.04,3.19c-0.02,0.78-0.48,1.27-1.2,1.3c-0.8,0.04-1.6,0.06-2.4,0.06
c-10,0-20,0-30,0c-0.67,0-1.33-0.01-2-0.04c-0.8-0.03-1.28-0.44-1.3-1.17c-0.04-1.46-0.07-2.92-0.07-4.39
c0-32.59,0-65.18-0.01-97.77c0-1.33-0.03-2.66-0.07-3.99c-0.02-0.74-0.54-1.28-1.22-1.3c-1.33-0.03-2.67-0.04-4-0.04
c-27.46,0-54.93,0-82.39,0c-1.2,0-2.4,0.01-3.6-0.02c-1.02-0.03-1.78-0.18-1.79-1.59c-0.01-1.2-0.02-2.39-0.02-3.59
c0-11.44,0-22.88,0-34.32c0-21.42,0-42.83,0-64.25c0-1.33-0.01-2.66-0.12-3.98c-0.02-0.3-0.41-0.74-0.7-0.81
c-0.64-0.15-1.32-0.14-1.98-0.14c-17.06-0.01-34.13-0.01-51.19,0c-0.53,0-1.07,0.01-1.6,0.03c-0.72,0.03-1.29,0.54-1.31,1.2
c-0.03,1.2-0.05,2.39-0.05,3.59c0,72.63,0,145.25,0,217.88c0,1.06,0,2.13,0.03,3.19c0.03,1.02,0.48,1.52,1.4,1.56
c0.93,0.03,1.87,0.04,2.8,0.04c16.13,0,32.26,0,48.39,0c0.8,0,1.6,0,2.4-0.01c0.27,0,0.53-0.03,0.8-0.06
c0.5-0.04,1.02-0.52,1.04-1.03c0.05-1.19,0.1-2.39,0.1-3.58c0.01-15.43,0-30.86,0-46.29c0-3.19,0-6.38,0.01-9.58
c0-0.53,0.05-1.06,0.16-1.58c0.05-0.22,0.31-0.52,0.52-0.55c0.78-0.13,1.58-0.22,2.37-0.22c7.2-0.02,14.4-0.02,21.6,0.01
c1.31,0.01,2.66-0.27,3.93,0.29c0.81,1.19,1.68,2.34,2.39,3.59c0.33,0.58,0.69,1.11,1.11,1.63c1.12,1.38,2.95,1.76,4.18,3
c0.4,0.4,1.25,0.33,1.87,0.53c0.87,0.28,1.6,0.92,2.6,0.88c1.46-0.06,2.93,0.03,4.4-0.03c0.64-0.03,1.32-0.2,1.9-0.47
c0.62-0.28,1.25-0.41,1.9-0.55c0.49-0.1,1.07-0.35,1.36-0.73c0.86-1.11,2.3-1.27,3.31-2.17c1.14-1.02,1.35-2.58,2.47-3.57
c0.38-0.34,0.84-0.86,0.84-1.3c-0.01-1.28,1.06-2.17,1.11-3.36c0.08-1.86,0.04-3.72,0-5.58c-0.01-0.37-0.27-0.73-0.4-1.1
c-0.18-0.49-0.48-0.98-0.52-1.48c-0.07-0.86-0.45-1.49-1.04-2.07c-0.37-0.37-0.73-0.8-0.92-1.28c-0.63-1.58-1.75-2.61-3.27-3.3
c-0.47-0.21-0.87-0.6-1.3-0.9c-0.43-0.31-0.81-0.73-1.29-0.9c-1.24-0.43-2.49-0.9-3.78-1.05c-1.58-0.18-3.2,0.02-4.79-0.07
c-1.27-0.07-2.14,1.04-3.39,1c-0.37-0.01-0.85,0.15-1.1,0.4c-0.95,0.96-2.19,1.48-3.26,2.24c-0.64,0.45-1.37,1.04-1.61,1.72
C259.77,705.09,258.21,706.07,257.46,707.97z M663.76,734.94c1.37-0.01,2.84-0.03,4.3-0.03c33.06,0,66.13,0,99.19,0
c1.6,0,3.2-0.03,4.8,0c2.16,0.04,4.24-0.32,6.3-0.92c14.01-4.11,21.63-19.6,15.67-33.17c-3.85-8.78-12.44-14.56-21.99-14.72
c-1.6-0.03-3.2-0.01-4.8-0.01c-39.2,0-78.39,0-117.59,0c-1.2,0-2.4,0-3.6-0.01c-0.4-0.01-0.84,0.02-1.18-0.15
c-0.31-0.15-0.66-0.52-0.71-0.83c-0.13-0.78-0.13-1.59-0.13-2.38c-0.01-10.11-0.01-20.22,0-30.33c0-0.66,0.03-1.33,0.06-1.99
c0.02-0.52,0.48-1.04,0.97-1.06c1.33-0.05,2.66-0.11,3.99-0.11c36-0.01,71.99,0,107.99,0c4.53,0,9.07,0.01,13.6-0.01
c1.33-0.01,2.69,0.04,3.99-0.19c10.05-1.77,16.93-7.37,20.31-17.04c1.34-3.84,1.54-7.86,0.7-11.82
c-2.97-13.95-14.73-20.03-24.19-19.93c-1.33,0.01-2.67,0-4,0c-29.6,0-59.19,0-88.79,0c-4.27,0-8.53,0.01-12.8-0.01
c-0.79,0-1.58-0.12-2.31-0.18c-0.71-1.86-2.49-2.9-3.03-4.75c-0.69-0.61-1.36-1.26-2.09-1.82c-0.52-0.4-1.24-0.57-1.68-1.03
c-0.8-0.83-1.79-1.03-2.82-1.33c-1.01-0.29-1.91-0.87-3.04-0.81c-1.2,0.06-2.4,0.01-3.6,0.01c-0.8,0-1.56,0.18-2.32,0.48
c-1.46,0.57-3.1,0.74-4.19,2.1c-0.22,0.28-0.72,0.31-1.06,0.52c-1.14,0.71-2.16,1.55-2.69,2.84c-0.1,0.25-0.13,0.59-0.32,0.72
c-1.7,1.2-1.5,3.32-2.4,4.9c-0.18,0.32-0.2,0.77-0.2,1.16c-0.02,1.6-0.11,3.2,0.04,4.78c0.08,0.89,0.7,1.71,0.86,2.6
c0.17,0.95,0.41,1.8,1.16,2.46c0.92,0.8,1.05,2.09,1.93,2.98c0.97,0.98,2.32,1.29,3.28,2.22c0.19,0.19,0.37,0.41,0.6,0.53
c0.46,0.23,0.95,0.42,1.44,0.59c0.49,0.18,1.01,0.29,1.5,0.47c2.59,0.92,5.22,0.62,7.85,0.24c1.44-0.89,3.42-0.68,4.53-2.28
c0.19-0.28,0.72-0.31,1.08-0.49c0.97-0.5,1.73-1.27,2.31-2.16c0.5-0.77,0.81-1.64,1.46-2.34c0.61-0.65,1.05-1.47,1.64-2.3
c1.42-0.02,2.88-0.06,4.34-0.06c33.46,0,66.93,0,100.39,0c1.07,0,2.13,0.02,3.2,0c2.9-0.05,5.5,0.89,7.89,2.45
c5.15,3.37,7.6,9.93,5.93,15.73c-1.81,6.26-7.09,10.47-13.42,10.67c-0.93,0.03-1.87,0.01-2.8,0.01c-43.2,0-86.39,0-129.59,0
c-1.2,0-2.4-0.01-3.6,0.01c-0.52,0.01-1.04,0.12-1.64,0.19c-0.13,0.56-0.32,1.07-0.35,1.58c-0.06,1.06-0.03,2.13-0.03,3.19
c0,15.7-0.02,31.39,0.01,47.09c0.01,5.52-0.67,4.82,4.93,4.82c36,0.02,71.99,0.01,107.99,0.01c7.33,0,14.67,0,22,0.01
c1.33,0,2.68-0.05,3.99,0.12c9.35,1.2,13.4,8.96,13.01,15.09c-0.44,6.87-5.32,12.77-13.25,13.71c-1.45,0.17-2.93,0.09-4.4,0.09
c-14.4,0.01-28.8,0-43.2,0c-19.06,0-38.13,0-57.19-0.01c-1.46,0-2.93,0.17-4.51-0.23c-0.45-1.79-2.33-2.78-2.82-4.65
c-0.75-0.74-1.34-1.7-2.39-2.06c-0.79-0.27-1.38-0.77-1.97-1.31c-0.29-0.26-0.63-0.59-0.98-0.64c-1.94-0.3-3.65-1.68-5.76-1.2
c-0.51,0.11-1.08,0.1-1.59,0c-1.98-0.39-3.5,1.13-5.35,1.25c-1.23,1.23-2.94,1.78-4.2,2.96c-0.59,0.54-1.12,1.12-1.42,1.9
c-0.19,0.48-0.54,0.89-0.83,1.33c-0.29,0.44-0.78,0.85-0.85,1.31c-0.17,1.21-0.99,2.22-1.09,3.36c-0.17,1.98-0.19,4,0.02,5.97
c0.12,1.13,0.96,2.14,1.04,3.37c0.02,0.33,0.41,0.66,0.67,0.96c0.44,0.5,0.89,0.97,1.1,1.64c0.29,0.94,1.02,1.56,1.73,2.15
c1.11,0.92,2.52,1.41,3.6,2.43c0.42,0.39,1.25,0.34,1.88,0.54c0.75,0.23,1.33,0.92,2.19,0.89c2.62-0.09,5.33,0.52,7.78-0.98
c0.98-0.04,1.77-0.44,2.46-1.15c0.36-0.37,0.84-0.68,1.32-0.85c1.21-0.43,1.98-1.36,2.68-2.31c0.54-0.73,0.8-1.64,1.46-2.34
C662.75,736.67,663.17,735.83,663.76,734.94z M663.5,565.46c1.66-0.03,2.99-0.07,4.32-0.07c33.59-0.01,67.19,0.01,100.78-0.02
c7.82-0.01,15.38,1.36,22.69,4.08c18.66,6.95,33.65,22.74,38.27,42.9c1.23,5.34,1.88,10.75,1.36,16.24
c-0.98,10.4-4.29,19.97-10.26,28.59c-2.21,3.19-4.71,6.12-7.45,8.85c-1.89,1.89-1.84,2.31,0.02,4.16
c3.31,3.3,6.32,6.86,8.75,10.86c7.4,12.12,10.65,25.12,8.26,39.3c-1.37,8.12-4.3,15.61-8.82,22.49
c-11.41,17.34-31.03,27.72-51.65,27.98c-3.86,0.05-7.73,0.01-11.59,0.01c-16.4,0-32.79,0-49.19,0c-14.26,0-28.53,0-42.79-0.01
c-0.79,0-1.61,0-2.38-0.18c-0.89-0.21-0.75-1.31-1.35-1.84c-0.49-0.43-1.04-0.95-1.24-1.54c-0.96-2.86-3.86-3.46-5.82-5.15
c-1.93-0.37-3.7-1.51-5.78-1.18c-0.52,0.08-1.08,0.09-1.6,0c-1.96-0.35-3.55,0.95-5.38,1.17c-0.24,0.03-0.48,0.23-0.67,0.4
c-0.7,0.62-1.42,1.16-2.29,1.57c-1.09,0.51-1.9,1.43-2.53,2.47c-0.34,0.56-0.55,1.26-1.01,1.68c-1.39,1.28-0.94,3.27-1.98,4.66
c0,1.99-0.15,4,0.05,5.97c0.11,1.14,0.95,2.15,1.05,3.37c0.03,0.34,0.41,0.66,0.64,0.97c0.31,0.43,0.76,0.8,0.94,1.27
c0.6,1.6,1.76,2.61,3.21,3.38c0.46,0.25,0.97,0.5,1.31,0.87c0.79,0.85,1.82,0.97,2.84,1.28c1,0.31,1.91,0.86,3.03,0.8
c1.33-0.07,2.67,0.01,4-0.03c0.52-0.02,1.1-0.06,1.54-0.29c1.5-0.77,3.4-0.72,4.53-2.26c0.21-0.28,0.7-0.36,1.06-0.52
c1-0.47,1.73-1.28,2.31-2.15c0.51-0.76,0.8-1.64,1.46-2.34c0.61-0.65,1.06-1.46,1.6-2.22c1.55-0.02,3.01-0.06,4.47-0.06
c29.86,0,59.72,0,89.58,0c2.93,0,5.87-0.1,8.8-0.01c6.3,0.18,12.49-0.56,18.63-1.96c13.6-3.11,25.41-9.5,35.29-19.29
c10.82-10.73,17.49-23.64,19.78-38.76c1.37-9.08,0.76-18.03-1.69-26.86c-2.37-8.56-6.29-16.36-11.68-23.43
c-0.4-0.53-0.82-1.05-1.21-1.59c-0.43-0.6-0.43-1.23,0.01-1.83c1.17-1.62,2.4-3.19,3.52-4.84c6.44-9.41,10.27-19.82,11.45-31.13
c0.91-8.68,0.03-17.25-2.58-25.63c-4.01-12.91-11.18-23.75-21.45-32.55c-8.64-7.4-18.51-12.49-29.53-15.31
c-6.08-1.56-12.26-2.54-18.58-2.42c-3.86,0.07-7.73,0.01-11.6,0.01c-16.53,0-33.06,0-49.59,0c-13.73,0-27.46,0-41.19-0.01
c-0.79,0-1.61,0.01-2.38-0.17c-0.89-0.21-0.75-1.31-1.35-1.84c-0.69-0.62-1.15-1.41-1.6-2.24c-0.49-0.9-1.27-1.74-2.13-2.31
c-0.87-0.58-1.79-1.1-2.67-1.7c-0.84-0.57-1.94-0.78-2.94-1.11c-2.6-0.85-5.22-0.61-7.85-0.19c-0.87,0.72-2.15,0.38-2.96,1.03
c-1.05,0.83-2.23,1.42-3.29,2.21c-0.84,0.63-1.64,1.35-2.04,2.4c-0.18,0.48-0.48,0.95-0.83,1.33c-0.66,0.71-1.03,1.46-1.16,2.45
c-0.11,0.89-0.77,1.72-0.84,2.6c-0.14,1.72-0.14,3.46,0,5.18c0.07,0.88,0.58,1.73,0.87,2.6c0.04,0.12,0.04,0.26,0.05,0.4
c0.02,0.7,0.36,1.24,0.81,1.76c0.43,0.49,0.85,1.04,1.1,1.63c0.37,0.91,1.02,1.57,1.74,2.14c1.13,0.91,2.57,1.39,3.57,2.49
c1.6,0.13,2.89,1.33,4.54,1.31c1.47-0.02,2.93,0.02,4.4-0.02c0.52-0.01,1.1-0.06,1.54-0.29c1.49-0.77,3.4-0.72,4.53-2.26
c0.21-0.28,0.7-0.36,1.06-0.52c1-0.47,1.62-1.35,2.34-2.13C661.15,568.3,662.88,567.35,663.5,565.46z M419.09,574.88
c1.99-0.88,3.25-2.29,4.91-3.14c0.89-0.46,1.64-1.32,2.06-2.37c0.19-0.48,0.45-1,0.83-1.33c1.4-1.25,1.06-3.22,2.01-4.64
c0.01-1.99,0.18-4-0.04-5.97c-0.14-1.26-0.84-2.46-1.14-3.75c-0.05-0.23-0.33-0.41-0.49-0.62c-0.39-0.53-0.88-1.03-1.11-1.62
c-0.42-1.04-1.13-1.95-2.02-2.39c-1.86-0.93-3.22-2.83-5.53-2.88c-1.28-1.01-2.79-0.75-4.24-0.72c-1.57,0.03-3.18-0.3-4.69,0.58
c-0.66,0.38-1.65,0.25-2.25,0.68c-1.07,0.78-2.2,1.45-3.29,2.2c-0.85,0.58-1.66,1.32-2.06,2.38c-0.18,0.48-0.46,0.98-0.83,1.34
c-0.82,0.78-1.3,1.66-1.28,2.8c-0.88,1.42-0.95,2.99-0.87,4.6c0.03,0.66,0.05,1.33,0,1.99c-0.15,1.8,1.13,3.23,1.26,4.95
c1.09,1.01,1.58,2.43,2.46,3.57c0.48,0.62,1.08,1.29,1.78,1.55c1.18,0.45,1.88,1.54,2.97,1.97c1.28,0.5,1.47,1.36,1.44,2.5
c-0.03,1.2,0,2.39,0,3.59c0,34.45,0,68.89,0,103.34c0,1.33-0.02,2.66,0.01,3.99c0.03,0.93,0.05,1.87,0.22,2.78
c0.87,4.84,1.8,9.68,3.15,14.41c3.76,13.17,9.58,25.34,17.77,36.36c6.97,9.38,15.22,17.44,24.95,23.94
c10.41,6.96,21.73,11.74,34.07,14.18c7.64,1.51,15.32,2.16,23.06,1.6c10.44-0.75,20.52-3.11,30.16-7.27
c16.55-7.14,29.97-18.18,40.67-32.58c10.09-13.58,16.46-28.8,19.88-45.3c0.78-3.78,1.16-7.59,1.14-11.48
c-0.09-16.49-0.04-32.98-0.04-49.47c0-18.49,0-36.97,0.01-55.46c0-1.45-0.18-2.92,0.16-4.2c1.18-0.87,2.23-1.65,3.29-2.42
c2.89-0.84,3.6-3.73,5.28-5.69c0.46-0.53,0.28-1.57,0.65-2.23c0.48-0.86,0.68-1.71,0.66-2.65c-0.02-1.2,0.02-2.39-0.02-3.59
c-0.02-0.52-0.01-1.12-0.27-1.55c-0.94-1.57-0.88-3.65-2.48-4.87c-0.32-1.04-0.96-1.86-1.76-2.6c-1.17-1.1-2.85-1.46-3.9-2.71
c-1.6-0.1-2.9-1.27-4.55-1.23c-1.33,0.03-2.67,0-4,0c-0.81,0-1.56,0.1-2.29,0.55c-0.43,0.26-1.02,0.26-1.52,0.43
c-0.49,0.17-1.01,0.34-1.41,0.65c-0.63,0.48-1.2,1.01-1.95,1.35c-1.37,0.62-2.38,1.65-2.95,3.08c-0.14,0.36-0.36,0.73-0.64,0.99
c-0.92,0.85-1.51,1.84-1.47,3.13c-0.96,1.26-0.88,2.73-0.86,4.19c0.01,0.66,0.02,1.33,0,1.99c-0.04,1.08,0.3,2.03,0.69,3.05
c0.47,1.22,0.64,2.53,1.74,3.44c0.29,0.24,0.47,0.65,0.6,1.02c0.47,1.35,1.49,2.19,2.66,2.84c0.7,0.39,1.38,0.77,1.99,1.29
c0.69,0.59,1.48,1.07,2.28,1.64c0.03,1.22,0.09,2.41,0.09,3.6c0.01,8.25,0,16.49,0,24.74c0,26.87,0,53.73,0,80.6
c0,2-0.04,3.98-0.37,5.96c-1.22,7.23-3,14.3-5.66,21.15c-6.41,16.46-16.21,30.47-30.16,41.46c-12.07,9.51-25.65,15.49-40.98,17.55
c-8.15,1.09-16.23,0.86-24.28-0.49c-5.92-0.99-11.67-2.73-17.21-5.07c-14.95-6.33-27.17-16.15-36.92-29.05
c-9.22-12.2-15.13-25.9-18.29-40.8c-0.8-3.77-1.17-7.59-1.17-11.47c0.06-34.58,0.04-69.16,0.04-103.74
C419.09,578.04,419.09,576.58,419.09,574.88z M406.07,454.42c0-1.2-0.01-2.39,0-3.59c0.08-6.41-0.74-12.73-2.09-18.99
c-0.84-3.91-2.17-7.67-3.97-11.26c-1.44-2.87-3.27-5.47-5.46-7.82c-4.1-4.4-9.15-7.26-14.82-9.07
c-6.13-1.96-12.46-2.82-18.88-2.99c-3.19-0.09-6.39,0-9.59-0.02c-6.03-0.04-11.96,0.82-17.8,2.21c-3.91,0.93-7.59,2.49-11.01,4.6
c-2.73,1.69-5.19,3.7-7.29,6.15c-3.34,3.91-5.62,8.38-7.2,13.23c-2.24,6.88-3.23,13.99-3.38,21.19
c-0.11,5.58-0.18,11.16,0.23,16.74c0.38,5.19,1.21,10.3,2.55,15.32c0.92,3.48,2.23,6.82,3.97,9.98
c2.74,4.98,6.37,9.16,11.23,12.21c2.72,1.7,5.6,3.06,8.65,4.08c4.96,1.66,10.09,2.43,15.27,2.9c5.05,0.45,10.13,0.36,15.18,0.16
c5.87-0.23,11.67-1.15,17.28-2.96c10.22-3.28,17.56-9.75,21.83-19.63c2.18-5.04,3.45-10.31,4.24-15.73
C405.86,465.57,406.25,460.02,406.07,454.42z M569.55,474.19C569.55,474.19,569.56,474.19,569.55,474.19
c0-8.91,0.01-17.83,0-26.74c0-3.56-0.1-3.67-3.48-3.68c-12.93-0.01-25.87-0.01-38.8,0c-0.66,0-1.33,0.05-1.99,0.09
c-0.51,0.03-1.06,0.52-1.11,0.97c-0.05,0.53-0.13,1.05-0.13,1.58c-0.01,5.32,0,10.64,0,15.96c0,0.27,0.01,0.53,0.03,0.8
c0.04,0.74,0.5,1.25,1.22,1.28c1.06,0.05,2.13,0.09,3.19,0.09c4.27,0.01,8.53,0,12.8,0.01c0.8,0,1.59,0.07,2.39,0.13
c0.46,0.04,1.03,0.6,1.07,1.03c0.04,0.53,0.09,1.06,0.09,1.59c0.01,5.45,0.01,10.91,0,16.36c0,0.4-0.01,0.8-0.03,1.2
c-0.05,0.94-0.52,1.52-1.41,1.61c-2.65,0.28-5.3,0.56-7.95,0.78c-1.06,0.09-2.14,0.07-3.2-0.01c-2.79-0.22-5.58-0.44-8.36-0.76
c-2.54-0.29-4.96-1.05-7.22-2.21c-2.55-1.31-4.56-3.21-6.01-5.68c-1.22-2.08-2.06-4.31-2.62-6.65
c-1.46-5.99-1.62-12.12-1.68-18.22c-0.04-3.7,0.28-7.43,0.71-11.13c0.34-2.92,0.95-5.78,2.01-8.52c1.62-4.15,4.37-7.29,8.6-8.94
c1.85-0.73,3.75-1.32,5.76-1.59c3.71-0.49,7.41-0.83,11.15-0.72c2.53,0.07,5.08-0.13,7.59,0.11c4.77,0.46,9.58,0.65,14.3,1.57
c1.57,0.31,3.14,0.61,4.71,0.9c0.58,0.11,1.39-0.42,1.57-0.99c0.08-0.25,0.17-0.5,0.22-0.76c0.93-4.56,1.85-9.12,2.77-13.68
c0.26-1.3,0.51-2.61,0.74-3.92c0.21-1.22-0.36-2.04-1.65-2.27c-3.67-0.65-7.33-1.41-11.03-1.84c-7.15-0.85-14.31-1.38-21.53-1.31
c-5.35,0.05-10.66,0.41-15.93,1.24c-3.55,0.56-7.05,1.4-10.43,2.67c-4.79,1.81-9.15,4.31-12.82,7.92
c-4.26,4.19-7.13,9.24-9.03,14.87c-2.32,6.87-3.33,13.97-3.48,21.17c-0.11,5.45-0.16,10.9,0.21,16.35
c0.35,5.06,1.12,10.04,2.33,14.96c0.66,2.71,1.56,5.36,2.7,7.92c3.98,8.99,10.56,15.22,19.86,18.5c2.89,1.02,5.87,1.71,8.9,2.25
c7.81,1.39,15.68,1.21,23.53,0.88c5.58-0.23,11.15-0.87,16.69-1.69c3.96-0.59,7.89-1.25,11.79-2.14c0.52-0.12,1.05-0.23,1.53-0.43
c0.77-0.33,1.35-0.87,1.38-1.78c0.03-1.06,0.04-2.13,0.04-3.19C569.56,491.48,569.55,482.84,569.55,474.19z M547.9,574.86
c0.04,0.73,0.13,1.52,0.13,2.31c0.02,3.19,0.01,6.38,0.01,9.58c0,30.73,0,61.45,0,92.18c0,1.33,0.1,2.68-0.12,3.98
c-0.54,3.14-0.94,6.31-1.86,9.38c-2.75,9.15-7.53,17.01-14.93,23.14c-8.12,6.73-17.47,10.12-28.08,9.44
c-8.65-0.56-16.27-3.83-22.94-9.32c-10.44-8.59-16.97-22.39-16.99-36.45c-0.05-33.39-0.01-66.77-0.01-100.16
c0-0.8-0.03-1.6,0.01-2.39c0.03-0.64,0.15-1.28,0.21-1.77c1.73-1.11,3.32-2.05,4.83-3.12c0.64-0.45,1.29-1.05,1.61-1.74
c0.47-0.98,1.16-1.76,1.72-2.65c0.54-0.85,0.74-1.94,1.04-2.93c0.78-2.6,0.69-5.22,0.07-7.82c-0.15-0.62-0.57-1.18-0.58-1.87
c-0.01-0.55-0.24-1.03-0.63-1.42c-0.67-0.67-1.09-1.47-1.54-2.29c-0.57-1.05-1.43-1.97-2.54-2.44c-0.89-0.38-1.57-0.98-2.29-1.57
c-0.2-0.16-0.44-0.36-0.68-0.39c-1.83-0.21-3.43-1.45-5.39-1.13c-0.78,0.13-1.61,0.11-2.39,0.01c-1.82-0.23-3.26,1.06-4.99,1.17
c-0.22,0.02-0.45,0.27-0.64,0.45c-0.67,0.67-1.48,1.08-2.31,1.53c-1.05,0.57-1.88,1.45-2.47,2.52c-0.25,0.46-0.39,1.08-0.77,1.37
c-1.57,1.15-1.15,3.19-2.16,4.57c-0.17,2.25-0.34,4.52,0.1,6.75c0.17,0.87,0.73,1.67,0.85,2.62c0.05,0.37,0.28,0.75,0.53,1.04
c0.61,0.71,1.13,1.44,1.55,2.29c0.52,1.08,1.41,1.94,2.51,2.46c0.73,0.35,1.38,0.77,1.99,1.29c0.69,0.58,1.45,1.05,2.25,1.62
c0,1.52,0,2.98,0,4.44c0,32.59,0.02,65.17-0.02,97.76c0,3.74,0.28,7.42,0.96,11.1c1.05,5.66,2.69,11.11,5.24,16.27
c5.03,10.17,12.33,18.26,22.24,23.9c9.21,5.24,19.15,7.27,29.65,6.1c9.34-1.04,17.74-4.63,25.15-10.43
c13.31-10.41,21.76-27.15,21.84-45.18c0.15-33.12,0.04-66.24,0.05-99.36c0-1.33,0.01-2.66,0.06-3.99
c0.02-0.53,0.16-1.06,0.75-1.29c1.38-0.54,2.26-1.8,3.66-2.39c1.07-0.45,1.96-1.38,2.4-2.55c0.29-0.78,0.83-1.35,1.36-1.95
c0.26-0.29,0.57-0.63,0.63-0.98c0.29-1.94,1.53-3.69,1.11-5.78c-0.13-0.64-0.08-1.33-0.01-1.99c0.21-1.81-1.09-3.24-1.22-4.95
c-1.16-1.12-1.68-2.68-2.7-3.88c-1.31-1.53-3.26-2.22-4.83-3.42c-1.93-0.35-3.7-1.49-5.79-1.14c-0.91,0.15-1.87-0.01-2.79,0.05
c-1.6,0.11-2.96,1.07-4.51,1.37c-0.86,0.85-1.89,1.43-2.95,2c-0.57,0.3-1.06,0.83-1.45,1.35c-0.86,1.16-1.44,2.5-2.43,3.61
c-0.38,0.42-0.1,1.39-0.44,1.89c-0.83,1.2-0.85,2.48-0.83,3.82c0.02,1.2-0.09,2.41,0.08,3.59c0.12,0.89,0.6,1.73,0.89,2.6
c0.17,0.49,0.14,1.12,0.45,1.49c0.77,0.92,1.4,1.9,1.96,2.97c0.36,0.67,1.01,1.33,1.69,1.66
C544.84,572.59,546.1,574.01,547.9,574.86z M756.45,454.38c0.04,0,0.08,0,0.12,0c0,0.8,0,1.6,0,2.39
c-0.04,6.13,0.34,12.22,1.5,18.25c1.01,5.24,2.41,10.34,4.93,15.1c3.82,7.24,9.49,12.4,17.09,15.46
c6.38,2.57,13.09,3.61,19.91,3.87c8.26,0.32,16.52,0.03,24.72-1.13c3.3-0.46,6.6-0.91,9.82-1.78c0.38-0.1,0.76-0.22,1.09-0.42
c0.2-0.12,0.4-0.41,0.4-0.63c0.03-0.65,0.05-1.33-0.09-1.97c-1.07-5.07-2.18-10.13-3.29-15.2c-0.23-1.03-0.54-2.05-0.81-3.07
c-0.07-0.27-0.67-0.65-0.94-0.62c-0.26,0.03-0.54,0.02-0.79,0.09c-4.38,1.24-8.88,1.69-13.39,2.2c-5.88,0.66-11.7,0.16-17.52-0.53
c-1.98-0.23-3.89-0.88-5.72-1.73c-3.26-1.51-5.72-3.82-7.36-7.02c-0.91-1.79-1.55-3.66-2.08-5.59c-1.02-3.75-1.17-7.61-1.49-11.44
c-0.19-2.25-0.34-4.52-0.24-6.77c0.17-3.84,0.36-7.69,0.79-11.52c0.31-2.79,0.91-5.52,1.92-8.12c1.55-3.99,4.11-7.09,8.15-8.86
c1.73-0.75,3.51-1.34,5.35-1.58c4.35-0.58,8.74-1.14,13.13-0.88c4.78,0.29,9.59,0.44,14.28,1.55c1.55,0.37,3.11,0.74,4.66,1.11
c0.48,0.11,1.12-0.25,1.27-0.72c0.08-0.25,0.17-0.5,0.23-0.76c1.3-5.84,2.59-11.68,3.88-17.52c0.08-0.38,0.09-0.79,0.12-1.18
c0.03-0.45-0.39-1.14-0.79-1.28c-0.37-0.14-0.74-0.3-1.13-0.38c-5.2-1.16-10.47-1.94-15.77-2.51c-5.04-0.53-10.1-0.58-15.16-0.57
c-4.68,0.01-9.32,0.39-13.92,1.15c-3.42,0.57-6.76,1.44-10,2.73c-6.25,2.49-11.28,6.47-14.94,12.13
c-2.48,3.84-4.14,8.03-5.26,12.43c-1.25,4.91-2.1,9.88-2.29,14.95C756.71,447.48,756.58,450.93,756.45,454.38z M166.25,455.51
c0,16.22,0,32.43,0,48.65c0,3.25,0.02,3.27,3.35,3.27c21.58,0.01,43.16,0.01,64.75,0c0.8,0,1.59-0.06,2.39-0.12
c0.48-0.03,0.95-0.55,1-1.06c0.02-0.26,0.08-0.53,0.08-0.79c0.01-5.72,0.01-11.43,0-17.15c0-0.39-0.08-0.79-0.13-1.18
c-0.06-0.43-0.64-0.99-1.1-1.02c-1.19-0.07-2.39-0.14-3.59-0.14c-12.39-0.01-24.78,0-37.17-0.01c-1.2,0-2.4-0.02-3.59-0.06
c-0.89-0.03-1.56-0.64-1.6-1.46c-0.06-1.33-0.09-2.65-0.09-3.98c0-24.06,0-48.12,0-72.18c0-1.06,0.01-2.13-0.02-3.19
c-0.04-1.29-0.64-1.83-2.03-1.9c-0.4-0.02-0.8-0.02-1.2-0.02c-5.99,0-11.99,0-17.98,0c-0.4,0-0.8,0.01-1.2,0.03
c-1.45,0.08-1.73,0.63-1.8,1.65c-0.07,1.06-0.05,2.12-0.05,3.19C166.25,423.88,166.25,439.7,166.25,455.51z M676.57,455.43
c0-15.83,0-31.65,0-47.48c0-1.06-0.01-2.13-0.06-3.19c-0.03-0.81-0.72-1.45-1.56-1.52c-0.27-0.02-0.53-0.05-0.8-0.05
c-6.53,0-13.06,0-19.59,0.01c-0.39,0-0.79,0.1-1.18,0.17c-0.46,0.08-1.03,0.63-1.05,1.08c-0.05,1.19-0.1,2.39-0.1,3.58
c-0.01,31.52,0,63.04,0,94.56c0,0.8-0.01,1.6,0,2.39c0.01,0.4,0.04,0.8,0.07,1.19c0.05,0.63,0.64,1.19,1.33,1.21
c1.07,0.04,2.13,0.05,3.2,0.05c4.13,0.01,8.26,0.01,12.4,0c1.87,0,3.73,0.01,5.6-0.04c1-0.03,1.72-0.29,1.73-1.7
c0.01-1.2,0.02-2.39,0.02-3.59C676.57,486.55,676.57,470.99,676.57,455.43z"/>
<path class="st0" d="M257.46,707.97c0.75-1.89,2.31-2.88,2.9-4.57c0.24-0.68,0.97-1.27,1.61-1.72c1.07-0.76,2.31-1.28,3.26-2.24
c0.25-0.25,0.73-0.41,1.1-0.4c1.25,0.04,2.12-1.07,3.39-1c1.6,0.09,3.21-0.11,4.79,0.07c1.28,0.15,2.54,0.62,3.78,1.05
c0.48,0.16,0.86,0.59,1.29,0.9c0.43,0.31,0.82,0.69,1.3,0.9c1.52,0.69,2.64,1.72,3.27,3.3c0.19,0.47,0.55,0.91,0.92,1.28
c0.58,0.59,0.97,1.21,1.04,2.07c0.04,0.5,0.34,0.99,0.52,1.48c0.14,0.37,0.4,0.73,0.4,1.1c0.04,1.86,0.08,3.72,0,5.58
c-0.05,1.19-1.12,2.08-1.11,3.36c0,0.44-0.46,0.96-0.84,1.3c-1.12,0.99-1.33,2.55-2.47,3.57c-1.01,0.9-2.45,1.06-3.31,2.17
c-0.29,0.38-0.87,0.62-1.36,0.73c-0.65,0.14-1.28,0.27-1.9,0.55c-0.59,0.26-1.26,0.44-1.9,0.47c-1.46,0.07-2.93-0.02-4.4,0.03
c-1.01,0.04-1.73-0.6-2.6-0.88c-0.62-0.2-1.48-0.13-1.87-0.53c-1.24-1.24-3.07-1.61-4.18-3c-0.42-0.52-0.77-1.04-1.11-1.63
c-0.71-1.25-1.58-2.4-2.39-3.59c-1.27-0.56-2.62-0.28-3.93-0.29c-7.2-0.03-14.4-0.02-21.6-0.01c-0.79,0-1.59,0.09-2.37,0.22
c-0.2,0.03-0.47,0.33-0.52,0.55c-0.12,0.51-0.16,1.05-0.16,1.58c-0.01,3.19-0.01,6.38-0.01,9.58c0,15.43,0,30.86,0,46.29
c0,1.19-0.05,2.39-0.1,3.58c-0.02,0.5-0.54,0.98-1.04,1.03c-0.27,0.02-0.53,0.05-0.8,0.06c-0.8,0.01-1.6,0.01-2.4,0.01
c-16.13,0-32.26,0-48.39,0c-0.93,0-1.87-0.01-2.8-0.04c-0.92-0.03-1.38-0.54-1.4-1.56c-0.03-1.06-0.03-2.13-0.03-3.19
c0-72.63,0-145.25,0-217.88c0-1.2,0.02-2.39,0.05-3.59c0.02-0.66,0.58-1.17,1.31-1.2c0.53-0.02,1.07-0.03,1.6-0.03
c17.06,0,34.13,0,51.19,0c0.66,0,1.35-0.01,1.98,0.14c0.29,0.07,0.68,0.51,0.7,0.81c0.1,1.32,0.12,2.65,0.12,3.98
c0.01,21.42,0,42.83,0,64.25c0,11.44,0,22.88,0,34.32c0,1.2,0.01,2.39,0.02,3.59c0.01,1.41,0.77,1.56,1.79,1.59
c1.2,0.03,2.4,0.02,3.6,0.02c27.46,0,54.93,0,82.39,0c1.33,0,2.67,0.01,4,0.04c0.68,0.01,1.2,0.56,1.22,1.3
c0.04,1.33,0.07,2.66,0.07,3.99c0,32.59,0,65.18,0.01,97.77c0,1.46,0.03,2.92,0.07,4.39c0.02,0.73,0.5,1.14,1.3,1.17
c0.67,0.03,1.33,0.04,2,0.04c10,0,20,0,30,0c0.8,0,1.6-0.02,2.4-0.06c0.72-0.03,1.18-0.53,1.2-1.3c0.03-1.06,0.04-2.13,0.04-3.19
c0-66.11,0-132.22,0-198.32c0-1.19-0.06-2.39-0.11-3.58c-0.02-0.47-0.55-0.95-1.08-0.98c-0.66-0.04-1.33-0.08-1.99-0.08
c-10.27,0-20.53,0-30.8,0c-0.67,0-1.34-0.01-1.99,0.1c-0.36,0.06-0.74,0.3-0.83,0.74c-0.08,0.39-0.18,0.78-0.18,1.17
c-0.03,1.2-0.02,2.39-0.02,3.59c0,17.03,0,34.05,0,51.08c0,1.2,0.01,2.39-0.03,3.59c-0.05,1.44-0.68,1.63-1.74,1.66
c-1.07,0.04-2.13,0.02-3.2,0.02c-9.47,0-18.93,0-28.4,0c-1.2,0-2.4,0.02-3.59,0.09c-0.38,0.02-0.79,0.16-0.94,0.6
c-0.55,1.52-2.09,2.42-2.49,4.03c-0.79,0.7-1.56,1.42-2.38,2.09c-0.3,0.24-0.83,0.26-1.05,0.54c-1.07,1.37-2.75,1.47-4.17,2.11
c-0.63,0.28-1.26,0.41-1.93,0.41c-1.2,0-2.4-0.04-3.6,0.02c-1.24,0.06-2.3-0.44-3.43-0.82c-1.01-0.34-2.02-0.51-2.82-1.33
c-0.36-0.37-0.86-0.64-1.34-0.82c-0.93-0.34-1.56-1.04-2.14-1.75c-0.92-1.11-1.34-2.59-2.48-3.56c-0.12-1.73-1.42-3.14-1.28-4.94
c0.05-0.66,0.03-1.33,0-1.99c-0.06-1.6-0.07-3.18,0.86-4.6c0-1.12,0.41-2.04,1.25-2.81c0.38-0.34,0.59-0.87,0.86-1.32
c0.99-1.62,1.36-1.99,3.04-3c0.23-0.14,0.56-0.17,0.7-0.36c1.14-1.51,3.03-1.51,4.55-2.24c0.46-0.22,1.03-0.25,1.55-0.28
c0.93-0.04,1.87,0,2.8-0.02c1.47-0.04,2.89,0.04,4.23,0.83c0.54,0.32,1.46,0.14,1.86,0.54c1.24,1.24,3.07,1.62,4.18,3
c0.25,0.31,0.7,0.55,0.78,0.89c0.42,1.77,2.26,2.61,2.59,4.33c0.52,0.11,0.9,0.26,1.29,0.26c8.13,0.02,16.26,0.02,24.4,0.01
c0.4,0,0.84,0.02,1.18-0.14c0.31-0.15,0.69-0.52,0.71-0.82c0.1-1.32,0.12-2.65,0.12-3.98c0.01-17.29-0.01-34.58,0.01-51.88
c0.01-5.54-0.67-4.91,4.85-4.92c16.13-0.02,32.26-0.01,48.39-0.01c3.92,0,3.71-0.24,3.71,3.64c0,0.67,0,1.33,0,2
c0,72.09,0,144.19,0,216.28c0,1.33,0,2.66-0.03,3.99c-0.02,1-0.5,1.5-1.41,1.54c-0.67,0.03-1.33,0.04-2,0.04
c-16.66,0-33.33,0-49.99-0.01c-0.93,0-1.86-0.08-2.78-0.19c-0.2-0.02-0.46-0.31-0.54-0.53c-0.13-0.37-0.18-0.78-0.19-1.17
c-0.03-1.2-0.02-2.39-0.02-3.59c0-32.72,0-65.44,0-98.16c0-1.33-0.03-2.66-0.06-3.99c-0.01-0.54-0.48-1.05-0.97-1.07
c-1.33-0.06-2.66-0.12-3.99-0.12c-17.46-0.01-34.93-0.01-52.39-0.01c-10.4,0-20.8,0.01-31.2-0.01c-1.19,0-2.38-0.1-3.57-0.2
c-0.2-0.02-0.42-0.28-0.54-0.49c-0.12-0.22-0.17-0.5-0.18-0.76c-0.03-1.33-0.03-2.66-0.03-3.99c0-32.72,0-65.44,0-98.16
c0-1.33-0.04-2.66-0.09-3.98c-0.02-0.51-0.51-1.03-0.99-1.06c-0.66-0.04-1.33-0.1-1.99-0.1c-10.27-0.01-20.53-0.01-30.8,0
c-0.79,0-1.58,0.09-2.36,0.21c-0.2,0.03-0.45,0.31-0.53,0.53c-0.14,0.36-0.21,0.77-0.23,1.16c-0.03,1.06-0.03,2.13-0.03,3.19
c0,65.71-0.01,131.42,0.01,197.12c0,6.1-0.86,5.26,5.26,5.29c9.07,0.04,18.13,0.02,27.2,0c1.2,0,2.39-0.07,3.59-0.13
c0.47-0.02,0.79-0.41,0.82-0.74c0.11-1.32,0.13-2.65,0.13-3.98c0.01-17.16,0-34.32,0-51.48c0-5.72,0-5.99,0.57-6.18
c1.94-0.68,3.95-0.31,5.93-0.32c9.2-0.04,18.4-0.02,27.6-0.02C254.51,707.97,255.84,707.97,257.46,707.97z"/>
<path class="st0" d="M663.76,734.94c-0.6,0.89-1.01,1.74-1.63,2.4c-0.66,0.7-0.92,1.61-1.46,2.34c-0.7,0.95-1.47,1.89-2.68,2.31
c-0.48,0.17-0.96,0.49-1.32,0.85c-0.69,0.71-1.48,1.1-2.46,1.15c-2.44,1.5-5.15,0.89-7.78,0.98c-0.87,0.03-1.44-0.66-2.19-0.89
c-0.63-0.19-1.45-0.14-1.88-0.54c-1.09-1.01-2.49-1.51-3.6-2.43c-0.71-0.59-1.45-1.21-1.73-2.15c-0.2-0.67-0.66-1.14-1.1-1.64
c-0.26-0.3-0.65-0.62-0.67-0.96c-0.08-1.23-0.92-2.24-1.04-3.37c-0.21-1.97-0.2-3.99-0.02-5.97c0.1-1.14,0.92-2.16,1.09-3.36
c0.07-0.47,0.56-0.87,0.85-1.31c0.29-0.44,0.64-0.85,0.83-1.33c0.31-0.78,0.84-1.35,1.42-1.9c1.26-1.18,2.97-1.73,4.2-2.96
c1.85-0.12,3.37-1.64,5.35-1.25c0.51,0.1,1.09,0.11,1.59,0c2.11-0.47,3.82,0.91,5.76,1.2c0.35,0.05,0.69,0.38,0.98,0.64
c0.6,0.53,1.19,1.04,1.97,1.31c1.06,0.36,1.65,1.31,2.39,2.06c0.48,1.87,2.37,2.86,2.82,4.65c1.58,0.4,3.05,0.23,4.51,0.23
c19.06,0.01,38.13,0.01,57.19,0.01c14.4,0,28.8,0,43.2,0c1.47,0,2.95,0.08,4.4-0.09c7.93-0.94,12.81-6.84,13.25-13.71
c0.39-6.13-3.66-13.89-13.01-15.09c-1.31-0.17-2.66-0.11-3.99-0.12c-7.33-0.01-14.67-0.01-22-0.01c-36,0-71.99,0.01-107.99-0.01
c-5.6,0-4.92,0.71-4.93-4.82c-0.02-15.7-0.01-31.39-0.01-47.09c0-1.06-0.02-2.13,0.03-3.19c0.03-0.51,0.22-1.02,0.35-1.58
c0.6-0.07,1.12-0.17,1.64-0.19c1.2-0.03,2.4-0.01,3.6-0.01c43.2,0,86.39,0,129.59,0c0.93,0,1.87,0.02,2.8-0.01
c6.33-0.21,11.61-4.41,13.42-10.67c1.68-5.8-0.78-12.36-5.93-15.73c-2.39-1.56-4.99-2.51-7.89-2.45c-1.07,0.02-2.13,0-3.2,0
c-33.46,0-66.93,0-100.39,0c-1.46,0-2.92,0.04-4.34,0.06c-0.58,0.84-1.02,1.65-1.64,2.3c-0.66,0.7-0.96,1.57-1.46,2.34
c-0.58,0.88-1.34,1.65-2.31,2.16c-0.35,0.18-0.88,0.21-1.08,0.49c-1.11,1.6-3.09,1.39-4.53,2.28c-2.63,0.38-5.25,0.68-7.85-0.24
c-0.49-0.18-1.01-0.29-1.5-0.47c-0.49-0.17-0.98-0.36-1.44-0.59c-0.23-0.12-0.41-0.34-0.6-0.53c-0.96-0.93-2.3-1.23-3.28-2.22
c-0.88-0.89-1.01-2.18-1.93-2.98c-0.75-0.65-0.99-1.5-1.16-2.46c-0.16-0.89-0.77-1.72-0.86-2.6c-0.15-1.58-0.06-3.19-0.04-4.78
c0-0.39,0.02-0.84,0.2-1.16c0.9-1.58,0.7-3.7,2.4-4.9c0.18-0.13,0.22-0.48,0.32-0.72c0.54-1.29,1.55-2.13,2.69-2.84
c0.33-0.21,0.84-0.24,1.06-0.52c1.08-1.36,2.72-1.53,4.19-2.1c0.76-0.3,1.52-0.48,2.32-0.48c1.2-0.01,2.4,0.05,3.6-0.01
c1.12-0.06,2.03,0.52,3.04,0.81c1.03,0.3,2.03,0.51,2.82,1.33c0.44,0.46,1.16,0.63,1.68,1.03c0.73,0.56,1.4,1.21,2.09,1.82
c0.53,1.84,2.31,2.88,3.03,4.75c0.74,0.06,1.52,0.18,2.31,0.18c4.27,0.02,8.53,0.01,12.8,0.01c29.6,0,59.19,0,88.79,0
c1.33,0,2.67,0.02,4,0c9.46-0.1,21.22,5.98,24.19,19.93c0.84,3.96,0.64,7.97-0.7,11.82c-3.38,9.67-10.27,15.27-20.31,17.04
c-1.3,0.23-2.66,0.18-3.99,0.19c-4.53,0.02-9.07,0.01-13.6,0.01c-36,0-71.99,0-107.99,0c-1.33,0-2.66,0.06-3.99,0.11
c-0.48,0.02-0.95,0.54-0.97,1.06c-0.03,0.66-0.06,1.33-0.06,1.99c0,10.11-0.01,20.22,0,30.33c0,0.8,0,1.6,0.13,2.38
c0.05,0.32,0.4,0.68,0.71,0.83c0.33,0.17,0.78,0.14,1.18,0.15c1.2,0.02,2.4,0.01,3.6,0.01c39.2,0,78.39,0,117.59,0
c1.6,0,3.2-0.02,4.8,0.01c9.54,0.17,18.13,5.95,21.99,14.72c5.96,13.58-1.67,29.07-15.67,33.17c-2.06,0.6-4.15,0.96-6.3,0.92
c-1.6-0.03-3.2,0-4.8,0c-33.06,0-66.13,0-99.19,0C666.6,734.91,665.14,734.93,663.76,734.94z"/>
<path class="st0" d="M663.5,565.46c-0.62,1.89-2.34,2.83-2.77,4.58c-0.72,0.77-1.34,1.66-2.34,2.13
c-0.36,0.17-0.86,0.24-1.06,0.52c-1.13,1.54-3.04,1.48-4.53,2.26c-0.45,0.23-1.02,0.28-1.54,0.29c-1.47,0.04-2.93,0-4.4,0.02
c-1.65,0.02-2.94-1.18-4.54-1.31c-1.01-1.1-2.44-1.58-3.57-2.49c-0.72-0.58-1.37-1.24-1.74-2.14c-0.25-0.59-0.67-1.14-1.1-1.63
c-0.45-0.52-0.79-1.06-0.81-1.76c0-0.13-0.01-0.27-0.05-0.4c-0.3-0.87-0.8-1.71-0.87-2.6c-0.14-1.72-0.14-3.46,0-5.18
c0.07-0.88,0.72-1.71,0.84-2.6c0.13-0.99,0.5-1.75,1.16-2.45c0.35-0.38,0.65-0.85,0.83-1.33c0.4-1.05,1.21-1.77,2.04-2.4
c1.05-0.79,2.24-1.38,3.29-2.21c0.82-0.65,2.1-0.31,2.96-1.03c2.63-0.43,5.26-0.66,7.85,0.19c1,0.33,2.09,0.54,2.94,1.11
c0.88,0.6,1.8,1.12,2.67,1.7c0.85,0.57,1.64,1.41,2.13,2.31c0.45,0.83,0.91,1.63,1.6,2.24c0.6,0.53,0.47,1.63,1.35,1.84
c0.76,0.18,1.58,0.17,2.38,0.17c13.73,0.01,27.46,0.01,41.19,0.01c16.53,0,33.06,0,49.59,0c3.87,0,7.73,0.07,11.6-0.01
c6.32-0.12,12.5,0.86,18.58,2.42c11.01,2.82,20.89,7.91,29.53,15.31c10.27,8.8,17.43,19.64,21.45,32.55
c2.6,8.37,3.49,16.94,2.58,25.63c-1.19,11.3-5.02,21.71-11.45,31.13c-1.13,1.65-2.35,3.22-3.52,4.84
c-0.44,0.6-0.44,1.24-0.01,1.83c0.39,0.54,0.81,1.06,1.21,1.59c5.38,7.07,9.31,14.87,11.68,23.43c2.45,8.83,3.06,17.77,1.69,26.86
c-2.29,15.12-8.96,28.03-19.78,38.76c-9.87,9.79-21.69,16.18-35.29,19.29c-6.14,1.4-12.33,2.14-18.63,1.96
c-2.93-0.08-5.86,0.01-8.8,0.01c-29.86,0-59.72,0-89.58,0c-1.46,0-2.92,0.04-4.47,0.06c-0.54,0.77-0.99,1.57-1.6,2.22
c-0.66,0.7-0.95,1.59-1.46,2.34c-0.58,0.87-1.31,1.68-2.31,2.15c-0.36,0.17-0.86,0.24-1.06,0.52c-1.13,1.54-3.03,1.49-4.53,2.26
c-0.45,0.23-1.02,0.28-1.54,0.29c-1.33,0.04-2.67-0.04-4,0.03c-1.12,0.06-2.03-0.5-3.03-0.8c-1.02-0.31-2.05-0.43-2.84-1.28
c-0.35-0.38-0.85-0.63-1.31-0.87c-1.45-0.77-2.61-1.78-3.21-3.38c-0.18-0.47-0.63-0.84-0.94-1.27c-0.23-0.32-0.62-0.63-0.64-0.97
c-0.1-1.22-0.94-2.24-1.05-3.37c-0.19-1.97-0.05-3.98-0.05-5.97c1.03-1.39,0.59-3.39,1.98-4.66c0.46-0.42,0.67-1.12,1.01-1.68
c0.64-1.03,1.45-1.95,2.53-2.47c0.87-0.41,1.59-0.95,2.29-1.57c0.19-0.17,0.44-0.37,0.67-0.4c1.83-0.22,3.42-1.52,5.38-1.17
c0.52,0.09,1.08,0.08,1.6,0c2.08-0.33,3.85,0.81,5.78,1.18c1.96,1.69,4.86,2.29,5.82,5.15c0.2,0.59,0.75,1.1,1.24,1.54
c0.6,0.53,0.46,1.64,1.35,1.84c0.76,0.18,1.58,0.18,2.38,0.18c14.26,0.01,28.53,0.01,42.79,0.01c16.4,0,32.79,0,49.19,0
c3.86,0,7.73,0.04,11.59-0.01c20.61-0.26,40.24-10.63,51.65-27.98c4.52-6.87,7.45-14.37,8.82-22.49
c2.39-14.19-0.87-27.18-8.26-39.3c-2.44-3.99-5.44-7.56-8.75-10.86c-1.86-1.85-1.91-2.27-0.02-4.16c2.74-2.73,5.24-5.66,7.45-8.85
c5.97-8.62,9.28-18.19,10.26-28.59c0.52-5.48-0.13-10.89-1.36-16.24c-4.63-20.16-19.61-35.95-38.27-42.9
c-7.31-2.72-14.87-4.09-22.69-4.08c-33.59,0.03-67.19,0.01-100.78,0.02C666.49,565.4,665.16,565.44,663.5,565.46z"/>
<path class="st0" d="M419.09,574.88c0,1.71,0,3.16,0,4.62c0,34.58,0.03,69.16-0.04,103.74c-0.01,3.89,0.36,7.7,1.17,11.47
c3.16,14.9,9.07,28.6,18.29,40.8c9.75,12.9,21.97,22.72,36.92,29.05c5.54,2.35,11.29,4.08,17.21,5.07
c8.05,1.35,16.13,1.58,24.28,0.49c15.33-2.05,28.91-8.03,40.98-17.55c13.94-10.99,23.75-25,30.16-41.46
c2.66-6.84,4.44-13.92,5.66-21.15c0.34-1.98,0.37-3.97,0.37-5.96c0-26.87,0-53.73,0-80.6c0-8.25,0-16.49,0-24.74
c0-1.19-0.06-2.38-0.09-3.6c-0.81-0.57-1.59-1.05-2.28-1.64c-0.62-0.53-1.29-0.91-1.99-1.29c-1.17-0.65-2.19-1.49-2.66-2.84
c-0.13-0.37-0.31-0.78-0.6-1.02c-1.1-0.91-1.27-2.22-1.74-3.44c-0.39-1.02-0.73-1.96-0.69-3.05c0.03-0.66,0.01-1.33,0-1.99
c-0.02-1.46-0.1-2.93,0.86-4.19c-0.04-1.29,0.55-2.28,1.47-3.13c0.28-0.26,0.5-0.63,0.64-0.99c0.57-1.43,1.57-2.46,2.95-3.08
c0.75-0.34,1.31-0.88,1.95-1.35c0.41-0.3,0.92-0.48,1.41-0.65c0.5-0.17,1.09-0.16,1.52-0.43c0.72-0.45,1.48-0.55,2.29-0.55
c1.33,0,2.67,0.03,4,0c1.64-0.04,2.95,1.14,4.55,1.23c1.05,1.25,2.73,1.61,3.9,2.71c0.79,0.74,1.44,1.55,1.76,2.6
c1.6,1.23,1.54,3.3,2.48,4.87c0.25,0.42,0.25,1.02,0.27,1.55c0.04,1.2,0,2.39,0.02,3.59c0.01,0.95-0.18,1.8-0.66,2.65
c-0.37,0.66-0.19,1.7-0.65,2.23c-1.68,1.96-2.39,4.85-5.28,5.69c-1.05,0.78-2.11,1.55-3.29,2.42c-0.34,1.27-0.16,2.74-0.16,4.2
c-0.01,18.49-0.01,36.97-0.01,55.46c0,16.49-0.05,32.98,0.04,49.47c0.02,3.89-0.36,7.69-1.14,11.48
c-3.42,16.5-9.79,31.72-19.88,45.3c-10.7,14.4-24.12,25.44-40.67,32.58c-9.63,4.16-19.72,6.52-30.16,7.27
c-7.74,0.56-15.43-0.09-23.06-1.6c-12.33-2.44-23.65-7.22-34.07-14.18c-9.73-6.5-17.98-14.57-24.95-23.94
c-8.19-11.02-14.01-23.19-17.77-36.36c-1.35-4.73-2.28-9.57-3.15-14.41c-0.16-0.91-0.19-1.85-0.22-2.78
c-0.04-1.33-0.01-2.66-0.01-3.99c0-34.45,0-68.89,0-103.34c0-1.2-0.03-2.39,0-3.59c0.03-1.14-0.16-2-1.44-2.5
c-1.08-0.43-1.79-1.52-2.97-1.97c-0.69-0.26-1.3-0.93-1.78-1.55c-0.88-1.14-1.37-2.56-2.46-3.57c-0.13-1.72-1.41-3.15-1.26-4.95
c0.05-0.66,0.03-1.33,0-1.99c-0.08-1.61-0.01-3.18,0.87-4.6c-0.02-1.14,0.46-2.02,1.28-2.8c0.37-0.35,0.65-0.85,0.83-1.34
c0.4-1.06,1.21-1.79,2.06-2.38c1.09-0.74,2.22-1.42,3.29-2.2c0.6-0.43,1.59-0.3,2.25-0.68c1.51-0.88,3.12-0.55,4.69-0.58
c1.44-0.03,2.96-0.3,4.24,0.72c2.31,0.05,3.67,1.95,5.53,2.88c0.89,0.45,1.6,1.35,2.02,2.39c0.24,0.59,0.73,1.09,1.11,1.62
c0.16,0.21,0.44,0.39,0.49,0.62c0.29,1.28,1,2.48,1.14,3.75c0.22,1.97,0.04,3.98,0.04,5.97c-0.95,1.42-0.61,3.39-2.01,4.64
c-0.38,0.34-0.64,0.85-0.83,1.33c-0.41,1.04-1.17,1.91-2.06,2.37C422.35,572.59,421.08,574,419.09,574.88z"/>
<path class="st0" d="M406.07,454.42c0.18,5.6-0.22,11.16-1.03,16.7c-0.79,5.41-2.07,10.68-4.24,15.73
c-4.27,9.88-11.62,16.35-21.83,19.63c-5.62,1.81-11.41,2.73-17.28,2.96c-5.05,0.2-10.13,0.29-15.18-0.16
c-5.18-0.47-10.31-1.24-15.27-2.9c-3.05-1.02-5.93-2.37-8.65-4.08c-4.86-3.05-8.49-7.22-11.23-12.21
c-1.74-3.16-3.05-6.5-3.97-9.98c-1.33-5.02-2.17-10.12-2.55-15.32c-0.41-5.58-0.34-11.16-0.23-16.74
c0.14-7.2,1.14-14.31,3.38-21.19c1.58-4.85,3.86-9.32,7.2-13.23c2.1-2.46,4.56-4.46,7.29-6.15c3.42-2.11,7.1-3.68,11.01-4.6
c5.84-1.39,11.77-2.25,17.8-2.21c3.2,0.02,6.39-0.06,9.59,0.02c6.42,0.17,12.74,1.03,18.88,2.99c5.67,1.82,10.72,4.67,14.82,9.07
c2.2,2.36,4.03,4.95,5.46,7.82c1.79,3.58,3.12,7.34,3.97,11.26c1.35,6.25,2.17,12.57,2.09,18.99
C406.06,452.02,406.07,453.22,406.07,454.42z M380.16,455.14c0.02,0,0.04,0,0.06,0c-0.06-2.66-0.19-5.32-0.16-7.97
c0.05-3.75-0.5-7.43-1.1-11.1c-0.3-1.82-0.93-3.59-1.72-5.29c-1.31-2.8-3.42-4.74-6.26-5.85c-1.48-0.58-3.04-1.08-4.6-1.26
c-4.1-0.48-8.21-0.95-12.34-0.79c-2.92,0.11-5.84,0.46-8.76,0.64c-1.62,0.1-3.14,0.55-4.67,1.04c-3.81,1.22-6.36,3.74-7.77,7.46
c-0.86,2.25-1.38,4.59-1.66,6.96c-0.6,5.15-0.68,10.34-0.89,15.51c-0.04,0.93,0.06,1.86,0.11,2.79c0.04,0.93,0.16,1.86,0.13,2.78
c-0.11,3.6,0.32,7.17,0.7,10.74c0.36,3.31,1.16,6.53,2.86,9.47c1.17,2.03,2.76,3.57,4.84,4.61c1.55,0.78,3.17,1.39,4.9,1.66
c3.03,0.47,6.06,0.91,9.14,0.77c1.46-0.06,2.93-0.05,4.4,0c3.08,0.11,6.11-0.27,9.15-0.73c1.47-0.22,2.86-0.68,4.2-1.24
c2.53-1.06,4.55-2.76,5.85-5.21c0.62-1.17,1.17-2.39,1.57-3.65c1.3-4.09,1.7-8.32,1.88-12.58
C380.13,460.99,380.11,458.06,380.16,455.14z"/>
<path class="st0" d="M569.55,474.19c0,8.65,0,17.29,0,25.94c0,1.06-0.01,2.13-0.04,3.19c-0.03,0.91-0.61,1.45-1.38,1.78
c-0.49,0.2-1.01,0.32-1.53,0.43c-3.9,0.89-7.83,1.55-11.79,2.14c-5.54,0.82-11.11,1.46-16.69,1.69
c-7.85,0.32-15.72,0.5-23.53-0.88c-3.03-0.54-6.01-1.23-8.9-2.25c-9.3-3.29-15.88-9.51-19.86-18.5c-1.13-2.56-2.03-5.21-2.7-7.92
c-1.2-4.92-1.98-9.9-2.33-14.96c-0.37-5.45-0.32-10.9-0.21-16.35c0.15-7.2,1.16-14.3,3.48-21.17c1.9-5.63,4.77-10.68,9.03-14.87
c3.67-3.61,8.03-6.11,12.82-7.92c3.38-1.28,6.88-2.11,10.43-2.67c5.27-0.83,10.58-1.19,15.93-1.24
c7.22-0.07,14.38,0.46,21.53,1.31c3.7,0.44,7.36,1.2,11.03,1.84c1.28,0.23,1.86,1.05,1.65,2.27c-0.23,1.31-0.48,2.62-0.74,3.92
c-0.92,4.56-1.84,9.12-2.77,13.68c-0.05,0.26-0.15,0.51-0.22,0.76c-0.17,0.57-0.99,1.1-1.57,0.99c-1.57-0.29-3.14-0.59-4.71-0.9
c-4.72-0.92-9.53-1.11-14.3-1.57c-2.51-0.24-5.06-0.04-7.59-0.11c-3.74-0.11-7.45,0.23-11.15,0.72c-2.01,0.27-3.9,0.86-5.76,1.59
c-4.23,1.66-6.98,4.79-8.6,8.94c-1.07,2.74-1.67,5.59-2.01,8.52c-0.43,3.7-0.75,7.43-0.71,11.13c0.06,6.1,0.22,12.23,1.68,18.22
c0.57,2.34,1.4,4.57,2.62,6.65c1.46,2.47,3.47,4.38,6.01,5.68c2.27,1.16,4.69,1.93,7.22,2.21c2.78,0.31,5.57,0.54,8.36,0.76
c1.06,0.08,2.14,0.1,3.2,0.01c2.65-0.22,5.3-0.5,7.95-0.78c0.89-0.09,1.36-0.67,1.41-1.61c0.02-0.4,0.03-0.8,0.03-1.2
c0-5.45,0-10.91,0-16.36c0-0.53-0.05-1.06-0.09-1.59c-0.04-0.43-0.61-1-1.07-1.03c-0.79-0.06-1.59-0.13-2.39-0.13
c-4.27-0.01-8.53,0-12.8-0.01c-1.06,0-2.13-0.04-3.19-0.09c-0.73-0.04-1.19-0.54-1.22-1.28c-0.01-0.27-0.03-0.53-0.03-0.8
c0-5.32-0.01-10.64,0-15.96c0-0.53,0.07-1.06,0.13-1.58c0.05-0.45,0.59-0.94,1.11-0.97c0.66-0.04,1.33-0.09,1.99-0.09
c12.93,0,25.87-0.01,38.8,0c3.38,0,3.48,0.12,3.48,3.68C569.56,456.36,569.56,465.28,569.55,474.19
C569.56,474.19,569.55,474.19,569.55,474.19z"/>
<path class="st0" d="M547.9,574.86c-1.79-0.85-3.06-2.26-4.74-3.07c-0.68-0.33-1.33-0.98-1.69-1.66
c-0.56-1.06-1.19-2.04-1.96-2.97c-0.31-0.36-0.28-0.99-0.45-1.49c-0.29-0.87-0.76-1.71-0.89-2.6c-0.16-1.18-0.06-2.39-0.08-3.59
c-0.02-1.34,0-2.62,0.83-3.82c0.34-0.5,0.06-1.47,0.44-1.89c0.99-1.1,1.57-2.45,2.43-3.61c0.39-0.53,0.89-1.05,1.45-1.35
c1.06-0.57,2.09-1.15,2.95-2c1.55-0.29,2.91-1.26,4.51-1.37c0.93-0.06,1.89,0.1,2.79-0.05c2.1-0.36,3.86,0.78,5.79,1.14
c1.57,1.19,3.52,1.89,4.83,3.42c1.03,1.2,1.54,2.76,2.7,3.88c0.14,1.72,1.43,3.14,1.22,4.95c-0.07,0.66-0.12,1.35,0.01,1.99
c0.42,2.09-0.83,3.84-1.11,5.78c-0.05,0.35-0.37,0.69-0.63,0.98c-0.53,0.6-1.06,1.16-1.36,1.95c-0.44,1.17-1.33,2.1-2.4,2.55
c-1.4,0.58-2.28,1.85-3.66,2.39c-0.59,0.23-0.73,0.76-0.75,1.29c-0.05,1.33-0.06,2.66-0.06,3.99c0,33.12,0.11,66.24-0.05,99.36
c-0.08,18.03-8.53,34.77-21.84,45.18c-7.41,5.8-15.8,9.39-25.15,10.43c-10.49,1.17-20.44-0.87-29.65-6.1
c-9.91-5.64-17.21-13.73-22.24-23.9c-2.55-5.16-4.19-10.61-5.24-16.27c-0.68-3.68-0.96-7.36-0.96-11.1
c0.04-32.59,0.02-65.18,0.02-97.76c0-1.46,0-2.92,0-4.44c-0.79-0.56-1.56-1.04-2.25-1.62c-0.62-0.52-1.26-0.94-1.99-1.29
c-1.1-0.52-1.98-1.39-2.51-2.46c-0.42-0.86-0.94-1.59-1.55-2.29c-0.25-0.29-0.48-0.68-0.53-1.04c-0.12-0.94-0.68-1.75-0.85-2.62
c-0.44-2.22-0.27-4.49-0.1-6.75c1.01-1.38,0.6-3.41,2.16-4.57c0.38-0.28,0.52-0.91,0.77-1.37c0.59-1.07,1.42-1.95,2.47-2.52
c0.83-0.44,1.64-0.86,2.31-1.53c0.18-0.18,0.41-0.43,0.64-0.45c1.73-0.12,3.17-1.41,4.99-1.17c0.79,0.1,1.61,0.12,2.39-0.01
c1.96-0.32,3.56,0.92,5.39,1.13c0.24,0.03,0.48,0.22,0.68,0.39c0.72,0.58,1.4,1.19,2.29,1.57c1.12,0.48,1.98,1.4,2.54,2.44
c0.45,0.83,0.87,1.63,1.54,2.29c0.39,0.39,0.62,0.87,0.63,1.42c0.01,0.69,0.43,1.25,0.58,1.87c0.63,2.6,0.71,5.22-0.07,7.82
c-0.3,1-0.5,2.08-1.04,2.93c-0.56,0.88-1.26,1.67-1.72,2.65c-0.33,0.68-0.97,1.29-1.61,1.74c-1.51,1.06-3.1,2.01-4.83,3.12
c-0.06,0.48-0.18,1.12-0.21,1.77c-0.04,0.8-0.01,1.6-0.01,2.39c0,33.39-0.03,66.77,0.01,100.16c0.02,14.06,6.55,27.86,16.99,36.45
c6.67,5.49,14.29,8.76,22.94,9.32c10.62,0.68,19.96-2.71,28.08-9.44c7.4-6.13,12.18-13.99,14.93-23.14
c0.92-3.07,1.33-6.24,1.86-9.38c0.22-1.3,0.12-2.65,0.12-3.98c0-30.73,0-61.45,0-92.18c0-3.19,0.01-6.38-0.01-9.58
C548.02,576.37,547.94,575.58,547.9,574.86z"/>
<path class="st0" d="M756.45,454.38c0.13-3.45,0.26-6.9,0.39-10.35c0.19-5.07,1.04-10.04,2.29-14.95
c1.12-4.4,2.78-8.59,5.26-12.43c3.66-5.66,8.68-9.64,14.94-12.13c3.23-1.29,6.58-2.16,10-2.73c4.61-0.76,9.24-1.14,13.92-1.15
c5.07-0.01,10.12,0.03,15.16,0.57c5.3,0.56,10.57,1.34,15.77,2.51c0.38,0.09,0.75,0.25,1.13,0.38c0.4,0.14,0.82,0.84,0.79,1.28
c-0.03,0.4-0.03,0.8-0.12,1.18c-1.28,5.84-2.58,11.68-3.88,17.52c-0.06,0.26-0.15,0.51-0.23,0.76c-0.14,0.46-0.78,0.83-1.27,0.72
c-1.55-0.37-3.1-0.74-4.66-1.11c-4.7-1.11-9.51-1.26-14.28-1.55c-4.39-0.27-8.78,0.3-13.13,0.88c-1.83,0.24-3.62,0.82-5.35,1.58
c-4.04,1.77-6.6,4.87-8.15,8.86c-1.01,2.61-1.61,5.34-1.92,8.12c-0.43,3.83-0.62,7.68-0.79,11.52c-0.1,2.25,0.06,4.52,0.24,6.77
c0.32,3.83,0.46,7.69,1.49,11.44c0.53,1.93,1.16,3.8,2.08,5.59c1.64,3.21,4.11,5.51,7.36,7.02c1.83,0.85,3.74,1.49,5.72,1.73
c5.82,0.68,11.64,1.19,17.52,0.53c4.5-0.51,9.01-0.96,13.39-2.2c0.25-0.07,0.53-0.06,0.79-0.09c0.27-0.03,0.87,0.35,0.94,0.62
c0.27,1.02,0.58,2.04,0.81,3.07c1.11,5.06,2.22,10.13,3.29,15.2c0.13,0.63,0.11,1.31,0.09,1.97c-0.01,0.22-0.21,0.51-0.4,0.63
c-0.33,0.2-0.71,0.32-1.09,0.42c-3.22,0.87-6.53,1.31-9.82,1.78c-8.2,1.16-16.46,1.45-24.72,1.13
c-6.81-0.26-13.52-1.31-19.91-3.87c-7.6-3.06-13.26-8.22-17.09-15.46c-2.52-4.76-3.91-9.86-4.93-15.1
c-1.16-6.03-1.55-12.12-1.5-18.25c0.01-0.8,0-1.6,0-2.39C756.52,454.39,756.49,454.39,756.45,454.38z"/>
<path class="st0" d="M166.25,455.51c0-15.82,0-31.63,0-47.45c0-1.06-0.02-2.13,0.05-3.19c0.07-1.03,0.35-1.57,1.8-1.65
c0.4-0.02,0.8-0.03,1.2-0.03c5.99,0,11.99,0,17.98,0c0.4,0,0.8,0.01,1.2,0.02c1.4,0.07,1.99,0.61,2.03,1.9
c0.03,1.06,0.02,2.13,0.02,3.19c0,24.06,0,48.12,0,72.18c0,1.33,0.03,2.66,0.09,3.98c0.04,0.82,0.71,1.42,1.6,1.46
c1.2,0.05,2.4,0.06,3.59,0.06c12.39,0,24.78,0,37.17,0.01c1.2,0,2.39,0.07,3.59,0.14c0.46,0.03,1.04,0.59,1.1,1.02
c0.06,0.39,0.13,0.79,0.13,1.18c0.01,5.72,0,11.43,0,17.15c0,0.26-0.05,0.53-0.08,0.79c-0.05,0.5-0.52,1.03-1,1.06
c-0.8,0.05-1.59,0.12-2.39,0.12c-21.58,0.01-43.16,0.01-64.75,0c-3.32,0-3.34-0.03-3.35-3.27
C166.25,487.95,166.25,471.73,166.25,455.51z"/>
<path class="st0" d="M676.57,455.43c0,15.56,0,31.12,0,46.68c0,1.2,0,2.39-0.02,3.59c-0.02,1.41-0.74,1.67-1.73,1.7
c-1.86,0.05-3.73,0.04-5.6,0.04c-4.13,0-8.26,0-12.4,0c-1.07,0-2.13-0.02-3.2-0.05c-0.7-0.02-1.29-0.58-1.33-1.21
c-0.03-0.4-0.06-0.79-0.07-1.19c-0.01-0.8,0-1.6,0-2.39c0-31.52,0-63.04,0-94.56c0-1.19,0.05-2.39,0.1-3.58
c0.02-0.44,0.59-1,1.05-1.08c0.39-0.07,0.79-0.17,1.18-0.17c6.53-0.01,13.06-0.01,19.59-0.01c0.27,0,0.53,0.02,0.8,0.05
c0.85,0.07,1.53,0.71,1.56,1.52c0.04,1.06,0.06,2.13,0.06,3.19C676.57,423.78,676.57,439.6,676.57,455.43z"/>
<path class="st1" d="M380.16,455.14c-0.04,2.92-0.02,5.85-0.15,8.77c-0.18,4.25-0.58,8.49-1.88,12.58
c-0.4,1.26-0.95,2.48-1.57,3.65c-1.3,2.44-3.32,4.15-5.85,5.21c-1.34,0.56-2.73,1.01-4.2,1.24c-3.04,0.46-6.07,0.84-9.15,0.73
c-1.46-0.05-2.93-0.06-4.4,0c-3.08,0.13-6.11-0.3-9.14-0.77c-1.73-0.27-3.34-0.89-4.9-1.66c-2.08-1.04-3.67-2.58-4.84-4.61
c-1.69-2.95-2.5-6.17-2.86-9.47c-0.39-3.56-0.81-7.13-0.7-10.74c0.03-0.93-0.09-1.86-0.13-2.78c-0.04-0.93-0.15-1.86-0.11-2.79
c0.21-5.18,0.29-10.36,0.89-15.51c0.28-2.37,0.8-4.71,1.66-6.96c1.41-3.72,3.97-6.24,7.77-7.46c1.52-0.49,3.05-0.94,4.67-1.04
c2.92-0.18,5.84-0.53,8.76-0.64c4.13-0.16,8.25,0.31,12.34,0.79c1.57,0.18,3.13,0.69,4.6,1.26c2.83,1.11,4.95,3.05,6.26,5.85
c0.79,1.7,1.43,3.47,1.72,5.29c0.6,3.67,1.15,7.35,1.1,11.1c-0.03,2.66,0.1,5.31,0.16,7.97
C380.2,455.14,380.18,455.14,380.16,455.14z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 44 KiB

25
interface/src/App.jsx Normal file
View File

@ -0,0 +1,25 @@
import NetworkSettings from "./components/NetworkSettings.jsx";
import NavMenu from "./components/menu/menu.jsx";
import style from "./app.module.scss";
import {Card} from "primereact/card";
function App() {
return (
<div className={style.wrapper}>
<NavMenu/>
<div className={style.centered}>
<div className="c w-11 m-0 flex justify-content-center">
<NetworkSettings/>
</div>
{/*<Card className="c w-11 m-0 flex justify-content-center">*/}
{/* <NetworkSettings/>*/}
{/*</Card>*/}
</div>
</div>
)
}
export default App

View File

@ -0,0 +1,28 @@
* {
font-family: Arial, Helvetica, sans-serif;
letter-spacing: 2px !important;
text-decoration: none;
}
.wrapper {
display: block;
justify-content: center;
align-items: center;
height: 100vh;
position: relative;
}
.nav {
//position: absolute;
width: 100wh;
display: flex;
gap: 15px;
top: 10px;
}
.centered {
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,182 @@
import React, { useState, useEffect } from 'react';
import WiFiScanner from './WiFiScanner';
import {Card} from "primereact/card";
import Ethernet from "./ethernet.jsx";
import Wlan from "./wlan.jsx";
function NetworkSettings() {
return <div className="flex w-full flex-wrap items-center ">
<Card className="m-1 w-6 md:w-2 m-1 flex-grow-1">
<Ethernet/>
</Card>
<Card className="m-1 w-6 md:w-2 m-1 flex-grow-1">
<Wlan/>
</Card>
</div>
}
export default NetworkSettings;
// const [networkStatus, setNetworkStatus] = useState({
// eth0: { name: 'eth0', ip: '', netmask: '', gateway: '' },
// wlan0: { name: 'wlan0', ip: '', netmask: '', gateway: '' },
// });
// const [staticIPConfig, setStaticIPConfig] = useState({
// eth0: { ip: '', netmask: '', gateway: '' },
// wlan0: { ip: '', netmask: '', gateway: '' },
// });
// const [message, setMessage] = useState('');
// const [error, setError] = useState('');
// const [connectedSSID, setConnectedSSID] = useState('');
//
// useEffect(() => {
// fetchNetworkStatus();
// fetchConnectedSSID(); // Получаем SSID при загрузке
// }, []);
//
// const fetchConnectedSSID = async () => {
// try {
// const response = await fetch('/api/wifi/connected-ssid');
// if (!response.ok) {
// throw new Error(`HTTP error! status: ${response.status}`);
// }
// const data = await response.json();
// setConnectedSSID(data.ssid);
// } catch (e) {
// setError(`Ошибка при получении SSID: ${e.message}`);
// }
// }
//
// const fetchNetworkStatus = async () => {
// try {
// const response = await fetch('/api/network/status');
// if (!response.ok) {
// throw new Error(`HTTP error! status: ${response.status}`);
// }
// const data = await response.json();
// const statusMap = {};
// data.forEach(iface => {
// statusMap[iface.name] = iface;
// });
// setNetworkStatus(statusMap);
// } catch (e) {
// setError(`Ошибка при получении статуса сети: ${e.message}`);
// }
// };
//
// const handleStaticIPChange = (iface, field, value) => {
// setStaticIPConfig(prevState => ({
// ...prevState,
// [iface]: { ...prevState[iface], [field]: value },
// }));
// };
//
// const applyStaticIPConfig = async (iface) => {
// setMessage('');
// setError('');
// try {
// const response = await fetch('/api/network/configure', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// interface: iface,
// ip: staticIPConfig[iface].ip,
// netmask: staticIPConfig[iface].netmask,
// gateway: staticIPConfig[iface].gateway,
// }),
// });
// if (!response.ok) {
// const errorData = await response.json();
// throw new Error(`Ошибка применения настроек: ${errorData.error || response.statusText}`);
// }
// setMessage('Статические настройки применены. Сеть может быть перезапущена.');
// fetchNetworkStatus(); // Обновляем статус после применения
// } catch (e) {
// setError(`Ошибка при применении статических настроек: ${e.message}`);
// }
// };
//
// const disconnectWiFi = async () => {
// setMessage('');
// setError('');
// try {
// const response = await fetch('/api/wifi/disconnect', {
// method: 'POST',
// });
// if (!response.ok) {
// const errorData = await response.json();
// throw new Error(`Ошибка отключения: ${errorData.error || response.statusText}`);
// }
// setMessage('Отключение от WiFi инициировано.');
// setConnectedSSID(''); // Очищаем SSID после отключения
// fetchNetworkStatus(); // Обновляем статус сети
// } catch (e) {
// setError(`Ошибка при отключении от WiFi: ${e.message}`);
// }
// };
//
// return (
// <div>
// {error && <div className="error">{error}</div>}
// {message && <div className="message">{message}</div>}
//
// <h2>Статус сети</h2>
// <div>
// <h3>Ethernet (eth0)</h3>
// <p>IP: {networkStatus.eth0?.ip || 'Не назначено'}</p>
// <p>Маска: {networkStatus.eth0?.netmask || 'Не назначено'}</p>
// <p>Шлюз: {networkStatus.eth0?.gateway || 'Не назначено'}</p>
// </div>
// <div>
// <h3>WiFi (wlan0)</h3>
// <p>IP: {networkStatus.wlan0?.ip || 'Не назначено'}</p>
// <p>Маска: {networkStatus.wlan0?.netmask || 'Не назначено'}</p>
// <p>Шлюз: {networkStatus.wlan0?.gateway || 'Не назначено'}</p>
// <p>Подключен к сети: {connectedSSID || 'Не подключено'}</p> {/* Отображаем SSID */}
//
// {connectedSSID &&
// <button onClick={disconnectWiFi}>Отключиться от WiFi</button>} {/* Кнопка отключения */}
// </div>
//
// <h2>Настройка статического IP</h2>
// <div>
// <h3>Ethernet (eth0)</h3>
// <label>IP:</label>
// <input type="text" value={staticIPConfig.eth0.ip}
// onChange={(e) => handleStaticIPChange('eth0', 'ip', e.target.value)}/>
// <label>Маска:</label>
// <input type="text" value={staticIPConfig.eth0.netmask}
// onChange={(e) => handleStaticIPChange('eth0', 'netmask', e.target.value)}/>
// <label>Шлюз:</label>
// <input type="text" value={staticIPConfig.eth0.gateway}
// onChange={(e) => handleStaticIPChange('eth0', 'gateway', e.target.value)}/>
// <button onClick={() => applyStaticIPConfig('eth0')}>Применить eth0</button>
// </div>
//
// <div>
// <h3>WiFi (wlan0)</h3>
// <label>IP:</label>
// <input type="text" value={staticIPConfig.wlan0.ip}
// onChange={(e) => handleStaticIPChange('wlan0', 'ip', e.target.value)}/>
// <label>Маска:</label>
// <input type="text" value={staticIPConfig.wlan0.netmask}
// onChange={(e) => handleStaticIPChange('wlan0', 'netmask', e.target.value)}/>
// <label>Шлюз:</label>
// <input type="text" value={staticIPConfig.wlan0.gateway}
// onChange={(e) => handleStaticIPChange('wlan0', 'gateway', e.target.value)}/>
// <button onClick={() => applyStaticIPConfig('wlan0')}>Применить wlan0</button>
// </div>
//
// <h2>WiFi Сканер и подключение</h2>
// <WiFiScanner onNetworkChange={fetchNetworkStatus} onConnectSuccess={fetchConnectedSSID} />
// </div>
// );

View File

@ -0,0 +1,99 @@
import React, { useState, useEffect } from 'react';
import log from "eslint-plugin-react/lib/util/log.js";
function WiFiScanner({ onNetworkChange }) {
const [wifiNetworks, setWifiNetworks] = useState([]);
const [ssid, setSsid] = useState('');
const [password, setPassword] = useState('');
const [message, setMessage] = useState('');
const [error, setError] = useState('');
const [isScanning, setIsScanning] = useState(false);
const [isConnecting, setIsConnecting] = useState(false);
useEffect(() => {
scanWiFi(); // Сканировать WiFi при загрузке компонента
}, []);
const scanWiFi = async () => {
setIsScanning(true);
setError('');
setMessage('');
try {
const response = await fetch('/api/wifi/scan');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setWifiNetworks(data);
} catch (e) {
setError(`Ошибка сканирования WiFi: ${e.message}`);
} finally {
setIsScanning(false);
}
};
const connectToWiFi = async () => {
setIsConnecting(true);
setError('');
setMessage('');
if (!ssid || !password) {
setError('Введите SSID и пароль.');
setIsConnecting(false);
return;
}
try {
const response = await fetch('/api/wifi/connect', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ ssid, password }),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Ошибка подключения: ${errorData.error || response.statusText}`);
}
setMessage('Идет подключение к WiFi. Подождите...');
onNetworkChange(); // Обновляем статус сети после подключения
onConnectSuccess(); // Вызываем функцию для обновления SSID после успешного подключения
} catch (e) {
setError(`Ошибка подключения к WiFi: ${e.message}`);
} finally {
setIsConnecting(false);
}
};
return (
<div>
<h2>Сканирование WiFi</h2>
{error && <div className="error">{error}</div>}
{message && <div className="message">{message}</div>}
<button onClick={scanWiFi} disabled={isScanning}>
{isScanning ? 'Сканирование...' : 'Сканировать WiFi'}
</button>
{wifiNetworks.length > 0 && (
<ul>
{wifiNetworks.map((network, index) => (
<li key={index}>
{network.ssid} (Сигнал: {network.signal}, Безопасность: {network.security || 'Открытая'})
<button onClick={() => setSsid(network.ssid)}>Выбрать</button>
</li>
))}
</ul>
)}
<h2>Подключение к WiFi</h2>
<label>SSID:</label>
<input type="text" value={ssid} onChange={(e) => setSsid(e.target.value)} />
<label>Пароль:</label>
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
<button onClick={connectToWiFi} disabled={isConnecting}>
{isConnecting ? 'Подключение...' : 'Подключиться'}
</button>
</div>
);
}
export default WiFiScanner;

View File

@ -0,0 +1,260 @@
import {useEffect, useState} from 'react';
import RequestButton from "./ethernet_components/bt_submit.jsx";
import IPAddressInput from "./ethernet_components/ip_addres_input.jsx";
import {ToggleButton} from 'primereact/togglebutton';
import { InputSwitch } from 'primereact/inputswitch';
import IPGatewayInput from "./ethernet_components/ip_gateway_input.jsx";
import { Divider } from 'primereact/divider';
import Info from "./ethernet_components/info.jsx";
export default function Ethernet() {
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 [isRawEditMode, setIsRawEditMode] = useState(false); // Состояние для режима ручного редактирования
const [rawYamlContent, setRawYamlContent] = useState(''); // Состояние для хранения raw YAML
useEffect(() => {
fetchConfig();
fetchRawConfig(); // Загружаем raw YAML при старте, чтобы быть готовыми к режиму raw edit
}, []);
useEffect(() => {
if (dhcpEnabled) {
setAdditionalIps('')
setGateway('')
}
}, [dhcpEnabled]);
const fetchConfig = async () => {
try {
const response = await fetch('/api/eth/netplan');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setDhcpEnabled(data.network.ethernets.eth0.dhcp4 === undefined || data.network.ethernets.eth0.dhcp4 === true); // По умолчанию DHCP включен, если не указано явно false
setAdditionalIps(data.network.ethernets.eth0.addresses ? data.network.ethernets.eth0.addresses.filter(ip => ip !== data.network.ethernets.eth0.addresses[0]).join('\n') : '');
setGateway(data.network.ethernets.eth0.gateway4 || '');
setError(null);
setConfig(data);
} catch (e) {
console.error("Could not fetch netplan config:", e);
setError("Failed to load configuration. Please check the server.");
}
};
const fetchRawConfig = async () => {
try {
const response = await fetch('/api/eth/netplan/raw');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const text = await response.text();
setRawYamlContent(text);
setError(null);
} catch (e) {
console.error("Could not fetch raw netplan config:", e);
setError("Failed to load raw 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 handleRawYamlChange = (e) => {
setRawYamlContent(e.target.value);
};
const handleToggleRawEditMode = () => {
setIsRawEditMode(!isRawEditMode);
if (!isRawEditMode) {
fetchRawConfig(); // Загружаем raw config при переключении в режим raw edit
}
};
const handleSubmit = async () => {
setSuccessMessage('');
setError(null);
if (isRawEditMode) {
// Сохранение в raw режиме
try {
const response = await fetch('/api/eth/netplan/raw', {
method: 'PUT',
headers: {
'Content-Type': 'text/plain', // Важно указать text/plain для raw YAML
},
body: rawYamlContent,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);
}
setSuccessMessage('Raw configuration updated successfully!');
fetchConfig(); // Обновляем и форму после сохранения raw, чтобы данные были синхронизированы
fetchRawConfig(); // Обновляем raw content
} catch (e) {
console.error("Could not update raw netplan config:", e);
setError(`Failed to save raw configuration: ${e.message}`);
}
} else {
// Сохранение через форму
if (!config) return;
let updatedAddresses = [];
if (config.network.ethernets.eth0.addresses && config.network.ethernets.eth0.addresses.length > 0) {
updatedAddresses.push(config.network.ethernets.eth0.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,
ethernets: {
eth0: {
dhcp4: dhcpEnabled,
addresses: updatedAddresses.length > 0 ? updatedAddresses : undefined,
gateway4: gateway || undefined,
nameservers: config.network.ethernets.eth0.nameservers
}
}
}
};
try {
const response = await fetch('/api/eth/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(); // Обновляем отображение конфигурации после успешного сохранения
fetchRawConfig(); // Обновляем raw content, чтобы быть синхронизированными
} 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.ethernets.eth0.dhcp4 === undefined ? 'yes' : (config.network.ethernets.eth0.dhcp4 ? 'yes' : 'no')}\n addresses: ${config.network.ethernets.eth0.addresses ? JSON.stringify(config.network.ethernets.eth0.addresses).replace(/,/g, ', ') : '[]'}\n gateway4: ${config.network.ethernets.eth0.gateway4 || 'null'}\n nameservers:\n addresses: ${JSON.stringify(config.network.ethernets.eth0.nameservers.addresses).replace(/,/g, ', ')}` : '';
return (
<div>
<h2>Настройки ethernet</h2>
<ToggleButton
className={"mb-3"}
checked={isRawEditMode}
onChange={handleToggleRawEditMode}
onLabel="Форма"
offLabel="Режим кода"
/><Divider/>
<Info update={config}/>
<Divider/>
{isRawEditMode ? (
<div>
<p style={{color: 'orange'}}>
<b>Внимание:</b> Вы находитесь в режиме редактирования кода. Будьте осторожны, некорректный YAML
может нарушить работу вашей сетевой конфигурации!
Рекомендуется ознакомиться с <a href="https://netplan.io/reference" target="_blank"
rel="noopener noreferrer">документацией Netplan</a>.
</p>
<label htmlFor="rawYaml">Редактирование YAML конфигурации:</label><br/>
<textarea
id="rawYaml"
value={rawYamlContent}
onChange={handleRawYamlChange}
rows="15"
style={{width: '100%', fontFamily: 'monospace'}}
/>
</div>
) : (
<div>
<div style={{marginBottom: '15px', display: 'flex', alignItems: 'center', gap: '10px'}}>
<label htmlFor="dhcpToggle">DHCP:</label>
<InputSwitch
id="dhcpToggle"
checked={dhcpEnabled}
onChange={handleDhcpChange}
/>
</div>
<Divider/>
<div className={"mb-3"}><label >Постоянный IP: <b>{config.network.ethernets.eth0.addresses[0]}</b></label></div>
{!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>
</>) : (<></>)}
</div>
)}
<Divider/>
<RequestButton handleSubmit={handleSubmit}/>
{successMessage && <div style={{color: 'green', marginTop: '10px'}}>{successMessage}</div>}
{error && <div style={{color: 'red', marginTop: '10px'}}>Error: {error}</div>}
{!isRawEditMode && ( // Показываем YAML только в режиме формы для сравнения
<div>
<h3>Current Configuration (YAML):</h3>
<pre style={{
backgroundColor: '#3a3838',
padding: '10px',
border: '1px solid #ccc',
overflowX: 'auto'
}}>
{yamlString}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,50 @@
import {useRef, useState} from 'react';
import {Button} from 'primereact/button';
import {ProgressSpinner} from 'primereact/progressspinner';
import {Toast} from 'primereact/toast';
export default function RequestButton({handleSubmit}) {
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const toastRef = useRef(null);
const sendRequest = async () => {
setLoading(true);
setSuccess(false);
try {
await handleSubmit()
setTimeout(() => {
setSuccess(false);
}, 2000);
} catch (error) {
showError(error)
} finally {
setLoading(false);
}
};
const showError = (err) => {
if (toastRef.current) {
toastRef.current.show({severity: 'error', summary: 'Error', detail: err, life: 3000});
}
}
return (
<>
<Toast ref={toastRef}/>
<Button
label={"Сохранить"}
icon={loading ? <ProgressSpinner style={{width: '20px', height: '20px', margin: '0 10px 0 0'}}/>
: (success ? 'pi pi-check' : null)
}
iconPos='left'
onClick={sendRequest}
disabled={loading}
/>
</>
);
}

View File

@ -0,0 +1,83 @@
import {useEffect, useState} from "react";
export default function Info({update}){
const [macAddress, setMacAddress] = useState(null);
const [ipAddresses, setIpAddresses] = 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/eth/mac'); // Предполагается, что Go сервер запущен на том же домене или настроен прокси
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/eth/eth0ips');
if (!ipResponse.ok) {
throw new Error(`Failed to fetch IP addresses: ${ipResponse.status} ${ipResponse.statusText}`);
}
const ipData = await ipResponse.json();
setIpAddresses(ipData);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchNetworkInfo();
}, [update]); // Пустой массив зависимостей означает, что useEffect выполняется только один раз при монтировании компонента
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>}
</div>
);
}
return (
<div>
{macAddress && macAddress.mac_address ? (
<p><strong>MAC-адрес (eth0):</strong> {macAddress.mac_address}</p>
) : (
macAddress && macAddress.error ? (
<p><strong>Ошибка получения MAC-адреса:</strong> {macAddress.error}</p>
) : (
<p><strong>MAC-адрес (eth0):</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-адреса (eth0):</strong> Не найдены</p>
)
)}
</div>
);
}

View File

@ -0,0 +1,219 @@
import React, { useState, useCallback, useEffect } from 'react';
import { InputText } from 'primereact/inputtext';
import { InputNumber } from 'primereact/inputnumber';
import {Message} from 'primereact/message';
export default function IPAddressInput({ value, onChange, placeholder = "192.168.0.100/24" }){
const [internalValue, setInternalValue] = useState(value || '');
useEffect(() => {
setInternalValue(value || ''); // Синхронизация internalValue с внешним value
}, [value]);
const isValidIPAddressCIDR = useCallback((ipCidr) => {
if (!ipCidr) {
return true; // Пустое значение валидно
}
const parts = ipCidr.split('/');
if (parts.length !== 2 ) {
return false; // More than one '/' is invalid
}
const ipPart = parts[0];
const cidrPart = parts[1];
// Validate IP part (same as before)
const ipParts = ipPart.split('.');
if (ipParts.length !== 4) {
return false;
}
for (const part of ipParts) {
if (isNaN(part) || part === '') {
return false;
}
const num = Number(part);
if (num < 0 || num > 255) {
return false;
}
}
// Validate CIDR part (if present)
if (cidrPart !== undefined) {
if (isNaN(cidrPart) || cidrPart === '') {
return false;
}
const cidrNum = Number(cidrPart);
if (cidrNum < 0 || cidrNum > 32) {
return false;
}
}
return true;
}, []);
const handleChange = (e) => {
const newValue = e.target.value;
setInternalValue(newValue);
if (onChange) {
if (isValidIPAddressCIDR(newValue)) {
onChange(newValue); // Вызываем onChange только с валидным значением (или пустым)
} else {
onChange(newValue); // Или можно вызывать onChange и с невалидным, если родительский компонент должен знать о процессе ввода
}
}
};
return (
<div className={"bloc"} id={"additionalIps"}>
<InputText
keyfilter={/^[0-9./]*$/}
value={internalValue}
onChange={handleChange}
placeholder={placeholder}
className={(!isValidIPAddressCIDR(internalValue) && internalValue !== '' ? 'p-invalid' : '') + " mr-3"} // Добавляем класс p-invalid при невалидном вводе
/>
{(!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

@ -0,0 +1,67 @@
import {Fragment, useCallback, useEffect, useState} from 'react';
import {InputText} from 'primereact/inputtext';
import {Message} from 'primereact/message';
const IPGatewayInput = ({value, onChange, placeholder = "192.168.0.1"}) => {
const [internalValue, setInternalValue] = useState(value || '');
useEffect(() => {
setInternalValue(value || ''); // Синхронизация internalValue с внешним value
}, [value]);
const isValidIPAddress = useCallback((ip) => {
if (!ip) {
return true; // Пустое значение валидно
}
const parts = ip.split('.');
if (parts.length !== 4) {
return false;
}
for (const part of parts) {
if (isNaN(part) || part === '') {
return false;
}
const num = Number(part);
if (num < 0 || num > 255) {
return false;
}
}
return true;
}, []);
const handleChange = (e) => {
const newValue = e.target.value;
setInternalValue(newValue);
if (onChange) {
if (isValidIPAddress(newValue)) {
onChange(newValue); // Вызываем onChange только с валидным значением (или пустым)
} else {
onChange(newValue); // Можно вызывать onChange и с невалидным, если родительский компонент должен знать о процессе ввода
// Или можно не вызывать onChange при невалидном вводе, если нужно строгая валидация на уровне компонента ввода
}
}
};
return (
<div className={"bloc"}>
<InputText
keyfilter={/^[0-9.]*$/}
value={internalValue}
onChange={handleChange}
placeholder={placeholder}
className={(!isValidIPAddress(internalValue) && internalValue !== '' ? 'p-invalid' : '')+" mr-3"} // Добавляем класс p-invalid при невалидном вводе
/>
{(!isValidIPAddress(internalValue) && internalValue !== '') ?
<Message severity="error" text="Неправильный формат."/>
: <></>
}
</div>
);
};
export default IPGatewayInput;

View File

@ -0,0 +1,28 @@
import {Menubar} from 'primereact/menubar';
import 'primeicons/primeicons.css';
export default function NavMenu() {
let items = [
{
label: 'home',
icon: 'pi pi-home',
url: "/home"
},
];
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

@ -0,0 +1,16 @@
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";
function Wlan() {
const apiUrl = '/api/wlan';
return (
<div className="App">
<h1>Управление Wi-Fi</h1>
</div>
);
}
export default Wlan;

View File

@ -0,0 +1,44 @@
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

@ -0,0 +1,49 @@
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,37 @@
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

@ -0,0 +1,34 @@
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,10 @@
.network-info {
border: 1px solid #ccc;
padding: 1rem;
margin: 1rem 0;
border-radius: 4px;
}
.network-info p {
margin: 0.5rem 0;
}

11
interface/src/main.jsx Normal file
View File

@ -0,0 +1,11 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import "primereact/resources/themes/arya-purple/theme.css";
import 'primeicons/primeicons.css';
import 'primeflex/primeflex.css';
import 'primereact/resources/primereact.css';
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<App />
)

16
interface/vite.config.js Normal file
View File

@ -0,0 +1,16 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://192.168.0.102:8088',
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
plugins: [react()],
})

177
main.go Normal file
View File

@ -0,0 +1,177 @@
package main
import (
"encoding/json"
"log"
"net/http"
"network_configurator/network"
"network_configurator/network/eth"
"network_configurator/network/wlan"
"path/filepath"
"strings"
)
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/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)
port := ":8088"
log.Printf("Сервер запущен на порту %s", port)
log.Fatal(http.ListenAndServe(port, nil))
}

349
network/eth/handler.go Normal file
View File

@ -0,0 +1,349 @@
package eth
import (
"encoding/json"
"fmt"
"gopkg.in/yaml.v3"
"io"
"log"
"net"
"net/http"
"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)
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 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)
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() (*NetplanConfig, error) {
yamlFile, err := os.ReadFile(netplanFilePath)
if err != nil {
return nil, fmt.Errorf("failed to read netplan file: %w", err)
}
var config NetplanConfig
err = yaml.Unmarshal(yamlFile, &config)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal yaml: %w", err)
}
return &config, nil
}
func writeNetplanConfig(config NetplanConfig) 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
}
func RawNetplanHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
getRawNetplanConfig(w, r)
case http.MethodPut:
updateRawNetplanConfig(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func getRawNetplanConfig(w http.ResponseWriter, r *http.Request) {
content, err := readRawNetplanConfig()
if err != nil {
http.Error(w, fmt.Sprintf("Failed to read raw config: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(content))
}
func updateRawNetplanConfig(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to read request body: %v", err), http.StatusBadRequest)
return
}
rawYamlContent := string(body)
// Валидация YAML перед записью
if !isValidYAML(rawYamlContent) {
http.Error(w, "Invalid YAML content", http.StatusBadRequest)
return
}
err = writeRawNetplanConfig(rawYamlContent)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to write raw config: %v", err), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("Raw Netplan configuration updated successfully"))
}
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)
if err != nil {
return fmt.Errorf("failed to write netplan file: %w", err)
}
return nil
}
// isValidYAML проверяет, является ли строка валидным YAML
func isValidYAML(yamlString string) bool {
var temp map[interface{}]interface{}
err := yaml.Unmarshal([]byte(yamlString), &temp)
return err == nil
}
// Функция для создания тестового файла eth.yaml (для демонстрации)
func createTestNetplanFile() {
initialYaml := `
network:
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: yes
addresses: [192.168.8.111/24]
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
`
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()
}
}
// 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{
Error: fmt.Sprintf("Failed to get network interfaces: %v", err),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
json.NewEncoder(w).Encode(response)
return
}
var eth0Interface *net.Interface
for _, iface := range interfaces {
if iface.Name == "eth0" {
eth0Interface = &iface
break
}
}
if eth0Interface == nil {
response := MacAddressResponse{
Error: "Interface eth0 not found",
Interface: "eth0",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound) // 404 Not Found
json.NewEncoder(w).Encode(response)
return
}
macAddress := eth0Interface.HardwareAddr.String()
response := MacAddressResponse{
MacAddress: macAddress,
Interface: "eth0",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
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{
Error: fmt.Sprintf("Failed to get network interfaces: %v", err),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
json.NewEncoder(w).Encode(response)
return
}
var eth0Interface *net.Interface
for _, iface := range interfaces {
if iface.Name == "eth0" {
eth0Interface = &iface
break
}
}
if eth0Interface == nil {
response := InterfaceIPsResponse{
Interface: "eth0",
Error: "Interface eth0 not found",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound) // 404 Not Found
json.NewEncoder(w).Encode(response)
return
}
addrs, err := eth0Interface.Addrs()
if err != nil {
response := InterfaceIPsResponse{
Interface: "eth0",
Error: fmt.Sprintf("Failed to get addresses for eth0: %v", err),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
json.NewEncoder(w).Encode(response)
return
}
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
ipAddresses = append(ipAddresses, ipnet.IP.String())
}
}
}
response := InterfaceIPsResponse{
Interface: "eth0",
IPAddresses: ipAddresses,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) // 200 OK
json.NewEncoder(w).Encode(response)
}

333
network/network.go Normal file
View File

@ -0,0 +1,333 @@
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
}

169
network/wlan/handler.go Normal file
View File

@ -0,0 +1,169 @@
package wlan
import (
"encoding/json"
"fmt"
"net"
"net/http"
"os/exec"
"regexp"
"strings"
)
type ApiResponse struct {
Status string `json:"status"`
Error string `json:"error,omitempty"`
}
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()
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
}
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)
}
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)
}

21
wifi.yaml Normal file
View File

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