Compare commits
9 commits
release/0.
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c4546fec60 | |||
| 0ee3d21d7c | |||
| 98cfd6da94 | |||
| d7656bd261 | |||
| a7e7676897 | |||
| 936fc6ba2d | |||
| 47d8b2125b | |||
| 0af7608d0c | |||
| 8043eef048 |
|
|
@ -1,136 +1,20 @@
|
||||||
|
when:
|
||||||
|
event: manual
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
checkout:
|
checkout:
|
||||||
image: debian:latest
|
image: debian:latest
|
||||||
environment:
|
environment:
|
||||||
GONETS:
|
FORGEJO_TOKEN:
|
||||||
from_secret: GONETS
|
from_secret: forgejo_token
|
||||||
commands:
|
commands:
|
||||||
# ПОДГОТОВКА ГИТА И КЛОНИРОВАНИЕ GONETS
|
# ПОДГОТОВКА ГИТА И КЛОНИРОВАНИЕ GONETS
|
||||||
- apt update
|
- apt update
|
||||||
- apt install -y git git-lfs
|
- apt install -y git git-lfs
|
||||||
- git lfs install
|
- git lfs install
|
||||||
|
- git config --global user.email "s.galanskiy@gossys.dev"
|
||||||
# УСТАНОВКА НЕОБХОДИМЫХ ЗАВИСИМОСТЕЙ НА СИСТЕМЕ
|
- git config --global user.name "s.galanskiy"
|
||||||
- apt update
|
- touch file
|
||||||
- apt install -y python3 curl findutils grep sed openssh-server tree libnss3 libnss3-dev libnspr4 libnspr4-dev libasound2 libasound2-dev python3.11-venv python3-pip python3-poetry nodejs npm debhelper devscripts build-essential fakeroot dh-make dpkg-dev cmake libgirepository1.0-dev gobject-introspection libcairo2-dev pkg-config python3-dev libdbus-1-dev
|
- git add .
|
||||||
- python3 --version
|
- git commit -m "commit"
|
||||||
- node -v
|
- git push https://$FORGEJO_TOKEN@dev.gossys.dev/s.galanskiy/gonets.git
|
||||||
- npm -v
|
|
||||||
|
|
||||||
# ПОДГОТОВКА СПИСКА PYTHON ЗАВИСИМОСТЕЙ GONETS
|
|
||||||
- rm -rf /usr/lib/python3.11/EXTERNALLY-MANAGED
|
|
||||||
- rm -rf /root/.local/share/pypoetry
|
|
||||||
- rm -rf /root/.poetry
|
|
||||||
- rm -rf /usr/bin/poetry
|
|
||||||
- pip install pipx
|
|
||||||
- pipx ensurepath
|
|
||||||
- pipx install poetry
|
|
||||||
- echo 'export PATH="$HOME/.local/bin:$PATH"' >> /root/.bashrc
|
|
||||||
- . /root/.bashrc
|
|
||||||
- poetry --version
|
|
||||||
- cd SRV
|
|
||||||
- poetry env activate
|
|
||||||
- poetry add poetry-plugin-export
|
|
||||||
- poetry install
|
|
||||||
- poetry self update
|
|
||||||
- rm -f requirements.txt
|
|
||||||
- poetry run poetry export -f requirements.txt --output requirements.txt --without-hashes
|
|
||||||
|
|
||||||
# ПОДГОТОВКА И СБОРКА FRONTEND ЧАСТИ GONETS
|
|
||||||
- pwd
|
|
||||||
- cd ..
|
|
||||||
- pwd
|
|
||||||
- cd FRONT/DEV
|
|
||||||
- pwd
|
|
||||||
- npm install
|
|
||||||
- npm run package
|
|
||||||
- cd dist/gonets-linux-x64/resources/app
|
|
||||||
- pwd
|
|
||||||
- npm run build
|
|
||||||
- sed -i 's#href="/assets/#href="./assets/#g' dist/index.html
|
|
||||||
- sed -i 's#src="/assets/#src="./assets/#g' dist/index.html
|
|
||||||
- cd ../../../../..
|
|
||||||
- pwd
|
|
||||||
- rm -rf BUILD/*
|
|
||||||
- cp -r DEV/dist/gonets-linux-x64/* BUILD/
|
|
||||||
- rm -rf DEV
|
|
||||||
|
|
||||||
# ИНИЦИАЛИЗАЦИЯ DEBIAN КОНФИГУРАЦИЙ ДЛЯ СБОРКИ
|
|
||||||
- cd ..
|
|
||||||
- pwd
|
|
||||||
- cd ..
|
|
||||||
- pwd
|
|
||||||
- mv gonets gonets-0.1
|
|
||||||
- cd gonets-0.1
|
|
||||||
- pwd
|
|
||||||
- ls
|
|
||||||
- DEBEMAIL="devteam@gossys.dev" DEBFULLNAME="GOSSYS LLC" dh_make --single -y --createorig
|
|
||||||
- cd debian/
|
|
||||||
- rm -f *.ex
|
|
||||||
- rm -f README.*
|
|
||||||
|
|
||||||
# КЛОНИРОВАНИЕ НЕОБХОДИМЫХ ШАБЛОНОВ КОНФИГОВ ДЕБИАНА ДЛЯ СБОРКИ
|
|
||||||
- mkdir -p /woodpecker/src/dev.gossys.dev/s.galanskiy/templates
|
|
||||||
- git clone https://s.galanskiy:${GONETS}@dev.gossys.dev/s.galanskiy/gonets-debian-template.git /woodpecker/src/dev.gossys.dev/s.galanskiy/templates
|
|
||||||
- mv /woodpecker/src/dev.gossys.dev/s.galanskiy/templates/changelog /woodpecker/src/dev.gossys.dev/s.galanskiy/gonets-0.1/debian/
|
|
||||||
- mv /woodpecker/src/dev.gossys.dev/s.galanskiy/templates/control /woodpecker/src/dev.gossys.dev/s.galanskiy/gonets-0.1/debian/
|
|
||||||
- mv /woodpecker/src/dev.gossys.dev/s.galanskiy/templates/copyright /woodpecker/src/dev.gossys.dev/s.galanskiy/gonets-0.1/debian/
|
|
||||||
- mv /woodpecker/src/dev.gossys.dev/s.galanskiy/templates/install /woodpecker/src/dev.gossys.dev/s.galanskiy/gonets-0.1/debian/
|
|
||||||
- mv /woodpecker/src/dev.gossys.dev/s.galanskiy/templates/postinst /woodpecker/src/dev.gossys.dev/s.galanskiy/gonets-0.1/debian/
|
|
||||||
- mv /woodpecker/src/dev.gossys.dev/s.galanskiy/templates/postrm /woodpecker/src/dev.gossys.dev/s.galanskiy/gonets-0.1/debian/
|
|
||||||
- mv /woodpecker/src/dev.gossys.dev/s.galanskiy/templates/rules /woodpecker/src/dev.gossys.dev/s.galanskiy/gonets-0.1/debian/
|
|
||||||
|
|
||||||
# СБОРКА ДЕБИАН ПАКЕТА
|
|
||||||
- cd ..
|
|
||||||
- pwd
|
|
||||||
- dpkg-buildpackage -us -uc -b
|
|
||||||
|
|
||||||
# ИЗВЛЕЧЕНИЕ ВЕРСИИ ПАКЕТА ДЛЯ КОММИТА
|
|
||||||
- VERSION=$(dpkg-parsechangelog -l./debian/changelog --show-field Version)
|
|
||||||
- echo "VERSION=$VERSION" > version.env
|
|
||||||
|
|
||||||
# КОММИТ И ТЕГ НА ВЕТКУ DEVELOP
|
|
||||||
- . ./version.env
|
|
||||||
- git config user.name "s.galanskiy"
|
|
||||||
- git config user.email "s.galanskiy@gossys.dev"
|
|
||||||
- git fetch --all --prune
|
|
||||||
- git branch -r
|
|
||||||
- git checkout -b "release/$VERSION"
|
|
||||||
- git commit -am "Релиз $VERSION"
|
|
||||||
- git tag -f "v$VERSION"
|
|
||||||
- git push "https://s.galanskiy:b4785198d18200dfed812dc4299575f736255bd7@dev.gossys.dev/s.galanskiy/gonets.git" "release/$VERSION"
|
|
||||||
- git push -f https://s.galanskiy:b4785198d18200dfed812dc4299575f736255bd7@dev.gossys.dev/s.galanskiy/gonets.git "v$VERSION"
|
|
||||||
|
|
||||||
# СОЗДАНИЕ И ЗАГРУЗКА РЕЛИЗА ЧЕРЕЗ API
|
|
||||||
- |
|
|
||||||
VERSION=$(dpkg-parsechangelog -l./debian/changelog --show-field Version)
|
|
||||||
echo "VERSION=$VERSION" > version.env
|
|
||||||
|
|
||||||
# Задать переменные
|
|
||||||
. ./version.env
|
|
||||||
TAG="v$VERSION"
|
|
||||||
|
|
||||||
DEB_PATH=$(find .. -maxdepth 1 -type f -name "gonets_*_amd64.deb" | head -n 1)
|
|
||||||
|
|
||||||
if [ ! -f "$DEB_PATH" ]; then
|
|
||||||
echo "❌ .deb файл не найден по пути: $DEB_PATH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
RELEASE_JSON=$(curl -s -X POST "https://dev.gossys.dev/api/v1/repos/s.galanskiy/gonets/releases" \
|
|
||||||
-H "Authorization: token b4785198d18200dfed812dc4299575f736255bd7" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"tag_name\": \"$TAG\", \"name\": \"Релиз $TAG\", \"body\": \"Сборка .deb пакета для версии $TAG\", \"draft\": false, \"prerelease\": true}")
|
|
||||||
|
|
||||||
RELEASE_ID=$(echo "$RELEASE_JSON" | grep '"id":' | head -n1 | sed 's/[^0-9]*\([0-9]*\).*/\1/')
|
|
||||||
|
|
||||||
if [ -z "$RELEASE_ID" ]; then
|
|
||||||
echo "❌ Не удалось получить ID релиза из ответа:"
|
|
||||||
echo "$RELEASE_JSON"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
curl -s -X POST \
|
|
||||||
-H "Authorization: token b4785198d18200dfed812dc4299575f736255bd7" \
|
|
||||||
-F "attachment=@$DEB_PATH" \
|
|
||||||
"https://dev.gossys.dev/api/v1/repos/s.galanskiy/gonets/releases/$RELEASE_ID/assets" || echo "❌ Ошибка загрузки файла"
|
|
||||||
13
FRONT/BUILD/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>GoNets</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/assets/icon-4fEsvohP.png" />
|
||||||
|
<script type="module" crossorigin src="/assets/index-QSWEVNb0.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index-DWFkiel1.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import axios, { AxiosResponse } from "axios";
|
import axios, { AxiosResponse } from "axios";
|
||||||
|
|
||||||
const axiosWithoutCredentials = axios.create({
|
const axiosWithoutCredentials = axios.create({
|
||||||
baseURL: `http://localhost:8001`,
|
baseURL: `https://localhost:8001`,
|
||||||
})
|
})
|
||||||
|
|
||||||
export class NetworkService {
|
export class NetworkService {
|
||||||
static createAxiosInstance(ipAddress: string) {
|
static createAxiosInstance(ipAddress: string) {
|
||||||
return axios.create({
|
return axios.create({
|
||||||
baseURL: `http://${ipAddress}:8001`,
|
baseURL: `https://${ipAddress}:8001`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ const DeviceCard: React.FC<DeviceCardProps> = ({ device, status }) => {
|
||||||
<CardContent isExpanded={isExpanded}>
|
<CardContent isExpanded={isExpanded}>
|
||||||
<InfoRow>
|
<InfoRow>
|
||||||
<Label>Имя:</Label>
|
<Label>Имя:</Label>
|
||||||
<Value>{device.hostname || device.ip}</Value>
|
<Value>{device.hostname || 'Неизвестно'}</Value>
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
|
|
||||||
<InfoRow>
|
<InfoRow>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {useState} from 'react';
|
import React from 'react';
|
||||||
import { DeviceUser } from '../../../store/useDeviceStore';
|
import { DeviceUser } from '../../../store/useDeviceStore';
|
||||||
|
|
||||||
interface TableScannerProps {
|
interface TableScannerProps {
|
||||||
|
|
@ -9,19 +9,18 @@ import {IconComponent} from "../../../const/buttons/icons";
|
||||||
import {buttonNames} from "../../../const/buttons/names";
|
import {buttonNames} from "../../../const/buttons/names";
|
||||||
import {ModalMap} from "../../modalwindows/windowsType";
|
import {ModalMap} from "../../modalwindows/windowsType";
|
||||||
import {useModalContext} from "../../../store";
|
import {useModalContext} from "../../../store";
|
||||||
import Pagination from "../Pagination";
|
|
||||||
|
|
||||||
const TableContainer = styled.div`
|
const TableContainer = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||||
|
background: ${({ theme }) => theme.primary};
|
||||||
margin: 24px 0;
|
margin: 24px 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTable = styled.table`
|
const StyledTable = styled.table`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: separate;
|
border-collapse: separate;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
|
||||||
background: ${({ theme }) => theme.primary};
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TableHeaderCell = styled.th<{ sortable?: boolean }>`
|
const TableHeaderCell = styled.th<{ sortable?: boolean }>`
|
||||||
|
|
@ -29,7 +28,7 @@ const TableHeaderCell = styled.th<{ sortable?: boolean }>`
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: ${({ theme }) => theme.textColor};
|
color: ${({ theme }) => theme.textSecondary};
|
||||||
background: ${({ theme }) => theme.primary};
|
background: ${({ theme }) => theme.primary};
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.borderColor};
|
border-bottom: 1px solid ${({ theme }) => theme.borderColor};
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
@ -48,11 +47,6 @@ const TableCell = styled.td`
|
||||||
color: ${({ theme }) => theme.text};
|
color: ${({ theme }) => theme.text};
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.borderColor};
|
border-bottom: 1px solid ${({ theme }) => theme.borderColor};
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
button{
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StatusBadge = styled.span<{ status:string }>`
|
const StatusBadge = styled.span<{ status:string }>`
|
||||||
|
|
@ -74,23 +68,7 @@ const TableScanner = ({devices}:TableScannerProps) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Пагинация
|
return (
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
const [devicesPerPage] = useState(10);
|
|
||||||
|
|
||||||
const indexOfLastDevice = currentPage * devicesPerPage;
|
|
||||||
const indexOfFirstDevice = indexOfLastDevice - devicesPerPage;
|
|
||||||
const currentDevices = devices.slice(indexOfFirstDevice, indexOfLastDevice);
|
|
||||||
const totalPages = Math.ceil(devices.length / devicesPerPage);
|
|
||||||
|
|
||||||
const handlePrevPage = () => {
|
|
||||||
if(currentPage > 1) setCurrentPage(currentPage - 1);
|
|
||||||
}
|
|
||||||
const handleNextPage = () => {
|
|
||||||
if(currentPage < totalPages) setCurrentPage(currentPage + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<StyledTable>
|
<StyledTable>
|
||||||
<thead>
|
<thead>
|
||||||
|
|
@ -106,30 +84,24 @@ const TableScanner = ({devices}:TableScannerProps) => {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{currentDevices.map((item, i) => (
|
{devices.map((item, i) => (
|
||||||
<TableRow key={i}>
|
<TableRow key={i}>
|
||||||
<TableCell><StatusBadge status={item.status}></StatusBadge></TableCell>
|
<TableCell><StatusBadge status={item.status}></StatusBadge></TableCell>
|
||||||
<TableCell>{item.hostname || item.ip}</TableCell>
|
<TableCell>{item.hostname || 'Неизвестен'}</TableCell>
|
||||||
<TableCell>{item.vendor}</TableCell>
|
<TableCell>{item.vendor}</TableCell>
|
||||||
<TableCell>{item.ip}</TableCell>
|
<TableCell>{item.ip}</TableCell>
|
||||||
<TableCell>{item.open_ports.join(', ') || '-'}</TableCell>
|
<TableCell>{item.open_ports.join(', ') || '-'}</TableCell>
|
||||||
<TableCell>{item.last_seen}</TableCell>
|
<TableCell>{item.last_seen}</TableCell>
|
||||||
<TableCell>{item.note || '-'}</TableCell>
|
<TableCell>{item.note || '-'}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<button onClick={(e)=>openModalNote(e, item)}>
|
<span onClick={(e)=>openModalNote(e, item)}>
|
||||||
<IconComponent name={buttonNames[16]} width="26" height="26" />
|
<IconComponent name={buttonNames[16]} width="26" height="26" />
|
||||||
</button>
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</StyledTable>
|
</StyledTable>
|
||||||
<Pagination
|
|
||||||
currentPage={currentPage}
|
|
||||||
totalPages={totalPages}
|
|
||||||
onPrevPage={handlePrevPage}
|
|
||||||
onNextPage={handleNextPage}
|
|
||||||
/>
|
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,8 @@
|
||||||
import { MainButton } from "../../../UI/buttons/mainButton"
|
import { MainButton } from "../../../UI/buttons/mainButton"
|
||||||
import { workSpaceNames } from "../../../../const/workspace/names"
|
import { workSpaceNames } from "../../../../const/workspace/names"
|
||||||
import { useWorkSpaceStore } from "../../../../store"
|
import { useWorkSpaceStore } from "../../../../store"
|
||||||
import styled, {useTheme} from "styled-components"
|
import styled from "styled-components"
|
||||||
import ThemeButton from "../../../UI/buttons/toggletheme"
|
import ThemeButton from "../../../UI/buttons/toggletheme"
|
||||||
import {buttonNames} from "../../../../const/buttons/names";
|
|
||||||
import {IconComponent} from "../../../../const/buttons/icons";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
from: 'reg' | 'helloWindow' | 'workSpace'
|
from: 'reg' | 'helloWindow' | 'workSpace'
|
||||||
|
|
@ -31,41 +29,23 @@ const Content = styled.div`
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 40px 50px;
|
padding: 25px 50px;
|
||||||
background: ${(props => props.theme.primary)};
|
background: ${(props => props.theme.secondary)};
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
position: relative;
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const Listing = styled.div`
|
const Listing = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: ${(props => props.theme.secondary)};
|
gap: 15px;
|
||||||
border-radius: 10px;
|
|
||||||
padding: 24px 20px;
|
|
||||||
p{
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const List = styled.ul`
|
const List = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: 10px 0;
|
gap: 15px;
|
||||||
padding: 0;
|
margin-left: 10px;
|
||||||
list-style-type: none;
|
|
||||||
font-size: 18px;
|
|
||||||
gap: 4px;
|
|
||||||
& li:before {
|
|
||||||
color: ${(props => props.theme.accent)};
|
|
||||||
content: "•";
|
|
||||||
font-size: 28px;
|
|
||||||
width: .8em;
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
top: 2px;
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const Title = styled.div`
|
const Title = styled.div`
|
||||||
|
|
@ -75,55 +55,43 @@ const Title = styled.div`
|
||||||
const SubTitle = styled.div`
|
const SubTitle = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: 10px;
|
||||||
background-color:rgba(255, 248, 225, 0.6);
|
|
||||||
border-left: #ff9800 solid 4px;
|
|
||||||
border-radius: 0 8px 8px 0;
|
|
||||||
padding: 20px 12px;
|
|
||||||
|
|
||||||
a{
|
|
||||||
text-decoration: none;
|
|
||||||
color: ${(props => props.theme.errorColor)};
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 18px;
|
|
||||||
width: max-content;
|
|
||||||
}
|
|
||||||
p{
|
p{
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: ${(props=>props.theme.textColor)};
|
|
||||||
}
|
|
||||||
.icon{
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
fill: #ff9800;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const Links = styled.div`
|
const Links = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: start;
|
flex-direction: column;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const LinkBlock = styled.a`
|
const LinkColumn = styled.div`
|
||||||
text-decoration: none;
|
display: flex;
|
||||||
color: ${(props => props.theme.accent)};;
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
const LinkBlock = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Copyright = styled.div`
|
const Copyright = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: start;
|
text-align: center;
|
||||||
color: ${(props => props.theme.offlineColor)};
|
margin-top: 50px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ButtonContainer = styled.div`
|
const ButtonContainer = styled.div`
|
||||||
position: absolute;
|
|
||||||
top: 12px;
|
|
||||||
right: 14px;
|
|
||||||
`
|
|
||||||
const AboutLine = styled.hr`
|
|
||||||
border: ${(props => props.theme.hoverBtn)} 1px solid;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justifi-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
`
|
`
|
||||||
|
|
||||||
const MainTitle = styled.div`
|
const MainTitle = styled.div`
|
||||||
|
|
@ -131,33 +99,24 @@ const MainTitle = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
padding-top: 10px;
|
padding-top: 50px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
img{
|
img{
|
||||||
width: 50px;
|
width: 40px;
|
||||||
height: 50px;
|
height: 40px;
|
||||||
}
|
|
||||||
.version{
|
|
||||||
text-wrap: nowrap;
|
|
||||||
background-color: ${(props => props.theme.hoverBtn)};
|
|
||||||
color: ${(props => props.theme.borderColor)};
|
|
||||||
border-radius: 20px;
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 10px 18px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.title{
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 24px;
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
const Text = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 30px;
|
||||||
|
`
|
||||||
|
|
||||||
export default function About({ from, onBack }: Props) {
|
export default function About({ from, onBack }: Props) {
|
||||||
const { setWorkModule } = useWorkSpaceStore()
|
const { setWorkModule } = useWorkSpaceStore()
|
||||||
|
|
||||||
const getBackHandler = () => {
|
const getBackHandler = () => {
|
||||||
switch (from) {
|
switch (from) {
|
||||||
case 'reg':
|
case 'reg':
|
||||||
|
|
@ -179,63 +138,68 @@ export default function About({ from, onBack }: Props) {
|
||||||
<Content>
|
<Content>
|
||||||
|
|
||||||
<MainTitle>
|
<MainTitle>
|
||||||
<div className={'title'}>
|
<img src="./assets/images/gossys.png" alt="" />
|
||||||
<img src="./assets/images/gossys.png" alt="" />
|
<div style={{width: '100%', display: "flex", justifyContent: 'center'}}><h1>О приложении</h1></div>
|
||||||
<div style={{width: '100%', display: "flex", justifyContent: 'center'}}><h1>О приложении</h1></div>
|
|
||||||
</div>
|
|
||||||
<p className={'version'}>v 0.0.3</p>
|
|
||||||
</MainTitle>
|
</MainTitle>
|
||||||
|
|
||||||
<AboutLine/>
|
|
||||||
|
|
||||||
<Title>
|
<Text>
|
||||||
<p>ГоNets — минимально работоспособный кроссплатформенный продукт (MVP) для демонстрации нашего подхода к управлению настройками ПК в корпоративной среде.</p>
|
|
||||||
</Title>
|
|
||||||
|
|
||||||
<Listing>
|
<Title>
|
||||||
<p>Возможности:</p>
|
<p>Приложение ГоNets является минимально работоспособным кроссплатформенным продуктом (MVP) для демонстрации нашего подхода к управлению настройками ПК в корпоративной среде.</p>
|
||||||
<List>
|
</Title>
|
||||||
<li>Визуализация активных сетевых ресурсов</li>
|
|
||||||
<li>Настройка параметров локальных и удалённых сетевых ресурсов</li>
|
|
||||||
<li>Управление взаимодействием ресурсов с Клиентом ГoNets</li>
|
|
||||||
</List>
|
|
||||||
</Listing>
|
|
||||||
|
|
||||||
<SubTitle>
|
|
||||||
<svg className="icon" viewBox="0 0 24 24">
|
|
||||||
<path d="M12 2L1 21h22M12 6l7.53 13H4.47M11 10v4h2v-4m-2 6v2h2v-2"/>
|
|
||||||
</svg>
|
|
||||||
<p>Программное обеспечение исключительно для ознакомления и домашнего использования.</p>
|
|
||||||
<a href={'https://gossys.dev/pages/projects/gonets'}>Перейти на ГоNets-PRO</a>
|
|
||||||
</SubTitle>
|
|
||||||
|
|
||||||
<Links>
|
<Listing>
|
||||||
<LinkBlock href={'https://gossys.dev/pages/projects/gonets'}>
|
<p>Данное приложение умеет:</p>
|
||||||
Домашняя страницa
|
<List>
|
||||||
</LinkBlock>
|
<p>- Отображать в графическом виде активные сетевые ресурсы</p>
|
||||||
|
<p>- Настраивать сетевые параметры локального и удаленных сетевых ресурсов</p>
|
||||||
|
<p>- Отображать и изменять взаимодействие сетевых ресурсов с установленным Клиентом ГоNets</p>
|
||||||
|
</List>
|
||||||
|
</Listing>
|
||||||
|
|
||||||
<LinkBlock href={'https://wiki.gossys.dev/ru/gonets'}>
|
<SubTitle>
|
||||||
Документация
|
<p>Программное обеспечение исключительно для ознакомления и домашнего использования.</p>
|
||||||
</LinkBlock>
|
<p>Для коммерческого использования приобретите версию ГоNets-PRO (Подробная информация на странице продукта)</p>
|
||||||
|
</SubTitle>
|
||||||
|
|
||||||
<LinkBlock href={'https://wiki.gossys.dev/ru/lic/GosysPUB'}>
|
<Links>
|
||||||
Лицензия
|
<LinkBlock>
|
||||||
</LinkBlock>
|
<p>Ссылка на домашнюю страницу:</p>
|
||||||
</Links>
|
<a href="https://gossys.dev/pages/projects/gonets">https://gossys.dev/pages/projects/gonets</a>
|
||||||
|
</LinkBlock>
|
||||||
|
|
||||||
|
<LinkBlock>
|
||||||
|
<p>Инструкции и информация:</p>
|
||||||
|
<a href="https://wiki.gossys.dev/ru/gonets">https://wiki.gossys.dev/ru/gonets</a>
|
||||||
|
</LinkBlock>
|
||||||
|
|
||||||
|
<LinkColumn>
|
||||||
|
<p>Распространяется под лицензией GosysPUB текущей версии.</p>
|
||||||
|
<LinkBlock>
|
||||||
|
<p>Полный текст актуальной лицензии:</p>
|
||||||
|
<a href="https://wiki.gossys.dev/ru/lic/GosysPUB">https://wiki.gossys.dev/ru/lic/GosysPUB</a>
|
||||||
|
</LinkBlock>
|
||||||
|
</LinkColumn>
|
||||||
|
|
||||||
|
</Links>
|
||||||
|
|
||||||
|
<Copyright>
|
||||||
|
<p>Разработано и поддерживается ООО «Гибридные Офисные Системы» 2024-2025 г. Все права защищены.</p>
|
||||||
|
</Copyright>
|
||||||
|
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Copyright>
|
|
||||||
Разработано и поддерживается ООО «Гибридные Офисные Системы» 2024-2025 г. Все права защищены.
|
|
||||||
</Copyright>
|
|
||||||
|
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
{showBackButton && backHandler && (
|
{showBackButton && backHandler && (
|
||||||
<IconComponent
|
<MainButton
|
||||||
width="36"
|
title="Назад"
|
||||||
height="36"
|
|
||||||
name={buttonNames[12]}
|
|
||||||
onClick={backHandler}
|
onClick={backHandler}
|
||||||
|
style={{ marginRight: '10px' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{from == 'reg' &&
|
{from == 'reg' &&
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import DeviceCard from "../../../../../UI/Dropdown/DeviceCard";
|
import DeviceCard from "../../../../../UI/Dropdown/DeviceCard";
|
||||||
import {DeviceUser} from "../../../../../../store/useDeviceStore";
|
import {DeviceUser} from "../../../../../../store/useDeviceStore";
|
||||||
|
|
@ -28,15 +28,10 @@ const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps
|
||||||
const activeDevices = devices.filter(device => device.status === 'online');
|
const activeDevices = devices.filter(device => device.status === 'online');
|
||||||
const inactiveDevices = devices.filter(device => device.status !== 'online');
|
const inactiveDevices = devices.filter(device => device.status !== 'online');
|
||||||
|
|
||||||
// Пагинация
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
const [devicesPerPage] = useState(10);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScanBtn onClick={handleScan} disabled={loading}>
|
<ScanBtn onClick={handleScan} disabled={loading}>
|
||||||
{devices.length > 0 ? 'Обновить список' : 'Сканировать узлы'}
|
{devices.length > 0 ? 'Обновить список' : 'Сканировать сеть'}
|
||||||
</ScanBtn>
|
</ScanBtn>
|
||||||
|
|
||||||
{devices.length > 0 ? (
|
{devices.length > 0 ? (
|
||||||
|
|
@ -47,7 +42,7 @@ const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps
|
||||||
}}>
|
}}>
|
||||||
{/* Колонка активных устройств */}
|
{/* Колонка активных устройств */}
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<h3 style={{ color: '#4CAF50', marginBottom:'12px' }}>Активные узлы ({activeDevices.length})</h3>
|
<h3 style={{ color: '#4CAF50', marginBottom:'12px' }}>Активные устройства ({activeDevices.length})</h3>
|
||||||
{activeDevices.map((device, index) => (
|
{activeDevices.map((device, index) => (
|
||||||
<DeviceCard
|
<DeviceCard
|
||||||
key={index}
|
key={index}
|
||||||
|
|
@ -58,7 +53,7 @@ const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps
|
||||||
</div>
|
</div>
|
||||||
{/* Колонка неактивных устройств */}
|
{/* Колонка неактивных устройств */}
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<h3 style={{ color: '#ababab', marginBottom:'12px' }}>Неактивные узлы ({inactiveDevices.length})</h3>
|
<h3 style={{ color: '#ababab', marginBottom:'12px' }}>Неактивные устройства ({inactiveDevices.length})</h3>
|
||||||
{inactiveDevices.map((device, index) => (
|
{inactiveDevices.map((device, index) => (
|
||||||
<DeviceCard
|
<DeviceCard
|
||||||
key={index}
|
key={index}
|
||||||
|
|
@ -69,7 +64,7 @@ const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : scanInitiated ? (
|
) : scanInitiated ? (
|
||||||
<p style={{ marginTop: '20px' }}>Узлы не найдены</p>
|
<p style={{ marginTop: '20px' }}>Устройства не найдены</p>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,13 @@ const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScanBtn onClick={handleScan} disabled={loading}>
|
<ScanBtn onClick={handleScan} disabled={loading}>
|
||||||
{devices.length > 0 ? 'Обновить список' : 'Сканировать узлы'}
|
{devices.length > 0 ? 'Обновить список' : 'Сканировать сеть'}
|
||||||
</ScanBtn>
|
</ScanBtn>
|
||||||
|
|
||||||
{devices.length > 0 ? (
|
{devices.length > 0 ? (
|
||||||
<TableScanner devices={devices} />
|
<TableScanner devices={devices} />
|
||||||
) : scanInitiated ? (
|
) : scanInitiated ? (
|
||||||
<p style={{ marginTop: '20px' }}>Узлы не найдены</p>
|
<p style={{ marginTop: '20px' }}>Устройства не найдены</p>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||||
span {
|
span {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: ${(props) => props.theme.scannerColor};
|
background: ${(props) => props.theme.accent};
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
|
@ -163,7 +163,7 @@ export default function UserScanner() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper scanInitiated={scanInitiated} loading={loading}>
|
<Wrapper scanInitiated={scanInitiated} loading={loading}>
|
||||||
{(!scanInitiated || loading) && (<TitleWrapper>Сканирование узлов подсети</TitleWrapper>)}
|
{(!scanInitiated || loading) && (<TitleWrapper>Сканирование пользователей в сети</TitleWrapper>)}
|
||||||
<UserLocate loading={loading} scanInitiated={scanInitiated}>
|
<UserLocate loading={loading} scanInitiated={scanInitiated}>
|
||||||
<span className="center"></span>
|
<span className="center"></span>
|
||||||
<span className="ring1"></span>
|
<span className="ring1"></span>
|
||||||
|
|
@ -177,7 +177,7 @@ export default function UserScanner() {
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<LoadingText>
|
<LoadingText>
|
||||||
Сканирование узлов <ScanningText />
|
Сканирование сети<ScanningText />
|
||||||
</LoadingText>
|
</LoadingText>
|
||||||
) : selectedView.value == 1 ? (
|
) : selectedView.value == 1 ? (
|
||||||
<TableUsers loading={loading} handleScan={handleScan} devices={devices} scanInitiated={scanInitiated} />
|
<TableUsers loading={loading} handleScan={handleScan} devices={devices} scanInitiated={scanInitiated} />
|
||||||
|
|
|
||||||
|
|
@ -136,8 +136,7 @@ const ScanBtn = styled.button<{scanInitiated: boolean}>`
|
||||||
`
|
`
|
||||||
|
|
||||||
export function WifiScanner() {
|
export function WifiScanner() {
|
||||||
//const { ipAddress } = useIpStore();
|
const { ipAddress } = useIpStore();
|
||||||
const ipAddress = '192.168.1.147'
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [scanInitiated, setScanInitiated] = useState(false);
|
const [scanInitiated, setScanInitiated] = useState(false);
|
||||||
const { wifiNetworks, setWifiNetworks } = useNetworkStore();
|
const { wifiNetworks, setWifiNetworks } = useNetworkStore();
|
||||||
|
|
@ -162,19 +161,17 @@ export function WifiScanner() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper scanInitiated={scanInitiated} loading={loading}>
|
<Wrapper scanInitiated={scanInitiated} loading={loading}>
|
||||||
<TitleWrapper>Сканирование точек доступа Wi-Fi сети</TitleWrapper>
|
<TitleWrapper>Сканирование Wi-Fi сети</TitleWrapper>
|
||||||
|
|
||||||
<UserLocate loading={loading} scanInitiated={scanInitiated}>
|
<UserLocate loading={loading} scanInitiated={scanInitiated}>
|
||||||
<span className="center circel"></span>
|
<span className="center circel"></span>
|
||||||
<span className="ring1 circel"></span>
|
<span className="ring1 circel"></span>
|
||||||
<span className="ring2 circel"></span>
|
<span className="ring2 circel"></span>
|
||||||
<span className="ring3 circel"></span>
|
<span className="ring3 circel"></span>
|
||||||
{wifiNetworks.map((network, index) => (
|
{wifiNetworks.map((network) => (
|
||||||
<WifiDot
|
<WifiDot
|
||||||
key={network.bssid}
|
key={network.bssid}
|
||||||
network={network}
|
network={network}
|
||||||
index={index}
|
|
||||||
total={wifiNetworks.length}
|
|
||||||
onSelect={setCurrentNetwork}
|
onSelect={setCurrentNetwork}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
@ -189,11 +186,11 @@ export function WifiScanner() {
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<LoadingText>
|
<LoadingText>
|
||||||
Сканирование точек доступа <ScanningText/>
|
Сканирование сети <ScanningText/>
|
||||||
</LoadingText>
|
</LoadingText>
|
||||||
) : (
|
) : (
|
||||||
<ScanBtn onClick={handleScan} disabled={loading} scanInitiated={wifiNetworks.length > 0 }>
|
<ScanBtn onClick={handleScan} disabled={loading} scanInitiated={wifiNetworks.length > 0 }>
|
||||||
{wifiNetworks.length > 0 ? 'Обновить список' : 'Сканировать точки доступа'}
|
{wifiNetworks.length > 0 ? 'Обновить список' : 'Сканировать сеть'}
|
||||||
</ScanBtn>
|
</ScanBtn>
|
||||||
)}
|
)}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|
|
||||||
|
|
@ -31,45 +31,43 @@ const Dot = styled.div<{ top: string; left: string }>`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const getPositionInRing = (index: number, total: number, ringSize: number) => {
|
const WifiDot = ({ network, onSelect }: { network: WifiNetworkScanner, onSelect: (n: WifiNetworkScanner) => void }) => {
|
||||||
const angle = (index * (2 * Math.PI / total)) + (Math.PI / total * (index % 2));
|
const getPositionInRing = (bssid: string, ringSize: number) => {
|
||||||
const innerRadius = ringSize * 0.4;
|
const hash = bssid.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||||
const outerRadius = ringSize * 0.5;
|
const randomAngle = (hash % 360) * (Math.PI / 180);
|
||||||
const radius = innerRadius + (outerRadius - innerRadius) * (0.3 + 0.7 * (index % 10) / 10);
|
const innerRadius = ringSize * 0.4;
|
||||||
|
const outerRadius = ringSize * 0.5;
|
||||||
|
const randomValue = (hash % 1000) / 1000;
|
||||||
|
const randomRadius = Math.sqrt(innerRadius * innerRadius + randomValue * (outerRadius * outerRadius - innerRadius * innerRadius));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: Math.cos(angle) * radius,
|
x: Math.cos(randomAngle) * randomRadius,
|
||||||
y: Math.sin(angle) * radius
|
y: Math.sin(randomAngle) * randomRadius
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const WifiDot = ({ network, index, total, onSelect }: {
|
const getPosition = (signal: number, bssid: string) => {
|
||||||
network: WifiNetworkScanner,
|
|
||||||
index: number,
|
|
||||||
total: number,
|
|
||||||
onSelect: (n: WifiNetworkScanner) => void
|
|
||||||
}) => {
|
|
||||||
const getPosition = (signal: number, index: number, total: number) => {
|
|
||||||
let strength: 1 | 2 | 3;
|
let strength: 1 | 2 | 3;
|
||||||
if (signal >= -50) {
|
if (signal >= -50) {
|
||||||
strength = 3;
|
strength = 3;
|
||||||
} else if (signal >= -70) {
|
} else if (signal >= -70) {
|
||||||
strength = 2;
|
strength = 2;
|
||||||
|
} else if (signal >= -90) {
|
||||||
|
strength = 1;
|
||||||
} else {
|
} else {
|
||||||
strength = 1;
|
strength = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ringSizes = { 1: 550, 2: 400, 3: 250 };
|
const ringSizes = { 1: 550, 2: 400, 3: 250 };
|
||||||
const ringSize = ringSizes[strength];
|
const ringSize = ringSizes[strength];
|
||||||
const offset = getPositionInRing(index, total, ringSize);
|
const offset = getPositionInRing(bssid, ringSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
top: `calc(50% + ${offset.y}px)`,
|
top: `calc(50% - ${offset.y}px)`,
|
||||||
left: `calc(50% + ${offset.x}px)`
|
left: `calc(50% - ${offset.x}px)`
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const position = getPosition(network.signal, index, total);
|
const position = getPosition(network.signal, network.bssid);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dot top={position.top} left={position.left} onClick={() => onSelect(network)}>
|
<Dot top={position.top} left={position.left} onClick={() => onSelect(network)}>
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
|
||||||
setHasInited(false)
|
setHasInited(false)
|
||||||
initializationAttempt.current = 0;
|
initializationAttempt.current = 0;
|
||||||
|
|
||||||
const newSocket = new WebSocket(`ws://${ws}:8001/api/v1`);
|
const newSocket = new WebSocket(`wss://${ws}:8001/api/v1`);
|
||||||
|
|
||||||
let connectionTimeout;
|
let connectionTimeout;
|
||||||
setSocket(newSocket);
|
setSocket(newSocket);
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ export const DevicesBar = () => {
|
||||||
|
|
||||||
const checkWebSocketAvailability = async (ip: string): Promise<boolean> => {
|
const checkWebSocketAvailability = async (ip: string): Promise<boolean> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const testSocket = new WebSocket(`ws://${ip}:8001/api/v1`);
|
const testSocket = new WebSocket(`wss://${ip}:8001/api/v1`);
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
testSocket.close();
|
testSocket.close();
|
||||||
resolve(false);
|
resolve(false);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import settings from '@iconify/icons-ic/baseline-settings';
|
||||||
import info from '@iconify/icons-ic/baseline-info';
|
import info from '@iconify/icons-ic/baseline-info';
|
||||||
import arrowDown from '@iconify/icons-ic/baseline-keyboard-arrow-down';
|
import arrowDown from '@iconify/icons-ic/baseline-keyboard-arrow-down';
|
||||||
import arrowRight from '@iconify/icons-ic/baseline-keyboard-arrow-right';
|
import arrowRight from '@iconify/icons-ic/baseline-keyboard-arrow-right';
|
||||||
import arrowLeft from '@iconify/icons-ic/baseline-keyboard-arrow-left';
|
|
||||||
import edit from '@iconify/icons-ic/baseline-edit';
|
import edit from '@iconify/icons-ic/baseline-edit';
|
||||||
import checkCircle from '@iconify/icons-ic/round-check-circle';
|
import checkCircle from '@iconify/icons-ic/round-check-circle';
|
||||||
import cancel from '@iconify/icons-ic/baseline-cancel';
|
import cancel from '@iconify/icons-ic/baseline-cancel';
|
||||||
|
|
@ -38,7 +37,6 @@ export const ICONS = {
|
||||||
[buttonNames[14]]: userSearch,
|
[buttonNames[14]]: userSearch,
|
||||||
[buttonNames[15]]: wifiFind,
|
[buttonNames[15]]: wifiFind,
|
||||||
[buttonNames[16]]: noteEdit,
|
[buttonNames[16]]: noteEdit,
|
||||||
[buttonNames[17]]: arrowLeft,
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type IconName = keyof typeof ICONS;
|
type IconName = keyof typeof ICONS;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,5 @@ export const buttonNames: names ={
|
||||||
13: 'noAdmin',
|
13: 'noAdmin',
|
||||||
14: 'userSearch',
|
14: 'userSearch',
|
||||||
15: 'wifiFind',
|
15: 'wifiFind',
|
||||||
16: 'noteEdit',
|
16: 'noteEdit'
|
||||||
17: 'arrowLeft',
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ import { toolBarNames } from "./names"
|
||||||
import { useTheme } from "styled-components"
|
import { useTheme } from "styled-components"
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
|
|
||||||
import desktop from '@iconify/icons-ic/baseline-menu';
|
import desktop from '@iconify/icons-ic/round-desktop-windows';
|
||||||
|
|
||||||
export const ICONS = {
|
export const ICONS = {
|
||||||
[toolBarNames[1]]: desktop,
|
[toolBarNames[1]]: desktop,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export const dark = {
|
||||||
background: '#3A3A3A',
|
background: '#3A3A3A',
|
||||||
textColor: '#FFFFFF',
|
textColor: '#FFFFFF',
|
||||||
borderColor: '#b6b6b6',
|
borderColor: '#b6b6b6',
|
||||||
scannerColor: '#00E5FF',
|
scannerColor: '#0E9C9D',
|
||||||
offlineColor: '#656565',
|
offlineColor: '#656565',
|
||||||
hoverBtn: '#4a4a4a',
|
hoverBtn: '#4a4a4a',
|
||||||
};
|
};
|
||||||
2
FRONT/DEV/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/node_modules
|
||||||
|
/dist
|
||||||
BIN
FRONT/DEV/assets/icon.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
28
FRONT/DEV/eslint.config.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ['dist'] },
|
||||||
|
{
|
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
12
FRONT/DEV/index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>GoNets</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="assets/icon.png" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="./index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
52
FRONT/DEV/index.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { HashRouter } from 'react-router-dom';
|
||||||
|
import { useIpStore } from './src/store';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import {useCheckAdmin} from './src/utils/checkAdmin.ts'
|
||||||
|
|
||||||
|
import {
|
||||||
|
ModalProvider,
|
||||||
|
useModalContext,
|
||||||
|
ThemeProvider
|
||||||
|
} from './src/store';
|
||||||
|
|
||||||
|
import WindowComponent from './src/components/modalwindows/modalWindow.tsx';
|
||||||
|
import Router from './src/router.tsx';
|
||||||
|
|
||||||
|
const MainComponent = () => {
|
||||||
|
|
||||||
|
const checkAdmin = useCheckAdmin()
|
||||||
|
const {ipAddress} = useIpStore()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkAdminStatus = async () => {
|
||||||
|
await checkAdmin()
|
||||||
|
}
|
||||||
|
checkAdminStatus()
|
||||||
|
}, [ipAddress])
|
||||||
|
|
||||||
|
|
||||||
|
const { isOpen, modalType, modalData } = useModalContext();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WindowComponent isOpen={isOpen} type={modalType} data={modalData} />
|
||||||
|
<Router />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const container = document.getElementById('root');
|
||||||
|
if (!container) {
|
||||||
|
throw new Error('Root element with id "root" not found');
|
||||||
|
}
|
||||||
|
const root = createRoot(container);
|
||||||
|
root.render(
|
||||||
|
<ThemeProvider>
|
||||||
|
<HashRouter>
|
||||||
|
<ModalProvider>
|
||||||
|
<MainComponent />
|
||||||
|
</ModalProvider>
|
||||||
|
</HashRouter>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
61
FRONT/DEV/main.js
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
const { app, BrowserWindow, session, Menu, ipcMain } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
ipcMain.on('quit-app', () => {
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.commandLine.appendSwitch('ignore-certificate-errors');
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
icon: path.join(__dirname, 'assets/icon.png'),
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
|
contextIsolation: true,
|
||||||
|
},
|
||||||
|
show: false
|
||||||
|
});
|
||||||
|
|
||||||
|
win.maximize();
|
||||||
|
win.show();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
win.loadURL('https://localhost:8001');
|
||||||
|
win.webContents.openDevTools();
|
||||||
|
} else {
|
||||||
|
win.loadFile(path.join(__dirname, 'dist/index.html'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
session.defaultSession.setCertificateVerifyProc((request, callback) => {
|
||||||
|
// Разрешаем для:
|
||||||
|
// 1. localhost
|
||||||
|
// 2. Всех IP в локальной сети (192.168.x.x, 10.x.x.x и т.д.)
|
||||||
|
// 3. Конкретного домена (если есть)
|
||||||
|
if (
|
||||||
|
request.hostname === 'localhost' ||
|
||||||
|
request.hostname === '127.0.0.1' ||
|
||||||
|
request.hostname === '0.0.0.0' ||
|
||||||
|
/^(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/.test(request.hostname)
|
||||||
|
) {
|
||||||
|
return callback(0);
|
||||||
|
}
|
||||||
|
callback(-2);
|
||||||
|
});
|
||||||
|
Menu.setApplicationMenu(null);
|
||||||
|
createWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
4845
FRONT/DEV/package-lock.json
generated
Normal file
39
FRONT/DEV/package.json
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"name": "gonets",
|
||||||
|
"version": "0.0.2",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"start": "electron .",
|
||||||
|
"package": "vite build && electron-packager . --out=dist --overwrite --icon=assets/icon"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify/icons-ic": "^1.2.13",
|
||||||
|
"@iconify/icons-ph": "^1.2.5",
|
||||||
|
"@iconify/react": "^5.2.0",
|
||||||
|
"@reduxjs/toolkit": "^2.6.0",
|
||||||
|
"@xyflow/react": "^12.3.5",
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-flow-renderer": "^10.3.17",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
|
"react-router-dom": "^7.0.2",
|
||||||
|
"styled-components": "^6.1.13",
|
||||||
|
"zustand": "^5.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@iconify-json/ic": "^1.2.2",
|
||||||
|
"@types/react": "^18.0.28",
|
||||||
|
"@types/react-dom": "^18.0.11",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||||
|
"@vitejs/plugin-react": "^4.4.1",
|
||||||
|
"electron": "^34.0.2",
|
||||||
|
"electron-packager": "^17.1.2",
|
||||||
|
"esbuild-wasm": "^0.17.12",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"vite": "^6.3.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
FRONT/DEV/preload.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('electron', {
|
||||||
|
send: (channel, data) => {
|
||||||
|
ipcRenderer.send(channel, data);
|
||||||
|
},
|
||||||
|
on: (channel, func) => {
|
||||||
|
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
||||||
|
},
|
||||||
|
quitApp: () => {
|
||||||
|
ipcRenderer.send('quit-app');
|
||||||
|
},
|
||||||
|
|
||||||
|
onThemeChange: (callback) => {
|
||||||
|
ipcRenderer.on('theme-update', (event, theme) => {
|
||||||
|
callback(theme);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
836
FRONT/DEV/public/assets/images/back1.svg
Normal file
|
|
@ -0,0 +1,836 @@
|
||||||
|
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 1815.11 418.96024210526315" style="max-height: 500px" width="1815.11" height="418.96024210526315">
|
||||||
|
<desc>
|
||||||
|
Created with Pixso.
|
||||||
|
</desc>
|
||||||
|
<defs>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19033_0" y2="-712.221313" x2="270.959900" y1="-1308.030151" x1="1840.109863">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19034_0" y2="-712.765381" x2="270.761597" y1="-1308.578369" x1="1839.901978">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19035_0" y2="-679.637817" x2="283.338562" y1="-1275.450806" x1="1852.479004">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19036_0" y2="-880.955322" x2="206.882507" y1="-1476.766113" x1="1776.022949">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19037_0" y2="-848.618530" x2="219.157166" y1="-1444.431519" x1="1788.306396">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19038_0" y2="-638.924438" x2="298.801636" y1="-1234.733765" x1="1867.942139">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19039_0" y2="-936.472778" x2="185.797729" y1="-1532.290161" x1="1754.938477">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19040_0" y2="-690.210938" x2="279.326172" y1="-1286.019775" x1="1848.466797">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19041_0" y2="-523.330200" x2="342.703064" y1="-1119.139282" x1="1911.843506">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19042_0" y2="-572.199951" x2="324.145142" y1="-1168.012207" x1="1893.285767">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19043_0" y2="-684.131104" x2="281.774048" y1="-1279.698242" x1="1850.905151">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19044_0" y2="-814.235718" x2="232.216248" y1="-1410.045044" x1="1801.366211">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19045_0" y2="-853.401001" x2="217.349915" y1="-1449.210327" x1="1786.489990">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19046_0" y2="-806.166626" x2="235.284180" y1="-1401.981812" x1="1804.432251">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19047_0" y2="-824.094238" x2="228.479492" y1="-1419.903076" x1="1797.619019">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19048_0" y2="-761.783691" x2="252.137390" y1="-1357.603516" x1="1821.277344">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19049_0" y2="-608.633911" x2="310.301025" y1="-1204.443481" x1="1879.449463">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19050_0" y2="-589.109619" x2="317.717834" y1="-1184.921387" x1="1886.862549">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19051_0" y2="-574.739014" x2="323.179565" y1="-1170.548340" x1="1892.312744">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19052_0" y2="-510.040039" x2="347.747314" y1="-1105.853027" x1="1916.899292">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19053_0" y2="-602.636475" x2="312.581299" y1="-1198.442871" x1="1881.727905">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19054_0" y2="-457.936646" x2="367.535767" y1="-1053.743042" x1="1936.684692">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19055_0" y2="-669.309570" x2="287.256714" y1="-1265.125000" x1="1856.405640">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19056_0" y2="-778.359009" x2="245.844360" y1="-1374.172485" x1="1814.993408">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19057_0" y2="-953.510498" x2="179.315979" y1="-1549.335693" x1="1748.464844">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19058_0" y2="-920.018555" x2="192.050781" y1="-1515.837646" x1="1761.191650">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19059_0" y2="-802.950073" x2="236.501221" y1="-1398.764526" x1="1805.654297">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19060_0" y2="-722.904053" x2="266.916870" y1="-1318.703857" x1="1836.050903">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19061_0" y2="-88.563477" x2="1586.212402" y1="507.200928" x1="17.034790">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19062_0" y2="81.508667" x2="1650.698730" y1="677.302612" x1="81.543884">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19063_0" y2="12.648682" x2="1624.639160" y1="608.453613" x1="55.482483">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19064_0" y2="-56.019775" x2="1598.548340" y1="539.775146" x1="29.407227">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19065_0" y2="-349.650391" x2="1487.043701" y1="246.165894" x1="-82.104065">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19066_0" y2="-542.731079" x2="335.340210" y1="-1138.536011" x1="1904.482666">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19067_0" y2="-867.725708" x2="211.896606" y1="-1463.536499" x1="1781.049194">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19068_0" y2="-832.167236" x2="225.404663" y1="-1427.976074" x1="1794.552490">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19069_0" y2="-785.108154" x2="243.289429" y1="-1380.918945" x1="1812.429199">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19070_0" y2="-746.875610" x2="257.806580" y1="-1342.691528" x1="1826.945801">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19071_0" y2="-431.014648" x2="377.765869" y1="-1026.823975" x1="1946.915649">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19072_0" y2="-578.737061" x2="321.657166" y1="-1174.551514" x1="1890.793945">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19073_0" y2="-640.375244" x2="298.242615" y1="-1236.190430" x1="1867.394775">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19074_0" y2="136.595459" x2="1671.701538" y1="732.386963" x1="102.556885">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19075_0" y2="-851.596802" x2="218.019714" y1="-1447.403687" x1="1787.174072">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19076_0" y2="91.515869" x2="1654.585449" y1="687.262451" x1="85.415100">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19077_0" y2="-636.860107" x2="299.587952" y1="-1232.671387" x1="1868.727295">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19078_0" y2="-635.054810" x2="300.269287" y1="-1230.862671" x1="1869.408447">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19079_0" y2="-643.379150" x2="297.107666" y1="-1239.189453" x1="1866.248291">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19080_0" y2="-196.003906" x2="1545.392700" y1="399.793701" x1="-23.754089">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19081_0" y2="-178.466919" x2="1552.046021" y1="417.357788" x1="-17.090454">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19082_0" y2="-70.970093" x2="1592.883179" y1="524.854614" x1="23.737427">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19083_0" y2="255.285400" x2="1716.785522" y1="851.064453" x1="147.633545">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19084_0" y2="-468.268066" x2="363.617554" y1="-1064.076416" x1="1932.758301">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19085_0" y2="-533.139160" x2="338.975037" y1="-1128.951660" x1="1908.124268">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19086_0" y2="-572.218994" x2="324.135681" y1="-1168.031982" x1="1893.276123">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19087_0" y2="-790.724487" x2="241.158875" y1="-1386.530640" x1="1810.299805">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19088_0" y2="-734.533569" x2="262.490845" y1="-1330.342651" x1="1831.630371">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19089_0" y2="-181.838135" x2="1550.774536" y1="413.973633" x1="-18.375244">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19091_0" y2="-770.605835" x2="248.787354" y1="-1366.422241" x1="1817.928101">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19092_0" y2="-656.410156" x2="292.158752" y1="-1252.219971" x1="1861.299316">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19093_0" y2="-807.879028" x2="234.638977" y1="-1403.695679" x1="1803.779297">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19094_0" y2="-541.958984" x2="335.700134" y1="-1137.770752" x1="1904.849854">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19095_0" y2="-911.495239" x2="195.279907" y1="-1507.300903" x1="1764.429443">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19096_0" y2="-959.253418" x2="177.138245" y1="-1555.057861" x1="1746.288818">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19097_0" y2="-985.545288" x2="167.164673" y1="-1581.354370" x1="1736.304932">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19098_0" y2="-878.533691" x2="207.800232" y1="-1474.343018" x1="1776.940430">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19099_0" y2="-587.691162" x2="318.258789" y1="-1183.504150" x1="1887.398560">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19100_0" y2="-562.047729" x2="327.997192" y1="-1157.858276" x1="1897.145996">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19101_0" y2="-968.866333" x2="173.487244" y1="-1564.679810" x1="1742.635010">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19102_0" y2="-838.678467" x2="222.942749" y1="-1434.488525" x1="1792.072754">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19103_0" y2="-763.245728" x2="251.588379" y1="-1359.052856" x1="1820.718628">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19104_0" y2="-745.715210" x2="258.241577" y1="-1341.525024" x1="1827.390259">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19105_0" y2="108.613647" x2="1661.081299" y1="704.385864" x1="91.922668">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19106_0" y2="-141.191772" x2="1566.211914" y1="454.617798" x1="-2.940430">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19107_0" y2="171.605225" x2="1685.069946" y1="767.390137" x1="115.908752">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19108_0" y2="-884.187988" x2="205.653015" y1="-1480.000244" x1="1774.792236">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19109_0" y2="-901.711304" x2="199.010132" y1="-1497.515259" x1="1768.139282">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19110_0" y2="37.703979" x2="1634.147949" y1="633.531860" x1="65.011292">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19111_0" y2="-541.860962" x2="335.662231" y1="-1137.676636" x1="1904.809448">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19112_0" y2="-547.931274" x2="333.363098" y1="-1143.741333" x1="1902.503662">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19113_0" y2="-463.031372" x2="365.604980" y1="-1058.843872" x1="1934.745361">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19114_0" y2="-436.564575" x2="375.646118" y1="-1032.376587" x1="1944.796387">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19115_0" y2="-830.786743" x2="225.932434" y1="-1426.595825" x1="1795.072998">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19116_0" y2="-994.658203" x2="163.690491" y1="-1590.466797" x1="1732.840576">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19117_0" y2="-394.041870" x2="391.800293" y1="-989.854614" x1="1960.950195">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19118_0" y2="-532.041260" x2="339.400513" y1="-1127.852295" x1="1908.539551">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19119_0" y2="-786.902832" x2="242.598938" y1="-1382.719727" x1="1811.747070">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19120_0" y2="-509.625732" x2="347.908325" y1="-1105.437744" x1="1917.047729">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19121_0" y2="187.665039" x2="1691.103516" y1="783.447876" x1="121.945007">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19122_0" y2="280.240845" x2="1726.264160" y1="876.082397" x1="157.135376">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19123_0" y2="-1026.346680" x2="151.663452" y1="-1622.157227" x1="1720.802856">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19124_0" y2="-365.034912" x2="402.824951" y1="-960.850830" x1="1971.964478">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19127_0" y2="-510.000244" x2="285.704285" y1="-1227.124512" x1="2022.169922">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19128_0" y2="-419.934448" x2="322.905457" y1="-1137.061157" x1="2059.371094">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19129_0" y2="-348.977417" x2="352.214783" y1="-1066.102173" x1="2088.689209">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19130_0" y2="-553.336060" x2="267.799377" y1="-1270.463257" x1="2004.265137">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19131_0" y2="-535.517944" x2="275.152161" y1="-1252.645386" x1="2011.627441">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19132_0" y2="-367.547485" x2="344.548401" y1="-1084.675659" x1="2081.014160">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19133_0" y2="-305.386230" x2="370.222900" y1="-1022.509644" x1="2106.698730">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19134_0" y2="-661.351562" x2="223.178467" y1="-1378.478271" x1="1959.653564">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19135_0" y2="-680.692017" x2="215.191284" y1="-1397.818970" x1="1951.666626">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19136_0" y2="-626.946045" x2="237.392456" y1="-1344.071777" x1="1973.867676">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19137_0" y2="-326.333984" x2="361.573853" y1="-1043.458496" x1="2098.039062">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19138_0" y2="-628.664062" x2="236.681213" y1="-1345.783936" x1="1973.149658">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19139_0" y2="-225.000977" x2="403.421448" y1="-942.129395" x1="2139.895020">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19140_0" y2="-114.167847" x2="449.206177" y1="-831.292358" x1="2185.671875">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19141_0" y2="-332.256226" x2="359.122803" y1="-1049.382690" x1="2095.584473">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19142_0" y2="-306.446167" x2="369.787659" y1="-1023.566772" x1="2106.252197">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19143_0" y2="-615.994141" x2="241.914551" y1="-1333.116699" x1="1978.383911">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19144_0" y2="-281.688843" x2="380.008789" y1="-998.813232" x1="2116.475586">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19145_0" y2="-452.273682" x2="309.542419" y1="-1169.396729" x1="2046.019531">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19146_0" y2="-475.159912" x2="300.086975" y1="-1192.284668" x1="2036.565186">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19147_0" y2="-512.887573" x2="284.501648" y1="-1230.013062" x1="2020.978271">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19148_0" y2="-16.661743" x2="1710.429443" y1="700.462769" x1="-26.046570">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19149_0" y2="-501.537354" x2="289.204712" y1="-1218.656250" x1="2025.672485">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19150_0" y2="-211.520752" x2="408.985901" y1="-928.650757" x1="2145.460449">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19151_0" y2="-246.238037" x2="394.648071" y1="-963.367432" x1="2131.123535">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19152_0" y2="-169.549683" x2="426.323181" y1="-886.674927" x1="2162.796143">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19153_0" y2="-425.535889" x2="320.587646" y1="-1142.662354" x1="2057.060303">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19154_0" y2="-35.174561" x2="1702.530029" y1="681.950928" x1="-33.938721">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19155_0" y2="-89.187866" x2="1680.382080" y1="627.931274" x1="-56.077576">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19156_0" y2="-181.335327" x2="1642.316895" y1="535.789185" x1="-94.147400">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19157_0" y2="-428.551392" x2="319.337830" y1="-1145.679810" x1="2055.812256">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19158_0" y2="-243.617432" x2="395.736816" y1="-960.747803" x1="2132.202637">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19159_0" y2="-303.808472" x2="370.875916" y1="-1020.938843" x1="2107.341553">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19160_0" y2="-361.952759" x2="346.848816" y1="-1079.073853" x1="2083.333496">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19161_0" y2="-584.513428" x2="254.928223" y1="-1301.631348" x1="1991.393677">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19162_0" y2="-732.547729" x2="193.765625" y1="-1449.675903" x1="1930.240723">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19163_0" y2="-387.317383" x2="336.372070" y1="-1104.442627" x1="2072.846680">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19164_0" y2="-547.665771" x2="270.136536" y1="-1264.797119" x1="2006.611572">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19165_0" y2="-494.261963" x2="292.205688" y1="-1211.380127" x1="2028.671021">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19166_0" y2="-45.325562" x2="1698.596191" y1="671.799438" x1="-37.866760">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19167_0" y2="-271.514038" x2="384.210510" y1="-988.644409" x1="2120.675049">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19168_0" y2="-181.371216" x2="1642.248047" y1="535.747437" x1="-94.225708">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19169_0" y2="-421.084595" x2="1543.309448" y1="296.035767" x1="-193.166260">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19170_0" y2="-634.904907" x2="234.112793" y1="-1352.022339" x1="1970.572754">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19171_0" y2="-611.658325" x2="243.718201" y1="-1328.783081" x1="1980.177734">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19172_0" y2="171.647095" x2="1788.105713" y1="888.778442" x1="51.642212">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19173_0" y2="44.584839" x2="1735.573730" y1="761.715332" x1="-0.891785">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19174_0" y2="-321.950439" x2="1584.263306" y1="395.176758" x1="-152.200989">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19175_0" y2="-517.690308" x2="282.524231" y1="-1234.818237" x1="2018.989746">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19176_0" y2="-340.158936" x2="355.857727" y1="-1057.280029" x1="2092.323486">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19177_0" y2="-565.488403" x2="262.774292" y1="-1282.613159" x1="1999.248901">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19178_0" y2="-740.342773" x2="190.548523" y1="-1457.469727" x1="1927.023438">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19179_0" y2="-579.238770" x2="257.105530" y1="-1296.362549" x1="1993.570801">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19180_0" y2="-323.012939" x2="362.945740" y1="-1040.135742" x1="2099.411621">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19181_0" y2="-257.915771" x2="389.822021" y1="-975.044189" x1="2126.296875">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19182_0" y2="-243.395386" x2="395.822388" y1="-960.519897" x1="2132.296387">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19183_0" y2="-248.131592" x2="393.863159" y1="-965.260742" x1="2130.337891">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19184_0" y2="-182.964966" x2="420.786377" y1="-900.087036" x1="2157.261475">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19185_0" y2="-479.541748" x2="298.290771" y1="-1196.665283" x1="2034.755493">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19186_0" y2="-462.184082" x2="305.454651" y1="-1179.311523" x1="2041.920166">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19187_0" y2="-528.727661" x2="277.963867" y1="-1245.851929" x1="2014.437622">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19188_0" y2="-709.725220" x2="203.200684" y1="-1426.850952" x1="1939.666016">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19189_0" y2="-616.124390" x2="241.859924" y1="-1333.247192" x1="1978.334229">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19190_0" y2="-668.157959" x2="220.368225" y1="-1385.281494" x1="1956.842773">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19191_0" y2="-704.029785" x2="205.554565" y1="-1421.155273" x1="1942.013794">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19192_0" y2="-631.046143" x2="235.696838" y1="-1348.168213" x1="1972.165283">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19193_0" y2="-478.551025" x2="298.688049" y1="-1195.680176" x1="2035.164307">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19194_0" y2="-707.575684" x2="204.107727" y1="-1424.693115" x1="1940.564697">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19195_0" y2="57.502930" x2="1740.995117" y1="774.617188" x1="4.535767">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19196_0" y2="-95.063232" x2="1677.948975" y1="622.064453" x1="-58.530945">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19197_0" y2="-653.411255" x2="226.453186" y1="-1370.539185" x1="1962.927979">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19198_0" y2="-514.821411" x2="283.707214" y1="-1231.950806" x1="2020.172852">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19200_0" y2="133.678345" x2="1772.458618" y1="850.802734" x1="35.997070">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id="paint_linear_47_19201_0" y2="-10.831787" x2="1712.766113" y1="706.300537" x1="-23.694580">
|
||||||
|
<stop stop-color="#FFFFFF"/>
|
||||||
|
<stop stop-color="#000000" offset="1.000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g opacity="0.660000">
|
||||||
|
<g opacity="0.130000">
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19033_0)" d="M1274.23 255.03L1274.23 49.67L1278.49 49.67L1278.49 256.79L1250.04 285.23L1162.91 285.23L1140.42 307.71L1051.26 307.71L1029.78 286.24L832.87 286.24L832.87 281.98L1031.55 281.98L1053.02 303.45L1138.66 303.45L1161.14 280.97L1248.28 280.97L1274.23 255.03Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19034_0)" d="M797.86 263.9L1233.06 263.9L1254.82 242.14L1254.82 83.59L1256.96 83.59L1256.96 243.02L1233.93 266.03L797.86 266.03L797.86 263.9Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19035_0)" d="M1219.14 247.52L1092.19 247.52L1059.96 215.29L928.97 215.29L887.09 173.42L890.1 170.41L930.73 211.03L1061.73 211.03L1093.97 243.26L1219.14 243.26L1219.14 247.52Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19036_0)" d="M1480.87 314.06L1312.74 314.06L1283.53 284.86L1285.04 283.35L1313.62 311.93L1479.99 311.93L1509.3 282.63L1641.96 282.63L1641.96 284.75L1510.18 284.75L1480.87 314.06Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19037_0)" d="M1302.47 266.47L1305.48 263.46L1328.24 286.22L1462.24 286.22L1491.61 256.86L1494.61 259.87L1464.01 290.48L1326.47 290.48L1302.47 266.47Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19038_0)" d="M1011.65 247.52L744.55 247.52L719.63 222.6L722.64 219.59L746.31 243.26L1011.65 243.26L1011.65 247.52Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19039_0)" d="M1403.99 349.22L1501.45 349.22L1534.48 316.18L1585.82 316.18L1585.82 320.44L1536.25 320.44L1503.21 353.48L1403.99 353.48L1403.99 349.22Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19040_0)" d="M1219.14 220.03L1219.14 222.16L1111.77 222.16L1111.46 221.85L1049.84 160.24L1051.34 158.73L1112.65 220.03L1219.14 220.03Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19041_0)" d="M584.92 312.82L502.32 312.82L502.01 312.51L397.79 208.3L316.09 208.3L316.09 206.17L398.67 206.17L503.2 310.69L584.92 310.69L584.92 312.82Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19042_0)" d="M691.58 290.48L518.23 290.48L470.75 243.01L473.77 240L519.99 286.22L691.58 286.22L691.58 290.48Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19043_0)" d="M1306.1 18.35L1301.84 18.35L1301.84 221.1L1306.1 221.1L1306.1 18.35Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19044_0)" d="M1340.51 239.37L1432.28 239.37L1504.07 167.6L1585.82 167.6L1585.82 171.85L1505.83 171.85L1434.05 243.63L1340.51 243.63L1340.51 239.37Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19045_0)" d="M1493.34 218.9L1606.36 218.9L1676.82 148.45L1678.32 149.96L1607.25 221.03L1494.22 221.03L1463.88 251.36L1462.37 249.86L1493.34 218.9Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19046_0)" d="M1298.42 269.44C1293.86 269.44 1290.15 265.73 1290.15 261.17C1290.15 256.6 1293.86 252.89 1298.42 252.89C1302.98 252.89 1306.68 256.6 1306.68 261.17C1306.68 265.73 1302.98 269.44 1298.42 269.44ZM1298.42 257.15C1296.19 257.15 1294.4 258.95 1294.4 261.16C1294.4 263.38 1296.19 265.18 1298.42 265.18C1300.63 265.18 1302.43 263.38 1302.43 261.16C1302.43 258.95 1300.63 257.15 1298.42 257.15Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19047_0)" d="M1592.39 178.32C1587.83 178.32 1584.12 174.61 1584.12 170.05C1584.12 165.49 1587.83 161.78 1592.39 161.78C1596.96 161.78 1600.67 165.49 1600.67 170.05C1600.67 174.61 1596.96 178.32 1592.39 178.32ZM1592.39 166.03C1590.19 166.03 1588.38 167.83 1588.38 170.05C1588.38 172.26 1590.19 174.06 1592.39 174.06C1594.61 174.06 1596.41 172.26 1596.41 170.05C1596.41 167.83 1594.61 166.03 1592.39 166.03Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19048_0)" d="M1337.41 203.87C1332.85 203.87 1329.13 200.16 1329.13 195.59C1329.13 191.03 1332.85 187.32 1337.41 187.32C1341.97 187.32 1345.68 191.03 1345.68 195.59C1345.68 200.16 1341.97 203.87 1337.41 203.87ZM1337.41 191.58C1335.19 191.58 1333.38 193.38 1333.38 195.59C1333.38 197.8 1335.19 199.6 1337.41 199.6C1339.62 199.6 1341.42 197.8 1341.42 195.59C1341.42 193.38 1339.62 191.58 1337.41 191.58Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19049_0)" d="M1276.36 51.79C1271.8 51.79 1268.09 48.08 1268.09 43.52C1268.09 38.96 1271.8 35.25 1276.36 35.25C1280.92 35.25 1284.63 38.96 1284.63 43.52C1284.63 48.08 1280.92 51.79 1276.36 51.79ZM1276.36 39.5C1274.14 39.5 1272.34 41.3 1272.34 43.52C1272.34 45.73 1274.14 47.53 1276.36 47.53C1278.56 47.53 1280.38 45.73 1280.38 43.52C1280.38 41.3 1278.56 39.5 1276.36 39.5Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19050_0)" d="M885.18 178C880.62 178 876.91 174.28 876.91 169.72C876.91 165.16 880.62 161.45 885.18 161.45C889.74 161.45 893.45 165.16 893.45 169.72C893.45 174.28 889.74 178 885.18 178ZM885.18 165.71C882.97 165.71 881.17 167.51 881.17 169.72C881.17 171.93 882.97 173.73 885.18 173.73C887.39 173.73 889.2 171.93 889.2 169.72C889.2 167.51 887.39 165.71 885.18 165.71Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19051_0)" d="M717.51 225.22C712.94 225.22 709.23 221.51 709.23 216.95C709.23 212.39 712.94 208.68 717.51 208.68C722.07 208.68 725.78 212.39 725.78 216.95C725.78 221.51 722.07 225.22 717.51 225.22ZM717.51 212.93C715.29 212.93 713.49 214.73 713.49 216.95C713.49 219.16 715.29 220.96 717.51 220.96C719.72 220.96 721.52 219.16 721.52 216.95C721.52 214.73 719.72 212.93 717.51 212.93Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19052_0)" d="M469.01 245.54C464.45 245.54 460.74 241.83 460.74 237.27C460.74 232.7 464.45 228.99 469.01 228.99C473.57 228.99 477.29 232.7 477.29 237.27C477.29 241.83 473.57 245.54 469.01 245.54ZM469.01 233.25C466.8 233.25 465 235.05 465 237.26C465 239.48 466.8 241.28 469.01 241.28C471.23 241.28 473.02 239.48 473.02 237.26C473.02 235.05 471.23 233.25 469.01 233.25Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19053_0)" d="M675.08 273.24C670.51 273.24 666.8 269.53 666.8 264.97C666.8 260.41 670.51 256.7 675.08 256.7C679.64 256.7 683.35 260.41 683.35 264.97C683.35 269.53 679.64 273.24 675.08 273.24ZM675.08 260.95C672.86 260.95 671.06 262.75 671.06 264.96C671.06 267.18 672.86 268.98 675.08 268.98C677.29 268.98 679.09 267.18 679.09 264.96C679.09 262.75 677.29 260.95 675.08 260.95Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19054_0)" d="M534.64 160.99C530.08 160.99 526.37 157.28 526.37 152.72C526.37 148.16 530.08 144.45 534.64 144.45C539.2 144.45 542.91 148.16 542.91 152.72C542.91 157.28 539.2 160.99 534.64 160.99ZM534.64 148.71C532.43 148.71 530.63 150.5 530.63 152.72C530.63 154.93 532.43 156.73 534.64 156.73C536.85 156.73 538.65 154.93 538.65 152.72C538.65 150.5 536.85 148.71 534.64 148.71Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19055_0)" d="M826.72 291.96C822.16 291.96 818.45 288.25 818.45 283.69C818.45 279.13 822.16 275.42 826.72 275.42C831.28 275.42 834.99 279.13 834.99 283.69C834.99 288.25 831.28 291.96 826.72 291.96ZM826.72 279.67C824.51 279.67 822.71 281.47 822.71 283.69C822.71 285.9 824.51 287.7 826.72 287.7C828.93 287.7 830.73 285.9 830.73 283.69C830.73 281.47 828.93 279.67 826.72 279.67Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19056_0)" d="M1303.97 235.52C1299.41 235.52 1295.68 231.81 1295.68 227.24C1295.68 222.68 1299.41 218.97 1303.97 218.97C1308.53 218.97 1312.24 222.68 1312.24 227.24C1312.24 231.81 1308.53 235.52 1303.97 235.52ZM1303.97 223.23C1301.76 223.23 1299.96 225.03 1299.96 227.24C1299.96 229.45 1301.76 231.25 1303.97 231.25C1306.18 231.25 1307.98 229.45 1307.98 227.24C1307.98 225.03 1306.18 223.23 1303.97 223.23Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19057_0)" d="M1591.97 326.59C1587.41 326.59 1583.7 322.88 1583.7 318.31C1583.7 313.75 1587.41 310.04 1591.97 310.04C1596.54 310.04 1600.25 313.75 1600.25 318.31C1600.25 322.88 1596.54 326.59 1591.97 326.59ZM1591.97 314.3C1589.75 314.3 1587.95 316.1 1587.95 318.31C1587.95 320.53 1589.75 322.32 1591.97 322.32C1594.18 322.32 1595.99 320.53 1595.99 318.31C1595.99 316.1 1594.18 314.3 1591.97 314.3Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19058_0)" d="M1403.99 358.93C1408.18 358.93 1411.57 355.54 1411.57 351.35C1411.57 347.16 1408.18 343.76 1403.99 343.76C1399.8 343.76 1396.4 347.16 1396.4 351.35C1396.4 355.54 1399.8 358.93 1403.99 358.93Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19059_0)" d="M1340.51 249.09C1344.7 249.09 1348.09 245.69 1348.09 241.5C1348.09 237.31 1344.7 233.92 1340.51 233.92C1336.32 233.92 1332.92 237.31 1332.92 241.5C1332.92 245.69 1336.32 249.09 1340.51 249.09Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19060_0)" d="M1538.89 77.45C1538.89 73.26 1535.49 69.86 1531.3 69.86C1527.11 69.86 1523.72 73.26 1523.72 77.45C1523.72 81.64 1527.11 85.04 1531.3 85.04C1535.49 85.04 1538.89 81.64 1538.89 77.45Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19061_0)" d="M1306.29 25.8C1310.36 24.83 1312.89 20.75 1311.93 16.67C1310.97 12.59 1306.88 10.07 1302.8 11.03C1298.72 11.99 1296.18 16.08 1297.16 20.16C1298.12 24.23 1302.21 26.76 1306.29 25.8Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19062_0)" d="M1226.48 247.08C1227.44 243 1224.92 238.92 1220.84 237.96C1216.77 236.99 1212.68 239.52 1211.72 243.6C1210.76 247.67 1213.28 251.76 1217.36 252.72C1221.43 253.68 1225.52 251.16 1226.48 247.08Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19063_0)" d="M1018.67 248.29C1020.26 244.42 1018.43 239.99 1014.56 238.38C1010.69 236.78 1006.25 238.62 1004.64 242.49C1003.04 246.36 1004.88 250.8 1008.75 252.4C1012.62 254 1017.06 252.17 1018.67 248.29Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19064_0)" d="M692.54 295.85C696.7 295.34 699.65 291.55 699.14 287.39C698.63 283.24 694.84 280.28 690.68 280.79C686.53 281.3 683.57 285.09 684.08 289.25C684.59 293.41 688.38 296.36 692.54 295.85Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19065_0)" d="M309.1 105.61C313.22 104.81 315.9 100.83 315.1 96.72C314.3 92.6 310.32 89.92 306.2 90.72C302.09 91.52 299.41 95.5 300.21 99.62C301.01 103.73 304.99 106.41 309.1 105.61Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19066_0)" d="M604.82 230.68C609.01 230.68 612.4 227.28 612.4 223.09C612.4 218.9 609.01 215.51 604.82 215.51C600.63 215.51 597.23 218.9 597.23 223.09C597.23 227.28 600.63 230.68 604.82 230.68Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19067_0)" d="M1492.87 265.35C1497.06 265.35 1500.45 261.96 1500.45 257.77C1500.45 253.58 1497.06 250.18 1492.87 250.18C1488.68 250.18 1485.28 253.58 1485.28 257.77C1485.28 261.96 1488.68 265.35 1492.87 265.35Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19068_0)" d="M1682.25 152.38C1678.26 152.38 1675.04 149.15 1675.04 145.17C1675.04 141.2 1678.26 137.96 1682.25 137.96C1686.22 137.96 1689.45 141.2 1689.45 145.17C1689.45 149.15 1686.22 152.38 1682.25 152.38ZM1682.25 140.09C1679.45 140.09 1677.17 142.37 1677.17 145.17C1677.17 147.98 1679.44 150.25 1682.25 150.25C1685.05 150.25 1687.33 147.98 1687.33 145.17C1687.33 142.37 1685.05 140.09 1682.25 140.09Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19069_0)" d="M1458.73 183.4C1454.75 183.4 1451.52 180.17 1451.52 176.19C1451.52 172.22 1454.75 168.98 1458.73 168.98C1462.7 168.98 1465.94 172.22 1465.94 176.19C1465.94 180.17 1462.7 183.4 1458.73 183.4ZM1458.73 171.11C1455.93 171.11 1453.65 173.39 1453.65 176.19C1453.65 179 1455.92 181.27 1458.73 181.27C1461.53 181.27 1463.81 179 1463.81 176.19C1463.81 173.39 1461.53 171.11 1458.73 171.11Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19070_0)" d="M1225.29 228.31C1221.31 228.31 1218.07 225.08 1218.07 221.1C1218.07 217.12 1221.31 213.89 1225.29 213.89C1229.27 213.89 1232.5 217.12 1232.5 221.1C1232.5 225.08 1229.27 228.31 1225.29 228.31ZM1225.29 216.02C1222.49 216.02 1220.21 218.3 1220.21 221.1C1220.21 223.9 1222.49 226.18 1225.29 226.18C1228.09 226.18 1230.37 223.9 1230.37 221.1C1230.37 218.3 1228.09 216.02 1225.29 216.02Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19071_0)" d="M309.95 214.44C305.97 214.44 302.74 211.21 302.74 207.23C302.74 203.26 305.97 200.03 309.95 200.03C313.92 200.03 317.15 203.26 317.15 207.23C317.15 211.21 313.92 214.44 309.95 214.44ZM309.95 202.15C307.15 202.15 304.86 204.43 304.86 207.23C304.86 210.04 307.14 212.32 309.95 212.32C312.75 212.32 315.03 210.04 315.03 207.23C315.03 204.43 312.75 202.15 309.95 202.15Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19072_0)" d="M654.63 252.6C650.65 252.6 647.42 249.37 647.42 245.39C647.42 241.42 650.65 238.19 654.63 238.19C658.61 238.19 661.84 241.42 661.84 245.39C661.84 249.37 658.61 252.6 654.63 252.6ZM654.63 240.31C651.83 240.31 649.55 242.59 649.55 245.39C649.55 248.2 651.83 250.48 654.63 250.48C657.44 250.48 659.71 248.2 659.71 245.39C659.71 242.59 657.44 240.31 654.63 240.31Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19073_0)" d="M903.88 228.48C899.91 228.48 896.67 225.25 896.67 221.27C896.67 217.3 899.91 214.07 903.88 214.07C907.86 214.07 911.09 217.3 911.09 221.27C911.09 225.25 907.86 228.48 903.88 228.48ZM903.88 216.19C901.08 216.19 898.8 218.47 898.8 221.27C898.8 224.08 901.08 226.36 903.88 226.36C906.69 226.36 908.96 224.08 908.96 221.27C908.96 218.47 906.69 216.19 903.88 216.19Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19074_0)" d="M1286.63 289.37C1289.77 288.07 1291.26 284.47 1289.96 281.34C1288.66 278.2 1285.06 276.71 1281.93 278.01C1278.8 279.31 1277.3 282.91 1278.61 286.04C1279.91 289.18 1283.5 290.67 1286.63 289.37Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19075_0)" d="M1463.12 256.76C1466.52 256.76 1469.27 254.01 1469.27 250.61C1469.27 247.22 1466.52 244.47 1463.12 244.47C1459.73 244.47 1456.98 247.22 1456.98 250.61C1456.98 254.01 1459.73 256.76 1463.12 256.76Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19076_0)" d="M1337.96 217.72C1340.35 215.32 1340.35 211.43 1337.96 209.03C1335.55 206.63 1331.66 206.63 1329.26 209.03C1326.86 211.43 1326.86 215.32 1329.26 217.72C1331.66 220.12 1335.55 220.12 1337.96 217.72Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19077_0)" d="M1255.89 89.74C1259.29 89.74 1262.04 86.99 1262.04 83.59C1262.04 80.2 1259.29 77.45 1255.89 77.45C1252.5 77.45 1249.75 80.2 1249.75 83.59C1249.75 86.99 1252.5 89.74 1255.89 89.74Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19078_0)" d="M1050.59 165.63C1053.98 165.63 1056.74 162.88 1056.74 159.49C1056.74 156.09 1053.98 153.34 1050.59 153.34C1047.19 153.34 1044.43 156.09 1044.43 159.49C1044.43 162.88 1047.19 165.63 1050.59 165.63Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19079_0)" d="M797.86 271.11C801.25 271.11 804 268.36 804 264.96C804 261.57 801.25 258.82 797.86 258.82C794.47 258.82 791.71 261.57 791.71 264.96C791.71 268.36 794.47 271.11 797.86 271.11Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19080_0)" d="M512.21 202.86C515.35 201.56 516.85 197.97 515.56 194.84C514.26 191.7 510.67 190.2 507.53 191.49C504.39 192.79 502.9 196.38 504.19 199.52C505.48 202.65 509.07 204.15 512.21 202.86Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19081_0)" d="M663.27 165C665.67 162.6 665.67 158.71 663.27 156.31C660.87 153.91 656.98 153.91 654.58 156.31C652.18 158.71 652.18 162.6 654.58 165C656.98 167.4 660.87 167.4 663.27 165Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19082_0)" d="M589.26 316.1C591.66 313.7 591.66 309.81 589.26 307.41C586.86 305.01 582.97 305.01 580.57 307.41C578.17 309.81 578.17 313.7 580.57 316.1C582.97 318.5 586.86 318.5 589.26 316.1Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19083_0)" d="M1644.31 289.37C1647.45 288.07 1648.94 284.48 1647.64 281.34C1646.34 278.2 1642.75 276.71 1639.61 278.01C1636.47 279.31 1634.99 282.91 1636.28 286.04C1637.58 289.18 1641.18 290.67 1644.31 289.37Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19084_0)" d="M668.93 267.05L528.85 267.05L394.47 132.69L339.19 132.69L306.16 99.67L309.17 96.65L340.96 128.43L396.24 128.43L530.62 262.79L668.93 262.79L668.93 267.05Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19085_0)" d="M548.91 198.22L512.97 198.22L512.97 196.09L549.79 196.09L598.03 244.33L648.49 244.33L648.49 246.46L597.15 246.46L548.91 198.22Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19086_0)" d="M897.74 222.34L772.55 222.34L772.24 222.03L711.92 161.72L658.92 161.72L658.92 159.59L712.8 159.59L773.43 220.21L897.74 220.21L897.74 222.34Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19087_0)" d="M1421.86 214.44L1333.61 214.44L1333.61 212.31L1420.98 212.31L1454.37 178.93L1455.87 180.43L1421.86 214.44Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19088_0)" d="M1426.31 101.5L1503.85 101.5L1529.79 75.55L1532.8 78.57L1505.61 105.76L1428.07 105.76L1342.02 191.8L1339.01 188.78L1426.31 101.5Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19089_0)" d="M540 155.26L536.98 158.27L603.32 224.6L606.33 221.58L540 155.26Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19091_0)" d="M1014.88 315.26L1014.88 319.52L871.78 319.52L844.74 346.55L763.71 346.55L733.44 376.82L733.44 516.89L729.18 516.89L729.18 375.06L761.95 342.29L842.97 342.29L870.01 315.26L1014.88 315.26Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19092_0)" d="M603.46 370.77L363.14 370.77L363.14 366.51L605.22 366.51L634.74 396.02L687.33 396.02L687.33 400.28L632.97 400.28L603.46 370.77Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19093_0)" d="M1071.89 448.15L1014.44 390.7L961.94 390.7L936.66 365.42L773.87 365.42L773.87 363.3L937.54 363.3L962.83 388.57L1015.32 388.57L1073.39 446.64L1071.89 448.15Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19094_0)" d="M276.57 350.54L273.56 347.53L309.61 311.48L418.5 311.48L418.5 315.74L311.38 315.74L276.57 350.54Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19095_0)" d="M1354.6 333.02L1265.44 333.02L1265.44 328.76L1356.36 328.76L1399.26 371.65L1485.98 371.65L1485.98 375.91L1397.5 375.91L1354.6 333.02Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19096_0)" d="M1364.19 490.8L1362.06 490.8L1362.06 422.99L1297.31 358.24L1298.81 356.74L1364.19 422.11L1364.19 490.8Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19097_0)" d="M1405.48 418.84L1345.59 358.97L1347.11 357.46L1407.6 417.96L1407.6 453.82L1451.58 497.79L1450.08 499.3L1405.48 454.7L1405.48 418.84Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19098_0)" d="M1293.16 445.77L1247.12 399.74L1132.29 399.74L1099.03 366.49L987.48 366.49L987.48 362.23L1100.79 362.23L1134.05 395.48L1248.89 395.48L1296.17 442.76L1293.16 445.77Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19099_0)" d="M356.99 376.91C352.43 376.91 348.72 373.2 348.72 368.64C348.72 364.08 352.43 360.37 356.99 360.37C361.55 360.37 365.26 364.08 365.26 368.64C365.26 373.2 361.55 376.91 356.99 376.91ZM356.99 364.63C354.78 364.63 352.98 366.42 352.98 368.64C352.98 370.85 354.78 372.65 356.99 372.65C359.2 372.65 361 370.85 361 368.64C361 366.42 359.2 364.63 356.99 364.63Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19100_0)" d="M424.65 321.89C420.08 321.89 416.37 318.18 416.37 313.61C416.37 309.05 420.08 305.34 424.65 305.34C429.21 305.34 432.92 309.05 432.92 313.61C432.92 318.18 429.21 321.89 424.65 321.89ZM424.65 309.6C422.43 309.6 420.63 311.4 420.63 313.61C420.63 315.82 422.43 317.62 424.65 317.62C426.86 317.62 428.66 315.82 428.66 313.61C428.66 311.4 426.86 309.6 424.65 309.6Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19101_0)" d="M1492.13 382.05C1487.56 382.05 1483.85 378.34 1483.85 373.78C1483.85 369.22 1487.56 365.51 1492.13 365.51C1496.69 365.51 1500.4 369.22 1500.4 373.78C1500.4 378.34 1496.69 382.05 1492.13 382.05ZM1492.13 369.76C1489.91 369.76 1488.11 371.57 1488.11 373.78C1488.11 375.99 1489.91 377.79 1492.13 377.79C1494.34 377.79 1496.15 375.99 1496.15 373.78C1496.15 371.57 1494.34 369.76 1492.13 369.76Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19102_0)" d="M1083.65 388.2C1079.09 388.2 1075.37 384.49 1075.37 379.92C1075.37 375.36 1079.09 371.65 1083.65 371.65C1088.21 371.65 1091.92 375.36 1091.92 379.92C1091.92 384.49 1088.21 388.2 1083.65 388.2ZM1083.65 375.91C1081.43 375.91 1079.64 377.71 1079.64 379.92C1079.64 382.13 1081.43 383.93 1083.65 383.93C1085.87 383.93 1087.66 382.13 1087.66 379.92C1087.66 377.71 1085.87 375.91 1083.65 375.91Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19103_0)" d="M1021.02 325.66C1016.46 325.66 1012.75 321.95 1012.75 317.39C1012.75 312.83 1016.46 309.12 1021.02 309.12C1025.57 309.12 1029.3 312.83 1029.3 317.39C1029.3 321.95 1025.57 325.66 1021.02 325.66ZM1021.02 313.37C1018.81 313.37 1017.01 315.17 1017.01 317.38C1017.01 319.6 1018.81 321.4 1021.02 321.4C1023.24 321.4 1025.03 319.6 1025.03 317.38C1025.03 315.17 1023.24 313.37 1021.02 313.37Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19104_0)" d="M794.34 391.67C789.78 391.67 786.07 387.96 786.07 383.4C786.07 378.84 789.78 375.13 794.34 375.13C798.9 375.13 802.61 378.84 802.61 383.4C802.61 387.96 798.9 391.67 794.34 391.67ZM794.34 379.38C792.13 379.38 790.33 381.18 790.33 383.4C790.33 385.61 792.13 387.41 794.34 387.41C796.55 387.41 798.35 385.61 798.35 383.4C798.35 381.18 796.55 379.38 794.34 379.38Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19105_0)" d="M990.37 371.37C994.25 369.77 996.09 365.33 994.48 361.46C992.88 357.59 988.44 355.75 984.57 357.35C980.7 358.95 978.87 363.39 980.47 367.26C982.07 371.13 986.5 372.97 990.37 371.37Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19106_0)" d="M276.77 356.44C280.85 355.48 283.37 351.4 282.41 347.32C281.45 343.24 277.36 340.72 273.28 341.68C269.21 342.64 266.68 346.73 267.64 350.8C268.6 354.88 272.69 357.41 276.77 356.44Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19107_0)" d="M1271.91 334.95C1274.11 331.38 1273.02 326.71 1269.44 324.51C1265.88 322.31 1261.21 323.41 1259.01 326.97C1256.81 330.54 1257.9 335.21 1261.47 337.41C1265.04 339.62 1269.71 338.51 1271.91 334.95Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19108_0)" d="M1293.44 359.54C1289.47 359.54 1286.24 356.31 1286.24 352.33C1286.24 348.36 1289.47 345.12 1293.44 345.12C1297.42 345.12 1300.66 348.36 1300.66 352.33C1300.66 356.31 1297.42 359.54 1293.44 359.54ZM1293.44 347.25C1290.65 347.25 1288.37 349.53 1288.37 352.33C1288.37 355.14 1290.65 357.41 1293.44 357.41C1296.26 357.41 1298.53 355.14 1298.53 352.33C1298.53 349.53 1296.26 347.25 1293.44 347.25Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19109_0)" d="M1342.61 360.91C1338.63 360.91 1335.4 357.68 1335.4 353.7C1335.4 349.73 1338.63 346.49 1342.61 346.49C1346.59 346.49 1349.81 349.73 1349.81 353.7C1349.81 357.68 1346.59 360.91 1342.61 360.91ZM1342.61 348.62C1339.81 348.62 1337.53 350.9 1337.53 353.7C1337.53 356.51 1339.8 358.78 1342.61 358.78C1345.41 358.78 1347.69 356.51 1347.69 353.7C1347.69 350.9 1345.41 348.62 1342.61 348.62Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19110_0)" d="M778.22 368.71C780.62 366.31 780.62 362.42 778.22 360.02C775.82 357.62 771.92 357.62 769.52 360.02C767.12 362.42 767.12 366.31 769.52 368.71C771.92 371.11 775.82 371.11 778.22 368.71Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19111_0)" d="M317.2 337.47C320.6 337.47 323.35 334.72 323.35 331.32C323.35 327.93 320.6 325.18 317.2 325.18C313.81 325.18 311.06 327.93 311.06 331.32C311.06 334.72 313.81 337.47 317.2 337.47Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19112_0)" d="M173.65 371.9L277.09 371.9L318.42 330.57L319.93 332.08L277.98 374.03L174.53 374.03L128.46 420.1L126.95 418.6L173.65 371.9Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19113_0)" d="M194.79 237.97L123.21 237.97L123.21 233.71L196.56 233.71L250.56 287.7L396.85 287.7L396.85 291.96L248.8 291.96L194.79 237.97Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19114_0)" d="M372.45 270.04L372.45 272.17L274.63 272.17L274.32 271.86L182.9 180.46L184.41 178.95L275.51 270.04L372.45 270.04Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19115_0)" d="M1134.42 347.26L1021.02 347.26L1021.02 345.12L1135.3 345.12L1152.38 362.19L1231.43 362.19L1231.43 364.32L1151.5 364.32L1134.42 347.26Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19116_0)" d="M1639.26 382.63L1609.04 352.41L1539.01 352.41L1539.01 350.28L1609.92 350.28L1640.76 381.12L1639.26 382.63Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19117_0)" d="M123.21 243.42C127.4 243.42 130.79 240.02 130.79 235.83C130.79 231.64 127.4 228.25 123.21 228.25C119.02 228.25 115.62 231.64 115.62 235.83C115.62 240.02 119.02 243.42 123.21 243.42Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19118_0)" d="M404.44 289.83C404.44 285.65 401.04 282.25 396.85 282.25C392.66 282.25 389.26 285.65 389.26 289.83C389.26 294.02 392.66 297.42 396.85 297.42C401.04 297.42 404.44 294.02 404.44 289.83Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19119_0)" d="M1015.32 353.82C1011.34 353.82 1008.11 350.59 1008.11 346.62C1008.11 342.64 1011.34 339.41 1015.32 339.41C1019.3 339.41 1022.53 342.64 1022.53 346.62C1022.53 350.59 1019.3 353.82 1015.32 353.82ZM1015.32 341.53C1012.52 341.53 1010.24 343.81 1010.24 346.62C1010.24 349.42 1012.51 351.7 1015.32 351.7C1018.13 351.7 1020.41 349.42 1020.41 346.62C1020.41 343.81 1018.13 341.53 1015.32 341.53Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19120_0)" d="M378.63 278.32C374.65 278.32 371.42 275.08 371.42 271.11C371.42 267.13 374.65 263.9 378.63 263.9C382.6 263.9 385.83 267.13 385.83 271.11C385.83 275.08 382.6 278.32 378.63 278.32ZM378.63 266.03C375.82 266.03 373.54 268.3 373.54 271.11C373.54 273.91 375.82 276.19 378.63 276.19C381.43 276.19 383.71 273.91 383.71 271.11C383.71 268.3 381.43 266.03 378.63 266.03Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19121_0)" d="M1232.99 367.6C1235.38 365.2 1235.38 361.31 1232.99 358.91C1230.57 356.51 1226.68 356.51 1224.29 358.91C1221.89 361.31 1221.89 365.2 1224.29 367.6C1226.68 370 1230.57 370 1232.99 367.6Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19122_0)" d="M1541.36 357.02C1544.49 355.72 1545.98 352.13 1544.69 348.99C1543.38 345.86 1539.78 344.37 1536.65 345.67C1533.52 346.97 1532.02 350.56 1533.33 353.7C1534.63 356.83 1538.22 358.32 1541.36 357.02Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19123_0)" d="M1640 389.54C1643.4 389.54 1646.15 386.79 1646.15 383.4C1646.15 380.01 1643.4 377.25 1640 377.25C1636.62 377.25 1633.86 380.01 1633.86 383.4C1633.86 386.79 1636.62 389.54 1640 389.54Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19124_0)" d="M183.65 185.85C187.05 185.85 189.8 183.1 189.8 179.71C189.8 176.31 187.05 173.56 183.65 173.56C180.26 173.56 177.51 176.31 177.51 179.71C177.51 183.1 180.26 185.85 183.65 185.85Z" id="Vector"/>
|
||||||
|
</g>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19127_0)" d="M870.37 424.97L778.63 424.97L741.54 390.87L741.54 243.22L746.36 243.22L746.36 389.03L780.63 420.54L872.36 420.54L902.98 448.68L1064.99 448.68L1064.99 453.11L900.98 453.11L870.37 424.97Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19128_0)" d="M327.12 395.32L599.2 395.32L632.61 364.61L694.15 364.61L694.15 369.04L634.61 369.04L601.2 399.76L327.12 399.76L327.12 395.32Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19129_0)" d="M253.98 333.18L167.77 333.18L167.77 330.96L254.98 330.96L292.79 365.72L575.95 365.72L575.95 367.93L291.79 367.93L253.98 333.18Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19130_0)" d="M1065.49 376.8L1006.06 376.8L977.43 403.11L792.13 403.11L792.13 400.89L976.43 400.89L1005.06 374.58L1064.5 374.58L1129.54 314.78L1131.24 316.35L1065.49 376.8Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19131_0)" d="M822.26 379.97L954.66 379.97L996.88 341.15L1000.29 344.29L956.66 384.4L822.26 384.4L822.26 379.97Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19132_0)" d="M707.99 210.18L671.76 176.88L675.17 173.74L712.81 208.35L712.81 342.72L707.99 342.72L707.99 210.18Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19133_0)" d="M681.13 227.38L620.29 171.46L620.29 125.05L622.7 125.05L622.7 170.54L683.54 226.47L683.54 278.54L681.13 278.54L681.13 227.38Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19134_0)" d="M1386.45 409.94L1384.74 408.37L1458.06 340.98L1458.06 270.39L1460.47 270.39L1460.47 341.89L1460.11 342.22L1386.45 409.94Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19135_0)" d="M1507.2 345.29L1507.2 307.96L1557.7 261.54L1559.4 263.11L1509.61 308.88L1509.61 346.21L1441.12 409.18L1439.41 407.61L1507.2 345.29Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19136_0)" d="M1329.92 369.6L1199.91 369.6L1162.26 404.22L1033.97 404.22L1033.97 399.79L1160.26 399.79L1197.91 365.17L1327.93 365.17L1380.05 317.26L1383.46 320.39L1329.92 369.6Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19137_0)" d="M400.79 253.52L253.61 253.52L253.61 249.09L402.78 249.09L494.22 333.14L663.12 333.14L663.12 337.57L492.22 337.57L400.79 253.52Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19138_0)" d="M1385.59 324.18C1380.44 324.18 1376.24 320.32 1376.24 315.57C1376.24 310.82 1380.44 306.96 1385.59 306.96C1390.76 306.96 1394.96 310.82 1394.96 315.57C1394.96 320.32 1390.76 324.18 1385.59 324.18ZM1385.59 311.39C1383.1 311.39 1381.05 313.26 1381.05 315.57C1381.05 317.87 1383.1 319.74 1385.59 319.74C1388.1 319.74 1390.15 317.87 1390.15 315.57C1390.15 313.26 1388.1 311.39 1385.59 311.39Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19139_0)" d="M246.65 259.91C241.49 259.91 237.29 256.05 237.29 251.3C237.29 246.55 241.49 242.69 246.65 242.69C251.82 242.69 256.02 246.55 256.02 251.3C256.02 256.05 251.82 259.91 246.65 259.91ZM246.65 247.12C244.15 247.12 242.11 248.99 242.11 251.3C242.11 253.6 244.15 255.48 246.65 255.48C249.16 255.48 251.2 253.6 251.2 251.3C251.2 248.99 249.16 247.12 246.65 247.12Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19140_0)" d="M102.27 183.51C97.11 183.51 92.91 179.64 92.91 174.9C92.91 170.15 97.11 166.28 102.27 166.28C107.44 166.28 111.64 170.15 111.64 174.9C111.64 179.64 107.44 183.51 102.27 183.51ZM102.27 170.71C99.77 170.71 97.73 172.59 97.73 174.89C97.73 177.2 99.77 179.07 102.27 179.07C104.78 179.07 106.82 177.2 106.82 174.89C106.82 172.59 104.78 170.71 102.27 170.71Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19141_0)" d="M514.75 289.04C509.59 289.04 505.38 285.18 505.38 280.43C505.38 275.68 509.59 271.82 514.75 271.82C519.92 271.82 524.12 275.68 524.12 280.43C524.12 285.18 519.92 289.04 514.75 289.04ZM514.75 276.25C512.24 276.25 510.21 278.12 510.21 280.42C510.21 282.73 512.24 284.6 514.75 284.6C517.26 284.6 519.3 282.73 519.3 280.42C519.3 278.12 517.26 276.25 514.75 276.25Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19142_0)" d="M156.98 384.4C151.82 384.4 147.62 380.54 147.62 375.79C147.62 371.04 151.82 367.18 156.98 367.18C162.15 367.18 166.35 371.04 166.35 375.79C166.35 380.54 162.15 384.4 156.98 384.4ZM156.98 371.61C154.48 371.61 152.44 373.48 152.44 375.79C152.44 378.09 154.48 379.96 156.98 379.96C159.49 379.96 161.53 378.09 161.53 375.79C161.53 373.48 159.49 371.61 156.98 371.61Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19143_0)" d="M1142.85 394.41C1137.68 394.41 1133.48 390.55 1133.48 385.8C1133.48 381.05 1137.68 377.19 1142.85 377.19C1148.01 377.19 1152.21 381.05 1152.21 385.8C1152.21 390.55 1148.01 394.41 1142.85 394.41ZM1142.85 381.62C1140.34 381.62 1138.3 383.49 1138.3 385.8C1138.3 388.1 1140.34 389.97 1142.85 389.97C1145.35 389.97 1147.4 388.1 1147.4 385.8C1147.4 383.49 1145.35 381.62 1142.85 381.62Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19144_0)" d="M667.19 177.97C662.02 177.97 657.82 174.11 657.82 169.36C657.82 164.61 662.02 160.75 667.19 160.75C672.35 160.75 676.55 164.61 676.55 169.36C676.55 174.11 672.35 177.97 667.19 177.97ZM667.19 165.18C664.68 165.18 662.64 167.05 662.64 169.35C662.64 171.66 664.68 173.53 667.19 173.53C669.69 173.53 671.73 171.66 671.73 169.35C671.73 167.05 669.69 165.18 667.19 165.18Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19145_0)" d="M710.4 358.06C705.24 358.06 701.03 354.2 701.03 349.45C701.03 344.7 705.24 340.84 710.4 340.84C715.57 340.84 719.77 344.7 719.77 349.45C719.77 354.2 715.57 358.06 710.4 358.06ZM710.4 345.27C707.9 345.27 705.86 347.14 705.86 349.45C705.86 351.75 707.9 353.62 710.4 353.62C712.91 353.62 714.95 351.75 714.95 349.45C714.95 347.14 712.91 345.27 710.4 345.27Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19146_0)" d="M1210.73 209.57C1205.56 209.57 1201.37 205.71 1201.37 200.96C1201.37 196.21 1205.56 192.35 1210.73 192.35C1215.89 192.35 1220.1 196.21 1220.1 200.96C1220.1 205.71 1215.89 209.57 1210.73 209.57ZM1210.73 196.78C1208.23 196.78 1206.18 198.66 1206.18 200.96C1206.18 203.26 1208.23 205.14 1210.73 205.14C1213.24 205.14 1215.28 203.26 1215.28 200.96C1215.28 198.66 1213.24 196.78 1210.73 196.78Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19147_0)" d="M815.3 390.79C810.14 390.79 805.94 386.93 805.94 382.18C805.94 377.43 810.14 373.57 815.3 373.57C820.47 373.57 824.67 377.43 824.67 382.18C824.67 386.93 820.47 390.79 815.3 390.79ZM815.3 378C812.8 378 810.76 379.87 810.76 382.18C810.76 384.48 812.8 386.36 815.3 386.36C817.81 386.36 819.85 384.48 819.85 382.18C819.85 379.87 817.81 378 815.3 378Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19148_0)" d="M1007.14 343.73C1007.72 339.4 1004.38 335.46 999.66 334.92C994.95 334.39 990.67 337.47 990.08 341.8C989.5 346.12 992.85 350.07 997.56 350.6C1002.27 351.13 1006.56 348.05 1007.14 343.73Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19149_0)" d="M1069.51 283.44C1069.51 287.79 1065.66 291.33 1060.92 291.33C1056.18 291.33 1052.34 287.79 1052.34 283.44C1052.34 279.07 1056.18 275.54 1060.92 275.54C1065.66 275.54 1069.51 279.07 1069.51 283.44Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19150_0)" d="M18.26 318.62C18.26 322.98 14.41 326.52 9.67 326.52C4.92 326.52 1.08 322.98 1.08 318.62C1.08 314.26 4.92 310.73 9.67 310.73C14.41 310.73 18.26 314.26 18.26 318.62Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19151_0)" d="M389.09 228.88C389.09 233.24 385.25 236.77 380.51 236.77C375.76 236.77 371.92 233.24 371.92 228.88C371.92 224.52 375.76 220.98 380.51 220.98C385.25 220.98 389.09 224.52 389.09 228.88Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19152_0)" d="M346.25 156.09C346.25 160.45 342.4 163.98 337.66 163.98C332.92 163.98 329.07 160.45 329.07 156.09C329.07 151.73 332.92 148.19 337.66 148.19C342.4 148.19 346.25 151.73 346.25 156.09Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19153_0)" d="M663.12 343.26C667.87 343.26 671.71 339.72 671.71 335.36C671.71 331 667.87 327.46 663.12 327.46C658.38 327.46 654.53 331 654.53 335.36C654.53 339.72 658.38 343.26 663.12 343.26Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19154_0)" d="M1393 187.55C1393.52 183.22 1390.12 179.32 1385.41 178.84C1380.68 178.36 1376.44 181.49 1375.92 185.83C1375.41 190.16 1378.81 194.06 1383.53 194.54C1388.24 195.01 1392.48 191.89 1393 187.55Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19155_0)" d="M696.48 373.49C700.51 371.2 701.76 366.33 699.27 362.62C696.78 358.91 691.48 357.76 687.45 360.05C683.41 362.35 682.16 367.21 684.66 370.92C687.15 374.63 692.44 375.78 696.48 373.49Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19156_0)" d="M748.48 249.89C752.52 247.6 753.77 242.73 751.27 239.02C748.78 235.31 743.49 234.16 739.45 236.45C735.42 238.75 734.17 243.61 736.66 247.32C739.15 251.03 744.45 252.18 748.48 249.89Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19157_0)" d="M582.91 374.33C578.41 374.33 574.75 370.96 574.75 366.83C574.75 362.69 578.41 359.32 582.91 359.32C587.41 359.32 591.07 362.69 591.07 366.83C591.07 370.96 587.41 374.33 582.91 374.33ZM582.91 361.54C579.74 361.54 577.16 363.91 577.16 366.83C577.16 369.74 579.74 372.11 582.91 372.11C586.09 372.11 588.66 369.74 588.66 366.83C588.66 363.91 586.09 361.54 582.91 361.54Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19158_0)" d="M55.48 346.85C50.98 346.85 47.32 343.49 47.32 339.35C47.32 335.21 50.98 331.85 55.48 331.85C59.98 331.85 63.64 335.21 63.64 339.35C63.64 343.49 59.98 346.85 55.48 346.85ZM55.48 334.06C52.31 334.06 49.73 336.43 49.73 339.35C49.73 342.27 52.3 344.64 55.48 344.64C58.65 344.64 61.23 342.27 61.23 339.35C61.23 336.43 58.65 334.06 55.48 334.06Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19159_0)" d="M287.58 334.7C283.08 334.7 279.42 331.33 279.42 327.19C279.42 323.05 283.08 319.69 287.58 319.69C292.09 319.69 295.75 323.05 295.75 327.19C295.75 331.33 292.09 334.7 287.58 334.7ZM287.58 321.9C284.41 321.9 281.83 324.27 281.83 327.19C281.83 330.11 284.41 332.48 287.58 332.48C290.76 332.48 293.34 330.11 293.34 327.19C293.34 324.27 290.76 321.9 287.58 321.9Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19160_0)" d="M514.75 321.9C510.25 321.9 506.59 318.54 506.59 314.4C506.59 310.26 510.25 306.89 514.75 306.89C519.25 306.89 522.91 310.26 522.91 314.4C522.91 318.54 519.25 321.9 514.75 321.9ZM514.75 309.11C511.58 309.11 509 311.48 509 314.4C509 317.32 511.58 319.69 514.75 319.69C517.93 319.69 520.5 317.32 520.5 314.4C520.5 311.48 517.93 309.11 514.75 309.11Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19161_0)" d="M1278.37 309.97C1273.87 309.97 1270.21 306.6 1270.21 302.46C1270.21 298.32 1273.87 294.96 1278.37 294.96C1282.87 294.96 1286.53 298.32 1286.53 302.46C1286.53 306.6 1282.87 309.97 1278.37 309.97ZM1278.37 297.17C1275.19 297.17 1272.62 299.54 1272.62 302.46C1272.62 305.38 1275.18 307.75 1278.37 307.75C1281.54 307.75 1284.12 305.38 1284.12 302.46C1284.12 299.54 1281.54 297.17 1278.37 297.17Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19162_0)" d="M1695.9 333.61C1691.4 333.61 1687.74 330.24 1687.74 326.1C1687.74 321.96 1691.4 318.6 1695.9 318.6C1700.4 318.6 1704.06 321.96 1704.06 326.1C1704.06 330.24 1700.4 333.61 1695.9 333.61ZM1695.9 320.81C1692.72 320.81 1690.14 323.19 1690.14 326.1C1690.14 329.02 1692.72 331.39 1695.9 331.39C1699.07 331.39 1701.65 329.02 1701.65 326.1C1701.65 323.19 1699.07 320.81 1695.9 320.81Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19163_0)" d="M682.33 292.44C677.83 292.44 674.17 289.07 674.17 284.93C674.17 280.79 677.83 277.43 682.33 277.43C686.83 277.43 690.49 280.79 690.49 284.93C690.49 289.07 686.83 292.44 682.33 292.44ZM682.33 279.64C679.16 279.64 676.58 282.01 676.58 284.93C676.58 287.85 679.16 290.22 682.33 290.22C685.51 290.22 688.08 287.85 688.08 284.93C688.08 282.01 685.51 279.64 682.33 279.64Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19164_0)" d="M1135.89 317.56C1131.39 317.56 1127.72 314.2 1127.72 310.06C1127.72 305.92 1131.39 302.55 1135.89 302.55C1140.39 302.55 1144.05 305.92 1144.05 310.06C1144.05 314.2 1140.39 317.56 1135.89 317.56ZM1135.89 304.77C1132.72 304.77 1130.14 307.14 1130.14 310.06C1130.14 312.98 1132.71 315.35 1135.89 315.35C1139.06 315.35 1141.64 312.98 1141.64 310.06C1141.64 307.14 1139.06 304.77 1135.89 304.77Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19165_0)" d="M1141.19 254.59C1136.69 254.59 1133.04 251.22 1133.04 247.08C1133.04 242.94 1136.69 239.58 1141.19 239.58C1145.69 239.58 1149.37 242.94 1149.37 247.08C1149.37 251.22 1145.69 254.59 1141.19 254.59ZM1141.19 241.79C1138.03 241.79 1135.44 244.16 1135.44 247.08C1135.44 250 1138.03 252.37 1141.19 252.37C1144.38 252.37 1146.94 250 1146.94 247.08C1146.94 244.16 1144.38 241.79 1141.19 241.79Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19166_0)" d="M842.46 367.9C843.07 364.41 840.5 361.13 836.71 360.56C832.91 360 829.34 362.36 828.72 365.85C828.11 369.34 830.68 372.62 834.48 373.19C838.27 373.75 841.84 371.39 842.46 367.9Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19167_0)" d="M174.73 332.07C174.73 335.6 171.62 338.47 167.77 338.47C163.93 338.47 160.82 335.6 160.82 332.07C160.82 328.54 163.93 325.67 167.77 325.67C171.62 325.67 174.73 328.54 174.73 332.07Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19168_0)" d="M446.45 350.4C447.07 346.91 444.49 343.63 440.7 343.06C436.9 342.5 433.33 344.87 432.72 348.35C432.1 351.84 434.68 355.13 438.47 355.69C442.27 356.25 445.84 353.88 446.45 350.4Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19169_0)" d="M276.14 138.43C278.86 135.93 278.86 131.88 276.14 129.38C273.42 126.89 269.02 126.89 266.3 129.38C263.58 131.88 263.58 135.93 266.3 138.43C269.02 140.93 273.42 140.93 276.14 138.43Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19170_0)" d="M1565.51 262.32C1565.51 265.85 1562.39 268.72 1558.55 268.72C1554.71 268.72 1551.59 265.85 1551.59 262.32C1551.59 258.79 1554.71 255.93 1558.55 255.93C1562.39 255.93 1565.51 258.79 1565.51 262.32Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19171_0)" d="M1466.21 270.39C1466.21 273.92 1463.1 276.79 1459.26 276.79C1455.42 276.79 1452.3 273.92 1452.3 270.39C1452.3 266.86 1455.42 263.99 1459.26 263.99C1463.1 263.99 1466.21 266.86 1466.21 270.39Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19172_0)" d="M1532.15 378.58C1535.42 376.72 1536.42 372.78 1534.41 369.78C1532.38 366.77 1528.1 365.84 1524.83 367.7C1521.57 369.56 1520.55 373.5 1522.57 376.5C1524.6 379.51 1528.88 380.44 1532.15 378.58Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19173_0)" d="M1149.56 363.87C1150.46 360.43 1148.15 356.98 1144.41 356.17C1140.67 355.36 1136.92 357.49 1136.03 360.93C1135.15 364.37 1137.47 367.81 1141.21 368.62C1144.94 369.43 1148.68 367.3 1149.56 363.87Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19174_0)" d="M627.92 127.53C629.4 124.26 627.72 120.52 624.17 119.16C620.63 117.8 616.55 119.34 615.07 122.6C613.6 125.87 615.27 129.61 618.82 130.97C622.37 132.33 626.44 130.79 627.92 127.53Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19175_0)" d="M1079.41 305.15L1012.21 305.15L943.91 367.93L835.56 367.93L835.56 365.72L942.91 365.72L1011.21 302.94L1078.41 302.94L1134.99 250.92L1136.68 252.49L1079.41 305.15Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19176_0)" d="M290.11 334.02L291.82 332.45L309.1 348.34L439.6 348.34L439.6 350.55L308.1 350.55L290.11 334.02Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19177_0)" d="M1291.61 324.47L1212.97 324.47L1148.49 383.75L1145.07 380.62L1210.97 320.04L1289.62 320.04L1382.16 234.96L1382.16 186.77L1386.98 186.77L1386.98 236.8L1291.61 324.47Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19178_0)" d="M1528.48 372.11L1643.46 372.11L1689.54 329.75L1691.24 331.32L1644.46 374.33L1528.48 374.33L1528.48 372.11Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19179_0)" d="M1207.6 301.35L1271.41 301.35L1271.41 303.57L1208.6 303.57L1143.69 363.23L1141.99 361.67L1207.6 301.35Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19180_0)" d="M278.2 435.6L276.5 437.17L229.7 394.15L112.59 394.15L112.24 393.82L59.72 345.54L61.42 343.98L113.59 391.93L230.7 391.93L278.2 435.6Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19181_0)" d="M63.21 320.84L9.67 320.84L9.67 316.4L65.2 316.4L127.39 373.57L150.02 373.57L150.02 378.01L125.39 378.01L63.21 320.84Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19182_0)" d="M319.66 180.01L270.37 134.69L272.07 133.12L320.66 177.79L368.46 177.79L511.48 309.27L509.78 310.84L367.46 180.01L319.66 180.01Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19183_0)" d="M337.66 158.3L337.66 153.87L380.34 153.87L512.34 275.22L508.93 278.35L378.35 158.3L337.66 158.3Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19184_0)" d="M104.44 180.18L107.84 177.04L161.81 226.66L380.51 226.66L380.51 231.1L159.82 231.1L104.44 180.18Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19185_0)" d="M1150.62 198.74L1206.21 198.74L1206.21 203.18L1152.62 203.18L1062.63 285.91L1059.22 282.77L1150.62 198.74Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19186_0)" d="M891.11 325.75L817 325.75L786.88 353.45L783.47 350.31L815 321.32L889.11 321.32L978.49 239.15L1033.97 239.15L1033.97 243.59L980.48 243.59L891.11 325.75Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19187_0)" d="M1209.92 248.39L1270.92 248.39L1307.92 214.37L1309.62 215.94L1271.91 250.61L1210.92 250.61L1165.77 292.11L1164.06 290.55L1209.92 248.39Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19188_0)" d="M1551.59 344.99L1621.93 344.99L1698.21 274.86L1754.42 274.86L1754.42 279.29L1700.21 279.29L1623.92 349.42L1551.59 349.42L1551.59 344.99Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19189_0)" d="M1558.02 222.63L1648.65 222.63L1691.02 183.69L1694.43 186.82L1650.66 227.06L1560.02 227.06L1507.22 275.6L1503.81 272.47L1558.02 222.63Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19190_0)" d="M1637.86 252.71L1710.89 252.71L1746.41 220.05L1748.11 221.62L1711.89 254.93L1638.87 254.93L1573.32 315.18L1571.62 313.61L1637.86 252.71Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19191_0)" d="M1542.4 355.67C1537.24 355.67 1533.04 351.81 1533.04 347.06C1533.04 342.31 1537.24 338.45 1542.4 338.45C1547.56 338.45 1551.77 342.31 1551.77 347.06C1551.77 351.81 1547.56 355.67 1542.4 355.67ZM1542.4 342.88C1539.89 342.88 1537.85 344.75 1537.85 347.06C1537.85 349.36 1539.89 351.23 1542.4 351.23C1544.9 351.23 1546.95 349.36 1546.95 347.06C1546.95 344.75 1544.9 342.88 1542.4 342.88Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19192_0)" d="M1500.78 286.69C1495.61 286.69 1491.41 282.83 1491.41 278.08C1491.41 273.33 1495.61 269.47 1500.78 269.47C1505.93 269.47 1510.14 273.33 1510.14 278.08C1510.14 282.83 1505.93 286.69 1500.78 286.69ZM1500.78 273.9C1498.27 273.9 1496.22 275.77 1496.22 278.08C1496.22 280.38 1498.27 282.25 1500.78 282.25C1503.27 282.25 1505.32 280.38 1505.32 278.08C1505.32 275.77 1503.27 273.9 1500.78 273.9Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19193_0)" d="M778.22 364.46C773.05 364.46 768.85 360.59 768.85 355.84C768.85 351.09 773.05 347.23 778.22 347.23C783.38 347.23 787.58 351.09 787.58 355.84C787.58 360.59 783.38 364.46 778.22 364.46ZM778.22 351.66C775.71 351.66 773.67 353.54 773.67 355.84C773.67 358.14 775.71 360.02 778.22 360.02C780.72 360.02 782.76 358.14 782.76 355.84C782.76 353.54 780.72 351.66 778.22 351.66Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19194_0)" d="M1754.42 284.97C1759.16 284.97 1763.01 281.43 1763.01 277.07C1763.01 272.71 1759.16 269.18 1754.42 269.18C1749.68 269.18 1745.82 272.71 1745.82 277.07C1745.82 281.43 1749.68 284.97 1754.42 284.97Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19195_0)" d="M1696.01 192.55C1700.39 190.88 1702.47 186.26 1700.66 182.23C1698.84 178.2 1693.81 176.29 1689.43 177.96C1685.05 179.63 1682.97 184.24 1684.78 188.27C1686.6 192.3 1691.63 194.22 1696.01 192.55Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19196_0)" d="M1038.49 247.34C1042.53 245.05 1043.78 240.18 1041.28 236.47C1038.79 232.76 1033.49 231.61 1029.46 233.91C1025.43 236.2 1024.18 241.07 1026.67 244.78C1029.17 248.49 1034.46 249.63 1038.49 247.34Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19197_0)" d="M1751.81 223.54C1747.31 223.54 1743.65 220.18 1743.65 216.04C1743.65 211.9 1747.31 208.54 1751.81 208.54C1756.31 208.54 1759.97 211.9 1759.97 216.04C1759.97 220.18 1756.31 223.54 1751.81 223.54ZM1751.81 210.75C1748.64 210.75 1746.05 213.12 1746.05 216.04C1746.05 218.96 1748.64 221.33 1751.81 221.33C1754.99 221.33 1757.56 218.96 1757.56 216.04C1757.56 213.12 1754.99 210.75 1751.81 210.75Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19198_0)" d="M1313.95 217.84C1309.44 217.84 1305.79 214.47 1305.79 210.33C1305.79 206.19 1309.44 202.83 1313.95 202.83C1318.45 202.83 1322.11 206.19 1322.11 210.33C1322.11 214.47 1318.45 217.84 1313.95 217.84ZM1313.95 205.04C1310.78 205.04 1308.19 207.41 1308.19 210.33C1308.19 213.25 1310.78 215.62 1313.95 215.62C1317.13 215.62 1319.7 213.25 1319.7 210.33C1319.7 207.41 1317.13 205.04 1313.95 205.04Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19200_0)" d="M1578.9 316.84C1580.36 313.58 1578.68 309.84 1575.13 308.49C1571.58 307.13 1567.51 308.68 1566.04 311.95C1564.57 315.21 1566.25 318.95 1569.8 320.31C1573.35 321.66 1577.42 320.11 1578.9 316.84Z" id="Vector"/>
|
||||||
|
<path fill-rule="nonzero" fill-opacity="1.000000" fill="url(#paint_linear_47_19201_0)" d="M1167.57 297.24C1171.13 295.89 1172.81 292.15 1171.34 288.88C1169.87 285.62 1165.8 284.07 1162.26 285.42C1158.71 286.77 1157.02 290.51 1158.49 293.78C1159.96 297.04 1164.03 298.59 1167.57 297.24Z" id="Vector"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 94 KiB |
20
FRONT/DEV/public/assets/images/bigLogo.svg
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<svg width="529.000000" height="199.000000" viewBox="0 0 529 199" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<desc>
|
||||||
|
Created with Pixso.
|
||||||
|
</desc>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip47_19202">
|
||||||
|
<rect id="GO_NETS_LOGO 1" rx="0.000000" width="528.000000" height="198.000000" transform="translate(0.500000 0.500000)" fill="white" fill-opacity="0"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<rect id="GO_NETS_LOGO 1" rx="0.000000" width="528.000000" height="198.000000" transform="translate(0.500000 0.500000)" fill="#FFFFFF" fill-opacity="0"/>
|
||||||
|
<g clip-path="url(#clip47_19202)">
|
||||||
|
<path id="Vector" d="M337.61 194.16L361.93 194.16L361.93 1.04L337.85 1.04L337.85 87.6L337.85 101.58L337.85 125.69L337.85 159.44L193.39 1.28L169.31 1.28L169.55 194.16L193.63 194.16L193.63 169.33L193.39 125.93L193.39 101.82L193.39 87.84L193.39 71.93L193.39 36.48C196.12 39.54 199.81 43.64 204.46 48.78C209.12 53.92 217.71 63.41 230.23 77.23C242.75 91.05 254.58 104.03 265.74 116.17C276.89 128.3 288.21 140.68 299.69 153.3C311.16 165.91 320.35 175.92 327.25 183.31C334.16 190.71 337.61 194.32 337.61 194.16Z" fill="#006666" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||||
|
<path id="Vector" d="M72.5 0.02L193.33 0.02L193.33 72.62L169.16 72.62L169.16 24.22L72.5 24.22C59.12 24.22 47.72 28.94 38.3 38.38C28.87 47.82 24.16 59.23 24.16 72.62L24.16 193.62L0 193.62L0 72.62C0 52.62 7.08 35.52 21.26 21.32C35.44 7.12 52.52 0.02 72.5 0.02Z" fill="#716B64" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||||
|
<path id="Vector" d="M113.73 36.13C70.16 36.13 34.82 71.52 34.82 115.15C34.82 158.78 70.16 194.17 113.73 194.17C157.3 194.17 192.64 158.78 192.64 115.15C192.64 71.52 157.3 36.13 113.73 36.13ZM113.73 60.55C143.85 60.55 168.25 84.99 168.25 115.15C168.25 145.3 143.85 169.74 113.73 169.74C83.62 169.74 59.21 145.3 59.21 115.15C59.21 84.99 83.62 60.55 113.73 60.55Z" fill="#5166A6" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||||
|
<path id="Vector" d="M452.16 0C450.58 0 449.2 0.8 448.01 2.37C446.83 3.94 446.24 5.45 446.24 6.93L446.24 55.42L369.16 55.95C367.28 54.15 364.75 53.05 361.94 53.05C356.16 53.05 351.46 57.7 351.46 63.48C351.46 69.27 356.16 73.98 361.94 73.98C365.27 73.98 368.13 72.32 370.05 69.89L446.24 69.29L446.24 187.02C446.24 189.42 446.83 191.19 448.01 192.3C450.06 193.41 451.45 193.96 452.16 193.96C453.89 193.96 455.31 193.41 456.42 192.3C457.52 191 458.08 189.24 458.08 187.02L458.08 69.29L475.96 69.29C477.85 69.29 479.31 68.52 480.34 67.04C481.36 65.56 481.88 64.02 481.88 62.36C481.88 60.69 481.24 59.15 479.98 57.67C478.72 56.2 477.38 55.42 475.96 55.42L458.08 55.42L458.08 6.93C458.08 4.71 457.45 3.03 456.18 1.83C454.92 0.63 453.58 0 452.16 0Z" fill="#716B64" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||||
|
<path id="Vector" d="M392.43 86.42C386.33 86.42 381.24 88.64 377.15 93.12C373.07 97.61 371 102.56 371 107.94L371 134.5L346.43 134.5C344.58 131.55 341.46 129.46 337.73 129.46C331.95 129.46 327.31 134.17 327.31 139.95C327.31 145.74 331.95 150.39 337.73 150.39C341.46 150.39 344.58 148.36 346.43 145.41L371 145.41L371 172.44C371 178.47 373.05 183.57 377.21 187.73C381.37 191.9 386.48 193.96 392.43 193.96L430.01 193.96L442.75 193.96C444.59 196.94 447.68 199 451.45 199C457.22 199 461.92 194.35 461.92 188.56C461.92 182.78 457.22 178.07 451.45 178.07C447.65 178.07 444.52 180.21 442.69 183.23L430.08 183.23C430.05 183.23 430.04 183.17 430.01 183.17L392.43 183.17C389.49 183.17 386.93 182.17 384.85 180.09C382.77 178 381.77 175.45 381.77 172.44L381.77 145.53L430.01 145.53C433.67 145.53 435.52 143.72 435.52 140.13L435.52 107.94C435.52 101.91 433.46 96.81 429.31 92.65C425.15 88.49 420.05 86.42 414.03 86.42L392.43 86.42ZM392.43 97.15L414.03 97.15C417.04 97.15 419.59 98.21 421.67 100.3C423.75 102.38 424.75 104.93 424.75 107.94L424.75 134.86L381.77 134.86L381.77 107.94C381.77 104.93 382.77 102.38 384.85 100.3C386.93 98.21 389.49 97.15 392.43 97.15Z" fill="#5166A6" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||||
|
<path id="Vector" d="M486.97 86.66C480.09 86.66 474.48 89.03 470.04 93.83C465.88 98.28 463.76 103.65 463.76 109.96C463.76 111.18 463.93 112.64 464.29 114.29L465.83 119.56C467.33 122.64 469.49 125.38 472.28 127.74L491.23 143.21L513.6 161.47C516.61 164.7 518.1 167.87 518.1 170.96C518.1 174.25 516.96 177.16 514.67 179.67C512.38 182.18 509.44 183.46 505.79 183.46L491.05 183.46C487.4 183.46 484.05 182.66 481.05 181.15C477.55 178.96 464.68 168.7 462.34 166.1C462.37 165.77 462.52 165.48 462.52 165.15C462.52 159.36 457.82 154.65 452.04 154.65C446.26 154.65 441.62 159.36 441.62 165.15C441.62 170.93 446.26 175.64 452.04 175.64C453.84 175.64 455.47 175.08 456.95 174.28C457.06 174.39 457.15 174.52 457.25 174.63C462.35 180.03 472.83 189.16 476.55 190.99C480.84 193.11 485.68 194.19 491.05 194.19L505.79 194.19C512.59 194.19 518.22 191.77 522.66 186.96C526.82 182.52 528.88 177.15 528.88 170.84C528.88 167.54 528.18 164.38 526.75 161.29C525.38 158 523.26 155.26 520.47 153.11L479.15 119.32C476.07 116.67 474.53 113.56 474.53 109.9C474.53 106.53 475.68 103.6 477.97 101.13C480.26 98.65 483.24 97.39 486.97 97.39L501.71 97.39C503.43 97.39 505.09 97.61 506.74 98.04L511.59 99.76C514.74 101.34 517.29 103.41 519.23 105.99C519.73 106.63 520.11 107.17 520.47 107.53C520.61 107.67 520.79 107.8 521 107.94L523.49 108.24C524.85 108.24 526.13 107.76 527.28 106.76C528.42 105.54 529 104.25 529 102.96L528.64 101.18L527.81 99.52C524.66 95.29 520.84 92.11 516.33 89.92C511.82 87.73 506.93 86.66 501.71 86.66L486.97 86.66Z" fill="#5166A6" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||||
|
<path id="Текст" d="" fill="#000000" fill-opacity="1.000000" fill-rule="evenodd"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.5 KiB |
15
FRONT/DEV/public/assets/images/bukvi.svg
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<svg width="42" height="13" viewBox="0 0 42 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_1_2)">
|
||||||
|
<path d="M4.79966 0.00675106H12.7991V4.80641H11.1992V1.60664H4.79966C3.91972 1.60664 3.15978 1.91328 2.53316 2.53991C1.90653 3.16653 1.59989 3.91314 1.59989 4.80641V12.8058H5.81835e-07V4.80641C5.81835e-07 3.48651 0.466634 2.35325 1.41323 1.41998C2.3465 0.473385 3.47975 0.00675106 4.79966 0.00675106Z" fill="black"/>
|
||||||
|
<path d="M7.93277 5.73968H12.7858V10.5927C12.7858 11.166 12.5858 11.6593 12.1725 12.0592C11.7725 12.4592 11.2792 12.6725 10.6926 12.6725H5.83959V7.81953C5.83959 7.24624 6.03957 6.75294 6.45288 6.33964C6.86618 5.92633 7.34615 5.72635 7.93277 5.72635V5.73968ZM12.0925 10.5927V6.43296H7.93277C7.54613 6.43296 7.22616 6.56629 6.94618 6.83294C6.67953 7.09958 6.54621 7.43289 6.54621 7.81953V11.9792H10.7059C11.0926 11.9792 11.4125 11.8459 11.6925 11.5793C11.9592 11.3126 12.0925 10.9793 12.0925 10.5927Z" fill="black"/>
|
||||||
|
<path d="M22.1051 11.9126C22.1051 12.3792 21.9185 12.6192 21.5318 12.6192C21.2785 12.6192 21.0785 12.4859 20.9319 12.2192L15.8389 3.17986V11.9792C15.8389 12.3925 15.6256 12.6059 15.2123 12.6059C14.799 12.6059 14.5856 12.3925 14.5856 11.9792V0.7267C14.5856 0.260067 14.7856 0.0334158 15.1723 0.0334158C15.4256 0.0334158 15.6256 0.16674 15.7589 0.420055L20.8519 9.45942V0.660038C20.8519 0.246734 21.0652 0.0334158 21.4785 0.0334158C21.8918 0.0334158 22.1051 0.246734 22.1051 0.660038V11.8992V11.9126Z" fill="black"/>
|
||||||
|
<path d="M24.8116 9.61941V10.606C24.8116 10.8727 24.9049 11.1126 25.1049 11.2993C25.3049 11.4859 25.5315 11.5926 25.7982 11.5926H28.278C28.6113 11.5926 28.7713 11.7526 28.7713 12.0859C28.7713 12.4192 28.6113 12.5792 28.278 12.5792H25.7982C25.2516 12.5792 24.7849 12.3792 24.3983 11.9926C24.0116 11.6059 23.8117 11.1393 23.8117 10.5927V7.61955C23.8117 7.07292 24.0116 6.60629 24.3983 6.21965C24.7849 5.83301 25.2516 5.63302 25.7982 5.63302H26.7848C27.3314 5.63302 27.798 5.83301 28.1847 6.21965C28.5713 6.60629 28.7713 7.07292 28.7713 7.61955V9.11277C28.7713 9.44608 28.6113 9.60607 28.278 9.60607H24.8116V9.61941ZM24.8116 8.63281H27.7847V7.64621C27.7847 7.37956 27.6914 7.13958 27.4914 6.95293C27.2914 6.76627 27.0648 6.65961 26.7981 6.65961H25.8115C25.5449 6.65961 25.3049 6.75294 25.1182 6.95293C24.9316 7.15291 24.8249 7.37956 24.8249 7.64621V8.63281H24.8116Z" fill="black"/>
|
||||||
|
<path d="M32.7444 5.65968H34.2376C34.5709 5.65968 34.7309 5.81967 34.7309 6.15298C34.7309 6.48629 34.5709 6.64628 34.2376 6.64628H32.7444V12.0992C32.7444 12.4325 32.5844 12.5925 32.2511 12.5925C31.9177 12.5925 31.7578 12.4325 31.7578 12.0992V6.64628H31.2645C30.9311 6.64628 30.7712 6.48629 30.7712 6.15298C30.7712 5.81967 30.9311 5.65968 31.2645 5.65968H31.7578V3.17986C31.7578 2.84655 31.9177 2.68656 32.2511 2.68656C32.5844 2.68656 32.7444 2.84655 32.7444 3.17986V5.65968Z" fill="black"/>
|
||||||
|
<path d="M41.6504 6.63295C41.6504 6.76627 41.5971 6.88626 41.5037 6.99292C41.4104 7.09958 41.2904 7.15291 41.1571 7.15291C41.0771 7.15291 40.9971 7.12625 40.9171 7.09958C40.3438 6.8196 39.7839 6.67295 39.2239 6.67295C38.6639 6.67295 38.224 6.80627 37.8507 7.05959C37.7573 7.12625 37.7173 7.21958 37.7173 7.3529C37.7173 7.48622 37.7573 7.56622 37.8507 7.63288L41.0104 9.71273C41.4104 9.97938 41.6104 10.366 41.6104 10.8726C41.6104 11.3793 41.4104 11.7659 41.0104 12.0326C40.4371 12.4059 39.7972 12.5925 39.0772 12.5925C38.3573 12.5925 37.6907 12.4059 37.0241 12.0459C36.8507 11.9526 36.7574 11.8059 36.7574 11.6193C36.7574 11.4859 36.8107 11.3659 36.9041 11.2593C36.9974 11.1526 37.1174 11.0993 37.2507 11.0993C37.3307 11.0993 37.424 11.126 37.504 11.166C38.0106 11.4459 38.5306 11.5926 39.0639 11.5926C39.5972 11.5926 40.0505 11.4593 40.4638 11.1793C40.5705 11.1126 40.6371 10.9926 40.6371 10.846C40.6371 10.6993 40.5838 10.5927 40.4638 10.5127L37.2907 8.43282C36.9307 8.19284 36.7574 7.83287 36.7574 7.33957C36.7574 6.84627 36.9307 6.47296 37.2907 6.23298C37.8507 5.85967 38.4906 5.67302 39.2372 5.67302C39.9838 5.67302 40.6505 5.84634 41.3437 6.19298C41.5304 6.28631 41.6371 6.43296 41.6371 6.63295H41.6504Z" fill="black"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1_2">
|
||||||
|
<rect width="41.6504" height="12.8126" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
12
FRONT/DEV/public/assets/images/cancel.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="white" width="800px" height="800px" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<g>
|
||||||
|
|
||||||
|
<path d="M10,1a9,9,0,1,0,9,9A9,9,0,0,0,10,1Zm0,16.4A7.4,7.4,0,1,1,17.4,10,7.41,7.41,0,0,1,10,17.4ZM13.29,5.29,10,8.59,6.71,5.29,5.29,6.71,8.59,10l-3.3,3.29,1.42,1.42L10,11.41l3.29,3.3,1.42-1.42L11.41,10l3.3-3.29Z"/>
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 459 B |
5
FRONT/DEV/public/assets/images/check.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18 20.75H6C5.27065 20.75 4.57118 20.4603 4.05546 19.9445C3.53973 19.4288 3.25 18.7293 3.25 18V6C3.25 5.27065 3.53973 4.57118 4.05546 4.05546C4.57118 3.53973 5.27065 3.25 6 3.25H14.86C15.0589 3.25 15.2497 3.32902 15.3903 3.46967C15.531 3.61032 15.61 3.80109 15.61 4C15.61 4.19891 15.531 4.38968 15.3903 4.53033C15.2497 4.67098 15.0589 4.75 14.86 4.75H6C5.66848 4.75 5.35054 4.8817 5.11612 5.11612C4.8817 5.35054 4.75 5.66848 4.75 6V18C4.75 18.3315 4.8817 18.6495 5.11612 18.8839C5.35054 19.1183 5.66848 19.25 6 19.25H18C18.3315 19.25 18.6495 19.1183 18.8839 18.8839C19.1183 18.6495 19.25 18.3315 19.25 18V10.29C19.25 10.0911 19.329 9.90032 19.4697 9.75967C19.6103 9.61902 19.8011 9.54 20 9.54C20.1989 9.54 20.3897 9.61902 20.5303 9.75967C20.671 9.90032 20.75 10.0911 20.75 10.29V18C20.75 18.7293 20.4603 19.4288 19.9445 19.9445C19.4288 20.4603 18.7293 20.75 18 20.75Z" fill="white"/>
|
||||||
|
<path d="M10.5 15.25C10.3071 15.2352 10.1276 15.1455 10 15L7.00001 12C6.93317 11.86 6.91136 11.7028 6.93759 11.5499C6.96382 11.3971 7.03679 11.2561 7.14646 11.1464C7.25613 11.0368 7.3971 10.9638 7.54996 10.9376C7.70282 10.9113 7.86006 10.9331 8.00001 11L10.47 13.47L19 4.99998C19.14 4.93314 19.2972 4.91133 19.4501 4.93756C19.6029 4.96379 19.7439 5.03676 19.8536 5.14643C19.9632 5.2561 20.0362 5.39707 20.0624 5.54993C20.0887 5.70279 20.0669 5.86003 20 5.99998L11 15C10.8724 15.1455 10.693 15.2352 10.5 15.25Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
5
FRONT/DEV/public/assets/images/edit.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M21.2799 6.40005L11.7399 15.94C10.7899 16.89 7.96987 17.33 7.33987 16.7C6.70987 16.07 7.13987 13.25 8.08987 12.3L17.6399 2.75002C17.8754 2.49308 18.1605 2.28654 18.4781 2.14284C18.7956 1.99914 19.139 1.92124 19.4875 1.9139C19.8359 1.90657 20.1823 1.96991 20.5056 2.10012C20.8289 2.23033 21.1225 2.42473 21.3686 2.67153C21.6147 2.91833 21.8083 3.21243 21.9376 3.53609C22.0669 3.85976 22.1294 4.20626 22.1211 4.55471C22.1128 4.90316 22.0339 5.24635 21.8894 5.5635C21.7448 5.88065 21.5375 6.16524 21.2799 6.40005V6.40005Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11 4H6C4.93913 4 3.92178 4.42142 3.17163 5.17157C2.42149 5.92172 2 6.93913 2 8V18C2 19.0609 2.42149 20.0783 3.17163 20.8284C3.92178 21.5786 4.93913 22 6 22H17C19.21 22 20 20.2 20 18V13" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
11
FRONT/DEV/public/assets/images/ethernet.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="white" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 490 490" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M0,0v490h490V0H0z M430.1,332.9h-87.5v50.9h-33.1v50.9H180.4v-50.6h-33.1v-51.3H59.9v-278h46.7v66.5h38.5V54.8h40.8v66.5
|
||||||
|
h38.5V54.8h40.8v66.5h38.5V54.8h40.8v66.5H383V54.8h46.7v278.1L430.1,332.9L430.1,332.9z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 579 B |
BIN
FRONT/DEV/public/assets/images/gossys.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
23
FRONT/DEV/public/assets/images/logo.svg
Normal file
|
After Width: | Height: | Size: 16 KiB |
17
FRONT/DEV/public/assets/images/signal.svg
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<svg version="1.1" id="signalSvg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 296 235" style="enable-background:new 0 0 296 235;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:var(--st1-color, white);}
|
||||||
|
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:var(--st2-color, white);}
|
||||||
|
.st3{fill-rule:evenodd;clip-rule:evenodd;fill:var(--st3-color, white);}
|
||||||
|
.st4{fill-rule:evenodd;clip-rule:evenodd;fill:var(--st4-color, white);}
|
||||||
|
.st5{fill-rule:evenodd;clip-rule:evenodd;fill:var(--st5-color, white);}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<polygon class="st1" points="47,138 47,188 0,188 0,164 "/>
|
||||||
|
<polygon class="st2" points="109,104 109,188 62,188 62,130 "/>
|
||||||
|
<polygon class="st3" points="171,69 171,188 124,188 124,95 "/>
|
||||||
|
<polygon class="st4" points="234,35 234,188 187,188 187,61 "/>
|
||||||
|
<polygon class="st5" points="296,0 296,188 249,188 249,26 "/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 966 B |
1
FRONT/DEV/public/assets/images/vite.svg
Normal 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="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
27
FRONT/DEV/public/assets/images/vpn.svg
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="white" stroke="none">
|
||||||
|
<path d="M2350 5050 c-581 -194 -1241 -344 -1878 -426 l-133 -17 4 -976 c4
|
||||||
|
-1045 4 -1044 58 -1310 174 -864 747 -1624 1524 -2023 72 -36 244 -118 383
|
||||||
|
-182 l253 -116 242 111 c344 158 493 235 647 337 649 426 1104 1093 1264 1849
|
||||||
|
59 280 59 275 63 1336 3 581 0 977 -5 977 -24 0 -392 51 -502 70 -552 94
|
||||||
|
-1141 242 -1607 404 -56 20 -103 36 -105 35 -2 0 -95 -32 -208 -69z m379 -976
|
||||||
|
c31 -8 94 -33 141 -56 69 -33 101 -57 171 -128 137 -137 199 -285 199 -476 0
|
||||||
|
-287 -178 -543 -444 -640 l-61 -22 -3 -266 -2 -266 187 0 c169 0 191 -2 230
|
||||||
|
-21 92 -44 122 -171 60 -252 -46 -61 -72 -67 -286 -67 l-191 0 0 -170 0 -170
|
||||||
|
173 0 c191 0 232 -7 281 -50 45 -40 60 -79 54 -141 -8 -85 -69 -143 -161 -154
|
||||||
|
-29 -4 -167 -5 -307 -3 -285 3 -294 5 -347 75 l-28 36 -5 725 -5 724 -64 24
|
||||||
|
c-268 101 -441 352 -441 638 0 115 20 198 71 305 54 110 179 239 284 293 166
|
||||||
|
84 326 104 494 62z"/>
|
||||||
|
<path d="M2430 3728 c-66 -25 -145 -101 -180 -173 -20 -42 -25 -65 -25 -140 0
|
||||||
|
-79 4 -97 28 -147 36 -74 119 -150 189 -174 30 -10 82 -18 118 -18 36 0 88 8
|
||||||
|
118 18 70 24 153 100 189 174 24 50 28 68 28 147 0 109 -23 163 -100 241 -91
|
||||||
|
92 -238 121 -365 72z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
6
FRONT/DEV/public/assets/images/wifi.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 7L1.17157 5.82843C2.98259 4.01741 5.43884 3 8 3C10.5612 3 13.0174 4.01742 14.8284 5.82843L16 7L14.5858 8.41421L13.4142 7.24264C11.9783 5.8067 10.0307 5 8 5C5.96928 5 4.02173 5.8067 2.58579 7.24264L1.41421 8.41421L0 7Z" fill="white"/>
|
||||||
|
<path d="M4.24264 11.2426L2.82843 9.82843L4 8.65685C5.06086 7.59599 6.49971 7 8 7C9.50029 7 10.9391 7.59599 12 8.65686L13.1716 9.82843L11.7574 11.2426L10.5858 10.0711C9.89999 9.38527 8.96986 9 8 9C7.03014 9 6.1 9.38527 5.41421 10.0711L4.24264 11.2426Z" fill="white"/>
|
||||||
|
<path d="M8 15L5.65685 12.6569L6.82842 11.4853C7.13914 11.1746 7.56057 11 8 11C8.43942 11 8.86085 11.1746 9.17157 11.4853L10.3431 12.6569L8 15Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 906 B |
160
FRONT/DEV/src/api/request.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import axios, { AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
const axiosWithoutCredentials = axios.create({
|
||||||
|
baseURL: `http://localhost:8001`,
|
||||||
|
})
|
||||||
|
|
||||||
|
export class NetworkService {
|
||||||
|
static createAxiosInstance(ipAddress: string) {
|
||||||
|
return axios.create({
|
||||||
|
baseURL: `http://${ipAddress}:8001`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async checkIp(ipAddress: string, data: any): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.post('/ip/check_ip', data);
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async saveIp(ipAddress: string, data: any): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.post('/ip/apply_ip', data);
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getHostname(): Promise<AxiosResponse>{
|
||||||
|
try{
|
||||||
|
const response = await axiosWithoutCredentials.get('/network/get_hostname')
|
||||||
|
return response
|
||||||
|
} catch (error: any){
|
||||||
|
return error.response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createUser(ipAddress: string, data: any): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.post('/auth/register', data);
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async login(ipAddress: string, data: any): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.post('/auth/login', data);
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async logout(ipAddress: string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.get('/auth/logout');
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async checkServerConf(ipAddress: string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.get('/auth/check_json');
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async whoami(ipAddress: string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.get('/auth/me');
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async scanUser(ipAddress: string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.get('/subnet/devices');
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async scanWifi(ipAddress: string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.get('/subnet/wifi');
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static async addNote(ipAddress: string, address:string, note: string,type:string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.post(`/subnet/${type}/${address}/note`, {
|
||||||
|
note: note
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static async editNote(ipAddress: string, address:string, note: string,type:string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.put(`/subnet/${type}/${address}/note`, {
|
||||||
|
note: note
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static async deleteNote(ipAddress: string, address:string, type:string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.delete(`/subnet/${type}/${address}/note`);
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static async statusNetwork(ipAddress: string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.get(`/subnet/wifi/status`);
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static async connectNetwork(ipAddress: string, password:string, address: string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.post(`/subnet/wifi/connect`, {
|
||||||
|
ssid: address,
|
||||||
|
password: password,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static async disconnectNetwork(ipAddress: string, ssid: string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.post(`/subnet/wifi/disconnect`, {
|
||||||
|
ssid: ssid,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
108
FRONT/DEV/src/assets/css/flow.css
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
.react-flow {
|
||||||
|
/* Custom Variables */
|
||||||
|
--xy-theme-selected: #F57DBD;
|
||||||
|
--xy-theme-hover: #C5C5C5;
|
||||||
|
--xy-theme-edge-hover: black;
|
||||||
|
--xy-theme-color-focus: #E8E8E8;
|
||||||
|
|
||||||
|
/* Built-in Variables see https://reactflow.dev/learn/customization/theming */
|
||||||
|
--xy-node-border-default: 1px solid #EDEDED;
|
||||||
|
|
||||||
|
/* --xy-node-boxshadow-default:
|
||||||
|
0px 3.54px 4.55px 0px #00000005,
|
||||||
|
0px 3.54px 4.55px 0px #0000000D,
|
||||||
|
0px 0.51px 1.01px 0px #0000001A; */
|
||||||
|
|
||||||
|
--xy-node-border-radius-default: 8px;
|
||||||
|
|
||||||
|
--xy-handle-background-color-default: #ffffff;
|
||||||
|
--xy-handle-border-color-default: #AAAAAA;
|
||||||
|
|
||||||
|
--xy-edge-label-color-default: #505050;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow.dark {
|
||||||
|
--xy-node-boxshadow-default:
|
||||||
|
0px 3.54px 4.55px 0px rgba(255, 255, 255, 0.05), /* light shadow */
|
||||||
|
0px 3.54px 4.55px 0px rgba(255, 255, 255, 0.13), /* medium shadow */
|
||||||
|
0px 0.51px 1.01px 0px rgba(255, 255, 255, 0.2); /* smallest shadow */
|
||||||
|
--xy-theme-color-focus: #535353;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Customizing Default Theming */
|
||||||
|
|
||||||
|
.react-flow__node {
|
||||||
|
/* box-shadow: var(--xy-node-boxshadow-default);
|
||||||
|
border-radius: var(--xy-node-border-radius-default);
|
||||||
|
background-color: var(--xy-node-background-color-default); */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
flex-direction: column;
|
||||||
|
/* border: var(--xy-node-border-default);
|
||||||
|
color: var(--xy-node-color, var(--xy-node-color-default)); */
|
||||||
|
pointer-events: all;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__node.selectable:focus {
|
||||||
|
box-shadow: 0px 0px 0px 4px var(--xy-theme-color-focus);
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__node.selectable:focus:active {
|
||||||
|
box-shadow: var(--xy-node-boxshadow-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.react-flow__node.selectable:hover,
|
||||||
|
.react-flow__node.draggable:hover {
|
||||||
|
border-color: var(--xy-theme-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__node.selectable.selected {
|
||||||
|
border-color: var(--xy-theme-selected);
|
||||||
|
box-shadow: var(--xy-node-boxshadow-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__node-group {
|
||||||
|
background-color: rgba(207, 182, 255, 0.4);
|
||||||
|
border-color: #9E86ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__edge.selectable:hover .react-flow__edge-path,
|
||||||
|
.react-flow__edge.selectable.selected .react-flow__edge-path {
|
||||||
|
stroke: var(--xy-theme-edge-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__handle {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__handle.connectionindicator:hover {
|
||||||
|
pointer-events: all;
|
||||||
|
border-color: var(--xy-theme-edge-hover);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__handle.connectionindicator:focus ,
|
||||||
|
.react-flow__handle.connectingfrom,
|
||||||
|
.react-flow__handle.connectingto {
|
||||||
|
border-color: var(--xy-theme-edge-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__node-resizer {
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__resize-control.handle {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-color: #9E86ED;
|
||||||
|
border-radius: 0;
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
}
|
||||||
36
FRONT/DEV/src/assets/css/index.css
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button{
|
||||||
|
background-color: #006666;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover{
|
||||||
|
background-color: #255252;
|
||||||
|
}
|
||||||
|
}
|
||||||
137
FRONT/DEV/src/components/UI/Dropdown/DeviceCard.tsx
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
import styled, { css } from 'styled-components';
|
||||||
|
import { DeviceUser } from "../../../store/useDeviceStore";
|
||||||
|
import {buttonNames} from "../../../const/buttons/names";
|
||||||
|
import {ModalMap} from "../../modalwindows/windowsType";
|
||||||
|
import {IconComponent} from "../../../const/buttons/icons";
|
||||||
|
import {useModalContext} from "../../../store";
|
||||||
|
|
||||||
|
interface DeviceCardProps {
|
||||||
|
device: DeviceUser;
|
||||||
|
status: 'online' | 'offline';
|
||||||
|
}
|
||||||
|
|
||||||
|
const CardWrapper = styled.div<{ status: 'online' | 'offline' }>`
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CardHeader = styled.div<{ status: 'online' | 'offline' }>`
|
||||||
|
background: ${({ status, theme }) =>
|
||||||
|
status === 'online' ? theme.successColor : theme.offlineColor};
|
||||||
|
color: white;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
const CardContent = styled.div<{ isExpanded: boolean }>`
|
||||||
|
max-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
padding: 0 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
${({ isExpanded }) => isExpanded && css`
|
||||||
|
max-height: 500px;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 16px;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InfoRow = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Label = styled.span`
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Value = styled.span`
|
||||||
|
color: #333;
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
const DeviceCard: React.FC<DeviceCardProps> = ({ device, status }) => {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const toggleExpanded = () => setIsExpanded(!isExpanded);
|
||||||
|
const { openModal } = useModalContext();
|
||||||
|
|
||||||
|
const openModalNote = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openModal(ModalMap['6'], {
|
||||||
|
address: device.mac,
|
||||||
|
currentNote: device.note || ''
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardWrapper status={status} onClick={toggleExpanded}>
|
||||||
|
<CardHeader status={status}>
|
||||||
|
<p>
|
||||||
|
MAC: {device.mac}
|
||||||
|
</p>
|
||||||
|
<span onClick={openModalNote}>
|
||||||
|
<IconComponent name={buttonNames[16]} width="28" height="28" setColor={'white'} />
|
||||||
|
</span>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent isExpanded={isExpanded}>
|
||||||
|
<InfoRow>
|
||||||
|
<Label>Имя:</Label>
|
||||||
|
<Value>{device.hostname || device.ip}</Value>
|
||||||
|
</InfoRow>
|
||||||
|
|
||||||
|
<InfoRow>
|
||||||
|
<Label>Производитель:</Label>
|
||||||
|
<Value>{device.vendor || 'Неизвестно'}</Value>
|
||||||
|
</InfoRow>
|
||||||
|
|
||||||
|
<InfoRow>
|
||||||
|
<Label>IP адрес:</Label>
|
||||||
|
<Value>{device.ip}</Value>
|
||||||
|
</InfoRow>
|
||||||
|
|
||||||
|
{device.open_ports?.length > 0 && (
|
||||||
|
<InfoRow>
|
||||||
|
<Label>Открытые порты:</Label>
|
||||||
|
<Value>{device.open_ports.join(', ')}</Value>
|
||||||
|
</InfoRow>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<InfoRow>
|
||||||
|
<Label>Последняя активность:</Label>
|
||||||
|
<Value>{device.last_seen}</Value>
|
||||||
|
</InfoRow>
|
||||||
|
|
||||||
|
<InfoRow>
|
||||||
|
<Label>Заметка:</Label>
|
||||||
|
<Value>{device.note || '-'}</Value>
|
||||||
|
</InfoRow>
|
||||||
|
</CardContent>
|
||||||
|
</CardWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeviceCard;
|
||||||
130
FRONT/DEV/src/components/UI/Dropdown/DropdownType.tsx
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
import React, {useEffect, useRef, useState} from 'react';
|
||||||
|
import styled from "styled-components";
|
||||||
|
import {buttonNames} from "../../../const/buttons/names";
|
||||||
|
import {IconComponent} from "../../../const/buttons/icons";
|
||||||
|
|
||||||
|
type Option = {
|
||||||
|
value: number,
|
||||||
|
label: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropdownTypeProps {
|
||||||
|
options: Option[];
|
||||||
|
label?: string;
|
||||||
|
onChange?: (selectedItem: Option) => void;
|
||||||
|
value: Option | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DropdownWrapper = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
right:20px;
|
||||||
|
display: inline-block;
|
||||||
|
background-color: ${({theme}) => theme.background};
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 10;
|
||||||
|
`
|
||||||
|
const Label = styled.p`
|
||||||
|
color: ${({theme}) => theme.textColor};
|
||||||
|
font-size: 16px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Dropdown = styled.div`
|
||||||
|
background-color: ${({theme}) => theme.primary};
|
||||||
|
border-radius: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all .3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
margin-top: 6px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const DropdownHeader = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 200px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const DropdownContent = styled.div<{$isOpen: boolean}>`
|
||||||
|
max-height: ${({$isOpen}) => $isOpen ? '500px' : '0'};
|
||||||
|
opacity: ${({$isOpen}) => $isOpen ? 1 : 0};
|
||||||
|
transition: all .3s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
`
|
||||||
|
|
||||||
|
const DropdownRow = styled.button`
|
||||||
|
padding: 12px 24px;
|
||||||
|
background-color: transparent;
|
||||||
|
text-align: start;
|
||||||
|
font-size: 16px;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
color: ${({theme}) => theme.textColor};
|
||||||
|
transition: all .3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: ${({theme}) => theme.background};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const DropdownArrow = styled.div<{ $isOpen: boolean }>`
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
transform: rotate(${({ $isOpen }) => ($isOpen ? "90deg" : "0deg")});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DropdownType = ({options, value, onChange, label}: DropdownTypeProps) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
function toggleOpen() {
|
||||||
|
setIsOpen((prev) => !prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelect(option: Option) {
|
||||||
|
onChange?.(option);
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleClickOutside(event: MouseEvent) {
|
||||||
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownWrapper ref={dropdownRef}>
|
||||||
|
<Label>{label}</Label>
|
||||||
|
<Dropdown>
|
||||||
|
<DropdownHeader onClick={toggleOpen}>
|
||||||
|
<p>{value?.label || 'Select an option'}</p>
|
||||||
|
<DropdownArrow $isOpen={isOpen}>
|
||||||
|
<IconComponent name={buttonNames[6]} />
|
||||||
|
</DropdownArrow>
|
||||||
|
</DropdownHeader>
|
||||||
|
<DropdownContent $isOpen={isOpen}>
|
||||||
|
{options.map((option, i) => (
|
||||||
|
<DropdownRow key={i} onClick={() => handleSelect(option)}>
|
||||||
|
{option.label}
|
||||||
|
</DropdownRow>
|
||||||
|
))}
|
||||||
|
</DropdownContent>
|
||||||
|
</Dropdown>
|
||||||
|
</DropdownWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropdownType;
|
||||||
65
FRONT/DEV/src/components/UI/Pagination.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled from "styled-components";
|
||||||
|
import {buttonNames} from "../../const/buttons/names";
|
||||||
|
import {IconComponent} from "../../const/buttons/icons";
|
||||||
|
|
||||||
|
const PaginateWrapper = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
`
|
||||||
|
const Page = styled.button`
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background: ${(props => props.theme.primary)};
|
||||||
|
color: ${(props => props.theme.textColor)};
|
||||||
|
border: 1px solid ${(props => props.theme.borderColor)};
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
transition: .4s ease;
|
||||||
|
&:hover {
|
||||||
|
background: ${(props => props.theme.background)};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
interface PaginationProps {
|
||||||
|
currentPage: number;
|
||||||
|
totalPages: number;
|
||||||
|
onPrevPage: () => void;
|
||||||
|
onNextPage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Pagination = ({currentPage, totalPages, onPrevPage, onNextPage}: PaginationProps) => {
|
||||||
|
return (
|
||||||
|
<PaginateWrapper>
|
||||||
|
<Page
|
||||||
|
onClick={onPrevPage}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
>
|
||||||
|
<IconComponent
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
name={buttonNames[17]}
|
||||||
|
/>
|
||||||
|
</Page>
|
||||||
|
|
||||||
|
<Page
|
||||||
|
onClick={onNextPage}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
>
|
||||||
|
<IconComponent
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
name={buttonNames[6]}
|
||||||
|
/>
|
||||||
|
</Page>
|
||||||
|
</PaginateWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Pagination;
|
||||||
29
FRONT/DEV/src/components/UI/animDot.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import styled, { keyframes } from "styled-components";
|
||||||
|
|
||||||
|
const blink = keyframes`
|
||||||
|
0%, 100% { opacity: 0.3; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface DotProps {
|
||||||
|
$delay: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dot = styled.span<DotProps>`
|
||||||
|
animation: ${blink} 1s infinite;
|
||||||
|
animation-delay: ${({ $delay }) => $delay}s;
|
||||||
|
margin: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AnimDot = () => (
|
||||||
|
<Wrapper>
|
||||||
|
<Dot $delay={0}>.</Dot>
|
||||||
|
<Dot $delay={0.2}>.</Dot>
|
||||||
|
<Dot $delay={0.4}>.</Dot>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
75
FRONT/DEV/src/components/UI/buttons/mainButton.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { useTheme } from "styled-components";
|
||||||
|
import { IconComponent } from "../../../const/buttons/icons";
|
||||||
|
|
||||||
|
const Button = styled.button.withConfig({
|
||||||
|
shouldForwardProp: (prop) => !['color', '$hoverсolor', 'size', '$iconpos'].includes(prop),
|
||||||
|
})<{ color: string; size?: string, $hoverсolor: string, $iconpos: string }>`
|
||||||
|
background-color: ${(props) => props.color};
|
||||||
|
border: none;
|
||||||
|
padding: ${(props) =>
|
||||||
|
props.size === "small"
|
||||||
|
? "10px 28px"
|
||||||
|
: props.size === "big"
|
||||||
|
? "10px 20px"
|
||||||
|
: "12px 20px"}; // По умолчанию
|
||||||
|
border-radius: 8px;
|
||||||
|
color: white;
|
||||||
|
font-size: ${(props) =>
|
||||||
|
props.size === "small"
|
||||||
|
? "14px"
|
||||||
|
: props.size === "big"
|
||||||
|
? "16px"
|
||||||
|
: "14px"}; // По умолчанию
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: ${(props) =>
|
||||||
|
props.$iconpos === 'right'
|
||||||
|
? 'row-reverse'
|
||||||
|
: 'row'};
|
||||||
|
gap: 10px;
|
||||||
|
transition: all .3s ease;
|
||||||
|
&:hover{
|
||||||
|
background-color: ${(props) => props.$hoverсolor};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
color?: string;
|
||||||
|
hoverColor?: string
|
||||||
|
size?: "small" | "big";
|
||||||
|
title: string
|
||||||
|
onClick?: () => void;
|
||||||
|
type?: "button" | "submit" | "reset" | undefined
|
||||||
|
icon?: string
|
||||||
|
iconPosition?: 'left' | 'right'
|
||||||
|
iconSize?: {
|
||||||
|
width: string,
|
||||||
|
height: string
|
||||||
|
}
|
||||||
|
style?: any
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MainButton = ({ color, size, title, hoverColor, type, icon, iconPosition, iconSize, onClick, style }: Props) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const defaultColor: string = theme.accent;
|
||||||
|
const defaultHoverColor: string = theme.accentHover
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className="unselectable"
|
||||||
|
color={color || defaultColor} size={size}
|
||||||
|
$hoverсolor={hoverColor || defaultHoverColor}
|
||||||
|
onClick={onClick}
|
||||||
|
type={type}
|
||||||
|
$iconpos={iconPosition || 'left'}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{icon && <IconComponent name={icon} width={iconSize?.width} height={iconSize?.height} setColor="white"/> }
|
||||||
|
{title}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
45
FRONT/DEV/src/components/UI/buttons/quitButton.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { MainButton } from "./mainButton";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { buttonNames } from "../../../const/buttons/names";
|
||||||
|
import { IconComponent as Icon } from '../../../const/buttons/icons';
|
||||||
|
import { useWorkSpaceStore } from "../../../store";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
size: "small" | "big";
|
||||||
|
};
|
||||||
|
|
||||||
|
const Container = styled.div<{ size: "small" | "big" }>`
|
||||||
|
${(props) => props.size === "small" && "width: 100%;"}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Button = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
color: ${(props) => props.theme.textColor};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const IconContainer = styled.div`
|
||||||
|
padding: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const QuitButton = ({ size }: Props) => {
|
||||||
|
const handleExit = () => {
|
||||||
|
window.electron.quitApp();
|
||||||
|
};
|
||||||
|
|
||||||
|
const { setWorkModule } = useWorkSpaceStore()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container size={size} onClick={()=>setWorkModule('helloWindow')}>
|
||||||
|
{size === "big" ? (
|
||||||
|
<MainButton title="Выйти" size="big" />
|
||||||
|
) : (
|
||||||
|
<Button>
|
||||||
|
<IconContainer>
|
||||||
|
<Icon name={buttonNames[12]} width="30" height="30"/>
|
||||||
|
</IconContainer>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
26
FRONT/DEV/src/components/UI/buttons/toggletheme.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import styled, { useTheme } from "styled-components";
|
||||||
|
import { IconComponent as Icon } from '../../../const/theme/icons';
|
||||||
|
import { ThemName } from "../../../const/theme/names";
|
||||||
|
|
||||||
|
const Button = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
`
|
||||||
|
|
||||||
|
const IconContainer = styled.div`
|
||||||
|
padding: 10px;
|
||||||
|
color: ${props => props.theme.text};
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function ThemeButton() {
|
||||||
|
const theme = useTheme();
|
||||||
|
const name = localStorage.getItem('theme') as ThemName
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={theme.toggleTheme}>
|
||||||
|
<IconContainer>
|
||||||
|
<Icon name={name} width="30" height="30"/>
|
||||||
|
</IconContainer>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
65
FRONT/DEV/src/components/UI/inputs/input.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
placeholderText: string;
|
||||||
|
id: string;
|
||||||
|
value?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
onFileChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
type?: string;
|
||||||
|
multiple?: boolean;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
search?: boolean;
|
||||||
|
onFocus?: any
|
||||||
|
onBlur?: any
|
||||||
|
$size?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex: 3;
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledInput = styled.input`
|
||||||
|
padding: 8px 10px;
|
||||||
|
color: black;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #A1A1A1;
|
||||||
|
background-color: transparent;
|
||||||
|
&:focus{
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
}
|
||||||
|
width: 100%;
|
||||||
|
font-size: 18px;
|
||||||
|
color: ${props=>props.theme.textColor}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, Props>(({ placeholderText, id, value, onChange, onFileChange, type = "text", multiple, style, onFocus, onBlur }, ref) => {
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
if (onChange) {
|
||||||
|
onChange(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputContainer>
|
||||||
|
<StyledInput
|
||||||
|
type={type}
|
||||||
|
placeholder={placeholderText}
|
||||||
|
value={value}
|
||||||
|
multiple={multiple}
|
||||||
|
onChange={type === "file" && onFileChange ? onFileChange : handleInputChange}
|
||||||
|
ref={ref}
|
||||||
|
style={style}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
|
/>
|
||||||
|
</InputContainer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Input;
|
||||||
101
FRONT/DEV/src/components/UI/loader.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
type Props = {
|
||||||
|
loaderText: string; //Тип передаваемых данных loaderText - string
|
||||||
|
}
|
||||||
|
|
||||||
|
import styled, { keyframes } from 'styled-components'
|
||||||
|
import { AnimDot } from './animDot';
|
||||||
|
|
||||||
|
|
||||||
|
//Константа которая устанавливает кейфвреймы для вращения
|
||||||
|
const spinAnimation = keyframes`
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
//Стиль экрана загрузки
|
||||||
|
export const LoaderContainer = styled.div`
|
||||||
|
top: 0px;
|
||||||
|
z-index:998;
|
||||||
|
background-color: rgba(0,0,0, 0.7);
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: hidden;
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
//Стиль блока с элементами загрузки
|
||||||
|
export const Loaderblock = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
p{
|
||||||
|
font-size: 24px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
//Стиль для блока с кольцом загрузки
|
||||||
|
export const LoadingSpinner = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 65px;
|
||||||
|
height: 65px;
|
||||||
|
`
|
||||||
|
|
||||||
|
//Стиль для кольца загрузки
|
||||||
|
export const LoadingRing = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 65px;
|
||||||
|
height: 65px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 12px solid white;
|
||||||
|
`
|
||||||
|
//Стиль для части кольца загрузки, которая будет вращаться
|
||||||
|
export const LoadingPart = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 65px;
|
||||||
|
height: 65px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 12px solid transparent;
|
||||||
|
border-top: 12px solid ${props => props.theme.accent};
|
||||||
|
|
||||||
|
// Добавление аниманции и использование кейфремов
|
||||||
|
animation: ${spinAnimation} 1s linear infinite;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Text = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 3px;
|
||||||
|
font-size: 24px;
|
||||||
|
color: white;
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-left: 25px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function Loader({ loaderText }: Props) {
|
||||||
|
return (
|
||||||
|
|
||||||
|
<Loaderblock>
|
||||||
|
|
||||||
|
<LoadingSpinner>
|
||||||
|
<LoadingRing />
|
||||||
|
<LoadingPart />
|
||||||
|
</LoadingSpinner>
|
||||||
|
|
||||||
|
{/* Текст экрана загрузки устанавливается с помощью передаваемых данных loaderText */}
|
||||||
|
<Text>{loaderText}<AnimDot/></Text>
|
||||||
|
</Loaderblock>
|
||||||
|
)
|
||||||
|
}
|
||||||
103
FRONT/DEV/src/components/UI/routerLoader.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
type Props = {
|
||||||
|
loaderText: string; //Тип передаваемых данных loaderText - string
|
||||||
|
}
|
||||||
|
|
||||||
|
import styled, { keyframes } from 'styled-components'
|
||||||
|
import { AnimDot } from './animDot';
|
||||||
|
import { ModalWindow } from '../modalwindows/style';
|
||||||
|
|
||||||
|
|
||||||
|
//Константа которая устанавливает кейфвреймы для вращения
|
||||||
|
const spinAnimation = keyframes`
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
//Стиль экрана загрузки
|
||||||
|
export const LoaderContainer = styled.div`
|
||||||
|
top: 0px;
|
||||||
|
z-index:998;
|
||||||
|
background-color: rgba(0,0,0, 0.7);
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: hidden;
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
//Стиль блока с элементами загрузки
|
||||||
|
export const Loaderblock = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
p{
|
||||||
|
font-size: 24px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
//Стиль для блока с кольцом загрузки
|
||||||
|
export const LoadingSpinner = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 65px;
|
||||||
|
height: 65px;
|
||||||
|
`
|
||||||
|
|
||||||
|
//Стиль для кольца загрузки
|
||||||
|
export const LoadingRing = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 65px;
|
||||||
|
height: 65px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 12px solid white;
|
||||||
|
`
|
||||||
|
//Стиль для части кольца загрузки, которая будет вращаться
|
||||||
|
export const LoadingPart = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 65px;
|
||||||
|
height: 65px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 12px solid transparent;
|
||||||
|
border-top: 12px solid ${props => props.theme.accent};
|
||||||
|
|
||||||
|
// Добавление аниманции и использование кейфремов
|
||||||
|
animation: ${spinAnimation} 1s linear infinite;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Text = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 3px;
|
||||||
|
font-size: 24px;
|
||||||
|
color: white;
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-left: 25px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function RouterLoader({ loaderText }: Props) {
|
||||||
|
return (
|
||||||
|
<ModalWindow>
|
||||||
|
<Loaderblock>
|
||||||
|
<LoadingSpinner>
|
||||||
|
<LoadingRing />
|
||||||
|
<LoadingPart />
|
||||||
|
</LoadingSpinner>
|
||||||
|
|
||||||
|
{/* Текст экрана загрузки устанавливается с помощью передаваемых данных loaderText */}
|
||||||
|
<Text>{loaderText}<AnimDot/></Text>
|
||||||
|
</Loaderblock>
|
||||||
|
</ModalWindow>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
63
FRONT/DEV/src/components/UI/signalSvg.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
interface SignalSVGProps {
|
||||||
|
signal: number; // Принимаем signal как пропс
|
||||||
|
color: string;
|
||||||
|
size: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SignalSVG: React.FC<SignalSVGProps> = ({ signal, color, size }) => {
|
||||||
|
const svgRef = useRef<SVGSVGElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Устанавливаем цвета в зависимости от signal
|
||||||
|
const colors = {
|
||||||
|
'--st1-color': signal >= 1 ? color : 'black',
|
||||||
|
'--st2-color': signal >= 2 ? color : 'black',
|
||||||
|
'--st3-color': signal >= 3 ? color : 'black',
|
||||||
|
'--st4-color': signal >= 4 ? color : 'black',
|
||||||
|
'--st5-color': signal === 5 ? color : 'black',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Применяем стили к SVG
|
||||||
|
if (svgRef.current) {
|
||||||
|
Object.entries(colors).forEach(([key, value]) => {
|
||||||
|
svgRef.current?.style.setProperty(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [signal]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
ref={svgRef}
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 296 235"
|
||||||
|
xmlSpace="preserve"
|
||||||
|
width={size} // Фиксированная ширина
|
||||||
|
height={size}
|
||||||
|
>
|
||||||
|
<style type="text/css">
|
||||||
|
{`
|
||||||
|
.st1 { fill-rule: evenodd; clip-rule: evenodd; fill: var(--st1-color, white); }
|
||||||
|
.st2 { fill-rule: evenodd; clip-rule: evenodd; fill: var(--st2-color, white); }
|
||||||
|
.st3 { fill-rule: evenodd; clip-rule: evenodd; fill: var(--st3-color, white); }
|
||||||
|
.st4 { fill-rule: evenodd; clip-rule: evenodd; fill: var(--st4-color, white); }
|
||||||
|
.st5 { fill-rule: evenodd; clip-rule: evenodd; fill: var(--st5-color, white); }
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<polygon className="st1" points="47,138 47,188 0,188 0,164" />
|
||||||
|
<polygon className="st2" points="109,104 109,188 62,188 62,130" />
|
||||||
|
<polygon className="st3" points="171,69 171,188 124,188 124,95" />
|
||||||
|
<polygon className="st4" points="234,35 234,188 187,188 187,61" />
|
||||||
|
<polygon className="st5" points="296,0 296,188 249,188 249,26" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SignalSVG;
|
||||||
137
FRONT/DEV/src/components/UI/tables/TableScanner.tsx
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
import { DeviceUser } from '../../../store/useDeviceStore';
|
||||||
|
|
||||||
|
interface TableScannerProps {
|
||||||
|
devices: DeviceUser[];
|
||||||
|
}
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {IconComponent} from "../../../const/buttons/icons";
|
||||||
|
import {buttonNames} from "../../../const/buttons/names";
|
||||||
|
import {ModalMap} from "../../modalwindows/windowsType";
|
||||||
|
import {useModalContext} from "../../../store";
|
||||||
|
import Pagination from "../Pagination";
|
||||||
|
|
||||||
|
const TableContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
margin: 24px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledTable = styled.table`
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||||
|
background: ${({ theme }) => theme.primary};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TableHeaderCell = styled.th<{ sortable?: boolean }>`
|
||||||
|
padding: 16px 24px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
color: ${({ theme }) => theme.textColor};
|
||||||
|
background: ${({ theme }) => theme.primary};
|
||||||
|
border-bottom: 1px solid ${({ theme }) => theme.borderColor};
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TableRow = styled.tr<{ clickable?: boolean }>`
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
&:hover {
|
||||||
|
background-color: ${({ theme }) => theme.background};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TableCell = styled.td`
|
||||||
|
padding: 16px 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: ${({ theme }) => theme.text};
|
||||||
|
border-bottom: 1px solid ${({ theme }) => theme.borderColor};
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
button{
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StatusBadge = styled.span<{ status:string }>`
|
||||||
|
width: 10px;
|
||||||
|
height: 30px;
|
||||||
|
background-color: ${({theme, status}) => (status==='online' ? theme.successColor : theme.offlineColor)};
|
||||||
|
display: inline-block;
|
||||||
|
border-radius:4px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const TableScanner = ({devices}:TableScannerProps) => {
|
||||||
|
const { openModal } = useModalContext();
|
||||||
|
|
||||||
|
const openModalNote = (e: React.MouseEvent, device:DeviceUser ) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openModal(ModalMap['6'], {
|
||||||
|
address: device.mac,
|
||||||
|
currentNote: device.note || ''
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Пагинация
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [devicesPerPage] = useState(10);
|
||||||
|
|
||||||
|
const indexOfLastDevice = currentPage * devicesPerPage;
|
||||||
|
const indexOfFirstDevice = indexOfLastDevice - devicesPerPage;
|
||||||
|
const currentDevices = devices.slice(indexOfFirstDevice, indexOfLastDevice);
|
||||||
|
const totalPages = Math.ceil(devices.length / devicesPerPage);
|
||||||
|
|
||||||
|
const handlePrevPage = () => {
|
||||||
|
if(currentPage > 1) setCurrentPage(currentPage - 1);
|
||||||
|
}
|
||||||
|
const handleNextPage = () => {
|
||||||
|
if(currentPage < totalPages) setCurrentPage(currentPage + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer>
|
||||||
|
<StyledTable>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<TableHeaderCell></TableHeaderCell>
|
||||||
|
<TableHeaderCell>Имя</TableHeaderCell>
|
||||||
|
<TableHeaderCell>Производитель</TableHeaderCell>
|
||||||
|
<TableHeaderCell>IP адрес</TableHeaderCell>
|
||||||
|
<TableHeaderCell>Открытые порты</TableHeaderCell>
|
||||||
|
<TableHeaderCell>Последняя активность</TableHeaderCell>
|
||||||
|
<TableHeaderCell>Заметка</TableHeaderCell>
|
||||||
|
<TableHeaderCell></TableHeaderCell>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{currentDevices.map((item, i) => (
|
||||||
|
<TableRow key={i}>
|
||||||
|
<TableCell><StatusBadge status={item.status}></StatusBadge></TableCell>
|
||||||
|
<TableCell>{item.hostname || item.ip}</TableCell>
|
||||||
|
<TableCell>{item.vendor}</TableCell>
|
||||||
|
<TableCell>{item.ip}</TableCell>
|
||||||
|
<TableCell>{item.open_ports.join(', ') || '-'}</TableCell>
|
||||||
|
<TableCell>{item.last_seen}</TableCell>
|
||||||
|
<TableCell>{item.note || '-'}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<button onClick={(e)=>openModalNote(e, item)}>
|
||||||
|
<IconComponent name={buttonNames[16]} width="26" height="26" />
|
||||||
|
</button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</StyledTable>
|
||||||
|
<Pagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
totalPages={totalPages}
|
||||||
|
onPrevPage={handlePrevPage}
|
||||||
|
onNextPage={handleNextPage}
|
||||||
|
/>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableScanner;
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Handle, Position } from '@xyflow/react';
|
||||||
|
import { Circle } from './style';
|
||||||
|
|
||||||
|
interface CustomNodeProps {
|
||||||
|
data: {
|
||||||
|
label: string;
|
||||||
|
style:{
|
||||||
|
width: any;
|
||||||
|
height: any;
|
||||||
|
borderType: any;
|
||||||
|
borderColor: any;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CircleNode = ({ data }: CustomNodeProps) => {
|
||||||
|
return (
|
||||||
|
<Circle width={data.style.width} height={data.style.height} bordertype={data.style.borderType} bordercolor={data.style.borderColor}>{/* */}
|
||||||
|
{/* <div style={{transform: 'rotate(-45deg)', width: '100%', height: '100%'}}> */}
|
||||||
|
{data.label}
|
||||||
|
|
||||||
|
{/* Узлы для подключения */}
|
||||||
|
<Handle type="source" position={Position.Top} id="source-top" style={{ top: 0, }} />
|
||||||
|
<Handle type="source" position={Position.Right} id="source-right" style={{ right: 0 }} />
|
||||||
|
<Handle type="source" position={Position.Bottom} id="source-bottom" style={{ bottom: 0 }} />
|
||||||
|
<Handle type="source" position={Position.Left} id="source-left" style={{ left: 0 }} />
|
||||||
|
|
||||||
|
{/* Для входящих подключений */}
|
||||||
|
<Handle type="target" position={Position.Top} id="target-top" style={{ top: 0 }} />
|
||||||
|
<Handle type="target" position={Position.Right} id="target-right" style={{ right: 0 }} />
|
||||||
|
<Handle type="target" position={Position.Bottom} id="target-bottom" style={{ bottom: 0 }} />
|
||||||
|
<Handle type="target" position={Position.Left} id="target-left" style={{ left: 0 }} />
|
||||||
|
{/* </div> */}
|
||||||
|
|
||||||
|
</Circle>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
type Props={
|
||||||
|
width: string,
|
||||||
|
height: string,
|
||||||
|
bordertype: string,
|
||||||
|
bordercolor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const Circle = styled.div<Props>`
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
|
||||||
|
width: ${(props) => props.width}px;
|
||||||
|
height: ${(props) => props.height}px;
|
||||||
|
`
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Handle, Position } from '@xyflow/react';
|
||||||
|
import { Default } from './style';
|
||||||
|
|
||||||
|
interface CustomNodeProps {
|
||||||
|
data: {
|
||||||
|
label: string;
|
||||||
|
style:{
|
||||||
|
width: any;
|
||||||
|
height: any;
|
||||||
|
borderType: any;
|
||||||
|
borderColor: any;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultNode = ({ data }: CustomNodeProps) => {
|
||||||
|
return (
|
||||||
|
<Default width={data.style.width} height={data.style.height} bordertype={data.style.borderType} bordercolor={data.style.borderColor}>
|
||||||
|
{data.label}
|
||||||
|
|
||||||
|
{/* Узлы для подключения */}
|
||||||
|
{/* <Handle type="source" position={Position.Top} id="source-top" style={{ top: 0, }} />
|
||||||
|
<Handle type="source" position={Position.Right} id="source-right" style={{ right: 0 }} />
|
||||||
|
<Handle type="source" position={Position.Bottom} id="source-bottom" style={{ bottom: 0 }} />
|
||||||
|
<Handle type="source" position={Position.Left} id="source-left" style={{ left: 0 }} /> */}
|
||||||
|
|
||||||
|
{/* Для входящих подключений */}
|
||||||
|
{/* <Handle type="target" position={Position.Top} id="target-top" style={{ top: 0 }} /> */}
|
||||||
|
<Handle type="target" position={Position.Right} id="target-right" style={{ right: 0 }} />
|
||||||
|
{/* <Handle type="target" position={Position.Bottom} id="target-bottom" style={{ bottom: 0 }} />
|
||||||
|
<Handle type="target" position={Position.Left} id="target-left" style={{ left: 0 }} /> */}
|
||||||
|
{/* </div> */}
|
||||||
|
|
||||||
|
</Default>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
type Props={
|
||||||
|
width: string,
|
||||||
|
height: string,
|
||||||
|
bordertype: string,
|
||||||
|
bordercolor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Default = styled.div<Props>`
|
||||||
|
background-color: white;
|
||||||
|
border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
|
||||||
|
width: ${(props) => props.width}px;
|
||||||
|
height: ${(props) => props.height}px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
visibility: visible;
|
||||||
|
`
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Handle, Position } from '@xyflow/react';
|
||||||
|
import { Diamond, InnerDiamond, Border } from './style';
|
||||||
|
|
||||||
|
interface CustomNodeProps {
|
||||||
|
data: {
|
||||||
|
label: string;
|
||||||
|
style:{
|
||||||
|
width: any;
|
||||||
|
height: any;
|
||||||
|
borderType: any;
|
||||||
|
borderColor: any;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DiamondNode = ({ data }: CustomNodeProps) => {
|
||||||
|
|
||||||
|
const baseSize = Math.max(data.style.width, data.style.height);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Diamond width={data.style.width} height={data.style.height}>{/* */}
|
||||||
|
<Border width={data.style.width} height={data.style.height} bordercolor={data.style.borderColor}>
|
||||||
|
<InnerDiamond width={data.style.width} height={data.style.height}>
|
||||||
|
{data.label}
|
||||||
|
</InnerDiamond>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<div style={{ width: '100%', height: '100%' }}>
|
||||||
|
{/* Узлы для подключения */}
|
||||||
|
<Handle type="source" position={Position.Top} id="source-top" />
|
||||||
|
<Handle type="source" position={Position.Right} id="source-right" />
|
||||||
|
<Handle type="source" position={Position.Bottom} id="source-bottom" />
|
||||||
|
<Handle type="source" position={Position.Left} id="source-left" />
|
||||||
|
|
||||||
|
{/* Для входящих подключений */}
|
||||||
|
<Handle type="target" position={Position.Top} id="target-top" />
|
||||||
|
<Handle type="target" position={Position.Right} id="target-right" />
|
||||||
|
<Handle type="target" position={Position.Bottom} id="target-bottom" />
|
||||||
|
<Handle type="target" position={Position.Left} id="target-left" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Diamond >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
interface StyledDefaultProps {
|
||||||
|
width: any;
|
||||||
|
height: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Diamond = styled.div<StyledDefaultProps>`
|
||||||
|
position: relative;
|
||||||
|
width: ${(props) => props.width}px;
|
||||||
|
height: ${(props) => props.height}px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const InnerDiamond = styled.div<StyledDefaultProps>`
|
||||||
|
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
|
||||||
|
width: ${(props) => props.width - 6}px;
|
||||||
|
height: ${(props) => props.height - 4}px;
|
||||||
|
background-color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Border = styled.div<{ width: number; height: number, bordercolor: any }>`
|
||||||
|
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
|
||||||
|
width: ${(props) => props.width}px;
|
||||||
|
height: ${(props) => props.height}px;
|
||||||
|
background-color: ${(props) => props.bordercolor};
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export {DefaultNode} from './default/defaultNode'
|
||||||
|
export {PillNode} from './pill/pillNode'
|
||||||
|
export {ParalelorgamNode} from './paralelogram/paralelogramNode'
|
||||||
|
export {CircleNode} from './circle/circleNode'
|
||||||
|
export {DiamondNode} from './diamond/diamondNode'
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Handle, Position } from '@xyflow/react';
|
||||||
|
import { Paralelorgam } from './style';
|
||||||
|
|
||||||
|
interface CustomNodeProps {
|
||||||
|
data: {
|
||||||
|
label: string;
|
||||||
|
style:{
|
||||||
|
width: any;
|
||||||
|
height: any;
|
||||||
|
borderType: any;
|
||||||
|
borderColor: any;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ParalelorgamNode = ({ data }: CustomNodeProps) => {
|
||||||
|
return (
|
||||||
|
<Paralelorgam width={data.style.width} height={data.style.height} bordertype={data.style.borderType} bordercolor={data.style.borderColor}>
|
||||||
|
{data.label}
|
||||||
|
|
||||||
|
{/* Узлы для подключения */}
|
||||||
|
<Handle type="source" position={Position.Top} id="source-top"/>
|
||||||
|
<Handle type="source" position={Position.Right} id="source-right"/>
|
||||||
|
<Handle type="source" position={Position.Bottom} id="source-bottom"/>
|
||||||
|
<Handle type="source" position={Position.Left} id="source-left"/>
|
||||||
|
|
||||||
|
{/* Для входящих подключений */}
|
||||||
|
<Handle type="target" position={Position.Top} id="target-top"/>
|
||||||
|
<Handle type="target" position={Position.Right} id="target-right"/>
|
||||||
|
<Handle type="target" position={Position.Bottom} id="target-bottom"/>
|
||||||
|
<Handle type="target" position={Position.Left} id="target-left"/>
|
||||||
|
</Paralelorgam>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
type Props={
|
||||||
|
width: string,
|
||||||
|
height: string,
|
||||||
|
bordertype: string,
|
||||||
|
bordercolor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const Paralelorgam = styled.div<Props>`
|
||||||
|
transform: skewX(-20deg);
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
|
||||||
|
width: ${(props) => props.width}px;
|
||||||
|
height: ${(props) => props.height}px;
|
||||||
|
|
||||||
|
.react-flow__handle-top{
|
||||||
|
transform: skewX(20deg);
|
||||||
|
top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__handle-bottom{
|
||||||
|
transform: skewX(20deg);
|
||||||
|
bottom: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__handle-right{
|
||||||
|
transform: skewX(20deg);
|
||||||
|
right: -5px;
|
||||||
|
top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__handle-left{
|
||||||
|
transform: skewX(20deg);
|
||||||
|
left: -5px;
|
||||||
|
top: 25px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Handle, Position } from '@xyflow/react';
|
||||||
|
import { Pill } from './style';
|
||||||
|
|
||||||
|
interface CustomNodeProps {
|
||||||
|
data: {
|
||||||
|
label: string;
|
||||||
|
style:{
|
||||||
|
width: any;
|
||||||
|
height: any;
|
||||||
|
borderType: any;
|
||||||
|
borderColor: any;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PillNode = ({ data }: CustomNodeProps) => {
|
||||||
|
return (
|
||||||
|
<Pill width={data.style.width} height={data.style.height} bordertype={data.style.borderType} bordercolor={data.style.borderColor}>{/* */}
|
||||||
|
{/* <div style={{transform: 'rotate(-45deg)', width: '100%', height: '100%'}}> */}
|
||||||
|
{data.label}
|
||||||
|
|
||||||
|
{/* Узлы для подключения */}
|
||||||
|
<Handle type="source" position={Position.Top} id="source-top" style={{ top: 0, }} />
|
||||||
|
<Handle type="source" position={Position.Right} id="source-right" style={{ right: 0 }} />
|
||||||
|
<Handle type="source" position={Position.Bottom} id="source-bottom" style={{ bottom: 0 }} />
|
||||||
|
<Handle type="source" position={Position.Left} id="source-left" style={{ left: 0 }} />
|
||||||
|
|
||||||
|
{/* Для входящих подключений */}
|
||||||
|
<Handle type="target" position={Position.Top} id="target-top" style={{ top: 0 }} />
|
||||||
|
<Handle type="target" position={Position.Right} id="target-right" style={{ right: 0 }} />
|
||||||
|
<Handle type="target" position={Position.Bottom} id="target-bottom" style={{ bottom: 0 }} />
|
||||||
|
<Handle type="target" position={Position.Left} id="target-left" style={{ left: 0 }} />
|
||||||
|
{/* </div> */}
|
||||||
|
|
||||||
|
</Pill>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PillNode;
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
type Props={
|
||||||
|
width: string,
|
||||||
|
height: string,
|
||||||
|
bordertype: string,
|
||||||
|
bordercolor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Pill = styled.div<Props>`
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
|
||||||
|
width: ${(props) => props.width}px;
|
||||||
|
height: ${(props) => props.height}px;
|
||||||
|
`
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export {NodeGroupNode} from './networkGroup/nodeGroup'
|
||||||
|
export {NetWorkNode} from './networkNode/networkNode'
|
||||||
|
export {IpGroupeNode} from './ipGroup/ipGroup'
|
||||||
|
export {IpNode} from './ipNode/ipNode'
|
||||||
|
export {PortNode} from './portNode/portNode'
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
import { IpGroup } from "./style";
|
||||||
|
|
||||||
|
interface CustomNodeProps {
|
||||||
|
data: {
|
||||||
|
style: {
|
||||||
|
width: any,
|
||||||
|
height: any,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IpGroupeNode = ({ data }: CustomNodeProps) => {
|
||||||
|
return (
|
||||||
|
<IpGroup $width={data.style.width} $height={Number(data.style.height)} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import styled from "styled-components"
|
||||||
|
|
||||||
|
type Props={
|
||||||
|
$width: string,
|
||||||
|
$height?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const IpGroup = styled.div<Props>`
|
||||||
|
width: ${(props) => props.$width}px;
|
||||||
|
height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
|
||||||
|
`
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Handle, Position } from '@xyflow/react';
|
||||||
|
import { Ip } from './style';
|
||||||
|
import { Row, IpTitle, IpContent } from './style';
|
||||||
|
import Input from '../../../../UI/inputs/input';
|
||||||
|
import { useModalContext, usePannableStore } from '../../../../../store';
|
||||||
|
import { ModalMap } from '../../../../modalwindows/windowsType';
|
||||||
|
import { validateIPAddress, validateSameSubnet } from '../../../../../utils/nodeController/validator';
|
||||||
|
import {changeIp} from '../../../../../const/types/types'
|
||||||
|
import { IconComponent } from "../../../../../const/buttons/icons";
|
||||||
|
import { buttonNames } from "../../../../../const/buttons/names";
|
||||||
|
|
||||||
|
interface IIp {
|
||||||
|
ipType: string
|
||||||
|
ipAdress: string,
|
||||||
|
ipGate: string,
|
||||||
|
ipDns: string[]
|
||||||
|
ipVersion: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type StateType = Record<string, string>;
|
||||||
|
const typeMap: StateType = {
|
||||||
|
'auto': "DHCP",
|
||||||
|
'manual': "Static"
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CustomNodeProps {
|
||||||
|
data: {
|
||||||
|
ipData: IIp,
|
||||||
|
interfaceData: {
|
||||||
|
device: string,
|
||||||
|
connection: string
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
width: any,
|
||||||
|
height: any,
|
||||||
|
borderType: string,
|
||||||
|
borderColor: string,
|
||||||
|
}
|
||||||
|
route: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IpNode = ({ data }: CustomNodeProps) => {
|
||||||
|
const { toggleEditingMode } = usePannableStore();
|
||||||
|
const [isInput, setIsInput] = useState(false)
|
||||||
|
const { openModal } = useModalContext();
|
||||||
|
|
||||||
|
const [ipData, setIpData] = useState<changeIp>(
|
||||||
|
{
|
||||||
|
currentIp: data.ipData.ipAdress,
|
||||||
|
newIp: data.ipData.ipAdress,
|
||||||
|
currentGate: data.ipData.ipGate,
|
||||||
|
newGate: data.ipData.ipGate,
|
||||||
|
currentDns: data.ipData.ipDns,
|
||||||
|
newDns: data.ipData.ipDns,
|
||||||
|
device: data.interfaceData.device,
|
||||||
|
connection: data.interfaceData.connection,
|
||||||
|
version: data.ipData.ipVersion,
|
||||||
|
prefix: 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleFocus = () => {
|
||||||
|
toggleEditingMode();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
toggleEditingMode();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (isInput) {
|
||||||
|
setIsInput(false)
|
||||||
|
setIpData(prevData => ({
|
||||||
|
...prevData,
|
||||||
|
newIp: data.ipData.ipAdress,
|
||||||
|
newGate: data.ipData.ipGate,
|
||||||
|
newDns: data.ipData.ipDns
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setIsInput(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (id: string, value: string) => {
|
||||||
|
if (id === 'newDns') {
|
||||||
|
const dnsArray = value.split(',').map(dns => dns.trim());
|
||||||
|
setIpData(prevData => ({
|
||||||
|
...prevData,
|
||||||
|
[id]: dnsArray
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setIpData(prevData => ({
|
||||||
|
...prevData,
|
||||||
|
[id]: value
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSubnetMask(ipAddress: string, ipVersion: string): number {
|
||||||
|
if (ipVersion === "6") {
|
||||||
|
return 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstOctet = parseInt(ipAddress.split(".")[0], 10);
|
||||||
|
if (firstOctet >= 0 && firstOctet <= 127) {
|
||||||
|
return 8;
|
||||||
|
} else if (firstOctet >= 128 && firstOctet <= 191) {
|
||||||
|
return 16;
|
||||||
|
} else if (firstOctet >= 192 && firstOctet <= 223) {
|
||||||
|
return 24;
|
||||||
|
} else {
|
||||||
|
return 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
if (
|
||||||
|
!validateIPAddress(ipData.newIp) ||
|
||||||
|
!validateIPAddress(ipData.newGate) ||
|
||||||
|
!ipData.newDns.every(dns => validateIPAddress(dns))
|
||||||
|
) {
|
||||||
|
alert('Один или несколько IP-адресов некорректны.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateSameSubnet(ipData.newIp, ipData.newGate, ipData.newDns)) {
|
||||||
|
alert('Адрес, шлюз и хотя бы один DNS должны находиться в одной подсети.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculatedPrefix = getSubnetMask(ipData.newIp, data.ipData.ipVersion);
|
||||||
|
|
||||||
|
setIpData(prevData => ({
|
||||||
|
...prevData,
|
||||||
|
prefix: calculatedPrefix,
|
||||||
|
}));
|
||||||
|
|
||||||
|
openModal(ModalMap['1'], {
|
||||||
|
...ipData,
|
||||||
|
prefix: calculatedPrefix,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsInput(false)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<Ip $width={data.style.width} $height={Number(data.style.height)} $bordertype={data.style.borderType} $bordercolor={data.style.borderColor}>
|
||||||
|
<IpTitle>
|
||||||
|
{/* <Row>
|
||||||
|
<p>Версия IP:</p>
|
||||||
|
<p>v{data.ipData.ipVersion}</p>
|
||||||
|
</Row> */}
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<p>Метод:</p>
|
||||||
|
<p>{typeMap[data.ipData.ipType]}</p>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{isInput ? (
|
||||||
|
<Row>
|
||||||
|
<IconComponent name={buttonNames[8]} onClick={handleConfirm} width='18' height='18' setColor='white'/>
|
||||||
|
<IconComponent name={buttonNames[9]} onClick={handleClick} width='18' height='18' setColor='white'/>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
) : (
|
||||||
|
<IconComponent name={buttonNames[7]} onClick={handleClick} width='18' height='18' setColor='white'/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</IpTitle>
|
||||||
|
|
||||||
|
<IpContent>
|
||||||
|
<Row className='row'>
|
||||||
|
<p className='row-label'>IPv{data.ipData.ipVersion}-Addr:</p>
|
||||||
|
{isInput ? (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholderText={data.ipData.ipAdress}
|
||||||
|
id="newIp"
|
||||||
|
value={ipData.newIp}
|
||||||
|
onChange={(value) => handleChange('newIp', value)}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
style={{fontSize: '12px', padding: '4px 10px'}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p className='row-text'>{data.ipData.ipAdress}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row className='row'>
|
||||||
|
<p className='row-label'>IPv{data.ipData.ipVersion}-GW:</p>
|
||||||
|
{isInput ? (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholderText={data.ipData.ipGate}
|
||||||
|
id="newGate"
|
||||||
|
value={ipData.newGate}
|
||||||
|
onChange={(value) => handleChange('newGate', value)}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
style={{fontSize: '12px', padding: '4px 10px'}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p className='row-text'>{data.ipData.ipGate}</p>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row className='row'>
|
||||||
|
<p className='row-label'>IPv{data.ipData.ipVersion}-DNS:</p>
|
||||||
|
{isInput ? (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholderText={data.ipData.ipDns.join(', ')}
|
||||||
|
id="newDns"
|
||||||
|
value={ipData.newDns.join(', ')}
|
||||||
|
onChange={( value) => handleChange('newDns', value)}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
style={{fontSize: '12px', padding: '4px 10px'}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p className='row-text'>{data.ipData.ipDns.join(', ')}</p>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{data.route &&
|
||||||
|
<>
|
||||||
|
<Handle type="source" position={Position.Right} id={`source-right`} style={{ right: 0, top: 80 }} />
|
||||||
|
<Handle type="target" position={Position.Right} id={`target-right`} style={{ right: 0, top: 80 }} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</IpContent>
|
||||||
|
</Ip>
|
||||||
|
</>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
type Props={
|
||||||
|
$width: string,
|
||||||
|
$height?: any
|
||||||
|
$bordertype: string,
|
||||||
|
$bordercolor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Ip = styled.div<Props>`
|
||||||
|
background-color: ${(props)=>props.theme.secondary};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: ${(props) => props.$width}px;
|
||||||
|
height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
|
||||||
|
// box-shadow: 0px 0px 15px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
p{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img{
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const IpTitle = styled.div`
|
||||||
|
background: ${(props)=>props.theme.accent};
|
||||||
|
border-top-left-radius: 6px;
|
||||||
|
border-top-right-radius: 6px;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
export const IpContent = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.row{
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
.row-label{
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-text{
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
p{
|
||||||
|
|
||||||
|
text-wrap: nowrap;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Row = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
p{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { NodeGroup, Row, CollapseButton } from "./style";
|
||||||
|
import { useGroupStore } from "../../../../../store";
|
||||||
|
import { MainButton } from "../../../../UI/buttons/mainButton";
|
||||||
|
import { IconComponent } from "../../../../../const/buttons/icons";
|
||||||
|
import { buttonNames } from "../../../../../const/buttons/names";
|
||||||
|
|
||||||
|
interface CustomNodeProps {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
height: any
|
||||||
|
style: {
|
||||||
|
width: any;
|
||||||
|
height: any;
|
||||||
|
borderType: any;
|
||||||
|
borderColor: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NodeGroupNode = ({ data }: CustomNodeProps) => {
|
||||||
|
const [click, setClick] = useState(false);
|
||||||
|
const { groups, setGroups } = useGroupStore();
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
const newClickState = !click;
|
||||||
|
setClick(newClickState);
|
||||||
|
|
||||||
|
const updatedNodes = groups.map((groups) =>
|
||||||
|
groups.id === data.id ? { ...groups, click: newClickState } : groups
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!groups.some((groups) => groups.id === data.id)) {
|
||||||
|
updatedNodes.push({ id: data.id, click: newClickState });
|
||||||
|
}
|
||||||
|
|
||||||
|
setGroups(updatedNodes);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NodeGroup
|
||||||
|
$width={data.style.width}
|
||||||
|
$height={Number(data.style.height)}
|
||||||
|
$bordertype={data.style.borderType}
|
||||||
|
$bordercolor={data.style.borderColor}
|
||||||
|
>
|
||||||
|
<Row $bordertype={data.style.borderType} $bordercolor={data.style.borderColor} $width={data.style.width}>
|
||||||
|
<p>{data.label}</p>
|
||||||
|
|
||||||
|
{/* <MainButton title={click ? 'Показать' : 'Скрыть'} size="small" onClick={handleClick}/> */}
|
||||||
|
<CollapseButton onClick={handleClick}>
|
||||||
|
{click ?(
|
||||||
|
'Развернуть'
|
||||||
|
):(
|
||||||
|
'Свернуть'
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* <IconComponent name={click ? buttonNames[5] : buttonNames[6]}/> */}
|
||||||
|
</CollapseButton>
|
||||||
|
</Row>
|
||||||
|
</NodeGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import styled from "styled-components"
|
||||||
|
|
||||||
|
type Props={
|
||||||
|
$width?: string,
|
||||||
|
$height?: any
|
||||||
|
$bordertype?: string,
|
||||||
|
$bordercolor?: string
|
||||||
|
$backgroundcolor?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const NodeGroup = styled.div<Props>`
|
||||||
|
border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor};
|
||||||
|
width: ${(props) => props.$width}px;
|
||||||
|
height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
visibility: visible;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Row = styled.div<Props>`
|
||||||
|
position: absolute;
|
||||||
|
top: -25px;
|
||||||
|
left: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: ${props=>props.$width}px;
|
||||||
|
gap: 5px;
|
||||||
|
padding-right: 10px;
|
||||||
|
|
||||||
|
p{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const CollapseButton = styled.p`
|
||||||
|
padding: unset;
|
||||||
|
background: none;
|
||||||
|
color: ${(props)=>props.theme.textColor};
|
||||||
|
margin-right: 15px !important;
|
||||||
|
&:hover{
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Handle, Position } from '@xyflow/react';
|
||||||
|
import { Network, TitleContainer, Device, Status, Content, Row, MAC, LabelColumn } from './style';
|
||||||
|
import SignalSVG from '../../../../UI/signalSvg';
|
||||||
|
|
||||||
|
interface CustomNodeProps {
|
||||||
|
data: {
|
||||||
|
networkData: {
|
||||||
|
device: string
|
||||||
|
type: string
|
||||||
|
mac: string
|
||||||
|
mtu: number
|
||||||
|
state: string
|
||||||
|
connection: string
|
||||||
|
id: string,
|
||||||
|
strength: string
|
||||||
|
}
|
||||||
|
style: {
|
||||||
|
width: any,
|
||||||
|
height: any,
|
||||||
|
borderType: string,
|
||||||
|
borderColor: string,
|
||||||
|
backgroundColor: string
|
||||||
|
}
|
||||||
|
height: string
|
||||||
|
visible: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NetWorkNode = ({ data }: CustomNodeProps) => {
|
||||||
|
const [signal, setSignal] = useState(1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const perc = Number(data.networkData.strength);
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
case perc < 20:
|
||||||
|
setSignal(1);
|
||||||
|
break;
|
||||||
|
case perc < 40:
|
||||||
|
setSignal(2);
|
||||||
|
break;
|
||||||
|
case perc < 60:
|
||||||
|
setSignal(3);
|
||||||
|
break;
|
||||||
|
case perc < 80:
|
||||||
|
setSignal(4);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setSignal(5);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [data.networkData.strength])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Network $width={data.style.width} $height={Number(data.style.height)} $bordertype={data.style.borderType} $bordercolor={data.style.borderColor}>
|
||||||
|
<TitleContainer $backgroundcolor={data.style.backgroundColor} {...(!data.visible ? { $height: "100%" } : {})}>
|
||||||
|
|
||||||
|
{data.networkData.type == "WIFI" &&
|
||||||
|
<img src="./assets/images/wifi.svg" alt="" />
|
||||||
|
}
|
||||||
|
|
||||||
|
{data.networkData.type == "ETHERNET" &&
|
||||||
|
<img src="./assets/images/ethernet.svg" alt="" />
|
||||||
|
}
|
||||||
|
|
||||||
|
{data.networkData.type == "TUN" &&
|
||||||
|
<img src="./assets/images/vpn.svg" alt="" />
|
||||||
|
}
|
||||||
|
|
||||||
|
<Device>{data.networkData.device}</Device>
|
||||||
|
|
||||||
|
{data.networkData.type == 'WIFI' && data.networkData.state === "Подключено" ? (
|
||||||
|
<Status>
|
||||||
|
<SignalSVG signal={signal} color={'white'} size={'20px'} />
|
||||||
|
</Status>
|
||||||
|
) : (
|
||||||
|
<div></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
</TitleContainer>
|
||||||
|
|
||||||
|
{data.visible &&
|
||||||
|
<Content>
|
||||||
|
<LabelColumn>
|
||||||
|
<p className='networkNode-label'>Конфигурация</p>
|
||||||
|
<MAC>
|
||||||
|
<Row className='networkNode-row'>
|
||||||
|
<p>MAC:</p>
|
||||||
|
<p>{data.networkData.mac}</p>
|
||||||
|
</Row>
|
||||||
|
<Row className='networkNode-row'>
|
||||||
|
<p>MTU:</p>
|
||||||
|
<p>{data.networkData.mtu}</p>
|
||||||
|
</Row>
|
||||||
|
</MAC>
|
||||||
|
</LabelColumn>
|
||||||
|
</Content>
|
||||||
|
}
|
||||||
|
|
||||||
|
{data.networkData.connection !== 'unknown' &&
|
||||||
|
<Handle type="source" position={Position.Left} id={`g0_${data.networkData.id}-source-left`} style={{ left: 0 }} />
|
||||||
|
}
|
||||||
|
</Network>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NetWorkNode
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
$width?: string,
|
||||||
|
$height?: any
|
||||||
|
$bordertype?: string,
|
||||||
|
$bordercolor?: string
|
||||||
|
$backgroundcolor?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Network = styled.div<Props>`
|
||||||
|
background-color: ${(props) => props.theme.primary};
|
||||||
|
border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor};
|
||||||
|
min-width: ${(props) => props.$width}px;
|
||||||
|
height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
visibility: visible;
|
||||||
|
overflow: hidden;
|
||||||
|
p{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const TitleContainer = styled.div<Props>`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: ${(props) => props.$backgroundcolor};
|
||||||
|
color: white;
|
||||||
|
padding: 15px;
|
||||||
|
height: ${(props) => props.$height ? props.$height : 'unset'};
|
||||||
|
|
||||||
|
img{
|
||||||
|
width: 20px
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Device = styled.p`
|
||||||
|
font-size: 18px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Status = styled.p`
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
|
||||||
|
img{
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Content = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
padding: 15px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Row = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const MAC = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: ${(props) => props.theme.secondary};
|
||||||
|
border-radius: 8px;
|
||||||
|
// box-shadow: 0px 0px 15px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.networkNode-row{
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const LabelColumn = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.networkNode-label{
|
||||||
|
font-size: 10px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { Handle, Position } from '@xyflow/react';
|
||||||
|
import { Port, Column } from './style';
|
||||||
|
|
||||||
|
interface CustomNodeProps {
|
||||||
|
data: {
|
||||||
|
label: string;
|
||||||
|
port: string
|
||||||
|
id: string;
|
||||||
|
style: {
|
||||||
|
width: any;
|
||||||
|
height: any;
|
||||||
|
borderType: any;
|
||||||
|
borderColor: any;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PortNode = ({ data }: CustomNodeProps) => {
|
||||||
|
return (
|
||||||
|
<Port $width={data.style.width} $height={Number(data.style.height)} $bordertype={data.style.borderType} $bordercolor={data.style.borderColor}>
|
||||||
|
|
||||||
|
<Column>
|
||||||
|
<p>{data.label}</p>
|
||||||
|
|
||||||
|
|
||||||
|
{data.port &&
|
||||||
|
<p>{data.port}</p>
|
||||||
|
}
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
|
||||||
|
{data.port ? (
|
||||||
|
<>
|
||||||
|
<Handle type="target" position={Position.Left} id={`${data.id}_target-left`} style={{ left: 0 }} />
|
||||||
|
<Handle type="source" position={Position.Left} id={`${data.id}_target-left`} style={{ left: 0 }} />
|
||||||
|
</>
|
||||||
|
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Handle type="target" position={Position.Right} id={`${data.id}_target-right`} style={{ right: 0 }} />
|
||||||
|
<Handle type="source" position={Position.Right} id={`${data.id}source-right`} style={{ right: 0 }} />
|
||||||
|
</>
|
||||||
|
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Port>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import styled from "styled-components"
|
||||||
|
|
||||||
|
type Props={
|
||||||
|
$width: string,
|
||||||
|
$height?: any
|
||||||
|
$bordertype: string,
|
||||||
|
$bordercolor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Port = styled.div<Props>`
|
||||||
|
background-color: ${(props)=>props.theme.primary};
|
||||||
|
border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor};
|
||||||
|
width: ${(props) => props.$width}px;
|
||||||
|
height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
visibility: visible;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Column = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
p{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`
|
||||||
11
FRONT/DEV/src/components/customeNodes/types/colors.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
type ColorType = Record<string, string>;
|
||||||
|
|
||||||
|
export const NodeColors: ColorType = {
|
||||||
|
'0': '#c0c0c0',
|
||||||
|
'1': '#ef6e7d',
|
||||||
|
'2': '#f19e4c',
|
||||||
|
'3': '#e9c44c',
|
||||||
|
'4': '#51ce82',
|
||||||
|
'5': '#4cd2d0',
|
||||||
|
'6': '#a085f3',
|
||||||
|
};
|
||||||
16
FRONT/DEV/src/components/customeNodes/types/types.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
import { DefaultNode, PillNode, ParalelorgamNode, DiamondNode, CircleNode } from '../nodes/baseForm'
|
||||||
|
import { NodeGroupNode, NetWorkNode, IpGroupeNode, IpNode, PortNode } from '../nodes/networkInterface';
|
||||||
|
|
||||||
|
export const nodeTypes = {
|
||||||
|
custom: DefaultNode,
|
||||||
|
pill: PillNode,
|
||||||
|
parallelogram: ParalelorgamNode,
|
||||||
|
diamond: DiamondNode,
|
||||||
|
circle: CircleNode,
|
||||||
|
nodegroup: NodeGroupNode,
|
||||||
|
portNode: PortNode,
|
||||||
|
network: NetWorkNode,
|
||||||
|
ipgroup: IpGroupeNode,
|
||||||
|
ip: IpNode,
|
||||||
|
};
|
||||||
10
FRONT/DEV/src/components/modalwindows/modal.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { ModalWindow, ModalContainer } from "./style"
|
||||||
|
import { modalNames } from "../../const/modals/names"
|
||||||
|
|
||||||
|
export const WindowComponent = ()=>{
|
||||||
|
return(
|
||||||
|
<ModalWindow>
|
||||||
|
|
||||||
|
</ModalWindow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
// components/modalContainers/NoteDevice.tsx
|
||||||
|
import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../style";
|
||||||
|
import { MainButton } from "../../UI/buttons/mainButton";
|
||||||
|
import Input from "../../UI/inputs/input";
|
||||||
|
import { useState } from "react";
|
||||||
|
import {NetworkService} from "../../../api/request";
|
||||||
|
import useNetworkStore from "../../../store/useDeviceStore";
|
||||||
|
import RouterLoader from "../../UI/routerLoader";
|
||||||
|
import {useTheme} from "styled-components";
|
||||||
|
import {useIpStore} from "../../../store";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
address: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const ConnectNetwork = ({address, onClose}: Props) => {
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const theme = useTheme();
|
||||||
|
const { ipAddress } = useIpStore();
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
setError('')
|
||||||
|
const res = await NetworkService.connectNetwork(ipAddress, password, address);
|
||||||
|
if(res.status === 200) {
|
||||||
|
onClose()
|
||||||
|
}else{
|
||||||
|
setError(res.data.detail)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error connection:', error);
|
||||||
|
setError('Ошибка подключения к сети')
|
||||||
|
}finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContainer style={{ height: '230px' }}>
|
||||||
|
<ModalTitle>
|
||||||
|
<h3>Подключение к сети</h3>
|
||||||
|
</ModalTitle>
|
||||||
|
|
||||||
|
<ModalContent style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholderText='Введите пароль...'
|
||||||
|
value={password}
|
||||||
|
id='deviceNote'
|
||||||
|
onChange={(value) => setPassword(value)}
|
||||||
|
/>
|
||||||
|
<p style={{ color: 'red', marginLeft: '10px', fontSize: '14px', textAlign: 'right' }}>
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
title="Отмена"
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
<MainButton
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
color={theme.successColor}
|
||||||
|
hoverColor={theme.successColor}
|
||||||
|
title='Подключиться'
|
||||||
|
onClick={handleSave}
|
||||||
|
/>
|
||||||
|
</ButtonContainer>
|
||||||
|
|
||||||
|
{isLoading && <RouterLoader loaderText='Попытка подключения к сети'/>}
|
||||||
|
</ModalContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
// components/modalContainers/NoteDevice.tsx
|
||||||
|
import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../style";
|
||||||
|
import { MainButton } from "../../UI/buttons/mainButton";
|
||||||
|
import Input from "../../UI/inputs/input";
|
||||||
|
import { useState } from "react";
|
||||||
|
import {useIpStore} from "../../../store";
|
||||||
|
import {NetworkService} from "../../../api/request";
|
||||||
|
import useNetworkStore from "../../../store/useDeviceStore";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
address: string;
|
||||||
|
onClose: () => void;
|
||||||
|
currentNote?: string;
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const NoteDevice = ({ address, onClose, currentNote, type}: Props) => {
|
||||||
|
const [note, setNote] = useState(currentNote || '');
|
||||||
|
const { ipAddress } = useIpStore();
|
||||||
|
const { devices, setDevices } = useNetworkStore();
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
if (!currentNote && note.trim() !== '') {
|
||||||
|
await NetworkService.addNote(ipAddress, address, note, type);
|
||||||
|
}else if (currentNote && note.trim() !== '' && note !== currentNote) {
|
||||||
|
await NetworkService.editNote(ipAddress, address, note, type);
|
||||||
|
}else if (currentNote && note.trim() === '') {
|
||||||
|
await NetworkService.deleteNote(ipAddress, address, type);
|
||||||
|
}else {
|
||||||
|
onClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedDevices = devices.map(device =>
|
||||||
|
device.mac === address
|
||||||
|
? { ...device, note: note.trim() }
|
||||||
|
: device
|
||||||
|
);
|
||||||
|
setDevices(updatedDevices);
|
||||||
|
onClose();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving note:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContainer style={{ height: '250px' }}>
|
||||||
|
<ModalTitle>
|
||||||
|
<h3>{currentNote ? 'Редактирование заметки' : 'Добавление заметки'}</h3>
|
||||||
|
</ModalTitle>
|
||||||
|
|
||||||
|
<ModalContent style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholderText='Введите заметку...'
|
||||||
|
value={note}
|
||||||
|
id='deviceNote'
|
||||||
|
onChange={(value) => setNote(value)}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
title="Отмена"
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
<MainButton
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
title='Сохранить'
|
||||||
|
onClick={handleSave}
|
||||||
|
/>
|
||||||
|
</ButtonContainer>
|
||||||
|
</ModalContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
// components/modalContainers/NoteDevice.tsx
|
||||||
|
import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../style";
|
||||||
|
import { MainButton } from "../../UI/buttons/mainButton";
|
||||||
|
import Input from "../../UI/inputs/input";
|
||||||
|
import { useState } from "react";
|
||||||
|
import {NetworkService} from "../../../api/request";
|
||||||
|
import useNetworkStore from "../../../store/useDeviceStore";
|
||||||
|
import {useIpStore} from "../../../store";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
address: string;
|
||||||
|
onClose: () => void;
|
||||||
|
currentNote?: string;
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const NoteWifi = ({ address, onClose, currentNote, type}: Props) => {
|
||||||
|
const [note, setNote] = useState(currentNote || '');
|
||||||
|
const { ipAddress } = useIpStore();
|
||||||
|
const { wifiNetworks, setWifiNetworks } = useNetworkStore();
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
if (!currentNote && note.trim() !== '') {
|
||||||
|
await NetworkService.addNote(ipAddress, address, note, type);
|
||||||
|
}else if (currentNote && note.trim() !== '' && note !== currentNote) {
|
||||||
|
await NetworkService.editNote(ipAddress, address, note, type);
|
||||||
|
}else if (currentNote && note.trim() === '') {
|
||||||
|
await NetworkService.deleteNote(ipAddress, address, type);
|
||||||
|
}else {
|
||||||
|
onClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const updatedNetworks = wifiNetworks.map(network =>
|
||||||
|
network.ssid === address
|
||||||
|
? { ...network, note: note.trim() }
|
||||||
|
: { ...network }
|
||||||
|
);
|
||||||
|
setWifiNetworks(updatedNetworks);
|
||||||
|
onClose();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving note:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContainer style={{ height: '220px' }}>
|
||||||
|
<ModalTitle>
|
||||||
|
<h3>{currentNote ? 'Редактирование заметки' : 'Добавление заметки'}</h3>
|
||||||
|
</ModalTitle>
|
||||||
|
|
||||||
|
<ModalContent style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholderText='Введите заметку...'
|
||||||
|
value={note}
|
||||||
|
id='deviceNote'
|
||||||
|
onChange={(value) => setNote(value)}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
title="Отмена"
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
<MainButton
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
title='Сохранить'
|
||||||
|
onClick={handleSave}
|
||||||
|
/>
|
||||||
|
</ButtonContainer>
|
||||||
|
</ModalContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import Input from "../../UI/inputs/input"
|
||||||
|
import { MainButton } from "../../UI/buttons/mainButton";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
|
||||||
|
import { validateIPAddress } from "../../../utils/nodeController/validator";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onSave?: (data: string) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddDevice = ({ onSave, onClose }: Props) => {
|
||||||
|
const [ipAddress, setIpAddress] = useState('')
|
||||||
|
const [error, setError] = useState(false)
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (!validateIPAddress(ipAddress)) {
|
||||||
|
setError(true)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// e.preventDefault(); // Предотвращаем перезагрузку страницы
|
||||||
|
if (onSave && ipAddress) {
|
||||||
|
setError(false)
|
||||||
|
onSave(ipAddress);
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContainer style={{ height: '230px' }}>
|
||||||
|
{/* <form onSubmit={handleSubmit}> */}
|
||||||
|
<ModalTitle>
|
||||||
|
<h3>Добавление узла</h3>
|
||||||
|
</ModalTitle>
|
||||||
|
|
||||||
|
<ModalContent style={{ display: 'flex', flexDirection: 'column', gap: '5px' }}>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholderText='Введите адрес узла...'
|
||||||
|
id="newIp"
|
||||||
|
value={ipAddress}
|
||||||
|
onChange={(value) => setIpAddress(value)}
|
||||||
|
/>
|
||||||
|
{error && (
|
||||||
|
<p style={{ color: 'red', marginTop: '5px' }}>
|
||||||
|
Некорректный адрес узла
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton type="button" size="small" title="Отмена" onClick={onClose} />
|
||||||
|
<MainButton type="submit" size="small" title="Сохранить" onClick={handleSubmit} />
|
||||||
|
</ButtonContainer>
|
||||||
|
{/* </form> */}
|
||||||
|
</ModalContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
|
||||||
|
import { useModalContext } from "../../../store"
|
||||||
|
import { useIpStore } from "../../../store"
|
||||||
|
import { MainButton } from "../../UI/buttons/mainButton"
|
||||||
|
|
||||||
|
type Props={
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConnectionError = ({message}: Props)=>{
|
||||||
|
const {closeModal} = useModalContext()
|
||||||
|
const { setIpAddress } = useIpStore();
|
||||||
|
|
||||||
|
const close = ()=>{
|
||||||
|
setIpAddress('localhost')
|
||||||
|
closeModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<ModalContainer>
|
||||||
|
<ModalTitle>
|
||||||
|
<h3>Соединение прервано</h3>
|
||||||
|
</ModalTitle>
|
||||||
|
|
||||||
|
<ModalContent>
|
||||||
|
<ModalText>
|
||||||
|
<h3>Произошёл разрыв с сервером</h3>
|
||||||
|
<p>Пожалуйста, проверьте доступ к адресу и попробуйте снова.</p>
|
||||||
|
</ModalText>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton title="Закрыть" onClick={close} size="small"/>
|
||||||
|
<MainButton title="Повторить" onClick={closeModal} size="small"/>
|
||||||
|
</ButtonContainer>
|
||||||
|
</ModalContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
|
||||||
|
import { useModalContext } from "../../../store"
|
||||||
|
import { useIpStore } from "../../../store"
|
||||||
|
import { MainButton } from "../../UI/buttons/mainButton"
|
||||||
|
import {useState} from "react";
|
||||||
|
import {NetworkService} from "../../../api/request";
|
||||||
|
import RouterLoader from "../../UI/routerLoader";
|
||||||
|
import {useTheme} from "styled-components";
|
||||||
|
|
||||||
|
type Props={
|
||||||
|
address: string,
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DisconnectConfirm = ({address, onClose}: Props)=>{
|
||||||
|
const { ipAddress } = useIpStore();
|
||||||
|
const theme = useTheme()
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleDisconnect = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const res = await NetworkService.disconnectNetwork(ipAddress, address);
|
||||||
|
if(res.status === 200) {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error connection:', error);
|
||||||
|
}finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return(
|
||||||
|
<ModalContainer>
|
||||||
|
<ModalTitle>
|
||||||
|
<h3>Отключение от Wi-Fi сети</h3>
|
||||||
|
</ModalTitle>
|
||||||
|
|
||||||
|
<ModalContent>
|
||||||
|
<ModalText>
|
||||||
|
<h3>Подтверждение отключения</h3>
|
||||||
|
<p>Вы собираетесь отключиться от сети "{address}". Все активные подключения будут прерваны.</p>
|
||||||
|
</ModalText>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton title="Отменить" onClick={onClose} size="small"/>
|
||||||
|
<MainButton title="Отключиться" color={theme.errorColor} hoverColor={theme.errorColor} onClick={handleDisconnect} size="small"/>
|
||||||
|
</ButtonContainer>
|
||||||
|
{isLoading && <RouterLoader loaderText='Отключение от сети'/>}
|
||||||
|
</ModalContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export {IpConfirm} from './ipConfirm'
|
||||||
|
export {ConnectionError} from './connectionError'
|
||||||
|
export {AddDevice} from './addDevice'
|
||||||
|
export {RightsConfirm} from './rightsConfirm'
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
import Loader from "../../UI/loader";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { changeIp } from '../../../const/types/types'
|
||||||
|
import { NetworkService } from '../../../api/request'
|
||||||
|
import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer, ErrorMessage } from "../style"
|
||||||
|
import { useIpStore } from "../../../store";
|
||||||
|
import { MainButton } from "../../UI/buttons/mainButton";
|
||||||
|
import { useAdminStore } from "../../../store/adminStore";
|
||||||
|
import { useDevicesStore } from "../../../store";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ipData: changeIp
|
||||||
|
onClose: () => void;
|
||||||
|
smoothClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Information = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Info = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const DataContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 35px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const CurrentData = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const NewData = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const IpConfirm = ({ ipData, onClose, smoothClose }: Props) => {
|
||||||
|
const [loader, setLoader] = useState(true)
|
||||||
|
const [newIP, setNewIp] = useState(null)
|
||||||
|
const [currentIp, setCurrentIP] = useState({})
|
||||||
|
const [checkStatus, setcheckStatus] = useState(false)
|
||||||
|
const [message, setMessgae] = useState('')
|
||||||
|
const { ipAddress, setIpAddress } = useIpStore();
|
||||||
|
const {isAdmin} = useAdminStore()
|
||||||
|
const { devices, addDevice, deleteDevice } = useDevicesStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ipData && isAdmin) {
|
||||||
|
const data = {
|
||||||
|
"connection_id": ipData.connection,
|
||||||
|
"ipv4_updates": {
|
||||||
|
"ipv4.method": "manual",
|
||||||
|
"ipv4.address-data": [
|
||||||
|
{
|
||||||
|
"address": ipData.newIp,
|
||||||
|
"prefix": ipData.prefix
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv4.gateway": ipData.newGate,
|
||||||
|
"ipv4.dns-data": ipData.newDns
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const oldData = {
|
||||||
|
"connection_id": ipData.connection,
|
||||||
|
"ipv4_updates": {
|
||||||
|
"ipv4.method": "manual",
|
||||||
|
"ipv4.address-data": [
|
||||||
|
{
|
||||||
|
"address": ipData.currentIp,
|
||||||
|
"prefix": ipData.prefix
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv4.gateway": ipData.currentGate,
|
||||||
|
"ipv4.dns-data": ipData.currentDns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentIP(oldData)
|
||||||
|
|
||||||
|
checkIp(data);
|
||||||
|
}
|
||||||
|
}, [ipData, isAdmin]);
|
||||||
|
|
||||||
|
const checkIp = async (data: any) => {
|
||||||
|
try {
|
||||||
|
const result = await NetworkService.checkIp(ipAddress, data);
|
||||||
|
setcheckStatus(result.data.available)
|
||||||
|
setMessgae(result.data.message)
|
||||||
|
if (result.data.available) {
|
||||||
|
setNewIp(data)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error:', err);
|
||||||
|
} finally {
|
||||||
|
setLoader(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveIp = async (data: any) => {
|
||||||
|
try {
|
||||||
|
const result = await NetworkService.saveIp(ipAddress, data);
|
||||||
|
if (result.status === 200) {
|
||||||
|
if(ipAddress!=='localhost'){
|
||||||
|
setIpAddress(ipData.newIp)
|
||||||
|
|
||||||
|
const updatedDevices = devices.map(device =>
|
||||||
|
device.address === ipData.currentIp
|
||||||
|
? { ...device, address: ipData.newIp }
|
||||||
|
: device
|
||||||
|
);
|
||||||
|
useDevicesStore.setState({ devices: updatedDevices });
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error:', err);
|
||||||
|
} finally {
|
||||||
|
setLoader(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelIp = async (data: any) => {
|
||||||
|
try {
|
||||||
|
const result = await NetworkService.saveIp(ipAddress, data);
|
||||||
|
if (result.status === 200) {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error:', err);
|
||||||
|
} finally {
|
||||||
|
setLoader(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{loader && isAdmin ? (
|
||||||
|
<Loader loaderText="Идет проверка корректности IP адреса" />
|
||||||
|
) : (
|
||||||
|
<ModalContainer {...(checkStatus ? { height: "unset" } : {})}>
|
||||||
|
<ModalTitle>
|
||||||
|
<h3>Изменение IP-Адреса</h3>
|
||||||
|
</ModalTitle>
|
||||||
|
{checkStatus ? (
|
||||||
|
<ModalContent>
|
||||||
|
<Information>
|
||||||
|
<h3>Общие сведения</h3>
|
||||||
|
<Info>
|
||||||
|
<p>Интерфейс: {ipData.device}</p>
|
||||||
|
<p>--{'>'}</p>
|
||||||
|
<p>{ipData.connection}</p>
|
||||||
|
</Info>
|
||||||
|
<h3>Сведения об IP</h3>
|
||||||
|
<DataContainer>
|
||||||
|
<CurrentData>
|
||||||
|
<p>Текущий ip: {ipData.currentIp}</p>
|
||||||
|
<p>Текущий шлюз: {ipData.currentGate}</p>
|
||||||
|
<p>Текущий DNS: {ipData.currentDns}</p>
|
||||||
|
</CurrentData>
|
||||||
|
|
||||||
|
<NewData>
|
||||||
|
<p>Новый ip: {ipData.newIp}</p>
|
||||||
|
<p>Новый шлюз: {ipData.newGate}</p>
|
||||||
|
<p>Новый DNS: {ipData.newDns}</p>
|
||||||
|
</NewData>
|
||||||
|
</DataContainer>
|
||||||
|
</Information>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton title="Отмена" size='small' onClick={() => cancelIp(currentIp)}/>
|
||||||
|
<MainButton title="Сохранить" size='small' onClick={() => saveIp(newIP)}/>
|
||||||
|
</ButtonContainer>
|
||||||
|
</ModalContent>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ModalContent>
|
||||||
|
<ModalText>
|
||||||
|
{isAdmin ? (
|
||||||
|
<>
|
||||||
|
<h3>Произошла ошибка проверки корректности</h3>
|
||||||
|
<ErrorMessage>{message}</ErrorMessage>
|
||||||
|
</>
|
||||||
|
|
||||||
|
):(
|
||||||
|
<h3>Для изменения сетевых настроек необходимо авторизироваться под администратором</h3>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</ModalText>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton title="Закрыть" size='small' onClick={smoothClose}/>
|
||||||
|
</ButtonContainer>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalContainer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
|
||||||
|
import Input from "../../UI/inputs/input"
|
||||||
|
import { MainButton } from "../../UI/buttons/mainButton"
|
||||||
|
import { NetworkService } from "../../../api/request"
|
||||||
|
import { useIpStore } from "../../../store"
|
||||||
|
import { useAdminStore } from "../../../store/adminStore"
|
||||||
|
import { useCheckAdmin } from "../../../utils/checkAdmin"
|
||||||
|
|
||||||
|
import { buttonNames } from "../../../const/buttons/names"
|
||||||
|
import { IconComponent } from "../../../const/buttons/icons"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RightsConfirm = ({ onClose }: Props) => {
|
||||||
|
|
||||||
|
const { ipAddress } = useIpStore();
|
||||||
|
const { isAdmin, setAdmin } = useAdminStore()
|
||||||
|
const checkAdmin = useCheckAdmin();
|
||||||
|
const [error, setError] = useState(false)
|
||||||
|
const [success, setSuccess] = useState({
|
||||||
|
message: '',
|
||||||
|
status: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const [user, setUser] = useState({
|
||||||
|
username: 'operator',
|
||||||
|
password: ""
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleChange = (id: string, value: string) => {
|
||||||
|
setUser(prevData => ({
|
||||||
|
...prevData,
|
||||||
|
[id]: value
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const login = async (user: object) => {
|
||||||
|
try {
|
||||||
|
const response = await NetworkService.login(ipAddress, user);
|
||||||
|
if(response.status === 200){
|
||||||
|
await checkAdmin();
|
||||||
|
setError(false)
|
||||||
|
setSuccess(prevData => ({
|
||||||
|
...prevData,
|
||||||
|
message: 'Успешный вход',
|
||||||
|
status: true
|
||||||
|
}));
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
onClose();
|
||||||
|
}else{
|
||||||
|
setError(true)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
await NetworkService.logout(ipAddress);
|
||||||
|
await checkAdmin();
|
||||||
|
setSuccess(prevData => ({
|
||||||
|
...prevData,
|
||||||
|
message: 'Успешный выход',
|
||||||
|
status: true
|
||||||
|
}));
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContainer style={!isAdmin ? {height: '250px'} : {height: 'unset'}}>
|
||||||
|
{!success.status ? (
|
||||||
|
!isAdmin ? (
|
||||||
|
<>
|
||||||
|
<ModalTitle>
|
||||||
|
<h3>Получение прав доступа</h3>
|
||||||
|
</ModalTitle>
|
||||||
|
|
||||||
|
<ModalContent style={{display: 'flex', flexDirection: 'column', gap: '10px'}}>
|
||||||
|
|
||||||
|
<h3 style={{marginLeft: '5px', fontSize: '14px'}}>Включить административный доступ</h3>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholderText='Введите пароль администратора...'
|
||||||
|
id="password"
|
||||||
|
value={user.password}
|
||||||
|
onChange={(value) => handleChange('password', value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<p style={{ color: 'red', marginLeft: '10px', fontSize: '14px', textAlign: 'right' }}>
|
||||||
|
Неверный пароль оператора
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton size="small" title="Отмена" onClick={onClose} />
|
||||||
|
<MainButton size="small" title="Отправить" onClick={() => login(user)} />
|
||||||
|
</ButtonContainer>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ModalTitle>
|
||||||
|
<h3>Переключиться на ограниченный доступ</h3>
|
||||||
|
</ModalTitle>
|
||||||
|
|
||||||
|
<ModalContent>
|
||||||
|
<p>В данном режиме доступа ограничиваются полномочия администратора.</p>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton size="small" title="Отмена" onClick={onClose} />
|
||||||
|
<MainButton size="small" title="Ограничить доступ" onClick={logout} />
|
||||||
|
</ButtonContainer>
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
):(
|
||||||
|
<ModalContainer style={{display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column', marginTop: '15px'}}>
|
||||||
|
<IconComponent name={buttonNames[8]} width="50" height="50"/>
|
||||||
|
<h3>{success.message}</h3>
|
||||||
|
<p style={{fontSize: '12px'}}>Это окно можно закрыть, либо оно пропадёт само</p>
|
||||||
|
<MainButton size="small" title="Закрыть" onClick={onClose} style={{width: '30%'}}/>
|
||||||
|
</ModalContainer>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
</ModalContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
81
FRONT/DEV/src/components/modalwindows/modalWindow.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { useModalContext } from "../../store";
|
||||||
|
import { IpConfirm, ConnectionError, AddDevice, RightsConfirm } from "./modalContainers";
|
||||||
|
import { ModalMap } from "./windowsType";
|
||||||
|
import Loader from "../UI/loader";
|
||||||
|
import { ModalWindow } from "./style";
|
||||||
|
import {NoteDevice} from "./modalContainers/NoteDevice";
|
||||||
|
import { NoteWifi } from "./modalContainers/NoteWifi";
|
||||||
|
import {ConnectNetwork} from "./modalContainers/ConnectNetwork";
|
||||||
|
import {DisconnectConfirm} from "./modalContainers/disconnectConfirm";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean,
|
||||||
|
type: string
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WindowComponent({ isOpen, type, data }: Props) {
|
||||||
|
|
||||||
|
const { closeModal, closeLoader } = useModalContext()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isOpen &&
|
||||||
|
<ModalWindow>
|
||||||
|
{type == ModalMap['1'] && data &&
|
||||||
|
<IpConfirm
|
||||||
|
ipData={data}
|
||||||
|
onClose={closeModal}
|
||||||
|
smoothClose={closeLoader}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
{type == ModalMap['2'] &&
|
||||||
|
<Loader loaderText={data} />
|
||||||
|
}
|
||||||
|
|
||||||
|
{type === ModalMap['3'] &&
|
||||||
|
<ConnectionError
|
||||||
|
message={data.message}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{type === ModalMap['6'] && (
|
||||||
|
<NoteDevice
|
||||||
|
type={'devices'}
|
||||||
|
address={data.address}
|
||||||
|
currentNote={data.currentNote}
|
||||||
|
onClose={closeLoader}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === ModalMap['7'] && (
|
||||||
|
<NoteWifi
|
||||||
|
type={'wifi'}
|
||||||
|
address={data.address}
|
||||||
|
currentNote={data.currentNote}
|
||||||
|
onClose={closeLoader}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === ModalMap['8'] && (
|
||||||
|
<ConnectNetwork
|
||||||
|
address={data.address}
|
||||||
|
onClose={closeLoader}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === ModalMap['9'] && (
|
||||||
|
<DisconnectConfirm
|
||||||
|
address={data.address}
|
||||||
|
onClose={closeLoader}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === ModalMap['4'] && (
|
||||||
|
<AddDevice onSave={data?.onSave} onClose={closeLoader} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{type === ModalMap['5'] && (
|
||||||
|
<RightsConfirm onClose={closeLoader} />
|
||||||
|
)}
|
||||||
|
</ModalWindow >
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
70
FRONT/DEV/src/components/modalwindows/style.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
export const ModalWindow = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ModalContainer = styled.div<{height?: string}>`
|
||||||
|
border-radius: 12px;
|
||||||
|
background: ${(props)=>props.theme.secondary};
|
||||||
|
color: ${(props=>props.theme.textColor)};
|
||||||
|
position: relative;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 15px;
|
||||||
|
height: ${(props) => props.height ? props.height : '230px'};
|
||||||
|
width: 520px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ModalTitle = styled.div`
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid #9D9D9D;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
h3{
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ModalContent = styled.div`
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
h3{
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p{
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ModalText = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ErrorMessage = styled.p`
|
||||||
|
color: #d95454;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ButtonContainer = styled.div`
|
||||||
|
justify-content: flex-end;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 20px 20px;
|
||||||
|
`
|
||||||
13
FRONT/DEV/src/components/modalwindows/windowsType.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
export type ModalType = Record<string, string>;
|
||||||
|
|
||||||
|
export const ModalMap: ModalType = {
|
||||||
|
'1': 'ipConfirm',
|
||||||
|
'2': 'loader',
|
||||||
|
'3': 'connectionError',
|
||||||
|
'4': 'addDevice',
|
||||||
|
'5': 'sudo',
|
||||||
|
'6': 'noteDevice',
|
||||||
|
'7': 'noteWifi',
|
||||||
|
'8': 'connectNetwork',
|
||||||
|
'9': 'disconnectConfirm',
|
||||||
|
}
|
||||||
194
FRONT/DEV/src/components/register.tsx
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
import { ModalContainer, ModalTitle, ModalContent, ModalText } from "./modalwindows/style"
|
||||||
|
import Input from "./UI/inputs/input"
|
||||||
|
import { MainButton } from "./UI/buttons/mainButton"
|
||||||
|
import styled from "styled-components"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { useIpStore } from "../store"
|
||||||
|
import { NetworkService } from "../api/request"
|
||||||
|
import { IconComponent } from "../const/buttons/icons"
|
||||||
|
import { buttonNames } from "../const/buttons/names"
|
||||||
|
import ThemeButton from "./UI/buttons/toggletheme"
|
||||||
|
import About from "./workSpace/modules/appModules/about"
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
background: ${(props) => props.theme.background};
|
||||||
|
`
|
||||||
|
|
||||||
|
const Container = styled.form`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 25px;
|
||||||
|
align-items: center;
|
||||||
|
background: ${(props) => props.theme.primary};
|
||||||
|
padding: 25px;
|
||||||
|
border-radius: 18px;
|
||||||
|
width: 350px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Title = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 25px;
|
||||||
|
text-align: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ButtonContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Menu = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Button = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
`
|
||||||
|
|
||||||
|
const IconContainer = styled.div`
|
||||||
|
padding: 10px;
|
||||||
|
color: ${props => props.theme.text};
|
||||||
|
`
|
||||||
|
|
||||||
|
export const CreateUser = () => {
|
||||||
|
|
||||||
|
const { ipAddress, setIpAddress } = useIpStore();
|
||||||
|
const [repeatPassword, setRepeatPassword] = useState('');
|
||||||
|
const [passwordsMatch, setPasswordsMatch] = useState(true);
|
||||||
|
const [about, setAbout] = useState(false)
|
||||||
|
|
||||||
|
const [user, setUser] = useState({
|
||||||
|
username: 'operator',
|
||||||
|
password: '',
|
||||||
|
is_admin: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleChange = (id: string, value: string) => {
|
||||||
|
if (id === 'repeatPassword') {
|
||||||
|
setRepeatPassword(value);
|
||||||
|
setPasswordsMatch(user.password === value);
|
||||||
|
} else {
|
||||||
|
setUser(prevData => ({
|
||||||
|
...prevData,
|
||||||
|
[id]: value
|
||||||
|
}));
|
||||||
|
if (id === 'password') {
|
||||||
|
setPasswordsMatch(value === repeatPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = async (user: object) => {
|
||||||
|
try {
|
||||||
|
const result = await NetworkService.createUser(ipAddress, user);
|
||||||
|
if (result.status === 200) { window.location.reload() }
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (passwordsMatch) {
|
||||||
|
handleSave(user);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const back = ()=>{
|
||||||
|
setIpAddress('localhost')
|
||||||
|
}
|
||||||
|
|
||||||
|
const toAbout = ()=>{
|
||||||
|
setAbout(true)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
|
||||||
|
<>
|
||||||
|
{!about ? (
|
||||||
|
<Wrapper>
|
||||||
|
|
||||||
|
<Container onSubmit={handleSubmit}>
|
||||||
|
<Title>
|
||||||
|
<IconComponent name={buttonNames[3]} width="52" height="52"></IconComponent>
|
||||||
|
<h3>Настройка оператора узла</h3>
|
||||||
|
{ipAddress != 'localhost' &&
|
||||||
|
<p>{ipAddress}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
<Content>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>Перед началом работы необходимо создать пароль оператора приложения</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholderText='Введите пароль...'
|
||||||
|
id="password"
|
||||||
|
value={user.password}
|
||||||
|
onChange={(value) => handleChange('password', value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholderText='Повторите пароль...'
|
||||||
|
id="repeatPassword"
|
||||||
|
value={repeatPassword}
|
||||||
|
onChange={(value) => handleChange('repeatPassword', value)}
|
||||||
|
style={{ marginTop: '10px' }}
|
||||||
|
/>
|
||||||
|
{!passwordsMatch && (
|
||||||
|
<p style={{ color: 'red', marginTop: '5px' }}>
|
||||||
|
Пароли не совпадают!
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Content>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton style={{ width: '100%' }} title="Отправить" type="submit" />
|
||||||
|
</ButtonContainer>
|
||||||
|
|
||||||
|
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
|
||||||
|
<Menu>
|
||||||
|
{ipAddress != 'localhost' &&
|
||||||
|
<MainButton title="Назад" onClick={back} style={{marginRight: '10px'}}/>
|
||||||
|
}
|
||||||
|
<ThemeButton />
|
||||||
|
<Button>
|
||||||
|
<IconContainer>
|
||||||
|
<IconComponent name={buttonNames[4]} width="30" height="30" onClick={toAbout}/>
|
||||||
|
</IconContainer>
|
||||||
|
</Button>
|
||||||
|
</Menu>
|
||||||
|
</Wrapper>
|
||||||
|
): (
|
||||||
|
<About from="reg" onBack={() => setAbout(false)} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
249
FRONT/DEV/src/components/workSpace/modules/appModules/about.tsx
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
|
||||||
|
import { MainButton } from "../../../UI/buttons/mainButton"
|
||||||
|
import { workSpaceNames } from "../../../../const/workspace/names"
|
||||||
|
import { useWorkSpaceStore } from "../../../../store"
|
||||||
|
import styled, {useTheme} from "styled-components"
|
||||||
|
import ThemeButton from "../../../UI/buttons/toggletheme"
|
||||||
|
import {buttonNames} from "../../../../const/buttons/names";
|
||||||
|
import {IconComponent} from "../../../../const/buttons/icons";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
from: 'reg' | 'helloWindow' | 'workSpace'
|
||||||
|
onBack?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: ${(props => props.theme.background)};
|
||||||
|
padding: 50px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
max-width: 1200px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 40px 50px;
|
||||||
|
background: ${(props => props.theme.primary)};
|
||||||
|
border-radius: 18px;
|
||||||
|
position: relative;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Listing = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: ${(props => props.theme.secondary)};
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 24px 20px;
|
||||||
|
p{
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const List = styled.ul`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
font-size: 18px;
|
||||||
|
gap: 4px;
|
||||||
|
& li:before {
|
||||||
|
color: ${(props => props.theme.accent)};
|
||||||
|
content: "•";
|
||||||
|
font-size: 28px;
|
||||||
|
width: .8em;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const Title = styled.div`
|
||||||
|
font-size: 20px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const SubTitle = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
background-color:rgba(255, 248, 225, 0.6);
|
||||||
|
border-left: #ff9800 solid 4px;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
padding: 20px 12px;
|
||||||
|
|
||||||
|
a{
|
||||||
|
text-decoration: none;
|
||||||
|
color: ${(props => props.theme.errorColor)};
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 18px;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
font-size: 18px;
|
||||||
|
color: ${(props=>props.theme.textColor)};
|
||||||
|
}
|
||||||
|
.icon{
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
fill: #ff9800;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const Links = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: start;
|
||||||
|
gap: 20px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const LinkBlock = styled.a`
|
||||||
|
text-decoration: none;
|
||||||
|
color: ${(props => props.theme.accent)};;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Copyright = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
text-align: start;
|
||||||
|
color: ${(props => props.theme.offlineColor)};
|
||||||
|
`
|
||||||
|
|
||||||
|
const ButtonContainer = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 14px;
|
||||||
|
`
|
||||||
|
const AboutLine = styled.hr`
|
||||||
|
border: ${(props => props.theme.hoverBtn)} 1px solid;
|
||||||
|
width: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
|
const MainTitle = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
img{
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
.version{
|
||||||
|
text-wrap: nowrap;
|
||||||
|
background-color: ${(props => props.theme.hoverBtn)};
|
||||||
|
color: ${(props => props.theme.borderColor)};
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.title{
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function About({ from, onBack }: Props) {
|
||||||
|
const { setWorkModule } = useWorkSpaceStore()
|
||||||
|
const getBackHandler = () => {
|
||||||
|
switch (from) {
|
||||||
|
case 'reg':
|
||||||
|
return onBack
|
||||||
|
case 'helloWindow':
|
||||||
|
return () => setWorkModule(workSpaceNames[1])
|
||||||
|
case 'workSpace':
|
||||||
|
return () => setWorkModule(workSpaceNames[3])
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const backHandler = getBackHandler()
|
||||||
|
const showBackButton = from !== 'reg' || (from === 'reg' && onBack)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<Content>
|
||||||
|
|
||||||
|
<MainTitle>
|
||||||
|
<div className={'title'}>
|
||||||
|
<img src="./assets/images/gossys.png" alt="" />
|
||||||
|
<div style={{width: '100%', display: "flex", justifyContent: 'center'}}><h1>О приложении</h1></div>
|
||||||
|
</div>
|
||||||
|
<p className={'version'}>v 0.0.3</p>
|
||||||
|
</MainTitle>
|
||||||
|
|
||||||
|
<AboutLine/>
|
||||||
|
|
||||||
|
<Title>
|
||||||
|
<p>ГоNets — минимально работоспособный кроссплатформенный продукт (MVP) для демонстрации нашего подхода к управлению настройками ПК в корпоративной среде.</p>
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
<Listing>
|
||||||
|
<p>Возможности:</p>
|
||||||
|
<List>
|
||||||
|
<li>Визуализация активных сетевых ресурсов</li>
|
||||||
|
<li>Настройка параметров локальных и удалённых сетевых ресурсов</li>
|
||||||
|
<li>Управление взаимодействием ресурсов с Клиентом ГoNets</li>
|
||||||
|
</List>
|
||||||
|
</Listing>
|
||||||
|
|
||||||
|
<SubTitle>
|
||||||
|
<svg className="icon" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 2L1 21h22M12 6l7.53 13H4.47M11 10v4h2v-4m-2 6v2h2v-2"/>
|
||||||
|
</svg>
|
||||||
|
<p>Программное обеспечение исключительно для ознакомления и домашнего использования.</p>
|
||||||
|
<a href={'https://gossys.dev/pages/projects/gonets'}>Перейти на ГоNets-PRO</a>
|
||||||
|
</SubTitle>
|
||||||
|
|
||||||
|
<Links>
|
||||||
|
<LinkBlock href={'https://gossys.dev/pages/projects/gonets'}>
|
||||||
|
Домашняя страницa
|
||||||
|
</LinkBlock>
|
||||||
|
|
||||||
|
<LinkBlock href={'https://wiki.gossys.dev/ru/gonets'}>
|
||||||
|
Документация
|
||||||
|
</LinkBlock>
|
||||||
|
|
||||||
|
<LinkBlock href={'https://wiki.gossys.dev/ru/lic/GosysPUB'}>
|
||||||
|
Лицензия
|
||||||
|
</LinkBlock>
|
||||||
|
</Links>
|
||||||
|
|
||||||
|
<Copyright>
|
||||||
|
Разработано и поддерживается ООО «Гибридные Офисные Системы» 2024-2025 г. Все права защищены.
|
||||||
|
</Copyright>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
{showBackButton && backHandler && (
|
||||||
|
<IconComponent
|
||||||
|
width="36"
|
||||||
|
height="36"
|
||||||
|
name={buttonNames[12]}
|
||||||
|
onClick={backHandler}
|
||||||
|
/>
|
||||||
|
|
||||||
|
)}
|
||||||
|
|
||||||
|
{from == 'reg' &&
|
||||||
|
<ThemeButton />
|
||||||
|
}
|
||||||
|
</ButtonContainer>
|
||||||
|
</Content>
|
||||||
|
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import styled from "styled-components"
|
||||||
|
import { IconComponent } from "../../../../const/buttons/icons"
|
||||||
|
import { buttonNames } from "../../../../const/buttons/names"
|
||||||
|
import { useWorkSpaceStore } from "../../../../store"
|
||||||
|
import { useModalContext } from "../../../../store"
|
||||||
|
import { useAdminStore } from "../../../../store/adminStore"
|
||||||
|
import { ModalMap } from "../../../modalwindows/windowsType"
|
||||||
|
import { QuitButton } from "../../../UI/buttons/quitButton"
|
||||||
|
import { usePosStore } from "../../../../store/posStore"
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
background-color: ${(props) => props.theme.primary};
|
||||||
|
padding: 0 5px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ButtonContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
const IconContainer = styled.div`
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
svg{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Header = () => {
|
||||||
|
const {setNullPos} = usePosStore()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<ButtonContainer>
|
||||||
|
<IconContainer>
|
||||||
|
<IconComponent name={buttonNames[11]} width="30" height="30" onClick={()=>setNullPos(true)}/>
|
||||||
|
</IconContainer>
|
||||||
|
|
||||||
|
<QuitButton size="small"/>
|
||||||
|
</ButtonContainer>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import styled from "styled-components"
|
||||||
|
import { MainButton } from "../../../UI/buttons/mainButton"
|
||||||
|
import { useWorkSpaceStore } from "../../../../store"
|
||||||
|
import { workSpaceNames } from "../../../../const/workspace/names"
|
||||||
|
import { buttonNames } from "../../../../const/buttons/names"
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: ${(props => props.theme.background)};
|
||||||
|
background-image: url('./assets/images/back1.svg');
|
||||||
|
background-size: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: bottom;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 52px;
|
||||||
|
margin-bottom: 100px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ButtonWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 75px;
|
||||||
|
flex-direction: column;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ButtonContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
flex-direction: column;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Title = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
h1{
|
||||||
|
font-size: 52px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const HelloWindow = () => {
|
||||||
|
|
||||||
|
const { setWorkModule } = useWorkSpaceStore()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<Container className="unselectable">
|
||||||
|
<Title>
|
||||||
|
<img src="./assets/images/logo.svg" alt="" style={{width: '152px'}}/>
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
|
||||||
|
<ButtonWrapper>
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton
|
||||||
|
title="Настройки сети"
|
||||||
|
icon={buttonNames[3]}
|
||||||
|
iconSize={{ width: '32', height: '32' }}
|
||||||
|
onClick={() => setWorkModule(workSpaceNames[3])}
|
||||||
|
/>
|
||||||
|
<MainButton
|
||||||
|
title="О программе"
|
||||||
|
icon={buttonNames[4]}
|
||||||
|
iconSize={{ width: '32', height: '32' }}
|
||||||
|
onClick={()=>setWorkModule(workSpaceNames[4])}
|
||||||
|
/>
|
||||||
|
</ButtonContainer>
|
||||||
|
</ButtonWrapper>
|
||||||
|
</Container>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import DeviceCard from "../../../../../UI/Dropdown/DeviceCard";
|
||||||
|
import {DeviceUser} from "../../../../../../store/useDeviceStore";
|
||||||
|
|
||||||
|
const ScanBtn = styled.button`
|
||||||
|
position: relative;
|
||||||
|
padding: 14px 22px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
transition: .4s ease;
|
||||||
|
&:hover{
|
||||||
|
transform: scale(1.03);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
interface ListUsersProps {
|
||||||
|
loading: boolean;
|
||||||
|
handleScan: ()=>void;
|
||||||
|
devices: DeviceUser[];
|
||||||
|
scanInitiated: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps) => {
|
||||||
|
const activeDevices = devices.filter(device => device.status === 'online');
|
||||||
|
const inactiveDevices = devices.filter(device => device.status !== 'online');
|
||||||
|
|
||||||
|
// Пагинация
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [devicesPerPage] = useState(10);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ScanBtn onClick={handleScan} disabled={loading}>
|
||||||
|
{devices.length > 0 ? 'Обновить список' : 'Сканировать узлы'}
|
||||||
|
</ScanBtn>
|
||||||
|
|
||||||
|
{devices.length > 0 ? (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '70%',
|
||||||
|
gap: '20px',
|
||||||
|
}}>
|
||||||
|
{/* Колонка активных устройств */}
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<h3 style={{ color: '#4CAF50', marginBottom:'12px' }}>Активные узлы ({activeDevices.length})</h3>
|
||||||
|
{activeDevices.map((device, index) => (
|
||||||
|
<DeviceCard
|
||||||
|
key={index}
|
||||||
|
status="online"
|
||||||
|
device={device}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* Колонка неактивных устройств */}
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<h3 style={{ color: '#ababab', marginBottom:'12px' }}>Неактивные узлы ({inactiveDevices.length})</h3>
|
||||||
|
{inactiveDevices.map((device, index) => (
|
||||||
|
<DeviceCard
|
||||||
|
key={index}
|
||||||
|
status="offline"
|
||||||
|
device={device}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : scanInitiated ? (
|
||||||
|
<p style={{ marginTop: '20px' }}>Узлы не найдены</p>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListUsers;
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import DeviceCard from "../../../../../UI/Dropdown/DeviceCard";
|
||||||
|
import {DeviceUser} from "../../../../../../store/useDeviceStore";
|
||||||
|
import TableScanner from "../../../../../UI/tables/TableScanner";
|
||||||
|
|
||||||
|
const ScanBtn = styled.button`
|
||||||
|
position: relative;
|
||||||
|
padding: 14px 22px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
transition: .4s ease;
|
||||||
|
&:hover{
|
||||||
|
transform: scale(1.03);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
interface ListUsersProps {
|
||||||
|
loading: boolean;
|
||||||
|
handleScan: ()=>void;
|
||||||
|
devices: DeviceUser[];
|
||||||
|
scanInitiated: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ScanBtn onClick={handleScan} disabled={loading}>
|
||||||
|
{devices.length > 0 ? 'Обновить список' : 'Сканировать узлы'}
|
||||||
|
</ScanBtn>
|
||||||
|
|
||||||
|
{devices.length > 0 ? (
|
||||||
|
<TableScanner devices={devices} />
|
||||||
|
) : scanInitiated ? (
|
||||||
|
<p style={{ marginTop: '20px' }}>Узлы не найдены</p>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListUsers;
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
import { useIpStore } from "../../../../../store";
|
||||||
|
import styled, { keyframes } from "styled-components";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { NetworkService } from "../../../../../api/request";
|
||||||
|
import ListUsers from "./modules/ListUsers";
|
||||||
|
import TableUsers from "./modules/TableUsers";
|
||||||
|
import DropdownType from "../../../../UI/Dropdown/DropdownType";
|
||||||
|
import useNetworkStore from "../../../../../store/useDeviceStore";
|
||||||
|
|
||||||
|
type Option ={
|
||||||
|
value: number,
|
||||||
|
label: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Анимации
|
||||||
|
const ring1 = keyframes`
|
||||||
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||||
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.2; }
|
||||||
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ring2 = keyframes`
|
||||||
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||||
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.15; }
|
||||||
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ring3 = keyframes`
|
||||||
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||||
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.1; }
|
||||||
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||||
|
`;
|
||||||
|
const dotAnimation = keyframes`
|
||||||
|
0% { content: '.'; }
|
||||||
|
33% { content: '..'; }
|
||||||
|
66% { content: '...'; }
|
||||||
|
100% { content: '.'; }
|
||||||
|
`;
|
||||||
|
const pulse = keyframes`
|
||||||
|
0% { transform: translate(-50%, -50%) scale(1); }
|
||||||
|
50% { transform: translate(-50%, -50%) scale(1.1); }
|
||||||
|
100% { transform: translate(-50%, -50%) scale(1); }
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Стили
|
||||||
|
const Wrapper = styled.div<{ scanInitiated: boolean, loading: boolean}>`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content:${({scanInitiated, loading})=>(scanInitiated && !loading ? 'start' : 'center')};
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: ${(props) => props.theme.background};
|
||||||
|
padding: 50px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
gap: 24px;
|
||||||
|
transition: justify-content .4s ease;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
const TitleWrapper = styled.h2`
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-size: 32px;
|
||||||
|
width: max-content;
|
||||||
|
`
|
||||||
|
const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
transition: .4s ease;
|
||||||
|
transform: ${({scanInitiated,loading})=>(scanInitiated && !loading ? 'scale(0)' : 'scale(1)')};
|
||||||
|
span {
|
||||||
|
position: absolute;
|
||||||
|
display: inline-block;
|
||||||
|
background: ${(props) => props.theme.scannerColor};
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
&.center {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
animation: ${({ loading }) => (loading ? pulse : "none")} 2s ease infinite;
|
||||||
|
}
|
||||||
|
&.ring1 {
|
||||||
|
opacity: 0.12;
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
animation: ${({ loading }) => (loading ? ring1 : "none")} 4s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ring2 {
|
||||||
|
opacity: 0.08;
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
animation: ${({ loading }) => (loading ? ring2 : "none")} 4s linear infinite;
|
||||||
|
animation-delay: ${({ loading }) => (loading ? "0.1s" : "0")};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ring3 {
|
||||||
|
opacity: 0.04;
|
||||||
|
width: 550px;
|
||||||
|
height: 550px;
|
||||||
|
animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
|
||||||
|
animation-delay: ${({ loading }) => (loading ? "0.2s" : "0")};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LoadingText = styled.div`
|
||||||
|
position: relative;
|
||||||
|
padding: 14px 22px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
`;
|
||||||
|
const ScanningText = styled.span`
|
||||||
|
&::after {
|
||||||
|
content: '.';
|
||||||
|
animation: ${dotAnimation} 1.5s infinite;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
export default function UserScanner() {
|
||||||
|
const { ipAddress } = useIpStore();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { devices, setDevices } = useNetworkStore();
|
||||||
|
const [scanInitiated, setScanInitiated] = useState(devices.length > 0);
|
||||||
|
|
||||||
|
// Сканирование
|
||||||
|
const handleScan = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setScanInitiated(true);
|
||||||
|
const res = await NetworkService.scanUser(ipAddress);
|
||||||
|
console.log(res);
|
||||||
|
setDevices(res.data.devices || []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Ошибка сканирования сети:", err);
|
||||||
|
setDevices([])
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Способ отображения
|
||||||
|
const viewOptions: Option[] = [
|
||||||
|
{ value: 1, label: 'Таблица' },
|
||||||
|
{ value: 2, label: 'Список' },
|
||||||
|
];
|
||||||
|
const [selectedView, setSelectedView] = useState<Option>(viewOptions[0]);
|
||||||
|
const handleViewChange = (option: Option) => {
|
||||||
|
setSelectedView(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper scanInitiated={scanInitiated} loading={loading}>
|
||||||
|
{(!scanInitiated || loading) && (<TitleWrapper>Сканирование узлов подсети</TitleWrapper>)}
|
||||||
|
<UserLocate loading={loading} scanInitiated={scanInitiated}>
|
||||||
|
<span className="center"></span>
|
||||||
|
<span className="ring1"></span>
|
||||||
|
<span className="ring2"></span>
|
||||||
|
<span className="ring3"></span>
|
||||||
|
</UserLocate>
|
||||||
|
|
||||||
|
{scanInitiated && !loading && (
|
||||||
|
<DropdownType options={viewOptions} value={selectedView} onChange={handleViewChange} label="Способ отображения" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<LoadingText>
|
||||||
|
Сканирование узлов <ScanningText />
|
||||||
|
</LoadingText>
|
||||||
|
) : selectedView.value == 1 ? (
|
||||||
|
<TableUsers loading={loading} handleScan={handleScan} devices={devices} scanInitiated={scanInitiated} />
|
||||||
|
):(
|
||||||
|
<ListUsers loading={loading} handleScan={handleScan} devices={devices} scanInitiated={scanInitiated} />
|
||||||
|
)}
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
import styled, { keyframes } from "styled-components";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { NetworkService } from "../../../../../api/request";
|
||||||
|
import WifiDot, {WifiNetworkScanner} from "./modules/WifiDot";
|
||||||
|
import ModalWifiScaner from "./modules/ModalWifiScaner";
|
||||||
|
import useNetworkStore from "../../../../../store/useDeviceStore";
|
||||||
|
import {useIpStore} from "../../../../../store";
|
||||||
|
|
||||||
|
// Анимации
|
||||||
|
const ring1 = keyframes`
|
||||||
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||||
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.2; }
|
||||||
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||||
|
`;
|
||||||
|
const ring2 = keyframes`
|
||||||
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||||
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.15; }
|
||||||
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||||
|
`;
|
||||||
|
const ring3 = keyframes`
|
||||||
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||||
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.1; }
|
||||||
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||||
|
`;
|
||||||
|
const dotAnimation = keyframes`
|
||||||
|
0% { content: '.'; }
|
||||||
|
33% { content: '..'; }
|
||||||
|
66% { content: '...'; }
|
||||||
|
100% { content: '.'; }
|
||||||
|
`;
|
||||||
|
const pulse = keyframes`
|
||||||
|
0% { transform: translate(-50%, -50%) scale(1); }
|
||||||
|
50% { transform: translate(-50%, -50%) scale(1.1); }
|
||||||
|
100% { transform: translate(-50%, -50%) scale(1); }
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Стили
|
||||||
|
const Wrapper = styled.div<{ scanInitiated: boolean, loading: boolean}>`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content:center ;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
background: ${(props) => props.theme.background};
|
||||||
|
padding: 50px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
gap: 24px;
|
||||||
|
transition: justify-content .4s ease;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
`;
|
||||||
|
const TitleWrapper = styled.h2`
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-size: 32px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
transition: .4s ease;
|
||||||
|
transform: scale(1);
|
||||||
|
.circel {
|
||||||
|
position: absolute;
|
||||||
|
display: inline-block;
|
||||||
|
background: ${(props) => props.theme.scannerColor};
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
&.center {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
animation: ${({ loading }) => (loading ? pulse : "none")} 2s ease infinite;
|
||||||
|
}
|
||||||
|
&.ring1 {
|
||||||
|
opacity: 0.12;
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
animation: ${({ loading }) => (loading ? ring1 : "none")} 4s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ring2 {
|
||||||
|
opacity: 0.08;
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
animation: ${({ loading }) => (loading ? ring2 : "none")} 4s linear infinite;
|
||||||
|
animation-delay: ${({ loading }) => (loading ? "0.1s" : "0")};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ring3 {
|
||||||
|
opacity: 0.04;
|
||||||
|
width: 550px;
|
||||||
|
height: 550px;
|
||||||
|
animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
|
||||||
|
animation-delay: ${({ loading }) => (loading ? "0.2s" : "0")};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
const LoadingText = styled.div`
|
||||||
|
position: relative;
|
||||||
|
padding: 14px 22px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
`;
|
||||||
|
const ScanningText = styled.span`
|
||||||
|
&::after {
|
||||||
|
content: '.';
|
||||||
|
animation: ${dotAnimation} 1.5s infinite;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const ScanBtn = styled.button<{scanInitiated: boolean}>`
|
||||||
|
position: relative;
|
||||||
|
padding: 14px 22px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
transition: transform .4s ease;
|
||||||
|
transform: ${({scanInitiated}) => (scanInitiated ? "translateY(240px)" : "translateY(0)")};
|
||||||
|
&:hover{
|
||||||
|
transform: ${({scanInitiated}) => (scanInitiated ? "translateY(240px)" : "translateY(0)")} scale(1.03);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export function WifiScanner() {
|
||||||
|
//const { ipAddress } = useIpStore();
|
||||||
|
const ipAddress = '192.168.1.147'
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [scanInitiated, setScanInitiated] = useState(false);
|
||||||
|
const { wifiNetworks, setWifiNetworks } = useNetworkStore();
|
||||||
|
const [currentNetwork, setCurrentNetwork] = useState<WifiNetworkScanner | null>(null);
|
||||||
|
|
||||||
|
const handleScan = async () => {
|
||||||
|
setWifiNetworks([]);
|
||||||
|
setCurrentNetwork(null)
|
||||||
|
setLoading(true);
|
||||||
|
setScanInitiated(true);
|
||||||
|
try {
|
||||||
|
const res = await NetworkService.scanWifi(ipAddress);
|
||||||
|
const networks = res.data.access_points || [];
|
||||||
|
setWifiNetworks(networks);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Ошибка сканирования сети:", err);
|
||||||
|
setWifiNetworks([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper scanInitiated={scanInitiated} loading={loading}>
|
||||||
|
<TitleWrapper>Сканирование точек доступа Wi-Fi сети</TitleWrapper>
|
||||||
|
|
||||||
|
<UserLocate loading={loading} scanInitiated={scanInitiated}>
|
||||||
|
<span className="center circel"></span>
|
||||||
|
<span className="ring1 circel"></span>
|
||||||
|
<span className="ring2 circel"></span>
|
||||||
|
<span className="ring3 circel"></span>
|
||||||
|
{wifiNetworks.map((network, index) => (
|
||||||
|
<WifiDot
|
||||||
|
key={network.bssid}
|
||||||
|
network={network}
|
||||||
|
index={index}
|
||||||
|
total={wifiNetworks.length}
|
||||||
|
onSelect={setCurrentNetwork}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{currentNetwork && (
|
||||||
|
<ModalWifiScaner
|
||||||
|
onClose={() => setCurrentNetwork(null)}
|
||||||
|
network={currentNetwork}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</UserLocate>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<LoadingText>
|
||||||
|
Сканирование точек доступа <ScanningText/>
|
||||||
|
</LoadingText>
|
||||||
|
) : (
|
||||||
|
<ScanBtn onClick={handleScan} disabled={loading} scanInitiated={wifiNetworks.length > 0 }>
|
||||||
|
{wifiNetworks.length > 0 ? 'Обновить список' : 'Сканировать точки доступа'}
|
||||||
|
</ScanBtn>
|
||||||
|
)}
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {WifiNetworkScanner} from "./WifiDot";
|
||||||
|
import {IconComponent} from "../../../../../../const/buttons/icons";
|
||||||
|
import {buttonNames} from "../../../../../../const/buttons/names";
|
||||||
|
import {ModalMap} from "../../../../../modalwindows/windowsType";
|
||||||
|
import {useIpStore, useModalContext} from "../../../../../../store";
|
||||||
|
import {Status} from "../../../../../customeNodes/nodes/networkInterface/networkNode/style";
|
||||||
|
import SignalSVG from "../../../../../UI/signalSvg";
|
||||||
|
import { useTheme } from 'styled-components';
|
||||||
|
import useNetworkStore, {DeviceUser} from "../../../../../../store/useDeviceStore";
|
||||||
|
import {NetworkService} from "../../../../../../api/request";
|
||||||
|
|
||||||
|
const Modal = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: ${(props) => props.theme.secondary};
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 32px 28px;
|
||||||
|
bottom: -40%;
|
||||||
|
left: 220px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 20;
|
||||||
|
min-width: 300px;
|
||||||
|
&:hover .close-btn{
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const CloseBtn = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
right: -20px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: ${(props) => props.theme.primary};
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
transition: all .4s ease;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: visible;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: ${(props) => props.theme.hoverBtn};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const ModalTitle = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
h4{
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.functionalIcons{
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const ModalContent = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
`
|
||||||
|
const ModalRow = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: start;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
h6{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
max-width:180px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const ModalConnect = styled.button`
|
||||||
|
color: ${(props) => props.theme.primary};
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
background-color: ${(props) => props.theme.accent};
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: .4s ease;
|
||||||
|
&:hover{
|
||||||
|
background-color: ${(props) => props.theme.accentHover};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ModalWifiScaner = ({ network,onClose }: { network: WifiNetworkScanner, onClose: ()=>void }) => {
|
||||||
|
const { openModal } = useModalContext();
|
||||||
|
const { ipAddress } = useIpStore();
|
||||||
|
const theme = useTheme()
|
||||||
|
const [signal, setSignal] = useState(1);
|
||||||
|
const { wifiNetworks } = useNetworkStore();
|
||||||
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getStatus = async () => {
|
||||||
|
try {
|
||||||
|
const response = await NetworkService.statusNetwork(ipAddress);
|
||||||
|
const { status, ssid } = response.data;
|
||||||
|
setIsConnected(
|
||||||
|
!!network?.ssid &&
|
||||||
|
ssid === network.ssid &&
|
||||||
|
status === 'connected'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
setIsConnected(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getStatus();
|
||||||
|
}, [network?.ssid, ipAddress]);
|
||||||
|
|
||||||
|
const currentNetwork = wifiNetworks.find(n => n.bssid === network.bssid) || network;
|
||||||
|
const openModalNote = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openModal(ModalMap['7'], {
|
||||||
|
address: currentNetwork.ssid,
|
||||||
|
currentNote: currentNetwork.note || ''
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const openModalNetwork= (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if(isConnected){
|
||||||
|
openModal(ModalMap['9'], {
|
||||||
|
address: currentNetwork.ssid,
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
openModal(ModalMap['8'], {
|
||||||
|
address: currentNetwork.ssid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const perc = Number(network.signal);
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
case perc >= -50: setSignal(5); break;
|
||||||
|
case perc >= -60: setSignal(4); break;
|
||||||
|
case perc >= -70: setSignal(3); break;
|
||||||
|
case perc >= -80: setSignal(2); break;
|
||||||
|
default: setSignal(1); break;
|
||||||
|
}
|
||||||
|
}, [network.signal])
|
||||||
|
return (
|
||||||
|
<Modal>
|
||||||
|
<CloseBtn className={'close-btn'} onClick={onClose}>
|
||||||
|
<IconComponent name={buttonNames[12]} width="30" height="30" />
|
||||||
|
</CloseBtn>
|
||||||
|
<ModalTitle>
|
||||||
|
<h4>{network.ssid}</h4>
|
||||||
|
<div className={'functionalIcons'}>
|
||||||
|
|
||||||
|
<span onClick={openModalNote} style={{cursor: "pointer"}}>
|
||||||
|
<IconComponent name={buttonNames[16]} width="30" height="30" />
|
||||||
|
</span>
|
||||||
|
<Status>
|
||||||
|
<SignalSVG signal={signal} size={'36px'} color={theme.accent} />
|
||||||
|
</Status>
|
||||||
|
</div>
|
||||||
|
</ModalTitle>
|
||||||
|
<ModalContent>
|
||||||
|
<ModalRow>
|
||||||
|
<h6>BSSID:</h6>
|
||||||
|
<p>{network.bssid}</p>
|
||||||
|
</ModalRow>
|
||||||
|
<ModalRow>
|
||||||
|
<h6>Производитель:</h6>
|
||||||
|
<p>{network.vendor}</p>
|
||||||
|
</ModalRow>
|
||||||
|
<ModalRow>
|
||||||
|
<h6>Сигнал:</h6>
|
||||||
|
<p>{network.signal} dBm</p>
|
||||||
|
</ModalRow>
|
||||||
|
<ModalRow>
|
||||||
|
<h6>Канал:</h6>
|
||||||
|
<p>{network.channel}</p>
|
||||||
|
</ModalRow>
|
||||||
|
<ModalRow>
|
||||||
|
<h6>Шифрование:</h6>
|
||||||
|
<p>{network.encryption}</p>
|
||||||
|
</ModalRow>
|
||||||
|
<ModalRow>
|
||||||
|
<h6>Частота:</h6>
|
||||||
|
<p>{network.frequency}</p>
|
||||||
|
</ModalRow>
|
||||||
|
<ModalRow>
|
||||||
|
<h6>Скрытая сеть:</h6>
|
||||||
|
<p>{network.hidden ? 'Да' : 'Нет'}</p>
|
||||||
|
</ModalRow>
|
||||||
|
<ModalRow>
|
||||||
|
<h6>Заметка:</h6>
|
||||||
|
<p>{currentNetwork.note ? currentNetwork.note : '-'}</p>
|
||||||
|
</ModalRow>
|
||||||
|
<ModalConnect onClick={openModalNetwork}>{isConnected ? 'Отключиться' : 'Подключиться'}</ModalConnect>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalWifiScaner;
|
||||||