Compare commits
40 Commits
175b4f993d
...
e47161acd1
| Author | SHA1 | Date |
|---|---|---|
|
|
e47161acd1 | |
|
|
fd53b187d5 | |
|
|
b4d653f3a6 | |
|
|
54cf5504a4 | |
|
|
f38c8825fe | |
|
|
c7ebbcaf5c | |
|
|
d83f05e2b5 | |
|
|
bcdbd0f0fc | |
|
|
08eaa274b2 | |
|
|
22c5fcf02c | |
|
|
2c9813fbb9 | |
|
|
c1b0f10e62 | |
|
|
858f6e2c4c | |
|
|
4eed2f364c | |
|
|
5b25876056 | |
|
|
9ee15160e5 | |
|
|
e56b82bb66 | |
|
|
46484efdea | |
|
|
b6b3b36f5a | |
|
|
46da90fbb6 | |
|
|
64401cadbc | |
|
|
a24b89220c | |
|
|
32ece2f0ff | |
|
|
4405c693aa | |
|
|
6a4640ba93 | |
|
|
bd96278895 | |
|
|
ed2e03e202 | |
|
|
cf38b25678 | |
|
|
3ee0c3a02c | |
|
|
b6b8042d11 | |
|
|
fc1db66288 | |
|
|
c077449b2c | |
|
|
15a8a146fc | |
|
|
b601dedc0e | |
|
|
9399f544d2 | |
|
|
5ed1b448e5 | |
|
|
2d714b5985 | |
|
|
3fc7ee0ac3 | |
|
|
13101ac57c | |
|
|
67f776efe3 |
|
|
@ -30,7 +30,23 @@ pipeline {
|
||||||
stage ('Initialize variables') {
|
stage ('Initialize variables') {
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
env.IMAGE_TAG = sh(script: "git describe --tags --abbrev=0", returnStdout: true).trim()
|
def hasTags = sh(script: "git tag -l | wc -l", returnStdout: true).trim().toInteger() > 0
|
||||||
|
echo "${hasTags}"
|
||||||
|
|
||||||
|
def lastVersion = "0.0.0"
|
||||||
|
|
||||||
|
if (hasTags) {
|
||||||
|
lastVersion = sh(script: "git describe --tags --abbrev=0", returnStdout: true).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Last version: ${lastVersion}"
|
||||||
|
|
||||||
|
def (major, minor, patch) = lastVersion.tokenize('.')
|
||||||
|
def newVersion = "${major}.${minor}.${patch.toInteger() + 1}"
|
||||||
|
echo "New version: ${newVersion}"
|
||||||
|
|
||||||
|
env.IMAGE_TAG = newVersion
|
||||||
|
env.NEW_VERSION = newVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -71,17 +87,29 @@ pipeline {
|
||||||
echo "Attempting to merge PR ${env.CHANGE_ID} into master..."
|
echo "Attempting to merge PR ${env.CHANGE_ID} into master..."
|
||||||
withCredentials([usernamePassword(credentialsId: 'gitea_creds', usernameVariable: 'GITEA_USER', passwordVariable: 'GITEA_PASS')]) {
|
withCredentials([usernamePassword(credentialsId: 'gitea_creds', usernameVariable: 'GITEA_USER', passwordVariable: 'GITEA_PASS')]) {
|
||||||
def prId = env.CHANGE_ID
|
def prId = env.CHANGE_ID
|
||||||
|
|
||||||
sh """
|
sh """
|
||||||
curl -X POST \
|
curl -X POST \
|
||||||
-u "${GITEA_USER}:${GITEA_PASS}" \
|
-u "${GITEA_USER}:${GITEA_PASS}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"do":"merge"}' \
|
-d '{"do":"merge"}' \
|
||||||
http://git.entcor/api/v1/repos/deployer3000/trust-module-frontend/pulls/${prId}/merge
|
http://git.entcor/api/v1/repos/deployer3000/${env.IMAGE_NAME}/pulls/${prId}/merge
|
||||||
"""
|
"""
|
||||||
echo "PR ${prId} merged successfully into master!"
|
def commitHash = sh(script: "git rev-parse HEAD~1", returnStdout: true).trim() // необходим для корректного отображения статусов
|
||||||
def context = "test-org/trust-module-frontend/pipeline/pr-${env.CHANGE_TARGET}"
|
echo "PR ${prId} merged successfully into main!"
|
||||||
def commitHash = sh(script: "git rev-parse HEAD~1", returnStdout: true).trim()
|
sleep(time: 15, unit: 'SECONDS')
|
||||||
notify(context, GITEA_USER, GITEA_PASS, env.GITEA_REPOSITORY_URL, "trust-module-frontend", commitHash, "success")
|
sh "git checkout main && git pull origin main"
|
||||||
|
|
||||||
|
sh """
|
||||||
|
curl -v -X POST -u "${GITEA_USER}:${GITEA_PASS}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"tag_name": "${env.NEW_VERSION}", "name": "Release ${env.NEW_VERSION}", "target_commitish": "main"}' \
|
||||||
|
"${env.GITEA_REPOSITORY_URL}deployer3000/${env.IMAGE_NAME}/releases"
|
||||||
|
"""
|
||||||
|
echo "New release succeeded!"
|
||||||
|
|
||||||
|
def context = "test-org/${env.IMAGE_NAME}/pipeline/pr-${env.CHANGE_TARGET}"
|
||||||
|
notify(context, GITEA_USER, GITEA_PASS, env.GITEA_REPOSITORY_URL, env.IMAGE_NAME, commitHash, "success")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@
|
||||||
"name": "trust-module",
|
"name": "trust-module",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"@mui/icons-material": "^6.4.8",
|
||||||
|
"@mui/material": "^6.4.7",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"chart.js": "^4.0.0",
|
"chart.js": "^4.0.0",
|
||||||
"chartjs-adapter-date-fns": "^3.0.0",
|
"chartjs-adapter-date-fns": "^3.0.0",
|
||||||
|
|
@ -50,7 +54,6 @@
|
||||||
"version": "7.26.2",
|
"version": "7.26.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||||
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
|
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-validator-identifier": "^7.25.9",
|
"@babel/helper-validator-identifier": "^7.25.9",
|
||||||
|
|
@ -106,7 +109,6 @@
|
||||||
"version": "7.26.5",
|
"version": "7.26.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz",
|
||||||
"integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
|
"integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.26.5",
|
"@babel/parser": "^7.26.5",
|
||||||
|
|
@ -140,7 +142,6 @@
|
||||||
"version": "7.25.9",
|
"version": "7.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
|
||||||
"integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
|
"integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/traverse": "^7.25.9",
|
"@babel/traverse": "^7.25.9",
|
||||||
|
|
@ -182,7 +183,6 @@
|
||||||
"version": "7.25.9",
|
"version": "7.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
|
||||||
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
|
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
|
|
@ -192,7 +192,6 @@
|
||||||
"version": "7.25.9",
|
"version": "7.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
|
||||||
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
|
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
|
|
@ -226,7 +225,6 @@
|
||||||
"version": "7.26.7",
|
"version": "7.26.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
|
||||||
"integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
|
"integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.26.7"
|
"@babel/types": "^7.26.7"
|
||||||
|
|
@ -285,7 +283,6 @@
|
||||||
"version": "7.25.9",
|
"version": "7.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
|
||||||
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
|
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.25.9",
|
"@babel/code-frame": "^7.25.9",
|
||||||
|
|
@ -300,7 +297,6 @@
|
||||||
"version": "7.26.7",
|
"version": "7.26.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz",
|
||||||
"integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==",
|
"integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.26.2",
|
"@babel/code-frame": "^7.26.2",
|
||||||
|
|
@ -319,7 +315,6 @@
|
||||||
"version": "11.12.0",
|
"version": "11.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
|
|
@ -329,7 +324,6 @@
|
||||||
"version": "7.26.7",
|
"version": "7.26.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
|
||||||
"integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
|
"integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.25.9",
|
"@babel/helper-string-parser": "^7.25.9",
|
||||||
|
|
@ -339,6 +333,144 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emotion/babel-plugin": {
|
||||||
|
"version": "11.13.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||||
|
"integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-module-imports": "^7.16.7",
|
||||||
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"@emotion/hash": "^0.9.2",
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"babel-plugin-macros": "^3.1.0",
|
||||||
|
"convert-source-map": "^1.5.0",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"find-root": "^1.1.0",
|
||||||
|
"source-map": "^0.5.7",
|
||||||
|
"stylis": "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/cache": {
|
||||||
|
"version": "11.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
|
||||||
|
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/sheet": "^1.4.0",
|
||||||
|
"@emotion/utils": "^1.4.2",
|
||||||
|
"@emotion/weak-memoize": "^0.4.0",
|
||||||
|
"stylis": "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/hash": {
|
||||||
|
"version": "0.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
|
||||||
|
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/is-prop-valid": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/memoize": "^0.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/memoize": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/react": {
|
||||||
|
"version": "11.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||||
|
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
|
"@emotion/cache": "^11.14.0",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
||||||
|
"@emotion/utils": "^1.4.2",
|
||||||
|
"@emotion/weak-memoize": "^0.4.0",
|
||||||
|
"hoist-non-react-statics": "^3.3.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/serialize": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/hash": "^0.9.2",
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/unitless": "^0.10.0",
|
||||||
|
"@emotion/utils": "^1.4.2",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/sheet": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/styled": {
|
||||||
|
"version": "11.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
|
||||||
|
"integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
|
"@emotion/is-prop-valid": "^1.3.0",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
||||||
|
"@emotion/utils": "^1.4.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.0.0-rc.0",
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/unitless": {
|
||||||
|
"version": "0.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
|
||||||
|
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/utils": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA=="
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/weak-memoize": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.24.2",
|
"version": "0.24.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
|
||||||
|
|
@ -1023,7 +1155,6 @@
|
||||||
"version": "0.3.8",
|
"version": "0.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||||
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/set-array": "^1.2.1",
|
"@jridgewell/set-array": "^1.2.1",
|
||||||
|
|
@ -1038,7 +1169,6 @@
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
|
|
@ -1048,7 +1178,6 @@
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
|
|
@ -1058,14 +1187,12 @@
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.25",
|
"version": "0.3.25",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
|
|
@ -1077,6 +1204,247 @@
|
||||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
|
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/core-downloads-tracker": {
|
||||||
|
"version": "6.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.8.tgz",
|
||||||
|
"integrity": "sha512-vjP4+A1ybyCRhDZC7r5EPWu/gLseFZxaGyPdDl94vzVvk6Yj6gahdaqcjbhkaCrJjdZj90m3VioltWPAnWF/zw==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/icons-material": {
|
||||||
|
"version": "6.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.8.tgz",
|
||||||
|
"integrity": "sha512-LKGWiLWRyoOw3dWxZQ+lV//mK+4DVTTAiLd2ljmJdD6XV0rDB8JFKjRD9nyn9cJAU5XgWnii7ZR3c93ttUnMKg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@mui/material": "^6.4.8",
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/material": {
|
||||||
|
"version": "6.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.8.tgz",
|
||||||
|
"integrity": "sha512-5S9UTjKZZBd9GfbcYh/nYfD9cv6OXmj5Y7NgKYfk7JcSoshp8/pW5zP4wecRiroBSZX8wcrywSgogpVNO+5W0Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0",
|
||||||
|
"@mui/core-downloads-tracker": "^6.4.8",
|
||||||
|
"@mui/system": "^6.4.8",
|
||||||
|
"@mui/types": "~7.2.24",
|
||||||
|
"@mui/utils": "^6.4.8",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"@types/react-transition-group": "^4.4.12",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"csstype": "^3.1.3",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-is": "^19.0.0",
|
||||||
|
"react-transition-group": "^4.4.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.5.0",
|
||||||
|
"@emotion/styled": "^11.3.0",
|
||||||
|
"@mui/material-pigment-css": "^6.4.8",
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@mui/material-pigment-css": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/material/node_modules/react-is": {
|
||||||
|
"version": "19.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz",
|
||||||
|
"integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g=="
|
||||||
|
},
|
||||||
|
"node_modules/@mui/private-theming": {
|
||||||
|
"version": "6.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.8.tgz",
|
||||||
|
"integrity": "sha512-sWwQoNSn6elsPTAtSqCf+w5aaGoh7AASURNmpy+QTTD/zwJ0Jgwt0ZaaP6mXq2IcgHxYnYloM/+vJgHPMkRKTQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0",
|
||||||
|
"@mui/utils": "^6.4.8",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/styled-engine": {
|
||||||
|
"version": "6.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.8.tgz",
|
||||||
|
"integrity": "sha512-oyjx1b1FvUCI85ZMO4trrjNxGm90eLN3Ohy0AP/SqK5gWvRQg1677UjNf7t6iETOKAleHctJjuq0B3aXO2gtmw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0",
|
||||||
|
"@emotion/cache": "^11.13.5",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"@emotion/sheet": "^1.4.0",
|
||||||
|
"csstype": "^3.1.3",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.4.1",
|
||||||
|
"@emotion/styled": "^11.3.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/system": {
|
||||||
|
"version": "6.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.8.tgz",
|
||||||
|
"integrity": "sha512-gV7iBHoqlsIenU2BP0wq14BefRoZcASZ/4LeyuQglayBl+DfLX5rEd3EYR3J409V2EZpR0NOM1LATAGlNk2cyA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0",
|
||||||
|
"@mui/private-theming": "^6.4.8",
|
||||||
|
"@mui/styled-engine": "^6.4.8",
|
||||||
|
"@mui/types": "~7.2.24",
|
||||||
|
"@mui/utils": "^6.4.8",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"csstype": "^3.1.3",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.5.0",
|
||||||
|
"@emotion/styled": "^11.3.0",
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/types": {
|
||||||
|
"version": "7.2.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz",
|
||||||
|
"integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/utils": {
|
||||||
|
"version": "6.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.8.tgz",
|
||||||
|
"integrity": "sha512-C86gfiZ5BfZ51KqzqoHi1WuuM2QdSKoFhbkZeAfQRB+jCc4YNhhj11UXFVMMsqBgZ+Zy8IHNJW3M9Wj/LOwRXQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0",
|
||||||
|
"@mui/types": "~7.2.24",
|
||||||
|
"@types/prop-types": "^15.7.14",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-is": "^19.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/utils/node_modules/react-is": {
|
||||||
|
"version": "19.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz",
|
||||||
|
"integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g=="
|
||||||
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/popperjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.32.1",
|
"version": "4.32.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.1.tgz",
|
||||||
|
|
@ -1461,18 +1829,21 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/parse-json": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="
|
||||||
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.14",
|
"version": "15.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||||
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
|
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.18",
|
"version": "18.3.18",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
||||||
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
|
|
@ -1489,6 +1860,14 @@
|
||||||
"@types/react": "^18.0.0"
|
"@types/react": "^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-transition-group": {
|
||||||
|
"version": "4.4.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||||
|
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
|
||||||
|
|
@ -1749,6 +2128,39 @@
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/babel-plugin-macros": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"cosmiconfig": "^7.0.0",
|
||||||
|
"resolve": "^1.19.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10",
|
||||||
|
"npm": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/babel-plugin-macros/node_modules/resolve": {
|
||||||
|
"version": "1.22.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
|
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||||
|
"dependencies": {
|
||||||
|
"is-core-module": "^2.16.0",
|
||||||
|
"path-parse": "^1.0.7",
|
||||||
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"resolve": "bin/resolve"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
|
@ -1853,7 +2265,6 @@
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
|
|
@ -2026,6 +2437,29 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cosmiconfig": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/parse-json": "^4.0.0",
|
||||||
|
"import-fresh": "^3.2.1",
|
||||||
|
"parse-json": "^5.0.0",
|
||||||
|
"path-type": "^4.0.0",
|
||||||
|
"yaml": "^1.10.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cosmiconfig/node_modules/yaml": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
|
@ -2484,7 +2918,6 @@
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
|
|
@ -2605,6 +3038,14 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/error-ex": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||||
|
"dependencies": {
|
||||||
|
"is-arrayish": "^0.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-abstract": {
|
"node_modules/es-abstract": {
|
||||||
"version": "1.23.9",
|
"version": "1.23.9",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
|
||||||
|
|
@ -2827,7 +3268,6 @@
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
|
|
@ -3093,6 +3533,11 @@
|
||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/find-root": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
|
||||||
|
},
|
||||||
"node_modules/find-up": {
|
"node_modules/find-up": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||||
|
|
@ -3446,6 +3891,14 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
|
@ -3471,7 +3924,6 @@
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
|
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"parent-module": "^1.0.0",
|
"parent-module": "^1.0.0",
|
||||||
|
|
@ -3535,6 +3987,11 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-arrayish": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
|
||||||
|
},
|
||||||
"node_modules/is-async-function": {
|
"node_modules/is-async-function": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
|
||||||
|
|
@ -3605,7 +4062,6 @@
|
||||||
"version": "2.16.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
|
|
@ -3940,7 +4396,6 @@
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jsesc": "bin/jsesc"
|
"jsesc": "bin/jsesc"
|
||||||
|
|
@ -3956,6 +4411,11 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/json-parse-even-better-errors": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
|
||||||
|
},
|
||||||
"node_modules/json-schema-traverse": {
|
"node_modules/json-schema-traverse": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
|
|
@ -4023,6 +4483,11 @@
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lines-and-columns": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||||
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
|
|
@ -4126,7 +4591,6 @@
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
|
|
@ -4340,7 +4804,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"callsites": "^3.0.0"
|
"callsites": "^3.0.0"
|
||||||
|
|
@ -4349,6 +4812,23 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parse-json": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.0.0",
|
||||||
|
"error-ex": "^1.3.1",
|
||||||
|
"json-parse-even-better-errors": "^2.3.0",
|
||||||
|
"lines-and-columns": "^1.1.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-exists": {
|
"node_modules/path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
|
|
@ -4373,14 +4853,20 @@
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/path-type": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/possible-typed-array-names": {
|
"node_modules/possible-typed-array-names": {
|
||||||
|
|
@ -4657,7 +5143,6 @@
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
|
|
@ -4939,6 +5424,14 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
|
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
|
@ -5060,6 +5553,11 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stylis": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
|
||||||
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
|
@ -5077,7 +5575,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -5461,6 +5958,20 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,12 @@
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@mui/material": "^6.4.7",
|
"@mui/material": "^6.4.7",
|
||||||
"@mui/icons-material": "^6.4.8"
|
"@mui/icons-material": "^6.4.8",
|
||||||
|
"reactflow": "^11.11.4",
|
||||||
|
"vite-plugin-svgr": "^4.3.0",
|
||||||
|
"react-scripts": "^5.0.1",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
|
"antd": "^5.24.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
<svg
|
<svg width="43" height="43" viewBox="0 0 43 43" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
width="100" height="100"
|
<path d="M22.4391 0.0295059V0H21.5049H21.4951H20.5609V0.0295059C9.76424 0.48193 1.02264 8.95014 0.0884977 19.6116C0.0294994 20.2312 0 20.8607 0 21.5C0 22.1295 0.0294994 22.7589 0.0884977 23.3884C1.04231 34.3646 10.2756 43 21.4951 43H22.4391V39.2331H21.4951C12.37 39.2331 4.8182 32.3484 3.87423 23.3884H6.43083H14.6513C14.4349 22.7097 14.3169 21.9819 14.3169 21.2246C14.3169 20.6738 14.3858 20.1329 14.5038 19.6215H11.4752C12.37 14.8808 16.5884 11.3008 21.5049 11.3008C24.9367 11.3008 28.1226 13.0416 29.9909 15.8545H34.2584C32.0656 10.8484 27.0016 7.53385 21.5049 7.53385C14.5038 7.53385 8.58427 12.7761 7.65996 19.6215H6.2145H3.87423C4.8182 10.6615 12.37 3.77676 21.4951 3.77676H21.5049C30.63 3.77676 38.1818 10.6615 39.1258 19.6215H28.4962C28.6142 20.1427 28.6831 20.6738 28.6831 21.2246C28.6831 21.9819 28.5651 22.7097 28.3487 23.3884H28.919H31.5248H35.34H37.3067H43V21.5C43 9.95334 33.8552 0.511436 22.4391 0.0295059Z" fill="#428AC9"/>
|
||||||
viewBox="0 0 100 100"
|
<path d="M22.7045 32.25C22.3112 32.2992 21.9081 32.3287 21.5049 32.3287C17.2472 32.3287 13.5205 29.6436 12.016 25.8472H8.06311C9.70523 31.7681 15.1528 36.0956 21.5049 36.0956C21.9081 36.0956 22.3112 36.0759 22.7045 36.0366V32.25Z" fill="#428AC9"/>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path d="M25.2611 24.3817C23.383 26.457 20.1873 26.6242 18.1125 24.7457C16.0377 22.8769 15.8706 19.6706 17.7388 17.5954C19.617 15.5201 22.8127 15.3529 24.8875 17.2315C26.9623 19.1002 27.1294 22.3065 25.2611 24.3817Z" fill="url(#paint0_radial_2_3)"/>
|
||||||
fill="none" stroke="black" stroke-width="5" stroke-linecap="round" stroke-linejoin="round">
|
<defs>
|
||||||
<!-- Окружность -->
|
<radialGradient id="paint0_radial_2_3" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(19.8648 18.1752) scale(7.12571 7.12734)">
|
||||||
<circle cx="50" cy="50" r="45" stroke="#4CAF50" stroke-width="5" fill="none" />
|
<stop stop-color="#4A96D2"/>
|
||||||
|
<stop offset="1" stop-color="#1F2466"/>
|
||||||
<!-- График нагрузки -->
|
</radialGradient>
|
||||||
<polyline points="20,70 35,40 50,60 65,30 80,50" stroke="#4CAF50" stroke-width="5" fill="none" />
|
</defs>
|
||||||
|
</svg>
|
||||||
<!-- Крестик в центре, символизирующий мониторинг -->
|
|
||||||
<line x1="45" y1="45" x2="55" y2="55" stroke="#4CAF50" stroke-width="4" />
|
|
||||||
<line x1="55" y1="45" x2="45" y2="55" stroke="#4CAF50" stroke-width="4" />
|
|
||||||
</svg>
|
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 729 B After Width: | Height: | Size: 1.8 KiB |
128
src/App.jsx
128
src/App.jsx
|
|
@ -1,39 +1,137 @@
|
||||||
import React, { useState, useMemo } from "react";
|
import React, { useState, useMemo, useEffect } from "react";
|
||||||
import { ThemeProvider, CssBaseline, Switch, Box } from "@mui/material";
|
import { ThemeProvider, CssBaseline, Switch, Box, CircularProgress, Typography } from "@mui/material";
|
||||||
import Dashboard from "./Components/Layout/Dashboard";
|
import Dashboard from "./Components/Layout/Dashboard";
|
||||||
import LoginModal from "./Components/UI/LoginModal";
|
import LoginModal from "./Components/UI/LoginModal";
|
||||||
import { lightTheme, darkTheme } from "./Style/theme";
|
import { lightTheme, darkTheme } from "./Style/theme";
|
||||||
import "./Style/LoginModal.css";
|
import Logo from './assets/images/logo.svg?react';
|
||||||
|
import { checkAuth } from "./Components/UI/auth";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
const [authState, setAuthState] = useState({
|
||||||
const [showLoginModal, setShowLoginModal] = useState(true);
|
isAuthenticated: false,
|
||||||
|
isLoading: true,
|
||||||
|
user: null
|
||||||
|
});
|
||||||
|
const [showLoginModal, setShowLoginModal] = useState(false);
|
||||||
const [isDarkMode, setIsDarkMode] = useState(
|
const [isDarkMode, setIsDarkMode] = useState(
|
||||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||||
);
|
);
|
||||||
|
|
||||||
const theme = useMemo(() => (isDarkMode ? darkTheme : lightTheme), [isDarkMode]);
|
const theme = useMemo(() => (isDarkMode ? darkTheme : lightTheme), [isDarkMode]);
|
||||||
|
|
||||||
const handleLogin = () => {
|
useEffect(() => {
|
||||||
setIsAuthenticated(true);
|
const verifyAuth = async () => {
|
||||||
|
try {
|
||||||
|
const authStatus = await checkAuth();
|
||||||
|
setAuthState({
|
||||||
|
isAuthenticated: authStatus.isAuthenticated,
|
||||||
|
isLoading: false,
|
||||||
|
user: authStatus.user || null
|
||||||
|
});
|
||||||
|
setShowLoginModal(!authStatus.isAuthenticated);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Auth verification error:', error);
|
||||||
|
setAuthState({
|
||||||
|
isAuthenticated: false,
|
||||||
|
isLoading: false,
|
||||||
|
user: null
|
||||||
|
});
|
||||||
|
setShowLoginModal(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
verifyAuth();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleLogin = (userData) => {
|
||||||
|
setAuthState({
|
||||||
|
isAuthenticated: true,
|
||||||
|
isLoading: false,
|
||||||
|
user: userData
|
||||||
|
});
|
||||||
setShowLoginModal(false);
|
setShowLoginModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await fetch('http://192.168.2.39:3000/api/auth/logout', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
localStorage.removeItem('access_token');
|
||||||
|
setAuthState({
|
||||||
|
isAuthenticated: false,
|
||||||
|
isLoading: false,
|
||||||
|
user: null
|
||||||
|
});
|
||||||
|
setShowLoginModal(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Logout failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Полноэкранный лоадер во время проверки авторизации
|
||||||
|
if (authState.isLoading) {
|
||||||
|
return (
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<CssBaseline />
|
||||||
|
<Box sx={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0, left: 0, right: 0, bottom: 0,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
zIndex: 9999,
|
||||||
|
bgcolor: 'background.default'
|
||||||
|
}}>
|
||||||
|
<CircularProgress />
|
||||||
|
<Typography sx={{ mt: 2 }}>
|
||||||
|
Проверка авторизации...
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
{!isAuthenticated && showLoginModal ? (
|
{!authState.isAuthenticated ? (
|
||||||
<LoginModal onLogin={handleLogin} onClose={() => setShowLoginModal(false)} />
|
<>
|
||||||
) : (
|
<Box sx={{
|
||||||
<Box sx={{ display: "flex", height: "100vh", overflow: "hidden", bgcolor: "background.default", color: "text.primary" }}>
|
position: "fixed",
|
||||||
<Dashboard />
|
top: 24,
|
||||||
<Box sx={{ position: "absolute", top: 10, right: 10 }}>
|
left: "50%",
|
||||||
<Switch checked={isDarkMode} onChange={() => setIsDarkMode((prev) => !prev)} />
|
transform: "translateX(-50%)",
|
||||||
|
zIndex: 1200,
|
||||||
|
'& svg': { width: 400, height: 'auto' }
|
||||||
|
}}>
|
||||||
|
<Logo />
|
||||||
</Box>
|
</Box>
|
||||||
|
<LoginModal
|
||||||
|
open={showLoginModal}
|
||||||
|
onLogin={handleLogin}
|
||||||
|
onClose={() => setShowLoginModal(false)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
height: "100vh",
|
||||||
|
overflow: "hidden",
|
||||||
|
bgcolor: "background.default"
|
||||||
|
}}>
|
||||||
|
<Dashboard user={authState.user} onLogout={handleLogout} />
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
checked={isDarkMode}
|
||||||
|
onChange={() => setIsDarkMode(!isDarkMode)}
|
||||||
|
sx={{ position: "absolute", top: 10, right: 10 }}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const ConnectionStatusIndicator = ({ connectionStatus }) => {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '10px',
|
||||||
|
right: '10px',
|
||||||
|
padding: '5px 10px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
backgroundColor: connectionStatus === 'connected' ? '#4CAF50' :
|
||||||
|
connectionStatus === 'error' ? '#F44336' : '#FFC107',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '12px'
|
||||||
|
}}>
|
||||||
|
{connectionStatus === 'connected' ? 'Online' :
|
||||||
|
connectionStatus === 'error' ? 'Connection Error' : 'Offline'}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const CurrentRangeDisplay = ({ useCustomRange, startDate, endDate, selectedRange }) => {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
margin: '10px 0',
|
||||||
|
padding: '8px 12px',
|
||||||
|
backgroundColor: '#f0f7ff',
|
||||||
|
borderRadius: '4px',
|
||||||
|
borderLeft: '3px solid #4a6baf'
|
||||||
|
}}>
|
||||||
|
Текущий диапазон: {useCustomRange
|
||||||
|
? `${startDate.toLocaleString()} - ${endDate.toLocaleString()}`
|
||||||
|
: selectedRange.label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,90 +1,229 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer } from 'recharts';
|
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Line, ResponsiveContainer, ReferenceArea } from 'recharts';
|
||||||
|
import { HOUR, DAY } from './constants';
|
||||||
|
const TIME_FORMATS = {
|
||||||
|
LONG: 'dd.MM HH:mm', // Для диапазона > 24 часов
|
||||||
|
MEDIUM: 'HH:mm', // Для диапазона > 1 часа
|
||||||
|
SHORT: 'HH:mm:ss' // Для коротких диапазонов
|
||||||
|
};
|
||||||
|
|
||||||
const LineChartComponent = ({ chartData, metricName, colors, description, onRangeSelect, filteredData }) => {
|
const LineChartComponent = ({
|
||||||
const [selectionStart, setSelectionStart] = useState(null);
|
chartData,
|
||||||
const [selectionEnd, setSelectionEnd] = useState(null);
|
metricName,
|
||||||
|
colors,
|
||||||
|
onRangeSelect,
|
||||||
|
filteredData
|
||||||
|
}) => {
|
||||||
|
const [selectionArea, setSelectionArea] = useState(null);
|
||||||
|
const [isSelecting, setIsSelecting] = useState(false);
|
||||||
|
const chartRef = useRef(null);
|
||||||
|
|
||||||
// Создаем массив уникальных временных меток
|
|
||||||
const allTimes = Object.values(chartData)
|
const allTimestamps = Object.values(chartData)
|
||||||
.flat()
|
.flat()
|
||||||
.map(point => point.time)
|
.map(point => point.timestamp)
|
||||||
.filter((time, index, self) => self.indexOf(time) === index);
|
.filter((timestamp, index, self) => self.indexOf(timestamp) === index)
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
const data = allTimestamps.map(timestamp => {
|
||||||
|
const point = { timestamp };
|
||||||
|
|
||||||
|
const firstPoint = Object.values(chartData)
|
||||||
|
.flat()
|
||||||
|
.find(p => p.timestamp === timestamp);
|
||||||
|
|
||||||
|
if (firstPoint) {
|
||||||
|
point.time = firstPoint.time;
|
||||||
|
point.fullTime = firstPoint.fullTime;
|
||||||
|
}
|
||||||
|
|
||||||
// Формируем данные для графика
|
|
||||||
const data = allTimes.map(time => {
|
|
||||||
const point = { time };
|
|
||||||
Object.keys(chartData).forEach(key => {
|
Object.keys(chartData).forEach(key => {
|
||||||
const instanceData = chartData[key].find(p => p.time === time);
|
const instanceData = chartData[key].find(p => p.timestamp === timestamp);
|
||||||
point[key] = instanceData ? instanceData.value : null;
|
point[key] = instanceData ? instanceData.value : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return point;
|
return point;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Используем отфильтрованные данные, если они есть
|
|
||||||
const displayData = filteredData || data;
|
const displayData = filteredData || data;
|
||||||
|
|
||||||
// Обработчик клика на графике
|
const instanceKeys = displayData.length
|
||||||
const handleClick = (e) => {
|
? Object.keys(displayData[0]).filter(k => !['timestamp', 'time', 'fullTime'].includes(k))
|
||||||
if (!e || !e.activeLabel) return;
|
: [];
|
||||||
|
|
||||||
const clickedTime = e.activeLabel;
|
// Функция для определения оптимального формата времени в зависимости от диапазона
|
||||||
|
const getTimeFormat = () => {
|
||||||
|
if (!data.length) return TIME_FORMATS.SHORT;
|
||||||
|
|
||||||
if (!selectionStart) {
|
const range = data[data.length - 1].timestamp - data[0].timestamp;
|
||||||
setSelectionStart(clickedTime);
|
|
||||||
} else if (!selectionEnd) {
|
|
||||||
setSelectionEnd(clickedTime);
|
|
||||||
|
|
||||||
const startIndex = data.findIndex(point => point.time === selectionStart);
|
if (range > DAY) return TIME_FORMATS.LONG;
|
||||||
const endIndex = data.findIndex(point => point.time === clickedTime);
|
if (range > HOUR) return TIME_FORMATS.MEDIUM;
|
||||||
|
return TIME_FORMATS.SHORT;
|
||||||
onRangeSelect({ startIndex, endIndex });
|
};
|
||||||
|
|
||||||
setSelectionStart(null);
|
useEffect(() => {
|
||||||
setSelectionEnd(null);
|
const handleSelectStart = (e) => {
|
||||||
}
|
if (isSelecting) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
document.addEventListener('selectstart', handleSelectStart);
|
||||||
|
return () => document.removeEventListener('selectstart', handleSelectStart);
|
||||||
|
}, [isSelecting]);
|
||||||
|
|
||||||
|
const handleMouseDown = (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
|
||||||
|
// Получаем индекс точки по координатам
|
||||||
|
const activeIndex = e.activeTooltipIndex;
|
||||||
|
if (activeIndex === undefined || activeIndex < 0 || activeIndex >= data.length) return;
|
||||||
|
|
||||||
|
setIsSelecting(true);
|
||||||
|
setSelectionArea({
|
||||||
|
start: data[activeIndex].timestamp,
|
||||||
|
end: null,
|
||||||
|
startIndex: activeIndex,
|
||||||
|
endIndex: null
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseMove = (e) => {
|
||||||
|
if (!isSelecting || !selectionArea?.start || !e) return;
|
||||||
|
|
||||||
|
const activeIndex = e.activeTooltipIndex;
|
||||||
|
if (activeIndex === undefined || activeIndex < 0 || activeIndex >= data.length) return;
|
||||||
|
|
||||||
|
setSelectionArea(prev => ({
|
||||||
|
...prev,
|
||||||
|
end: data[activeIndex].timestamp,
|
||||||
|
endIndex: activeIndex
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
if (!isSelecting || !selectionArea?.start || !selectionArea?.end) {
|
||||||
|
setIsSelecting(false);
|
||||||
|
setSelectionArea(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startIndex = Math.min(selectionArea.startIndex, selectionArea.endIndex);
|
||||||
|
const endIndex = Math.max(selectionArea.startIndex, selectionArea.endIndex);
|
||||||
|
|
||||||
|
// Нормализуем индексы к диапазону [0, 1] для родительского компонента
|
||||||
|
const normalizedStart = startIndex / (data.length - 1);
|
||||||
|
const normalizedEnd = endIndex / (data.length - 1);
|
||||||
|
|
||||||
|
onRangeSelect({
|
||||||
|
startIndex: normalizedStart,
|
||||||
|
endIndex: normalizedEnd
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsSelecting(false);
|
||||||
|
setSelectionArea(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Кастомный Tooltip для отображения значения
|
|
||||||
const CustomTooltip = ({ active, payload, label }) => {
|
const CustomTooltip = ({ active, payload, label }) => {
|
||||||
if (active && payload && payload.length) {
|
if (active && payload && payload.length) {
|
||||||
|
const currentPoint = data.find(point => point.timestamp === label);
|
||||||
return (
|
return (
|
||||||
<div className="custom-tooltip" style={{ padding: '10px' }}>
|
<div style={{
|
||||||
<p>{`Время: ${label}`}</p>
|
backgroundColor: '#fff',
|
||||||
{payload.map((entry, index) => (
|
padding: '10px',
|
||||||
<p key={index} style={{}}>
|
border: '1px solid #ccc',
|
||||||
{`Значение: ${entry.value}`}
|
borderRadius: '4px',
|
||||||
|
boxShadow: '0 2px 5px rgba(0,0,0,0.1)'
|
||||||
|
}}>
|
||||||
|
<p style={{ fontWeight: 'bold', marginBottom: '5px' }}>
|
||||||
|
{currentPoint?.fullTime || new Date(label).toLocaleString('ru-RU')}
|
||||||
|
</p>
|
||||||
|
{payload.map((item, index) => (
|
||||||
|
<p key={index} style={{ color: item.color }}>
|
||||||
|
{`Значение: ${item.value}`}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!data.length) {
|
||||||
|
return <div style={{ padding: '20px', textAlign: 'center' }}>Нет данных для отображения</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ position: 'relative', height: '400px' }}>
|
||||||
<ResponsiveContainer width="100%" height={400}>
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<LineChart data={displayData} onClick={handleClick}>
|
<LineChart
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
data={displayData}
|
||||||
<XAxis dataKey="time" />
|
onMouseDown={handleMouseDown}
|
||||||
<YAxis />
|
onMouseMove={handleMouseMove}
|
||||||
|
onMouseUp={handleMouseUp}
|
||||||
|
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||||
|
ref={chartRef}
|
||||||
|
>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
||||||
|
<XAxis
|
||||||
|
dataKey="timestamp"
|
||||||
|
height={75}
|
||||||
|
tick={{ fontSize: 12, angle: -45, textAnchor: 'end' }}
|
||||||
|
interval={Math.max(1, Math.floor(data.length / 10))}
|
||||||
|
tickFormatter={(timestamp) => {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const format = getTimeFormat();
|
||||||
|
|
||||||
|
if (format === 'dd.MM HH:mm') {
|
||||||
|
return date.toLocaleString('ru-RU', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
} else if (format === 'HH:mm') {
|
||||||
|
return date.toLocaleString('ru-RU', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return date.toLocaleString('ru-RU', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<YAxis tick={{ fontSize: 12 }} />
|
||||||
<Tooltip content={<CustomTooltip />} />
|
<Tooltip content={<CustomTooltip />} />
|
||||||
<Legend />
|
{instanceKeys.map((instance, index) => (
|
||||||
{Object.keys(chartData).map((key, index) => (
|
|
||||||
<Line
|
<Line
|
||||||
key={key}
|
key={instance}
|
||||||
type="monotone"
|
type=""
|
||||||
dataKey={key}
|
dataKey={instance}
|
||||||
|
name={instance}
|
||||||
stroke={colors[index % colors.length]}
|
stroke={colors[index % colors.length]}
|
||||||
name={key}
|
strokeWidth={2}
|
||||||
|
dot={false}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
{selectionArea?.start && selectionArea?.end && (
|
||||||
|
<ReferenceArea
|
||||||
|
x1={selectionArea.start}
|
||||||
|
x2={selectionArea.end}
|
||||||
|
strokeOpacity={0.3}
|
||||||
|
fill="#4a6baf"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LineChartComponent;
|
export default React.memo(LineChartComponent);
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
import React from 'react';
|
||||||
|
import DatePicker from 'react-datepicker';
|
||||||
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
|
import { TIME_RANGES } from './constants';
|
||||||
|
|
||||||
|
export const TimeRangeSelector = ({
|
||||||
|
selectedRange,
|
||||||
|
handleRangeChange,
|
||||||
|
startDate,
|
||||||
|
setStartDate,
|
||||||
|
endDate,
|
||||||
|
setEndDate,
|
||||||
|
useCustomRange,
|
||||||
|
handleCustomRangeChange,
|
||||||
|
handleResetZoom
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '15px',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '15px'
|
||||||
|
}}>
|
||||||
|
{/* Стандартные диапазоны */}
|
||||||
|
<div style={{ flex: '1 1 200px', display: 'flex', gap: '10px', alignItems: 'flex-end' }}>
|
||||||
|
<div style={{ flex: '1' }}>
|
||||||
|
<label htmlFor="time-range" style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '5px',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#555'
|
||||||
|
}}>Стандартные диапазоны:</label>
|
||||||
|
<select
|
||||||
|
id="time-range"
|
||||||
|
value={selectedRange.value}
|
||||||
|
onChange={handleRangeChange}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
color: "#333",
|
||||||
|
backgroundColor: '#f9f9f9'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{TIME_RANGES.map(range => (
|
||||||
|
<option key={range.value} value={range.value}>{range.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleResetZoom}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
backgroundColor: '#f0f0f0',
|
||||||
|
color: '#333',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
height: '36px',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
}}
|
||||||
|
onMouseOver={(e) => e.target.style.backgroundColor = '#e0e0e0'}
|
||||||
|
onMouseOut={(e) => e.target.style.backgroundColor = '#f0f0f0'}
|
||||||
|
>
|
||||||
|
Сбросить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Кастомный диапазон */}
|
||||||
|
<div style={{ flex: '1 1 300px' }}>
|
||||||
|
<div style={{
|
||||||
|
marginBottom: '10px',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#555'
|
||||||
|
}}>
|
||||||
|
Или укажите свой диапазон:
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '10px',
|
||||||
|
flexWrap: 'wrap'
|
||||||
|
}}>
|
||||||
|
<div style={{ flex: '1 1 200px' }}>
|
||||||
|
<DatePicker
|
||||||
|
selected={startDate}
|
||||||
|
onChange={(date) => setStartDate(date)}
|
||||||
|
showTimeSelect
|
||||||
|
timeFormat="HH:mm"
|
||||||
|
timeIntervals={15}
|
||||||
|
dateFormat="yyyy-MM-dd HH:mm"
|
||||||
|
placeholderText="Начальная дата"
|
||||||
|
customInput={
|
||||||
|
<input style={{
|
||||||
|
backgroundColor: '#f9f9f9',
|
||||||
|
color: "#555",
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
border: '1px solid #ddd'
|
||||||
|
}} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: '1 1 200px' }}>
|
||||||
|
<DatePicker
|
||||||
|
selected={endDate}
|
||||||
|
onChange={(date) => setEndDate(date)}
|
||||||
|
showTimeSelect
|
||||||
|
timeFormat="HH:mm"
|
||||||
|
timeIntervals={15}
|
||||||
|
dateFormat="yyyy-MM-dd HH:mm"
|
||||||
|
placeholderText="Конечная дата"
|
||||||
|
customInput={
|
||||||
|
<input style={{
|
||||||
|
backgroundColor: '#f9f9f9',
|
||||||
|
color: "#555",
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
border: '1px solid #ddd'
|
||||||
|
}} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleCustomRangeChange}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
backgroundColor: '#4a6baf',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background-color 0.2s',
|
||||||
|
flex: '0 0 auto',
|
||||||
|
alignSelf: 'flex-end'
|
||||||
|
}}
|
||||||
|
onMouseOver={(e) => e.target.style.backgroundColor = '#3a5a9f'}
|
||||||
|
onMouseOut={(e) => e.target.style.backgroundColor = '#4a6baf'}
|
||||||
|
>
|
||||||
|
Применить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
export const TIME_RANGES = [
|
||||||
|
{ label: '1 минута', value: 60, interval: 3000 },
|
||||||
|
{ label: '5 минут', value: 300, interval: 15000 },
|
||||||
|
{ label: '30 минут', value: 1800, interval: 90000 },
|
||||||
|
{ label: '1 час', value: 3600, interval: 180000 },
|
||||||
|
{ label: '3 часа', value: 10800, interval: 540000 },
|
||||||
|
{ label: '6 часов', value: 21600, interval: 1080000 },
|
||||||
|
{ label: '12 часов', value: 43200, interval: 2160000 },
|
||||||
|
{ label: '24 часа', value: 86400, interval: 4320000 },
|
||||||
|
{ label: '2 дня', value: 172800, interval: 8640000 },
|
||||||
|
{ label: '7 дней', value: 604800, interval: 30240000 },
|
||||||
|
{ label: '30 дней', value: 2592000, interval: 129600000 },
|
||||||
|
{ label: '90 дней', value: 7776000, interval: 388800000 },
|
||||||
|
{ label: '6 месяцев', value: 15552000, interval: 777600000 },
|
||||||
|
{ label: '9 месяцев', value: 23328000, interval: 1166400000 },
|
||||||
|
{ label: '1 год', value: 31536000, interval: 1576800000 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const COLORS = ['#3e95cd', '#8e5ea2', '#3cba9f', '#e8c3b9', '#c45850'];
|
||||||
|
export const MAX_POINTS = 20;
|
||||||
|
|
||||||
|
|
||||||
|
// Для работы с временными интервалами (setTimeout и т.д.)
|
||||||
|
export const MS = 1;
|
||||||
|
export const SECOND_MS = 1000 * MS;
|
||||||
|
export const MINUTE_MS = 60 * SECOND_MS;
|
||||||
|
export const HOUR_MS = 60 * MINUTE_MS;
|
||||||
|
export const DAY_MS = 24 * HOUR_MS;
|
||||||
|
|
||||||
|
// Для работы с Unix-временем и API (Prometheus и т.д.)
|
||||||
|
export const SECOND = 1;
|
||||||
|
export const MINUTE = 60 * SECOND;
|
||||||
|
export const HOUR = 60 * MINUTE;
|
||||||
|
export const DAY = 24 * HOUR;
|
||||||
|
export const WEEK = 7 * DAY;
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
|
||||||
|
|
||||||
const SystemStatusChart = ({ data }) => {
|
|
||||||
// Обрезаем массив, оставляя только последние 20 точек
|
|
||||||
const trimmedData = data.slice(-20);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
|
||||||
<LineChart
|
|
||||||
data={trimmedData} // Используем обрезанный массив
|
|
||||||
margin={{
|
|
||||||
top: 5, right: 30, left: 20, bottom: 5,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
|
||||||
<XAxis dataKey="time" />
|
|
||||||
<YAxis domain={[0, 100]} />
|
|
||||||
<Tooltip />
|
|
||||||
<Legend />
|
|
||||||
<Line type="monotone" dataKey="status" stroke="#8884d8" activeDot={{ r: 8 }} />
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SystemStatusChart;
|
|
||||||
|
|
@ -1,267 +1,486 @@
|
||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
||||||
import axios from 'axios';
|
import { webSocketManager } from './WebSocketManager';
|
||||||
import DatePicker from 'react-datepicker';
|
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
|
||||||
import LineChartComponent from './Components/LineChartComponent';
|
import LineChartComponent from './Components/LineChartComponent';
|
||||||
|
import { TimeRangeSelector } from './Components/TimeRangeSelector';
|
||||||
|
import { ConnectionStatusIndicator } from './Components/ConnectionStatusIndicator';
|
||||||
|
import { CurrentRangeDisplay } from './Components/CurrentRangeDisplay';
|
||||||
|
import { TIME_RANGES, COLORS, SECOND, MINUTE, HOUR, DAY } from './Components/constants';
|
||||||
|
import axios from 'axios';
|
||||||
|
import Skeleton from '@mui/material/Skeleton';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
const MAX_POINTS = 20;
|
|
||||||
const COLORS = ['#3e95cd', '#8e5ea2', '#3cba9f', '#e8c3b9', '#c45850'];
|
// Компонент Skeleton для графика
|
||||||
const TIME_RANGES = [
|
const ChartSkeleton = () => (
|
||||||
{ label: '1 минута', value: 60, interval: 3000 },
|
<Box sx={{
|
||||||
{ label: '5 минут', value: 300, interval: 15000 },
|
backgroundColor: '#fff',
|
||||||
{ label: '30 минут', value: 1800, interval: 90000 },
|
borderRadius: '8px',
|
||||||
{ label: '1 час', value: 3600, interval: 180000 },
|
padding: '20px',
|
||||||
{ label: '3 часа', value: 10800, interval: 540000 },
|
marginBottom: '20px',
|
||||||
{ label: '6 часов', value: 21600, interval: 1080000 },
|
position: 'relative'
|
||||||
{ label: '12 часов', value: 43200, interval: 2160000 },
|
}}>
|
||||||
{ label: '24 часа', value: 86400, interval: 4320000 },
|
<Box sx={{ position: 'absolute', right: '20px', top: '20px' }}>
|
||||||
{ label: '2 дня', value: 172800, interval: 8640000 },
|
<Skeleton variant="circular" width={16} height={16} />
|
||||||
{ label: '7 дней', value: 604800, interval: 30240000 },
|
</Box>
|
||||||
{ label: '30 дней', value: 2592000, interval: 129600000 },
|
|
||||||
{ label: '90 дней', value: 7776000, interval: 388800000 },
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||||
{ label: '6 месяцев', value: 15552000, interval: 777600000 },
|
<Skeleton variant="text" width="40%" height={30} />
|
||||||
{ label: '9 месяцев', value: 23328000, interval: 1166400000 },
|
<Skeleton variant="text" width="30%" height={30} />
|
||||||
{ label: '1 год', value: 31536000, interval: 1576800000 },
|
</Box>
|
||||||
];
|
|
||||||
|
<Skeleton variant="rectangular" width="100%" height={300} />
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2, gap: 2 }}>
|
||||||
|
{[1, 2, 3, 4].map((_, i) => (
|
||||||
|
<Skeleton key={i} variant="rounded" width={80} height={36} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
const PrometheusChart = ({ metricName }) => {
|
const PrometheusChart = ({ metricName }) => {
|
||||||
const [chartData, setChartData] = useState({});
|
const [chartData, setChartData] = useState(null);
|
||||||
const [selectedRange, setSelectedRange] = useState(TIME_RANGES[0]);
|
const [selectedRange, setSelectedRange] = useState(TIME_RANGES[0]);
|
||||||
const [startDate, setStartDate] = useState(new Date());
|
const [startDate, setStartDate] = useState(new Date());
|
||||||
const [endDate, setEndDate] = useState(new Date());
|
const [endDate, setEndDate] = useState(new Date());
|
||||||
const [useCustomRange, setUseCustomRange] = useState(false);
|
const [useCustomRange, setUseCustomRange] = useState(false);
|
||||||
const [selectedGraphRange, setSelectedGraphRange] = useState(null); // Выбранный диапазон
|
const [connectionStatus, setConnectionStatus] = useState('disconnected');
|
||||||
const [filteredData, setFilteredData] = useState(null); // Отфильтрованные данные
|
const [selectedGraphRange, setSelectedGraphRange] = useState(null);
|
||||||
|
const [filteredData, setFilteredData] = useState(null);
|
||||||
|
const [isSelectingRange, setIsSelectingRange] = useState(false);
|
||||||
|
const [lastCustomRange, setLastCustomRange] = useState(null);
|
||||||
const intervalRef = useRef(null);
|
const intervalRef = useRef(null);
|
||||||
|
const debounceRef = useRef(null);
|
||||||
|
|
||||||
// Функция для интерполяции данных
|
const formatTime = useCallback((timestamp, rangeSeconds) => {
|
||||||
const interpolateData = (data, minPoints = 15) => {
|
const ts = typeof timestamp === 'number' ? timestamp : Date.now();
|
||||||
if (data.length >= minPoints) return data;
|
const date = new Date(ts);
|
||||||
|
|
||||||
const interpolatedData = [];
|
// Определяем формат в зависимости от диапазона
|
||||||
for (let i = 0; i < data.length - 1; i++) {
|
const showFullDate = rangeSeconds > 86400; // больше суток
|
||||||
interpolatedData.push(data[i]);
|
|
||||||
|
|
||||||
const currentPoint = data[i];
|
const timeOptions = {
|
||||||
const nextPoint = data[i + 1];
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
};
|
||||||
|
|
||||||
// Вычисляем разницу во времени между точками
|
const dateOptions = showFullDate ? {
|
||||||
const currentTime = new Date(currentPoint.time).getTime();
|
month: '2-digit',
|
||||||
const nextTime = new Date(nextPoint.time).getTime();
|
day: '2-digit',
|
||||||
const timeDiff = nextTime - currentTime;
|
...timeOptions
|
||||||
|
} : timeOptions;
|
||||||
|
|
||||||
// Добавляем промежуточные точки
|
return {
|
||||||
const steps = Math.ceil((minPoints - data.length) / (data.length - 1));
|
display: date.toLocaleString('ru-RU', dateOptions),
|
||||||
for (let j = 1; j <= steps; j++) {
|
fullDisplay: date.toLocaleString('ru-RU', {
|
||||||
const interpolatedTime = new Date(currentTime + (timeDiff * j) / (steps + 1)).toLocaleString();
|
year: 'numeric',
|
||||||
const interpolatedPoint = { time: interpolatedTime };
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
}),
|
||||||
|
timestamp: ts
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Интерполируем значения для каждой метрики
|
const calculateStep = useCallback((start, end) => {
|
||||||
Object.keys(currentPoint).forEach(key => {
|
const range = end - start;
|
||||||
if (key !== 'time') {
|
if (range <= MINUTE) return 1; // 1 мин
|
||||||
const currentValue = currentPoint[key];
|
if (range <= MINUTE * 5) return 5; // 5 мин
|
||||||
const nextValue = nextPoint[key];
|
if (range <= HOUR / 2) return 15; // 30 мин
|
||||||
interpolatedPoint[key] = currentValue + ((nextValue - currentValue) * j) / (steps + 1);
|
if (range <= HOUR) return 30; // 1 час
|
||||||
}
|
if (range <= HOUR * 3) return 60; // 3 часа
|
||||||
|
if (range <= HOUR * 6) return 120; // 6 часов
|
||||||
|
if (range <= DAY / 2) return 300; // 12 часов
|
||||||
|
if (range <= DAY) return 600; // 24 часа
|
||||||
|
return 1800; // > 24 часов
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
const processMetricsData = useCallback((response) => {
|
||||||
|
console.log('Processing metrics data:', response);
|
||||||
|
if (response.metric !== metricName) return;
|
||||||
|
|
||||||
|
const dataArray = Array.isArray(response.data) ? response.data : [response.data];
|
||||||
|
if (!dataArray.length) return;
|
||||||
|
|
||||||
|
setChartData(prev => {
|
||||||
|
const newData = { ...(prev || {}) };
|
||||||
|
const rangeSeconds = useCustomRange
|
||||||
|
? (endDate.getTime() - startDate.getTime()) / 1000
|
||||||
|
: selectedRange.value;
|
||||||
|
|
||||||
|
dataArray.forEach(item => {
|
||||||
|
const instance = item.instance || 'default';
|
||||||
|
if (!newData[instance]) newData[instance] = [];
|
||||||
|
|
||||||
|
// Унифицированная конвертация timestamp
|
||||||
|
let timestamp;
|
||||||
|
if (typeof item.timestamp === 'number') {
|
||||||
|
// Определяем, в секундах или миллисекундах пришел timestamp
|
||||||
|
timestamp = item.timestamp > 1e12 ? item.timestamp : item.timestamp * 1000;
|
||||||
|
} else {
|
||||||
|
timestamp = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = parseFloat(item.value);
|
||||||
|
const formattedTime = formatTime(timestamp, rangeSeconds);
|
||||||
|
|
||||||
|
newData[instance].push({
|
||||||
|
time: formattedTime.display,
|
||||||
|
fullTime: formattedTime.fullDisplay,
|
||||||
|
value: value,
|
||||||
|
timestamp: timestamp
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
interpolatedData.push(interpolatedPoint);
|
// Сортируем и ограничиваем данные
|
||||||
}
|
Object.keys(newData).forEach(instance => {
|
||||||
|
newData[instance] = newData[instance]
|
||||||
|
.sort((a, b) => a.timestamp - b.timestamp)
|
||||||
|
.slice(-1000);
|
||||||
|
});
|
||||||
|
return newData;
|
||||||
|
});
|
||||||
|
}, [metricName, selectedRange.value, formatTime, useCustomRange, startDate, endDate]);
|
||||||
|
|
||||||
|
const fetchData = useCallback(() => {
|
||||||
|
if (isSelectingRange) return;
|
||||||
|
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
const start = now - selectedRange.value;
|
||||||
|
const end = now;
|
||||||
|
const step = calculateStep(start, end);
|
||||||
|
|
||||||
|
webSocketManager.getMetricsRange(metricName, start, end, step)
|
||||||
|
.then(data => {
|
||||||
|
processMetricsData({ metric: metricName, data });
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching metrics:', error);
|
||||||
|
});
|
||||||
|
}, [metricName, selectedRange.value, isSelectingRange, calculateStep, processMetricsData]);
|
||||||
|
|
||||||
|
const fetchCustomRangeData = useCallback(async () => {
|
||||||
|
// Добавляем проверку на валидность дат
|
||||||
|
if (!startDate || !endDate || startDate >= endDate) {
|
||||||
|
console.error('Invalid date range');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolatedData.push(data[data.length - 1]); // Добавляем последнюю точку
|
const start = Math.floor(startDate.getTime() / 1000);
|
||||||
return interpolatedData.slice(0, minPoints); // Обрезаем до minPoints
|
const end = Math.ceil(endDate.getTime() / 1000); // Используем Math.ceil для конечной даты
|
||||||
};
|
const rangeSeconds = end - start;
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
try {
|
||||||
let start, end;
|
const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/api/metrics`, {
|
||||||
|
params: {
|
||||||
if (useCustomRange) {
|
metric: metricName,
|
||||||
start = Math.floor(startDate.getTime() / 1000);
|
start,
|
||||||
end = Math.floor(endDate.getTime() / 1000);
|
end,
|
||||||
} else {
|
step: calculateStep(start, end)
|
||||||
end = Math.floor(Date.now() / 1000);
|
}
|
||||||
start = end - selectedRange.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
let step;
|
|
||||||
const range = end - start;
|
|
||||||
if (range <= 3600) step = 5;
|
|
||||||
else if (range <= 21600) step = 30;
|
|
||||||
else if (range <= 86400) step = 120;
|
|
||||||
else step = 300;
|
|
||||||
|
|
||||||
const response = await axios.get('http://192.168.2.39:3000/metrics', {
|
|
||||||
params: { metric: metricName, start, end, step },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
if (response.data?.length) {
|
||||||
const response = await axios.get(`${process.env.REACT_APP_BACK_URL}/metrics`, {
|
// Добавляем нормализацию timestamp
|
||||||
params: { metric: metricName, start, end, step },
|
const processedData = response.data.map(item => ({
|
||||||
}); */
|
...item,
|
||||||
|
timestamp: item.timestamp > 1e12 ? item.timestamp : item.timestamp * 1000,
|
||||||
const result = response.data;
|
value: parseFloat(item.value)
|
||||||
let metrics = Array.isArray(result) ? result : result.data || [];
|
|
||||||
|
|
||||||
if (!Array.isArray(metrics)) {
|
|
||||||
metrics = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const timePoints = [];
|
|
||||||
for (let t = start; t <= end; t += step) {
|
|
||||||
const date = new Date(t * 1000);
|
|
||||||
const formattedTime = range > 86400
|
|
||||||
? date.toLocaleString([], { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })
|
|
||||||
: date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
||||||
|
|
||||||
timePoints.push(formattedTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedData = {};
|
|
||||||
metrics.forEach(m => {
|
|
||||||
const date = new Date(m.timestamp);
|
|
||||||
const formattedTime = range > 86400
|
|
||||||
? date.toLocaleString([], { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })
|
|
||||||
: date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
||||||
|
|
||||||
const key = `${m.instance}-${m.device || m.scrape_job}`;
|
|
||||||
if (!updatedData[key]) updatedData[key] = {};
|
|
||||||
updatedData[key][formattedTime] = m.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
const chartData = {};
|
|
||||||
Object.keys(updatedData).forEach(key => {
|
|
||||||
chartData[key] = timePoints.map(time => ({
|
|
||||||
time,
|
|
||||||
value: updatedData[key][time] ?? null,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
processMetricsData({
|
||||||
|
metric: metricName,
|
||||||
|
data: processedData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении кастомных данных:', error);
|
||||||
|
}
|
||||||
|
}, [metricName, startDate, endDate, calculateStep, processMetricsData]);
|
||||||
|
|
||||||
|
|
||||||
|
const handleRangeChange = useCallback(async (event) => {
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedValue = event.target.value;
|
||||||
|
const range = TIME_RANGES.find(r => r.value === parseInt(selectedValue, 10));
|
||||||
|
|
||||||
|
// Полный сброс состояния перед загрузкой новых данных
|
||||||
|
setChartData(null);
|
||||||
|
setSelectedRange(range);
|
||||||
|
setUseCustomRange(false);
|
||||||
|
setSelectedGraphRange(null);
|
||||||
|
setFilteredData(null);
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
setEndDate(now);
|
||||||
|
setStartDate(new Date(now.getTime() - range.value * 1000));
|
||||||
|
|
||||||
|
// Ждем завершения обновления состояния перед загрузкой
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 0));
|
||||||
|
fetchData();
|
||||||
|
}, [fetchData]);
|
||||||
|
|
||||||
|
const handleCustomRangeChange = useCallback(() => {
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUseCustomRange(true);
|
||||||
|
setChartData(null);
|
||||||
|
setSelectedGraphRange(null);
|
||||||
|
setFilteredData(null);
|
||||||
|
fetchCustomRangeData();
|
||||||
|
}, [fetchCustomRangeData]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const interpolateData = useCallback((data, targetPointCount) => {
|
||||||
|
if (!data || data.length < 2) return data;
|
||||||
|
if (data.length >= targetPointCount) return data;
|
||||||
|
|
||||||
|
const interpolated = [];
|
||||||
|
const step = (data.length - 1) / (targetPointCount - 1);
|
||||||
|
|
||||||
|
for (let i = 0; i < targetPointCount; i++) {
|
||||||
|
const index = i * step;
|
||||||
|
const lowerIndex = Math.floor(index);
|
||||||
|
const upperIndex = Math.ceil(index);
|
||||||
|
|
||||||
|
if (lowerIndex === upperIndex) {
|
||||||
|
interpolated.push(data[lowerIndex]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fraction = index - lowerIndex;
|
||||||
|
const interpolatedPoint = {};
|
||||||
|
|
||||||
|
Object.keys(data[lowerIndex]).forEach(key => {
|
||||||
|
if (key === 'timestamp') {
|
||||||
|
interpolatedPoint[key] = data[lowerIndex][key] +
|
||||||
|
fraction * (data[upperIndex][key] - data[lowerIndex][key]);
|
||||||
|
|
||||||
|
// Добавляем отображаемое время
|
||||||
|
const { display, fullDisplay } = formatTime(interpolatedPoint[key],
|
||||||
|
(endDate - startDate) / 1000);
|
||||||
|
interpolatedPoint.time = display;
|
||||||
|
interpolatedPoint.fullTime = fullDisplay;
|
||||||
|
} else if (typeof data[lowerIndex][key] === 'number') {
|
||||||
|
interpolatedPoint[key] = data[lowerIndex][key] +
|
||||||
|
fraction * (data[upperIndex][key] - data[lowerIndex][key]);
|
||||||
|
} else {
|
||||||
|
interpolatedPoint[key] = data[lowerIndex][key];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setChartData(chartData);
|
interpolated.push(interpolatedPoint);
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка при загрузке метрик:', error);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
return interpolated;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRangeSelect = useCallback((range) => {
|
||||||
|
setLastCustomRange(range);
|
||||||
|
if (!range || !chartData) return;
|
||||||
|
|
||||||
|
setIsSelectingRange(true);
|
||||||
|
setSelectedGraphRange(range);
|
||||||
|
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем все точки и сортируем по времени
|
||||||
|
const allPoints = Object.values(chartData).flat();
|
||||||
|
const sortedPoints = allPoints.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
|
||||||
|
// Вычисляем абсолютные индексы
|
||||||
|
const startIndex = Math.floor(range.startIndex * (sortedPoints.length - 1));
|
||||||
|
const endIndex = Math.floor(range.endIndex * (sortedPoints.length - 1));
|
||||||
|
|
||||||
|
// Фильтруем точки по выбранному диапазону
|
||||||
|
const filtered = sortedPoints.slice(startIndex, endIndex + 1);
|
||||||
|
|
||||||
|
// Применяем интерполяцию только если точек меньше 100
|
||||||
|
const interpolated = filtered.length < 100 ?
|
||||||
|
interpolateData(filtered, Math.min(100, filtered.length * 3)) :
|
||||||
|
filtered;
|
||||||
|
|
||||||
|
setFilteredData(interpolated);
|
||||||
|
setIsSelectingRange(false);
|
||||||
|
}, [chartData, interpolateData, formatTime]);
|
||||||
|
|
||||||
|
const handleResetZoom = useCallback(() => {
|
||||||
|
setSelectedGraphRange(null);
|
||||||
|
setFilteredData(null);
|
||||||
|
setIsSelectingRange(false);
|
||||||
|
|
||||||
|
if (useCustomRange) {
|
||||||
|
fetchCustomRangeData();
|
||||||
|
} else {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastCustomRange) {
|
||||||
|
handleRangeSelect(lastCustomRange);
|
||||||
|
}
|
||||||
|
}, [fetchData, fetchCustomRangeData, useCustomRange, lastCustomRange, handleRangeSelect]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
// Обработчик данных с сервера
|
||||||
|
const handleMetricsData = (data) => {
|
||||||
|
processMetricsData({ metric: metricName, data });
|
||||||
|
};
|
||||||
|
|
||||||
intervalRef.current = setInterval(() => {
|
// Подписываемся на обновления метрики
|
||||||
fetchData();
|
const unsubscribe = webSocketManager.subscribe(metricName, handleMetricsData);
|
||||||
}, selectedRange.interval);
|
|
||||||
|
// Подписываемся на изменения статуса соединения
|
||||||
|
const unsubscribeStatus = webSocketManager.onConnectionStatusChange(setConnectionStatus);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Отписываемся при размонтировании компонента
|
||||||
|
unsubscribe();
|
||||||
|
unsubscribeStatus();
|
||||||
|
|
||||||
|
// Очищаем интервал обновления
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [metricName, processMetricsData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (useCustomRange && !isSelectingRange) {
|
||||||
|
if (debounceRef.current) {
|
||||||
|
clearTimeout(debounceRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
debounceRef.current = setTimeout(() => {
|
||||||
|
fetchCustomRangeData();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (debounceRef.current) {
|
||||||
|
clearTimeout(debounceRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [useCustomRange, isSelectingRange, startDate, endDate, fetchCustomRangeData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (useCustomRange || isSelectingRange) return;
|
||||||
|
|
||||||
|
const fetchDataWrapper = () => {
|
||||||
|
try {
|
||||||
|
fetchData();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in interval fetch:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fetchDataWrapper();
|
||||||
|
intervalRef.current = setInterval(fetchDataWrapper, selectedRange.interval);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (intervalRef.current) {
|
if (intervalRef.current) {
|
||||||
clearInterval(intervalRef.current);
|
clearInterval(intervalRef.current);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [metricName, selectedRange, useCustomRange, startDate, endDate]);
|
}, [fetchData, selectedRange.interval, useCustomRange, isSelectingRange]);
|
||||||
|
|
||||||
const handleRangeChange = (event) => {
|
|
||||||
const selectedValue = event.target.value;
|
|
||||||
const range = TIME_RANGES.find(range => range.value === parseInt(selectedValue, 10));
|
|
||||||
setSelectedRange(range);
|
|
||||||
setUseCustomRange(false);
|
|
||||||
setSelectedGraphRange(null); // Сбрасываем выбранный диапазон
|
|
||||||
setFilteredData(null); // Сбрасываем отфильтрованные данные
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCustomRangeChange = () => {
|
|
||||||
setUseCustomRange(true);
|
|
||||||
setSelectedGraphRange(null); // Сбрасываем выбранный диапазон
|
|
||||||
setFilteredData(null); // Сбрасываем отфильтрованные данные
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedGraphRange) {
|
if (!selectedGraphRange || !chartData) {
|
||||||
const { startIndex, endIndex } = selectedGraphRange;
|
setFilteredData(null);
|
||||||
const allTimes = Object.values(chartData)
|
return;
|
||||||
.flat()
|
|
||||||
.map(point => point.time)
|
|
||||||
.filter((time, index, self) => self.indexOf(time) === index);
|
|
||||||
|
|
||||||
const data = allTimes.map(time => {
|
|
||||||
const point = { time };
|
|
||||||
Object.keys(chartData).forEach(key => {
|
|
||||||
const instanceData = chartData[key].find(p => p.time === time);
|
|
||||||
point[key] = instanceData ? instanceData.value : null;
|
|
||||||
});
|
|
||||||
return point;
|
|
||||||
});
|
|
||||||
|
|
||||||
const filtered = data.slice(startIndex, endIndex + 1);
|
|
||||||
|
|
||||||
// Интерполируем данные, если точек меньше 15
|
|
||||||
const interpolated = interpolateData(filtered, 15);
|
|
||||||
setFilteredData(interpolated); // Сохраняем интерполированные данные
|
|
||||||
} else {
|
|
||||||
setFilteredData(null); // Сбрасываем фильтрацию, если диапазон не выбран
|
|
||||||
}
|
}
|
||||||
}, [selectedGraphRange, chartData]);
|
|
||||||
|
|
||||||
if (!Object.keys(chartData).length) return <p>Loading...</p>;
|
const allPoints = Object.values(chartData).flat();
|
||||||
|
const sortedPoints = allPoints.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
|
||||||
const allTimes = Object.values(chartData)
|
const startIndex = Math.floor(selectedGraphRange.startIndex * (sortedPoints.length - 1));
|
||||||
.flat()
|
const endIndex = Math.floor(selectedGraphRange.endIndex * (sortedPoints.length - 1));
|
||||||
.map(point => point.time)
|
|
||||||
.filter((time, index, self) => self.indexOf(time) === index);
|
|
||||||
|
|
||||||
const data = allTimes.map(time => {
|
const filtered = sortedPoints.slice(startIndex, endIndex + 1);
|
||||||
const point = { time };
|
const interpolated = filtered.length > 100 ?
|
||||||
Object.keys(chartData).forEach(key => {
|
interpolateData(filtered, 100) :
|
||||||
const instanceData = chartData[key].find(p => p.time === time);
|
filtered;
|
||||||
point[key] = instanceData ? instanceData.value : null;
|
|
||||||
});
|
setFilteredData(interpolated);
|
||||||
return point;
|
}, [selectedGraphRange, chartData, interpolateData]);
|
||||||
});
|
|
||||||
|
if (chartData === null) {
|
||||||
|
return <ChartSkeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(chartData).length === 0) {
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '20px',
|
||||||
|
marginBottom: '20px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}>
|
||||||
|
No data available
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{
|
||||||
<div>
|
backgroundColor: '#fff',
|
||||||
<label htmlFor="time-range">Выберите временной диапазон: </label>
|
borderRadius: '8px',
|
||||||
<select id="time-range" value={selectedRange.value} onChange={handleRangeChange}>
|
padding: '20px',
|
||||||
{TIME_RANGES.map(range => (
|
marginBottom: '20px',
|
||||||
<option key={range.value} value={range.value}>{range.label}</option>
|
position: 'relative'
|
||||||
))}
|
}}>
|
||||||
</select>
|
<ConnectionStatusIndicator connectionStatus={connectionStatus} />
|
||||||
</div>
|
|
||||||
<div>
|
<TimeRangeSelector
|
||||||
<label>Или выберите другой диапазон: </label>
|
selectedRange={selectedRange}
|
||||||
<div>
|
handleRangeChange={handleRangeChange}
|
||||||
<label>Начальная дата: </label>
|
startDate={startDate}
|
||||||
<DatePicker
|
setStartDate={setStartDate}
|
||||||
selected={startDate}
|
endDate={endDate}
|
||||||
onChange={(date) => setStartDate(date)}
|
setEndDate={setEndDate}
|
||||||
showTimeSelect
|
useCustomRange={useCustomRange}
|
||||||
timeFormat="HH:mm"
|
handleCustomRangeChange={handleCustomRangeChange}
|
||||||
timeIntervals={15}
|
handleResetZoom={handleResetZoom}
|
||||||
dateFormat="yyyy-MM-dd HH:mm"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
<CurrentRangeDisplay
|
||||||
<div>
|
useCustomRange={useCustomRange}
|
||||||
<label>Конечная дата: </label>
|
startDate={startDate}
|
||||||
<DatePicker
|
endDate={endDate}
|
||||||
selected={endDate}
|
selectedRange={selectedRange}
|
||||||
onChange={(date) => setEndDate(date)}
|
/>
|
||||||
showTimeSelect
|
|
||||||
timeFormat="HH:mm"
|
|
||||||
timeIntervals={15}
|
|
||||||
dateFormat="yyyy-MM-dd HH:mm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button onClick={handleCustomRangeChange}>Использовать кастомный диапазон</button>
|
|
||||||
</div>
|
|
||||||
<LineChartComponent
|
<LineChartComponent
|
||||||
chartData={chartData}
|
chartData={chartData}
|
||||||
metricName={metricName}
|
metricName={metricName}
|
||||||
colors={COLORS}
|
colors={COLORS}
|
||||||
description={metricName}
|
onRangeSelect={handleRangeSelect}
|
||||||
onRangeSelect={setSelectedGraphRange}
|
|
||||||
filteredData={filteredData}
|
filteredData={filteredData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PrometheusChart;
|
export default React.memo(PrometheusChart);
|
||||||
|
|
@ -5,6 +5,22 @@ const SystemStatusChart = ({ data }) => {
|
||||||
// Обрезаем массив, оставляя только последние 20 точек
|
// Обрезаем массив, оставляя только последние 20 точек
|
||||||
const trimmedData = data.slice(-20);
|
const trimmedData = data.slice(-20);
|
||||||
|
|
||||||
|
const CustomTooltip = ({ active, payload, label }) => {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
return (
|
||||||
|
<div className="custom-tooltip" style={{
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
padding: '10px',
|
||||||
|
border: '1px solid #ccc'
|
||||||
|
}}>
|
||||||
|
<p>{`Время: ${label}`}</p>
|
||||||
|
<p>{`Значение: ${payload[0].value}`}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
<ResponsiveContainer width="100%" height={300}>
|
||||||
<LineChart
|
<LineChart
|
||||||
|
|
@ -16,8 +32,7 @@ const SystemStatusChart = ({ data }) => {
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis dataKey="time" />
|
<XAxis dataKey="time" />
|
||||||
<YAxis domain={[0, 100]} />
|
<YAxis domain={[0, 100]} />
|
||||||
<Tooltip />
|
<Tooltip content={<CustomTooltip />} />
|
||||||
<Legend />
|
|
||||||
<Line type="monotone" dataKey="status" stroke="#8884d8" activeDot={{ r: 8 }} />
|
<Line type="monotone" dataKey="status" stroke="#8884d8" activeDot={{ r: 8 }} />
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
|
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import "../Style/SystemStatusTable.css";
|
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
const SystemStatusTable = () => {
|
|
||||||
const [systemData, setSystemData] = useState([]);
|
|
||||||
const [expandedRow, setExpandedRow] = useState(null);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState(null);
|
|
||||||
|
|
||||||
// Загрузка данных с бэкенда
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get("/trust.json"); // Укажите ваш endpoint
|
|
||||||
setSystemData(response.data);
|
|
||||||
setLoading(false);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err.message);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Обработчик для кнопки "Подробнее"
|
|
||||||
const handleDetailsClick = (id) => {
|
|
||||||
setExpandedRow(expandedRow === id ? null : id);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <p>Загрузка данных...</p>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <p>Ошибка: {error}</p>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<table>
|
|
||||||
<caption>
|
|
||||||
<h2>Состояние системы</h2>
|
|
||||||
</caption>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Метрика</th>
|
|
||||||
<th>Значение</th>
|
|
||||||
<th>Статус</th>
|
|
||||||
<th>Детали</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{systemData.map((item) => (
|
|
||||||
<React.Fragment key={item.id}>
|
|
||||||
<tr>
|
|
||||||
<td>{item.name}</td>
|
|
||||||
<td>{item.value}%</td>
|
|
||||||
<td>
|
|
||||||
<span className={`status ${item.status}`}>{item.status}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onClick={() => handleDetailsClick(item.id)}>
|
|
||||||
{expandedRow === item.id ? "Скрыть" : "Подробнее"}
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{expandedRow === item.id && (
|
|
||||||
<tr>
|
|
||||||
<td colSpan="4">
|
|
||||||
<div className="details">
|
|
||||||
<p>{item.details}</p>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SystemStatusTable;
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import "../Style/SystemStatusTable.css";
|
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
const SystemStatusTableSoftware = () => {
|
|
||||||
const [systemData, setSystemData] = useState([]);
|
|
||||||
const [expandedRow, setExpandedRow] = useState(null);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState(null);
|
|
||||||
|
|
||||||
// Загрузка данных с бэкенда
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get("/TrustSoftware.json"); // Укажите ваш endpoint
|
|
||||||
setSystemData(response.data);
|
|
||||||
setLoading(false);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err.message);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Обработчик для кнопки "Подробнее"
|
|
||||||
const handleDetailsClick = (id) => {
|
|
||||||
setExpandedRow(expandedRow === id ? null : id);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <p>Загрузка данных...</p>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <p>Ошибка: {error}</p>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<table>
|
|
||||||
<caption>
|
|
||||||
<h2>Состояние ПО</h2>
|
|
||||||
</caption>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Метрика</th>
|
|
||||||
<th>Значение</th>
|
|
||||||
<th>Статус</th>
|
|
||||||
<th>Детали</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{systemData.map((item) => (
|
|
||||||
<React.Fragment key={item.id}>
|
|
||||||
<tr>
|
|
||||||
<td>{item.name}</td>
|
|
||||||
<td>{item.value}%</td>
|
|
||||||
<td>
|
|
||||||
<span className={`status ${item.status}`}>{item.status}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onClick={() => handleDetailsClick(item.id)}>
|
|
||||||
{expandedRow === item.id ? "Скрыть" : "Подробнее"}
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{expandedRow === item.id && (
|
|
||||||
<tr>
|
|
||||||
<td colSpan="4">
|
|
||||||
<div className="details">
|
|
||||||
<p>{item.details}</p>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SystemStatusTableSoftware;
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
// src/services/WebSocketManager.js
|
||||||
|
import { io } from 'socket.io-client';
|
||||||
|
|
||||||
|
class WebSocketManager {
|
||||||
|
constructor() {
|
||||||
|
this.socket = null;
|
||||||
|
this.subscribers = new Map();
|
||||||
|
this.connectionStatus = 'disconnected';
|
||||||
|
this.connectionCallbacks = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
if (this.socket && (this.socket.connected || this.socket.reconnecting)) {
|
||||||
|
return this.socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket = io(`${import.meta.env.VITE_BACK_WS_URL}/api/metrics-ws`, {
|
||||||
|
transports: ['websocket'],
|
||||||
|
reconnection: true,
|
||||||
|
reconnectionAttempts: Infinity,
|
||||||
|
reconnectionDelay: 1000,
|
||||||
|
reconnectionDelayMax: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('connect', () => {
|
||||||
|
this.connectionStatus = 'connected';
|
||||||
|
this.notifyConnectionStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('disconnect', (reason) => {
|
||||||
|
this.connectionStatus = 'disconnected';
|
||||||
|
this.notifyConnectionStatus();
|
||||||
|
if (reason === 'io server disconnect') this.socket.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('connect_error', (error) => {
|
||||||
|
this.connectionStatus = 'error';
|
||||||
|
this.notifyConnectionStatus();
|
||||||
|
setTimeout(() => this.socket.connect(), 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('metrics-data', (response) => {
|
||||||
|
const callbacks = this.subscribers.get(response.metric);
|
||||||
|
if (callbacks) {
|
||||||
|
callbacks.forEach(callback => callback(response.data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(metricName, callback) {
|
||||||
|
this.connect();
|
||||||
|
|
||||||
|
if (!this.subscribers.has(metricName)) {
|
||||||
|
this.subscribers.set(metricName, new Set());
|
||||||
|
this.socket.emit('subscribe-metric', {
|
||||||
|
metric: metricName,
|
||||||
|
isSubscription: true // Флаг для подписки
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subscribers.get(metricName).add(callback);
|
||||||
|
|
||||||
|
return () => this.unsubscribe(metricName, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe(metricName, callback) {
|
||||||
|
const callbacks = this.subscribers.get(metricName);
|
||||||
|
if (callbacks) {
|
||||||
|
callbacks.delete(callback);
|
||||||
|
if (callbacks.size === 0) {
|
||||||
|
this.subscribers.delete(metricName);
|
||||||
|
this.socket.emit('unsubscribe-metric', { metric: metricName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getMetricsRange(metricName, start, end, step) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
reject(new Error('Timeout while waiting for metrics data'));
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
// Временный обработчик для разового запроса
|
||||||
|
const tempHandler = (data) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
this.socket.off(`metrics-range-${metricName}`, tempHandler);
|
||||||
|
resolve(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.socket.on(`metrics-range-${metricName}`, tempHandler);
|
||||||
|
this.socket.emit('get-metrics', {
|
||||||
|
metric: metricName,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
step,
|
||||||
|
isRangeQuery: true // Флаг для разового запроса
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnectionStatusChange(callback) {
|
||||||
|
this.connectionCallbacks.add(callback);
|
||||||
|
callback(this.connectionStatus);
|
||||||
|
return () => this.connectionCallbacks.delete(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyConnectionStatus() {
|
||||||
|
this.connectionCallbacks.forEach(callback => callback(this.connectionStatus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const webSocketManager = new WebSocketManager();
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Box, styled } from "@mui/material";
|
||||||
import SidebarMenu from "./SidebarMenu";
|
import SidebarMenu from "./SidebarMenu";
|
||||||
import "../../Style/Dashboard.css";
|
|
||||||
import { statusManager1, statusManager2 } from "../TreeChart/dataUtils";
|
import { statusManager1, statusManager2 } from "../TreeChart/dataUtils";
|
||||||
import generateTabContent from "../TreeChart/tabContent";
|
import generateTabContent from "../TreeChart/tabContent";
|
||||||
import CustomTabs from "../UI/MUItabs";
|
import CustomTabs from "../UI/MUItabs";
|
||||||
|
|
@ -9,24 +9,70 @@ import useSidebarResize from "../hooks/useSidebarResize";
|
||||||
import TabContent from "../hooks/TabContent";
|
import TabContent from "../hooks/TabContent";
|
||||||
import menuData from "../TreeChart/menuData.json";
|
import menuData from "../TreeChart/menuData.json";
|
||||||
|
|
||||||
|
// Создаем стилизованные компоненты
|
||||||
|
const DashboardContainer = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
height: '100vh',
|
||||||
|
width: '100vw',
|
||||||
|
overflow: 'hidden',
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SidebarWrapper = styled(Box)(({ theme }) => ({
|
||||||
|
position: 'relative',
|
||||||
|
backgroundColor: theme.palette.custom.sidebar,
|
||||||
|
color: theme.palette.custom.sidebarText,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SidebarResizer = styled(Box)(({ theme }) => ({
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: '4px',
|
||||||
|
cursor: 'col-resize',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const MainContent = styled(Box)(({ theme }) => ({
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: theme.spacing(2.5), // 20px
|
||||||
|
overflow: 'auto',
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Content = styled(Box)(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.custom.modalBackground,
|
||||||
|
//padding: theme.spacing(2.5),
|
||||||
|
borderRadius: '10px',
|
||||||
|
//boxShadow: theme.shadows[2],
|
||||||
|
maxWidth: '100%',
|
||||||
|
overflow: 'auto',
|
||||||
|
color: theme.palette.custom.modalText,
|
||||||
|
}));
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const { tabs, activeTab, handleOpenTab, handleCloseTab, setActiveTab } = useTabs("Главная");
|
const { tabs, activeTab, handleOpenTab, handleCloseTab, setActiveTab } = useTabs("Главная");
|
||||||
const { sidebarWidth, startResizing } = useSidebarResize(250);
|
const { sidebarWidth, startResizing } = useSidebarResize(290);
|
||||||
const [tabContent, setTabContent] = useState({});
|
const [tabContent, setTabContent] = useState({});
|
||||||
const [treeData1, setTreeData1] = useState(menuData);
|
const [treeData1, setTreeData1] = useState(menuData);
|
||||||
const [treeData2, setTreeData2] = useState(menuData);
|
const [treeData2, setTreeData2] = useState(menuData);
|
||||||
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const [statusHistories, setStatusHistories] = useState({
|
const [statusHistories, setStatusHistories] = useState({
|
||||||
history1: [],
|
history1: [],
|
||||||
history2: [],
|
history2: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Генерация контента для вкладок
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const generatedTabContent = generateTabContent(menuData);
|
const generatedTabContent = generateTabContent(menuData);
|
||||||
setTabContent(generatedTabContent);
|
setTabContent(generatedTabContent);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Обновление статусов каждые 30 секунд
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
const updatedData1 = JSON.parse(JSON.stringify(treeData1));
|
const updatedData1 = JSON.parse(JSON.stringify(treeData1));
|
||||||
|
|
@ -56,15 +102,22 @@ const Dashboard = () => {
|
||||||
}, [treeData1, treeData2]);
|
}, [treeData1, treeData2]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-container">
|
<DashboardContainer>
|
||||||
{/* Сайдбар */}
|
{/* Сайдбар */}
|
||||||
<div className="sidebar" style={{ width: sidebarWidth }}>
|
<SidebarWrapper sx={{ width: collapsed ? 64 : sidebarWidth }}>
|
||||||
<SidebarMenu data={treeData1} onOpenTab={handleOpenTab} sidebarWidth={sidebarWidth} startResizing={startResizing} />
|
<SidebarMenu
|
||||||
<div className="sidebar-resizer" onMouseDown={startResizing} />
|
data={treeData1}
|
||||||
</div>
|
onOpenTab={handleOpenTab}
|
||||||
|
sidebarWidth={sidebarWidth}
|
||||||
|
startResizing={startResizing}
|
||||||
|
collapsed={collapsed}
|
||||||
|
setCollapsed={setCollapsed}
|
||||||
|
/>
|
||||||
|
<SidebarResizer onMouseDown={startResizing} />
|
||||||
|
</SidebarWrapper>
|
||||||
|
|
||||||
{/* Основной контент */}
|
{/* Основной контент */}
|
||||||
<div className="main-content">
|
<MainContent>
|
||||||
{/* Вкладки */}
|
{/* Вкладки */}
|
||||||
<CustomTabs
|
<CustomTabs
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
|
|
@ -74,7 +127,7 @@ const Dashboard = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Контент вкладки */}
|
{/* Контент вкладки */}
|
||||||
<div className="content">
|
<Content>
|
||||||
<TabContent
|
<TabContent
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
statusHistories={statusHistories}
|
statusHistories={statusHistories}
|
||||||
|
|
@ -82,9 +135,9 @@ const Dashboard = () => {
|
||||||
tabContent={tabContent}
|
tabContent={tabContent}
|
||||||
handleOpenTab={handleOpenTab}
|
handleOpenTab={handleOpenTab}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Content>
|
||||||
</div>
|
</MainContent>
|
||||||
</div>
|
</DashboardContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,160 @@
|
||||||
import React from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Drawer, List } from "@mui/material";
|
import {
|
||||||
|
Drawer,
|
||||||
|
List,
|
||||||
|
Typography,
|
||||||
|
styled,
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
Box
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Menu as MenuIcon
|
||||||
|
} from "@mui/icons-material";
|
||||||
import MenuItem from "./SidebarMenuComponents/MenuItem";
|
import MenuItem from "./SidebarMenuComponents/MenuItem";
|
||||||
import SidebarFooter from "./SidebarMenuComponents/SidebarFooter";
|
import SidebarFooter from "./SidebarMenuComponents/SidebarFooter";
|
||||||
|
import { statusManager1 } from "../TreeChart/dataUtils";
|
||||||
|
|
||||||
|
const SidebarResizer = styled('div')(({ theme }) => ({
|
||||||
|
width: "5px",
|
||||||
|
cursor: "ew-resize",
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
height: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
transition: 'background-color 0.2s',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
zIndex: 2
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SidebarMenu = ({ data, onOpenTab, sidebarWidth, startResizing, collapsed, setCollapsed }) => {
|
||||||
|
const [hovered, setHovered] = useState(false);
|
||||||
|
const [menuData, setMenuData] = useState(data);
|
||||||
|
|
||||||
|
// Обновляем статусы при изменении данных
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
// Создаем глубокую копию данных, чтобы не мутировать исходные
|
||||||
|
const dataCopy = JSON.parse(JSON.stringify(data));
|
||||||
|
statusManager1.updateStatuses(dataCopy);
|
||||||
|
setMenuData(dataCopy);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const handleToggleCollapse = () => {
|
||||||
|
setCollapsed(!collapsed);
|
||||||
|
};
|
||||||
|
|
||||||
const SidebarMenu = ({ data, onOpenTab, sidebarWidth, startResizing }) => {
|
|
||||||
const handleSelectItem = (id, title, children) => {
|
const handleSelectItem = (id, title, children) => {
|
||||||
onOpenTab(id, title, children);
|
onOpenTab(id, title, children);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const drawerWidth = collapsed ? 64 : sidebarWidth;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Box
|
||||||
variant="permanent"
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
sx={{
|
sx={{
|
||||||
width: sidebarWidth,
|
position: 'relative',
|
||||||
flexShrink: 0,
|
width: drawerWidth,
|
||||||
"& .MuiDrawer-paper": {
|
transition: 'width 0.3s ease',
|
||||||
width: sidebarWidth,
|
|
||||||
boxSizing: "border-box",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<List>
|
<Drawer
|
||||||
<h2 style={{ padding: "16px", fontWeight: "bold" }}>Меню</h2>
|
variant="permanent"
|
||||||
<MenuItem item={data} onSelectItem={handleSelectItem} />
|
sx={{
|
||||||
</List>
|
width: drawerWidth,
|
||||||
|
flexShrink: 0,
|
||||||
{/* Ресайзер */}
|
'& .MuiDrawer-paper': {
|
||||||
<div
|
width: drawerWidth,
|
||||||
onMouseDown={startResizing}
|
boxSizing: "border-box",
|
||||||
style={{
|
display: "flex",
|
||||||
width: "5px",
|
flexDirection: "column",
|
||||||
cursor: "ew-resize",
|
backgroundColor: 'custom.sidebar',
|
||||||
backgroundColor: "#ccc",
|
color: 'custom.sidebarText',
|
||||||
height: "100%",
|
transition: 'width 0.3s ease',
|
||||||
position: "absolute",
|
overflowX: 'hidden',
|
||||||
right: 0,
|
borderRight: 'none'
|
||||||
top: 0,
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{/* Кнопка сворачивания/разворачивания */}
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
p: 1,
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
backgroundColor: 'custom.sidebar'
|
||||||
|
}}>
|
||||||
|
<Tooltip title={collapsed ? "Развернуть меню" : "Свернуть меню"}>
|
||||||
|
<IconButton
|
||||||
|
onClick={handleToggleCollapse}
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
color: 'custom.sidebarText',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'custom.sidebarHover',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{collapsed ? <ChevronRight /> : <ChevronLeft />}
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<SidebarFooter sidebarWidth={sidebarWidth} />
|
{/* Содержимое меню */}
|
||||||
</Drawer>
|
<Box sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
|
<List sx={{
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
flex: '1 1 auto'
|
||||||
|
}}>
|
||||||
|
{!collapsed && (
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Меню
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{menuData && (
|
||||||
|
<MenuItem
|
||||||
|
item={menuData}
|
||||||
|
onSelectItem={handleSelectItem}
|
||||||
|
collapsed={collapsed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
{/* Футер */}
|
||||||
|
{!collapsed && (
|
||||||
|
<SidebarFooter />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Ресайзер */}
|
||||||
|
{!collapsed && (
|
||||||
|
<SidebarResizer onMouseDown={startResizing} />
|
||||||
|
)}
|
||||||
|
</Drawer>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SidebarMenu;
|
export default SidebarMenu;
|
||||||
|
|
@ -1,87 +1,108 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Drawer, List, ListItem, ListItemIcon, ListItemText, Collapse } from "@mui/material";
|
import {
|
||||||
|
ListItem,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Collapse,
|
||||||
|
List,
|
||||||
|
styled
|
||||||
|
} from "@mui/material";
|
||||||
import { ExpandLess, ExpandMore, Folder, FolderOpen } from "@mui/icons-material";
|
import { ExpandLess, ExpandMore, Folder, FolderOpen } from "@mui/icons-material";
|
||||||
|
import { getStatusColor } from "../../TreeChart/dataUtils";
|
||||||
|
|
||||||
// Функция для сбора всех потомков
|
|
||||||
const getAllChildren = (node) => {
|
|
||||||
let children = [];
|
|
||||||
if (node.items && node.items.length > 0) {
|
|
||||||
node.items.forEach((child) => {
|
|
||||||
children.push(child); // Добавляем текущий элемент
|
|
||||||
children = children.concat(getAllChildren(child)); // Рекурсивно добавляем потомков
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return children;
|
|
||||||
};
|
|
||||||
|
|
||||||
const MenuItem = ({ item, onSelectItem }) => {
|
|
||||||
|
const StyledListItem = styled(ListItem)(({ theme, level }) => ({
|
||||||
|
cursor: "pointer",
|
||||||
|
paddingLeft: theme.spacing(2 + level * 2),
|
||||||
|
position: 'relative', // Добавляем для позиционирования индикатора
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.action.hover,
|
||||||
|
},
|
||||||
|
'&.Mui-selected': {
|
||||||
|
backgroundColor: theme.palette.custom.sidebarHover,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StatusIndicator = styled('div')(({ theme, status }) => ({
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: '4px',
|
||||||
|
backgroundColor: status ? getStatusColor(status) : 'transparent',
|
||||||
|
borderTopRightRadius: '4px',
|
||||||
|
borderBottomRightRadius: '4px',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const IconWrapper = styled('div')(({ theme }) => ({
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
padding: theme.spacing(0.5),
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.action.selected,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const MenuItem = ({ item, onSelectItem, level = 0, collapsed }) => {
|
||||||
const [isOpen, setIsOpen] = React.useState(false);
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
const hasChildren = Array.isArray(item.items) && item.items.length > 0;
|
const hasChildren = Array.isArray(item.items) && item.items.length > 0;
|
||||||
|
|
||||||
const handleToggle = () => {
|
const handleToggle = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
setIsOpen(!isOpen);
|
setIsOpen(!isOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenTab = (e) => {
|
const handleOpenTab = (e) => {
|
||||||
e.stopPropagation(); // Останавливаем всплытие события
|
e.stopPropagation();
|
||||||
const allChildren = getAllChildren(item); // Собираем всех потомков
|
const allChildren = getAllChildren(item);
|
||||||
onSelectItem(item.id, item.title, allChildren); // Передаем данные в родительский компонент
|
onSelectItem(item.id, item.title, allChildren);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem
|
<StyledListItem
|
||||||
component="div"
|
component="div"
|
||||||
onClick={hasChildren ? handleToggle : handleOpenTab}
|
onClick={hasChildren ? handleToggle : handleOpenTab}
|
||||||
|
level={level}
|
||||||
sx={{
|
sx={{
|
||||||
cursor: "pointer", // Курсор pointer везде
|
pl: collapsed ? 2 : 2 + level * 2,
|
||||||
"&:hover": {
|
justifyContent: collapsed ? 'center' : 'flex-start',
|
||||||
backgroundColor: "#f5f5f5", // Подсветка при наведении на весь элемент
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListItemIcon>
|
{/* Индикатор статуса */}
|
||||||
{hasChildren ? (
|
{!collapsed && <StatusIndicator status={item.status} />}
|
||||||
<div
|
|
||||||
onClick={handleOpenTab}
|
<ListItemIcon sx={{ minWidth: collapsed ? 'auto' : 56 }}>
|
||||||
style={{
|
<IconWrapper onClick={handleOpenTab}>
|
||||||
cursor: "pointer",
|
{hasChildren ? (isOpen ? <FolderOpen /> : <Folder />) : <Folder />}
|
||||||
borderRadius: "4px", // Скругление углов
|
</IconWrapper>
|
||||||
padding: "4px", // Отступы для увеличения области hover
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: "#e0e0e0", // Подсветка при наведении на иконку
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isOpen ? <FolderOpen /> : <Folder />}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
onClick={handleOpenTab}
|
|
||||||
style={{
|
|
||||||
cursor: "pointer",
|
|
||||||
borderRadius: "4px", // Скругление углов
|
|
||||||
padding: "4px", // Отступы для увеличения области hover
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: "#e0e0e0", // Подсветка при наведении на иконку
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Здесь можно добавить другую иконку или оставить пустым */}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText
|
|
||||||
primary={item.title}
|
{!collapsed && (
|
||||||
sx={{ cursor: "pointer" }} // Курсор pointer для текста
|
<>
|
||||||
/>
|
<ListItemText
|
||||||
{hasChildren && (isOpen ? <ExpandLess /> : <ExpandMore />)}
|
primary={item.title}
|
||||||
</ListItem>
|
primaryTypographyProps={{
|
||||||
{hasChildren && (
|
color: 'custom.sidebarText'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{hasChildren && (isOpen ? <ExpandLess /> : <ExpandMore />)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</StyledListItem>
|
||||||
|
|
||||||
|
{hasChildren && !collapsed && (
|
||||||
<Collapse in={isOpen} timeout="auto" unmountOnExit>
|
<Collapse in={isOpen} timeout="auto" unmountOnExit>
|
||||||
<List component="div" disablePadding>
|
<List component="div" disablePadding>
|
||||||
{item.items.map((child, index) => (
|
{item.items.map((child, index) => (
|
||||||
<MenuItem key={index} item={child} onSelectItem={onSelectItem} />
|
<MenuItem
|
||||||
|
key={index}
|
||||||
|
item={child}
|
||||||
|
onSelectItem={onSelectItem}
|
||||||
|
level={level + 1}
|
||||||
|
collapsed={collapsed}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|
@ -90,4 +111,15 @@ const MenuItem = ({ item, onSelectItem }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getAllChildren = (node) => {
|
||||||
|
let children = [];
|
||||||
|
if (node.items && node.items.length > 0) {
|
||||||
|
node.items.forEach((child) => {
|
||||||
|
children.push(child);
|
||||||
|
children = children.concat(getAllChildren(child));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
export default MenuItem;
|
export default MenuItem;
|
||||||
|
|
@ -1,16 +1,47 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { List, ListItem, ListItemText } from "@mui/material";
|
import {
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemText,
|
||||||
|
styled
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
const SidebarFooter = ({ sidebarWidth }) => {
|
const FooterList = styled(List)(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.custom.sidebar,
|
||||||
|
padding: theme.spacing(1, 0),
|
||||||
|
borderTop: `1px solid ${theme.palette.divider}`,
|
||||||
|
marginTop: 'auto'
|
||||||
|
}));
|
||||||
|
|
||||||
|
const FooterListItem = styled(ListItem)(({ theme }) => ({
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.custom.sidebarHover,
|
||||||
|
},
|
||||||
|
padding: theme.spacing(1, 2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SidebarFooter = () => {
|
||||||
return (
|
return (
|
||||||
<List sx={{ marginTop: "auto", backgroundColor: "#ffffff", padding: "10px 0" }}>
|
<FooterList>
|
||||||
<ListItem button={true}>
|
<FooterListItem button>
|
||||||
<ListItemText primary="Помощь" sx={{ color: "#000000" }} />
|
<ListItemText
|
||||||
</ListItem>
|
primary="Помощь"
|
||||||
<ListItem button={true}>
|
primaryTypographyProps={{
|
||||||
<ListItemText primary="Настройка" sx={{ color: "#000000" }} />
|
color: 'custom.sidebarText',
|
||||||
</ListItem>
|
variant: 'body2'
|
||||||
</List>
|
}}
|
||||||
|
/>
|
||||||
|
</FooterListItem>
|
||||||
|
<FooterListItem button>
|
||||||
|
<ListItemText
|
||||||
|
primary="Настройка"
|
||||||
|
primaryTypographyProps={{
|
||||||
|
color: 'custom.sidebarText',
|
||||||
|
variant: 'body2'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FooterListItem>
|
||||||
|
</FooterList>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from "react";
|
||||||
|
import { styled } from "@mui/material";
|
||||||
|
|
||||||
|
const StatusIndicator = styled('div')(({ theme, status }) => ({
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: '4px',
|
||||||
|
backgroundColor: getStatusColor(status),
|
||||||
|
borderRadius: '0 2px 2px 0',
|
||||||
|
transition: 'background-color 0.3s ease'
|
||||||
|
}));
|
||||||
|
|
||||||
|
const getStatusColor = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'red': return '#F44336';
|
||||||
|
case 'orange': return '#FF9800';
|
||||||
|
case 'yellow': return '#cebd21';
|
||||||
|
case 'green': return '#4CAF50';
|
||||||
|
default: return 'transparent';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatusIndicator;
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
import React, { useEffect, useMemo, useRef } from 'react';
|
||||||
|
import ReactFlow, { Controls, Background } from 'reactflow';
|
||||||
|
import 'reactflow/dist/style.css';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { useFlowChart } from './FlowChartComponents/useFlowChart';
|
||||||
|
import { useNodeHandlers } from './FlowChartComponents/useNodeHandlers';
|
||||||
|
import { useDataParser } from './FlowChartComponents/DataParser';
|
||||||
|
import NodeWrapper from './FlowChartComponents/NodeWrapper';
|
||||||
|
|
||||||
|
const nodeTypes = {
|
||||||
|
customNode: NodeWrapper
|
||||||
|
};
|
||||||
|
|
||||||
|
const FlowChart = ({ data }) => {
|
||||||
|
const {
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
nodePositions,
|
||||||
|
setNodes,
|
||||||
|
setEdges,
|
||||||
|
onNodesChange,
|
||||||
|
onEdgesChange,
|
||||||
|
setNodePositions,
|
||||||
|
collapsedNodes,
|
||||||
|
toggleNodeCollapse
|
||||||
|
} = useFlowChart(data);
|
||||||
|
|
||||||
|
const { parseData } = useDataParser(nodePositions, collapsedNodes);
|
||||||
|
const initialized = useRef(false);
|
||||||
|
|
||||||
|
const debouncedSetNodePositions = useMemo(
|
||||||
|
() => debounce(setNodePositions, 100),
|
||||||
|
[setNodePositions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { onNodeDrag, onNodeDragStop } = useNodeHandlers(debouncedSetNodePositions);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { nodes: initialNodes, edges: initialEdges } = parseData(data);
|
||||||
|
setNodes(initialNodes);
|
||||||
|
setEdges(initialEdges);
|
||||||
|
|
||||||
|
// Автоматически сворачиваем узлы, которые являются родителями последнего уровня
|
||||||
|
if (!initialized.current && data) {
|
||||||
|
const findAndCollapseLastLevelParents = (items) => {
|
||||||
|
items.forEach(item => {
|
||||||
|
if (item.items && item.items.length > 0) {
|
||||||
|
// Проверяем, есть ли у детей свои дети
|
||||||
|
const hasGrandchildren = item.items.some(child =>
|
||||||
|
child.items && child.items.length > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
// Если у детей нет своих детей - это родители последнего уровня
|
||||||
|
if (!hasGrandchildren) {
|
||||||
|
toggleNodeCollapse(item.id);
|
||||||
|
} else {
|
||||||
|
findAndCollapseLastLevelParents(item.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
findAndCollapseLastLevelParents(data.items || []);
|
||||||
|
initialized.current = true;
|
||||||
|
}
|
||||||
|
}, [data, parseData, setNodes, setEdges, toggleNodeCollapse]);
|
||||||
|
|
||||||
|
const onNodeClick = (event, node) => {
|
||||||
|
if (node.data.hasChildren) {
|
||||||
|
toggleNodeCollapse(node.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
debouncedSetNodePositions.cancel();
|
||||||
|
};
|
||||||
|
}, [debouncedSetNodePositions]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ height: '85vh', width: '100%' }}>
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
nodeTypes={nodeTypes}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
onNodeDrag={onNodeDrag}
|
||||||
|
onNodeDragStop={onNodeDragStop}
|
||||||
|
nodeDragThreshold={1}
|
||||||
|
onNodeClick={onNodeClick}
|
||||||
|
fitView
|
||||||
|
>
|
||||||
|
<Background />
|
||||||
|
<Controls />
|
||||||
|
</ReactFlow>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(FlowChart);
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { isLeafNode } from './nodeUtils';
|
||||||
|
import { getStatusColor } from '../dataUtils';
|
||||||
|
|
||||||
|
export const useDataParser = (nodePositions, collapsedNodes) => {
|
||||||
|
const getNodeStyle = useCallback((item, isLeaf) => ({
|
||||||
|
width: isLeaf ? 60 : 70,
|
||||||
|
height: isLeaf ? 60 : 70,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: getStatusColor(item.status),
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'black',
|
||||||
|
border: '2px solid #fff',
|
||||||
|
fontSize: isLeaf ? '0.8rem' : '1rem'
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
const getCenterNodeStyle = useCallback((item) => ({
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: getStatusColor(item.status),
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'black',
|
||||||
|
border: '2px solid #fff',
|
||||||
|
fontSize: '1.2rem'
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
const parseData = useCallback((data) => {
|
||||||
|
if (!data) return { nodes: [], edges: [] };
|
||||||
|
|
||||||
|
const nodes = [];
|
||||||
|
const edges = [];
|
||||||
|
const centerX = 500;
|
||||||
|
const centerY = 400;
|
||||||
|
const baseLevelRadius = 150;
|
||||||
|
|
||||||
|
const traverse = (item, parentId = null, level = 0, angleStart = 0, angleEnd = 2 * Math.PI, parentRadius = 0) => {
|
||||||
|
if (!item || collapsedNodes[parentId]) return; // Пропускаем свёрнутые узлы
|
||||||
|
|
||||||
|
const nodeId = item.id;
|
||||||
|
const items = item.items || [];
|
||||||
|
const isLeaf = isLeafNode(item);
|
||||||
|
|
||||||
|
const savedPosition = nodePositions[nodeId];
|
||||||
|
let position = savedPosition || {
|
||||||
|
x: Math.round(centerX + Math.cos((angleStart + angleEnd) / 2) * (parentRadius + baseLevelRadius)),
|
||||||
|
y: Math.round(centerY + Math.sin((angleStart + angleEnd) / 2) * (parentRadius + baseLevelRadius))
|
||||||
|
};
|
||||||
|
|
||||||
|
const node = {
|
||||||
|
id: nodeId,
|
||||||
|
type: 'customNode',
|
||||||
|
position,
|
||||||
|
data: {
|
||||||
|
...item,
|
||||||
|
label: item.title,
|
||||||
|
style: getNodeStyle(item, isLeaf), // Переносим стили в data
|
||||||
|
hasChildren: items.length > 0,
|
||||||
|
collapsed: collapsedNodes[nodeId]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes.push(node);
|
||||||
|
|
||||||
|
if (parentId) {
|
||||||
|
edges.push({
|
||||||
|
id: `${parentId}-${nodeId}`,
|
||||||
|
source: parentId,
|
||||||
|
target: nodeId,
|
||||||
|
style: { stroke: isLeaf ? '#aaa' : '#666', strokeWidth: isLeaf ? 1 : 2 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!collapsedNodes[nodeId] && items.length > 0) {
|
||||||
|
const spreadAngle = angleEnd - angleStart;
|
||||||
|
items.forEach((child, index) => {
|
||||||
|
if (!child) return;
|
||||||
|
const itemAngleStart = angleStart + (index / items.length) * spreadAngle;
|
||||||
|
const itemAngleEnd = angleStart + ((index + 1) / items.length) * spreadAngle;
|
||||||
|
traverse(child, nodeId, level + 1, itemAngleStart, itemAngleEnd, parentRadius + baseLevelRadius);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const centerNode = {
|
||||||
|
id: data.id,
|
||||||
|
type: 'customNode', // Добавляем тип узла
|
||||||
|
position: nodePositions[data.id] || { x: centerX, y: centerY },
|
||||||
|
style: getCenterNodeStyle(data),
|
||||||
|
data: { label: data.title, hasChildren: data.items.length > 0, collapsed: collapsedNodes[data.id] }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
nodes.push(centerNode);
|
||||||
|
|
||||||
|
if (!collapsedNodes[data.id] && data.items.length > 0) {
|
||||||
|
const angleStep = (2 * Math.PI) / data.items.length;
|
||||||
|
data.items.forEach((child, index) => {
|
||||||
|
if (!child) return;
|
||||||
|
traverse(child, data.id, 1, index * angleStep, (index + 1) * angleStep, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { nodes, edges };
|
||||||
|
}, [nodePositions, collapsedNodes, getNodeStyle, getCenterNodeStyle]);
|
||||||
|
|
||||||
|
return { parseData };
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Handle } from 'reactflow';
|
||||||
|
|
||||||
|
const NodeWrapper = memo(({ id, data, selected }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...data.style,
|
||||||
|
position: 'relative',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
overflow: 'hidden', // Чтобы текст не выходил за границы
|
||||||
|
textOverflow: 'ellipsis', // Добавляем многоточие если текст не помещается
|
||||||
|
whiteSpace: 'nowrap', // Запрещаем перенос строк
|
||||||
|
padding: '0 8px', // Горизонтальный padding для текста
|
||||||
|
boxSizing: 'border-box' // Учитываем padding в общей ширине
|
||||||
|
}}
|
||||||
|
title={data.label} // Простой tooltip при наведении
|
||||||
|
>
|
||||||
|
{/* Хендл для входящих соединений */}
|
||||||
|
<Handle
|
||||||
|
type="target"
|
||||||
|
position="top"
|
||||||
|
style={{ visibility: 'hidden' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Обёртка для текста с ограничением ширины */}
|
||||||
|
<div style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis'
|
||||||
|
}}>
|
||||||
|
{data.label}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{data.hasChildren && (
|
||||||
|
<span style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 5,
|
||||||
|
right: 5,
|
||||||
|
fontSize: '12px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
background: '#fff',
|
||||||
|
padding: '2px 5px',
|
||||||
|
borderRadius: '3px',
|
||||||
|
border: '1px solid #aaa'
|
||||||
|
}}>
|
||||||
|
{data.collapsed ? '+' : '-'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Хендл для исходящих соединений */}
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position="bottom"
|
||||||
|
style={{ visibility: 'hidden' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default NodeWrapper;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const isLeafNode = (item) => {
|
||||||
|
return !item.items || item.items.length === 0;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
import { useNodesState, useEdgesState } from 'reactflow';
|
||||||
|
import { statusManager1 } from '../dataUtils';
|
||||||
|
|
||||||
|
export const useFlowChart = (initialData) => {
|
||||||
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
|
const [nodePositions, setNodePositions] = useState({});
|
||||||
|
const [collapsedNodes, setCollapsedNodes] = useState({}); // Добавили
|
||||||
|
|
||||||
|
const toggleNodeCollapse = useCallback((nodeId) => {
|
||||||
|
setCollapsedNodes((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[nodeId]: !prev[nodeId]
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const initializeNodePositions = useCallback((nodes) => {
|
||||||
|
const positions = {};
|
||||||
|
nodes.forEach(node => {
|
||||||
|
positions[node.id] = node.position;
|
||||||
|
});
|
||||||
|
setNodePositions(positions);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateStatuses = (data) => {
|
||||||
|
statusManager1.updateStatuses(data);
|
||||||
|
};
|
||||||
|
updateStatuses(initialData);
|
||||||
|
}, [initialData]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
nodePositions,
|
||||||
|
setNodes,
|
||||||
|
setEdges,
|
||||||
|
onNodesChange,
|
||||||
|
onEdgesChange,
|
||||||
|
setNodePositions,
|
||||||
|
collapsedNodes,
|
||||||
|
toggleNodeCollapse,
|
||||||
|
initializeNodePositions
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
export const useNodeHandlers = (debouncedSetNodePositions) => {
|
||||||
|
const onNodeDrag = useCallback((event, node) => {
|
||||||
|
// Фиксируем позицию сразу при перемещении
|
||||||
|
node.position = {
|
||||||
|
x: Math.round(node.position.x),
|
||||||
|
y: Math.round(node.position.y)
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onNodeDragStop = useCallback((event, node) => {
|
||||||
|
node.position = {
|
||||||
|
x: Math.round(node.position.x),
|
||||||
|
y: Math.round(node.position.y)
|
||||||
|
};
|
||||||
|
debouncedSetNodePositions(prev => ({
|
||||||
|
...prev,
|
||||||
|
[node.id]: node.position
|
||||||
|
}));
|
||||||
|
}, [debouncedSetNodePositions]);
|
||||||
|
|
||||||
|
return { onNodeDrag, onNodeDragStop };
|
||||||
|
};
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
import React, { useRef, useEffect, useMemo } from "react";
|
|
||||||
import * as d3 from "d3";
|
|
||||||
import "../../Style/TreeChart.css";
|
|
||||||
import { getStatusColor } from "./dataUtils";
|
|
||||||
|
|
||||||
const TreeChart = ({ data, onNodeClick }) => {
|
|
||||||
const chartRef = useRef();
|
|
||||||
const nodePositions = useRef(new Map());
|
|
||||||
|
|
||||||
const { root, nodes, links } = useMemo(() => {
|
|
||||||
if (!data || !data.items) return { root: null, nodes: [], links: [] };
|
|
||||||
|
|
||||||
const root = d3.hierarchy(data, (d) => d.items);
|
|
||||||
const maxDepth = d3.max(root.descendants(), (d) => d.depth);
|
|
||||||
|
|
||||||
// Фильтруем узлы, исключая последний уровень
|
|
||||||
const nodes = root.descendants().filter((d) => d.depth < maxDepth);
|
|
||||||
|
|
||||||
// Фильтруем связи
|
|
||||||
const links = nodes.filter((d) => d.parent).map((d) => ({
|
|
||||||
source: d.parent,
|
|
||||||
target: d,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Размещаем узлы иерархически
|
|
||||||
const center = { x: 0, y: 0 }; // Центральная точка
|
|
||||||
const baseRadius = 150; // Базовый радиус для 1-го уровня
|
|
||||||
const branchOffset = 80; // Смещение узлов вдоль ветки
|
|
||||||
const angleOffset = Math.PI / 4; // Угол смещения для дочерних ветвей
|
|
||||||
const spreadFactor = 1.5; // Коэффициент растяжения для последних узлов
|
|
||||||
|
|
||||||
nodes.forEach((node) => {
|
|
||||||
const prev = nodePositions.current.get(node.data.id);
|
|
||||||
if (prev) {
|
|
||||||
node.x = prev.x;
|
|
||||||
node.y = prev.y;
|
|
||||||
} else {
|
|
||||||
if (node.depth === 0) {
|
|
||||||
// Центральный узел
|
|
||||||
node.x = center.x;
|
|
||||||
node.y = center.y;
|
|
||||||
} else if (node.depth === 1) {
|
|
||||||
// Первый уровень - равномерно по окружности
|
|
||||||
const parent = node.parent;
|
|
||||||
const index = parent.children.indexOf(node);
|
|
||||||
const totalSiblings = parent.children.length;
|
|
||||||
|
|
||||||
const radius = baseRadius * node.depth;
|
|
||||||
const sectorAngle = (Math.PI * 2) / totalSiblings;
|
|
||||||
const angle = index * sectorAngle;
|
|
||||||
|
|
||||||
node.x = parent.x + radius * Math.cos(angle);
|
|
||||||
node.y = parent.y + radius * Math.sin(angle);
|
|
||||||
node.angle = angle; // Запоминаем угол для веток
|
|
||||||
} else {
|
|
||||||
// Второй уровень и дальше - ветка растет в направлении родителя
|
|
||||||
const parent = node.parent;
|
|
||||||
const siblings = parent.children || [];
|
|
||||||
const index = siblings.indexOf(node);
|
|
||||||
const totalSiblings = siblings.length;
|
|
||||||
|
|
||||||
const direction = parent.angle || 0;
|
|
||||||
const offsetAngle = ((index - (totalSiblings - 1) / 2) * angleOffset) / totalSiblings;
|
|
||||||
|
|
||||||
let distance = branchOffset;
|
|
||||||
if (!node.children || node.children.length === 0) {
|
|
||||||
// Если это последний узел, увеличиваем расстояние
|
|
||||||
distance *= spreadFactor + node.depth * 0.2; // Чем глубже, тем больше разброс
|
|
||||||
}
|
|
||||||
|
|
||||||
node.x = parent.x + distance * Math.cos(direction + offsetAngle);
|
|
||||||
node.y = parent.y + distance * Math.sin(direction + offsetAngle);
|
|
||||||
node.angle = direction + offsetAngle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nodePositions.current.set(node.data.id, { x: node.x, y: node.y });
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { root, nodes, links };
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!chartRef.current) return;
|
|
||||||
|
|
||||||
const svg = d3.select(chartRef.current)
|
|
||||||
.attr("width", 2000)
|
|
||||||
.attr("height", 2000)
|
|
||||||
.attr("viewBox", [-500, -500, 1500, 1500])
|
|
||||||
.attr("style", "max-width: 100%; height: auto;");
|
|
||||||
|
|
||||||
svg.append("g").attr("class", "links");
|
|
||||||
svg.append("g").attr("class", "nodes");
|
|
||||||
svg.append("g").attr("class", "labels");
|
|
||||||
|
|
||||||
// Очищаем предыдущие элементы
|
|
||||||
svg.selectAll(".links line").remove();
|
|
||||||
svg.selectAll(".nodes circle").remove();
|
|
||||||
svg.selectAll(".labels text").remove();
|
|
||||||
|
|
||||||
// Рисуем связи
|
|
||||||
const linkGroup = svg.select(".links");
|
|
||||||
const link = linkGroup
|
|
||||||
.selectAll("line")
|
|
||||||
.data(links, (d) => `${d.source.data.id}-${d.target.data.id}`)
|
|
||||||
.join("line")
|
|
||||||
.attr("stroke", "#999")
|
|
||||||
.attr("stroke-opacity", 0.6)
|
|
||||||
.attr("x1", (d) => d.source.x)
|
|
||||||
.attr("y1", (d) => d.source.y)
|
|
||||||
.attr("x2", (d) => d.target.x)
|
|
||||||
.attr("y2", (d) => d.target.y);
|
|
||||||
|
|
||||||
// Рисуем узлы
|
|
||||||
const nodeGroup = svg.select(".nodes");
|
|
||||||
const node = nodeGroup
|
|
||||||
.selectAll("circle")
|
|
||||||
.data(nodes, (d) => d.data.id)
|
|
||||||
.join("circle")
|
|
||||||
.attr("fill", (d) => getStatusColor(d.data.status))
|
|
||||||
.attr("stroke", "#fff")
|
|
||||||
.attr("r", 7)
|
|
||||||
.attr("cx", (d) => d.x)
|
|
||||||
.attr("cy", (d) => d.y)
|
|
||||||
.call(drag());
|
|
||||||
|
|
||||||
node.on("click", (event, d) => {
|
|
||||||
if (onNodeClick) {
|
|
||||||
onNodeClick(d.data.id, d.data.title);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Рисуем текстовые метки
|
|
||||||
const labelGroup = svg.select(".labels");
|
|
||||||
const text = labelGroup
|
|
||||||
.selectAll("text")
|
|
||||||
.data(nodes, (d) => d.data.id)
|
|
||||||
.join("text")
|
|
||||||
.text((d) => (nodes.length > 50 ? "" : d.data.title)) // Скрываем текст, если узлов много
|
|
||||||
.attr("dx", 12)
|
|
||||||
.attr("dy", 4)
|
|
||||||
.style("user-select", "none") // Запрет выделения текста
|
|
||||||
.style("pointer-events", "none") // Запрет взаимодействия с текстом
|
|
||||||
.style("fill", "var(--TreeChart-text-color)") // Используем переменную для цвета текста
|
|
||||||
.attr("x", (d) => d.x + 12)
|
|
||||||
.attr("y", (d) => d.y + 4);
|
|
||||||
|
|
||||||
}, [root, links, nodes, onNodeClick]);
|
|
||||||
|
|
||||||
const drag = () => {
|
|
||||||
function dragstarted(event, d) {
|
|
||||||
d3.select(this).raise().attr("stroke", "#000");
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragged(event, d) {
|
|
||||||
d.x = event.x;
|
|
||||||
d.y = event.y;
|
|
||||||
d3.select(this).attr("cx", d.x).attr("cy", d.y);
|
|
||||||
|
|
||||||
// Обновляем текстовую метку
|
|
||||||
d3.select(this.parentNode)
|
|
||||||
.select("text")
|
|
||||||
.attr("x", d.x + 12)
|
|
||||||
.attr("y", d.y + 4);
|
|
||||||
|
|
||||||
// Обновляем связи
|
|
||||||
d3.select(chartRef.current)
|
|
||||||
.selectAll(".links line")
|
|
||||||
.filter((link) => link.source === d || link.target === d)
|
|
||||||
.attr("x1", (link) => link.source.x)
|
|
||||||
.attr("y1", (link) => link.source.y)
|
|
||||||
.attr("x2", (link) => link.target.x)
|
|
||||||
.attr("y2", (link) => link.target.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragended(event, d) {
|
|
||||||
d3.select(this).attr("stroke", "#fff");
|
|
||||||
nodePositions.current.set(d.data.id, { x: d.x, y: d.y });
|
|
||||||
}
|
|
||||||
|
|
||||||
return d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended);
|
|
||||||
};
|
|
||||||
|
|
||||||
return <svg ref={chartRef} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TreeChart;
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,714 +0,0 @@
|
||||||
{
|
|
||||||
"title": "Сервис ЗВКС",
|
|
||||||
"id": "1",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"title": "Функциональные задачи",
|
|
||||||
"id": "functional_tasks",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "system_control",
|
|
||||||
"title": "Контроль системы"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "system_management",
|
|
||||||
"title": "Система управления"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "conference",
|
|
||||||
"title": "Проведение ВКС"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "backup",
|
|
||||||
"title": "Резервное копирование"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "relay_info",
|
|
||||||
"title": "Ретрансляция информации"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "18",
|
|
||||||
"title": "Graviton S2082I (device$18)",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "4",
|
|
||||||
"title": "OS Linux (module$4) АО",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "190",
|
|
||||||
"title": "Загрузка процессора за 1 минуту"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "191",
|
|
||||||
"title": "Загрузка процессора за 5 минут"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "192",
|
|
||||||
"title": "Загрузка процессора за 15 минут"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "197",
|
|
||||||
"title": "Общий объем SWAP-файла"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "198",
|
|
||||||
"title": "Используемый объем SWAP-файла"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "199",
|
|
||||||
"title": "Общий объем физической оперативной памяти"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "200",
|
|
||||||
"title": "Доступный объем физической оперативной памяти"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "201",
|
|
||||||
"title": "Свободный объем физической и виртуальной оперативной памяти"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "202",
|
|
||||||
"title": "Буферизованный объем оперативной памяти"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "203",
|
|
||||||
"title": "Кэшированый объем оперативной памяти"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "274",
|
|
||||||
"title": "Используемый объем SWAP-файла"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "275",
|
|
||||||
"title": "Время затраченное процессором на процессы с пониженным приоритетом"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "276",
|
|
||||||
"title": "Время затраченное процессором на процессы ядра ОС"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "277",
|
|
||||||
"title": "Время простоя процессора"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "278",
|
|
||||||
"title": "Общая емкость жестких дисков"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "279",
|
|
||||||
"title": "Доступная емкость жестких дисков"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "5",
|
|
||||||
"title": "Vinteo (module$5) ПО",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "31",
|
|
||||||
"title": "Общее количество участников"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "32",
|
|
||||||
"title": "Ожидание соединения"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "33",
|
|
||||||
"title": "Зарегистрированные абоненты"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "34",
|
|
||||||
"title": "Количество пользоватей HLS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "35",
|
|
||||||
"title": "Общее количество P2P комнат"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "36",
|
|
||||||
"title": "Общее количество конференций"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "37",
|
|
||||||
"title": "Общее количество активных конференций"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "38",
|
|
||||||
"title": "Статус записи"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "39",
|
|
||||||
"title": "Общее количество сохранённых записей"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "280",
|
|
||||||
"title": "Сетевой адаптер №1 (port$261) Eth_1",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "207",
|
|
||||||
"title": "Скорость порта Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "209",
|
|
||||||
"title": "Административное состояние порта Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "210",
|
|
||||||
"title": "Оперативное состояние порта Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "211",
|
|
||||||
"title": "Общее количество отправленных октетов Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "212",
|
|
||||||
"title": "Количество входящих Multicast пакетов Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "213",
|
|
||||||
"title": "Количество иcходящих Multiicast пакетов Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "214",
|
|
||||||
"title": "Количество входящих Broadcast пакетов Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "215",
|
|
||||||
"title": "Количество иcходящих Broadcast пакетов Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "216",
|
|
||||||
"title": "Количество входящих Unicast пакетов Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "217",
|
|
||||||
"title": "Количество иcходящих Unicast пакетов Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "218",
|
|
||||||
"title": "Количество входящих пакетов помеченные как отброшенные Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "219",
|
|
||||||
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "220",
|
|
||||||
"title": "Количество входящих пакетов с ошибкой Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "221",
|
|
||||||
"title": "Количество исходящих пакетов с ошибкой Eth_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "222",
|
|
||||||
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "281",
|
|
||||||
"title": "Сетевой адаптер №2 (port$262) Eth_2",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "224",
|
|
||||||
"title": "Скорость порта Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "226",
|
|
||||||
"title": "Административное состояние порта Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "227",
|
|
||||||
"title": "Оперативное состояние порта Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "228",
|
|
||||||
"title": "Общее количество отправленных октетов Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "229",
|
|
||||||
"title": "Количество входящих Multicast пакетов Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "230",
|
|
||||||
"title": "Количество иcходящих Multiicast пакетов Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "231",
|
|
||||||
"title": "Количество входящих Broadcast пакетов Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "232",
|
|
||||||
"title": "Количество иcходящих Broadcast пакетов Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "233",
|
|
||||||
"title": "Количество входящих Unicast пакетов Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "234",
|
|
||||||
"title": "Количество иcходящих Unicast пакетов Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "235",
|
|
||||||
"title": "Количество входящих пакетов помеченные как отброшенные Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "236",
|
|
||||||
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "237",
|
|
||||||
"title": "Количество входящих пакетов с ошибкой Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "238",
|
|
||||||
"title": "Количество исходящих пакетов с ошибкой Eth_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "239",
|
|
||||||
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "282",
|
|
||||||
"title": "Сетевой адаптер №3 (port$263) Eth_3",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "241",
|
|
||||||
"title": "Скорость порта Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "243",
|
|
||||||
"title": "Административное состояние порта Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "244",
|
|
||||||
"title": "Оперативное состояние порта Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "245",
|
|
||||||
"title": "Общее количество отправленных октетов Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "246",
|
|
||||||
"title": "Количество входящих Multicast пакетов Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "247",
|
|
||||||
"title": "Количество иcходящих Multiicast пакетов Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "248",
|
|
||||||
"title": "Количество входящих Broadcast пакетов Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "249",
|
|
||||||
"title": "Количество иcходящих Broadcast пакетов Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "250",
|
|
||||||
"title": "Количество входящих Unicast пакетов Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "251",
|
|
||||||
"title": "Количество иcходящих Unicast пакетов Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "252",
|
|
||||||
"title": "Количество входящих пакетов помеченные как отброшенные Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "253",
|
|
||||||
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "254",
|
|
||||||
"title": "Количество входящих пакетов с ошибкой Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "255",
|
|
||||||
"title": "Количество исходящих пакетов с ошибкой Eth_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "256",
|
|
||||||
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_3"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "283",
|
|
||||||
"title": "Сетевой адаптер №4 (port$264) Eth_4",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "258",
|
|
||||||
"title": "Скорость порта Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "260",
|
|
||||||
"title": "Административное состояние порта Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "261",
|
|
||||||
"title": "Оперативное состояние порта Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "262",
|
|
||||||
"title": "Общее количество отправленных октетов Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "263",
|
|
||||||
"title": "Количество входящих Multicast пакетов Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "264",
|
|
||||||
"title": "Количество иcходящих Multiicast пакетов Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "265",
|
|
||||||
"title": "Количество входящих Broadcast пакетов Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "266",
|
|
||||||
"title": "Количество иcходящих Broadcast пакетов Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "267",
|
|
||||||
"title": "Количество входящих Unicast пакетов Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "268",
|
|
||||||
"title": "Количество иcходящих Unicast пакетов Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "269",
|
|
||||||
"title": "Количество входящих пакетов помеченные как отброшенные Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "270",
|
|
||||||
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "271",
|
|
||||||
"title": "Количество входящих пакетов с ошибкой Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "272",
|
|
||||||
"title": "Количество исходящих пакетов с ошибкой Eth_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "273",
|
|
||||||
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Медиа сервер",
|
|
||||||
"id": "media_server_1",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"title": "Аппаратное обеспечение",
|
|
||||||
"id": "system_software_1",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "media_system_software_1_2",
|
|
||||||
"title": "Центральный процессор"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_2_2",
|
|
||||||
"title": "Оперативная память"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_3_2",
|
|
||||||
"title": "Жесткий диск"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_4_2",
|
|
||||||
"title": "Сетевые адаптеры"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Программное обеспечение",
|
|
||||||
"id": "software_1",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "media_software_1_2",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_2_2",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_3_2",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_4_2",
|
|
||||||
"title": "ПО"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Медиа сервер",
|
|
||||||
"id": "media_server_2",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"title": "Аппаратное обеспечение",
|
|
||||||
"id": "system_software_2",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "media_system_software_1_3",
|
|
||||||
"title": "Центральный процессор"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_2_3",
|
|
||||||
"title": "Оперативная память"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_3_3",
|
|
||||||
"title": "Жесткий диск"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_4_3",
|
|
||||||
"title": "Сетевые адаптеры"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Программное обеспечение",
|
|
||||||
"id": "software_2",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "media_software_1_3",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_2_3",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_3_3",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_4_3",
|
|
||||||
"title": "ПО"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Медиа сервер",
|
|
||||||
"id": "media_server_3",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"title": "Аппаратное обеспечение",
|
|
||||||
"id": "system_software_3",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "media_system_software_1_4",
|
|
||||||
"title": "Центральный процессор"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_2_4",
|
|
||||||
"title": "Оперативная память"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_3_4",
|
|
||||||
"title": "Жесткий диск"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_4_4",
|
|
||||||
"title": "Сетевые адаптеры"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Программное обеспечение",
|
|
||||||
"id": "software_3",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "media_software_1_4",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_2_4",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_3_4",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_4_4",
|
|
||||||
"title": "ПО"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Медиа сервер",
|
|
||||||
"id": "media_server_4",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"title": "Аппаратное обеспечение",
|
|
||||||
"id": "system_software_4",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "media_system_software_1_5",
|
|
||||||
"title": "Центральный процессор"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_2_5",
|
|
||||||
"title": "Оперативная память"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_3_5",
|
|
||||||
"title": "Жесткий диск"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_system_software_4_5",
|
|
||||||
"title": "Сетевые адаптеры"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Программное обеспечение",
|
|
||||||
"id": "software_4",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "media_software_1_5",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_2_5",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_3_5",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "media_software_4_5",
|
|
||||||
"title": "ПО"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Сервер систем",
|
|
||||||
"id": "system_server_1",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"title": "Аппаратное обеспечение",
|
|
||||||
"id": "system_software_5",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "copy_system_software_1",
|
|
||||||
"title": "Центральный процессор"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "copy_system_software_2",
|
|
||||||
"title": "Оперативная память"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "copy_system_software_3",
|
|
||||||
"title": "Жесткий диск"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "copy_system_software_4",
|
|
||||||
"title": "Сетевые адаптеры"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Программное обеспечение",
|
|
||||||
"id": "software_5",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "copy_software_1",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "copy_software_2",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "copy_software_3",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "copy_software_4",
|
|
||||||
"title": "ПО"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Сервер систем",
|
|
||||||
"id": "system_server_2",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"title": "Аппаратное обеспечение",
|
|
||||||
"id": "system_software_6",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "control_system_software_1",
|
|
||||||
"title": "Центральный процессор"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "control_system_software_2",
|
|
||||||
"title": "Оперативная память"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "control_system_software_3",
|
|
||||||
"title": "Жесткий диск"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "control_system_software_4",
|
|
||||||
"title": "Сетевые адаптеры"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Программное обеспечение",
|
|
||||||
"id": "software_6",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "control_software_1",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "control_software_2",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "control_software_3",
|
|
||||||
"title": "ПО"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "control_software_4",
|
|
||||||
"title": "ПО"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import React, { lazy, Suspense } from "react";
|
import React, { lazy, Suspense } from "react";
|
||||||
|
import Skeleton from '@mui/material/Skeleton';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
const PrometheusChart = lazy(() => import('../../Charts/PrometheusChart'));
|
const PrometheusChart = lazy(() => import('../../Charts/PrometheusChart'));
|
||||||
|
import LazyChartBatchRenderer from "../hooks/LazyChartBatchRender";
|
||||||
|
|
||||||
// Функция для генерации названия метрики на основе id
|
// Функция для генерации названия метрики на основе id
|
||||||
const getMetricName = (id) => {
|
const getMetricName = (id) => {
|
||||||
return `zvks_apiforsnmp_measure_${id}`;
|
return `zvks_apiforsnmp_measure_${id}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
//!!!!!!!!!!Пофиксить вкладуи с eth4, во всех eth 1-4 открывается именно 4 !!!!!!!!!!!!!
|
|
||||||
|
|
||||||
// Функция для рекурсивного сбора всех id потомков
|
// Функция для рекурсивного сбора всех id потомков
|
||||||
const getAllChildIds = (node) => {
|
const getAllChildIds = (node) => {
|
||||||
let ids = [];
|
let ids = [];
|
||||||
|
|
@ -23,9 +24,32 @@ const getAllChildIds = (node) => {
|
||||||
return ids;
|
return ids;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Компонент Skeleton для графика
|
||||||
|
const ChartSkeleton = () => (
|
||||||
|
<Box sx={{ width: '100%' }}>
|
||||||
|
<Skeleton variant="text" width="60%" height={30} /> {/* Заголовок */}
|
||||||
|
<Skeleton variant="rectangular" width="100%" height={300} sx={{ mt: 2 }} /> {/* График */}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Компонент Skeleton для родительского контейнера
|
||||||
|
const ContainerSkeleton = () => (
|
||||||
|
<Box sx={{ width: '100%' }}>
|
||||||
|
<Skeleton variant="text" width="40%" height={40} /> {/* Заголовок */}
|
||||||
|
<Skeleton variant="text" width="80%" height={20} sx={{ mt: 1 }} /> {/* Описание */}
|
||||||
|
{/* Место для дочерних элементов */}
|
||||||
|
<Box sx={{ mt: 2 }}>
|
||||||
|
{[...Array(3)].map((_, i) => (
|
||||||
|
<ChartSkeleton key={i} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
const tabContent = (data) => {
|
const tabContent = (data) => {
|
||||||
const tabContent = {};
|
const tabContent = {};
|
||||||
|
|
||||||
|
// Функция для рекурсивного обхода и сбора данных
|
||||||
// Функция для рекурсивного обхода и сбора данных
|
// Функция для рекурсивного обхода и сбора данных
|
||||||
const generateContent = (nodes) => {
|
const generateContent = (nodes) => {
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
|
|
@ -37,8 +61,11 @@ const tabContent = (data) => {
|
||||||
const content = (
|
const content = (
|
||||||
<div>
|
<div>
|
||||||
<h2>{node.title}</h2>
|
<h2>{node.title}</h2>
|
||||||
|
<Suspense fallback={<ContainerSkeleton />}>
|
||||||
|
<LazyChartBatchRenderer charts={node.items.map((child) => tabContent[child.id].content)} />
|
||||||
|
</Suspense>
|
||||||
<p>Контент для {node.title}.</p>
|
<p>Контент для {node.title}.</p>
|
||||||
{childrenContent}
|
{/*childrenContent*/}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -53,7 +80,7 @@ const tabContent = (data) => {
|
||||||
const content = (
|
const content = (
|
||||||
<div key={node.id}>
|
<div key={node.id}>
|
||||||
<h3>{node.title}</h3> {/* Используем title узла */}
|
<h3>{node.title}</h3> {/* Используем title узла */}
|
||||||
<Suspense fallback={<div>Загрузка графика...</div>}>
|
<Suspense fallback={<ChartSkeleton />}>
|
||||||
<PrometheusChart metricName={metricName} />
|
<PrometheusChart metricName={metricName} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
|
const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
// Дополнительные стили
|
||||||
|
}));
|
||||||
|
|
||||||
|
const CustomButton = ({
|
||||||
|
children,
|
||||||
|
variant = 'contained',
|
||||||
|
color = 'primary',
|
||||||
|
loading = false,
|
||||||
|
startIcon,
|
||||||
|
endIcon,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
variant={variant}
|
||||||
|
color={color}
|
||||||
|
startIcon={startIcon && !loading ? startIcon : undefined}
|
||||||
|
endIcon={endIcon && !loading ? endIcon : undefined}
|
||||||
|
disabled={loading}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{loading ? <CircularProgress size={24} /> : children}
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomButton;
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import criticalIcon from "../../assets/images/critical.png"; // Красный треугольник
|
|
||||||
import warningIcon from "../../assets/images/warning.png"; // Желтый треугольник
|
|
||||||
import "../../Style/ErrorIndicator.css"; // Подключаем стили
|
|
||||||
|
|
||||||
const ErrorIndicator = ({ criticalCount, warningCount }) => {
|
|
||||||
return (
|
|
||||||
<div className="error-indicator">
|
|
||||||
{/* Красный индикатор (критические ошибки) */}
|
|
||||||
<div className="error-item critical">
|
|
||||||
<img src={criticalIcon} alt="Критическая ошибка" />
|
|
||||||
<span>{criticalCount}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Желтый индикатор (предупреждения) */}
|
|
||||||
<div className="error-item warning">
|
|
||||||
<img src={warningIcon} alt="Предупреждение" />
|
|
||||||
<span>{warningCount}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ErrorIndicator;
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import React, { useState } from "react";
|
|
||||||
import "../Style/Expandable.css"
|
|
||||||
|
|
||||||
const ExpandableInfo = ({ details }) => {
|
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
|
||||||
|
|
||||||
const toggleExpand = () => {
|
|
||||||
setIsExpanded(!isExpanded);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="expandable-info">
|
|
||||||
<button onClick={toggleExpand} className="expand-button">
|
|
||||||
{isExpanded ? "Скрыть" : "Подробнее"}
|
|
||||||
</button>
|
|
||||||
{isExpanded && (
|
|
||||||
<div className="details-menu">
|
|
||||||
{details.map((detail, index) => (
|
|
||||||
<div key={index} className="detail-item">
|
|
||||||
<span className="label">{detail.label}:</span>
|
|
||||||
<span className="value">{detail.value}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ExpandableInfo;
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import Modal from "./Modal";
|
import Modal from "./Modal";
|
||||||
import "../../Style/LoginModal.css";
|
import "../../Style/LoginModal.css";
|
||||||
|
import Logo from '../../assets/images/logo.svg?react';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
const LoginModal = ({ onLogin, onClose }) => {
|
const LoginModal = ({ onLogin, onClose }) => {
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
|
|
@ -15,10 +17,9 @@ const LoginModal = ({ onLogin, onClose }) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Отправляем данные на бэкенд
|
const response = await fetch('http://192.168.2.39:3000/api/auth/login', {
|
||||||
console.log("Отправляем данные:", { username, password });
|
|
||||||
const response = await fetch('http://192.168.2.39:3000/auth/login', {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
|
@ -28,8 +29,9 @@ const LoginModal = ({ onLogin, onClose }) => {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
onLogin(); // Успешная авторизация
|
localStorage.setItem('access_token', data.access_token);
|
||||||
onClose(); // Закрыть модальное окно
|
onLogin(data.user);
|
||||||
|
onClose();
|
||||||
} else {
|
} else {
|
||||||
setError(data.message || "Неверный логин или пароль");
|
setError(data.message || "Неверный логин или пароль");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,22 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Tabs, Tab, Box } from "@mui/material";
|
import { Tabs, Tab, Box, styled } from "@mui/material";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
|
||||||
|
const StyledTab = styled(Tab)(({ theme }) => ({
|
||||||
|
minHeight: 48,
|
||||||
|
'&.Mui-selected': {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
},
|
||||||
|
'&:focus-visible': {
|
||||||
|
outline: `2px solid ${theme.palette.primary.main}`,
|
||||||
|
outlineOffset: '-2px',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const CustomTabs = ({ tabs, activeTab, onTabClick, onCloseTab }) => {
|
const CustomTabs = ({ tabs, activeTab, onTabClick, onCloseTab }) => {
|
||||||
const handleMouseDown = (e, id) => {
|
const handleMouseDown = (e, id) => {
|
||||||
if (e.button === 1) {
|
if (e.button === 1) { // Middle mouse button
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onCloseTab(id);
|
onCloseTab(id);
|
||||||
}
|
}
|
||||||
|
|
@ -15,7 +27,13 @@ const CustomTabs = ({ tabs, activeTab, onTabClick, onCloseTab }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
<Box sx={{
|
||||||
|
borderBottom: 1,
|
||||||
|
borderColor: 'divider',
|
||||||
|
'& .MuiTabs-indicator': {
|
||||||
|
backgroundColor: 'primary.main',
|
||||||
|
}
|
||||||
|
}}>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
|
@ -23,28 +41,34 @@ const CustomTabs = ({ tabs, activeTab, onTabClick, onCloseTab }) => {
|
||||||
scrollButtons="auto"
|
scrollButtons="auto"
|
||||||
aria-label="tabs"
|
aria-label="tabs"
|
||||||
>
|
>
|
||||||
{/* Всегда отображаемые вкладки */}
|
{/* Статические вкладки */}
|
||||||
<Tab
|
<StyledTab
|
||||||
label="Главная"
|
label="Главная"
|
||||||
value="Главная"
|
value="Главная"
|
||||||
onMouseDown={(e) => handleMouseDown(e, "Главная")}
|
onMouseDown={(e) => handleMouseDown(e, "Главная")}
|
||||||
/>
|
/>
|
||||||
<Tab
|
<StyledTab
|
||||||
label="Визуализация"
|
label="Визуализация"
|
||||||
value="Визуализация"
|
value="Визуализация"
|
||||||
onMouseDown={(e) => handleMouseDown(e, "Визуализация")}
|
onMouseDown={(e) => handleMouseDown(e, "Визуализация")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Динамически добавляемые вкладки */}
|
{/* Динамические вкладки */}
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<Tab
|
<StyledTab
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
label={
|
label={
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
<span>{tab.title}</span>
|
<span>{tab.title}</span>
|
||||||
<CloseIcon
|
<CloseIcon
|
||||||
fontSize="small"
|
fontSize="small"
|
||||||
sx={{ ml: 1, cursor: "pointer" }}
|
sx={{
|
||||||
|
ml: 1,
|
||||||
|
cursor: "pointer",
|
||||||
|
'&:hover': {
|
||||||
|
color: 'error.main'
|
||||||
|
}
|
||||||
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onCloseTab(tab.id);
|
onCloseTab(tab.id);
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import "../../Style/common.css"; // Общие стили для табов
|
|
||||||
|
|
||||||
const Tabs = ({ tabs, activeTab, onTabClick, onCloseTab }) => {
|
|
||||||
const handleMouseDown = (e, id) => {
|
|
||||||
// Проверяем, была ли нажата средняя кнопка мыши (button === 1)
|
|
||||||
if (e.button === 1) {
|
|
||||||
e.preventDefault(); // Предотвращаем стандартное поведение (например, прокрутку)
|
|
||||||
onCloseTab(id); // Закрываем вкладку
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="tabs">
|
|
||||||
{/* Всегда отображаемые вкладки */}
|
|
||||||
<div
|
|
||||||
className={`tab ${activeTab === "Главная" ? "active" : ""}`}
|
|
||||||
onClick={() => onTabClick("Главная")}
|
|
||||||
onMouseDown={(e) => handleMouseDown(e, "Главная")} // Добавляем обработчик для СКМ
|
|
||||||
>
|
|
||||||
<span>Главная</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`tab ${activeTab === "Визуализация" ? "active" : ""}`}
|
|
||||||
onClick={() => onTabClick("Визуализация")}
|
|
||||||
onMouseDown={(e) => handleMouseDown(e, "Визуализация")} // Добавляем обработчик для СКМ
|
|
||||||
>
|
|
||||||
<span>Визуализация</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Динамически добавляемые вкладки */}
|
|
||||||
{tabs.map((tab) => (
|
|
||||||
<div
|
|
||||||
key={tab.id}
|
|
||||||
className={`tab ${activeTab === tab.id ? "active" : ""}`}
|
|
||||||
onClick={() => onTabClick(tab.id)}
|
|
||||||
onMouseDown={(e) => handleMouseDown(e, tab.id)} // Добавляем обработчик для СКМ
|
|
||||||
>
|
|
||||||
<span>{tab.title}</span>
|
|
||||||
<button
|
|
||||||
className="close-tab"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onCloseTab(tab.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Tabs;
|
|
||||||
|
|
@ -1,12 +1,27 @@
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import "../../Style/TreeTable.css";
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
Button,
|
||||||
|
Collapse,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
Tooltip
|
||||||
|
} from '@mui/material';
|
||||||
import { statusManager1, statusManager2 } from "../TreeChart/dataUtils";
|
import { statusManager1, statusManager2 } from "../TreeChart/dataUtils";
|
||||||
|
|
||||||
const TreeTable = ({ data }) => {
|
const TreeTable = ({ data }) => {
|
||||||
|
const theme = useTheme();
|
||||||
const tableRef = useRef(null);
|
const tableRef = useRef(null);
|
||||||
const [fontSize, setFontSize] = useState(16);
|
const [fontSize, setFontSize] = useState(16);
|
||||||
const [log, setLog] = useState([]);
|
const [log, setLog] = useState([]);
|
||||||
const [isLogVisible, setIsLogVisible] = useState(true);
|
const [isLogVisible, setIsLogVisible] = useState(false);
|
||||||
|
|
||||||
const adjustFontSize = () => {
|
const adjustFontSize = () => {
|
||||||
if (tableRef.current) {
|
if (tableRef.current) {
|
||||||
|
|
@ -27,6 +42,13 @@ const TreeTable = ({ data }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
adjustFontSize();
|
||||||
|
window.addEventListener('resize', adjustFontSize);
|
||||||
|
return () => window.removeEventListener('resize', adjustFontSize);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
// Логирование статусов
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newLog = [];
|
const newLog = [];
|
||||||
const traverse = (items) => {
|
const traverse = (items) => {
|
||||||
|
|
@ -35,7 +57,7 @@ const TreeTable = ({ data }) => {
|
||||||
newLog.push({
|
newLog.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
status: item.status,
|
status: item.status,
|
||||||
time: new Date().toLocaleTimeString(), // Добавляем время
|
time: new Date().toLocaleTimeString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (item.items) {
|
if (item.items) {
|
||||||
|
|
@ -44,200 +66,285 @@ const TreeTable = ({ data }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
traverse(data.items);
|
traverse(data.items);
|
||||||
|
setLog(prevLog => [...newLog, ...prevLog].slice(0, 50));
|
||||||
// Ограничиваем количество сообщений до 50
|
|
||||||
setLog((prevLog) => [...newLog, ...prevLog].slice(0, 50));
|
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const filteredData = data.items.filter((item) => item.title !== "Функциональные задачи");
|
const filteredData = data.items.filter(item => item.title !== "Функциональные задачи");
|
||||||
|
|
||||||
// Функция для отображения заголовков
|
// Компонент индикаторов статуса
|
||||||
const renderHeaders = (items) => {
|
const StatusIndicators = ({ status }) => (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '4px',
|
||||||
|
height: '20px',
|
||||||
|
display: 'inline-block',
|
||||||
|
backgroundColor: statusManager1.getStatusColor(status),
|
||||||
|
marginRight: '4px',
|
||||||
|
verticalAlign: 'middle'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '4px',
|
||||||
|
height: '20px',
|
||||||
|
display: 'inline-block',
|
||||||
|
backgroundColor: statusManager2.getStatusColor(status),
|
||||||
|
marginRight: '8px',
|
||||||
|
verticalAlign: 'middle'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ячейка с тултипом
|
||||||
|
const TableCellWithTooltip = ({ children, title, ...props }) => (
|
||||||
|
<Tooltip title={title} arrow>
|
||||||
|
<TableCell {...props}>
|
||||||
|
{children}
|
||||||
|
</TableCell>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Рендер заголовков (первый уровень)
|
||||||
|
const renderMainHeaders = (items) => {
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
const colSpan = item.items ? item.items.length : 1;
|
const colSpan = item.items ? item.items.length : 1;
|
||||||
return (
|
return (
|
||||||
<th key={item.id} colSpan={colSpan} className="tree-table-header" title={item.title}>
|
<TableCellWithTooltip
|
||||||
<div className="header-content">
|
key={item.id}
|
||||||
<div
|
colSpan={colSpan}
|
||||||
className="status-indicator-bar"
|
align="center"
|
||||||
style={{ backgroundColor: statusManager1.getStatusColor(item.status) }}
|
title={item.title}
|
||||||
/>
|
sx={{
|
||||||
<div
|
backgroundColor: theme.palette.background.paper,
|
||||||
className="status-indicator-bar"
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
style={{
|
padding: '8px',
|
||||||
backgroundColor: statusManager2.getStatusColor(item.status),
|
whiteSpace: 'nowrap',
|
||||||
marginLeft: "5px",
|
overflow: 'hidden',
|
||||||
}}
|
textOverflow: 'ellipsis'
|
||||||
/>
|
}}
|
||||||
|
>
|
||||||
|
<StatusIndicators status={item.status} />
|
||||||
|
<Typography component="span" variant="subtitle2" noWrap>
|
||||||
{item.title}
|
{item.title}
|
||||||
</div>
|
</Typography>
|
||||||
</th>
|
</TableCellWithTooltip>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Функция для отображения подзаголовков
|
// Рендер подзаголовков (второй уровень)
|
||||||
const renderSubHeaders = (items) => {
|
const renderSubHeaders = (items) => {
|
||||||
return items.map((item) => {
|
return items.flatMap((item) => {
|
||||||
if (item.items) {
|
if (item.items) {
|
||||||
return item.items.map((child) => (
|
return item.items.map((child) => (
|
||||||
<th key={child.id} className="tree-table-header" title={child.title}>
|
<TableCellWithTooltip
|
||||||
<div className="header-content">
|
key={child.id}
|
||||||
<div
|
align="center"
|
||||||
className="status-indicator-bar"
|
title={child.title}
|
||||||
style={{ backgroundColor: statusManager1.getStatusColor(child.status) }}
|
sx={{
|
||||||
/>
|
backgroundColor: theme.palette.background.paper,
|
||||||
<div
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
className="status-indicator-bar"
|
padding: '8px',
|
||||||
style={{
|
whiteSpace: 'nowrap',
|
||||||
backgroundColor: statusManager2.getStatusColor(child.status),
|
overflow: 'hidden',
|
||||||
marginLeft: "5px",
|
textOverflow: 'ellipsis'
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<StatusIndicators status={child.status} />
|
||||||
|
<Typography component="span" variant="subtitle2" noWrap>
|
||||||
{child.title}
|
{child.title}
|
||||||
</div>
|
</Typography>
|
||||||
</th>
|
</TableCellWithTooltip>
|
||||||
));
|
));
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<th key={item.id} className="tree-table-header" title={item.title}>
|
|
||||||
<div className="header-content">
|
|
||||||
<div
|
|
||||||
className="status-indicator-bar"
|
|
||||||
style={{ backgroundColor: statusManager1.getStatusColor(item.status) }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="status-indicator-bar"
|
|
||||||
style={{
|
|
||||||
backgroundColor: statusManager2.getStatusColor(item.status),
|
|
||||||
marginLeft: "5px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{item.title}
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
|
<TableCellWithTooltip
|
||||||
|
key={item.id}
|
||||||
|
align="center"
|
||||||
|
title={item.title}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
padding: '8px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StatusIndicators status={item.status} />
|
||||||
|
<Typography component="span" variant="subtitle2" noWrap>
|
||||||
|
{item.title}
|
||||||
|
</Typography>
|
||||||
|
</TableCellWithTooltip>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Функция для отображения данных
|
// Рендер данных (третий уровень)
|
||||||
const renderData = (items) => {
|
const renderDataCells = (items) => {
|
||||||
return items.map((item) => {
|
return items.flatMap((item) => {
|
||||||
if (item.items) {
|
if (item.items) {
|
||||||
return item.items.map((child) => {
|
return item.items.flatMap((child) => {
|
||||||
if (child.items) {
|
if (child.items) {
|
||||||
return child.items.map((subChild) => (
|
return child.items.map((subChild) => (
|
||||||
<td key={subChild.id} className="tree-table-cell" title={subChild.title}>
|
<TableCellWithTooltip
|
||||||
<div className="cell-content">
|
key={subChild.id}
|
||||||
<div
|
title={subChild.title}
|
||||||
className="status-indicator-bar"
|
sx={{
|
||||||
style={{ backgroundColor: statusManager1.getStatusColor(subChild.status) }}
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
/>
|
padding: '8px',
|
||||||
<div
|
whiteSpace: 'nowrap',
|
||||||
className="status-indicator-bar"
|
overflow: 'hidden',
|
||||||
style={{
|
textOverflow: 'ellipsis'
|
||||||
backgroundColor: statusManager2.getStatusColor(subChild.status),
|
|
||||||
marginLeft: "5px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span className="cell-text">{subChild.title}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<td key={child.id} className="tree-table-cell" title={child.title}>
|
|
||||||
<div className="cell-content">
|
|
||||||
<div
|
|
||||||
className="status-indicator-bar"
|
|
||||||
style={{ backgroundColor: statusManager1.getStatusColor(child.status) }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="status-indicator-bar"
|
|
||||||
style={{
|
|
||||||
backgroundColor: statusManager2.getStatusColor(child.status),
|
|
||||||
marginLeft: "5px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span className="cell-text">{child.title}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<td key={item.id} className="tree-table-cell" title={item.title}>
|
|
||||||
<div className="cell-content">
|
|
||||||
<div
|
|
||||||
className="status-indicator-bar"
|
|
||||||
style={{ backgroundColor: statusManager1.getStatusColor(item.status) }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="status-indicator-bar"
|
|
||||||
style={{
|
|
||||||
backgroundColor: statusManager2.getStatusColor(item.status),
|
|
||||||
marginLeft: "5px",
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
<span className="cell-text">{item.title}</span>
|
<StatusIndicators status={subChild.status} />
|
||||||
</div>
|
<Typography component="span" variant="body2" noWrap>
|
||||||
</td>
|
{subChild.title}
|
||||||
);
|
</Typography>
|
||||||
|
</TableCellWithTooltip>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<TableCellWithTooltip
|
||||||
|
key={child.id}
|
||||||
|
title={child.title}
|
||||||
|
sx={{
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
padding: '8px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StatusIndicators status={child.status} />
|
||||||
|
<Typography component="span" variant="body2" noWrap>
|
||||||
|
{child.title}
|
||||||
|
</Typography>
|
||||||
|
</TableCellWithTooltip>
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
|
<TableCellWithTooltip
|
||||||
|
key={item.id}
|
||||||
|
title={item.title}
|
||||||
|
sx={{
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
padding: '8px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StatusIndicators status={item.status} />
|
||||||
|
<Typography component="span" variant="body2" noWrap>
|
||||||
|
{item.title}
|
||||||
|
</Typography>
|
||||||
|
</TableCellWithTooltip>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tree-table-container">
|
<Box sx={{ width: '100%' }}>
|
||||||
<table ref={tableRef} className="tree-table" style={{ fontSize: `${fontSize}px` }}>
|
<TableContainer
|
||||||
<thead>
|
component={Paper}
|
||||||
<tr>
|
ref={tableRef}
|
||||||
<th
|
sx={{
|
||||||
colSpan={filteredData.reduce((acc, item) => acc + (item.items ? item.items.length : 1), 0)}
|
fontSize: `${fontSize}px`,
|
||||||
className="tree-table-header"
|
width: '100%',
|
||||||
title={data.title}
|
'& .MuiTableCell-root': {
|
||||||
>
|
py: 1,
|
||||||
<div className="header-content">
|
px: 2
|
||||||
<div
|
}
|
||||||
className="status-indicator-bar"
|
}}
|
||||||
style={{ backgroundColor: statusManager1.getStatusColor(data.status) }}
|
>
|
||||||
/>
|
<Table sx={{ width: '100%', tableLayout: 'fixed' }}>
|
||||||
<div
|
<TableHead>
|
||||||
className="status-indicator-bar"
|
{/* Основной заголовок таблицы */}
|
||||||
style={{
|
<TableRow>
|
||||||
backgroundColor: statusManager2.getStatusColor(data.status),
|
<TableCellWithTooltip
|
||||||
marginLeft: "5px",
|
colSpan={filteredData.reduce((acc, item) => acc + (item.items ? item.items.length : 1), 0)}
|
||||||
}}
|
align="center"
|
||||||
/>
|
title={data.title}
|
||||||
{data.title}
|
sx={{
|
||||||
</div>
|
backgroundColor: theme.palette.background.paper,
|
||||||
</th>
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
</tr>
|
padding: '8px'
|
||||||
<tr>{renderHeaders(filteredData)}</tr>
|
}}
|
||||||
<tr>{renderSubHeaders(filteredData)}</tr>
|
>
|
||||||
</thead>
|
<StatusIndicators status={data.status} />
|
||||||
<tbody>
|
<Typography component="span" variant="subtitle1" fontWeight="bold" noWrap>
|
||||||
<tr className="tree-table-row">{renderData(filteredData)}</tr>
|
{data.title}
|
||||||
</tbody>
|
</Typography>
|
||||||
</table>
|
</TableCellWithTooltip>
|
||||||
<button onClick={() => setIsLogVisible(!isLogVisible)} className="toggle-log-button">
|
</TableRow>
|
||||||
{isLogVisible ? "Скрыть лог" : "Показать лог"}
|
|
||||||
</button>
|
{/* Строка с основными заголовками */}
|
||||||
{isLogVisible && (
|
<TableRow>
|
||||||
<div className="status-log">
|
{renderMainHeaders(filteredData)}
|
||||||
<h3>Лог статусов</h3>
|
</TableRow>
|
||||||
<ul>
|
|
||||||
|
{/* Строка с подзаголовками (которая пропала в предыдущей версии) */}
|
||||||
|
<TableRow>
|
||||||
|
{renderSubHeaders(filteredData)}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
{renderDataCells(filteredData)}
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => setIsLogVisible(!isLogVisible)}
|
||||||
|
size="small"
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
>
|
||||||
|
{isLogVisible ? 'Скрыть историю изменения статусов' : 'Показать историю изменения статусов'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Collapse in={isLogVisible}>
|
||||||
|
<Box sx={{
|
||||||
|
mt: 2,
|
||||||
|
p: 2,
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
borderRadius: 1,
|
||||||
|
backgroundColor: theme.palette.background.paper
|
||||||
|
}}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
История изменения статусов
|
||||||
|
</Typography>
|
||||||
|
<Box component="ul" sx={{
|
||||||
|
pl: 2,
|
||||||
|
maxHeight: 400,
|
||||||
|
overflow: 'auto',
|
||||||
|
listStyle: 'none'
|
||||||
|
}}>
|
||||||
{log.map((entry, index) => (
|
{log.map((entry, index) => (
|
||||||
<li key={index} style={{ color: statusManager1.getStatusColor(entry.status) }}>
|
<Box
|
||||||
|
component="li"
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
py: 1,
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
color: statusManager1.getStatusColor(entry.status)
|
||||||
|
}}
|
||||||
|
>
|
||||||
[{entry.time}] {entry.status}: {entry.title}
|
[{entry.time}] {entry.status}: {entry.title}
|
||||||
</li>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</Box>
|
||||||
</div>
|
</Box>
|
||||||
)}
|
</Collapse>
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export const checkAuth = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://192.168.2.39:3000/api/auth/check', {
|
||||||
|
method: 'GET',
|
||||||
|
credentials: 'include', // Важно для отправки cookies
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('access_token') || ''}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Not authenticated');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Auth check failed:', err);
|
||||||
|
return { isAuthenticated: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Skeleton from '@mui/material/Skeleton';
|
||||||
|
|
||||||
|
const LazyChartBatchRenderer = ({ charts }) => {
|
||||||
|
const [visibleIndices, setVisibleIndices] = useState(new Set());
|
||||||
|
const placeholderRefs = useRef([]);
|
||||||
|
|
||||||
|
const ChartSkeleton = () => (
|
||||||
|
<Box sx={{ backgroundColor: '#fff', borderRadius: '8px', padding: '20px', marginBottom: '20px', position: 'relative', height: '500px' }}>
|
||||||
|
<Box sx={{ position: 'absolute', right: '20px', top: '20px' }}>
|
||||||
|
<Skeleton variant="circular" width={16} height={16} />
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||||
|
<Skeleton variant="text" width="40%" height={30} />
|
||||||
|
<Skeleton variant="text" width="30%" height={30} />
|
||||||
|
</Box>
|
||||||
|
<Skeleton variant="rectangular" width="100%" height={300} />
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2, gap: 2 }}>
|
||||||
|
{[1, 2, 3, 4].map((_, i) => (
|
||||||
|
<Skeleton key={i} variant="rounded" width={80} height={36} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
setVisibleIndices((prev) => {
|
||||||
|
const updated = new Set(prev);
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
const index = parseInt(entry.target.dataset.index, 10);
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
updated.add(index);
|
||||||
|
} else {
|
||||||
|
updated.delete(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: null,
|
||||||
|
rootMargin: '200px',
|
||||||
|
threshold: 0.1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
placeholderRefs.current.forEach((ref) => {
|
||||||
|
if (ref) observer.observe(ref);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, [charts]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{charts.map((chart, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
ref={(el) => (placeholderRefs.current[index] = el)}
|
||||||
|
data-index={index}
|
||||||
|
>
|
||||||
|
{visibleIndices.has(index) ? chart : <ChartSkeleton />}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LazyChartBatchRenderer;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import SystemStatusChart from "../../Charts/SystemStatusChart";
|
import SystemStatusChart from "../../Charts/SystemStatusChart";
|
||||||
import TreeTable from "../UI/TreeTable";
|
import TreeTable from "../UI/TreeTable";
|
||||||
import TreeChart from "../TreeChart/TreeChart";
|
import FlowChart from "../TreeChart/FlowChart";
|
||||||
|
|
||||||
const TabContent = ({ activeTab, statusHistories, treeData1, tabContent, handleOpenTab }) => {
|
const TabContent = ({ activeTab, statusHistories, treeData1, tabContent, handleOpenTab }) => {
|
||||||
if (activeTab === "Главная") {
|
if (activeTab === "Главная") {
|
||||||
|
|
@ -22,7 +22,7 @@ const TabContent = ({ activeTab, statusHistories, treeData1, tabContent, handleO
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (activeTab === "Визуализация") {
|
} else if (activeTab === "Визуализация") {
|
||||||
return <TreeChart data={treeData1} onNodeClick={(id, title) => handleOpenTab(id, title)} />;
|
return <FlowChart data={treeData1} onNodeClick={(id, title) => handleOpenTab(id, title)} />;
|
||||||
} else {
|
} else {
|
||||||
const tabData = tabContent[activeTab];
|
const tabData = tabContent[activeTab];
|
||||||
return tabData ? tabData.content : <p>Нет данных</p>;
|
return tabData ? tabData.content : <p>Нет данных</p>;
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
/* Основной контейнер */
|
|
||||||
.dashboard-container {
|
|
||||||
display: flex;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Сайдбар */
|
|
||||||
.sidebar {
|
|
||||||
flex-shrink: 0;
|
|
||||||
height: 100vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
background-color: var(--sidebar-color);
|
|
||||||
color: var(--sidebar-text-color);
|
|
||||||
transition: width 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Основной контент */
|
|
||||||
.main-content {
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 20px;
|
|
||||||
overflow: auto;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Контент */
|
|
||||||
.content {
|
|
||||||
background-color: var(--modal-background);
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.521);
|
|
||||||
max-width: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Заголовки */
|
|
||||||
h2 {
|
|
||||||
color: var(--text-color);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
.error-indicator {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-item img {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-item span {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.critical span {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning span {
|
|
||||||
color: orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
.expandable-info {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand-button {
|
|
||||||
background-color: #444;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand-button:hover {
|
|
||||||
background-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-menu {
|
|
||||||
margin-top: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #333;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
/* Сайдбар */
|
|
||||||
.sidebar {
|
|
||||||
height: 100vh;
|
|
||||||
background-color: var(--sidebar-color);
|
|
||||||
color: var(--sidebar-text-color);
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
z-index: 999;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: width 0.2s ease;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Контейнер для основного контента меню */
|
|
||||||
.sidebar-content {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
padding-right: 10px;
|
|
||||||
/* Отступ справа для скроллбара */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Заголовок меню */
|
|
||||||
.sidebar-title {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 1.5em;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--sidebar-text-color);
|
|
||||||
padding: 10px;
|
|
||||||
text-align: center;
|
|
||||||
/* font-size: 2vh; */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Элементы меню */
|
|
||||||
.menu-item {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: var(--sidebar-text-color);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Элемент для перетаскивания */
|
|
||||||
.sidebar-resizer {
|
|
||||||
width: 5px;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
cursor: ew-resize;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-resizer:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для заголовка элемента меню */
|
|
||||||
.menu-item-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
/* Распределяем пространство между элементами */
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
width: 100%;
|
|
||||||
/* Занимаем всю доступную ширину */
|
|
||||||
box-sizing: border-box;
|
|
||||||
/* Учитываем padding в ширине */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для текста элемента меню */
|
|
||||||
.menu-item-header span {
|
|
||||||
flex: 1;
|
|
||||||
/* Текст занимает все доступное пространство */
|
|
||||||
margin-right: 14px;
|
|
||||||
/* Отступ справа для текста */
|
|
||||||
overflow: hidden;
|
|
||||||
/* Скрываем текст, который не помещается */
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
/* Добавляем многоточие, если текст не помещается */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для иконок */
|
|
||||||
.menu-item-header .open-parent-icon,
|
|
||||||
.menu-item-header .toggle-icon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
/* Запрещаем сжатие иконок */
|
|
||||||
margin-left: 1px;
|
|
||||||
/* Отступ между иконками */
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item-header:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Круглый индикатор статуса */
|
|
||||||
.status-indicator {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 10px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Подменю */
|
|
||||||
.submenu {
|
|
||||||
margin-left: 20px;
|
|
||||||
/* Отступ слева для вложенных элементов */
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для элементов нижнего уровня вложенности */
|
|
||||||
|
|
||||||
/* Дополнительные отступы для элементов без иконок */
|
|
||||||
.menu-item:not(.has-children) .menu-item-header {
|
|
||||||
padding-right: 25px;
|
|
||||||
/* Добавляем отступ справа для элементов без иконок */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Футер сайдбара */
|
|
||||||
.sidebar-footer {
|
|
||||||
padding: 10px;
|
|
||||||
background-color: var(--sidebar-color);
|
|
||||||
text-align: center;
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help,
|
|
||||||
.settings {
|
|
||||||
color: var(--sidebar-text-color);
|
|
||||||
margin: 5px 0;
|
|
||||||
overflow-x: hidden;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
.tree-table-container {
|
|
||||||
width: 100%;
|
|
||||||
overflow-x: hidden;
|
|
||||||
/* Убираем горизонтальный скролл */
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
text-align: center;
|
|
||||||
table-layout: fixed;
|
|
||||||
/* Фиксированная ширина колонок */
|
|
||||||
background-color: var(--table-cell-background);
|
|
||||||
color: var(--table-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-table-header {
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid black;
|
|
||||||
font-weight: bold;
|
|
||||||
white-space: nowrap;
|
|
||||||
/* Текст не переносится */
|
|
||||||
overflow: hidden;
|
|
||||||
/* Скрываем текст, который не помещается */
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
/* Добавляем многоточие */
|
|
||||||
background-color: var(--table-header-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-table-cell {
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid black;
|
|
||||||
white-space: nowrap;
|
|
||||||
/* Текст не переносится */
|
|
||||||
overflow: hidden;
|
|
||||||
/* Скрываем текст, который не помещается */
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
/* Добавляем многоточие */
|
|
||||||
}
|
|
||||||
|
|
||||||
.cell-content,
|
|
||||||
.header-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2px;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cell-text {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator-bar {
|
|
||||||
width: 6px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 3px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
/* Контейнер для вкладок */
|
|
||||||
.tabs {
|
|
||||||
display: flex;
|
|
||||||
gap: 5px;
|
|
||||||
padding: 5px;
|
|
||||||
background-color: var(--sidebar-color);
|
|
||||||
border-bottom: 2px solid var(--accent-color);
|
|
||||||
overflow-x: auto;
|
|
||||||
border-radius: 5px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для отдельной вкладки */
|
|
||||||
.tab {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--sidebar-color);
|
|
||||||
color: var(--sidebar-text-color);
|
|
||||||
/* Используем переменную для цвета текста */
|
|
||||||
padding: 5px 15px;
|
|
||||||
border-radius: 5px 5px 0 0;
|
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Активная вкладка */
|
|
||||||
.tab.active {
|
|
||||||
background-color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Кнопка закрытия вкладки */
|
|
||||||
.close-tab {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--sidebar-text-color);
|
|
||||||
/* Используем переменную для цвета текста */
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-left: 10px;
|
|
||||||
padding: 0;
|
|
||||||
transition: color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Эффект при наведении на кнопку закрытия */
|
|
||||||
.close-tab:hover {
|
|
||||||
color: #ff6b6b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Эффект при наведении на вкладку */
|
|
||||||
.tab:hover {
|
|
||||||
background-color: var(--accent-hover-color);
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
/* Темная тема, если пользователь предпочитает ее */
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background-color: #1E1E1E;
|
|
||||||
--text-color: #E0E0E0;
|
|
||||||
--header-color: #FFFFFF;
|
|
||||||
/* Основной цвет текста (светлый) */
|
|
||||||
--sidebar-color: #2d2d2d;
|
|
||||||
/* Темный цвет сайдбара */
|
|
||||||
--sidebar-text-color: #E0E0E0;
|
|
||||||
/* Светлый текст в сайдбаре */
|
|
||||||
--modal-background: #2d2d2d;
|
|
||||||
--modal--btn-background: #333333;
|
|
||||||
--modal-text: #FFFFFF;
|
|
||||||
--table-border: #444444;
|
|
||||||
--table-header-background: #2d2d2d;
|
|
||||||
--table-cell-background: #333333;
|
|
||||||
--table-text-color: #E0E0E0;
|
|
||||||
/* Светлый текст в таблице */
|
|
||||||
--TreeChart-text-color: #ffffff;
|
|
||||||
--scrollbar-track-color: #333;
|
|
||||||
/* hover for buttons */
|
|
||||||
--hover-button: #333d4d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
/* Светлая тема по умолчанию */
|
|
||||||
:root {
|
|
||||||
--background-color: #FFFFFF;
|
|
||||||
--text-color: #000000;
|
|
||||||
--header-color: #333333;
|
|
||||||
/* Основной цвет текста (черный) */
|
|
||||||
--sidebar-color: #3d74c7;
|
|
||||||
/* Синий цвет сайдбара */
|
|
||||||
--sidebar-text-color: #FFFFFF;
|
|
||||||
/* Белый текст в сайдбаре и вкладках */
|
|
||||||
--modal-background: #FFFFFF;
|
|
||||||
--modal--btn-background: #0f55bec2;
|
|
||||||
--modal-text: #333333;
|
|
||||||
--table-border: #ddd;
|
|
||||||
--table-header-background: #f9f9f9;
|
|
||||||
--table-cell-background: #FFFFFF;
|
|
||||||
--table-text-color: #000000;
|
|
||||||
/* Черный текст в таблице */
|
|
||||||
|
|
||||||
/* hover for buttons */
|
|
||||||
--hover-button: #2d62b1;
|
|
||||||
--hover-text-color: #FFFFFF
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +1,191 @@
|
||||||
import { createTheme } from "@mui/material/styles";
|
import { createTheme } from "@mui/material/styles";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Общие настройки темы, применяемые для обеих тем (светлой и темной)
|
||||||
|
*/
|
||||||
|
const commonThemeSettings = {
|
||||||
|
// Настройки формы элементов
|
||||||
|
shape: {
|
||||||
|
borderRadius: 8, // Базовый радиус скругления углов для всех компонентов
|
||||||
|
},
|
||||||
|
|
||||||
|
// Переопределения стилей конкретных MUI компонентов
|
||||||
|
components: {
|
||||||
|
// Стили для компонента Drawer (боковое меню)
|
||||||
|
MuiDrawer: {
|
||||||
|
styleOverrides: {
|
||||||
|
paper: {
|
||||||
|
borderRight: 'none', // Убираем правую границу у бокового меню
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MuiTab: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
textTransform: 'none', // Убираем uppercase
|
||||||
|
minWidth: 'unset', // Убираем минимальную ширину
|
||||||
|
padding: '6px 16px',
|
||||||
|
'&:hover': {
|
||||||
|
color: 'primary.main',
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
'&.Mui-selected': {
|
||||||
|
color: 'primary.main',
|
||||||
|
},
|
||||||
|
'&.Mui-focusVisible': {
|
||||||
|
backgroundColor: 'action.selected',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiTabs: {
|
||||||
|
styleOverrides: {
|
||||||
|
indicator: {
|
||||||
|
height: 3, // Толщина индикатора
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Стили для кнопок-элементов списка
|
||||||
|
MuiListItemButton: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
// Стиль для выбранного элемента
|
||||||
|
'&.Mui-selected': {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.16)',
|
||||||
|
},
|
||||||
|
// Стиль при наведении на выбранный элемент
|
||||||
|
'&.Mui-selected:hover': {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.24)',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Светлая тема приложения
|
||||||
|
*/
|
||||||
export const lightTheme = createTheme({
|
export const lightTheme = createTheme({
|
||||||
|
...commonThemeSettings, // Распаковываем общие настройки
|
||||||
|
|
||||||
|
// Цветовая палитра для светлой темы
|
||||||
palette: {
|
palette: {
|
||||||
mode: "light",
|
mode: "light", // Режим светлой темы
|
||||||
|
|
||||||
|
// Фоновые цвета
|
||||||
background: {
|
background: {
|
||||||
default: "#FFFFFF",
|
default: "#FFFFFF", // Основной фон приложения
|
||||||
paper: "#FFFFFF",
|
paper: "#FFFFFF", // Фон "бумажных" поверхностей (карточек, панелей)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Текстовые цвета
|
||||||
text: {
|
text: {
|
||||||
primary: "#000000",
|
primary: "#000000", // Основной цвет текста
|
||||||
|
secondary: "#333333", // Вторичный цвет текста
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Основные цвета UI
|
||||||
primary: {
|
primary: {
|
||||||
main: "#3d74c7",
|
main: "#3d74c7", // Основной брендовый цвет
|
||||||
|
contrastText: "#FFFFFF", // Цвет текста на кнопках primary цвета
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Дополнительные цвета UI
|
||||||
secondary: {
|
secondary: {
|
||||||
main: "#0f55bec2",
|
main: "#0f55bec2", // Вторичный брендовый цвет
|
||||||
},
|
},
|
||||||
|
|
||||||
|
divider: "#e0e0e0", // Цвет разделителей
|
||||||
|
|
||||||
|
// Кастомные цвета для специфических элементов
|
||||||
custom: {
|
custom: {
|
||||||
background: "#FFFFFF",
|
background: "#D4EFFC", // Кастомный фоновый цвет
|
||||||
text: "#000000",
|
text: "#000000", // Кастомный цвет текста
|
||||||
sidebar: "#3d74c7",
|
sidebar: "#025EA1", // Фон боковой панели
|
||||||
sidebarText: "#FFFFFF",
|
sidebarText: "#FFFFFF", // Текст в боковой панели
|
||||||
modalBackground: "#FFFFFF",
|
sidebarHover: "rgba(255, 255, 255, 0.08)", // Цвет при наведении в боковой панели
|
||||||
modalBtnBackground: "#0f55bec2",
|
modalBackground: "#FFFFFF", // Фон модальных окон
|
||||||
modalText: "#333333",
|
modalBtnBackground: "#0f55bec2", // Фон кнопок в модальных окнах
|
||||||
tableBorder: "#ddd",
|
modalText: "#333333", // Текст в модальных окнах
|
||||||
tableHeaderBackground: "#f9f9f9",
|
tableBorder: "#ddd", // Границы таблиц
|
||||||
tableCellBackground: "#FFFFFF",
|
tableHeaderBackground: "#f9f9f9", // Фон заголовков таблиц
|
||||||
tableText: "#000000",
|
tableCellBackground: "#FFFFFF", // Фон ячеек таблиц
|
||||||
treeChartText: "#000000",
|
tableText: "#000000", // Текст в таблицах
|
||||||
scrollbarTrack: "#f1f1f1",
|
treeChartText: "#000000", // Текст в древовидных диаграммах
|
||||||
hoverButton: "#2d62b1",
|
scrollbarTrack: "#f1f1f1", // Цвет трека скроллбара
|
||||||
hoverText: "#FFFFFF",
|
hoverButton: "#2d62b1", // Цвет кнопок при наведении
|
||||||
|
hoverText: "#FFFFFF", // Цвет текста при наведении
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Цвета для различных состояний
|
||||||
|
action: {
|
||||||
|
hover: "rgba(0, 0, 0, 0.04)", // Цвет при наведении на интерактивные элементы
|
||||||
|
selected: "rgba(0, 0, 0, 0.08)", // Цвет выбранных элементов
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Темная тема приложения
|
||||||
|
*/
|
||||||
export const darkTheme = createTheme({
|
export const darkTheme = createTheme({
|
||||||
|
...commonThemeSettings, // Распаковываем общие настройки
|
||||||
|
|
||||||
|
// Цветовая палитра для темной темы
|
||||||
palette: {
|
palette: {
|
||||||
mode: "dark",
|
mode: "dark", // Режим темной темы
|
||||||
|
|
||||||
|
// Фоновые цвета
|
||||||
background: {
|
background: {
|
||||||
default: "#1E1E1E",
|
default: "#2d2d2d", // Основной фон приложения
|
||||||
paper: "#2d2d2d",
|
paper: "#2d2d2d", // Фон "бумажных" поверхностей
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Текстовые цвета
|
||||||
text: {
|
text: {
|
||||||
primary: "#E0E0E0",
|
primary: "#E0E0E0", // Основной цвет текста
|
||||||
|
secondary: "#B0B0B0", // Вторичный цвет текста
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Основные цвета UI
|
||||||
primary: {
|
primary: {
|
||||||
main: "#2d2d2d",
|
main: "#3d74c7", // Основной брендовый цвет (может совпадать со светлой темой)
|
||||||
|
contrastText: "#FFFFFF", // Цвет текста на кнопках primary цвета
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Дополнительные цвета UI
|
||||||
secondary: {
|
secondary: {
|
||||||
main: "#333333",
|
main: "#0f55bec2", // Вторичный брендовый цвет
|
||||||
},
|
},
|
||||||
|
|
||||||
|
divider: "#444444", // Цвет разделителей
|
||||||
|
|
||||||
|
// Кастомные цвета для специфических элементов
|
||||||
custom: {
|
custom: {
|
||||||
background: "#1E1E1E",
|
background: "#1E1E1E", // Кастомный фоновый цвет
|
||||||
text: "#E0E0E0",
|
text: "#E0E0E0", // Кастомный цвет текста
|
||||||
sidebar: "#2d2d2d",
|
sidebar: "#2d2d2d", // Фон боковой панели
|
||||||
sidebarText: "#E0E0E0",
|
sidebarText: "#E0E0E0", // Текст в боковой панели
|
||||||
modalBackground: "#2d2d2d",
|
sidebarHover: "rgba(255, 255, 255, 0.16)", // Цвет при наведении в боковой панели
|
||||||
modalBtnBackground: "#333333",
|
modalBackground: "#2d2d2d", // Фон модальных окон
|
||||||
modalText: "#FFFFFF",
|
modalBtnBackground: "#333333", // Фон кнопок в модальных окнах
|
||||||
tableBorder: "#444444",
|
modalText: "#FFFFFF", // Текст в модальных окнах
|
||||||
tableHeaderBackground: "#2d2d2d",
|
tableBorder: "#444444", // Границы таблиц
|
||||||
tableCellBackground: "#333333",
|
tableHeaderBackground: "#2d2d2d", // Фон заголовков таблиц
|
||||||
tableText: "#E0E0E0",
|
tableCellBackground: "#333333", // Фон ячеек таблиц
|
||||||
treeChartText: "#FFFFFF",
|
tableText: "#E0E0E0", // Текст в таблицах
|
||||||
scrollbarTrack: "#333",
|
treeChartText: "#FFFFFF", // Текст в древовидных диаграммах
|
||||||
hoverButton: "#333d4d",
|
scrollbarTrack: "#333", // Цвет трека скроллбара
|
||||||
hoverText: "#E0E0E0",
|
hoverButton: "#333d4d", // Цвет кнопок при наведении
|
||||||
|
hoverText: "#E0E0E0", // Цвет текста при наведении
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Цвета для различных состояний
|
||||||
|
action: {
|
||||||
|
hover: "rgba(255, 255, 255, 0.08)", // Цвет при наведении на интерактивные элементы
|
||||||
|
selected: "rgba(255, 255, 255, 0.16)", // Цвет выбранных элементов
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -0,0 +1,319 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 28.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 273.81 45.36" style="enable-background:new 0 0 273.81 45.36;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#428AC9;}
|
||||||
|
.st1{fill:url(#SVGID_1_);}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<rect x="58.03" y="1.44" class="st0" width="1.62" height="40.83"/>
|
||||||
|
<path class="st0" d="M29.84,0.03V0h-0.95h-0.01h-0.95v0.03C16.95,0.49,8.06,9.1,7.11,19.94c-0.06,0.63-0.09,1.27-0.09,1.92
|
||||||
|
c0,0.64,0.03,1.28,0.09,1.92c0.97,11.16,10.36,19.94,21.77,19.94h0.96v-3.83h-0.96c-9.28,0-16.96-7-17.92-16.11h2.6h8.36
|
||||||
|
c-0.22-0.69-0.34-1.43-0.34-2.2c0-0.56,0.07-1.11,0.19-1.63h-3.08c0.91-4.82,5.2-8.46,10.2-8.46c3.49,0,6.73,1.77,8.63,4.63h4.34
|
||||||
|
c-2.23-5.09-7.38-8.46-12.97-8.46c-7.12,0-13.14,5.33-14.08,12.29h-1.47h-2.38c0.96-9.11,8.64-16.11,17.92-16.11h0.01
|
||||||
|
c9.28,0,16.96,7,17.92,16.11H36c0.12,0.53,0.19,1.07,0.19,1.63c0,0.77-0.12,1.51-0.34,2.2h0.58h2.65h3.88h2h5.79v-1.92
|
||||||
|
C50.75,10.12,41.45,0.52,29.84,0.03z"/>
|
||||||
|
<path class="st0" d="M30.11,32.79c-0.4,0.05-0.81,0.08-1.22,0.08c-4.33,0-8.12-2.73-9.65-6.59h-4.02
|
||||||
|
c1.67,6.02,7.21,10.42,13.67,10.42c0.41,0,0.82-0.02,1.22-0.06V32.79z"/>
|
||||||
|
|
||||||
|
<radialGradient id="SVGID_1_" cx="-5958.7173" cy="3785.8042" r="51.5778" gradientTransform="matrix(0.1405 0 0 0.1405 864.4218 -513.426)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" style="stop-color:#4A96D2"/>
|
||||||
|
<stop offset="1" style="stop-color:#1F2466"/>
|
||||||
|
</radialGradient>
|
||||||
|
<path class="st1" d="M32.71,24.79c-1.91,2.11-5.16,2.28-7.27,0.37c-2.11-1.9-2.28-5.16-0.38-7.27c1.91-2.11,5.16-2.28,7.27-0.37
|
||||||
|
C34.44,19.42,34.61,22.68,32.71,24.79z"/>
|
||||||
|
<g>
|
||||||
|
<path d="M77.34,11.48h-1.17V3.5h-4.29v7.98h-1.17V2.41h6.62V11.48z"/>
|
||||||
|
<path d="M80.63,14.2h-1.09V4.87h1.04v0.95h0.03c0.16-0.23,0.35-0.42,0.55-0.56c0.2-0.14,0.41-0.25,0.61-0.32
|
||||||
|
c0.2-0.07,0.4-0.11,0.58-0.14c0.18-0.02,0.33-0.03,0.45-0.03c0.52,0,0.96,0.08,1.33,0.24c0.37,0.16,0.67,0.38,0.9,0.67
|
||||||
|
c0.23,0.29,0.4,0.64,0.51,1.06c0.11,0.42,0.16,0.88,0.16,1.39c0,0.48-0.06,0.93-0.17,1.35s-0.29,0.78-0.52,1.09
|
||||||
|
c-0.23,0.31-0.53,0.56-0.89,0.74c-0.36,0.18-0.78,0.27-1.26,0.27c-0.21,0-0.41-0.02-0.62-0.05c-0.21-0.03-0.41-0.09-0.6-0.17
|
||||||
|
c-0.19-0.08-0.38-0.18-0.54-0.3c-0.17-0.12-0.31-0.27-0.43-0.45h-0.03V14.2z M82.61,10.6c0.68,0,1.17-0.22,1.47-0.65
|
||||||
|
c0.3-0.44,0.45-1.06,0.45-1.86c0-0.37-0.03-0.7-0.1-1c-0.06-0.29-0.17-0.54-0.32-0.73c-0.15-0.19-0.35-0.34-0.6-0.45
|
||||||
|
s-0.56-0.16-0.93-0.16c-0.64,0-1.12,0.22-1.46,0.67s-0.5,1.04-0.5,1.76c0,0.73,0.17,1.32,0.5,1.76S81.96,10.6,82.61,10.6z"/>
|
||||||
|
<path d="M90.1,11.58c-1.04,0-1.81-0.29-2.33-0.87c-0.52-0.58-0.78-1.43-0.78-2.53c0-1.08,0.27-1.92,0.8-2.51
|
||||||
|
c0.53-0.6,1.3-0.89,2.31-0.89c1.04,0,1.81,0.29,2.33,0.87c0.52,0.58,0.78,1.42,0.78,2.54c0,0.53-0.07,1-0.21,1.42
|
||||||
|
c-0.14,0.42-0.34,0.78-0.6,1.08c-0.26,0.29-0.59,0.52-0.98,0.67C91.03,11.5,90.59,11.58,90.1,11.58z M90.1,10.6
|
||||||
|
c0.65,0,1.13-0.2,1.46-0.59c0.32-0.39,0.49-1,0.49-1.83c0-0.82-0.15-1.43-0.45-1.83c-0.3-0.4-0.8-0.6-1.5-0.6
|
||||||
|
c-0.62,0-1.1,0.2-1.44,0.6c-0.34,0.4-0.5,1.01-0.5,1.83c0,0.84,0.17,1.45,0.52,1.84C89.03,10.4,89.51,10.6,90.1,10.6z"/>
|
||||||
|
<path d="M95.98,11.48h-1.09V4.87h4.34v0.98h-3.25V11.48z"/>
|
||||||
|
<path d="M101.46,14.2h-1.09V4.87h1.04v0.95h0.03c0.16-0.23,0.35-0.42,0.55-0.56c0.2-0.14,0.41-0.25,0.61-0.32
|
||||||
|
c0.2-0.07,0.4-0.11,0.58-0.14c0.18-0.02,0.33-0.03,0.45-0.03c0.52,0,0.96,0.08,1.33,0.24c0.37,0.16,0.67,0.38,0.9,0.67
|
||||||
|
c0.23,0.29,0.4,0.64,0.51,1.06c0.11,0.42,0.16,0.88,0.16,1.39c0,0.48-0.06,0.93-0.17,1.35s-0.29,0.78-0.52,1.09
|
||||||
|
c-0.23,0.31-0.53,0.56-0.89,0.74c-0.36,0.18-0.78,0.27-1.26,0.27c-0.21,0-0.41-0.02-0.62-0.05c-0.21-0.03-0.41-0.09-0.6-0.17
|
||||||
|
c-0.19-0.08-0.38-0.18-0.54-0.3c-0.17-0.12-0.31-0.27-0.43-0.45h-0.03V14.2z M103.44,10.6c0.68,0,1.17-0.22,1.47-0.65
|
||||||
|
c0.3-0.44,0.45-1.06,0.45-1.86c0-0.37-0.03-0.7-0.1-1c-0.06-0.29-0.17-0.54-0.32-0.73c-0.15-0.19-0.35-0.34-0.6-0.45
|
||||||
|
s-0.56-0.16-0.93-0.16c-0.64,0-1.12,0.22-1.46,0.67s-0.5,1.04-0.5,1.76c0,0.73,0.17,1.32,0.5,1.76S102.79,10.6,103.44,10.6z"/>
|
||||||
|
<path d="M112.92,11.48h-0.96v-1.21h-0.03c-0.07,0.18-0.17,0.35-0.32,0.51c-0.14,0.16-0.31,0.3-0.5,0.42
|
||||||
|
c-0.19,0.12-0.4,0.21-0.63,0.28s-0.48,0.1-0.73,0.1c-0.3,0-0.58-0.05-0.83-0.14s-0.46-0.23-0.64-0.39
|
||||||
|
c-0.18-0.17-0.32-0.37-0.41-0.59c-0.1-0.22-0.15-0.47-0.15-0.73c0-0.44,0.12-0.8,0.35-1.08C108.3,8.38,108.61,8.16,109,8
|
||||||
|
c0.38-0.16,0.82-0.27,1.31-0.34s1-0.11,1.52-0.14V7.19c0-0.48-0.11-0.84-0.34-1.08s-0.63-0.36-1.2-0.36
|
||||||
|
c-0.32,0-0.66,0.04-1.02,0.12s-0.67,0.19-0.93,0.32l-0.19-0.89c0.27-0.15,0.6-0.27,1.01-0.38c0.41-0.1,0.82-0.16,1.26-0.16
|
||||||
|
c0.49,0,0.9,0.06,1.22,0.17c0.32,0.12,0.58,0.29,0.77,0.51s0.32,0.5,0.4,0.84c0.08,0.34,0.12,0.73,0.12,1.17V11.48z M111.83,8.37
|
||||||
|
c-0.37,0.02-0.73,0.04-1.08,0.08c-0.35,0.03-0.66,0.1-0.94,0.19s-0.5,0.22-0.67,0.38c-0.17,0.16-0.25,0.37-0.25,0.63
|
||||||
|
c0,0.27,0.09,0.5,0.28,0.68c0.19,0.19,0.48,0.28,0.89,0.28c0.52,0,0.94-0.16,1.28-0.48s0.5-0.79,0.5-1.41V8.37z"/>
|
||||||
|
<path d="M121.53,4.87l0.65,6.61h-1.04l-0.39-4.53V6.28h-0.03l-0.21,0.66l-1.66,4.53h-0.95l-1.66-4.53l-0.21-0.66h-0.03v0.65
|
||||||
|
l-0.39,4.55h-1.04l0.65-6.61h1.22l1.94,5.4h0.03l1.88-5.4H121.53z"/>
|
||||||
|
<path d="M130.54,4.87l0.65,6.61h-1.04l-0.39-4.53V6.28h-0.03l-0.21,0.66l-1.66,4.53h-0.95l-1.66-4.53l-0.21-0.66h-0.03v0.65
|
||||||
|
l-0.39,4.55h-1.04l0.65-6.61h1.22l1.94,5.4h0.03l1.88-5.4H130.54z"/>
|
||||||
|
<path d="M138.5,11.48h-1.09V8.54h-3.39v2.94h-1.09V4.87h1.09v2.68h3.39V4.87h1.09V11.48z"/>
|
||||||
|
<path d="M143.3,11.58c-1.04,0-1.81-0.29-2.33-0.87c-0.52-0.58-0.78-1.43-0.78-2.53c0-1.08,0.27-1.92,0.8-2.51
|
||||||
|
c0.53-0.6,1.3-0.89,2.31-0.89c1.04,0,1.81,0.29,2.33,0.87c0.52,0.58,0.78,1.42,0.78,2.54c0,0.53-0.07,1-0.21,1.42
|
||||||
|
c-0.14,0.42-0.34,0.78-0.6,1.08c-0.26,0.29-0.59,0.52-0.98,0.67C144.23,11.5,143.79,11.58,143.3,11.58z M143.3,10.6
|
||||||
|
c0.65,0,1.13-0.2,1.46-0.59c0.32-0.39,0.49-1,0.49-1.83c0-0.82-0.15-1.43-0.45-1.83c-0.3-0.4-0.8-0.6-1.5-0.6
|
||||||
|
c-0.62,0-1.1,0.2-1.44,0.6c-0.34,0.4-0.5,1.01-0.5,1.83c0,0.84,0.17,1.45,0.52,1.84C142.23,10.4,142.7,10.6,143.3,10.6z"/>
|
||||||
|
<path d="M153.17,11.08c-0.16,0.14-0.41,0.26-0.76,0.36c-0.35,0.1-0.78,0.15-1.28,0.15c-1.17,0-2.03-0.3-2.58-0.91
|
||||||
|
c-0.56-0.6-0.84-1.45-0.84-2.53c0-0.51,0.07-0.97,0.2-1.39c0.13-0.42,0.33-0.77,0.59-1.07c0.26-0.3,0.57-0.53,0.94-0.69
|
||||||
|
c0.37-0.16,0.78-0.24,1.25-0.24c0.53,0,0.98,0.09,1.35,0.26c0.37,0.17,0.67,0.41,0.9,0.71c0.23,0.3,0.4,0.65,0.5,1.06
|
||||||
|
c0.1,0.41,0.16,0.84,0.16,1.31v0.35h-4.74c0.06,0.73,0.28,1.26,0.65,1.62c0.37,0.35,0.95,0.53,1.72,0.53
|
||||||
|
c0.42,0,0.77-0.04,1.04-0.11s0.51-0.17,0.72-0.29L153.17,11.08z M152.42,7.54c0.01-0.25-0.02-0.49-0.09-0.71
|
||||||
|
c-0.07-0.22-0.17-0.41-0.31-0.57c-0.14-0.16-0.32-0.29-0.53-0.38c-0.22-0.09-0.47-0.14-0.75-0.14c-0.56,0-1,0.15-1.31,0.45
|
||||||
|
c-0.31,0.3-0.5,0.75-0.57,1.33H152.42z"/>
|
||||||
|
<path d="M160.94,11.58c-1.04,0-1.81-0.29-2.33-0.87c-0.52-0.58-0.78-1.43-0.78-2.53c0-1.08,0.27-1.92,0.8-2.51
|
||||||
|
c0.53-0.6,1.3-0.89,2.31-0.89c1.04,0,1.81,0.29,2.33,0.87c0.52,0.58,0.78,1.42,0.78,2.54c0,0.53-0.07,1-0.21,1.42
|
||||||
|
c-0.14,0.42-0.34,0.78-0.6,1.08c-0.26,0.29-0.59,0.52-0.98,0.67C161.87,11.5,161.43,11.58,160.94,11.58z M160.94,10.6
|
||||||
|
c0.65,0,1.13-0.2,1.46-0.59c0.32-0.39,0.49-1,0.49-1.83c0-0.82-0.15-1.43-0.45-1.83c-0.3-0.4-0.8-0.6-1.5-0.6
|
||||||
|
c-0.62,0-1.1,0.2-1.44,0.6c-0.34,0.4-0.5,1.01-0.5,1.83c0,0.84,0.17,1.45,0.52,1.84C159.87,10.4,160.35,10.6,160.94,10.6z"/>
|
||||||
|
<path d="M166.4,6.35c0.18-0.48,0.49-0.85,0.91-1.1c0.43-0.25,0.95-0.38,1.57-0.38c0.94,0,1.65,0.28,2.14,0.84s0.73,1.38,0.73,2.46
|
||||||
|
c0,1.07-0.26,1.91-0.79,2.51c-0.53,0.6-1.27,0.9-2.24,0.9c-0.6,0-1.1-0.09-1.52-0.29c-0.42-0.19-0.76-0.46-1.02-0.82
|
||||||
|
c-0.26-0.36-0.45-0.79-0.57-1.3c-0.12-0.5-0.17-1.08-0.17-1.72c0-1.04,0.07-1.91,0.22-2.6c0.15-0.69,0.39-1.25,0.72-1.67
|
||||||
|
c0.33-0.42,0.77-0.73,1.3-0.93s1.19-0.31,1.97-0.36c0.25-0.02,0.48-0.04,0.67-0.07s0.41-0.08,0.63-0.14v1.02
|
||||||
|
c-0.11,0.03-0.22,0.05-0.31,0.08c-0.1,0.03-0.2,0.05-0.3,0.07c-0.11,0.02-0.23,0.03-0.36,0.05s-0.29,0.03-0.47,0.05
|
||||||
|
c-0.57,0.05-1.05,0.13-1.43,0.24c-0.38,0.11-0.7,0.28-0.93,0.53c-0.24,0.24-0.42,0.57-0.54,0.98c-0.12,0.41-0.2,0.96-0.23,1.63
|
||||||
|
H166.4z M166.64,8.17c0,0.35,0.04,0.68,0.12,0.98c0.08,0.3,0.21,0.55,0.38,0.77c0.17,0.22,0.39,0.38,0.65,0.5
|
||||||
|
c0.26,0.12,0.57,0.17,0.93,0.17c0.62,0,1.09-0.22,1.39-0.65c0.31-0.43,0.46-1.02,0.46-1.78c0-0.76-0.16-1.34-0.47-1.73
|
||||||
|
c-0.32-0.39-0.81-0.59-1.48-0.59c-0.37,0-0.68,0.07-0.93,0.19c-0.25,0.13-0.45,0.3-0.61,0.51c-0.16,0.21-0.27,0.46-0.34,0.74
|
||||||
|
C166.68,7.58,166.64,7.87,166.64,8.17z"/>
|
||||||
|
<path d="M178.51,11.08c-0.16,0.14-0.41,0.26-0.76,0.36c-0.35,0.1-0.78,0.15-1.28,0.15c-1.17,0-2.03-0.3-2.58-0.91
|
||||||
|
c-0.56-0.6-0.84-1.45-0.84-2.53c0-0.51,0.07-0.97,0.2-1.39c0.13-0.42,0.33-0.77,0.59-1.07c0.26-0.3,0.57-0.53,0.94-0.69
|
||||||
|
c0.37-0.16,0.78-0.24,1.25-0.24c0.53,0,0.98,0.09,1.35,0.26c0.37,0.17,0.67,0.41,0.9,0.71c0.23,0.3,0.4,0.65,0.5,1.06
|
||||||
|
c0.1,0.41,0.16,0.84,0.16,1.31v0.35h-4.74c0.06,0.73,0.28,1.26,0.65,1.62c0.37,0.35,0.95,0.53,1.72,0.53
|
||||||
|
c0.42,0,0.77-0.04,1.04-0.11s0.51-0.17,0.72-0.29L178.51,11.08z M177.76,7.54c0.01-0.25-0.02-0.49-0.09-0.71
|
||||||
|
c-0.07-0.22-0.17-0.41-0.31-0.57c-0.14-0.16-0.32-0.29-0.53-0.38c-0.22-0.09-0.47-0.14-0.75-0.14c-0.56,0-1,0.15-1.31,0.45
|
||||||
|
c-0.31,0.3-0.5,0.75-0.57,1.33H177.76z"/>
|
||||||
|
<path d="M185.3,11.24c-0.15,0.07-0.36,0.14-0.65,0.22c-0.29,0.08-0.69,0.12-1.21,0.12c-0.56,0-1.05-0.08-1.46-0.24
|
||||||
|
c-0.41-0.16-0.75-0.39-1.02-0.69s-0.48-0.67-0.61-1.09c-0.13-0.42-0.2-0.9-0.2-1.44c0-1.06,0.29-1.89,0.86-2.47
|
||||||
|
c0.57-0.59,1.37-0.88,2.39-0.88c0.48,0,0.86,0.04,1.13,0.11c0.27,0.07,0.49,0.13,0.66,0.17l-0.21,0.93
|
||||||
|
c-0.16-0.05-0.36-0.1-0.6-0.16s-0.51-0.08-0.84-0.08c-0.69,0-1.23,0.18-1.63,0.55s-0.6,0.94-0.6,1.72c0,0.89,0.21,1.54,0.63,1.96
|
||||||
|
c0.42,0.42,1.03,0.62,1.81,0.62c0.26,0,0.51-0.03,0.75-0.08c0.24-0.05,0.43-0.11,0.59-0.18L185.3,11.24z"/>
|
||||||
|
<path d="M192.26,11.48h-1.09V5.85h-3.32v5.62h-1.09V4.87h5.49V11.48z"/>
|
||||||
|
<path d="M199.41,11.08c-0.16,0.14-0.41,0.26-0.76,0.36c-0.35,0.1-0.78,0.15-1.28,0.15c-1.17,0-2.03-0.3-2.58-0.91
|
||||||
|
c-0.56-0.6-0.84-1.45-0.84-2.53c0-0.51,0.07-0.97,0.2-1.39c0.13-0.42,0.33-0.77,0.59-1.07c0.26-0.3,0.57-0.53,0.94-0.69
|
||||||
|
c0.37-0.16,0.78-0.24,1.25-0.24c0.53,0,0.98,0.09,1.35,0.26c0.37,0.17,0.67,0.41,0.9,0.71c0.23,0.3,0.4,0.65,0.5,1.06
|
||||||
|
c0.1,0.41,0.16,0.84,0.16,1.31v0.35h-4.74c0.06,0.73,0.28,1.26,0.65,1.62c0.37,0.35,0.95,0.53,1.72,0.53
|
||||||
|
c0.42,0,0.77-0.04,1.04-0.11s0.51-0.17,0.72-0.29L199.41,11.08z M198.66,7.54c0.01-0.25-0.02-0.49-0.09-0.71
|
||||||
|
c-0.07-0.22-0.17-0.41-0.31-0.57c-0.14-0.16-0.32-0.29-0.53-0.38c-0.22-0.09-0.47-0.14-0.75-0.14c-0.56,0-1,0.15-1.31,0.45
|
||||||
|
c-0.31,0.3-0.5,0.75-0.57,1.33H198.66z"/>
|
||||||
|
<path d="M206.22,11.48h-1.09v-2.6c-0.04,0.01-0.12,0.03-0.22,0.05c-0.1,0.03-0.23,0.05-0.38,0.08s-0.31,0.06-0.5,0.08
|
||||||
|
c-0.19,0.02-0.38,0.03-0.58,0.03c-0.35,0-0.68-0.03-0.98-0.1c-0.3-0.07-0.56-0.18-0.77-0.34c-0.22-0.16-0.38-0.36-0.51-0.6
|
||||||
|
c-0.12-0.25-0.18-0.55-0.18-0.9V4.87h1.09v1.94c0,0.27,0.04,0.49,0.12,0.65c0.08,0.17,0.19,0.3,0.33,0.4
|
||||||
|
c0.14,0.1,0.31,0.17,0.52,0.21c0.2,0.04,0.43,0.06,0.67,0.06c0.33,0,0.61-0.03,0.85-0.08c0.24-0.05,0.42-0.1,0.54-0.14V4.87h1.09
|
||||||
|
V11.48z"/>
|
||||||
|
<path d="M213.37,11.08c-0.16,0.14-0.41,0.26-0.76,0.36c-0.35,0.1-0.78,0.15-1.28,0.15c-1.17,0-2.03-0.3-2.58-0.91
|
||||||
|
c-0.56-0.6-0.84-1.45-0.84-2.53c0-0.51,0.07-0.97,0.2-1.39c0.13-0.42,0.33-0.77,0.59-1.07c0.26-0.3,0.57-0.53,0.94-0.69
|
||||||
|
c0.37-0.16,0.78-0.24,1.25-0.24c0.53,0,0.98,0.09,1.35,0.26c0.37,0.17,0.67,0.41,0.9,0.71c0.23,0.3,0.4,0.65,0.5,1.06
|
||||||
|
c0.1,0.41,0.16,0.84,0.16,1.31v0.35h-4.74c0.06,0.73,0.28,1.26,0.65,1.62c0.37,0.35,0.95,0.53,1.72,0.53
|
||||||
|
c0.42,0,0.77-0.04,1.04-0.11s0.51-0.17,0.72-0.29L213.37,11.08z M212.62,7.54c0.01-0.25-0.02-0.49-0.09-0.71
|
||||||
|
c-0.07-0.22-0.17-0.41-0.31-0.57c-0.14-0.16-0.32-0.29-0.53-0.38c-0.22-0.09-0.47-0.14-0.75-0.14c-0.56,0-1,0.15-1.31,0.45
|
||||||
|
c-0.31,0.3-0.5,0.75-0.57,1.33H212.62z"/>
|
||||||
|
<path d="M220.98,11.48h-1.09V8.54h-3.39v2.94h-1.09V4.87h1.09v2.68h3.39V4.87h1.09V11.48z"/>
|
||||||
|
<path d="M223.05,11.48V4.87h1.09v4.21l-0.04,0.87h0.01l0.43-0.69l3.08-4.39h1.04v6.61h-1.09V7.16l0.04-0.76h-0.01l-0.4,0.65
|
||||||
|
l-3.11,4.43H223.05z"/>
|
||||||
|
<path d="M235.81,11.08c-0.16,0.14-0.41,0.26-0.76,0.36c-0.35,0.1-0.78,0.15-1.28,0.15c-1.17,0-2.03-0.3-2.58-0.91
|
||||||
|
c-0.56-0.6-0.84-1.45-0.84-2.53c0-0.51,0.07-0.97,0.2-1.39c0.13-0.42,0.33-0.77,0.59-1.07c0.26-0.3,0.57-0.53,0.94-0.69
|
||||||
|
c0.37-0.16,0.78-0.24,1.25-0.24c0.53,0,0.98,0.09,1.35,0.26c0.37,0.17,0.67,0.41,0.9,0.71c0.23,0.3,0.4,0.65,0.5,1.06
|
||||||
|
c0.1,0.41,0.16,0.84,0.16,1.31v0.35h-4.74c0.06,0.73,0.28,1.26,0.65,1.62c0.37,0.35,0.95,0.53,1.72,0.53
|
||||||
|
c0.42,0,0.77-0.04,1.04-0.11s0.51-0.17,0.72-0.29L235.81,11.08z M235.06,7.54c0.01-0.25-0.02-0.49-0.09-0.71
|
||||||
|
c-0.07-0.22-0.17-0.41-0.31-0.57c-0.14-0.16-0.32-0.29-0.53-0.38c-0.22-0.09-0.47-0.14-0.75-0.14c-0.56,0-1,0.15-1.31,0.45
|
||||||
|
c-0.31,0.3-0.5,0.75-0.57,1.33H235.06z"/>
|
||||||
|
<path d="M71.68,23.32h0.35l2.5-2.9h1.4l-2.81,3.1c0.1,0.05,0.19,0.11,0.27,0.19c0.07,0.07,0.15,0.16,0.24,0.27l2.63,3.06h-1.48
|
||||||
|
l-2-2.45c-0.13-0.16-0.24-0.28-0.32-0.34s-0.21-0.09-0.36-0.09h-0.42v2.88h-1.09v-6.61h1.09V23.32z"/>
|
||||||
|
<path d="M79.87,27.13c-1.04,0-1.81-0.29-2.33-0.87c-0.52-0.58-0.78-1.43-0.78-2.53c0-1.08,0.27-1.92,0.8-2.51
|
||||||
|
c0.53-0.6,1.3-0.89,2.31-0.89c1.04,0,1.81,0.29,2.33,0.87c0.52,0.58,0.78,1.42,0.78,2.54c0,0.53-0.07,1-0.21,1.42
|
||||||
|
c-0.14,0.42-0.34,0.78-0.6,1.08c-0.26,0.29-0.59,0.52-0.98,0.67C80.8,27.05,80.36,27.13,79.87,27.13z M79.87,26.14
|
||||||
|
c0.65,0,1.13-0.2,1.46-0.59c0.32-0.39,0.49-1,0.49-1.83c0-0.82-0.15-1.43-0.45-1.83c-0.3-0.4-0.8-0.6-1.5-0.6
|
||||||
|
c-0.62,0-1.1,0.2-1.44,0.6c-0.34,0.4-0.5,1.01-0.5,1.83c0,0.84,0.17,1.45,0.52,1.84C78.8,25.95,79.27,26.14,79.87,26.14z"/>
|
||||||
|
<path d="M90.23,27.02h-1.09v-2.94h-3.39v2.94h-1.09v-6.61h1.09v2.68h3.39v-2.68h1.09V27.02z"/>
|
||||||
|
<path d="M94.73,27.02h-1.09V21.4h-2.29v-0.98h5.67v0.98h-2.29V27.02z"/>
|
||||||
|
<path d="M99.22,29.74h-1.09v-9.33h1.04v0.95h0.03c0.16-0.23,0.35-0.42,0.55-0.56c0.2-0.14,0.41-0.25,0.61-0.32
|
||||||
|
c0.2-0.07,0.4-0.11,0.58-0.14c0.18-0.02,0.33-0.03,0.45-0.03c0.52,0,0.96,0.08,1.33,0.24c0.37,0.16,0.67,0.38,0.9,0.67
|
||||||
|
c0.23,0.29,0.4,0.64,0.51,1.06c0.11,0.42,0.16,0.88,0.16,1.39c0,0.48-0.06,0.93-0.17,1.35s-0.29,0.78-0.52,1.09
|
||||||
|
c-0.23,0.31-0.53,0.56-0.89,0.74c-0.36,0.18-0.78,0.27-1.26,0.27c-0.21,0-0.41-0.02-0.62-0.05c-0.21-0.03-0.41-0.09-0.6-0.17
|
||||||
|
c-0.19-0.08-0.38-0.18-0.54-0.3c-0.17-0.12-0.31-0.27-0.43-0.45h-0.03V29.74z M101.2,26.14c0.68,0,1.17-0.22,1.47-0.65
|
||||||
|
c0.3-0.44,0.45-1.06,0.45-1.86c0-0.37-0.03-0.7-0.1-1c-0.06-0.29-0.17-0.54-0.32-0.73c-0.15-0.19-0.35-0.34-0.6-0.45
|
||||||
|
s-0.56-0.16-0.93-0.16c-0.64,0-1.12,0.22-1.46,0.67s-0.5,1.04-0.5,1.76c0,0.73,0.17,1.32,0.5,1.76S100.55,26.14,101.2,26.14z"/>
|
||||||
|
<path d="M108.69,27.13c-1.04,0-1.81-0.29-2.33-0.87c-0.52-0.58-0.78-1.43-0.78-2.53c0-1.08,0.27-1.92,0.8-2.51
|
||||||
|
c0.53-0.6,1.3-0.89,2.31-0.89c1.04,0,1.81,0.29,2.33,0.87c0.52,0.58,0.78,1.42,0.78,2.54c0,0.53-0.07,1-0.21,1.42
|
||||||
|
c-0.14,0.42-0.34,0.78-0.6,1.08c-0.26,0.29-0.59,0.52-0.98,0.67C109.63,27.05,109.18,27.13,108.69,27.13z M108.69,26.14
|
||||||
|
c0.65,0,1.13-0.2,1.46-0.59c0.32-0.39,0.49-1,0.49-1.83c0-0.82-0.15-1.43-0.45-1.83c-0.3-0.4-0.8-0.6-1.5-0.6
|
||||||
|
c-0.62,0-1.1,0.2-1.44,0.6c-0.34,0.4-0.5,1.01-0.5,1.83c0,0.84,0.17,1.45,0.52,1.84C107.62,25.95,108.1,26.14,108.69,26.14z"/>
|
||||||
|
<path d="M113.32,27.13c-0.12,0-0.23-0.01-0.32-0.03c-0.09-0.02-0.17-0.04-0.23-0.06V26c0.07,0.03,0.12,0.04,0.16,0.05
|
||||||
|
s0.09,0.01,0.16,0.01c0.22,0,0.41-0.1,0.56-0.29c0.15-0.19,0.27-0.5,0.36-0.92c0.09-0.42,0.16-0.96,0.19-1.61
|
||||||
|
c0.04-0.66,0.06-1.45,0.06-2.38v-0.44h4.51v6.61h-1.09V21.4h-2.38v0.16c0,1.01-0.03,1.87-0.1,2.57c-0.07,0.7-0.18,1.28-0.34,1.72
|
||||||
|
c-0.16,0.45-0.36,0.77-0.62,0.97C113.99,27.03,113.68,27.13,113.32,27.13z"/>
|
||||||
|
<path d="M123.62,24.5c-0.22,0-0.39,0.04-0.52,0.12c-0.13,0.08-0.25,0.21-0.36,0.39l-1.1,2.02h-1.31l1.19-2.09
|
||||||
|
c0.1-0.18,0.21-0.32,0.32-0.41c0.11-0.1,0.22-0.16,0.31-0.21c-0.47-0.09-0.84-0.29-1.13-0.59c-0.29-0.3-0.43-0.71-0.43-1.25
|
||||||
|
c0-0.34,0.07-0.65,0.2-0.91c0.13-0.26,0.31-0.47,0.54-0.64s0.49-0.3,0.79-0.38c0.3-0.09,0.62-0.13,0.96-0.13h2.67v6.61h-1.09V24.5
|
||||||
|
H123.62z M124.67,23.51V21.4h-1.46c-0.48,0-0.84,0.08-1.08,0.25c-0.24,0.17-0.36,0.45-0.36,0.85c0,0.38,0.15,0.64,0.44,0.79
|
||||||
|
c0.29,0.15,0.67,0.22,1.11,0.22H124.67z"/>
|
||||||
|
<path d="M136.34,27.02h-1.09V21.4h-3.32v5.62h-1.09v-6.61h5.49V27.02z"/>
|
||||||
|
<path d="M143.11,27.02h-0.96v-1.21h-0.03c-0.07,0.18-0.17,0.35-0.32,0.51c-0.14,0.16-0.31,0.3-0.5,0.42
|
||||||
|
c-0.19,0.12-0.4,0.21-0.63,0.28s-0.48,0.1-0.73,0.1c-0.3,0-0.58-0.05-0.83-0.14s-0.46-0.23-0.64-0.39
|
||||||
|
c-0.18-0.17-0.32-0.37-0.41-0.59c-0.1-0.22-0.15-0.47-0.15-0.73c0-0.44,0.12-0.8,0.35-1.08c0.23-0.28,0.54-0.49,0.93-0.65
|
||||||
|
s0.82-0.27,1.31-0.34s1-0.11,1.52-0.14v-0.34c0-0.48-0.11-0.84-0.34-1.08s-0.63-0.36-1.2-0.36c-0.32,0-0.66,0.04-1.02,0.12
|
||||||
|
s-0.67,0.19-0.93,0.32l-0.19-0.89c0.27-0.15,0.6-0.27,1.01-0.38c0.41-0.1,0.82-0.16,1.26-0.16c0.49,0,0.9,0.06,1.22,0.17
|
||||||
|
c0.32,0.12,0.58,0.29,0.77,0.51s0.32,0.5,0.4,0.84c0.08,0.34,0.12,0.73,0.12,1.17V27.02z M142.03,23.91
|
||||||
|
c-0.37,0.02-0.73,0.04-1.08,0.08c-0.35,0.03-0.66,0.1-0.94,0.19s-0.5,0.22-0.67,0.38c-0.17,0.16-0.25,0.37-0.25,0.63
|
||||||
|
c0,0.27,0.09,0.5,0.28,0.68c0.19,0.19,0.48,0.28,0.89,0.28c0.52,0,0.94-0.16,1.28-0.48s0.5-0.79,0.5-1.41V23.91z"/>
|
||||||
|
<path d="M146.21,29.74h-1.09v-9.33h1.04v0.95h0.03c0.16-0.23,0.35-0.42,0.55-0.56c0.2-0.14,0.41-0.25,0.61-0.32
|
||||||
|
c0.2-0.07,0.4-0.11,0.58-0.14c0.18-0.02,0.33-0.03,0.45-0.03c0.52,0,0.96,0.08,1.33,0.24c0.37,0.16,0.67,0.38,0.9,0.67
|
||||||
|
c0.23,0.29,0.4,0.64,0.51,1.06c0.11,0.42,0.16,0.88,0.16,1.39c0,0.48-0.06,0.93-0.17,1.35s-0.29,0.78-0.52,1.09
|
||||||
|
c-0.23,0.31-0.53,0.56-0.89,0.74c-0.36,0.18-0.78,0.27-1.26,0.27c-0.21,0-0.41-0.02-0.62-0.05c-0.21-0.03-0.41-0.09-0.6-0.17
|
||||||
|
c-0.19-0.08-0.38-0.18-0.54-0.3c-0.17-0.12-0.31-0.27-0.43-0.45h-0.03V29.74z M148.19,26.14c0.68,0,1.17-0.22,1.47-0.65
|
||||||
|
c0.3-0.44,0.45-1.06,0.45-1.86c0-0.37-0.03-0.7-0.1-1c-0.06-0.29-0.17-0.54-0.32-0.73c-0.15-0.19-0.35-0.34-0.6-0.45
|
||||||
|
s-0.56-0.16-0.93-0.16c-0.64,0-1.12,0.22-1.46,0.67s-0.5,1.04-0.5,1.76c0,0.73,0.17,1.32,0.5,1.76S147.54,26.14,148.19,26.14z"/>
|
||||||
|
<path d="M157.66,27.02h-0.96v-1.21h-0.03c-0.07,0.18-0.17,0.35-0.32,0.51c-0.14,0.16-0.31,0.3-0.5,0.42
|
||||||
|
c-0.19,0.12-0.4,0.21-0.63,0.28s-0.48,0.1-0.73,0.1c-0.3,0-0.58-0.05-0.83-0.14s-0.46-0.23-0.64-0.39
|
||||||
|
c-0.18-0.17-0.32-0.37-0.41-0.59c-0.1-0.22-0.15-0.47-0.15-0.73c0-0.44,0.12-0.8,0.35-1.08c0.23-0.28,0.54-0.49,0.93-0.65
|
||||||
|
s0.82-0.27,1.31-0.34s1-0.11,1.52-0.14v-0.34c0-0.48-0.11-0.84-0.34-1.08s-0.63-0.36-1.2-0.36c-0.32,0-0.66,0.04-1.02,0.12
|
||||||
|
s-0.67,0.19-0.93,0.32l-0.19-0.89c0.27-0.15,0.6-0.27,1.01-0.38c0.41-0.1,0.82-0.16,1.26-0.16c0.49,0,0.9,0.06,1.22,0.17
|
||||||
|
c0.32,0.12,0.58,0.29,0.77,0.51s0.32,0.5,0.4,0.84c0.08,0.34,0.12,0.73,0.12,1.17V27.02z M156.58,23.91
|
||||||
|
c-0.37,0.02-0.73,0.04-1.08,0.08c-0.35,0.03-0.66,0.1-0.94,0.19s-0.5,0.22-0.67,0.38c-0.17,0.16-0.25,0.37-0.25,0.63
|
||||||
|
c0,0.27,0.09,0.5,0.28,0.68c0.19,0.19,0.48,0.28,0.89,0.28c0.52,0,0.94-0.16,1.28-0.48s0.5-0.79,0.5-1.41V23.91z"/>
|
||||||
|
<path d="M166.28,20.42l0.65,6.61h-1.04l-0.39-4.53v-0.66h-0.03l-0.21,0.66l-1.66,4.53h-0.95l-1.66-4.53l-0.21-0.66h-0.03v0.65
|
||||||
|
l-0.39,4.55h-1.04l0.65-6.61h1.22l1.94,5.4h0.03l1.88-5.4H166.28z"/>
|
||||||
|
<path d="M173.76,26.62c-0.16,0.14-0.41,0.26-0.76,0.36c-0.35,0.1-0.78,0.15-1.28,0.15c-1.17,0-2.03-0.3-2.58-0.91
|
||||||
|
c-0.56-0.6-0.84-1.45-0.84-2.53c0-0.51,0.07-0.97,0.2-1.39c0.13-0.42,0.33-0.77,0.59-1.07c0.26-0.3,0.57-0.53,0.94-0.69
|
||||||
|
c0.37-0.16,0.78-0.24,1.25-0.24c0.53,0,0.98,0.09,1.35,0.26c0.37,0.17,0.67,0.41,0.9,0.71c0.23,0.3,0.4,0.65,0.5,1.06
|
||||||
|
c0.1,0.41,0.16,0.84,0.16,1.31v0.35h-4.74c0.06,0.73,0.28,1.26,0.65,1.62c0.37,0.35,0.95,0.53,1.72,0.53
|
||||||
|
c0.42,0,0.77-0.04,1.04-0.11s0.51-0.17,0.72-0.29L173.76,26.62z M173,23.09c0.01-0.25-0.02-0.49-0.09-0.71
|
||||||
|
c-0.07-0.22-0.17-0.41-0.31-0.57c-0.14-0.16-0.32-0.29-0.53-0.38c-0.22-0.09-0.47-0.14-0.75-0.14c-0.56,0-1,0.15-1.31,0.45
|
||||||
|
c-0.31,0.3-0.5,0.75-0.57,1.33H173z"/>
|
||||||
|
<path d="M178.21,27.02h-1.09V21.4h-2.29v-0.98h5.67v0.98h-2.29V27.02z"/>
|
||||||
|
<path d="M182.71,29.74h-1.09v-9.33h1.04v0.95h0.03c0.16-0.23,0.35-0.42,0.55-0.56c0.2-0.14,0.41-0.25,0.61-0.32
|
||||||
|
c0.2-0.07,0.4-0.11,0.58-0.14c0.18-0.02,0.33-0.03,0.45-0.03c0.52,0,0.96,0.08,1.33,0.24c0.37,0.16,0.67,0.38,0.9,0.67
|
||||||
|
c0.23,0.29,0.4,0.64,0.51,1.06c0.11,0.42,0.16,0.88,0.16,1.39c0,0.48-0.06,0.93-0.17,1.35s-0.29,0.78-0.52,1.09
|
||||||
|
c-0.23,0.31-0.53,0.56-0.89,0.74c-0.36,0.18-0.78,0.27-1.26,0.27c-0.21,0-0.41-0.02-0.62-0.05c-0.21-0.03-0.41-0.09-0.6-0.17
|
||||||
|
c-0.19-0.08-0.38-0.18-0.54-0.3c-0.17-0.12-0.31-0.27-0.43-0.45h-0.03V29.74z M184.69,26.14c0.68,0,1.17-0.22,1.47-0.65
|
||||||
|
c0.3-0.44,0.45-1.06,0.45-1.86c0-0.37-0.03-0.7-0.1-1c-0.06-0.29-0.17-0.54-0.32-0.73c-0.15-0.19-0.35-0.34-0.6-0.45
|
||||||
|
s-0.56-0.16-0.93-0.16c-0.64,0-1.12,0.22-1.46,0.67s-0.5,1.04-0.5,1.76c0,0.73,0.17,1.32,0.5,1.76S184.03,26.14,184.69,26.14z"/>
|
||||||
|
<path d="M192.18,27.13c-1.04,0-1.81-0.29-2.33-0.87c-0.52-0.58-0.78-1.43-0.78-2.53c0-1.08,0.27-1.92,0.8-2.51
|
||||||
|
c0.53-0.6,1.3-0.89,2.31-0.89c1.04,0,1.81,0.29,2.33,0.87c0.52,0.58,0.78,1.42,0.78,2.54c0,0.53-0.07,1-0.21,1.42
|
||||||
|
c-0.14,0.42-0.34,0.78-0.6,1.08c-0.26,0.29-0.59,0.52-0.98,0.67C193.11,27.05,192.67,27.13,192.18,27.13z M192.18,26.14
|
||||||
|
c0.65,0,1.13-0.2,1.46-0.59c0.32-0.39,0.49-1,0.49-1.83c0-0.82-0.15-1.43-0.45-1.83c-0.3-0.4-0.8-0.6-1.5-0.6
|
||||||
|
c-0.62,0-1.1,0.2-1.44,0.6c-0.34,0.4-0.5,1.01-0.5,1.83c0,0.84,0.17,1.45,0.52,1.84C191.11,25.95,191.58,26.14,192.18,26.14z"/>
|
||||||
|
<path d="M196.97,27.02v-6.61h2.47c0.81,0,1.43,0.12,1.85,0.36c0.42,0.24,0.63,0.68,0.63,1.31c0,0.36-0.11,0.68-0.32,0.95
|
||||||
|
c-0.22,0.27-0.5,0.46-0.86,0.55v0.04c0.42,0.07,0.77,0.23,1.04,0.49c0.27,0.25,0.41,0.61,0.41,1.06c0,0.36-0.06,0.66-0.18,0.9
|
||||||
|
c-0.12,0.24-0.29,0.43-0.51,0.57s-0.48,0.24-0.79,0.3c-0.31,0.06-0.64,0.08-1,0.08H196.97z M198.06,23.14h1.35
|
||||||
|
c0.16,0,0.32-0.01,0.49-0.02s0.32-0.05,0.46-0.11c0.14-0.06,0.25-0.15,0.34-0.27c0.09-0.12,0.14-0.28,0.14-0.49
|
||||||
|
c0-0.18-0.03-0.33-0.09-0.44c-0.06-0.11-0.15-0.2-0.27-0.26c-0.12-0.06-0.25-0.1-0.41-0.12c-0.16-0.02-0.33-0.03-0.52-0.03h-1.48
|
||||||
|
V23.14z M198.06,26.04h1.67c0.41,0,0.73-0.07,0.96-0.2c0.23-0.13,0.35-0.39,0.35-0.76c0-0.23-0.05-0.42-0.15-0.55
|
||||||
|
c-0.1-0.13-0.23-0.24-0.38-0.31c-0.16-0.07-0.33-0.12-0.51-0.14s-0.37-0.03-0.55-0.03h-1.39V26.04z"/>
|
||||||
|
<path d="M71.11,44.64c0.22-0.19,0.46-0.43,0.69-0.73c0.24-0.3,0.41-0.62,0.52-0.95l0.05-0.18l-2.71-6.81h1.18l2.1,5.44l1.76-5.44
|
||||||
|
h1.1l-2.42,7.01c-0.08,0.24-0.18,0.48-0.3,0.73c-0.12,0.24-0.25,0.47-0.38,0.68c-0.13,0.21-0.28,0.4-0.43,0.57
|
||||||
|
c-0.15,0.17-0.3,0.3-0.43,0.41L71.11,44.64z"/>
|
||||||
|
<path d="M81.73,42.34c-0.15,0.07-0.36,0.14-0.65,0.22c-0.29,0.08-0.69,0.12-1.21,0.12c-0.56,0-1.05-0.08-1.46-0.24
|
||||||
|
c-0.41-0.16-0.75-0.39-1.02-0.69s-0.48-0.67-0.61-1.09c-0.13-0.42-0.2-0.9-0.2-1.44c0-1.06,0.29-1.89,0.86-2.47
|
||||||
|
c0.57-0.59,1.37-0.88,2.39-0.88c0.48,0,0.86,0.04,1.13,0.11c0.27,0.07,0.49,0.13,0.66,0.17l-0.21,0.93
|
||||||
|
c-0.16-0.05-0.36-0.1-0.6-0.16s-0.51-0.08-0.84-0.08c-0.69,0-1.23,0.18-1.63,0.55s-0.6,0.94-0.6,1.72c0,0.89,0.21,1.54,0.63,1.96
|
||||||
|
c0.42,0.42,1.03,0.62,1.81,0.62c0.26,0,0.51-0.03,0.75-0.08c0.24-0.05,0.43-0.11,0.59-0.18L81.73,42.34z"/>
|
||||||
|
<path d="M85.81,42.57h-1.09v-5.62h-2.29v-0.98h5.67v0.98h-2.29V42.57z"/>
|
||||||
|
<path d="M91.94,42.67c-1.04,0-1.81-0.29-2.33-0.87c-0.52-0.58-0.78-1.43-0.78-2.53c0-1.08,0.27-1.92,0.8-2.51
|
||||||
|
c0.53-0.6,1.3-0.89,2.31-0.89c1.04,0,1.81,0.29,2.33,0.87c0.52,0.58,0.78,1.42,0.78,2.54c0,0.53-0.07,1-0.21,1.42
|
||||||
|
c-0.14,0.42-0.34,0.78-0.6,1.08c-0.26,0.29-0.59,0.52-0.98,0.67C92.87,42.6,92.43,42.67,91.94,42.67z M91.94,41.69
|
||||||
|
c0.65,0,1.13-0.2,1.46-0.59c0.32-0.39,0.49-1,0.49-1.83c0-0.82-0.15-1.43-0.45-1.83c-0.3-0.4-0.8-0.6-1.5-0.6
|
||||||
|
c-0.62,0-1.1,0.2-1.44,0.6c-0.34,0.4-0.5,1.01-0.5,1.83c0,0.84,0.17,1.45,0.52,1.84C90.87,41.5,91.34,41.69,91.94,41.69z"/>
|
||||||
|
<path d="M96.73,42.57v-6.61h1.09v4.21l-0.04,0.87h0.01l0.43-0.69l3.08-4.39h1.04v6.61h-1.09v-4.31l0.04-0.76h-0.01l-0.4,0.65
|
||||||
|
l-3.11,4.43H96.73z M101.63,33.11c-0.02,0.29-0.08,0.54-0.19,0.75s-0.26,0.39-0.44,0.54c-0.18,0.14-0.4,0.25-0.64,0.32
|
||||||
|
c-0.25,0.07-0.5,0.1-0.77,0.1c-0.28,0-0.54-0.04-0.78-0.1c-0.24-0.07-0.45-0.17-0.63-0.32c-0.18-0.14-0.33-0.32-0.45-0.54
|
||||||
|
c-0.12-0.22-0.18-0.47-0.19-0.75l0.92-0.1c0.04,0.31,0.17,0.54,0.37,0.7c0.2,0.16,0.46,0.23,0.76,0.23s0.55-0.08,0.76-0.23
|
||||||
|
c0.2-0.16,0.33-0.39,0.37-0.7L101.63,33.11z"/>
|
||||||
|
<path d="M109.2,42.57h-1.09v-2.6c-0.04,0.01-0.12,0.03-0.22,0.05c-0.1,0.03-0.23,0.05-0.38,0.08s-0.31,0.06-0.5,0.08
|
||||||
|
c-0.19,0.02-0.38,0.03-0.58,0.03c-0.35,0-0.68-0.03-0.98-0.1c-0.3-0.07-0.56-0.18-0.77-0.34c-0.22-0.16-0.38-0.36-0.51-0.6
|
||||||
|
c-0.12-0.25-0.18-0.55-0.18-0.9v-2.31h1.09v1.94c0,0.27,0.04,0.49,0.12,0.65c0.08,0.17,0.19,0.3,0.33,0.4
|
||||||
|
c0.14,0.1,0.31,0.17,0.52,0.21c0.2,0.04,0.43,0.06,0.67,0.06c0.33,0,0.61-0.03,0.85-0.08c0.24-0.05,0.42-0.1,0.54-0.14v-3.04h1.09
|
||||||
|
V42.57z"/>
|
||||||
|
<path d="M111.27,42.57v-6.61h1.09v4.21l-0.04,0.87h0.01l0.43-0.69l3.08-4.39h1.04v6.61h-1.09v-4.31l0.04-0.76h-0.01l-0.4,0.65
|
||||||
|
l-3.11,4.43H111.27z"/>
|
||||||
|
<path d="M118.95,42.57v-6.61h2.47c0.81,0,1.43,0.12,1.85,0.36c0.42,0.24,0.63,0.68,0.63,1.31c0,0.36-0.11,0.68-0.32,0.95
|
||||||
|
c-0.22,0.27-0.5,0.46-0.86,0.55v0.04c0.42,0.07,0.77,0.23,1.04,0.49c0.27,0.25,0.41,0.61,0.41,1.06c0,0.36-0.06,0.66-0.18,0.9
|
||||||
|
c-0.12,0.24-0.29,0.43-0.51,0.57s-0.48,0.24-0.79,0.3c-0.31,0.06-0.64,0.08-1,0.08H118.95z M120.04,38.68h1.35
|
||||||
|
c0.16,0,0.32-0.01,0.49-0.02s0.32-0.05,0.46-0.11c0.14-0.06,0.25-0.15,0.34-0.27c0.09-0.12,0.14-0.28,0.14-0.49
|
||||||
|
c0-0.18-0.03-0.33-0.09-0.44c-0.06-0.11-0.15-0.2-0.27-0.26c-0.12-0.06-0.25-0.1-0.41-0.12c-0.16-0.02-0.33-0.03-0.52-0.03h-1.48
|
||||||
|
V38.68z M120.04,41.59h1.67c0.41,0,0.73-0.07,0.96-0.2c0.23-0.13,0.35-0.39,0.35-0.76c0-0.23-0.05-0.42-0.15-0.55
|
||||||
|
c-0.1-0.13-0.23-0.24-0.38-0.31c-0.16-0.07-0.33-0.12-0.51-0.14s-0.37-0.03-0.55-0.03h-1.39V41.59z"/>
|
||||||
|
<path d="M128.57,42.67c-1.04,0-1.81-0.29-2.33-0.87c-0.52-0.58-0.78-1.43-0.78-2.53c0-1.08,0.27-1.92,0.8-2.51
|
||||||
|
c0.53-0.6,1.3-0.89,2.31-0.89c1.04,0,1.81,0.29,2.33,0.87c0.52,0.58,0.78,1.42,0.78,2.54c0,0.53-0.07,1-0.21,1.42
|
||||||
|
c-0.14,0.42-0.34,0.78-0.6,1.08c-0.26,0.29-0.59,0.52-0.98,0.67C129.5,42.6,129.06,42.67,128.57,42.67z M128.57,41.69
|
||||||
|
c0.65,0,1.13-0.2,1.46-0.59c0.32-0.39,0.49-1,0.49-1.83c0-0.82-0.15-1.43-0.45-1.83c-0.3-0.4-0.8-0.6-1.5-0.6
|
||||||
|
c-0.62,0-1.1,0.2-1.44,0.6c-0.34,0.4-0.5,1.01-0.5,1.83c0,0.84,0.17,1.45,0.52,1.84C127.5,41.5,127.97,41.69,128.57,41.69z"/>
|
||||||
|
<path d="M134.45,42.57h-1.09v-6.61h4.34v0.98h-3.25V42.57z"/>
|
||||||
|
<path d="M141.43,42.67c-1.04,0-1.81-0.29-2.33-0.87c-0.52-0.58-0.78-1.43-0.78-2.53c0-1.08,0.27-1.92,0.8-2.51
|
||||||
|
c0.53-0.6,1.3-0.89,2.31-0.89c1.04,0,1.81,0.29,2.33,0.87c0.52,0.58,0.78,1.42,0.78,2.54c0,0.53-0.07,1-0.21,1.42
|
||||||
|
c-0.14,0.42-0.34,0.78-0.6,1.08c-0.26,0.29-0.59,0.52-0.98,0.67C142.36,42.6,141.92,42.67,141.43,42.67z M141.43,41.69
|
||||||
|
c0.65,0,1.13-0.2,1.46-0.59c0.32-0.39,0.49-1,0.49-1.83c0-0.82-0.15-1.43-0.45-1.83c-0.3-0.4-0.8-0.6-1.5-0.6
|
||||||
|
c-0.62,0-1.1,0.2-1.44,0.6c-0.34,0.4-0.5,1.01-0.5,1.83c0,0.84,0.17,1.45,0.52,1.84C140.36,41.5,140.83,41.69,141.43,41.69z"/>
|
||||||
|
<path d="M152.3,42.65c-0.09,0.02-0.17,0.03-0.25,0.03s-0.16,0-0.24,0c-0.99,0-1.73-0.29-2.22-0.86c-0.49-0.57-0.73-1.43-0.73-2.56
|
||||||
|
c0-1.08,0.25-1.92,0.75-2.51c0.5-0.59,1.24-0.89,2.22-0.89c0.08,0,0.16,0,0.24,0c0.08,0,0.16,0.01,0.24,0.03v-3.03h1.09v3.04
|
||||||
|
c0.16-0.03,0.33-0.04,0.49-0.04c1.01,0,1.76,0.28,2.23,0.83c0.48,0.55,0.72,1.39,0.72,2.53c0,0.54-0.06,1.02-0.19,1.44
|
||||||
|
c-0.13,0.43-0.31,0.79-0.56,1.09c-0.25,0.3-0.56,0.53-0.93,0.69c-0.37,0.16-0.8,0.24-1.28,0.24h-0.48v2.62h-1.09V42.65z
|
||||||
|
M152.3,36.83c-0.12-0.03-0.28-0.04-0.47-0.04c-0.28,0-0.54,0.04-0.76,0.14s-0.41,0.24-0.57,0.43c-0.16,0.2-0.27,0.46-0.36,0.77
|
||||||
|
c-0.08,0.32-0.12,0.7-0.12,1.16c0,0.85,0.16,1.48,0.47,1.87s0.8,0.58,1.44,0.58c0.06,0,0.12,0,0.17,0s0.12,0,0.19-0.01V36.83z
|
||||||
|
M153.39,41.74h0.47c0.57,0,1.01-0.2,1.33-0.6c0.32-0.4,0.48-1.05,0.48-1.97c0-0.85-0.15-1.47-0.46-1.83s-0.8-0.55-1.47-0.55
|
||||||
|
c-0.05,0-0.11,0-0.17,0.01c-0.06,0-0.12,0.01-0.18,0.02V41.74z"/>
|
||||||
|
<path d="M159.04,44.64c0.22-0.19,0.46-0.43,0.69-0.73c0.24-0.3,0.41-0.62,0.52-0.95l0.05-0.18l-2.71-6.81h1.18l2.1,5.44l1.76-5.44
|
||||||
|
h1.1l-2.42,7.01c-0.08,0.24-0.18,0.48-0.3,0.73c-0.12,0.24-0.25,0.47-0.38,0.68c-0.13,0.21-0.28,0.4-0.43,0.57
|
||||||
|
c-0.15,0.17-0.3,0.3-0.43,0.41L159.04,44.64z"/>
|
||||||
|
<path d="M170.48,42.57h-1.09v-2.94h-3.39v2.94h-1.09v-6.61h1.09v2.68h3.39v-2.68h1.09V42.57z"/>
|
||||||
|
<path d="M173.64,38.87h0.35l2.5-2.9h1.4l-2.81,3.1c0.1,0.05,0.19,0.11,0.27,0.19c0.07,0.07,0.15,0.16,0.24,0.27l2.63,3.06h-1.48
|
||||||
|
l-2-2.45c-0.13-0.16-0.24-0.28-0.32-0.34s-0.21-0.09-0.36-0.09h-0.42v2.88h-1.09v-6.61h1.09V38.87z"/>
|
||||||
|
<path d="M185.65,44.44h-1.04v-1.87h-5.31v-6.61h1.09v5.62h3.17v-5.62h1.09v5.62h1V44.44z"/>
|
||||||
|
<path d="M187.07,42.57v-6.61h1.09v4.21l-0.04,0.87h0.01l0.43-0.69l3.08-4.39h1.04v6.61h-1.09v-4.31l0.04-0.76h-0.01l-0.4,0.65
|
||||||
|
l-3.11,4.43H187.07z"/>
|
||||||
|
<path d="M197.48,42.67c-1.04,0-1.81-0.29-2.33-0.87c-0.52-0.58-0.78-1.43-0.78-2.53c0-1.08,0.27-1.92,0.8-2.51
|
||||||
|
c0.53-0.6,1.3-0.89,2.31-0.89c1.04,0,1.81,0.29,2.33,0.87c0.52,0.58,0.78,1.42,0.78,2.54c0,0.53-0.07,1-0.21,1.42
|
||||||
|
c-0.14,0.42-0.34,0.78-0.6,1.08c-0.26,0.29-0.59,0.52-0.98,0.67C198.41,42.6,197.97,42.67,197.48,42.67z M197.48,41.69
|
||||||
|
c0.65,0,1.13-0.2,1.46-0.59c0.32-0.39,0.49-1,0.49-1.83c0-0.82-0.15-1.43-0.45-1.83c-0.3-0.4-0.8-0.6-1.5-0.6
|
||||||
|
c-0.62,0-1.1,0.2-1.44,0.6c-0.34,0.4-0.5,1.01-0.5,1.83c0,0.84,0.17,1.45,0.52,1.84C196.41,41.5,196.88,41.69,197.48,41.69z"/>
|
||||||
|
<path d="M207.84,42.57h-1.09v-2.94h-3.39v2.94h-1.09v-6.61h1.09v2.68h3.39v-2.68h1.09V42.57z"/>
|
||||||
|
<path d="M209.91,42.57v-6.61H211v4.21l-0.04,0.87h0.01l0.43-0.69l3.08-4.39h1.04v6.61h-1.09v-4.31l0.04-0.76h-0.01l-0.4,0.65
|
||||||
|
l-3.11,4.43H209.91z"/>
|
||||||
|
<path d="M218.68,45.29h-1.09v-9.33h1.04v0.95h0.03c0.16-0.23,0.35-0.42,0.55-0.56c0.2-0.14,0.41-0.25,0.61-0.32
|
||||||
|
c0.2-0.07,0.4-0.11,0.58-0.14c0.18-0.02,0.33-0.03,0.45-0.03c0.52,0,0.96,0.08,1.33,0.24c0.37,0.16,0.67,0.38,0.9,0.67
|
||||||
|
c0.23,0.29,0.4,0.64,0.51,1.06c0.11,0.42,0.16,0.88,0.16,1.39c0,0.48-0.06,0.93-0.17,1.35s-0.29,0.78-0.52,1.09
|
||||||
|
c-0.23,0.31-0.53,0.56-0.89,0.74c-0.36,0.18-0.78,0.27-1.26,0.27c-0.21,0-0.41-0.02-0.62-0.05c-0.21-0.03-0.41-0.09-0.6-0.17
|
||||||
|
c-0.19-0.08-0.38-0.18-0.54-0.3c-0.17-0.12-0.31-0.27-0.43-0.45h-0.03V45.29z M220.67,41.69c0.68,0,1.17-0.22,1.47-0.65
|
||||||
|
c0.3-0.44,0.45-1.06,0.45-1.86c0-0.37-0.03-0.7-0.1-1c-0.06-0.29-0.17-0.54-0.32-0.73c-0.15-0.19-0.35-0.34-0.6-0.45
|
||||||
|
s-0.56-0.16-0.93-0.16c-0.64,0-1.12,0.22-1.46,0.67s-0.5,1.04-0.5,1.76c0,0.73,0.17,1.32,0.5,1.76S220.01,41.69,220.67,41.69z"/>
|
||||||
|
<path d="M228.16,42.67c-1.04,0-1.81-0.29-2.33-0.87c-0.52-0.58-0.78-1.43-0.78-2.53c0-1.08,0.27-1.92,0.8-2.51
|
||||||
|
c0.53-0.6,1.3-0.89,2.31-0.89c1.04,0,1.81,0.29,2.33,0.87c0.52,0.58,0.78,1.42,0.78,2.54c0,0.53-0.07,1-0.21,1.42
|
||||||
|
c-0.14,0.42-0.34,0.78-0.6,1.08c-0.26,0.29-0.59,0.52-0.98,0.67C229.09,42.6,228.65,42.67,228.16,42.67z M228.16,41.69
|
||||||
|
c0.65,0,1.13-0.2,1.46-0.59c0.32-0.39,0.49-1,0.49-1.83c0-0.82-0.15-1.43-0.45-1.83c-0.3-0.4-0.8-0.6-1.5-0.6
|
||||||
|
c-0.62,0-1.1,0.2-1.44,0.6c-0.34,0.4-0.5,1.01-0.5,1.83c0,0.84,0.17,1.45,0.52,1.84C227.09,41.5,227.56,41.69,228.16,41.69z"/>
|
||||||
|
<path d="M232.95,42.57v-6.61h2.47c0.81,0,1.43,0.12,1.85,0.36c0.42,0.24,0.63,0.68,0.63,1.31c0,0.36-0.11,0.68-0.32,0.95
|
||||||
|
c-0.22,0.27-0.5,0.46-0.86,0.55v0.04c0.42,0.07,0.77,0.23,1.04,0.49c0.27,0.25,0.41,0.61,0.41,1.06c0,0.36-0.06,0.66-0.18,0.9
|
||||||
|
c-0.12,0.24-0.29,0.43-0.51,0.57s-0.48,0.24-0.79,0.3c-0.31,0.06-0.64,0.08-1,0.08H232.95z M234.04,38.68h1.35
|
||||||
|
c0.16,0,0.32-0.01,0.49-0.02s0.32-0.05,0.46-0.11c0.14-0.06,0.25-0.15,0.34-0.27c0.09-0.12,0.14-0.28,0.14-0.49
|
||||||
|
c0-0.18-0.03-0.33-0.09-0.44c-0.06-0.11-0.15-0.2-0.27-0.26c-0.12-0.06-0.25-0.1-0.41-0.12c-0.16-0.02-0.33-0.03-0.52-0.03h-1.48
|
||||||
|
V38.68z M234.04,41.59h1.67c0.41,0,0.73-0.07,0.96-0.2c0.23-0.13,0.35-0.39,0.35-0.76c0-0.23-0.05-0.42-0.15-0.55
|
||||||
|
c-0.1-0.13-0.23-0.24-0.38-0.31c-0.16-0.07-0.33-0.12-0.51-0.14s-0.37-0.03-0.55-0.03h-1.39V41.59z"/>
|
||||||
|
<path d="M244.54,42.57h-0.96v-1.21h-0.03c-0.07,0.18-0.17,0.35-0.32,0.51c-0.14,0.16-0.31,0.3-0.5,0.42
|
||||||
|
c-0.19,0.12-0.4,0.21-0.63,0.28s-0.48,0.1-0.73,0.1c-0.3,0-0.58-0.05-0.83-0.14s-0.46-0.23-0.64-0.39
|
||||||
|
c-0.18-0.17-0.32-0.37-0.41-0.59c-0.1-0.22-0.15-0.47-0.15-0.73c0-0.44,0.12-0.8,0.35-1.08c0.23-0.28,0.54-0.49,0.93-0.65
|
||||||
|
s0.82-0.27,1.31-0.34s1-0.11,1.52-0.14v-0.34c0-0.48-0.11-0.84-0.34-1.08s-0.63-0.36-1.2-0.36c-0.32,0-0.66,0.04-1.02,0.12
|
||||||
|
s-0.67,0.19-0.93,0.32l-0.19-0.89c0.27-0.15,0.6-0.27,1.01-0.38c0.41-0.1,0.82-0.16,1.26-0.16c0.49,0,0.9,0.06,1.22,0.17
|
||||||
|
c0.32,0.12,0.58,0.29,0.77,0.51s0.32,0.5,0.4,0.84c0.08,0.34,0.12,0.73,0.12,1.17V42.57z M243.46,39.46
|
||||||
|
c-0.37,0.02-0.73,0.04-1.08,0.08c-0.35,0.03-0.66,0.1-0.94,0.19s-0.5,0.22-0.67,0.38c-0.17,0.16-0.25,0.37-0.25,0.63
|
||||||
|
c0,0.27,0.09,0.5,0.28,0.68c0.19,0.19,0.48,0.28,0.89,0.28c0.52,0,0.94-0.16,1.28-0.48s0.5-0.79,0.5-1.41V39.46z"/>
|
||||||
|
<path d="M252.12,42.57h-1.09v-2.94h-3.39v2.94h-1.09v-6.61h1.09v2.68h3.39v-2.68h1.09V42.57z"/>
|
||||||
|
<path d="M254.2,42.57v-6.61h1.09v4.21l-0.04,0.87h0.01l0.43-0.69l3.08-4.39h1.04v6.61h-1.09v-4.31l0.04-0.76h-0.01l-0.4,0.65
|
||||||
|
l-3.11,4.43H254.2z"/>
|
||||||
|
<path d="M264.65,40.04c-0.22,0-0.39,0.04-0.52,0.12c-0.13,0.08-0.25,0.21-0.36,0.39l-1.1,2.02h-1.31l1.19-2.09
|
||||||
|
c0.1-0.18,0.21-0.32,0.32-0.41c0.11-0.1,0.22-0.16,0.31-0.21c-0.47-0.09-0.84-0.29-1.13-0.59c-0.29-0.3-0.43-0.71-0.43-1.25
|
||||||
|
c0-0.34,0.07-0.65,0.2-0.91c0.13-0.26,0.31-0.47,0.54-0.64s0.49-0.3,0.79-0.38c0.3-0.09,0.62-0.13,0.96-0.13h2.67v6.61h-1.09
|
||||||
|
v-2.53H264.65z M265.7,39.06v-2.11h-1.46c-0.48,0-0.84,0.08-1.08,0.25c-0.24,0.17-0.36,0.45-0.36,0.85c0,0.38,0.15,0.64,0.44,0.79
|
||||||
|
c0.29,0.15,0.67,0.22,1.11,0.22H265.7z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 33 KiB |
|
|
@ -83,7 +83,7 @@ button:focus-visible {
|
||||||
|
|
||||||
/* Фон скроллбара */
|
/* Фон скроллбара */
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background: var(--scrollbar-track-color, #f1f1f1);
|
background: var(--scrollbar-track-color, #025EA1);
|
||||||
/* Цвет фона */
|
/* Цвет фона */
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
/* Скругление углов */
|
/* Скругление углов */
|
||||||
|
|
@ -91,7 +91,7 @@ button:focus-visible {
|
||||||
|
|
||||||
/* Ползунок */
|
/* Ползунок */
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #3d74c7;
|
background: #D4EFFC;
|
||||||
/* Основной цвет */
|
/* Основной цвет */
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
/* Скругляем края */
|
/* Скругляем края */
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
import svgr from 'vite-plugin-svgr'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [
|
||||||
|
react(),
|
||||||
|
svgr()
|
||||||
|
],
|
||||||
server: {
|
server: {
|
||||||
host: true,
|
host: true,
|
||||||
allowedHosts: ['dev.msf.enode']
|
allowedHosts: ['dev.msf.enode', 'demo-msf.kis-npo.ru']
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Loading…
Reference in New Issue