mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
synced 2025-11-01 03:07:49 +00:00
Compare commits
38 Commits
v2.6.0
...
release/v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b79a0b74c | ||
|
|
c158f0aef8 | ||
|
|
4e5c6a9426 | ||
|
|
7ad184cb48 | ||
|
|
41a7d5d0a8 | ||
|
|
78c48e004c | ||
|
|
7106d61881 | ||
|
|
8ead4c4708 | ||
|
|
52ca7d3503 | ||
|
|
7d504da0a8 | ||
|
|
c6dee2252b | ||
|
|
680c4a9ec4 | ||
|
|
3887d57fa4 | ||
|
|
d733daed9d | ||
|
|
de8651ab52 | ||
|
|
0ce641d10b | ||
|
|
316224b424 | ||
|
|
cf9bbce284 | ||
|
|
6eae6c046e | ||
|
|
837a430228 | ||
|
|
71431f8fb5 | ||
|
|
0c7cd1f299 | ||
|
|
674682e919 | ||
|
|
a5ca8115af | ||
|
|
d4338fce42 | ||
|
|
14e8135f81 | ||
|
|
e925f07505 | ||
|
|
b792b51bd0 | ||
|
|
fb64813b2a | ||
|
|
b16e0e33ab | ||
|
|
818921e4a2 | ||
|
|
6c437459ca | ||
|
|
b276901874 | ||
|
|
53a3de1ebc | ||
|
|
2d35747e75 | ||
|
|
71feebea6d | ||
|
|
c8c75e7a70 | ||
|
|
7b2263e9a5 |
8
.github/workflows/cleanup.yml
vendored
8
.github/workflows/cleanup.yml
vendored
@@ -17,4 +17,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-')
|
export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-')
|
||||||
curl -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owgw-ui/$PR_BRANCH_TAG"
|
|
||||||
|
if [[ ! $PR_BRANCH_TAG =~ (main|master|release-*) ]]; then
|
||||||
|
echo "PR branch is $PR_BRANCH_TAG, deleting Docker image"
|
||||||
|
curl -s -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owgw-ui/$PR_BRANCH_TAG"
|
||||||
|
else
|
||||||
|
echo "PR branch is $PR_BRANCH_TAG, not deleting Docker image"
|
||||||
|
fi
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:14-alpine3.11 AS build
|
FROM node:18.7.0-alpine3.15 AS build
|
||||||
|
|
||||||
COPY package.json package-lock.json /
|
COPY package.json package-lock.json /
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ COPY . .
|
|||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM nginx:1.20.1-alpine AS runtime
|
FROM nginx:1.22.0-alpine AS runtime
|
||||||
|
|
||||||
COPY --from=build /build/ /usr/share/nginx/html/
|
COPY --from=build /build/ /usr/share/nginx/html/
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ fullnameOverride: ""
|
|||||||
images:
|
images:
|
||||||
owgwui:
|
owgwui:
|
||||||
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
|
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
|
||||||
tag: v2.6.0
|
tag: v2.7.0
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|||||||
347
package-lock.json
generated
347
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.6.29",
|
"version": "2.7.0(8)",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.6.29",
|
"version": "2.7.0(8)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coreui/coreui": "^3.4.0",
|
"@coreui/coreui": "^3.4.0",
|
||||||
"@coreui/icons": "^2.0.1",
|
"@coreui/icons": "^2.0.1",
|
||||||
@@ -2069,6 +2069,64 @@
|
|||||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/set-array": "^1.0.1",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/set-array": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/source-map": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.0",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
|
"version": "1.4.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||||
|
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
|
||||||
|
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.0.3",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -7320,60 +7378,6 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/html-minifier-terser/node_modules/acorn": {
|
|
||||||
"version": "8.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
|
|
||||||
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"acorn": "bin/acorn"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/html-minifier-terser/node_modules/source-map": {
|
|
||||||
"version": "0.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
|
||||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/html-minifier-terser/node_modules/terser": {
|
|
||||||
"version": "5.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
|
|
||||||
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"commander": "^2.20.0",
|
|
||||||
"source-map": "~0.7.2",
|
|
||||||
"source-map-support": "~0.5.20"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"terser": "bin/terser"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"acorn": "^8.5.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"acorn": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": {
|
|
||||||
"version": "2.20.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/html-parse-stringify": {
|
"node_modules/html-parse-stringify": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||||
@@ -9201,9 +9205,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/moment": {
|
"node_modules/moment": {
|
||||||
"version": "2.29.3",
|
"version": "2.29.4",
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||||
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==",
|
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
@@ -13073,6 +13077,24 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/terser": {
|
||||||
|
"version": "5.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
|
||||||
|
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/source-map": "^0.3.2",
|
||||||
|
"acorn": "^8.5.0",
|
||||||
|
"commander": "^2.20.0",
|
||||||
|
"source-map-support": "~0.5.20"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"terser": "bin/terser"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/terser-webpack-plugin": {
|
"node_modules/terser-webpack-plugin": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
|
||||||
@@ -13107,26 +13129,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser-webpack-plugin/node_modules/acorn": {
|
|
||||||
"version": "8.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
|
|
||||||
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"acorn": "bin/acorn"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/terser-webpack-plugin/node_modules/commander": {
|
|
||||||
"version": "2.20.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/terser-webpack-plugin/node_modules/has-flag": {
|
"node_modules/terser-webpack-plugin/node_modules/has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
@@ -13192,39 +13194,23 @@
|
|||||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser-webpack-plugin/node_modules/terser": {
|
"node_modules/terser/node_modules/acorn": {
|
||||||
"version": "5.10.0",
|
"version": "8.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
|
||||||
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
|
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
|
||||||
"commander": "^2.20.0",
|
|
||||||
"source-map": "~0.7.2",
|
|
||||||
"source-map-support": "~0.5.20"
|
|
||||||
},
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"terser": "bin/terser"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=0.4.0"
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"acorn": "^8.5.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"acorn": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser-webpack-plugin/node_modules/terser/node_modules/source-map": {
|
"node_modules/terser/node_modules/commander": {
|
||||||
"version": "0.7.3",
|
"version": "2.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"engines": {
|
|
||||||
"node": ">= 8"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/text-table": {
|
"node_modules/text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
@@ -16414,6 +16400,55 @@
|
|||||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@jridgewell/gen-mapping": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/set-array": "^1.0.1",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@jridgewell/resolve-uri": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@jridgewell/set-array": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@jridgewell/source-map": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.0",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@jridgewell/sourcemap-codec": {
|
||||||
|
"version": "1.4.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||||
|
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
|
||||||
|
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.0.3",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@nodelib/fs.scandir": {
|
"@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -20424,41 +20459,6 @@
|
|||||||
"param-case": "^3.0.4",
|
"param-case": "^3.0.4",
|
||||||
"relateurl": "^0.2.7",
|
"relateurl": "^0.2.7",
|
||||||
"terser": "^5.10.0"
|
"terser": "^5.10.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"acorn": {
|
|
||||||
"version": "8.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
|
|
||||||
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"source-map": {
|
|
||||||
"version": "0.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
|
||||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"terser": {
|
|
||||||
"version": "5.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
|
|
||||||
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"commander": "^2.20.0",
|
|
||||||
"source-map": "~0.7.2",
|
|
||||||
"source-map-support": "~0.5.20"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"commander": {
|
|
||||||
"version": "2.20.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"html-parse-stringify": {
|
"html-parse-stringify": {
|
||||||
@@ -21815,9 +21815,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"moment": {
|
"moment": {
|
||||||
"version": "2.29.3",
|
"version": "2.29.4",
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||||
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw=="
|
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
||||||
},
|
},
|
||||||
"mrmime": {
|
"mrmime": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -24722,6 +24722,32 @@
|
|||||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"terser": {
|
||||||
|
"version": "5.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
|
||||||
|
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/source-map": "^0.3.2",
|
||||||
|
"acorn": "^8.5.0",
|
||||||
|
"commander": "^2.20.0",
|
||||||
|
"source-map-support": "~0.5.20"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": {
|
||||||
|
"version": "8.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
|
||||||
|
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"terser-webpack-plugin": {
|
"terser-webpack-plugin": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
|
||||||
@@ -24735,20 +24761,6 @@
|
|||||||
"terser": "^5.7.2"
|
"terser": "^5.7.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": {
|
|
||||||
"version": "8.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
|
|
||||||
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"commander": {
|
|
||||||
"version": "2.20.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"has-flag": {
|
"has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
@@ -24791,25 +24803,6 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"has-flag": "^4.0.0"
|
"has-flag": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"terser": {
|
|
||||||
"version": "5.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
|
|
||||||
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"commander": "^2.20.0",
|
|
||||||
"source-map": "~0.7.2",
|
|
||||||
"source-map-support": "~0.5.20"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"source-map": {
|
|
||||||
"version": "0.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
|
||||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.6.29",
|
"version": "2.7.0(8)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coreui/coreui": "^3.4.0",
|
"@coreui/coreui": "^3.4.0",
|
||||||
"@coreui/icons": "^2.0.1",
|
"@coreui/icons": "^2.0.1",
|
||||||
|
|||||||
@@ -326,6 +326,7 @@
|
|||||||
"device": {
|
"device": {
|
||||||
"add_to_blacklist": "Gerät zur Blacklist hinzufügen",
|
"add_to_blacklist": "Gerät zur Blacklist hinzufügen",
|
||||||
"all_devices": "Alle Geräte",
|
"all_devices": "Alle Geräte",
|
||||||
|
"already_running_command": "Gerät führt bereits einen Befehl aus, bitte versuchen Sie es später erneut",
|
||||||
"blacklisted_on": "Datum",
|
"blacklisted_on": "Datum",
|
||||||
"capabilities": "Fähigkeiten",
|
"capabilities": "Fähigkeiten",
|
||||||
"certificate_explanation": "Zertifikate der angeschlossenen Geräte",
|
"certificate_explanation": "Zertifikate der angeschlossenen Geräte",
|
||||||
@@ -337,6 +338,7 @@
|
|||||||
"error_fetching_devices": "Fehler beim Abrufen von Geräten: {{error}}",
|
"error_fetching_devices": "Fehler beim Abrufen von Geräten: {{error}}",
|
||||||
"firmware_count_explanation": "Dies ist die Gesamtzahl der Geräte, die diesem Firmware-Server hinzugefügt wurden, einschließlich der Geräte, die derzeit nicht auf den zugehörigen Gateway-Server verweisen.",
|
"firmware_count_explanation": "Dies ist die Gesamtzahl der Geräte, die diesem Firmware-Server hinzugefügt wurden, einschließlich der Geräte, die derzeit nicht auf den zugehörigen Gateway-Server verweisen.",
|
||||||
"health_explanation": "Zustand der verbundenen Geräte ((Geräte = 100 % * 100 + Geräte > 90 % * 95 + Geräte > 60 % * 75 + Geräte < 60 % * 35) / Verbundene Geräte)",
|
"health_explanation": "Zustand der verbundenen Geräte ((Geräte = 100 % * 100 + Geräte > 90 % * 95 + Geräte > 60 % * 75 + Geräte < 60 % * 35) / Verbundene Geräte)",
|
||||||
|
"mac_not_found": "Seriennummer nicht gefunden, Sie werden zur Seite „Geräte“ weitergeleitet",
|
||||||
"memory_explanation": "Anzahl verbundener Geräte mit entsprechendem belegtem Speicher %",
|
"memory_explanation": "Anzahl verbundener Geräte mit entsprechendem belegtem Speicher %",
|
||||||
"remove_from_blacklist": "Von der schwarzen Liste entfernen",
|
"remove_from_blacklist": "Von der schwarzen Liste entfernen",
|
||||||
"success_added_blacklist": "Gerät erfolgreich zur Blacklist hinzugefügt!",
|
"success_added_blacklist": "Gerät erfolgreich zur Blacklist hinzugefügt!",
|
||||||
|
|||||||
@@ -326,6 +326,7 @@
|
|||||||
"device": {
|
"device": {
|
||||||
"add_to_blacklist": "Add Device To Blacklist",
|
"add_to_blacklist": "Add Device To Blacklist",
|
||||||
"all_devices": "All Devices",
|
"all_devices": "All Devices",
|
||||||
|
"already_running_command": "Device is already executing a command, please try later",
|
||||||
"blacklisted_on": "Date",
|
"blacklisted_on": "Date",
|
||||||
"capabilities": "Capabilities",
|
"capabilities": "Capabilities",
|
||||||
"certificate_explanation": "Certificates of connected devices",
|
"certificate_explanation": "Certificates of connected devices",
|
||||||
@@ -337,6 +338,7 @@
|
|||||||
"error_fetching_devices": "Error while fetching devices: {{error}}",
|
"error_fetching_devices": "Error while fetching devices: {{error}}",
|
||||||
"firmware_count_explanation": "This is the total amount of devices that were added to this firmware server, including devices not currently pointing at the related gateway server.",
|
"firmware_count_explanation": "This is the total amount of devices that were added to this firmware server, including devices not currently pointing at the related gateway server.",
|
||||||
"health_explanation": "Health of connected devices ((Devices=100% * 100 + Devices>90% * 95 + Devices>60% * 75 + Devices<60% * 35) / ConnectedDevices)",
|
"health_explanation": "Health of connected devices ((Devices=100% * 100 + Devices>90% * 95 + Devices>60% * 75 + Devices<60% * 35) / ConnectedDevices)",
|
||||||
|
"mac_not_found": "Serial number not found, redirecting you to the Devices page",
|
||||||
"memory_explanation": "Amount of connected devices with corresponding memory used percentage",
|
"memory_explanation": "Amount of connected devices with corresponding memory used percentage",
|
||||||
"remove_from_blacklist": "Remove from blacklist",
|
"remove_from_blacklist": "Remove from blacklist",
|
||||||
"success_added_blacklist": "Device successfully added to blacklist!",
|
"success_added_blacklist": "Device successfully added to blacklist!",
|
||||||
|
|||||||
@@ -326,6 +326,7 @@
|
|||||||
"device": {
|
"device": {
|
||||||
"add_to_blacklist": "Agregar dispositivo a la lista negra",
|
"add_to_blacklist": "Agregar dispositivo a la lista negra",
|
||||||
"all_devices": "Todos los dispositivos",
|
"all_devices": "Todos los dispositivos",
|
||||||
|
"already_running_command": "El dispositivo ya está ejecutando un comando, intente más tarde",
|
||||||
"blacklisted_on": "Fecha",
|
"blacklisted_on": "Fecha",
|
||||||
"capabilities": "capacidades",
|
"capabilities": "capacidades",
|
||||||
"certificate_explanation": "Certificados de dispositivos conectados",
|
"certificate_explanation": "Certificados de dispositivos conectados",
|
||||||
@@ -337,6 +338,7 @@
|
|||||||
"error_fetching_devices": "Error al recuperar dispositivos: {{error}}",
|
"error_fetching_devices": "Error al recuperar dispositivos: {{error}}",
|
||||||
"firmware_count_explanation": "Esta es la cantidad total de dispositivos que se agregaron a este servidor de firmware, incluidos los dispositivos que actualmente no apuntan al servidor de puerta de enlace relacionado.",
|
"firmware_count_explanation": "Esta es la cantidad total de dispositivos que se agregaron a este servidor de firmware, incluidos los dispositivos que actualmente no apuntan al servidor de puerta de enlace relacionado.",
|
||||||
"health_explanation": "Estado de los dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos conectados)",
|
"health_explanation": "Estado de los dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos conectados)",
|
||||||
|
"mac_not_found": "Número de serie no encontrado, lo redirige a la página Dispositivos",
|
||||||
"memory_explanation": "Cantidad de dispositivos conectados con la memoria correspondiente utilizada%",
|
"memory_explanation": "Cantidad de dispositivos conectados con la memoria correspondiente utilizada%",
|
||||||
"remove_from_blacklist": "ELIMINAR DE LA LISTA NEGRA",
|
"remove_from_blacklist": "ELIMINAR DE LA LISTA NEGRA",
|
||||||
"success_added_blacklist": "¡Dispositivo agregado exitosamente a la lista negra!",
|
"success_added_blacklist": "¡Dispositivo agregado exitosamente a la lista negra!",
|
||||||
|
|||||||
@@ -326,6 +326,7 @@
|
|||||||
"device": {
|
"device": {
|
||||||
"add_to_blacklist": "Ajouter un appareil à la liste noire",
|
"add_to_blacklist": "Ajouter un appareil à la liste noire",
|
||||||
"all_devices": "Tous les dispositifs",
|
"all_devices": "Tous les dispositifs",
|
||||||
|
"already_running_command": "L'appareil exécute déjà une commande, veuillez réessayer plus tard",
|
||||||
"blacklisted_on": "Rendez-vous amoureux",
|
"blacklisted_on": "Rendez-vous amoureux",
|
||||||
"capabilities": "Capacités",
|
"capabilities": "Capacités",
|
||||||
"certificate_explanation": "Certificats des appareils connectés",
|
"certificate_explanation": "Certificats des appareils connectés",
|
||||||
@@ -337,6 +338,7 @@
|
|||||||
"error_fetching_devices": "Erreur lors de la récupération des appareils : {{error}}",
|
"error_fetching_devices": "Erreur lors de la récupération des appareils : {{error}}",
|
||||||
"firmware_count_explanation": "Il s'agit du nombre total d'appareils qui ont été ajoutés à ce serveur de micrologiciel, y compris les appareils qui ne pointent pas actuellement vers le serveur de passerelle associé.",
|
"firmware_count_explanation": "Il s'agit du nombre total d'appareils qui ont été ajoutés à ce serveur de micrologiciel, y compris les appareils qui ne pointent pas actuellement vers le serveur de passerelle associé.",
|
||||||
"health_explanation": "Santé des appareils connectés ((Appareils = 100 % * 100 + Appareils> 90 % * 95 + Appareils> 60 % * 75 + Appareils < 60 % * 35) / Appareils connectés)",
|
"health_explanation": "Santé des appareils connectés ((Appareils = 100 % * 100 + Appareils> 90 % * 95 + Appareils> 60 % * 75 + Appareils < 60 % * 35) / Appareils connectés)",
|
||||||
|
"mac_not_found": "Numéro de série introuvable, vous redirigeant vers la page Appareils",
|
||||||
"memory_explanation": "Nombre d'appareils connectés avec la mémoire correspondante utilisée %",
|
"memory_explanation": "Nombre d'appareils connectés avec la mémoire correspondante utilisée %",
|
||||||
"remove_from_blacklist": "Supprimer de la liste noire",
|
"remove_from_blacklist": "Supprimer de la liste noire",
|
||||||
"success_added_blacklist": "Appareil ajouté avec succès à la liste noire !",
|
"success_added_blacklist": "Appareil ajouté avec succès à la liste noire !",
|
||||||
|
|||||||
@@ -326,6 +326,7 @@
|
|||||||
"device": {
|
"device": {
|
||||||
"add_to_blacklist": "Adicionar dispositivo à lista negra",
|
"add_to_blacklist": "Adicionar dispositivo à lista negra",
|
||||||
"all_devices": "Todos os dispositivos",
|
"all_devices": "Todos os dispositivos",
|
||||||
|
"already_running_command": "O dispositivo já está executando um comando, tente mais tarde",
|
||||||
"blacklisted_on": "Encontro",
|
"blacklisted_on": "Encontro",
|
||||||
"capabilities": "Recursos",
|
"capabilities": "Recursos",
|
||||||
"certificate_explanation": "Certificados de dispositivos conectados",
|
"certificate_explanation": "Certificados de dispositivos conectados",
|
||||||
@@ -337,6 +338,7 @@
|
|||||||
"error_fetching_devices": "Erro ao buscar dispositivos: {{error}}",
|
"error_fetching_devices": "Erro ao buscar dispositivos: {{error}}",
|
||||||
"firmware_count_explanation": "Esta é a quantidade total de dispositivos que foram adicionados a este servidor de firmware, incluindo dispositivos que não estão apontando para o servidor de gateway relacionado.",
|
"firmware_count_explanation": "Esta é a quantidade total de dispositivos que foram adicionados a este servidor de firmware, incluindo dispositivos que não estão apontando para o servidor de gateway relacionado.",
|
||||||
"health_explanation": "Integridade dos dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos Conectados)",
|
"health_explanation": "Integridade dos dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos Conectados)",
|
||||||
|
"mac_not_found": "Número de série não encontrado, redirecionando você para a página Dispositivos",
|
||||||
"memory_explanation": "Quantidade de dispositivos conectados com a memória correspondente usada%",
|
"memory_explanation": "Quantidade de dispositivos conectados com a memória correspondente usada%",
|
||||||
"remove_from_blacklist": "Remover da lista negra",
|
"remove_from_blacklist": "Remover da lista negra",
|
||||||
"success_added_blacklist": "Dispositivo adicionado à lista negra com sucesso!",
|
"success_added_blacklist": "Dispositivo adicionado à lista negra com sucesso!",
|
||||||
|
|||||||
@@ -72,7 +72,18 @@ const BlinkModal = ({ show, toggleModal }) => {
|
|||||||
}
|
}
|
||||||
toggleModal();
|
toggleModal();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
|
if (e.response?.data?.ErrorDescription !== undefined) {
|
||||||
|
const split = e.response?.data?.ErrorDescription.split(':');
|
||||||
|
if (split !== undefined && split.length >= 2) {
|
||||||
|
addToast({
|
||||||
|
title: t('common.error'),
|
||||||
|
body: split[1],
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
setResult('error');
|
setResult('error');
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|||||||
@@ -205,10 +205,11 @@ const DeviceCommands = () => {
|
|||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } },
|
{ key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } },
|
||||||
{ key: 'command', label: t('common.command'), _style: { width: '15%' } },
|
{ key: 'command', label: t('common.command'), _style: { width: '0%' } },
|
||||||
|
{ key: 'status', label: t('common.status'), _style: { width: '0%' } },
|
||||||
{ key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } },
|
{ key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } },
|
||||||
{ key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } },
|
{ key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } },
|
||||||
{ key: 'errorCode', label: t('common.error_code'), filter: false, _style: { width: '8%' } },
|
{ key: 'errorCode', label: t('common.error_code'), filter: false },
|
||||||
{
|
{
|
||||||
key: 'show_buttons',
|
key: 'show_buttons',
|
||||||
label: '',
|
label: '',
|
||||||
@@ -317,16 +318,17 @@ const DeviceCommands = () => {
|
|||||||
{item.completed && item.completed !== 0 ? (
|
{item.completed && item.completed !== 0 ? (
|
||||||
<FormattedDate date={item.completed} />
|
<FormattedDate date={item.completed} />
|
||||||
) : (
|
) : (
|
||||||
'Pending'
|
'-'
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
),
|
),
|
||||||
|
status: (item) => <td className="align-middle">{item.status}</td>,
|
||||||
executed: (item) => (
|
executed: (item) => (
|
||||||
<td className="align-middle">
|
<td className="align-middle">
|
||||||
{item.executed && item.executed !== 0 ? (
|
{item.executed && item.executed !== 0 ? (
|
||||||
<FormattedDate date={item.executed} />
|
<FormattedDate date={item.executed} />
|
||||||
) : (
|
) : (
|
||||||
'Pending'
|
'-'
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
),
|
),
|
||||||
@@ -335,7 +337,7 @@ const DeviceCommands = () => {
|
|||||||
{item.submitted && item.submitted !== '' ? (
|
{item.submitted && item.submitted !== '' ? (
|
||||||
<FormattedDate date={item.submitted} />
|
<FormattedDate date={item.submitted} />
|
||||||
) : (
|
) : (
|
||||||
'Pending'
|
'-'
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -103,12 +103,17 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
|||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
setResponseBody('Error while submitting command!');
|
setResponseBody('Error while submitting command!');
|
||||||
addToast({
|
if (e.response?.data?.ErrorDescription !== undefined) {
|
||||||
title: t('common.error'),
|
const split = e.response?.data?.ErrorDescription.split(':');
|
||||||
body: `${t('common.general_error')}: ${e.response?.data?.ErrorDescription}`,
|
if (split !== undefined && split.length >= 2) {
|
||||||
color: 'danger',
|
addToast({
|
||||||
autohide: true,
|
title: t('common.error'),
|
||||||
});
|
body: split[1],
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
setHadFailure(true);
|
setHadFailure(true);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|||||||
@@ -54,12 +54,17 @@ const DeviceActions = ({ device }) => {
|
|||||||
if (newWindow) newWindow.opener = null;
|
if (newWindow) newWindow.opener = null;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
addToast({
|
if (e.response?.data?.ErrorDescription !== undefined) {
|
||||||
title: t('common.error'),
|
const split = e.response?.data?.ErrorDescription.split(':');
|
||||||
body: t('connect.error_trying_to_connect', { error: e.response?.data?.ErrorDescription }),
|
if (split !== undefined && split.length >= 2) {
|
||||||
color: 'danger',
|
addToast({
|
||||||
autohide: true,
|
title: t('common.error'),
|
||||||
});
|
body: split[1],
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setConnectLoading(false);
|
setConnectLoading(false);
|
||||||
@@ -68,18 +73,20 @@ const DeviceActions = ({ device }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (upgradeStatus.result !== undefined) {
|
if (upgradeStatus.result !== undefined) {
|
||||||
addToast({
|
if (upgradeStatus.result.success) {
|
||||||
title: upgradeStatus.result.success ? t('common.success') : t('common.error'),
|
addToast({
|
||||||
body: upgradeStatus.result.success
|
title: upgradeStatus.result.success ? t('common.success') : t('common.error'),
|
||||||
? t('firmware.upgrade_command_submitted')
|
body: upgradeStatus.result.success
|
||||||
: upgradeStatus.result.error,
|
? t('firmware.upgrade_command_submitted')
|
||||||
color: upgradeStatus.result.success ? 'success' : 'danger',
|
: upgradeStatus.result.error,
|
||||||
autohide: true,
|
color: upgradeStatus.result.success ? 'success' : 'danger',
|
||||||
});
|
autohide: true,
|
||||||
|
});
|
||||||
|
setShowUpgradeModal(false);
|
||||||
|
}
|
||||||
setUpgradeStatus({
|
setUpgradeStatus({
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
setShowUpgradeModal(false);
|
|
||||||
}
|
}
|
||||||
}, [upgradeStatus]);
|
}, [upgradeStatus]);
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,18 @@ const DeviceFirmwareModal = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
|
if (e.response?.data?.ErrorDescription !== undefined) {
|
||||||
|
const split = e.response?.data?.ErrorDescription.split(':');
|
||||||
|
if (split !== undefined && split.length >= 2) {
|
||||||
|
addToast({
|
||||||
|
title: t('common.error'),
|
||||||
|
body: split[1],
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
setUpgradeStatus({
|
setUpgradeStatus({
|
||||||
loading: false,
|
loading: false,
|
||||||
result: {
|
result: {
|
||||||
|
|||||||
75
src/components/DeviceSearchBar/Input.js
Normal file
75
src/components/DeviceSearchBar/Input.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Select, { components } from 'react-select';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const DeviceSearchBarInput = ({ search, results, history, action, isDisabled }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [selected, setSelected] = useState('');
|
||||||
|
const NoOptionsMessage = (props) => (
|
||||||
|
<components.NoOptionsMessage {...props}>
|
||||||
|
<span>{t('common.no_devices_found')}</span>
|
||||||
|
</components.NoOptionsMessage>
|
||||||
|
);
|
||||||
|
|
||||||
|
const onInputChange = (value) => {
|
||||||
|
if (value === '' || value.match('^[a-fA-F0-9-*]+$')) {
|
||||||
|
setSelected(value);
|
||||||
|
search(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
components={{ NoOptionsMessage }}
|
||||||
|
options={results.map((serial) => ({ label: serial, value: serial }))}
|
||||||
|
filterOption={() => true}
|
||||||
|
inputValue={selected}
|
||||||
|
placeholder={t('common.search')}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
styles={{
|
||||||
|
placeholder: (provided) => ({
|
||||||
|
...provided,
|
||||||
|
// disable placeholder mouse events
|
||||||
|
pointerEvents: 'none',
|
||||||
|
userSelect: 'none',
|
||||||
|
MozUserSelect: 'none',
|
||||||
|
WebkitUserSelect: 'none',
|
||||||
|
msUserSelect: 'none',
|
||||||
|
}),
|
||||||
|
input: (css) => ({
|
||||||
|
...css,
|
||||||
|
/* expand the Input Component div */
|
||||||
|
flex: '1 1 auto',
|
||||||
|
/* expand the Input Component child div */
|
||||||
|
'> div': {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
/* expand the Input Component input */
|
||||||
|
input: {
|
||||||
|
width: '100% !important',
|
||||||
|
textAlign: 'left',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
onInputChange={onInputChange}
|
||||||
|
onChange={(property) =>
|
||||||
|
action === null ? history.push(`/devices/${property.value}`) : action(property.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceSearchBarInput.propTypes = {
|
||||||
|
search: PropTypes.func.isRequired,
|
||||||
|
results: PropTypes.instanceOf(Array).isRequired,
|
||||||
|
history: PropTypes.instanceOf(Object).isRequired,
|
||||||
|
isDisabled: PropTypes.bool.isRequired,
|
||||||
|
action: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceSearchBarInput.defaultProps = {
|
||||||
|
action: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeviceSearchBarInput;
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { useAuth, DeviceSearchBar as SearchBar } from 'ucentral-libs';
|
import { useAuth } from 'ucentral-libs';
|
||||||
import { toJson } from 'utils/helper';
|
import { toJson } from 'utils/helper';
|
||||||
|
import DeviceSearchBarInput from './Input';
|
||||||
|
|
||||||
const DeviceSearchBar = ({ action }) => {
|
const DeviceSearchBar = ({ action }) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { currentToken, endpoints } = useAuth();
|
const { currentToken, endpoints } = useAuth();
|
||||||
const [socket, setSocket] = useState(null);
|
const [socket, setSocket] = useState(null);
|
||||||
@@ -14,20 +13,22 @@ const DeviceSearchBar = ({ action }) => {
|
|||||||
const [waitingSearch, setWaitingSearch] = useState('');
|
const [waitingSearch, setWaitingSearch] = useState('');
|
||||||
|
|
||||||
const search = (value) => {
|
const search = (value) => {
|
||||||
if (socket.readyState === WebSocket.OPEN) {
|
if (socket) {
|
||||||
if (value.length > 1 && value.match('^[a-fA-F0-9-*]+$')) {
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
setWaitingSearch('');
|
if (value.length > 1 && value.match('^[a-fA-F0-9-*]+$')) {
|
||||||
socket.send(
|
setWaitingSearch('');
|
||||||
JSON.stringify({ command: 'serial_number_search', serial_prefix: value.toLowerCase() }),
|
socket.send(
|
||||||
);
|
JSON.stringify({ command: 'serial_number_search', serial_prefix: value.toLowerCase() }),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setResults([]);
|
||||||
|
}
|
||||||
|
} else if (socket.readyState !== WebSocket.CONNECTING && endpoints?.owgw !== undefined) {
|
||||||
|
setWaitingSearch(value);
|
||||||
|
setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`));
|
||||||
} else {
|
} else {
|
||||||
setResults([]);
|
setWaitingSearch(value);
|
||||||
}
|
}
|
||||||
} else if (socket.readyState !== WebSocket.CONNECTING) {
|
|
||||||
setWaitingSearch(value);
|
|
||||||
setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`));
|
|
||||||
} else {
|
|
||||||
setWaitingSearch(value);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,12 +60,20 @@ const DeviceSearchBar = ({ action }) => {
|
|||||||
}, [socket]);
|
}, [socket]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (socket === null && endpoints?.owgw) {
|
if (socket === null && endpoints?.owgw !== undefined) {
|
||||||
setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`));
|
setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <SearchBar t={t} search={search} results={results} history={history} action={action} />;
|
return (
|
||||||
|
<DeviceSearchBarInput
|
||||||
|
search={search}
|
||||||
|
results={results}
|
||||||
|
history={history}
|
||||||
|
action={action}
|
||||||
|
isDisabled={endpoints.owgw === undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
DeviceSearchBar.propTypes = {
|
DeviceSearchBar.propTypes = {
|
||||||
|
|||||||
@@ -34,12 +34,17 @@ const EventQueueModal = ({ show, toggle }) => {
|
|||||||
setResult(response.data);
|
setResult(response.data);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
addToast({
|
if (e.response?.data?.ErrorDescription !== undefined) {
|
||||||
title: t('common.error'),
|
const split = e.response?.data?.ErrorDescription.split(':');
|
||||||
body: t('commands.unable_queue', { error: e.response?.data?.ErrorDescription }),
|
if (split !== undefined && split.length >= 2) {
|
||||||
color: 'danger',
|
addToast({
|
||||||
autohide: true,
|
title: t('common.error'),
|
||||||
});
|
body: split[1],
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import 'react-widgets/styles.css';
|
import 'react-widgets/styles.css';
|
||||||
import { useAuth, useDevice } from 'ucentral-libs';
|
import { useAuth, useDevice, useToast } from 'ucentral-libs';
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
import axiosInstance from 'utils/axiosInstance';
|
||||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { currentToken, endpoints } = useAuth();
|
const { currentToken, endpoints } = useAuth();
|
||||||
const { deviceSerialNumber } = useDevice();
|
const { deviceSerialNumber } = useDevice();
|
||||||
|
const { addToast } = useToast();
|
||||||
const [hadSuccess, setHadSuccess] = useState(false);
|
const [hadSuccess, setHadSuccess] = useState(false);
|
||||||
const [hadFailure, setHadFailure] = useState(false);
|
const [hadFailure, setHadFailure] = useState(false);
|
||||||
const [doingNow, setDoingNow] = useState(false);
|
const [doingNow, setDoingNow] = useState(false);
|
||||||
@@ -74,7 +75,18 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
setHadSuccess(true);
|
setHadSuccess(true);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
|
if (e.response?.data?.ErrorDescription !== undefined) {
|
||||||
|
const split = e.response?.data?.ErrorDescription.split(':');
|
||||||
|
if (split !== undefined && split.length >= 2) {
|
||||||
|
addToast({
|
||||||
|
title: t('common.error'),
|
||||||
|
body: split[1],
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
setResponseBody(t('commands.error'));
|
setResponseBody(t('commands.error'));
|
||||||
setHadFailure(true);
|
setHadFailure(true);
|
||||||
})
|
})
|
||||||
|
|||||||
32
src/components/FormattedDate/index.js
Normal file
32
src/components/FormattedDate/index.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { CPopover } from '@coreui/react';
|
||||||
|
import { formatDaysAgo, prettyDate } from 'utils/helper';
|
||||||
|
|
||||||
|
const FormattedDate = ({ date, size }) => {
|
||||||
|
if (size === 'lg') {
|
||||||
|
return (
|
||||||
|
<CPopover content={prettyDate(date)} advancedOptions={{ animation: false }}>
|
||||||
|
<h2 className="d-inline-block">{date === 0 ? '-' : formatDaysAgo(date)}</h2>
|
||||||
|
</CPopover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CPopover content={prettyDate(date)} advancedOptions={{ animation: false }}>
|
||||||
|
<span className="d-inline-block">{date === 0 ? '-' : formatDaysAgo(date)}</span>
|
||||||
|
</CPopover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
FormattedDate.propTypes = {
|
||||||
|
date: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
size: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
FormattedDate.defaultProps = {
|
||||||
|
date: 0,
|
||||||
|
size: 'md',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormattedDate;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
|
import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
|
||||||
import CIcon from '@coreui/icons-react';
|
import CIcon from '@coreui/icons-react';
|
||||||
import { cilX } from '@coreui/icons';
|
import { cilX } from '@coreui/icons';
|
||||||
@@ -32,6 +32,17 @@ const LatestStatisticsModal = ({ show, toggle }) => {
|
|||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const latestStatsString = useMemo(() => {
|
||||||
|
if (latestStats) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(latestStats, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}, [latestStats]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
if (show) {
|
||||||
getLatestStats();
|
getLatestStats();
|
||||||
@@ -52,13 +63,9 @@ const LatestStatisticsModal = ({ show, toggle }) => {
|
|||||||
</CModalHeader>
|
</CModalHeader>
|
||||||
<CModalBody>
|
<CModalBody>
|
||||||
<div style={{ textAlign: 'right' }}>
|
<div style={{ textAlign: 'right' }}>
|
||||||
<CopyToClipboardButton
|
<CopyToClipboardButton t={t} size="lg" content={latestStatsString} />
|
||||||
t={t}
|
|
||||||
size="lg"
|
|
||||||
content={JSON.stringify(latestStats ?? {}, null, 4)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<pre className="ignore">{JSON.stringify(latestStats, null, 2)}</pre>
|
<pre className="ignore">{latestStatsString}</pre>
|
||||||
</CModalBody>
|
</CModalBody>
|
||||||
</CModal>
|
</CModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { CSpinner } from '@coreui/react';
|
import { CSpinner, CAlert } from '@coreui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as createUuid } from 'uuid';
|
import { v4 as createUuid } from 'uuid';
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
import axiosInstance from 'utils/axiosInstance';
|
||||||
@@ -23,230 +23,258 @@ const StatisticsChartList = ({ deviceSerialNumber, setOptions, section, time })
|
|||||||
memory: [],
|
memory: [],
|
||||||
settings: {},
|
settings: {},
|
||||||
});
|
});
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
const transformIntoDataset = (data) => {
|
const transformIntoDataset = (data) => {
|
||||||
let sortedData = data.sort((a, b) => {
|
try {
|
||||||
if (a.recorded > b.recorded) return 1;
|
let sortedData = data.sort((a, b) => {
|
||||||
if (b.recorded > a.recorded) return -1;
|
if (a.recorded > b.recorded) return 1;
|
||||||
return 0;
|
if (b.recorded > a.recorded) return -1;
|
||||||
});
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
const dataLength = sortedData.length;
|
const dataLength = sortedData.length;
|
||||||
if (dataLength > 1000 && dataLength < 3000) {
|
if (dataLength > 1000 && dataLength < 3000) {
|
||||||
sortedData = sortedData.filter((dat, index) => index % 4 === 0);
|
sortedData = sortedData.filter((dat, index) => index % 4 === 0);
|
||||||
} else if (dataLength >= 3000 && dataLength < 5000) {
|
} else if (dataLength >= 3000 && dataLength < 5000) {
|
||||||
sortedData = sortedData.filter((dat, index) => index % 8 === 0);
|
sortedData = sortedData.filter((dat, index) => index % 8 === 0);
|
||||||
} else if (dataLength >= 5000 && dataLength < 7000) {
|
} else if (dataLength >= 5000 && dataLength < 7000) {
|
||||||
sortedData = sortedData.filter((dat, index) => index % 12 === 0);
|
sortedData = sortedData.filter((dat, index) => index % 12 === 0);
|
||||||
} else if (dataLength > 7000) {
|
} else if (dataLength > 7000) {
|
||||||
sortedData = sortedData.filter((dat, index) => index % 20 === 0);
|
sortedData = sortedData.filter((dat, index) => index % 20 === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Looping through data to build our memory graph data
|
// Looping through data to build our memory graph data
|
||||||
const memoryUsed = [
|
const memoryUsed = [
|
||||||
{
|
{
|
||||||
titleName: t('statistics.memory'),
|
titleName: t('statistics.memory'),
|
||||||
name: 'Used',
|
name: 'Used',
|
||||||
backgroundColor: 'rgb(228,102,81,0.9)',
|
backgroundColor: 'rgb(228,102,81,0.9)',
|
||||||
data: [],
|
data: [],
|
||||||
fill: true,
|
fill: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
titleName: t('statistics.memory'),
|
titleName: t('statistics.memory'),
|
||||||
name: 'Buffered',
|
name: 'Buffered',
|
||||||
backgroundColor: 'rgb(228,102,81,0.9)',
|
backgroundColor: 'rgb(228,102,81,0.9)',
|
||||||
data: [],
|
data: [],
|
||||||
fill: true,
|
fill: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
titleName: t('statistics.memory'),
|
titleName: t('statistics.memory'),
|
||||||
name: 'Cached',
|
name: 'Cached',
|
||||||
backgroundColor: 'rgb(228,102,81,0.9)',
|
backgroundColor: 'rgb(228,102,81,0.9)',
|
||||||
data: [],
|
data: [],
|
||||||
fill: true,
|
fill: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const log of sortedData) {
|
for (const log of sortedData) {
|
||||||
memoryUsed[0].data.push(
|
memoryUsed[0].data.push(
|
||||||
Math.floor((log.data.unit.memory.total - log.data.unit.memory.free) / 1024 / 1024),
|
Math.floor((log.data.unit.memory.total - log.data.unit.memory.free) / 1024 / 1024),
|
||||||
|
);
|
||||||
|
memoryUsed[1].data.push(Math.floor(log.data.unit.memory.buffered / 1024 / 1024));
|
||||||
|
memoryUsed[2].data.push(Math.floor(log.data.unit.memory.cached / 1024 / 1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUsed = memoryUsed[0].data;
|
||||||
|
if (newUsed.length > 0) newUsed.shift();
|
||||||
|
memoryUsed[0].data = newUsed;
|
||||||
|
const newBuff = memoryUsed[1].data;
|
||||||
|
if (newBuff.length > 0) newBuff.shift();
|
||||||
|
memoryUsed[1].data = newBuff;
|
||||||
|
const newCached = memoryUsed[2].data;
|
||||||
|
if (newCached.length > 0) newCached.shift();
|
||||||
|
memoryUsed[2].data = newCached;
|
||||||
|
|
||||||
|
// This dictionary will have a key that is the interface name and a value of it's index in the final array
|
||||||
|
const interfaceTypes = {};
|
||||||
|
const interfaceList = [];
|
||||||
|
const categories = [];
|
||||||
|
let i = 0;
|
||||||
|
const areSameDay = datesSameDay(
|
||||||
|
new Date(sortedData[0].recorded * 1000),
|
||||||
|
new Date(sortedData[sortedData.length - 1].recorded * 1000),
|
||||||
);
|
);
|
||||||
memoryUsed[1].data.push(Math.floor(log.data.unit.memory.buffered / 1024 / 1024));
|
|
||||||
memoryUsed[2].data.push(Math.floor(log.data.unit.memory.cached / 1024 / 1024));
|
|
||||||
}
|
|
||||||
|
|
||||||
const newUsed = memoryUsed[0].data;
|
// Just building the array for all the interfaces
|
||||||
if (newUsed.length > 0) newUsed.shift();
|
for (const log of sortedData) {
|
||||||
memoryUsed[0].data = newUsed;
|
categories.push(areSameDay ? unixToTime(log.recorded) : prettyDate(log.recorded));
|
||||||
const newBuff = memoryUsed[1].data;
|
for (const logInterface of log.data.interfaces) {
|
||||||
if (newBuff.length > 0) newBuff.shift();
|
if (interfaceTypes[logInterface.name] === undefined) {
|
||||||
memoryUsed[1].data = newBuff;
|
interfaceTypes[logInterface.name] = i;
|
||||||
const newCached = memoryUsed[2].data;
|
interfaceList.push([
|
||||||
if (newCached.length > 0) newCached.shift();
|
{
|
||||||
memoryUsed[2].data = newCached;
|
titleName: logInterface.name,
|
||||||
|
name: 'Tx',
|
||||||
// This dictionary will have a key that is the interface name and a value of it's index in the final array
|
backgroundColor: 'rgb(228,102,81,0.9)',
|
||||||
const interfaceTypes = {};
|
data: [],
|
||||||
const interfaceList = [];
|
fill: false,
|
||||||
const categories = [];
|
},
|
||||||
let i = 0;
|
{
|
||||||
const areSameDay = datesSameDay(
|
titleName: logInterface.name,
|
||||||
new Date(sortedData[0].recorded * 1000),
|
name: 'Rx',
|
||||||
new Date(sortedData[sortedData.length - 1].recorded * 1000),
|
backgroundColor: 'rgb(0,216,255,0.9)',
|
||||||
);
|
data: [],
|
||||||
|
fill: false,
|
||||||
// Just building the array for all the interfaces
|
},
|
||||||
for (const log of sortedData) {
|
]);
|
||||||
categories.push(areSameDay ? unixToTime(log.recorded) : prettyDate(log.recorded));
|
i += 1;
|
||||||
for (const logInterface of log.data.interfaces) {
|
}
|
||||||
if (interfaceTypes[logInterface.name] === undefined) {
|
|
||||||
interfaceTypes[logInterface.name] = i;
|
|
||||||
interfaceList.push([
|
|
||||||
{
|
|
||||||
titleName: logInterface.name,
|
|
||||||
name: 'Tx',
|
|
||||||
backgroundColor: 'rgb(228,102,81,0.9)',
|
|
||||||
data: [],
|
|
||||||
fill: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
titleName: logInterface.name,
|
|
||||||
name: 'Rx',
|
|
||||||
backgroundColor: 'rgb(0,216,255,0.9)',
|
|
||||||
data: [],
|
|
||||||
fill: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
i += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Looping through all the data
|
// Looping through all the data
|
||||||
const prevTxObj = {};
|
const prevTxObj = {};
|
||||||
const prevRxObj = {};
|
const prevRxObj = {};
|
||||||
for (const log of sortedData) {
|
for (const log of sortedData) {
|
||||||
// Looping through the interfaces of the log
|
// Looping through the interfaces of the log
|
||||||
const version = log.data.version ?? 0;
|
const version = log.data.version ?? 0;
|
||||||
for (const inter of log.data.interfaces) {
|
for (const inter of log.data.interfaces) {
|
||||||
if (version > 0) {
|
if (version > 0) {
|
||||||
const prevTx = prevTxObj[inter.name] !== undefined ? prevTxObj[inter.name] : 0;
|
const prevTx = prevTxObj[inter.name] !== undefined ? prevTxObj[inter.name] : 0;
|
||||||
const prevRx = prevTxObj[inter.name] !== undefined ? prevRxObj[inter.name] : 0;
|
const prevRx = prevTxObj[inter.name] !== undefined ? prevRxObj[inter.name] : 0;
|
||||||
const tx = inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0;
|
const tx = inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0;
|
||||||
const rx = inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0;
|
const rx = inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0;
|
||||||
interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(0, tx - prevTx));
|
interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(0, tx - prevTx));
|
||||||
interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(0, rx - prevRx));
|
interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(0, rx - prevRx));
|
||||||
prevTxObj[inter.name] = tx;
|
prevTxObj[inter.name] = tx;
|
||||||
prevRxObj[inter.name] = rx;
|
prevRxObj[inter.name] = rx;
|
||||||
} else {
|
} else {
|
||||||
interfaceList[interfaceTypes[inter.name]][0].data.push(
|
interfaceList[interfaceTypes[inter.name]][0].data.push(
|
||||||
inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0,
|
inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0,
|
||||||
);
|
);
|
||||||
interfaceList[interfaceTypes[inter.name]][1].data.push(
|
interfaceList[interfaceTypes[inter.name]][1].data.push(
|
||||||
inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0,
|
inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (let y = 0; y < interfaceList.length; y += 1) {
|
for (let y = 0; y < interfaceList.length; y += 1) {
|
||||||
for (let z = 0; z < interfaceList[y].length; z += 1) {
|
for (let z = 0; z < interfaceList[y].length; z += 1) {
|
||||||
const newArray = interfaceList[y][z].data;
|
const newArray = interfaceList[y][z].data;
|
||||||
if (newArray.length > 0) newArray.shift();
|
if (newArray.length > 0) newArray.shift();
|
||||||
interfaceList[y][z].data = newArray;
|
interfaceList[y][z].data = newArray;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const newCategories = categories;
|
const newCategories = categories;
|
||||||
if (newCategories.length > 0) newCategories.shift();
|
if (newCategories.length > 0) newCategories.shift();
|
||||||
const interfaceOptions = {
|
const interfaceOptions = {
|
||||||
chart: {
|
chart: {
|
||||||
id: 'chart',
|
id: 'chart',
|
||||||
},
|
},
|
||||||
stroke: {
|
stroke: {
|
||||||
curve: 'smooth',
|
curve: 'smooth',
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
title: {
|
title: {
|
||||||
text: 'Time',
|
text: 'Time',
|
||||||
style: {
|
style: {
|
||||||
fontSize: '15px',
|
fontSize: '15px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
categories: newCategories,
|
||||||
|
tickAmount: areSameDay ? 15 : 10,
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
minWidth: 40,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: t('statistics.data'),
|
||||||
|
style: {
|
||||||
|
fontSize: '15px',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
categories: newCategories,
|
legend: {
|
||||||
tickAmount: areSameDay ? 15 : 10,
|
position: 'top',
|
||||||
},
|
horizontalAlign: 'right',
|
||||||
yaxis: {
|
float: true,
|
||||||
labels: {
|
|
||||||
minWidth: 40,
|
|
||||||
},
|
},
|
||||||
title: {
|
};
|
||||||
text: t('statistics.data'),
|
|
||||||
style: {
|
const memoryOptions = {
|
||||||
fontSize: '15px',
|
chart: {
|
||||||
|
id: 'chart',
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: 'smooth',
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
tickAmount: areSameDay ? 15 : 10,
|
||||||
|
title: {
|
||||||
|
text: 'Time',
|
||||||
|
style: {
|
||||||
|
fontSize: '15px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
categories,
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
tickAmount: 5,
|
||||||
|
title: {
|
||||||
|
text: t('statistics.data_mb'),
|
||||||
|
style: {
|
||||||
|
fontSize: '15px',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
legend: {
|
||||||
legend: {
|
position: 'top',
|
||||||
position: 'top',
|
horizontalAlign: 'right',
|
||||||
horizontalAlign: 'right',
|
float: true,
|
||||||
float: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const memoryOptions = {
|
|
||||||
chart: {
|
|
||||||
id: 'chart',
|
|
||||||
},
|
|
||||||
stroke: {
|
|
||||||
curve: 'smooth',
|
|
||||||
},
|
|
||||||
xaxis: {
|
|
||||||
tickAmount: areSameDay ? 15 : 10,
|
|
||||||
title: {
|
|
||||||
text: 'Time',
|
|
||||||
style: {
|
|
||||||
fontSize: '15px',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
categories,
|
};
|
||||||
},
|
|
||||||
yaxis: {
|
|
||||||
tickAmount: 5,
|
|
||||||
title: {
|
|
||||||
text: t('statistics.data_mb'),
|
|
||||||
style: {
|
|
||||||
fontSize: '15px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
position: 'top',
|
|
||||||
horizontalAlign: 'right',
|
|
||||||
float: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const newOptions = {
|
const newOptions = {
|
||||||
interfaceList,
|
interfaceList,
|
||||||
memory: [memoryUsed],
|
memory: [memoryUsed],
|
||||||
interfaceOptions,
|
interfaceOptions,
|
||||||
memoryOptions,
|
memoryOptions,
|
||||||
start: new Date(sortedData[0].recorded * 1000).toISOString(),
|
start: new Date(sortedData[0].recorded * 1000).toISOString(),
|
||||||
end: new Date(sortedData[sortedData.length - 1].recorded * 1000).toISOString(),
|
end: new Date(sortedData[sortedData.length - 1].recorded * 1000).toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (statOptions !== newOptions) {
|
if (statOptions !== newOptions) {
|
||||||
const sectionOptions = newOptions.interfaceList.map((opt) => ({
|
const sectionOptions = newOptions.interfaceList.map((opt) => ({
|
||||||
value: opt[0].titleName,
|
value: opt[0].titleName,
|
||||||
label: opt[0].titleName,
|
label: opt[0].titleName,
|
||||||
}));
|
}));
|
||||||
setOptions([...sectionOptions, { value: 'memory', label: t('statistics.memory') }]);
|
setOptions([...sectionOptions, { value: 'memory', label: t('statistics.memory') }]);
|
||||||
setStatOptions({ ...newOptions });
|
setStatOptions({ ...newOptions });
|
||||||
|
}
|
||||||
|
setError(undefined);
|
||||||
|
} catch (e) {
|
||||||
|
if (data?.length === 0) {
|
||||||
|
setError('nodata');
|
||||||
|
} else {
|
||||||
|
setError('error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInterface = useCallback(() => {
|
const getInterface = useCallback(() => {
|
||||||
|
if (error === 'error') {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<CAlert color="danger" style={{ width: '240px', margin: 'auto' }}>
|
||||||
|
Error while parsing statistics
|
||||||
|
</CAlert>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (error === 'nodata') {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<CAlert color="danger" style={{ width: '340px', margin: 'auto' }}>
|
||||||
|
No available statistics during this time period
|
||||||
|
</CAlert>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
if (statOptions.interfaceList.length === 0) return <p>N/A</p>;
|
if (statOptions.interfaceList.length === 0) return <p>N/A</p>;
|
||||||
|
|
||||||
const interfaceToShow = statOptions.interfaceList.find(
|
const interfaceToShow = statOptions.interfaceList.find(
|
||||||
@@ -273,8 +301,9 @@ const StatisticsChartList = ({ deviceSerialNumber, setOptions, section, time })
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <p>N/A</p>;
|
return <p>N/A</p>;
|
||||||
}, [statOptions, section]);
|
}, [statOptions, section, error]);
|
||||||
|
|
||||||
const getStatistics = () => {
|
const getStatistics = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const NetworkDiagram = ({ show, elements, setElements }) => {
|
|||||||
onElementsRemove={onElementsRemove}
|
onElementsRemove={onElementsRemove}
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
snapToGrid
|
snapToGrid
|
||||||
|
minZoom={0.1}
|
||||||
snapGrid={[20, 20]}
|
snapGrid={[20, 20]}
|
||||||
>
|
>
|
||||||
<MiniMap
|
<MiniMap
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const associationNode = (associationInfo) => (
|
|||||||
<div>
|
<div>
|
||||||
<CRow>
|
<CRow>
|
||||||
<CCol className="text-center">
|
<CCol className="text-center">
|
||||||
<h6>{associationInfo.bssid}</h6>
|
<h6>{associationInfo.station}</h6>
|
||||||
</CCol>
|
</CCol>
|
||||||
</CRow>
|
</CRow>
|
||||||
<CRow>
|
<CRow>
|
||||||
@@ -92,7 +92,6 @@ const NetworkDiagram = ({ show, radios, associations }) => {
|
|||||||
// Creating the association nodes and their edges
|
// Creating the association nodes and their edges
|
||||||
for (let i = 0; i < associations.length; i += 1) {
|
for (let i = 0; i < associations.length; i += 1) {
|
||||||
const assoc = associations[i];
|
const assoc = associations[i];
|
||||||
|
|
||||||
// If the radio has not been added, we create a new unknown radio based on its index
|
// If the radio has not been added, we create a new unknown radio based on its index
|
||||||
if (radiosAdded[assoc.radio.radioIndex] === undefined) {
|
if (radiosAdded[assoc.radio.radioIndex] === undefined) {
|
||||||
newElements.push({
|
newElements.push({
|
||||||
@@ -107,7 +106,7 @@ const NetworkDiagram = ({ show, radios, associations }) => {
|
|||||||
|
|
||||||
// Adding the association
|
// Adding the association
|
||||||
newElements.push({
|
newElements.push({
|
||||||
id: `a-${assoc.bssid}`,
|
id: `a-${assoc.station}`,
|
||||||
data: { label: associationNode(assoc) },
|
data: { label: associationNode(assoc) },
|
||||||
position: {
|
position: {
|
||||||
x: getX(radiosAdded[assoc.radio.radioIndex]),
|
x: getX(radiosAdded[assoc.radio.radioIndex]),
|
||||||
@@ -120,9 +119,9 @@ const NetworkDiagram = ({ show, radios, associations }) => {
|
|||||||
|
|
||||||
// Creating the edge
|
// Creating the edge
|
||||||
newElements.push({
|
newElements.push({
|
||||||
id: `e-${assoc.radio.radioIndex}-${assoc.bssid}`,
|
id: `e-${assoc.radio.radioIndex}-${assoc.station}`,
|
||||||
source: `r-${assoc.radio.radioIndex}`,
|
source: `r-${assoc.radio.radioIndex}`,
|
||||||
target: `a-${assoc.bssid}`,
|
target: `a-${assoc.station}`,
|
||||||
arrowHeadType: 'arrowclosed',
|
arrowHeadType: 'arrowclosed',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,18 @@ const ActionModal = ({ show, toggleModal }) => {
|
|||||||
});
|
});
|
||||||
toggleModal();
|
toggleModal();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
|
if (e.response?.data?.ErrorDescription !== undefined) {
|
||||||
|
const split = e.response?.data?.ErrorDescription.split(':');
|
||||||
|
if (split !== undefined && split.length >= 2) {
|
||||||
|
addToast({
|
||||||
|
title: t('common.error'),
|
||||||
|
body: split[1],
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
setResult('error');
|
setResult('error');
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|||||||
@@ -105,12 +105,17 @@ const TelemetryModal = ({ show, toggle }) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
addToast({
|
if (e.response?.data?.ErrorDescription !== undefined) {
|
||||||
title: t('common.error'),
|
const split = e.response?.data?.ErrorDescription.split(':');
|
||||||
body: t('telemetry.connection_failed', { error: e.response?.data?.ErrorDescription }),
|
if (split !== undefined && split.length >= 2) {
|
||||||
color: 'danger',
|
addToast({
|
||||||
autohide: true,
|
title: t('common.error'),
|
||||||
});
|
body: split[1],
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,13 +23,14 @@ import PropTypes from 'prop-types';
|
|||||||
import 'react-widgets/styles.css';
|
import 'react-widgets/styles.css';
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
import axiosInstance from 'utils/axiosInstance';
|
||||||
import eventBus from 'utils/eventBus';
|
import eventBus from 'utils/eventBus';
|
||||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs';
|
||||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||||
import WaitingForTraceBody from './WaitingForTraceBody';
|
import WaitingForTraceBody from './WaitingForTraceBody';
|
||||||
|
|
||||||
const TraceModal = ({ show, toggleModal }) => {
|
const TraceModal = ({ show, toggleModal }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { currentToken, endpoints } = useAuth();
|
const { currentToken, endpoints } = useAuth();
|
||||||
|
const { addToast } = useToast();
|
||||||
const { deviceSerialNumber, getDeviceConnection } = useDevice();
|
const { deviceSerialNumber, getDeviceConnection } = useDevice();
|
||||||
const [hadSuccess, setHadSuccess] = useState(false);
|
const [hadSuccess, setHadSuccess] = useState(false);
|
||||||
const [hadFailure, setHadFailure] = useState(false);
|
const [hadFailure, setHadFailure] = useState(false);
|
||||||
@@ -94,7 +95,18 @@ const TraceModal = ({ show, toggleModal }) => {
|
|||||||
setWaitingForTrace(true);
|
setWaitingForTrace(true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
|
if (e.response?.data?.ErrorDescription !== undefined) {
|
||||||
|
const split = e.response?.data?.ErrorDescription.split(':');
|
||||||
|
if (split !== undefined && split.length >= 2) {
|
||||||
|
addToast({
|
||||||
|
title: t('common.error'),
|
||||||
|
body: split[1],
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
setResponseBody(t('commands.error'));
|
setResponseBody(t('commands.error'));
|
||||||
setHadFailure(true);
|
setHadFailure(true);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,31 +20,22 @@ import PropTypes from 'prop-types';
|
|||||||
import axiosInstance from 'utils/axiosInstance';
|
import axiosInstance from 'utils/axiosInstance';
|
||||||
import eventBus from 'utils/eventBus';
|
import eventBus from 'utils/eventBus';
|
||||||
import { prettyDateForFile } from 'utils/helper';
|
import { prettyDateForFile } from 'utils/helper';
|
||||||
import { useAuth, useDevice } from 'ucentral-libs';
|
import { useAuth, useDevice, useToast } from 'ucentral-libs';
|
||||||
import WifiChannelTable from 'components/WifiScanResultModal/WifiChannelTable';
|
import WifiChannelTable from 'components/WifiScanResultModal/WifiChannelTable';
|
||||||
import 'react-widgets/styles.css';
|
import 'react-widgets/styles.css';
|
||||||
import { CSVLink } from 'react-csv';
|
import { CSVLink } from 'react-csv';
|
||||||
import Select from 'react-select';
|
|
||||||
import IeDisplay from 'components/WifiScanResultModal/IeDisplay';
|
import IeDisplay from 'components/WifiScanResultModal/IeDisplay';
|
||||||
import IE_OPTIONS from './IE_OPTIONS.json';
|
import IE_OPTIONS from './IE_OPTIONS.json';
|
||||||
|
|
||||||
const getIeOptions = () => {
|
const allIes = Object.entries(IE_OPTIONS).map(([, value]) => value);
|
||||||
const arr = [];
|
|
||||||
for (const [key, value] of Object.entries(IE_OPTIONS)) {
|
|
||||||
arr.push({
|
|
||||||
label: `${key} (${value})`,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
};
|
|
||||||
const WifiScanModal = ({ show, toggleModal }) => {
|
const WifiScanModal = ({ show, toggleModal }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { currentToken, endpoints } = useAuth();
|
const { currentToken, endpoints } = useAuth();
|
||||||
const { deviceSerialNumber } = useDevice();
|
const { deviceSerialNumber } = useDevice();
|
||||||
|
const { addToast } = useToast();
|
||||||
const [hadSuccess, setHadSuccess] = useState(false);
|
const [hadSuccess, setHadSuccess] = useState(false);
|
||||||
const [selectedIes, setSelectedIes] = useState(undefined);
|
const [selectedIes, setSelectedIes] = useState(undefined);
|
||||||
const [ies, setIes] = useState([]);
|
|
||||||
const [hadFailure, setHadFailure] = useState(false);
|
const [hadFailure, setHadFailure] = useState(false);
|
||||||
const [errorCode, setErrorCode] = useState(0);
|
const [errorCode, setErrorCode] = useState(0);
|
||||||
const [waiting, setWaiting] = useState(false);
|
const [waiting, setWaiting] = useState(false);
|
||||||
@@ -63,14 +54,6 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
|||||||
setActiveScan(!activeScan);
|
setActiveScan(!activeScan);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onIesChange = (v) => {
|
|
||||||
if (v.find(({ value }) => value === '*')) {
|
|
||||||
setIes(getIeOptions());
|
|
||||||
} else {
|
|
||||||
setIes(v);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setHadSuccess(false);
|
setHadSuccess(false);
|
||||||
setHadFailure(false);
|
setHadFailure(false);
|
||||||
@@ -82,7 +65,7 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
|||||||
setActiveScan(false);
|
setActiveScan(false);
|
||||||
setHideOptions(false);
|
setHideOptions(false);
|
||||||
setErrorCode(0);
|
setErrorCode(0);
|
||||||
setIes([]);
|
setSelectedIes(undefined);
|
||||||
}, [show]);
|
}, [show]);
|
||||||
|
|
||||||
const parseThroughList = (scanList) => {
|
const parseThroughList = (scanList) => {
|
||||||
@@ -164,7 +147,7 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
|||||||
override_dfs: dfs,
|
override_dfs: dfs,
|
||||||
bandwidth: bandwidth !== '' ? bandwidth : undefined,
|
bandwidth: bandwidth !== '' ? bandwidth : undefined,
|
||||||
activeScan,
|
activeScan,
|
||||||
ies: ies?.length > 0 ? ies.map(({ value }) => value) : undefined,
|
ies: allIes,
|
||||||
};
|
};
|
||||||
const headers = {
|
const headers = {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
@@ -190,7 +173,18 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
|||||||
setHadFailure(true);
|
setHadFailure(true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
|
if (e.response?.data?.ErrorDescription !== undefined) {
|
||||||
|
const split = e.response?.data?.ErrorDescription.split(':');
|
||||||
|
if (split !== undefined && split.length >= 2) {
|
||||||
|
addToast({
|
||||||
|
title: t('common.error'),
|
||||||
|
body: split[1],
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
setHadFailure(true);
|
setHadFailure(true);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -289,23 +283,6 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
|||||||
</CSelect>
|
</CSelect>
|
||||||
</CCol>
|
</CCol>
|
||||||
</CRow>
|
</CRow>
|
||||||
<CRow className="mt-3">
|
|
||||||
<CCol md="3">
|
|
||||||
<p className="pl-2">{t('actions.request_ie')}:</p>
|
|
||||||
</CCol>
|
|
||||||
<CCol>
|
|
||||||
<Select
|
|
||||||
isMulti
|
|
||||||
closeMenuOnSelect={false}
|
|
||||||
name="request_ie"
|
|
||||||
options={[{ label: 'All', value: '*' }, ...getIeOptions()]}
|
|
||||||
onChange={onIesChange}
|
|
||||||
value={ies}
|
|
||||||
className="basic-multi-select"
|
|
||||||
classNamePrefix="select"
|
|
||||||
/>
|
|
||||||
</CCol>
|
|
||||||
</CRow>
|
|
||||||
</div>
|
</div>
|
||||||
<div hidden={!waiting}>
|
<div hidden={!waiting}>
|
||||||
<CRow>
|
<CRow>
|
||||||
@@ -332,10 +309,10 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
|||||||
</CCol>
|
</CCol>
|
||||||
</CRow>
|
</CRow>
|
||||||
)}
|
)}
|
||||||
{selectedIes && <IeDisplay ies={selectedIes} setIes={setSelectedIes} />}
|
|
||||||
{selectedIes || channelList === null ? null : (
|
{selectedIes || channelList === null ? null : (
|
||||||
<WifiChannelTable channels={channelList} setIes={setSelectedIes} />
|
<WifiChannelTable channels={channelList} setIes={setSelectedIes} />
|
||||||
)}
|
)}
|
||||||
|
{selectedIes && <IeDisplay ies={selectedIes} setIes={setSelectedIes} />}
|
||||||
</div>
|
</div>
|
||||||
</CModalBody>
|
</CModalBody>
|
||||||
</CModal>
|
</CModal>
|
||||||
|
|||||||
4
src/constants.js
Normal file
4
src/constants.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const AUTH_EXPIRED_TOKEN_CODE = 9;
|
||||||
|
export const AUTH_INVALID_TOKEN_CODE = 8;
|
||||||
|
|
||||||
|
export const LOGOUT_ON_SEC_ERROR_CODES = [AUTH_EXPIRED_TOKEN_CODE, AUTH_INVALID_TOKEN_CODE];
|
||||||
@@ -11,7 +11,7 @@ const WebSocketContext = React.createContext({
|
|||||||
addDeviceListener: () => {},
|
addDeviceListener: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WebSocketProvider = ({ children }) => {
|
export const WebSocketProvider = ({ children, setNewConnectionData }) => {
|
||||||
const { currentToken, endpoints } = useAuth();
|
const { currentToken, endpoints } = useAuth();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const ws = useRef(undefined);
|
const ws = useRef(undefined);
|
||||||
@@ -20,6 +20,9 @@ export const WebSocketProvider = ({ children }) => {
|
|||||||
|
|
||||||
const onMessage = useCallback((message) => {
|
const onMessage = useCallback((message) => {
|
||||||
const result = extractWebSocketResponse(message);
|
const result = extractWebSocketResponse(message);
|
||||||
|
if (result?.type === 'device_connections_statistics') {
|
||||||
|
setNewConnectionData(result.content);
|
||||||
|
}
|
||||||
if (result?.type === 'NOTIFICATION') {
|
if (result?.type === 'NOTIFICATION') {
|
||||||
dispatch({ type: 'NEW_NOTIFICATION', notification: result.notification });
|
dispatch({ type: 'NEW_NOTIFICATION', notification: result.notification });
|
||||||
pushNotification(result.notification);
|
pushNotification(result.notification);
|
||||||
@@ -36,23 +39,29 @@ export const WebSocketProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// useEffect for created the WebSocket and 'storing' it in useRef
|
const onStartWebSocket = () => {
|
||||||
useEffect(() => {
|
ws.current = new WebSocket(`${endpoints.owgw?.replace('https', 'wss')}/api/v1/ws`);
|
||||||
ws.current = new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`);
|
|
||||||
ws.current.onopen = () => {
|
ws.current.onopen = () => {
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
ws.current?.send(`token:${currentToken}`);
|
ws.current?.send(`token:${currentToken}`);
|
||||||
};
|
};
|
||||||
ws.current.onclose = () => {
|
ws.current.onclose = () => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
|
setTimeout(onStartWebSocket, 3000);
|
||||||
};
|
};
|
||||||
ws.current.onerror = () => {
|
ws.current.onerror = () => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// useEffect for created the WebSocket and 'storing' it in useRef
|
||||||
|
useEffect(() => {
|
||||||
|
if (endpoints?.owgw !== undefined) {
|
||||||
|
onStartWebSocket();
|
||||||
|
}
|
||||||
const wsCurrent = ws?.current;
|
const wsCurrent = ws?.current;
|
||||||
return () => wsCurrent?.close();
|
return () => wsCurrent?.close();
|
||||||
}, []);
|
}, [endpoints]);
|
||||||
|
|
||||||
// useEffect for generating global notifications
|
// useEffect for generating global notifications
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -65,6 +74,7 @@ export const WebSocketProvider = ({ children }) => {
|
|||||||
if (wsCurrent) wsCurrent.removeEventListener('message', onMessage);
|
if (wsCurrent) wsCurrent.removeEventListener('message', onMessage);
|
||||||
};
|
};
|
||||||
}, [ws?.current]);
|
}, [ws?.current]);
|
||||||
|
|
||||||
const values = useMemo(
|
const values = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
lastMessage,
|
lastMessage,
|
||||||
@@ -83,6 +93,7 @@ export const WebSocketProvider = ({ children }) => {
|
|||||||
|
|
||||||
WebSocketProvider.propTypes = {
|
WebSocketProvider.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
|
setNewConnectionData: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGlobalWebSocket = () => React.useContext(WebSocketContext);
|
export const useGlobalWebSocket = () => React.useContext(WebSocketContext);
|
||||||
|
|||||||
@@ -26,30 +26,11 @@ export const extractWebSocketResponse = (message) => {
|
|||||||
if (data.command_response_id) {
|
if (data.command_response_id) {
|
||||||
return { data, type: 'COMMAND' };
|
return { data, type: 'COMMAND' };
|
||||||
}
|
}
|
||||||
|
if (data.notification.type === 'device_connections_statistics') {
|
||||||
|
return { content: data.notification.content, type: 'device_connections_statistics' };
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStatusFromNotification = (notification) => {
|
|
||||||
let status = 'success';
|
|
||||||
if (notification.content.warning?.length > 0) status = 'warning';
|
|
||||||
if (notification.content.error?.length > 0) status = 'error';
|
|
||||||
|
|
||||||
return status;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getNotificationDescription = (t, notification) => {
|
|
||||||
if (
|
|
||||||
notification.content.type === 'venue_configuration_update' ||
|
|
||||||
notification.content.type === 'entity_configuration_update'
|
|
||||||
) {
|
|
||||||
return t('configurations.notification_details', {
|
|
||||||
success: notification.content.success.length,
|
|
||||||
warning: notification.content.warning.length,
|
|
||||||
error: notification.content.error.length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return notification.content.details;
|
|
||||||
};
|
|
||||||
|
|||||||
17
src/hooks/useToggle/index.js
Normal file
17
src/hooks/useToggle/index.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
const useToggle = (initialState) => {
|
||||||
|
const [value, setValue] = useState(initialState);
|
||||||
|
|
||||||
|
return [
|
||||||
|
value,
|
||||||
|
() => {
|
||||||
|
setValue(!value);
|
||||||
|
},
|
||||||
|
(newValue) => {
|
||||||
|
setValue(newValue);
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useToggle;
|
||||||
120
src/layout/Devices.js
Normal file
120
src/layout/Devices.js
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import axiosInstance from 'utils/axiosInstance';
|
||||||
|
import { useAuth } from 'ucentral-libs';
|
||||||
|
import { CPopover } from '@coreui/react';
|
||||||
|
import { extraCompactSecondsToDetailed, secondsToDetailed } from 'utils/helper';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
newData: PropTypes.instanceOf(Object),
|
||||||
|
};
|
||||||
|
const defaultProps = {
|
||||||
|
newData: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SidebarDevices = ({ newData }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { currentToken, endpoints } = useAuth();
|
||||||
|
const [stats, setStats] = useState();
|
||||||
|
const [lastUpdate, setLastUpdate] = useState();
|
||||||
|
const [lastTime, setLastTime] = useState();
|
||||||
|
|
||||||
|
const getInitialStats = async () => {
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${currentToken}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
axiosInstance
|
||||||
|
.get(`${endpoints.owgw}/api/v1/devices?connectionStatistics=true`, options)
|
||||||
|
.then(({ data }) => {
|
||||||
|
setStats(data);
|
||||||
|
setLastUpdate(new Date());
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTime = () => {
|
||||||
|
if (lastTime === undefined || lastUpdate === undefined) return null;
|
||||||
|
|
||||||
|
const seconds = lastTime.getTime() - lastUpdate.getTime();
|
||||||
|
|
||||||
|
return Math.max(0, Math.floor(seconds / 1000));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (newData !== undefined && Object.keys(newData).length > 0) {
|
||||||
|
setStats({ ...newData });
|
||||||
|
setLastUpdate(new Date());
|
||||||
|
}
|
||||||
|
}, [newData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getInitialStats();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setLastTime(new Date());
|
||||||
|
}, 1000);
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!stats) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '0px',
|
||||||
|
width: '100%',
|
||||||
|
background: '#2f3d54 !important',
|
||||||
|
backgroundColor: '#2f3d54 !important',
|
||||||
|
borderTop: '3px solid #d8dbe0',
|
||||||
|
color: 'white',
|
||||||
|
textAlign: 'center',
|
||||||
|
paddingTop: '15px',
|
||||||
|
paddingBottom: '25px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3 style={{ marginBottom: '0px' }}>{stats?.connectedDevices ?? stats?.numberOfDevices}</h3>
|
||||||
|
<h6>Connected Devices</h6>
|
||||||
|
<CPopover
|
||||||
|
content={secondsToDetailed(
|
||||||
|
stats?.averageConnectionTime ?? stats?.averageConnectedTime,
|
||||||
|
t('common.day'),
|
||||||
|
t('common.days'),
|
||||||
|
t('common.hour'),
|
||||||
|
t('common.hours'),
|
||||||
|
t('common.minute'),
|
||||||
|
t('common.minutes'),
|
||||||
|
t('common.second'),
|
||||||
|
t('common.seconds'),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<h3 style={{ marginBottom: '0px' }}>
|
||||||
|
{extraCompactSecondsToDetailed(
|
||||||
|
stats?.averageConnectionTime ?? stats?.averageConnectedTime,
|
||||||
|
t('common.day'),
|
||||||
|
t('common.days'),
|
||||||
|
t('common.seconds'),
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
</CPopover>
|
||||||
|
<h6>Avg. Connection Time</h6>
|
||||||
|
<h7 style={{ color: '#ebedef', fontStyle: 'italic' }}>{getTime()} seconds ago</h7>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SidebarDevices.propTypes = propTypes;
|
||||||
|
SidebarDevices.defaultProps = defaultProps;
|
||||||
|
|
||||||
|
export default React.memo(SidebarDevices);
|
||||||
49
src/layout/Sidebar/index.js
Normal file
49
src/layout/Sidebar/index.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CSidebar, CSidebarBrand, CSidebarNav } from '@coreui/react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
const Sidebar = ({
|
||||||
|
showSidebar,
|
||||||
|
setShowSidebar,
|
||||||
|
logo,
|
||||||
|
options,
|
||||||
|
redirectTo,
|
||||||
|
logoHeight,
|
||||||
|
logoWidth,
|
||||||
|
}) => (
|
||||||
|
<CSidebar show={showSidebar} onShowChange={(val) => setShowSidebar(val)}>
|
||||||
|
<CSidebarBrand className="d-md-down-none" to={redirectTo}>
|
||||||
|
<img
|
||||||
|
className={[styles.sidebarImgFull, 'c-sidebar-brand-full'].join(' ')}
|
||||||
|
style={{ height: logoHeight ?? undefined, width: logoWidth ?? undefined }}
|
||||||
|
src={logo}
|
||||||
|
alt="OpenWifi"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
className={[styles.sidebarImgMinimized, 'c-sidebar-brand-minimized'].join(' ')}
|
||||||
|
style={{ height: logoHeight ?? undefined, width: logoWidth ?? undefined }}
|
||||||
|
src={logo}
|
||||||
|
alt="OpenWifi"
|
||||||
|
/>
|
||||||
|
</CSidebarBrand>
|
||||||
|
<CSidebarNav>{options}</CSidebarNav>
|
||||||
|
</CSidebar>
|
||||||
|
);
|
||||||
|
|
||||||
|
Sidebar.propTypes = {
|
||||||
|
showSidebar: PropTypes.string.isRequired,
|
||||||
|
setShowSidebar: PropTypes.func.isRequired,
|
||||||
|
logo: PropTypes.string.isRequired,
|
||||||
|
options: PropTypes.node.isRequired,
|
||||||
|
redirectTo: PropTypes.string.isRequired,
|
||||||
|
logoHeight: PropTypes.string,
|
||||||
|
logoWidth: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
Sidebar.defaultProps = {
|
||||||
|
logoHeight: null,
|
||||||
|
logoWidth: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(Sidebar);
|
||||||
9
src/layout/Sidebar/index.module.scss
Normal file
9
src/layout/Sidebar/index.module.scss
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.sidebarImgFull {
|
||||||
|
height: 75px;
|
||||||
|
width: 175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarImgMinimized {
|
||||||
|
height: 75px;
|
||||||
|
width: 75px;
|
||||||
|
}
|
||||||
@@ -4,13 +4,20 @@ import routes from 'routes';
|
|||||||
import { CSidebarNavItem } from '@coreui/react';
|
import { CSidebarNavItem } from '@coreui/react';
|
||||||
import { cilBarcode, cilRouter, cilSave, cilSettings, cilPeople } from '@coreui/icons';
|
import { cilBarcode, cilRouter, cilSave, cilSettings, cilPeople } from '@coreui/icons';
|
||||||
import CIcon from '@coreui/icons-react';
|
import CIcon from '@coreui/icons-react';
|
||||||
import { Header, Sidebar, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs';
|
import { Header, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs';
|
||||||
import { WebSocketProvider } from 'contexts/WebSocketProvider';
|
import { WebSocketProvider } from 'contexts/WebSocketProvider';
|
||||||
|
import Sidebar from './Sidebar';
|
||||||
|
import SidebarDevices from './Devices';
|
||||||
|
|
||||||
const TheLayout = () => {
|
const TheLayout = () => {
|
||||||
const [showSidebar, setShowSidebar] = useState('responsive');
|
const [showSidebar, setShowSidebar] = useState('responsive');
|
||||||
const { endpoints, currentToken, user, avatar, logout } = useAuth();
|
const { endpoints, currentToken, user, avatar, logout } = useAuth();
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
|
const [newConnectionData, setNewConnectionData] = useState();
|
||||||
|
|
||||||
|
const onConnectionDataChange = React.useCallback((newData) => {
|
||||||
|
setNewConnectionData({ ...newData });
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="c-app c-default-layout">
|
<div className="c-app c-default-layout">
|
||||||
@@ -50,6 +57,7 @@ const TheLayout = () => {
|
|||||||
to="/system"
|
to="/system"
|
||||||
icon={<CIcon content={cilSettings} size="xl" className="mr-3" />}
|
icon={<CIcon content={cilSettings} size="xl" className="mr-3" />}
|
||||||
/>
|
/>
|
||||||
|
<SidebarDevices newData={newConnectionData} />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
redirectTo="/devices"
|
redirectTo="/devices"
|
||||||
@@ -71,7 +79,7 @@ const TheLayout = () => {
|
|||||||
/>
|
/>
|
||||||
<div className="c-body">
|
<div className="c-body">
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<WebSocketProvider>
|
<WebSocketProvider setNewConnectionData={onConnectionDataChange}>
|
||||||
<PageContainer t={t} routes={routes} redirectTo="/devices" />
|
<PageContainer t={t} routes={routes} redirectTo="/devices" />
|
||||||
</WebSocketProvider>
|
</WebSocketProvider>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import { CRow, CCol, CCard, CCardBody, CNav, CNavLink, CTabPane, CTabContent } from '@coreui/react';
|
import { CRow, CCol, CCard, CCardBody, CNav, CNavLink, CTabPane, CTabContent } from '@coreui/react';
|
||||||
import DeviceHealth from 'components/DeviceHealth';
|
import DeviceHealth from 'components/DeviceHealth';
|
||||||
import CommandHistory from 'components/CommandHistory';
|
import CommandHistory from 'components/CommandHistory';
|
||||||
@@ -7,7 +7,7 @@ import DeviceLogs from 'components/DeviceLogs';
|
|||||||
import DeviceStatisticsCard from 'components/InterfaceStatistics';
|
import DeviceStatisticsCard from 'components/InterfaceStatistics';
|
||||||
import DeviceActionCard from 'components/DeviceActionCard';
|
import DeviceActionCard from 'components/DeviceActionCard';
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
import axiosInstance from 'utils/axiosInstance';
|
||||||
import { DeviceProvider, useAuth } from 'ucentral-libs';
|
import { DeviceProvider, useAuth, useToast } from 'ucentral-libs';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import ConfigurationDisplay from 'components/ConfigurationDisplay';
|
import ConfigurationDisplay from 'components/ConfigurationDisplay';
|
||||||
import WifiAnalysis from 'components/WifiAnalysis';
|
import WifiAnalysis from 'components/WifiAnalysis';
|
||||||
@@ -23,7 +23,9 @@ const DevicePage = () => {
|
|||||||
const [index, setIndex] = useState(0);
|
const [index, setIndex] = useState(0);
|
||||||
const { currentToken, endpoints } = useAuth();
|
const { currentToken, endpoints } = useAuth();
|
||||||
const [lastStats, setLastStats] = useState(null);
|
const [lastStats, setLastStats] = useState(null);
|
||||||
|
const { addToast } = useToast();
|
||||||
const [status, setStatus] = useState(null);
|
const [status, setStatus] = useState(null);
|
||||||
|
const history = useHistory();
|
||||||
const [deviceConfig, setDeviceConfig] = useState(null);
|
const [deviceConfig, setDeviceConfig] = useState(null);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -64,7 +66,16 @@ const DevicePage = () => {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response) setDeviceConfig({ ...deviceInfo, extendedInfo: response.data.extendedInfo });
|
if (response) setDeviceConfig({ ...deviceInfo, extendedInfo: response.data.extendedInfo });
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
|
if (e.response?.status === 404 || e.response?.status === 400) {
|
||||||
|
addToast({
|
||||||
|
title: t('common.error'),
|
||||||
|
body: t('device.mac_not_found'),
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
history.push('/devices');
|
||||||
|
}
|
||||||
setDeviceConfig(deviceInfo);
|
setDeviceConfig(deviceInfo);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
189
src/pages/SystemPage/ApiStatusCard/index.js
Normal file
189
src/pages/SystemPage/ApiStatusCard/index.js
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
CCard,
|
||||||
|
CCardBody,
|
||||||
|
CCardHeader,
|
||||||
|
CRow,
|
||||||
|
CCol,
|
||||||
|
CButton,
|
||||||
|
CPopover,
|
||||||
|
CModal,
|
||||||
|
CModalBody,
|
||||||
|
CModalHeader,
|
||||||
|
CModalTitle,
|
||||||
|
CDataTable,
|
||||||
|
} from '@coreui/react';
|
||||||
|
import Select from 'react-select';
|
||||||
|
import CIcon from '@coreui/icons-react';
|
||||||
|
import { cilSync, cilX } from '@coreui/icons';
|
||||||
|
import { prettyDate } from 'utils/helper';
|
||||||
|
import useToggle from 'hooks/useToggle';
|
||||||
|
import FormattedDate from 'components/FormattedDate';
|
||||||
|
|
||||||
|
const ApiStatusCard = ({ t, info, reload }) => {
|
||||||
|
const [types, setTypes] = useState([]);
|
||||||
|
const [showCerts, toggleCerts] = useToggle();
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
reload(
|
||||||
|
types.map((v) => v.value),
|
||||||
|
info.endpoint,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CCard>
|
||||||
|
<CCardHeader className="dark-header">
|
||||||
|
<div style={{ fontWeight: '600' }} className=" text-value-lg float-left">
|
||||||
|
{info.title}
|
||||||
|
</div>
|
||||||
|
</CCardHeader>
|
||||||
|
<CCardBody>
|
||||||
|
<CRow>
|
||||||
|
<CCol sm="4">
|
||||||
|
<div block="true">{t('common.endpoint')}:</div>
|
||||||
|
</CCol>
|
||||||
|
<CCol>
|
||||||
|
<div block="true">{info.endpoint}</div>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
<CRow>
|
||||||
|
<CCol sm="4">
|
||||||
|
<div block="true">{t('system.hostname')}:</div>
|
||||||
|
</CCol>
|
||||||
|
<CCol>
|
||||||
|
<div block="true">{info.hostname}</div>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
<CRow>
|
||||||
|
<CCol sm="4">
|
||||||
|
<div block="true">{t('system.os')}:</div>
|
||||||
|
</CCol>
|
||||||
|
<CCol>
|
||||||
|
<div block="true">{info.os}</div>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
<CRow>
|
||||||
|
<CCol sm="4">
|
||||||
|
<div block="true">{t('system.processors')}:</div>
|
||||||
|
</CCol>
|
||||||
|
<CCol>
|
||||||
|
<div block="true">{info.processors}</div>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
<CRow>
|
||||||
|
<CCol sm="4">
|
||||||
|
<div block="true">{t('common.start')}:</div>
|
||||||
|
</CCol>
|
||||||
|
<CCol>
|
||||||
|
<div block="true">
|
||||||
|
{info.start ? <FormattedDate date={info.start} /> : t('common.unknown')}
|
||||||
|
</div>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
<CRow>
|
||||||
|
<CCol sm="4">
|
||||||
|
<div block="true">{t('status.uptime')}:</div>
|
||||||
|
</CCol>
|
||||||
|
<CCol>
|
||||||
|
<div block="true">{info.uptime}</div>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
<CRow>
|
||||||
|
<CCol sm="4">
|
||||||
|
<div block="true">{t('footer.version')}:</div>
|
||||||
|
</CCol>
|
||||||
|
<CCol>
|
||||||
|
<div block="true">{info.version}</div>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
<CRow>
|
||||||
|
<CCol sm="4">
|
||||||
|
<div block="true">{t('common.certificates')}:</div>
|
||||||
|
</CCol>
|
||||||
|
<CCol>
|
||||||
|
<div block="true">
|
||||||
|
{info.certificates?.length > 0 ? (
|
||||||
|
<CButton className="ml-0 pl-0 py-0" color="link" onClick={toggleCerts}>
|
||||||
|
{t('common.details')} ({info.certificates.length})
|
||||||
|
</CButton>
|
||||||
|
) : (
|
||||||
|
<div>{t('common.unknown')}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
<CRow>
|
||||||
|
<CCol sm="4" className="pt-1">
|
||||||
|
<div block="true">{t('system.reload_subsystems')}:</div>
|
||||||
|
</CCol>
|
||||||
|
<CCol>
|
||||||
|
<div block="true">
|
||||||
|
{info.subsystems.length === 0 ? (
|
||||||
|
t('common.unknown')
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<div className="float-left" style={{ width: '85%' }}>
|
||||||
|
<Select
|
||||||
|
isMulti
|
||||||
|
closeMenuOnSelect={false}
|
||||||
|
name="Subsystems"
|
||||||
|
options={info.subsystems.map((sys) => ({ value: sys, label: sys }))}
|
||||||
|
onChange={setTypes}
|
||||||
|
value={types}
|
||||||
|
className="basic-multi-select"
|
||||||
|
classNamePrefix="select"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="float-left text-right" style={{ width: '15%' }}>
|
||||||
|
<CPopover content={t('system.reload')}>
|
||||||
|
<CButton
|
||||||
|
color="primary"
|
||||||
|
variant="outline"
|
||||||
|
onClick={submit}
|
||||||
|
disabled={types.length === 0}
|
||||||
|
>
|
||||||
|
<CIcon content={cilSync} />
|
||||||
|
</CButton>
|
||||||
|
</CPopover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
</CCardBody>
|
||||||
|
<CModal size="lg" show={showCerts} onClose={toggleCerts}>
|
||||||
|
<CModalHeader className="p-1">
|
||||||
|
<CModalTitle className="pl-1 pt-1">{t('common.certificates')}</CModalTitle>
|
||||||
|
<div className="text-right">
|
||||||
|
<CPopover content={t('common.close')}>
|
||||||
|
<CButton color="primary" variant="outline" className="ml-2" onClick={toggleCerts}>
|
||||||
|
<CIcon content={cilX} />
|
||||||
|
</CButton>
|
||||||
|
</CPopover>
|
||||||
|
</div>
|
||||||
|
</CModalHeader>
|
||||||
|
<CModalBody>
|
||||||
|
<CDataTable
|
||||||
|
addTableClasses="table-sm"
|
||||||
|
border
|
||||||
|
items={info?.certificates.map((cert) => ({
|
||||||
|
...cert,
|
||||||
|
expiresOn: prettyDate(cert.expiresOn),
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</CModalBody>
|
||||||
|
</CModal>
|
||||||
|
</CCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ApiStatusCard.propTypes = {
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
|
info: PropTypes.instanceOf(Object).isRequired,
|
||||||
|
reload: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(ApiStatusCard);
|
||||||
@@ -1,22 +1,135 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { v4 as createUuid } from 'uuid';
|
||||||
|
import { CRow, CCol } from '@coreui/react';
|
||||||
|
import { useAuth, useToast } from 'ucentral-libs';
|
||||||
|
import { secondsToDetailed } from 'utils/helper';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SystemPage as Page, useToast, useAuth } from 'ucentral-libs';
|
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
import axiosInstance from 'utils/axiosInstance';
|
||||||
|
import ApiStatusCard from './ApiStatusCard';
|
||||||
|
|
||||||
const SystemPage = () => {
|
const SystemPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { currentToken, endpoints } = useAuth();
|
const { currentToken, endpoints } = useAuth();
|
||||||
const { addToast } = useToast();
|
const { addToast } = useToast();
|
||||||
|
const [endpointsInfo, setEndpointsInfo] = useState([]);
|
||||||
|
|
||||||
|
const getSystemInfo = async (key, endpoint) => {
|
||||||
|
let systemInfo = {
|
||||||
|
title: key,
|
||||||
|
endpoint,
|
||||||
|
hostname: t('common.unknown'),
|
||||||
|
os: t('common.unknown'),
|
||||||
|
processors: t('common.unknown'),
|
||||||
|
uptime: t('common.unknown'),
|
||||||
|
version: t('common.unknown'),
|
||||||
|
certificates: [],
|
||||||
|
subsystems: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${currentToken}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await axiosInstance
|
||||||
|
.get(`${endpoint}/api/v1/system?command=info`, options)
|
||||||
|
.then((newInfo) => {
|
||||||
|
systemInfo = { ...systemInfo, ...newInfo.data };
|
||||||
|
systemInfo.uptime = secondsToDetailed(
|
||||||
|
newInfo.data.uptime,
|
||||||
|
t('common.day'),
|
||||||
|
t('common.days'),
|
||||||
|
t('common.hour'),
|
||||||
|
t('common.hours'),
|
||||||
|
t('common.minute'),
|
||||||
|
t('common.minutes'),
|
||||||
|
t('common.second'),
|
||||||
|
t('common.seconds'),
|
||||||
|
);
|
||||||
|
systemInfo.start = newInfo.data.start;
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
await axiosInstance
|
||||||
|
.post(`${endpoint}/api/v1/system`, { command: 'getsubsystemnames' }, options)
|
||||||
|
.then((newSubs) => {
|
||||||
|
systemInfo.subsystems = newSubs.data.list.sort((a, b) => {
|
||||||
|
if (a < b) return -1;
|
||||||
|
if (a > b) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
return systemInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reload = (subsystems, endpoint) => {
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${currentToken}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const parameters = {
|
||||||
|
command: 'reload',
|
||||||
|
subsystems,
|
||||||
|
};
|
||||||
|
|
||||||
|
axiosInstance
|
||||||
|
.post(`${endpoint}/api/v1/system?command=info`, parameters, options)
|
||||||
|
.then(() => {
|
||||||
|
addToast({
|
||||||
|
title: t('common.success'),
|
||||||
|
body: t('system.success_reload'),
|
||||||
|
color: 'success',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
addToast({
|
||||||
|
title: t('common.error'),
|
||||||
|
body: t('system.error_reloading', { error: e.response?.data?.ErrorDescription }),
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllInfo = async () => {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(endpoints)) {
|
||||||
|
promises.push(getSystemInfo(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const results = await Promise.all(promises);
|
||||||
|
setEndpointsInfo(results);
|
||||||
|
} catch (e) {
|
||||||
|
addToast({
|
||||||
|
title: t('common.error'),
|
||||||
|
body: t('system.error_fetching'),
|
||||||
|
color: 'danger',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getAllInfo();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<CRow>
|
||||||
t={t}
|
{endpointsInfo.map((info) => (
|
||||||
currentToken={currentToken}
|
<CCol sm="12" lg="6" xxl="4" key={createUuid()}>
|
||||||
endpoints={endpoints}
|
<ApiStatusCard t={t} info={info} reload={reload} />
|
||||||
addToast={addToast}
|
</CCol>
|
||||||
axiosInstance={axiosInstance}
|
))}
|
||||||
/>
|
</CRow>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SystemPage;
|
export default SystemPage;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const Routes = () => {
|
|||||||
path="/"
|
path="/"
|
||||||
name="Devices"
|
name="Devices"
|
||||||
render={(props) =>
|
render={(props) =>
|
||||||
currentToken !== '' && Object.keys(endpoints).length !== 0 ? (
|
currentToken !== '' && endpoints && Object.keys(endpoints).length !== 0 ? (
|
||||||
<TheLayout {...props} />
|
<TheLayout {...props} />
|
||||||
) : (
|
) : (
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as axios from 'axios';
|
import * as axios from 'axios';
|
||||||
import axiosRetry from 'axios-retry';
|
import axiosRetry from 'axios-retry';
|
||||||
|
import { LOGOUT_ON_SEC_ERROR_CODES } from 'constants';
|
||||||
|
|
||||||
const axiosInstance = axios.create();
|
const axiosInstance = axios.create();
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ axiosInstance.interceptors.response.use(
|
|||||||
retries += 1;
|
retries += 1;
|
||||||
localStorage.setItem('sec_retries', retries);
|
localStorage.setItem('sec_retries', retries);
|
||||||
}
|
}
|
||||||
if (error.response.data?.ErrorCode === 9) {
|
if (LOGOUT_ON_SEC_ERROR_CODES.includes(error.response.data?.ErrorCode)) {
|
||||||
localStorage.removeItem('access_token');
|
localStorage.removeItem('access_token');
|
||||||
localStorage.removeItem('gateway_endpoints');
|
localStorage.removeItem('gateway_endpoints');
|
||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
export const cleanTimestamp = (timestamp) => timestamp.replace('T', ' ').replace('Z', ' ');
|
|
||||||
|
|
||||||
export const cleanBytesString = (bytes, decimals = 2) => {
|
export const cleanBytesString = (bytes, decimals = 2) => {
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
if (!bytes || bytes === 0) {
|
if (!bytes || bytes === 0) {
|
||||||
@@ -132,6 +130,25 @@ export const compactSecondsToDetailed = (seconds, dayLabel, daysLabel, secondsLa
|
|||||||
return finalString;
|
return finalString;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const extraCompactSecondsToDetailed = (seconds) => {
|
||||||
|
let secondsLeft = seconds;
|
||||||
|
const days = Math.floor(secondsLeft / (3600 * 24));
|
||||||
|
secondsLeft -= days * (3600 * 24);
|
||||||
|
const hours = Math.floor(secondsLeft / 3600);
|
||||||
|
secondsLeft -= hours * 3600;
|
||||||
|
const minutes = Math.floor(secondsLeft / 60);
|
||||||
|
secondsLeft -= minutes * 60;
|
||||||
|
|
||||||
|
let finalString = '';
|
||||||
|
|
||||||
|
finalString = `${finalString}${prettyNumber(days)}:`;
|
||||||
|
finalString = `${finalString}${prettyNumber(hours)}:`;
|
||||||
|
finalString = `${finalString}${prettyNumber(minutes)}:`;
|
||||||
|
finalString = `${finalString}${prettyNumber(secondsLeft)}`;
|
||||||
|
|
||||||
|
return finalString;
|
||||||
|
};
|
||||||
|
|
||||||
export const validateEmail = (email) => {
|
export const validateEmail = (email) => {
|
||||||
const regex = /\S+@\S+\.\S+/;
|
const regex = /\S+@\S+\.\S+/;
|
||||||
return regex.test(email);
|
return regex.test(email);
|
||||||
@@ -143,3 +160,25 @@ export const testRegex = (value, regexString) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const datesSameDay = (first, second) => first.getDate() === second.getDate();
|
export const datesSameDay = (first, second) => first.getDate() === second.getDate();
|
||||||
|
|
||||||
|
const units = {
|
||||||
|
year: 24 * 60 * 60 * 1000 * 365,
|
||||||
|
month: (24 * 60 * 60 * 1000 * 365) / 12,
|
||||||
|
day: 24 * 60 * 60 * 1000,
|
||||||
|
hour: 60 * 60 * 1000,
|
||||||
|
minute: 60 * 1000,
|
||||||
|
second: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const rtf = new Intl.RelativeTimeFormat('en', { localeMatcher: 'best fit', style: 'long' });
|
||||||
|
export const formatDaysAgo = (d1, d2 = new Date()) => {
|
||||||
|
const convertedTimestamp = unixToDateString(d1);
|
||||||
|
const date = new Date(convertedTimestamp);
|
||||||
|
const elapsed = date - d2;
|
||||||
|
|
||||||
|
for (const [key] of Object.entries(units))
|
||||||
|
if (Math.abs(elapsed) > units[key] || key === 'second')
|
||||||
|
return rtf.format(Math.round(elapsed / units[key]), key);
|
||||||
|
|
||||||
|
return prettyDate(date);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user