mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
synced 2025-10-30 02:12:33 +00:00
Compare commits
173 Commits
feature/he
...
v2.2.0-RC1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b119b7e575 | ||
|
|
601c369f2d | ||
|
|
818bdd67ba | ||
|
|
967ef64728 | ||
|
|
6607d52539 | ||
|
|
d48925f9ba | ||
|
|
63c7212685 | ||
|
|
6f5d2170c2 | ||
|
|
0ff74497ad | ||
|
|
cc658f0223 | ||
|
|
c634082608 | ||
|
|
8c3b3d8ac1 | ||
|
|
f791e19ae7 | ||
|
|
c19f6cc535 | ||
|
|
b256b941a6 | ||
|
|
6a2501ad81 | ||
|
|
323a9e3f99 | ||
|
|
352891d7d1 | ||
|
|
b094d88770 | ||
|
|
f9a762ffc1 | ||
|
|
3476a235ab | ||
|
|
c978301d23 | ||
|
|
e61475e9ac | ||
|
|
e440d10bce | ||
|
|
b4b74c9949 | ||
|
|
157f32f094 | ||
|
|
1ce58a361f | ||
|
|
d19b7bafd9 | ||
|
|
797caf0e7b | ||
|
|
0e17964204 | ||
|
|
eca61f9418 | ||
|
|
658a4c0f1a | ||
|
|
50fa8d1c1d | ||
|
|
9e7b252700 | ||
|
|
9fc9163453 | ||
|
|
1f01a1b8b6 | ||
|
|
8055b6891c | ||
|
|
6416f24f93 | ||
|
|
7cbf3c0dfa | ||
|
|
28526885f6 | ||
|
|
da50285edf | ||
|
|
3bf4400836 | ||
|
|
d1afb828d4 | ||
|
|
ac0398d8f3 | ||
|
|
671057e507 | ||
|
|
78fe66a155 | ||
|
|
1900a2cc1e | ||
|
|
d202938370 | ||
|
|
6f66d7134d | ||
|
|
a70d363b62 | ||
|
|
3799d6d187 | ||
|
|
1fd56085f2 | ||
|
|
61de7f7abf | ||
|
|
bd37b67ab8 | ||
|
|
6c0a5c0806 | ||
|
|
5be16c47be | ||
|
|
ebe37afa4a | ||
|
|
4ec232f35c | ||
|
|
53e2909690 | ||
|
|
092653c838 | ||
|
|
d204861c56 | ||
|
|
1494dd5ee9 | ||
|
|
4a3e697d78 | ||
|
|
f234ca2985 | ||
|
|
b64bbb4262 | ||
|
|
41f139fdf1 | ||
|
|
386b94c126 | ||
|
|
36d2d31878 | ||
|
|
3e0dc52376 | ||
|
|
aef48107fe | ||
|
|
9fb2c92a15 | ||
|
|
6dbe24e34f | ||
|
|
656d407ade | ||
|
|
133af50067 | ||
|
|
bb03f0d250 | ||
|
|
4d373fb1c7 | ||
|
|
c21453861f | ||
|
|
587a0aac68 | ||
|
|
58a083109e | ||
|
|
97d326ac88 | ||
|
|
909616efbe | ||
|
|
fa566c2101 | ||
|
|
8a7d740c95 | ||
|
|
69e5c2ef48 | ||
|
|
f202cd7327 | ||
|
|
f6861ec122 | ||
|
|
76a10f3dd7 | ||
|
|
6c16125fa4 | ||
|
|
d6b9d445aa | ||
|
|
5403ce690e | ||
|
|
8bb3f64e70 | ||
|
|
bcbdf3441e | ||
|
|
4c355d8a68 | ||
|
|
44c32f364a | ||
|
|
439bb9667e | ||
|
|
d0daf64ae9 | ||
|
|
6e90ed67ad | ||
|
|
ebc44fa1ea | ||
|
|
5ba716a437 | ||
|
|
b7a427fc6f | ||
|
|
6067fcbc9e | ||
|
|
8b767a8aff | ||
|
|
3c6cf48d9f | ||
|
|
c6b77aecb9 | ||
|
|
3119c8f9a6 | ||
|
|
ada2dea463 | ||
|
|
9001214462 | ||
|
|
77bc5eaabd | ||
|
|
c4263299b9 | ||
|
|
633b59f99f | ||
|
|
35f1ddcbae | ||
|
|
7279acff7e | ||
|
|
91dc8f4c92 | ||
|
|
dfba26b76a | ||
|
|
4954f11c93 | ||
|
|
40f752f48f | ||
|
|
5ccf299003 | ||
|
|
b5f8b3c1eb | ||
|
|
cfe2199354 | ||
|
|
074c973aeb | ||
|
|
2166001174 | ||
|
|
98b82b9fc6 | ||
|
|
a56d92e4eb | ||
|
|
283c8361f2 | ||
|
|
3743fc5cfa | ||
|
|
08d2154489 | ||
|
|
449ce0a927 | ||
|
|
5dff6f76d1 | ||
|
|
5ae181cd89 | ||
|
|
09887a439e | ||
|
|
b1d999a42e | ||
|
|
c857796318 | ||
|
|
68b3693531 | ||
|
|
79fb4c550d | ||
|
|
b02e495028 | ||
|
|
acdd51ffdf | ||
|
|
69f3d96be2 | ||
|
|
c7e3c9d8d0 | ||
|
|
d80002afc8 | ||
|
|
5ade42ca7c | ||
|
|
8636215314 | ||
|
|
4f4fe972d3 | ||
|
|
38a39a2aaa | ||
|
|
1d40cef5d5 | ||
|
|
d9160aac2d | ||
|
|
5125be8094 | ||
|
|
cadf862c7b | ||
|
|
13639b3a01 | ||
|
|
f0c8a4053c | ||
|
|
e47d8b02d3 | ||
|
|
de7faed4c3 | ||
|
|
fbd03c1fc5 | ||
|
|
e23b77c400 | ||
|
|
df6ec36515 | ||
|
|
192836dd2c | ||
|
|
3510f6f90b | ||
|
|
db7394d86c | ||
|
|
15403befc0 | ||
|
|
4d0f7f2de2 | ||
|
|
8cdb1865bf | ||
|
|
c5843a55c3 | ||
|
|
75b2dd2e27 | ||
|
|
83b7560d4d | ||
|
|
946fbbc053 | ||
|
|
54174dd4f2 | ||
|
|
017d1719a6 | ||
|
|
efdf5b2b30 | ||
|
|
4a58852ebd | ||
|
|
cf18a96900 | ||
|
|
d1f4e77e6d | ||
|
|
757b09f031 | ||
|
|
112b01afff | ||
|
|
4a45ad0025 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build Docker image
|
||||
run: docker build -t wlan-cloud-ucentralgw-ui:${{ github.sha }} .
|
||||
run: docker build -t owgw-ui:${{ github.sha }} .
|
||||
|
||||
- name: Tag Docker image
|
||||
run: |
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
echo "Result tags: $TAGS"
|
||||
|
||||
for tag in $TAGS; do
|
||||
docker tag wlan-cloud-ucentralgw-ui:${{ github.sha }} ${{ env.DOCKER_REGISTRY_URL }}/ucentralgw-ui:$tag
|
||||
docker tag owgw-ui:${{ github.sha }} ${{ env.DOCKER_REGISTRY_URL }}/owgw-ui:$tag
|
||||
done
|
||||
|
||||
- name: Log into Docker registry
|
||||
@@ -65,4 +65,4 @@ jobs:
|
||||
- name: Push Docker images
|
||||
if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
docker images | grep ${{ env.DOCKER_REGISTRY_URL }}/ucentralgw-ui | awk -F ' ' '{print $1":"$2}' | xargs -I {} docker push {}
|
||||
docker images | grep ${{ env.DOCKER_REGISTRY_URL }}/owgw-ui | awk -F ' ' '{print $1":"$2}' | xargs -I {} docker push {}
|
||||
|
||||
2
.github/workflows/cleanup.yml
vendored
2
.github/workflows/cleanup.yml
vendored
@@ -16,4 +16,4 @@ jobs:
|
||||
steps:
|
||||
- run: |
|
||||
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/ucentralgw-ui/$PR_BRANCH_TAG"
|
||||
curl -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owgw-ui/$PR_BRANCH_TAG"
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false
|
||||
}
|
||||
],
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": ["@babel/plugin-proposal-class-properties"]
|
||||
"env": {
|
||||
"production": {
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-react-inline-elements",
|
||||
"@babel/plugin-transform-react-constant-elements",
|
||||
[
|
||||
"transform-react-remove-prop-types",
|
||||
{
|
||||
"removeImport": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
|
||||
@@ -19,8 +20,9 @@ module.exports = {
|
||||
preferRelative: true,
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.VERSION': JSON.stringify(process.env.npm_package_version),
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'styles/[name].[contenthash].css',
|
||||
chunkFilename: '[id].[contenthash].css',
|
||||
@@ -53,6 +55,7 @@ module.exports = {
|
||||
template: paths.public + '/index.html',
|
||||
filename: 'index.html',
|
||||
}),
|
||||
new CleanWebpackPlugin(),
|
||||
],
|
||||
|
||||
module: {
|
||||
|
||||
@@ -47,6 +47,7 @@ module.exports = merge(common, {
|
||||
react: path.resolve(__dirname, '../', 'node_modules', 'react'),
|
||||
'react-router-dom': path.resolve('./node_modules/react-router-dom'),
|
||||
'ucentral-libs': path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'),
|
||||
graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'),
|
||||
},
|
||||
},
|
||||
plugins: [new ReactRefreshWebpackPlugin()],
|
||||
|
||||
@@ -4,6 +4,8 @@ const { merge } = require('webpack-merge');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
const common = require('./webpack.common');
|
||||
|
||||
@@ -16,17 +18,65 @@ module.exports = merge(common, {
|
||||
filename: 'js/[name].[contenthash].bundle.js',
|
||||
},
|
||||
plugins: [
|
||||
// new BundleAnalyzerPlugin(),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'styles/[name].[contenthash].css',
|
||||
chunkFilename: '[contenthash].css',
|
||||
}),
|
||||
new CompressionPlugin({
|
||||
filename: '[path]/[name].gz[query]',
|
||||
algorithm: 'gzip',
|
||||
test: /\.js$|\.css$|\.html$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/,
|
||||
threshold: 10240,
|
||||
minRatio: 0.8,
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [],
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [`...`, new TerserPlugin(), new CssMinimizerPlugin()],
|
||||
minimizer: [
|
||||
'...',
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
warnings: false,
|
||||
compress: {
|
||||
comparisons: false,
|
||||
},
|
||||
parse: {},
|
||||
mangle: true,
|
||||
output: {
|
||||
ascii_only: true,
|
||||
},
|
||||
},
|
||||
parallel: true,
|
||||
}),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
nodeEnv: 'production',
|
||||
sideEffects: true,
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
maxInitialRequests: 10,
|
||||
minSize: 0,
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name(module) {
|
||||
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
|
||||
return `npm.${packageName.replace('@', '')}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
modules: [],
|
||||
alias: {
|
||||
graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'),
|
||||
},
|
||||
},
|
||||
performance: {
|
||||
hints: false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/ash
|
||||
# Check if variables are set
|
||||
export DEFAULT_UCENTRALSEC_URL="${DEFAULT_UCENTRALSEC_URL:-https://ucentral.dpaas.arilia.com:16001}"
|
||||
export ALLOW_UCENTRALSEC_CHANGE="${ALLOW_UCENTRALSEC_CHANGE:-false}"
|
||||
export DEFAULT_OWSEC_URL="${DEFAULT_OWSEC_URL:-https://ucentral.dpaas.arilia.com:16001}"
|
||||
export ALLOW_OWSEC_CHANGE="${ALLOW_OWSEC_CHANGE:-false}"
|
||||
|
||||
echo '{"DEFAULT_UCENTRALSEC_URL": "'$DEFAULT_UCENTRALSEC_URL'","ALLOW_UCENTRALSEC_CHANGE": '$ALLOW_UCENTRALSEC_CHANGE'}' > /usr/share/nginx/html/config.json
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v1
|
||||
appVersion: "1.0"
|
||||
description: A Helm chart for Kubernetes
|
||||
name: ucentralgwui
|
||||
name: owgwui
|
||||
version: 0.1.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ucentralgwui
|
||||
# owgwui
|
||||
|
||||
This Helm chart helps to deploy uCentralGW-UI to the Kubernetes clusters. It is mainly used in [assembly chart](https://github.com/Telecominfraproject/wlan-cloud-ucentral-deploy/tree/main/chart) as uCentralGW-UI requires other services as dependencies that are considered in that Helm chart. This chart is purposed to define deployment logic close to the application code itself and define default values that could be overriden during deployment.
|
||||
This Helm chart helps to deploy OpenWIFI Web UI (further on refered as __Web UI__) to the Kubernetes clusters. It is mainly used in [assembly chart](https://github.com/Telecominfraproject/wlan-cloud-ucentral-deploy/tree/main/chart) as Web UI requires other services as dependencies that are considered in that Helm chart. This chart is purposed to define deployment logic close to the application code itself and define default values that could be overriden during deployment.
|
||||
|
||||
|
||||
## TL;DR;
|
||||
@@ -11,7 +11,7 @@ $ helm install .
|
||||
|
||||
## Introduction
|
||||
|
||||
This chart bootstraps an ucentralgwui on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
|
||||
This chart bootstraps the Web UI on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
|
||||
|
||||
## Installing the Chart
|
||||
|
||||
@@ -23,7 +23,7 @@ To install the chart with the release name `my-release`:
|
||||
$ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui@helm?ref=main
|
||||
```
|
||||
|
||||
The command deploys ucentralgwui on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation.
|
||||
The command deploys the Web UI on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation.
|
||||
|
||||
> **Tip**: List all releases using `helm list`
|
||||
|
||||
@@ -46,21 +46,21 @@ The following table lists the configurable parameters of the chart and their def
|
||||
| replicaCount | number | Amount of replicas to be deployed | `1` |
|
||||
| nameOverride | string | Override to be used for application deployment | |
|
||||
| fullnameOverride | string | Override to be used for application deployment (has priority over nameOverride) | |
|
||||
| images.ucentralgwui.repository | string | Docker image repository | |
|
||||
| images.ucentralgwui.tag | string | Docker image tag | `'master'` |
|
||||
| images.ucentralgwui.pullPolicy | string | Docker image pull policy | `'Always'` |
|
||||
| services.ucentralgwui.type | string | uCentralGW-UI service type | `'ClusterIP'` |
|
||||
| services.ucentralgwui.ports.http.servicePort | number | Websocket endpoint port to be exposed on service | `80` |
|
||||
| services.ucentralgwui.ports.http.targetPort | number | Websocket endpoint port to be targeted by service | `80` |
|
||||
| services.ucentralgwui.ports.http.protocol | string | Websocket endpoint protocol | `'TCP'` |
|
||||
| checks.ucentralgwui.liveness.httpGet.path | string | Liveness check path to be used | `'/'` |
|
||||
| checks.ucentralgwui.liveness.httpGet.port | number | Liveness check port to be used (should be pointint to ALB endpoint) | `http` |
|
||||
| checks.ucentralgwui.readiness.httpGet.path | string | Readiness check path to be used | `'/'` |
|
||||
| checks.ucentralgwui.readiness.httpGet.port | number | Readiness check port to be used | `http` |
|
||||
| ingresses.default.enabled | boolean | Defines if uCentralGW-UI should be exposed via Ingress controller | `False` |
|
||||
| ingresses.default.hosts | array | List of hosts for exposed uCentralGW-UI | |
|
||||
| ingresses.default.paths | array | List of paths to be exposed for uCentralGW-UI | |
|
||||
| public_env_variables | hash | Defines list of environment variables to be passed to uCentralGW-UI (required for application configuration) | |
|
||||
| images.owgwui.repository | string | Docker image repository | |
|
||||
| images.owgwui.tag | string | Docker image tag | `'master'` |
|
||||
| images.owgwui.pullPolicy | string | Docker image pull policy | `'Always'` |
|
||||
| services.owgwui.type | string | OpenWIFI Web UI service type | `'ClusterIP'` |
|
||||
| services.owgwui.ports.http.servicePort | number | Websocket endpoint port to be exposed on service | `80` |
|
||||
| services.owgwui.ports.http.targetPort | number | Websocket endpoint port to be targeted by service | `80` |
|
||||
| services.owgwui.ports.http.protocol | string | Websocket endpoint protocol | `'TCP'` |
|
||||
| checks.owgwui.liveness.httpGet.path | string | Liveness check path to be used | `'/'` |
|
||||
| checks.owgwui.liveness.httpGet.port | number | Liveness check port to be used (should be pointint to ALB endpoint) | `http` |
|
||||
| checks.owgwui.readiness.httpGet.path | string | Readiness check path to be used | `'/'` |
|
||||
| checks.owgwui.readiness.httpGet.port | number | Readiness check port to be used | `http` |
|
||||
| ingresses.default.enabled | boolean | Defines if the Web UI should be exposed via Ingress controller | `False` |
|
||||
| ingresses.default.hosts | array | List of hosts for the exposed Web UI | |
|
||||
| ingresses.default.paths | array | List of paths to be exposed for the Web UI | |
|
||||
| public_env_variables | hash | Defines list of environment variables to be passed to the Web UI (required for application configuration) | |
|
||||
|
||||
|
||||
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "ucentralgwui.name" -}}
|
||||
{{- define "owgwui.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
@@ -11,7 +11,7 @@ Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "ucentralgwui.fullname" -}}
|
||||
{{- define "owgwui.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
@@ -27,6 +27,6 @@ If release name contains chart name it will be used as a full name.
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "ucentralgwui.chart" -}}
|
||||
{{- define "owgwui.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
@@ -3,57 +3,59 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "ucentralgwui.fullname" . }}
|
||||
name: {{ include "owgwui.fullname" . }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" . }}
|
||||
helm.sh/chart: {{ include "ucentralgwui.chart" . }}
|
||||
app.kubernetes.io/name: {{ include "owgwui.name" . }}
|
||||
helm.sh/chart: {{ include "owgwui.chart" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" . }}
|
||||
app.kubernetes.io/name: {{ include "owgwui.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- with .Values.services.ucentralgwui.labels }}
|
||||
{{- with .Values.services.owgwui.labels }}
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" . }}
|
||||
app.kubernetes.io/name: {{ include "owgwui.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- with .Values.services.ucentralgwui.labels }}
|
||||
{{- with .Values.services.owgwui.labels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
|
||||
containers:
|
||||
|
||||
- name: ucentralgwui
|
||||
image: "{{ .Values.images.ucentralgwui.repository }}:{{ .Values.images.ucentralgwui.tag }}"
|
||||
imagePullPolicy: {{ .Values.images.ucentralgwui.pullPolicy }}
|
||||
- name: owgwui
|
||||
image: "{{ .Values.images.owgwui.repository }}:{{ .Values.images.owgwui.tag }}"
|
||||
imagePullPolicy: {{ .Values.images.owgwui.pullPolicy }}
|
||||
|
||||
env:
|
||||
- name: KUBERNETES_DEPLOYED
|
||||
value: "{{ now }}"
|
||||
{{- range $key, $value := .Values.public_env_variables }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
|
||||
ports:
|
||||
{{- range $key, $value := .Values.services.ucentralgwui.ports }}
|
||||
{{- range $key, $value := .Values.services.owgwui.ports }}
|
||||
- name: {{ $key }}
|
||||
containerPort: {{ $value.targetPort }}
|
||||
protocol: {{ $value.protocol }}
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.checks.ucentralgwui.liveness }}
|
||||
{{- if .Values.checks.owgwui.liveness }}
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.checks.ucentralgwui.liveness | nindent 12 }}
|
||||
{{- toYaml .Values.checks.owgwui.liveness | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.checks.ucentralgwui.readiness }}
|
||||
{{- if .Values.checks.owgwui.readiness }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.checks.ucentralgwui.readiness | nindent 12 }}
|
||||
{{- toYaml .Values.checks.owgwui.readiness | nindent 12 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.resources }}
|
||||
@@ -64,7 +66,7 @@ spec:
|
||||
imagePullSecrets:
|
||||
{{- range $image, $imageValue := .Values.images }}
|
||||
{{- if $imageValue.regcred }}
|
||||
- name: {{ include "ucentralgwui.fullname" $root }}-{{ $image }}-regcred
|
||||
- name: {{ include "owgwui.fullname" $root }}-{{ $image }}-regcred
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "ucentralgwui.fullname" $root }}-{{ $ingress }}
|
||||
name: {{ include "owgwui.fullname" $root }}-{{ $ingress }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }}
|
||||
helm.sh/chart: {{ include "ucentralgwui.chart" $root }}
|
||||
app.kubernetes.io/name: {{ include "owgwui.name" $root }}
|
||||
helm.sh/chart: {{ include "owgwui.chart" $root }}
|
||||
app.kubernetes.io/instance: {{ $root.Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ $root.Release.Service }}
|
||||
{{- with $ingressValue.annotations }}
|
||||
@@ -37,7 +37,7 @@ spec:
|
||||
{{- range $ingressValue.paths }}
|
||||
- path: {{ .path }}
|
||||
backend:
|
||||
serviceName: {{ include "ucentralgwui.fullname" $root }}-{{ .serviceName }}
|
||||
serviceName: {{ include "owgwui.fullname" $root }}-{{ .serviceName }}
|
||||
servicePort: {{ .servicePort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -10,11 +10,11 @@ kind: Secret
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
metadata:
|
||||
labels:
|
||||
app.kuberentes.io/name: {{ include "ucentralgwui.name" $root }}
|
||||
helm.sh/chart: {{ include "ucentralgwui.chart" $root }}
|
||||
app.kuberentes.io/name: {{ include "owgwui.name" $root }}
|
||||
helm.sh/chart: {{ include "owgwui.chart" $root }}
|
||||
app.kubernetes.io/instance: {{ $root.Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ $root.Release.Service }}
|
||||
name: {{ include "ucentralgwui.fullname" $root }}-{{ $image }}-regcred
|
||||
name: {{ include "owgwui.fullname" $root }}-{{ $image }}-regcred
|
||||
data:
|
||||
.dockerconfigjson: {{ template "imagePullSecret" $imageValue.regcred }}
|
||||
{{- end }}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "ucentralgwui.fullname" $root }}-{{ $service }}
|
||||
name: {{ include "owgwui.fullname" $root }}-{{ $service }}
|
||||
{{- with $serviceValue.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }}
|
||||
helm.sh/chart: {{ include "ucentralgwui.chart" $root }}
|
||||
app.kubernetes.io/name: {{ include "owgwui.name" $root }}
|
||||
helm.sh/chart: {{ include "owgwui.chart" $root }}
|
||||
app.kubernetes.io/instance: {{ $root.Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ $root.Release.Service }}
|
||||
|
||||
@@ -39,7 +39,7 @@ spec:
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
selector:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }}
|
||||
app.kubernetes.io/name: {{ include "owgwui.name" $root }}
|
||||
app.kubernetes.io/instance: {{ $root.Release.Name }}
|
||||
{{- with $serviceValue.labels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
|
||||
@@ -5,13 +5,13 @@ nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
images:
|
||||
ucentralgwui:
|
||||
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/ucentralgw-ui
|
||||
tag: main
|
||||
owgwui:
|
||||
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
|
||||
tag: v2.2.0-RC1
|
||||
pullPolicy: Always
|
||||
|
||||
services:
|
||||
ucentralgwui:
|
||||
owgwui:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
http:
|
||||
@@ -20,7 +20,7 @@ services:
|
||||
protocol: TCP
|
||||
|
||||
checks:
|
||||
ucentralgwui:
|
||||
owgwui:
|
||||
liveness:
|
||||
httpGet:
|
||||
path: /
|
||||
@@ -37,7 +37,7 @@ ingresses:
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
# tls:
|
||||
# - secretName: '{{ include "ucentralgwui.fullname" . }}-default-tls' # template may be used
|
||||
# - secretName: '{{ include "owgwui.fullname" . }}-default-tls' # template may be used
|
||||
# cert: |
|
||||
# CERT_HERE_IN_PEM
|
||||
# key: |
|
||||
@@ -48,7 +48,7 @@ ingresses:
|
||||
- chart-example.local
|
||||
paths:
|
||||
- path: /
|
||||
serviceName: ucentralgwui
|
||||
serviceName: owgwui
|
||||
servicePort: http
|
||||
|
||||
resources: {}
|
||||
|
||||
5023
package-lock.json
generated
5023
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"name": "ucentral-client",
|
||||
"version": "0.9.14",
|
||||
"version": "2.2.2",
|
||||
"dependencies": {
|
||||
"@coreui/coreui": "^3.4.0",
|
||||
"@coreui/icons": "^2.0.1",
|
||||
"@coreui/icons-react": "^1.1.0",
|
||||
"@coreui/react": "^3.4.6",
|
||||
"@coreui/react-chartjs": "^1.1.0",
|
||||
"apexcharts": "^3.27.1",
|
||||
"axios": "^0.21.1",
|
||||
"axios-retry": "^3.1.9",
|
||||
"dagre": "^0.8.5",
|
||||
"i18next": "^20.3.1",
|
||||
"i18next-browser-languagedetector": "^6.1.2",
|
||||
"i18next-http-backend": "^1.2.6",
|
||||
@@ -16,12 +18,15 @@
|
||||
"react": "^17.0.2",
|
||||
"react-apexcharts": "^1.3.9",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-flow-renderer": "^9.6.6",
|
||||
"react-i18next": "^11.11.0",
|
||||
"react-paginate": "^7.1.3",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-select": "^4.3.1",
|
||||
"react-tooltip": "^4.2.21",
|
||||
"react-widgets": "^5.1.1",
|
||||
"sass": "^1.35.1",
|
||||
"ucentral-libs": "^0.8.7",
|
||||
"ucentral-libs": "^0.9.41",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"main": "index.js",
|
||||
@@ -58,6 +63,7 @@
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"compression-webpack-plugin": "^8.0.1",
|
||||
"copy-webpack-plugin": "^7.0.0",
|
||||
"css-loader": "^5.2.6",
|
||||
"css-minimizer-webpack-plugin": "^2.0.0",
|
||||
@@ -84,6 +90,7 @@
|
||||
"style-loader": "^2.0.0",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"webpack": "^5.40.0",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-merge": "^5.8.0"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"actions": "Aktionen",
|
||||
"blink": "LEDs Blinken",
|
||||
"configure": "Konfigurieren",
|
||||
"connect": "Konsole öffnen",
|
||||
@@ -7,9 +8,10 @@
|
||||
"factory_reset": "Auf Werkseinstellungen zurückgesetzt",
|
||||
"firmware_upgrade": "Firmware Aktualisierung",
|
||||
"reboot": "Gerät neustarten",
|
||||
"telemetry": "Telemetrie",
|
||||
"title": "Geräte Administrations",
|
||||
"trace": "Tcpdump starten",
|
||||
"wifi_scan": "WiFi Scan"
|
||||
"wifi_scan": "Wi-Fi Scan"
|
||||
},
|
||||
"blink": {
|
||||
"blink": "LEDs Blinken",
|
||||
@@ -21,18 +23,28 @@
|
||||
},
|
||||
"commands": {
|
||||
"error": "Fehler beim Senden des Befehls!",
|
||||
"error_delete_log": "Fehler beim Versuch zu löschen: {{error}}",
|
||||
"event_queue": "Ereigniswarteschlange",
|
||||
"success": "Befehl wurde erfolgreich übermittelt",
|
||||
"title": "Gerätebefehle"
|
||||
"title": "Gerätebefehle",
|
||||
"unable_queue": "Anfrage für Ereigniswarteschlange kann nicht abgeschlossen werden: {{error}}"
|
||||
},
|
||||
"common": {
|
||||
"access_policy": "Zugangsrichtlinien",
|
||||
"add": "Hinzufügen",
|
||||
"adding_ellipsis": "Hinzufügen ...",
|
||||
"are_you_sure": "Bist du sicher?",
|
||||
"back_to_login": "Zurück zur Anmeldung",
|
||||
"back_to_start": "Zurück zum Start",
|
||||
"by": "Durch",
|
||||
"cancel": "Abbrechen",
|
||||
"certificate": "Zertifikat",
|
||||
"certificates": "Zertifikate",
|
||||
"clear": "Löschen",
|
||||
"close": "Schließen",
|
||||
"command": "Befehl",
|
||||
"commands": "Befehle",
|
||||
"commands_executed": "Ausgeführte Befehle",
|
||||
"compatible": "kompatibel",
|
||||
"completed": "Abgeschlossen",
|
||||
"config_id": "Konfigurations ID",
|
||||
@@ -40,78 +52,168 @@
|
||||
"connected": "Verbindung wurde hergestellt",
|
||||
"copied": "kopiert!",
|
||||
"copy_to_clipboard": "In die Zwischenablage kopieren",
|
||||
"create": "Erstellen",
|
||||
"created": "Erstellt",
|
||||
"created_by": "Erstellt von",
|
||||
"current": "Aktuell",
|
||||
"custom_date": "Benutzerdefiniertes Datum",
|
||||
"dashboard": "Instrumententafel",
|
||||
"date": "Datum",
|
||||
"day": "tag",
|
||||
"days": "tage",
|
||||
"delete": "Löschen",
|
||||
"delete_device": "Gerät löschen",
|
||||
"details": "Einzelheiten",
|
||||
"device": "Gerät #{{serialNumber}}",
|
||||
"device_dashboard": "Geräte-Dashboard",
|
||||
"device_delete": "Gerät Nr.{{serialNumber}}löschen",
|
||||
"device_deleted": "Gerät erfolgreich gelöscht",
|
||||
"device_health": "Gerätezustand",
|
||||
"device_list": "Liste der Geräte",
|
||||
"device_page": "Geräte",
|
||||
"device_page": "Aussicht",
|
||||
"device_status": "Gerätestatus",
|
||||
"devices": "Geräte",
|
||||
"devices_using_latest": "Geräte mit der neuesten Firmware",
|
||||
"devices_using_unknown": "Geräte mit unbekannter Firmware",
|
||||
"dismiss": "entlassen",
|
||||
"do_now": "Sofort",
|
||||
"download": "Herunterladen",
|
||||
"duration": "Dauer",
|
||||
"edit": "Bearbeiten",
|
||||
"edit_user": "Bearbeiten",
|
||||
"email_address": "E-Mail-Addresse",
|
||||
"endpoint": "Endpunkt",
|
||||
"endpoints": "Endpunkte",
|
||||
"error": "Fehler",
|
||||
"error_adding_note": "Fehler beim Hinzufügen einer Notiz",
|
||||
"execute_now": "Möchten Sie diesen Befehl jetzt ausführen?",
|
||||
"executed": "Ausgeführt",
|
||||
"exit": "Ausgang",
|
||||
"firmware": "Firmware",
|
||||
"firmware_dashboard": "Firmware-Dashboard",
|
||||
"firmware_installed": "Firmware installiert",
|
||||
"forgot_password": "Haben Sie Ihr Passwort vergessen?",
|
||||
"forgot_password_title": "Passwort vergessen",
|
||||
"from": "Von",
|
||||
"general_error": "API-Fehler, wenden Sie sich bitte an Ihren Administrator",
|
||||
"hide": "verbergen",
|
||||
"hour": "stunde",
|
||||
"hours": "std",
|
||||
"id": "ID",
|
||||
"ip_address": "IP Adresse",
|
||||
"items_per_page": "Objekte pro Seite:",
|
||||
"last_dashboard_refresh": "Letzte Dashboard-Aktualisierung",
|
||||
"later_tonight": "Später am Abend",
|
||||
"latest": "Neueste",
|
||||
"list": "Liste",
|
||||
"loading_ellipsis": "Wird geladen...",
|
||||
"loading_more_ellipsis": "Mehr laden ...",
|
||||
"logout": "Ausloggen",
|
||||
"mac": "MAC-Adresse",
|
||||
"manufacturer": "Hersteller",
|
||||
"memory_used": "Verwendeter Speicher",
|
||||
"minute": "Minute",
|
||||
"minutes": "protokoll",
|
||||
"modified": "Geändert",
|
||||
"na": "(unbekannt)",
|
||||
"need_date": "Du brauchst ein Datum...",
|
||||
"no": "Nein",
|
||||
"no_devices_found": "Keine Geräte gefunden",
|
||||
"no_items": "Keine Gegenstände",
|
||||
"none": "Keiner",
|
||||
"not_connected": "Nicht verbunden",
|
||||
"of_connected": "% der Geräte",
|
||||
"off": "Aus",
|
||||
"on": "An",
|
||||
"optional": "Wahlweise",
|
||||
"overall_health": "Allgemeine Gesundheit",
|
||||
"password_policy": "Kennwortrichtlinie",
|
||||
"preview": "Vorschau",
|
||||
"recorded": "Verzeichnet",
|
||||
"refresh": "Aktualisierung",
|
||||
"refresh_device": "Gerät aktualisieren",
|
||||
"required": "Erforderlich",
|
||||
"result": "Ergebnis",
|
||||
"save": "Sparen",
|
||||
"saved": "Gerettet!",
|
||||
"saving": "Speichern ...",
|
||||
"schedule": "Zeitplan",
|
||||
"search": "Geräte suchen",
|
||||
"second": "zweite",
|
||||
"seconds": "sekunden",
|
||||
"seconds_elapsed": "Sekunden verstrichen",
|
||||
"serial_number": "Seriennummer",
|
||||
"show_all": "Zeige alles",
|
||||
"socket_connection_closed": "Verbindung geschlossen!",
|
||||
"start": "Start",
|
||||
"stop_editing": "Stoppen Sie die Bearbeitung",
|
||||
"submit": "Absenden",
|
||||
"submitted": "Eingereicht",
|
||||
"success": "Erfolg",
|
||||
"system": "System",
|
||||
"table": "Tabelle",
|
||||
"timestamp": "Zeit",
|
||||
"to": "zu",
|
||||
"type": "Art",
|
||||
"type_for_options": "Geben Sie den Wert ein, den Sie erstellen müssen...",
|
||||
"type_for_options_format": "Geben Sie einen Wert im gültigen Format ein ({{format}})...",
|
||||
"unable_to_connect": "Keine Verbindung zum Gerät möglich",
|
||||
"unable_to_delete": "Löschen nicht möglich",
|
||||
"unknown": "unbekannte",
|
||||
"up_to_date": "Aktuelle Geräte",
|
||||
"uptimes": "Betriebszeiten",
|
||||
"uuid": "UUID",
|
||||
"vendors": "Anbieter",
|
||||
"view_more": "Mehr anzeigen",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"configuration": {
|
||||
"add_configuration": "Konfiguration hinzufügen",
|
||||
"add_new_block": "Neuen Konfigurationsblock hinzufügen",
|
||||
"add_or_link": "Verlinken oder hinzufügen",
|
||||
"cannot_delete": "Diese Konfiguration kann nicht gelöscht werden, da sie von mindestens einer Entität, einem Veranstaltungsort oder einem Gerät verwendet wird",
|
||||
"choose_section": "Welchen Abschnitt soll dieser Block enthalten?",
|
||||
"configuration_browser": "Konfigurationsbrowser",
|
||||
"configurations": "Konfigurationen",
|
||||
"create": "Konfiguration erstellen",
|
||||
"create_config": "Neue Konfiguration erstellen",
|
||||
"create_new_configuration": "Neues Konfigurationselement erstellen",
|
||||
"created": "Erstellt",
|
||||
"creation_success": "Konfiguration erfolgreich erstellt!",
|
||||
"currently_associated": "Aktuell zugeordnete Konfiguration: {{config}}",
|
||||
"currently_selected_config": "Derzeit ausgewählte Konfiguration: {{config}}",
|
||||
"delete_config": "Konfiguration löschen",
|
||||
"details": "Gerätedetails",
|
||||
"device_password": "Passwort",
|
||||
"last_configuration_change": "Letzte Konfigurationsänderung",
|
||||
"device_type": "Gerätetyp",
|
||||
"device_types": "Gerätetypen",
|
||||
"edit_configuration": "Konfiguration bearbeiten",
|
||||
"error_delete": "Fehler beim Versuch zu löschen: {{error}}",
|
||||
"error_fetching_config": "Fehler beim Abrufen der Konfiguration",
|
||||
"error_trying_delete": "Fehler beim Versuch zu löschen: {{error}}",
|
||||
"error_update": "Fehler: {{error}}",
|
||||
"explanation": "Erläuterung",
|
||||
"last_configuration_change": "Konfigurationsänderung",
|
||||
"last_configuration_download": "Letzter Konfigurations-Download",
|
||||
"location": "Ort",
|
||||
"need_device_type": "Jede Konfiguration muss mindestens einen Gerätetyp unterstützen",
|
||||
"no_associated_config": "Keine zugehörige Konfiguration",
|
||||
"no_associated_configuration": "Keine zugeordneten Konfigurationen",
|
||||
"note": "Hinweis",
|
||||
"notes": "Anmerkungen",
|
||||
"owner": "Inhaber",
|
||||
"select_configuration": "Wählen Sie diese Konfiguration",
|
||||
"success_block_delete": "Konfigurationsblock erfolgreich gelöscht",
|
||||
"success_update": "Konfiguration erfolgreich aktualisiert!",
|
||||
"successful_delete": "Konfiguration gelöscht!",
|
||||
"support_all": "Alle unterstützen",
|
||||
"supported_device_types": "Unterstützte Gerätetypen",
|
||||
"title": "Gerätekonfiguration",
|
||||
"type": "Gerätetyp",
|
||||
"used_by": "Benutzt von",
|
||||
"used_by_details": "{{entities}} Entitäten, {{venues}} Veranstaltungsorte und {{devices}} Geräte",
|
||||
"uuid": "Konfigurations-ID",
|
||||
"view_in_use": "In Verwendung anzeigen",
|
||||
"view_json": "Rohe Konfiguration anzeigen"
|
||||
},
|
||||
"configure": {
|
||||
@@ -121,6 +223,9 @@
|
||||
"title": "Gerät konfigurieren",
|
||||
"valid_json": "Sie müssen ein gültiges JSON eingeben"
|
||||
},
|
||||
"connect": {
|
||||
"error_trying_to_connect": "Fehler beim Versuch, eine Verbindung zum Gerät herzustellen: {{error}}"
|
||||
},
|
||||
"delete_command": {
|
||||
"explanation": "Möchten Sie diesen Befehl wirklich löschen? Diese Aktion ist nicht umkehrbar.",
|
||||
"title": "Befehl löschen"
|
||||
@@ -131,11 +236,37 @@
|
||||
"explanation": "Dadurch werden alle {{object}} vor dem von Ihnen gewählten Datum gelöscht. Seien Sie vorsichtig, diese Aktion ist nicht umkehrbar.",
|
||||
"healthchecks_title": "Healthchecks löschen"
|
||||
},
|
||||
"device": {
|
||||
"error_fetching_device": "Fehler beim Abrufen der Geräteinformationen: {{error}}",
|
||||
"error_fetching_devices": "Fehler beim Abrufen von Geräten: {{error}}"
|
||||
},
|
||||
"device_logs": {
|
||||
"log": "Protokoll",
|
||||
"severity": "Wichtigkeit",
|
||||
"title": "Geräteprotokolle"
|
||||
},
|
||||
"entity": {
|
||||
"add_child": "Untergeordnete Entität zu {{entityName}}hinzufügen",
|
||||
"add_failure": "Fehler, der Server hat zurückgegeben: {{error}}",
|
||||
"add_root": "Root-Entität hinzufügen",
|
||||
"add_success": "Entität erfolgreich erstellt!",
|
||||
"assigned_inventory": "Zugewiesenes Inventar",
|
||||
"cannot_delete": "Entitäten mit untergeordneten Elementen können nicht gelöscht werden. Löschen Sie die untergeordneten Elemente dieser Entität, um sie löschen zu können.",
|
||||
"currently_selected_entity": "Derzeit ausgewähltes Unternehmen: {{config}}",
|
||||
"currently_selected_venue": "Aktuell ausgewählter Veranstaltungsort: {{config}}",
|
||||
"delete_success": "Entität erfolgreich gelöscht",
|
||||
"delete_warning": "Achtung: Dieser Vorgang kann nicht rückgängig gemacht werden",
|
||||
"edit_failure": "Aktualisierung fehlgeschlagen : {{error}}",
|
||||
"entities": "Entitäten",
|
||||
"entity": "Entität",
|
||||
"error_fetch_entity": "Fehler beim Abrufen von Entitätsinformationen",
|
||||
"error_fetching": "Fehler beim Abrufen von Entitäten",
|
||||
"error_saving": "Fehler beim Speichern der Entität",
|
||||
"not_assigned": "Nicht zugeordnet",
|
||||
"only_unassigned": "Nur nicht zugewiesen",
|
||||
"valid_serial": "Muss eine gültige Seriennummer sein (12 HEX-Zeichen)",
|
||||
"venues": "Veranstaltungsorte"
|
||||
},
|
||||
"factory_reset": {
|
||||
"redirector": "Gatewaykonfiguration beibehalten:",
|
||||
"reset": "Zurücksetzen",
|
||||
@@ -143,6 +274,35 @@
|
||||
"title": "Gerät auf Werkseinstellungen zurücksetzen",
|
||||
"warning": "Achtung: Nach dem Absenden kann dies nicht rückgängig gemacht werden"
|
||||
},
|
||||
"firmware": {
|
||||
"average_age": "Durchschnittliches Firmware-Alter",
|
||||
"choose_custom": "Wählen",
|
||||
"details_title": "Bild #{{image}} Details",
|
||||
"device_type": "Gerätetyp",
|
||||
"device_types": "Gerätetypen",
|
||||
"downloads": "Downloads",
|
||||
"error_fetching_latest": "Fehler beim Abrufen der neuesten Firmware",
|
||||
"from_release": "Von",
|
||||
"history_title": "Geschichte",
|
||||
"image": "Bild",
|
||||
"image_date": "Bilddatum",
|
||||
"installed_firmware": "Installierte Firmware",
|
||||
"latest_version_installed": "Neueste Version installiert Version",
|
||||
"newer_firmware_available": "Neuere Versionen verfügbar",
|
||||
"reinstall_latest": "Neu installieren",
|
||||
"revision": "Revision",
|
||||
"show_dev": "Dev-Releases anzeigen",
|
||||
"size": "Größe",
|
||||
"status": "Firmware-Status",
|
||||
"title": "Firmware",
|
||||
"to_release": "Zu",
|
||||
"unknown_firmware_status": "Unbekannter Firmware-Status",
|
||||
"upgrade": "Aktualisierung",
|
||||
"upgrade_command_submitted": "Upgrade-Befehl erfolgreich gesendet",
|
||||
"upgrade_to_latest": "Neueste",
|
||||
"upgrade_to_version": "Upgrade auf diese Revision",
|
||||
"upgrading": "Upgrade durchführen..."
|
||||
},
|
||||
"footer": {
|
||||
"coreui_for_react": "CoreUI für React",
|
||||
"powered_by": "Unterstützt von",
|
||||
@@ -152,13 +312,111 @@
|
||||
"sanity": "Gesundheitzustand",
|
||||
"title": "Gesundheitzustand"
|
||||
},
|
||||
"inventory": {
|
||||
"add_child": "Untergeordneten Veranstaltungsort hinzufügen",
|
||||
"add_child_venue": "Untergeordneten Veranstaltungsort zu {{entityName}}hinzufügen",
|
||||
"add_tag": "Tag erstellen",
|
||||
"add_tag_to": "Neues Gerät zu {{name}}hinzufügen",
|
||||
"add_venue": "Veranstaltungsort hinzufügen",
|
||||
"assign_entity_instructions": "Sie können die Entität, der dieses Tag zugewiesen werden soll, entweder über das Menü unten finden oder die UUID der Entität manuell in das Feld oben einfügen.",
|
||||
"assign_error": "Fehler beim Versuch, Tag zuzuweisen",
|
||||
"assign_to_entity": "Zu Entität zuweisen",
|
||||
"bulk_delete_assigned": "Möchten Sie zugewiesene Geräte in Ihrer Datei massenweise löschen?",
|
||||
"bulk_delete_assigned_warning": "Achtung: Diese Aktion ist nicht umkehrbar",
|
||||
"bulk_delete_devices": "Massenlöschgeräte",
|
||||
"bulk_delete_devices_not_found": "{{number}} Geräte nicht gefunden",
|
||||
"bulk_delete_explanation": "Verwenden Sie zum Massenlöschen von Geräten eine CSV-Datei mit einer Spalte namens SerialNumber",
|
||||
"bulk_delete_test": "Datei validieren",
|
||||
"close_entity_menu": "Menü \"Entität bearbeiten\" schließen",
|
||||
"delete_devices": "Geräte löschen",
|
||||
"delete_errors": "{{number}} Gerätefehler löschen",
|
||||
"delete_tag": "Tag löschen",
|
||||
"delete_venue": "Veranstaltungsort endgültig löschen",
|
||||
"deleted_devices": "{{number}} Gelöschte Geräte",
|
||||
"deleting": "Löschen ...",
|
||||
"deletion_failure": "Löschfehler",
|
||||
"devices_assigned": "{{number}} vorhandene Geräte zugewiesen und aktualisiert",
|
||||
"devices_created": "{{number}} Geräte erstellt",
|
||||
"devices_deleted": "Geräte gelöscht",
|
||||
"devices_errors_while_creating": "{{number}} Geräteerstellung fehlgeschlagen",
|
||||
"devices_errors_while_updating": "{{number}} Geräteupdates fehlgeschlagen",
|
||||
"devices_found_assigned": "{{number}} Geräte gefunden und bereits einer Entität oder einem Veranstaltungsort zugewiesen",
|
||||
"devices_found_unassigned": "{{number}} Geräte gefunden, aber nicht zugewiesen",
|
||||
"devices_imported": " Importierte Geräte",
|
||||
"devices_not_found": "{{number}} Geräte ohne Konflikt",
|
||||
"devices_tested": "Getestete Geräte",
|
||||
"duplicate_serial": "Seriennummer bereits in Datei verwendet (Duplikat)",
|
||||
"error_create_venue": "Fehler beim Erstellen des Veranstaltungsortes",
|
||||
"error_delete_tag": "Fehler beim Löschen des Inventar-Tags",
|
||||
"error_get_venue": "Fehler beim Abrufen von Veranstaltungsorten",
|
||||
"error_retrieving": "Beim Abrufen von Inventar-Tags ist ein Fehler aufgetreten",
|
||||
"error_unassign": "Fehler beim Aufheben der Zuweisung",
|
||||
"error_update_venue": "Fehler beim Aktualisieren des Veranstaltungsorts",
|
||||
"error_venue_delete": "Fehler beim Löschen des Veranstaltungsortes",
|
||||
"error_within_file": "{{number}} Geräte mit falschen Informationen in der Datei (werden ignoriert)",
|
||||
"file_error": "Es scheint ein Fehler in Ihrer Datei zu sein. Bitte stellen Sie sicher, dass die Datei im CSV-Format vorliegt und die oben genannten 5 Spalten in der ersten Zeile der Datei enthält",
|
||||
"final_delete_results": "Endgültige Löschergebnisse",
|
||||
"final_import_results": "Endgültige Importergebnisse",
|
||||
"import_assigned_devices": "Möchten Sie sie mit diesem Import neu zuweisen?",
|
||||
"import_assigned_devices_explanation": "Einige Geräte haben Konflikte mit bereits zugewiesenen Geräten. Sie sollten diese Probleme vor dem Importieren beheben.",
|
||||
"import_devices": "Geräte importieren",
|
||||
"import_devices_explanation": "Für den Massenimport von Geräten müssen Sie eine CSV-Datei mit den folgenden Spalten verwenden: SerialNumber,Name,Description,DeviceType,NoteText",
|
||||
"import_devices_to": "Geräte nach {{name}}importieren",
|
||||
"import_existing_devices": "Aktualisieren und zuweisen?",
|
||||
"import_existing_devices_explanation": "Einige Geräte sind bereits im Inventar vorhanden und nicht zugewiesen.",
|
||||
"importing": "Importieren ...",
|
||||
"last_modification": "Letzte Änderung",
|
||||
"no_devices_to_delete": "Keine Geräte zum Löschen",
|
||||
"no_devices_to_import": "Keine gültigen Geräte zum Erstellen oder Aktualisieren!",
|
||||
"note_text": "Text notieren",
|
||||
"passed_tests": "Alle Tests wurden bestanden, Ihre Geräte sind bereit für den Import!",
|
||||
"serial_number_required": "Fehler: Seriennummer fehlt",
|
||||
"showing_top_10": "Hier ist eine Vorschau der Informationen, die wir aus Ihrer Datei abgerufen haben:",
|
||||
"sub_venues": "Unterräume",
|
||||
"subscriber": "Teilnehmer",
|
||||
"successful_assign": "Tag erfolgreich zugewiesen",
|
||||
"successful_tag_delete": "Inventar-Tag erfolgreich gelöscht",
|
||||
"successful_tag_update": "Tag erfolgreich aktualisiert",
|
||||
"successful_unassign": "Vorgang zum Aufheben der Zuweisung war erfolgreich",
|
||||
"successful_venue_create": "Erfolgreich erstellter Veranstaltungsort",
|
||||
"successful_venue_delete": "Veranstaltungsort erfolgreich gelöscht",
|
||||
"successful_venue_update": "Erfolgreich aktualisierter Veranstaltungsort",
|
||||
"tag_created": "Inventar-Tag erfolgreich erstellt",
|
||||
"tag_creation_error": "Fehler beim Versuch, Inventar-Tag zu erstellen",
|
||||
"tag_update_error": "Fehler beim Aktualisieren des Tags",
|
||||
"tags_assigned_to": "Inventar-Tags {{name}}zugewiesen",
|
||||
"test_import": "Importdaten validieren",
|
||||
"test_results": "Testergebnisse",
|
||||
"title": "Inventar",
|
||||
"type_invalid": "Fehler: Ungültiger Gerätetyp",
|
||||
"unassign": "Zuordnung aufheben",
|
||||
"unassign_tag": "Tag von Entität zuweisen",
|
||||
"unassigned_deleted_devices": "{{number}} Geräte gelöscht und nicht zugewiesen",
|
||||
"unassigned_tags": "Nicht zugewiesene Tags",
|
||||
"validating_import_file": "Importdatei und -daten werden validiert...",
|
||||
"venue": "Tagungsort"
|
||||
},
|
||||
"login": {
|
||||
"change_password": "Ändere das Passwort",
|
||||
"change_password_error": "Fehler beim Ändern des Passworts. Stellen Sie sicher, dass das neue Passwort gültig ist, indem Sie die Seite \"Passwortrichtlinie\" besuchen",
|
||||
"change_password_instructions": "Geben Sie Ihr neues Passwort ein und bestätigen Sie es",
|
||||
"changing_password": "Passwort ändern...",
|
||||
"confirm_new_password": "Bestätige neues Passwort",
|
||||
"different_passwords": "Sie müssen das gleiche Passwort zweimal eingeben",
|
||||
"forgot_password_error": "Fehler beim Versuch, eine E-Mail mit vergessenem Passwort zu senden. Stellen Sie sicher, dass diese Benutzer-ID mit einem Konto verknüpft ist.",
|
||||
"forgot_password_explanation": "Geben Sie Ihren Benutzernamen ein, um eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts zu erhalten",
|
||||
"forgot_password_success": "Sie sollten in Kürze eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts erhalten. Bitte überprüfen Sie Ihren Spam, wenn Sie die E-Mail nicht finden können",
|
||||
"logging_in": "Einloggen...",
|
||||
"login": "Anmeldung",
|
||||
"login_error": "Anmeldefehler, bestätigen Sie, dass Ihr Benutzername, Ihr Passwort und Ihre Gateway-URL gültig sind",
|
||||
"login_error": "Anmeldefehler, stellen Sie sicher, dass die von Ihnen angegebenen Informationen gültig sind",
|
||||
"new_password": "Neues Kennwort",
|
||||
"password": "Passwort",
|
||||
"please_enter_gateway": "Bitte geben Sie eine uCentralSec-URL ein",
|
||||
"please_enter_password": "Bitte geben Sie Ihr Passwort ein",
|
||||
"please_enter_username": "Bitte geben Sie Ihren Benutzernamen ein",
|
||||
"previously_used": "Passwort wurde zuvor verwendet",
|
||||
"send_forgot": "E-Mail senden",
|
||||
"sending_ellipsis": "Senden…",
|
||||
"sign_in_to_account": "Melden Sie sich bei Ihrem Konto an",
|
||||
"url": "uCentralSec-URL",
|
||||
"username": "Benutzername"
|
||||
@@ -179,17 +437,22 @@
|
||||
"scanning": "Scannen... ",
|
||||
"waiting_directions": "Bitte warten Sie auf das Scanergebnis. Dies kann bis zu 25 Sekunden dauern. Sie können den Vorgang später beenden und die Ergebnisse aus der Befehlstabelle anzeigen."
|
||||
},
|
||||
"settings": {
|
||||
"title": "die Einstellungen"
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Daten (KB)",
|
||||
"latest_statistics": "Neueste Statistiken",
|
||||
"show_latest": "Neueste Statistiken anzeigen JSON",
|
||||
"lifetime_stats": "Lifetime-Statistik",
|
||||
"no_interfaces": "Keine Statistiken zur Schnittstellenlebensdauer verfügbar",
|
||||
"show_latest": "Letzte Statistik",
|
||||
"title": "Statistiken"
|
||||
},
|
||||
"status": {
|
||||
"connection_status": "Verbindungsstatus",
|
||||
"connection_status": "Status",
|
||||
"error": "Statusdaten sind nicht verfügbar",
|
||||
"last_contact": "Letzter Kontakt",
|
||||
"load_averages": "Belastung (Durchschnitt 1 / 5 / 15 Minuten)",
|
||||
"load_averages": "Belastung (1/5/15 m.)",
|
||||
"localtime": "Ortszeit",
|
||||
"memory": "Verwendeter Speicher",
|
||||
"percentage_free": "{{percentage}}% von {{total}} kostenlos",
|
||||
@@ -198,6 +461,23 @@
|
||||
"uptime": "Betriebszeit",
|
||||
"used_total_memory": "{{used}} verwendet / {{total}} insgesamt"
|
||||
},
|
||||
"system": {
|
||||
"error_fetching": "Fehler beim Abrufen von Systeminformationen",
|
||||
"error_reloading": "Fehler beim Neuladen: {{error}}",
|
||||
"hostname": "Hostname",
|
||||
"os": "Betriebssystem",
|
||||
"processors": "Prozessoren",
|
||||
"reload": "Neu laden",
|
||||
"reload_subsystems": "Subsysteme",
|
||||
"subsystems": "Subsysteme",
|
||||
"success_reload": "Reload-Befehl erfolgreich gesendet!"
|
||||
},
|
||||
"telemetry": {
|
||||
"connection_failed": "Verbindung konnte nicht hergestellt werden. Fehler: {{error}}",
|
||||
"interval": "Intervall",
|
||||
"last_update": "Letztes Update",
|
||||
"types": "Typen"
|
||||
},
|
||||
"trace": {
|
||||
"choose_network": "Netzwerk auswählen",
|
||||
"directions": "Starten Sie eine Tcpdump auf diesem Geräts für eine bestimmte Dauer oder eine Anzahl von Paketen",
|
||||
@@ -205,6 +485,7 @@
|
||||
"packets": "Pakete",
|
||||
"title": "Tcpdump",
|
||||
"trace": "Spur",
|
||||
"trace_not_successful": "Trace nicht erfolgreich: Gateway hat folgenden Fehler gemeldet: {{error}}",
|
||||
"wait_for_file": "Möchten Sie warten, bis die Trace-Datei fertig ist?",
|
||||
"waiting_directions": "Bitte warten Sie auf die Trace-Datendatei. Dies könnte eine Weile dauern. Sie können das Warten beenden und die Ablaufverfolgungsdatei später aus der Befehlstabelle abrufen.",
|
||||
"waiting_seconds": "Verstrichene Zeit: {{seconds}} Sekunden"
|
||||
@@ -224,5 +505,53 @@
|
||||
"upgrade": "Aktualisierung",
|
||||
"wait_for_upgrade": "Möchten Sie warten, bis das Upgrade abgeschlossen ist?",
|
||||
"waiting_for_device": "Warten, bis das Gerät wieder verbunden ist"
|
||||
},
|
||||
"user": {
|
||||
"avatar": "Dein Avatar",
|
||||
"avatar_file": "Dein Avatar (max. 2 MB)",
|
||||
"create": "Benutzer erstellen",
|
||||
"create_failure": "Fehler beim Erstellen des Benutzers. Bitte stellen Sie sicher, dass diese E-Mail-Adresse nicht bereits mit einem Konto verknüpft ist.",
|
||||
"create_success": "Benutzer erfolgreich erstellt",
|
||||
"creating": "Benutzer erstellen ...",
|
||||
"delete_avatar": "Avatar löschen",
|
||||
"delete_failure": "Fehler beim Versuch, den Benutzer zu löschen: {{error}}",
|
||||
"delete_success": "Benutzer erfolgreich gelöscht!",
|
||||
"delete_title": "Benutzer löschen",
|
||||
"delete_warning": "Warnung: Sobald Sie einen Benutzer gelöscht haben, können Sie ihn nicht wiederherstellen",
|
||||
"deleting": "Löschen ...",
|
||||
"description": "Beschreibung",
|
||||
"edit": "Benutzer bearbeiten",
|
||||
"email_address": "E-Mail-Addresse",
|
||||
"error_fetching_users": "Fehler beim Abrufen der Nutzer: {{error}}",
|
||||
"force_password_change": "Passwortänderung bei der Anmeldung erzwingen",
|
||||
"id": "Benutzeridentifikation.",
|
||||
"last_login": "Letzte Anmeldung",
|
||||
"login_id": "Anmelde-ID.",
|
||||
"my_profile": "Mein Profil",
|
||||
"name": "Name",
|
||||
"nickname": "Spitzname",
|
||||
"nickname_explanation": "Spitzname (optional)",
|
||||
"not_validated": "Nicht validiert",
|
||||
"note": "Hinweis",
|
||||
"password": "Passwort",
|
||||
"provide_email": "Bitte geben Sie eine gültige E-Mail Adresse an",
|
||||
"provide_password": "Bitte geben Sie ein gültiges Passwort ein",
|
||||
"save_avatar": "Avatar speichern",
|
||||
"show_hide_password": "Passwort anzeigen/verbergen",
|
||||
"update_failure": "Fehler beim Aktualisieren: {{error}}",
|
||||
"update_failure_title": "Update fehlgeschlagen",
|
||||
"update_success": "Benutzer erfolgreich aktualisiert",
|
||||
"update_success_title": "Erfolg",
|
||||
"user_role": "Rolle",
|
||||
"users": "Benutzer",
|
||||
"validated": "Bestätigt"
|
||||
},
|
||||
"wifi_analysis": {
|
||||
"association": "Verband",
|
||||
"associations": "Verbände",
|
||||
"mode": "Modus",
|
||||
"network_diagram": "Netzwerkdiagramm",
|
||||
"radios": "Radios",
|
||||
"title": "WLAN-Analyse"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"actions": "Actions",
|
||||
"blink": "Blink",
|
||||
"configure": "Configure",
|
||||
"connect": "Connect",
|
||||
@@ -7,9 +8,10 @@
|
||||
"factory_reset": "Factory Reset",
|
||||
"firmware_upgrade": "Firmware Upgrade",
|
||||
"reboot": "Reboot",
|
||||
"telemetry": "Telemetry",
|
||||
"title": "Commands",
|
||||
"trace": "Trace",
|
||||
"wifi_scan": "Wifi Scan"
|
||||
"wifi_scan": "Wi-Fi Scan"
|
||||
},
|
||||
"blink": {
|
||||
"blink": "Blink",
|
||||
@@ -21,18 +23,28 @@
|
||||
},
|
||||
"commands": {
|
||||
"error": "Error while submitting command!",
|
||||
"error_delete_log": "Error while trying to delete: {{error}}",
|
||||
"event_queue": "Event Queue",
|
||||
"success": "Command submitted successfully, you can look at the Commands log for the result",
|
||||
"title": "Command History"
|
||||
"title": "Command History",
|
||||
"unable_queue": "Unable to complete event queue request: {{error}}"
|
||||
},
|
||||
"common": {
|
||||
"access_policy": "Access Policy",
|
||||
"add": "Add",
|
||||
"adding_ellipsis": "Adding...",
|
||||
"are_you_sure": "Are you sure?",
|
||||
"back_to_login": "Back to Login",
|
||||
"back_to_start": "Back to start",
|
||||
"by": "By",
|
||||
"cancel": "Cancel",
|
||||
"certificate": "Certificate",
|
||||
"certificates": "Certificates",
|
||||
"clear": "Clear",
|
||||
"close": "Close",
|
||||
"command": "Command",
|
||||
"commands": "Commands",
|
||||
"commands_executed": "Commands Executed",
|
||||
"compatible": "Compatible",
|
||||
"completed": "Completed",
|
||||
"config_id": "Config. Id",
|
||||
@@ -40,78 +52,168 @@
|
||||
"connected": "Connected",
|
||||
"copied": "Copied!",
|
||||
"copy_to_clipboard": "Copy to clipboard",
|
||||
"create": "Create",
|
||||
"created": "Created",
|
||||
"created_by": "Created By",
|
||||
"current": "Current ",
|
||||
"custom_date": "Custom Date",
|
||||
"dashboard": "Dashboard",
|
||||
"date": "Date",
|
||||
"day": "day",
|
||||
"days": "days",
|
||||
"delete": "Delete",
|
||||
"delete_device": "Delete Device",
|
||||
"details": "Details",
|
||||
"device": "Device #{{serialNumber}}",
|
||||
"device_dashboard": "Device Dashboard",
|
||||
"device_delete": "Delete Device #{{serialNumber}}",
|
||||
"device_deleted": "Device Successfully Deleted",
|
||||
"device_health": "Device Health",
|
||||
"device_list": "List of Devices",
|
||||
"device_page": "Device Page",
|
||||
"device_page": "View",
|
||||
"device_status": "Device Status",
|
||||
"devices": "Devices",
|
||||
"devices_using_latest": "Devices Using Latest Firmware",
|
||||
"devices_using_unknown": "Devices Using Unknown Firmware",
|
||||
"dismiss": "Dismiss",
|
||||
"do_now": "Do Now!",
|
||||
"download": "Download",
|
||||
"duration": "Duration",
|
||||
"edit": "Edit",
|
||||
"edit_user": "Edit",
|
||||
"email_address": "Email Address",
|
||||
"endpoint": "Endpoint",
|
||||
"endpoints": "Endpoints",
|
||||
"error": "Error",
|
||||
"error_adding_note": "Error while adding note",
|
||||
"execute_now": "Would you like to execute this command now?",
|
||||
"executed": "Executed",
|
||||
"exit": "Exit",
|
||||
"firmware": "Firmware",
|
||||
"firmware_dashboard": "Firmware Dashboard",
|
||||
"firmware_installed": "Firmware Installed",
|
||||
"forgot_password": "Forgot your Password?",
|
||||
"forgot_password_title": "Forgot Password",
|
||||
"from": "From",
|
||||
"general_error": "API Error, please consult your administrator",
|
||||
"hide": "Hide",
|
||||
"hour": "hour",
|
||||
"hours": "hours",
|
||||
"id": "Id",
|
||||
"ip_address": "Ip Address",
|
||||
"ip_address": "IP Address",
|
||||
"items_per_page": "Items per page: ",
|
||||
"last_dashboard_refresh": "Last Dashboard Refresh",
|
||||
"later_tonight": "Later tonight",
|
||||
"latest": "Latest",
|
||||
"list": "List",
|
||||
"loading_ellipsis": "Loading...",
|
||||
"loading_more_ellipsis": "Loading more...",
|
||||
"logout": "Logout",
|
||||
"mac": "MAC Address",
|
||||
"manufacturer": "Manufacturer",
|
||||
"memory_used": "Memory Used",
|
||||
"minute": "minute",
|
||||
"minutes": "minutes",
|
||||
"modified": "Modified",
|
||||
"na": "N/A",
|
||||
"need_date": "You need a date...",
|
||||
"no": "No",
|
||||
"no_devices_found": "No Devices Found",
|
||||
"no_items": "No Items",
|
||||
"none": "None",
|
||||
"not_connected": "Not Connected",
|
||||
"of_connected": "% of devices",
|
||||
"off": "Off",
|
||||
"on": "On",
|
||||
"optional": "Optional",
|
||||
"overall_health": "Overall Health",
|
||||
"password_policy": "Password Policy",
|
||||
"preview": "Preview",
|
||||
"recorded": "Recorded",
|
||||
"refresh": "Refresh",
|
||||
"refresh_device": "Refresh Device",
|
||||
"required": "Required",
|
||||
"result": "Result",
|
||||
"save": "Save",
|
||||
"saved": "Saved!",
|
||||
"saving": "Saving... ",
|
||||
"schedule": "Schedule",
|
||||
"search": "Search Devices",
|
||||
"second": "second",
|
||||
"seconds": "seconds",
|
||||
"seconds_elapsed": "Seconds elapsed",
|
||||
"serial_number": "Serial Number",
|
||||
"show_all": "Show All",
|
||||
"socket_connection_closed": "Connection closed!",
|
||||
"start": "Start",
|
||||
"stop_editing": "Stop Editing",
|
||||
"submit": "Submit",
|
||||
"submitted": "Submitted",
|
||||
"success": "Success",
|
||||
"system": "System",
|
||||
"table": "Table",
|
||||
"timestamp": "Time",
|
||||
"to": "To",
|
||||
"type": "Type",
|
||||
"type_for_options": "Type the value you need to create...",
|
||||
"type_for_options_format": "Type a value of the valid format ({{format}})...",
|
||||
"unable_to_connect": "Unable to Connect to Device",
|
||||
"unable_to_delete": "Unable to Delete",
|
||||
"unknown": "Unknown",
|
||||
"up_to_date": "Up to Date Devices",
|
||||
"uptimes": "Uptimes",
|
||||
"uuid": "UUID",
|
||||
"vendors": "Vendors",
|
||||
"view_more": "View more",
|
||||
"yes": "Yes"
|
||||
},
|
||||
"configuration": {
|
||||
"add_configuration": "Add Configuration",
|
||||
"add_new_block": "Add new Configuration Block",
|
||||
"add_or_link": "Link or Add",
|
||||
"cannot_delete": "This configuration cannot be deleted because it is being used by at least one entity, venue or device",
|
||||
"choose_section": "Which section you would like this block to contain?",
|
||||
"configuration_browser": "Configuration Browser",
|
||||
"configurations": "Configurations",
|
||||
"create": "Create Configuration",
|
||||
"create_config": "Create New Configuration",
|
||||
"create_new_configuration": "Create New Configuration Element",
|
||||
"created": "Created",
|
||||
"creation_success": "Configuration successfully created!",
|
||||
"currently_associated": "Currently Associated Configuration: {{config}}",
|
||||
"currently_selected_config": "Currently Selected Configuration: {{config}}",
|
||||
"delete_config": "Delete Config",
|
||||
"details": "Details",
|
||||
"device_password": "Password",
|
||||
"last_configuration_change": "Last Configuration Change",
|
||||
"device_type": "Device Type",
|
||||
"device_types": "Device Types",
|
||||
"edit_configuration": "Edit Configuration",
|
||||
"error_delete": "Error while trying to delete: {{error}}",
|
||||
"error_fetching_config": "Error while fetching configuration",
|
||||
"error_trying_delete": "Error while trying to delete: {{error}}",
|
||||
"error_update": "Error: {{error}}",
|
||||
"explanation": "Explanation",
|
||||
"last_configuration_change": "Config Change",
|
||||
"last_configuration_download": "Last Configuration Download",
|
||||
"location": "Location",
|
||||
"need_device_type": "Every configuration needs to support at least one device type",
|
||||
"no_associated_config": "No Associated Configuration",
|
||||
"no_associated_configuration": "No Associated Configurations",
|
||||
"note": "Note",
|
||||
"notes": "Notes",
|
||||
"owner": "Owner",
|
||||
"select_configuration": "Select this Configuration",
|
||||
"success_block_delete": "Successfully deleted configuration block",
|
||||
"success_update": "Configuration Successfully Updated!",
|
||||
"successful_delete": "Deleted Configuration!",
|
||||
"support_all": "Support All",
|
||||
"supported_device_types": "Supported Device Types",
|
||||
"title": "Configuration",
|
||||
"type": "Device Type",
|
||||
"used_by": "Used By",
|
||||
"used_by_details": "{{entities}} Entities, {{venues}} Venues and {{devices}} Devices",
|
||||
"uuid": "Config ID",
|
||||
"view_in_use": "View In Use",
|
||||
"view_json": "View raw JSON"
|
||||
},
|
||||
"configure": {
|
||||
@@ -121,6 +223,9 @@
|
||||
"title": "Configure",
|
||||
"valid_json": "You need to enter valid JSON"
|
||||
},
|
||||
"connect": {
|
||||
"error_trying_to_connect": "Error while trying to connect to device: {{error}}"
|
||||
},
|
||||
"delete_command": {
|
||||
"explanation": "Are you sure you want to delete this command? This action is not reversible.",
|
||||
"title": "Delete Command"
|
||||
@@ -131,11 +236,37 @@
|
||||
"explanation": "This will delete all of the {{object}} before the date you choose. Be careful, this action is not reversible.",
|
||||
"healthchecks_title": "Delete Healthchecks"
|
||||
},
|
||||
"device": {
|
||||
"error_fetching_device": "Error fetching device information: {{error}}",
|
||||
"error_fetching_devices": "Error while fetching devices: {{error}}"
|
||||
},
|
||||
"device_logs": {
|
||||
"log": "Log",
|
||||
"severity": "Severity",
|
||||
"title": "Logs"
|
||||
},
|
||||
"entity": {
|
||||
"add_child": "Add Child Entity to {{entityName}}",
|
||||
"add_failure": "Error, the server returned : {{error}}",
|
||||
"add_root": "Add Root Entity",
|
||||
"add_success": "Entity Successfully Created!",
|
||||
"assigned_inventory": "Assigned Inventory",
|
||||
"cannot_delete": "You cannot delete entities which have children. Delete this entity's children to be able to delete it.",
|
||||
"currently_selected_entity": "Currently Selected Entity: {{config}}",
|
||||
"currently_selected_venue": "Currently Selected Venue: {{config}}",
|
||||
"delete_success": "Entity Successfully Deleted",
|
||||
"delete_warning": "Warning: this operation cannot be reverted",
|
||||
"edit_failure": "Update unsuccessful : {{error}}",
|
||||
"entities": "Entities",
|
||||
"entity": "Entity",
|
||||
"error_fetch_entity": "Error while fetching entity information",
|
||||
"error_fetching": "Error while fetching entities",
|
||||
"error_saving": "Error while saving entity",
|
||||
"not_assigned": "Not Assigned",
|
||||
"only_unassigned": "Only Unassigned",
|
||||
"valid_serial": "Needs to be a valid serial number (12 HEX characters)",
|
||||
"venues": "Venues"
|
||||
},
|
||||
"factory_reset": {
|
||||
"redirector": "Keep redirector: ",
|
||||
"reset": "Reset",
|
||||
@@ -143,6 +274,35 @@
|
||||
"title": "Factory Reset",
|
||||
"warning": "Warning: Once you submit this cannot be reverted"
|
||||
},
|
||||
"firmware": {
|
||||
"average_age": "Average Firmware Age",
|
||||
"choose_custom": "Choose",
|
||||
"details_title": "Image #{{image}} Details",
|
||||
"device_type": "Device Type",
|
||||
"device_types": "Device Types",
|
||||
"downloads": "Downloads",
|
||||
"error_fetching_latest": "Error while fetching latest firmware",
|
||||
"from_release": "From",
|
||||
"history_title": "History",
|
||||
"image": "Image",
|
||||
"image_date": "Image Date",
|
||||
"installed_firmware": "Installed Firmware",
|
||||
"latest_version_installed": "Latest Version Installed",
|
||||
"newer_firmware_available": "Newer Revisions Available",
|
||||
"reinstall_latest": "Reinstall ",
|
||||
"revision": "Revision",
|
||||
"show_dev": "Show Dev Releases",
|
||||
"size": "Size",
|
||||
"status": "Firmware Status",
|
||||
"title": "Firmware",
|
||||
"to_release": "To",
|
||||
"unknown_firmware_status": "Unknown Firmware Status",
|
||||
"upgrade": "Upgrade",
|
||||
"upgrade_command_submitted": "Upgrade Command Submitted Successfully",
|
||||
"upgrade_to_latest": "Latest",
|
||||
"upgrade_to_version": "Upgrade to this Revision",
|
||||
"upgrading": "Upgrading..."
|
||||
},
|
||||
"footer": {
|
||||
"coreui_for_react": "CoreUI for React",
|
||||
"powered_by": "Powered by",
|
||||
@@ -152,13 +312,111 @@
|
||||
"sanity": "Sanity",
|
||||
"title": "Health"
|
||||
},
|
||||
"inventory": {
|
||||
"add_child": "Add Child Venue",
|
||||
"add_child_venue": "Add Child Venue to {{entityName}}",
|
||||
"add_tag": "Create Tag",
|
||||
"add_tag_to": "Add New Device to {{name}}",
|
||||
"add_venue": "Add Venue",
|
||||
"assign_entity_instructions": "You can either find the entity you want this tag to be assigned to by using the menu below, or you can manually paste the entity's UUID in the field above.",
|
||||
"assign_error": "Error while trying to assign tag",
|
||||
"assign_to_entity": "Assign to Entity",
|
||||
"bulk_delete_assigned": "Would you like to bulk delete assigned devices within your file?",
|
||||
"bulk_delete_assigned_warning": "Warning: this action is not reversible",
|
||||
"bulk_delete_devices": "Bulk Delete Devices",
|
||||
"bulk_delete_devices_not_found": "{{number}} devices not found",
|
||||
"bulk_delete_explanation": "To bulk delete devices, use a CSV file with one column called SerialNumber",
|
||||
"bulk_delete_test": "Validate File",
|
||||
"close_entity_menu": "Close Edit Entity Menu",
|
||||
"delete_devices": "Delete Devices",
|
||||
"delete_errors": "{{number}} Delete Device Errors",
|
||||
"delete_tag": "Delete Tag",
|
||||
"delete_venue": "Permanently Delete Venue",
|
||||
"deleted_devices": "{{number}} Deleted Devices",
|
||||
"deleting": "Deleting... ",
|
||||
"deletion_failure": "Deletion error",
|
||||
"devices_assigned": "{{number}} existing devices assigned and updated",
|
||||
"devices_created": "{{number}} devices created",
|
||||
"devices_deleted": "Devices Deleted",
|
||||
"devices_errors_while_creating": "{{number}} device creations failed",
|
||||
"devices_errors_while_updating": "{{number}} device updates failed",
|
||||
"devices_found_assigned": "{{number}} devices found and already assigned to an entity or venue",
|
||||
"devices_found_unassigned": "{{number}} devices found, but not assigned",
|
||||
"devices_imported": " Devices Imported",
|
||||
"devices_not_found": "{{number}} devices with no conflict",
|
||||
"devices_tested": "Devices Tested",
|
||||
"duplicate_serial": "Serial Number already used in file (duplicate)",
|
||||
"error_create_venue": "Error while creating venue",
|
||||
"error_delete_tag": "Error while deleting inventory tag",
|
||||
"error_get_venue": "Error while retrieving venues",
|
||||
"error_retrieving": "Error occurred while retrieving inventory tags",
|
||||
"error_unassign": "Error during unassign operation",
|
||||
"error_update_venue": "Error while updating venue",
|
||||
"error_venue_delete": "Error while deleting venue",
|
||||
"error_within_file": "{{number}} devices with wrong information in file (will be ignored)",
|
||||
"file_error": "There seems to be a mistake in your file. Please make sure the file is in CSV format and contains the 5 columns mentioned above in the first line of the file",
|
||||
"final_delete_results": "Final Delete Results",
|
||||
"final_import_results": "Final Import Results",
|
||||
"import_assigned_devices": "Do you want to reassign them with this import?",
|
||||
"import_assigned_devices_explanation": "Some devices conflict with already assigned devices. You should resolve those problems before importing. ",
|
||||
"import_devices": "Import Devices",
|
||||
"import_devices_explanation": "To bulk import devices, you need to use a CSV file with the following columns: SerialNumber,Name,Description,DeviceType,NoteText",
|
||||
"import_devices_to": "Import Devices to {{name}}",
|
||||
"import_existing_devices": "Update and assign them?",
|
||||
"import_existing_devices_explanation": "Some devices already exist in the inventory and are unassigned.",
|
||||
"importing": "Importing...",
|
||||
"last_modification": "Last Modification",
|
||||
"no_devices_to_delete": "No Devices to Delete",
|
||||
"no_devices_to_import": "No valid devices to create or update!",
|
||||
"note_text": "Note Text",
|
||||
"passed_tests": "All tests were passed, your devices are ready to import!",
|
||||
"serial_number_required": "Error: Missing Serial Number",
|
||||
"showing_top_10": "Here is a preview of the information we have retrieved from your file:",
|
||||
"sub_venues": "Subvenues",
|
||||
"subscriber": "Subscriber",
|
||||
"successful_assign": "Tag successfully assigned",
|
||||
"successful_tag_delete": "Inventory Tag Successfully Deleted",
|
||||
"successful_tag_update": "Successfully updated tag",
|
||||
"successful_unassign": "Unassign operation was successful",
|
||||
"successful_venue_create": "Successfully Created Venue",
|
||||
"successful_venue_delete": "Venue successfully deleted",
|
||||
"successful_venue_update": "Successfully Updated Venue",
|
||||
"tag_created": "Inventory tag successfully created",
|
||||
"tag_creation_error": "Error while trying to create inventory tag",
|
||||
"tag_update_error": "Error while updating tag",
|
||||
"tags_assigned_to": "Inventory tags assigned to {{name}}",
|
||||
"test_import": "Validate Import Data",
|
||||
"test_results": "Test Results",
|
||||
"title": "Inventory",
|
||||
"type_invalid": "Error: Invalid Device Type",
|
||||
"unassign": "Unassign",
|
||||
"unassign_tag": "Unassign Tag from Entity",
|
||||
"unassigned_deleted_devices": "{{number}} Devices Deleted and Unassigned",
|
||||
"unassigned_tags": "Unassigned tags",
|
||||
"validating_import_file": "Validating import file and data...",
|
||||
"venue": "Venue"
|
||||
},
|
||||
"login": {
|
||||
"change_password": "Change Password",
|
||||
"change_password_error": "Error while changing password. Make sure the new password is valid by visiting the 'Password Policy' page",
|
||||
"change_password_instructions": "Enter and confirm your new password",
|
||||
"changing_password": "Changing Password... ",
|
||||
"confirm_new_password": "Confirm New Password",
|
||||
"different_passwords": "You need to enter the same password twice",
|
||||
"forgot_password_error": "Error while trying to send Forgot Password email. Please make sure this userId is associated to an account.",
|
||||
"forgot_password_explanation": "Enter your username to receive an email containing the instructions to reset your password",
|
||||
"forgot_password_success": "You should soon receive an email containing the instructions to reset your password. Please make sure to check your spam if you can't find the email",
|
||||
"logging_in": "Logging In... ",
|
||||
"login": "Login",
|
||||
"login_error": "Login error, confirm that your username, password and gateway url are valid",
|
||||
"login_error": "Login error, make sure the information you are providing is valid",
|
||||
"new_password": "New Password",
|
||||
"password": "Password",
|
||||
"please_enter_gateway": "Please enter a uCentralSec URL",
|
||||
"please_enter_password": "Please enter your password",
|
||||
"please_enter_username": "Please enter your username",
|
||||
"previously_used": "Password was previously used",
|
||||
"send_forgot": "Send Email",
|
||||
"sending_ellipsis": "Sending... ",
|
||||
"sign_in_to_account": "Sign in to your account",
|
||||
"url": "uCentralSec URL",
|
||||
"username": "Username"
|
||||
@@ -174,22 +432,27 @@
|
||||
"directions": "Launch a wifi scan of this device, which should take approximately 25 seconds.",
|
||||
"re_scan": "Re-Scan",
|
||||
"result_directions": "Please click the '$t(scan.re_scan)' button if you would like to do a scan with the same configuration as the last.",
|
||||
"results": "Wifi Scan Results",
|
||||
"results": "Wi-Fi Scan Results",
|
||||
"scan": "Scan",
|
||||
"scanning": "Scanning... ",
|
||||
"waiting_directions": "Please wait for the scan result. This may take up to 25 seconds. You can exit and look at the results from the commands table later."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings"
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Data (KB)",
|
||||
"latest_statistics": "Latest Statistics",
|
||||
"show_latest": "Show latest statistics JSON",
|
||||
"lifetime_stats": "Lifetime Statistics",
|
||||
"no_interfaces": "No interface lifetime statistics available",
|
||||
"show_latest": "Last Statistics",
|
||||
"title": "Statistics"
|
||||
},
|
||||
"status": {
|
||||
"connection_status": "Connection Status",
|
||||
"connection_status": "Status",
|
||||
"error": "Status data is unavailable",
|
||||
"last_contact": "Last Contact",
|
||||
"load_averages": "Load ( 1 / 5 / 15 minute average)",
|
||||
"load_averages": "Load (1/5/15 m.)",
|
||||
"localtime": "Localtime",
|
||||
"memory": "Memory Used",
|
||||
"percentage_free": "{{percentage}}% of {{total}} free",
|
||||
@@ -198,6 +461,23 @@
|
||||
"uptime": "Uptime",
|
||||
"used_total_memory": "{{used}} used / {{total}} total "
|
||||
},
|
||||
"system": {
|
||||
"error_fetching": "Error while fetching system information",
|
||||
"error_reloading": "Error while reloading: {{error}}",
|
||||
"hostname": "Host Name",
|
||||
"os": "Operation System",
|
||||
"processors": "Processors",
|
||||
"reload": "Reload",
|
||||
"reload_subsystems": "Reload",
|
||||
"subsystems": "Subsystems",
|
||||
"success_reload": "Reload command successfully submitted!"
|
||||
},
|
||||
"telemetry": {
|
||||
"connection_failed": "Failed to create connection. Error: {{error}}",
|
||||
"interval": "Interval",
|
||||
"last_update": "Last Update",
|
||||
"types": "Types"
|
||||
},
|
||||
"trace": {
|
||||
"choose_network": "Choose network",
|
||||
"directions": "Launch a remote trace of this device for either a specific duration or a number of packets",
|
||||
@@ -205,6 +485,7 @@
|
||||
"packets": "Packets",
|
||||
"title": "Trace",
|
||||
"trace": "Trace",
|
||||
"trace_not_successful": "Trace not successful: gateway reported the following error : {{error}}",
|
||||
"wait_for_file": "Would you like to wait until the trace file is ready?",
|
||||
"waiting_directions": "Please wait for the trace data file. This may take some time. You can exit the wait and retrieve the trace file from the commands table later.",
|
||||
"waiting_seconds": "Time Elapsed: {{seconds}} seconds"
|
||||
@@ -224,5 +505,53 @@
|
||||
"upgrade": "Upgrade",
|
||||
"wait_for_upgrade": "Would you like to wait for the upgrade to finish?",
|
||||
"waiting_for_device": "Waiting for device to reconnect"
|
||||
},
|
||||
"user": {
|
||||
"avatar": "Your Avatar",
|
||||
"avatar_file": "Your Avatar (max. of 2 MB)",
|
||||
"create": "Create User",
|
||||
"create_failure": "Error while creating user. Please make sure this email address is not already linked to an account.",
|
||||
"create_success": "User Created Successfully",
|
||||
"creating": "Creating User...",
|
||||
"delete_avatar": "Delete Avatar",
|
||||
"delete_failure": "Error while trying to delete user: {{error}}",
|
||||
"delete_success": "User successfully deleted!",
|
||||
"delete_title": "Delete User",
|
||||
"delete_warning": "Warning: Once you delete a user you cannot revert",
|
||||
"deleting": "Deleting... ",
|
||||
"description": "Description",
|
||||
"edit": "Edit User",
|
||||
"email_address": "Email Address",
|
||||
"error_fetching_users": "Error fetching users: {{error}}",
|
||||
"force_password_change": "Force Password Change on Login",
|
||||
"id": "User Id.",
|
||||
"last_login": "Last Login",
|
||||
"login_id": "Login Id.",
|
||||
"my_profile": "My Profile",
|
||||
"name": "Name",
|
||||
"nickname": "Nickname",
|
||||
"nickname_explanation": "Nickname (optional)",
|
||||
"not_validated": "Not Validated",
|
||||
"note": "Note",
|
||||
"password": "Password",
|
||||
"provide_email": "Please provide a valid email address",
|
||||
"provide_password": "Please provide a valid password",
|
||||
"save_avatar": "Save Avatar",
|
||||
"show_hide_password": "Show/Hide Password",
|
||||
"update_failure": "Error while trying to update: {{error}}",
|
||||
"update_failure_title": "Update Failed",
|
||||
"update_success": "User Updated Successfully",
|
||||
"update_success_title": "Success",
|
||||
"user_role": "Role",
|
||||
"users": "Users",
|
||||
"validated": "Validated"
|
||||
},
|
||||
"wifi_analysis": {
|
||||
"association": "Association",
|
||||
"associations": "Associations",
|
||||
"mode": "Mode",
|
||||
"network_diagram": "Network Diagram",
|
||||
"radios": "Radios",
|
||||
"title": "Wi-Fi Analysis"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"actions": "Comportamiento",
|
||||
"blink": "Parpadeo",
|
||||
"configure": "Configurar",
|
||||
"connect": "Conectar",
|
||||
@@ -7,9 +8,10 @@
|
||||
"factory_reset": "Restablecimiento De Fábrica",
|
||||
"firmware_upgrade": "Actualización de firmware",
|
||||
"reboot": "Reiniciar",
|
||||
"telemetry": "Telemetria",
|
||||
"title": "Comandos",
|
||||
"trace": "Rastro",
|
||||
"wifi_scan": "Escaneo Wifi"
|
||||
"wifi_scan": "Escaneo Wi-Fi "
|
||||
},
|
||||
"blink": {
|
||||
"blink": "Parpadeo",
|
||||
@@ -21,18 +23,28 @@
|
||||
},
|
||||
"commands": {
|
||||
"error": "¡Error al enviar el comando!",
|
||||
"error_delete_log": "Error al intentar eliminar: {{error}}",
|
||||
"event_queue": "Cola de eventos",
|
||||
"success": "Comando enviado con éxito, puede consultar el registro de Comandos para ver el resultado",
|
||||
"title": "Historial de Comandos"
|
||||
"title": "Historial de Comandos",
|
||||
"unable_queue": "No se pudo completar la solicitud de cola de eventos: {{error}}"
|
||||
},
|
||||
"common": {
|
||||
"access_policy": "Política de acceso",
|
||||
"add": "Añadir",
|
||||
"adding_ellipsis": "Añadiendo ...",
|
||||
"are_you_sure": "¿Estás seguro?",
|
||||
"back_to_login": "Atrás para iniciar sesión",
|
||||
"back_to_start": "volver a empezar",
|
||||
"by": "Por",
|
||||
"cancel": "Cancelar",
|
||||
"certificate": "Certificado",
|
||||
"certificates": "Certificados",
|
||||
"clear": "Claro",
|
||||
"close": "Cerrar",
|
||||
"command": "Mando",
|
||||
"commands": "comandos",
|
||||
"commands_executed": "Comandos ejecutados",
|
||||
"compatible": "Compatible",
|
||||
"completed": "terminado",
|
||||
"config_id": "Config. Identificación",
|
||||
@@ -40,78 +52,168 @@
|
||||
"connected": "Conectado",
|
||||
"copied": "Copiado!",
|
||||
"copy_to_clipboard": "Copiar al portapapeles",
|
||||
"create": "Crear",
|
||||
"created": "creado",
|
||||
"created_by": "Creado por",
|
||||
"current": "Corriente",
|
||||
"custom_date": "Fecha personalizada",
|
||||
"dashboard": "Tablero",
|
||||
"date": "Fecha",
|
||||
"day": "día",
|
||||
"days": "días",
|
||||
"delete": "Borrar",
|
||||
"delete_device": "Eliminar dispositivo",
|
||||
"details": "Detalles",
|
||||
"device": "Dispositivo n.º{{serialNumber}}",
|
||||
"device_dashboard": "Panel de control del dispositivo",
|
||||
"device_delete": "Eliminar dispositivo n.º{{serialNumber}}",
|
||||
"device_deleted": "Dispositivo eliminado correctamente",
|
||||
"device_health": "Salud del dispositivo",
|
||||
"device_list": "Listado de dispositivos",
|
||||
"device_page": "Página del dispositivo",
|
||||
"device_page": "Ver",
|
||||
"device_status": "Estado del dispositivo",
|
||||
"devices": "Dispositivos",
|
||||
"devices_using_latest": "Dispositivos que utilizan el firmware más reciente",
|
||||
"devices_using_unknown": "Dispositivos que utilizan firmware desconocido",
|
||||
"dismiss": "Despedir",
|
||||
"do_now": "¡Hagan ahora!",
|
||||
"download": "Descargar",
|
||||
"duration": "Duración",
|
||||
"edit": "Editar",
|
||||
"edit_user": "Editar",
|
||||
"email_address": "Dirección de correo electrónico",
|
||||
"endpoint": "punto final",
|
||||
"endpoints": "Puntos finales",
|
||||
"error": "Error",
|
||||
"error_adding_note": "Error al agregar una nota",
|
||||
"execute_now": "¿Le gustaría ejecutar este comando ahora?",
|
||||
"executed": "ejecutado",
|
||||
"exit": "salida",
|
||||
"firmware": "Firmware",
|
||||
"firmware_dashboard": "Panel de firmware",
|
||||
"firmware_installed": "Firmware instalado",
|
||||
"forgot_password": "¿Olvidaste tu contraseña?",
|
||||
"forgot_password_title": "Se te olvidó tu contraseña",
|
||||
"from": "Desde",
|
||||
"general_error": "Error de API, consulte a su administrador",
|
||||
"hide": "Esconder",
|
||||
"hour": "hora",
|
||||
"hours": "horas",
|
||||
"id": "Carné de identidad",
|
||||
"ip_address": "Dirección IP",
|
||||
"items_per_page": "Artículos por página:",
|
||||
"last_dashboard_refresh": "Última actualización del panel",
|
||||
"later_tonight": "Más tarde esta noche",
|
||||
"latest": "último",
|
||||
"list": "Lista",
|
||||
"loading_ellipsis": "Cargando...",
|
||||
"loading_more_ellipsis": "Cargando más ...",
|
||||
"logout": "Cerrar sesión",
|
||||
"mac": "Dirección MAC",
|
||||
"manufacturer": "Fabricante",
|
||||
"memory_used": "Memoria usada",
|
||||
"minute": "minuto",
|
||||
"minutes": "minutos",
|
||||
"modified": "Modificado",
|
||||
"na": "N / A",
|
||||
"need_date": "Necesitas una cita ...",
|
||||
"no": "No",
|
||||
"no_devices_found": "No se encontraron dispositivos",
|
||||
"no_items": "No hay articulos",
|
||||
"none": "Ninguna",
|
||||
"not_connected": "No conectado",
|
||||
"of_connected": "% de dispositivos",
|
||||
"off": "Apagado",
|
||||
"on": "en",
|
||||
"optional": "Opcional",
|
||||
"overall_health": "Salud en general",
|
||||
"password_policy": "Política de contraseñas",
|
||||
"preview": "Avance",
|
||||
"recorded": "Grabado",
|
||||
"refresh": "Refrescar",
|
||||
"refresh_device": "Actualizar dispositivo",
|
||||
"required": "Necesario",
|
||||
"result": "Resultado",
|
||||
"save": "Salvar",
|
||||
"saved": "¡Salvado!",
|
||||
"saving": "Ahorro...",
|
||||
"schedule": "Programar",
|
||||
"search": "Dispositivos de búsqueda",
|
||||
"second": "segundo",
|
||||
"seconds": "segundos",
|
||||
"seconds_elapsed": "Segundos transcurridos",
|
||||
"serial_number": "Número de serie",
|
||||
"show_all": "Mostrar todo",
|
||||
"socket_connection_closed": "¡Conexión cerrada!",
|
||||
"start": "comienzo",
|
||||
"stop_editing": "Dejar de editar",
|
||||
"submit": "Enviar",
|
||||
"submitted": "Presentado",
|
||||
"success": "Éxito",
|
||||
"system": "Sistema",
|
||||
"table": "Mesa",
|
||||
"timestamp": "hora",
|
||||
"to": "a",
|
||||
"type": "Tipo",
|
||||
"type_for_options": "Escriba el valor que necesita crear ...",
|
||||
"type_for_options_format": "Escriba un valor del formato válido ({{format}}) ...",
|
||||
"unable_to_connect": "No se puede conectar al dispositivo",
|
||||
"unable_to_delete": "No se puede eliminar",
|
||||
"unknown": "Desconocido",
|
||||
"up_to_date": "Dispositivos actualizados",
|
||||
"uptimes": "Tiempos de actividad",
|
||||
"uuid": "UUID",
|
||||
"vendors": "Vendedores",
|
||||
"view_more": "Ver más",
|
||||
"yes": "Sí"
|
||||
},
|
||||
"configuration": {
|
||||
"add_configuration": "Agregar configuración",
|
||||
"add_new_block": "Agregar nuevo bloque de configuración",
|
||||
"add_or_link": "Vincular o agregar",
|
||||
"cannot_delete": "Esta configuración no se puede eliminar porque está siendo utilizada por al menos una entidad, lugar o dispositivo",
|
||||
"choose_section": "Qué sección le gustaría que contenga este bloque?",
|
||||
"configuration_browser": "Navegador de configuración",
|
||||
"configurations": "Configuraciones",
|
||||
"create": "Crear Configuración",
|
||||
"create_config": "Crear nueva configuración",
|
||||
"create_new_configuration": "Crear nuevo elemento de configuración",
|
||||
"created": "creado",
|
||||
"creation_success": "¡Configuración creada con éxito!",
|
||||
"currently_associated": "Configuración asociada actual: {{config}}",
|
||||
"currently_selected_config": "Configuración seleccionada actualmente: {{config}}",
|
||||
"delete_config": "Eliminar Configuración",
|
||||
"details": "Detalles",
|
||||
"device_password": "Contraseña",
|
||||
"last_configuration_change": "Último cambio de configuración",
|
||||
"device_type": "Tipo de dispositivo",
|
||||
"device_types": "Tipos de dispositivos",
|
||||
"edit_configuration": "Editar configuración",
|
||||
"error_delete": "Error al intentar eliminar: {{error}}",
|
||||
"error_fetching_config": "Error al obtener la configuración",
|
||||
"error_trying_delete": "Error al intentar eliminar: {{error}}",
|
||||
"error_update": "Error: {{error}}",
|
||||
"explanation": "Explicación",
|
||||
"last_configuration_change": "CAMBIO DE CONFIGURACIÓN",
|
||||
"last_configuration_download": "Descarga de la última configuración",
|
||||
"location": "Ubicación",
|
||||
"need_device_type": "Cada configuración debe admitir al menos un tipo de dispositivo",
|
||||
"no_associated_config": "Sin configuración asociada",
|
||||
"no_associated_configuration": "Sin configuraciones asociadas",
|
||||
"note": "Nota",
|
||||
"notes": "Notas",
|
||||
"owner": "Propietario",
|
||||
"select_configuration": "Seleccione esta configuración",
|
||||
"success_block_delete": "Bloque de configuración eliminado con éxito",
|
||||
"success_update": "¡Configuración actualizada con éxito!",
|
||||
"successful_delete": "Configuración eliminada!",
|
||||
"support_all": "Apoyar a todos",
|
||||
"supported_device_types": "Tipos de dispositivos compatibles",
|
||||
"title": "Configuración",
|
||||
"type": "Tipo de dispositivo",
|
||||
"used_by": "Usado por",
|
||||
"used_by_details": "{{entities}} Entidades, {{venues}} lugares y {{devices}} dispositivos",
|
||||
"uuid": "ID de configuración",
|
||||
"view_in_use": "Ver en uso",
|
||||
"view_json": "Ver JSON sin procesar"
|
||||
},
|
||||
"configure": {
|
||||
@@ -121,6 +223,9 @@
|
||||
"title": "Configurar",
|
||||
"valid_json": "Debes ingresar un JSON válido"
|
||||
},
|
||||
"connect": {
|
||||
"error_trying_to_connect": "Error al intentar conectarse al dispositivo: {{error}}"
|
||||
},
|
||||
"delete_command": {
|
||||
"explanation": "¿Está seguro de que desea eliminar este comando? Esta acción no es reversible.",
|
||||
"title": "Eliminar comando"
|
||||
@@ -131,11 +236,37 @@
|
||||
"explanation": "Esto eliminará todos los {{object}} antes de la fecha que elija. Tenga cuidado, esta acción no es reversible.",
|
||||
"healthchecks_title": "Eliminar comprobaciones de estado"
|
||||
},
|
||||
"device": {
|
||||
"error_fetching_device": "Error al obtener la información del dispositivo: {{error}}",
|
||||
"error_fetching_devices": "Error al recuperar dispositivos: {{error}}"
|
||||
},
|
||||
"device_logs": {
|
||||
"log": "Iniciar sesión",
|
||||
"severity": "Gravedad",
|
||||
"title": "Registros"
|
||||
},
|
||||
"entity": {
|
||||
"add_child": "Agregar entidad secundaria a {{entityName}}",
|
||||
"add_failure": "Error, el servidor devolvió: {{error}}",
|
||||
"add_root": "Agregar entidad raíz",
|
||||
"add_success": "¡Entidad creada con éxito!",
|
||||
"assigned_inventory": "Inventario asignado",
|
||||
"cannot_delete": "No puede eliminar entidades que tienen hijos. Elimina los hijos de esta entidad para poder eliminarla.",
|
||||
"currently_selected_entity": "Entidad seleccionada actualmente: {{config}}",
|
||||
"currently_selected_venue": "Lugar seleccionado actualmente: {{config}}",
|
||||
"delete_success": "Entidad eliminada correctamente",
|
||||
"delete_warning": "Advertencia: esta operación no se puede revertir",
|
||||
"edit_failure": "Actualización fallida: {{error}}",
|
||||
"entities": "entidades",
|
||||
"entity": "Entidad",
|
||||
"error_fetch_entity": "Error al obtener la información de la entidad",
|
||||
"error_fetching": "Error al recuperar entidades",
|
||||
"error_saving": "Error al guardar la entidad",
|
||||
"not_assigned": "No asignado",
|
||||
"only_unassigned": "Solo sin asignar",
|
||||
"valid_serial": "Debe ser un número de serie válido (12 caracteres HEX)",
|
||||
"venues": "Sedes"
|
||||
},
|
||||
"factory_reset": {
|
||||
"redirector": "Mantener el redirector:",
|
||||
"reset": "Reiniciar",
|
||||
@@ -143,6 +274,35 @@
|
||||
"title": "Restablecimiento De Fábrica",
|
||||
"warning": "Advertencia: una vez que envíe, esto no se podrá revertir"
|
||||
},
|
||||
"firmware": {
|
||||
"average_age": "Edad promedio del firmware",
|
||||
"choose_custom": "Escoger",
|
||||
"details_title": "Detalles de la imagen n. °{{image}} ",
|
||||
"device_type": "Tipo de dispositivo",
|
||||
"device_types": "Tipos de dispositivos",
|
||||
"downloads": "Descargas",
|
||||
"error_fetching_latest": "Error al obtener el firmware más reciente",
|
||||
"from_release": "Desde",
|
||||
"history_title": "Historia",
|
||||
"image": "Imagen",
|
||||
"image_date": "Fecha de la imagen",
|
||||
"installed_firmware": "Firmware instalado",
|
||||
"latest_version_installed": "Última versión instalada",
|
||||
"newer_firmware_available": "Nuevas revisiones disponibles",
|
||||
"reinstall_latest": "Reinstalar",
|
||||
"revision": "Revisión",
|
||||
"show_dev": "Mostrar lanzamientos para desarrolladores",
|
||||
"size": "Tamaño",
|
||||
"status": "Estado del firmware",
|
||||
"title": "Firmware",
|
||||
"to_release": "A",
|
||||
"unknown_firmware_status": "Estado de firmware desconocido",
|
||||
"upgrade": "Mejorar",
|
||||
"upgrade_command_submitted": "El comando de actualización se envió correctamente",
|
||||
"upgrade_to_latest": "último",
|
||||
"upgrade_to_version": "Actualizar a esta revisión",
|
||||
"upgrading": "Actualizando ..."
|
||||
},
|
||||
"footer": {
|
||||
"coreui_for_react": "CoreUI para React",
|
||||
"powered_by": "energizado por",
|
||||
@@ -152,13 +312,111 @@
|
||||
"sanity": "Cordura",
|
||||
"title": "Salud"
|
||||
},
|
||||
"inventory": {
|
||||
"add_child": "Agregar lugar secundario",
|
||||
"add_child_venue": "Agregar lugar infantil a {{entityName}}",
|
||||
"add_tag": "Crear etiqueta",
|
||||
"add_tag_to": "Agregar nuevo dispositivo a {{name}}",
|
||||
"add_venue": "Agregar lugar",
|
||||
"assign_entity_instructions": "Puede encontrar la entidad a la que desea que se asigne esta etiqueta utilizando el menú a continuación, o puede pegar manualmente el UUID de la entidad en el campo de arriba.",
|
||||
"assign_error": "Error al intentar asignar la etiqueta",
|
||||
"assign_to_entity": "Asignar a entidad",
|
||||
"bulk_delete_assigned": "¿Le gustaría eliminar de forma masiva los dispositivos asignados dentro de su archivo?",
|
||||
"bulk_delete_assigned_warning": "Advertencia: esta acción no es reversible",
|
||||
"bulk_delete_devices": "Dispositivos de eliminación masiva",
|
||||
"bulk_delete_devices_not_found": "{{number}} dispositivos no encontrados",
|
||||
"bulk_delete_explanation": "Para eliminar dispositivos de forma masiva, use un archivo CSV con una columna llamada SerialNumber",
|
||||
"bulk_delete_test": "Validar archivo",
|
||||
"close_entity_menu": "Cerrar el menú Editar entidad",
|
||||
"delete_devices": "BORRAR DISPOSITIVOS",
|
||||
"delete_errors": "{{number}} Eliminar errores del dispositivo",
|
||||
"delete_tag": "Eliminar etiqueta",
|
||||
"delete_venue": "Eliminar el lugar de forma permanente",
|
||||
"deleted_devices": "{{number}} Dispositivos eliminados",
|
||||
"deleting": "Eliminando ...",
|
||||
"deletion_failure": "Error de borrado",
|
||||
"devices_assigned": "{{number}} dispositivos existentes asignados y actualizados",
|
||||
"devices_created": "{{number}} dispositivos creados",
|
||||
"devices_deleted": "Dispositivos eliminados",
|
||||
"devices_errors_while_creating": "{{number}} fallaron las creaciones del dispositivo",
|
||||
"devices_errors_while_updating": "{{number}} fallaron las actualizaciones del dispositivo",
|
||||
"devices_found_assigned": "{{number}} dispositivos encontrados y ya asignados a una entidad o lugar",
|
||||
"devices_found_unassigned": "{{number}} dispositivos encontrados, pero no asignados",
|
||||
"devices_imported": " Dispositivos importados",
|
||||
"devices_not_found": "{{number}} dispositivos sin conflicto",
|
||||
"devices_tested": "Dispositivos probados",
|
||||
"duplicate_serial": "Número de serie ya utilizado en el archivo (duplicado)",
|
||||
"error_create_venue": "Error al crear el lugar",
|
||||
"error_delete_tag": "Error al eliminar la etiqueta de inventario",
|
||||
"error_get_venue": "Error al recuperar lugares",
|
||||
"error_retrieving": "Se produjo un error al recuperar las etiquetas de inventario",
|
||||
"error_unassign": "Error durante la operación de anulación de asignación",
|
||||
"error_update_venue": "Error al actualizar el lugar",
|
||||
"error_venue_delete": "Error al eliminar el lugar",
|
||||
"error_within_file": "{{number}} dispositivos con información incorrecta en el archivo (se ignorarán)",
|
||||
"file_error": "Parece haber un error en su archivo. Asegúrese de que el archivo esté en formato CSV y contenga las 5 columnas mencionadas anteriormente en la primera línea del archivo",
|
||||
"final_delete_results": "Resultados de eliminación final",
|
||||
"final_import_results": "Resultados finales de importación",
|
||||
"import_assigned_devices": "¿Quieres reasignarlos con esta importación?",
|
||||
"import_assigned_devices_explanation": "Algunos dispositivos entran en conflicto con los dispositivos ya asignados. Debe resolver esos problemas antes de importar.",
|
||||
"import_devices": "Importar dispositivos",
|
||||
"import_devices_explanation": "Para importar dispositivos de forma masiva, debe utilizar un archivo CSV con las siguientes columnas: Número de serie, Nombre, Descripción, Tipo de dispositivo, Texto de nota",
|
||||
"import_devices_to": "Importar dispositivos a {{name}}",
|
||||
"import_existing_devices": "¿Actualizarlos y asignarlos?",
|
||||
"import_existing_devices_explanation": "Algunos dispositivos ya existen en el inventario y no están asignados.",
|
||||
"importing": "Importador...",
|
||||
"last_modification": "Última modificación",
|
||||
"no_devices_to_delete": "No hay dispositivos para eliminar",
|
||||
"no_devices_to_import": "¡No hay dispositivos válidos para crear o actualizar!",
|
||||
"note_text": "Texto de la nota",
|
||||
"passed_tests": "Se aprobaron todas las pruebas, ¡sus dispositivos están listos para importar!",
|
||||
"serial_number_required": "Error: falta el número de serie",
|
||||
"showing_top_10": "Aquí hay una vista previa de la información que hemos recuperado de su archivo:",
|
||||
"sub_venues": "Subvenues",
|
||||
"subscriber": "Abonado",
|
||||
"successful_assign": "Etiqueta asignada correctamente",
|
||||
"successful_tag_delete": "Etiqueta de inventario eliminada correctamente",
|
||||
"successful_tag_update": "Etiqueta actualizada correctamente",
|
||||
"successful_unassign": "La operación de anulación de asignación se realizó correctamente",
|
||||
"successful_venue_create": "Lugar creado con éxito",
|
||||
"successful_venue_delete": "Lugar eliminado correctamente",
|
||||
"successful_venue_update": "Lugar actualizado con éxito",
|
||||
"tag_created": "Etiqueta de inventario creada correctamente",
|
||||
"tag_creation_error": "Error al intentar crear una etiqueta de inventario",
|
||||
"tag_update_error": "Error al actualizar la etiqueta",
|
||||
"tags_assigned_to": "Etiquetas de inventario asignadas a {{name}}",
|
||||
"test_import": "Validar datos de importación",
|
||||
"test_results": "Resultados de la prueba",
|
||||
"title": "Inventario",
|
||||
"type_invalid": "Error: tipo de dispositivo no válido",
|
||||
"unassign": "Anular asignación",
|
||||
"unassign_tag": "Anular asignación de etiqueta de entidad",
|
||||
"unassigned_deleted_devices": "{{number}} Dispositivos eliminados y sin asignar",
|
||||
"unassigned_tags": "Etiquetas sin asignar",
|
||||
"validating_import_file": "Validando archivo y datos de importación ...",
|
||||
"venue": "Lugar de encuentro"
|
||||
},
|
||||
"login": {
|
||||
"change_password": "Cambia la contraseña",
|
||||
"change_password_error": "Error al cambiar la contraseña. Asegúrese de que la nueva contraseña sea válida visitando la página 'Política de contraseñas'",
|
||||
"change_password_instructions": "Ingrese y confirme su nueva contraseña",
|
||||
"changing_password": "Cambio de contraseña ...",
|
||||
"confirm_new_password": "confirmar nueva contraseña",
|
||||
"different_passwords": "Debes ingresar la misma contraseña dos veces",
|
||||
"forgot_password_error": "Error al intentar enviar el correo electrónico de Olvidé mi contraseña. Asegúrese de que este ID de usuario esté asociado a una cuenta.",
|
||||
"forgot_password_explanation": "Ingrese su nombre de usuario para recibir un correo electrónico con las instrucciones para restablecer su contraseña",
|
||||
"forgot_password_success": "Pronto debería recibir un correo electrónico con las instrucciones para restablecer su contraseña. Asegúrese de verificar su correo no deseado si no puede encontrar el correo electrónico",
|
||||
"logging_in": "Iniciar sesión...",
|
||||
"login": "Iniciar sesión",
|
||||
"login_error": "Error de inicio de sesión, confirme que su nombre de usuario, contraseña y URL de puerta de enlace son válidos",
|
||||
"login_error": "Error de inicio de sesión, asegúrese de que la información que proporciona sea válida",
|
||||
"new_password": "Nueva contraseña",
|
||||
"password": "Contraseña",
|
||||
"please_enter_gateway": "Ingrese una URL de uCentralSec",
|
||||
"please_enter_password": "Por favor, introduzca su contraseña",
|
||||
"please_enter_username": "Por favor, ingrese su nombre de usuario",
|
||||
"previously_used": "La contraseña se usó anteriormente",
|
||||
"send_forgot": "Enviar correo electrónico",
|
||||
"sending_ellipsis": "Enviando...",
|
||||
"sign_in_to_account": "Iniciar sesión en su cuenta",
|
||||
"url": "URL de uCentralSec",
|
||||
"username": "Nombre de usuario"
|
||||
@@ -174,22 +432,27 @@
|
||||
"directions": "Ejecute un escaneo wifi de este dispositivo, que debería tomar aproximadamente 25 segundos.",
|
||||
"re_scan": "Vuelva a escanear",
|
||||
"result_directions": "Haga clic en el botón '$ t (scan.re_scan)' si desea realizar un escaneo con la misma configuración que el anterior.",
|
||||
"results": "Resultados de escaneo Wifi",
|
||||
"results": "Resultados de escaneo Wi-Fi",
|
||||
"scan": "Escanear",
|
||||
"scanning": "Exploración... ",
|
||||
"waiting_directions": "Espere el resultado del escaneo. Esto puede tardar hasta 25 segundos. Puede salir y ver los resultados de la tabla de comandos más adelante."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ajustes"
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Datos (KB)",
|
||||
"latest_statistics": "Últimas estadísticas",
|
||||
"show_latest": "Mostrar las últimas estadísticas JSON",
|
||||
"lifetime_stats": "Estadísticas de por vida",
|
||||
"no_interfaces": "No hay estadísticas de vida útil de la interfaz disponibles",
|
||||
"show_latest": "Últimas estadísticas",
|
||||
"title": "estadística"
|
||||
},
|
||||
"status": {
|
||||
"connection_status": "Estado de conexión",
|
||||
"connection_status": "Estado",
|
||||
"error": "Los datos de estado no están disponibles",
|
||||
"last_contact": "Último contacto",
|
||||
"load_averages": "Carga (promedio de 1/5/15 minutos)",
|
||||
"load_averages": "Carga (1/5/15 m.)",
|
||||
"localtime": "Hora local",
|
||||
"memory": "Memoria usada",
|
||||
"percentage_free": "{{percentage}}% de {{total}} gratis",
|
||||
@@ -198,6 +461,23 @@
|
||||
"uptime": "Tiempo de actividad",
|
||||
"used_total_memory": "{{used}} usado / {{total}} total"
|
||||
},
|
||||
"system": {
|
||||
"error_fetching": "Error al obtener información del sistema",
|
||||
"error_reloading": "Error al recargar: {{error}}",
|
||||
"hostname": "Nombre de host",
|
||||
"os": "sistema operativo",
|
||||
"processors": "Procesadores",
|
||||
"reload": "Recargar",
|
||||
"reload_subsystems": "Recargar",
|
||||
"subsystems": "Subsistemas",
|
||||
"success_reload": "¡El comando de recarga se envió correctamente!"
|
||||
},
|
||||
"telemetry": {
|
||||
"connection_failed": "No se pudo crear la conexión. Error: {{error}}",
|
||||
"interval": "intervalo",
|
||||
"last_update": "Última actualización",
|
||||
"types": "Los tipos"
|
||||
},
|
||||
"trace": {
|
||||
"choose_network": "Elija la red",
|
||||
"directions": "Lanzar un rastreo remoto de este dispositivo por una duración específica o por una cantidad de paquetes",
|
||||
@@ -205,6 +485,7 @@
|
||||
"packets": "Paquetes",
|
||||
"title": "Rastro",
|
||||
"trace": "Rastro",
|
||||
"trace_not_successful": "Seguimiento fallido: la puerta de enlace informó el siguiente error: {{error}}",
|
||||
"wait_for_file": "¿Le gustaría esperar hasta que el archivo de seguimiento esté listo?",
|
||||
"waiting_directions": "Espere el archivo de datos de seguimiento. Esto puede tomar algo de tiempo. Puede salir de la espera y recuperar el archivo de seguimiento de la tabla de comandos más tarde.",
|
||||
"waiting_seconds": "Tiempo transcurrido: {{seconds}} segundos"
|
||||
@@ -224,5 +505,53 @@
|
||||
"upgrade": "Mejorar",
|
||||
"wait_for_upgrade": "¿Le gustaría esperar a que finalice la actualización?",
|
||||
"waiting_for_device": "Esperando que el dispositivo se vuelva a conectar"
|
||||
},
|
||||
"user": {
|
||||
"avatar": "Tu avatar",
|
||||
"avatar_file": "Tu avatar (máx. De 2 MB)",
|
||||
"create": "Crear usuario",
|
||||
"create_failure": "Error al crear usuario. Asegúrese de que esta dirección de correo electrónico no esté vinculada a una cuenta.",
|
||||
"create_success": "Usuario creado con éxito",
|
||||
"creating": "Creando usuario ...",
|
||||
"delete_avatar": "Eliminar avatar",
|
||||
"delete_failure": "Error al intentar eliminar al usuario: {{error}}",
|
||||
"delete_success": "¡Usuario eliminado correctamente!",
|
||||
"delete_title": "Borrar usuario",
|
||||
"delete_warning": "Advertencia: una vez que elimina un usuario, no puede revertir",
|
||||
"deleting": "Eliminando ...",
|
||||
"description": "Descripción",
|
||||
"edit": "editar usuario",
|
||||
"email_address": "Dirección de correo electrónico",
|
||||
"error_fetching_users": "Error al obtener usuarios: {{error}}",
|
||||
"force_password_change": "Forzar cambio de contraseña al iniciar sesión",
|
||||
"id": "Id. De usuario",
|
||||
"last_login": "Último acceso",
|
||||
"login_id": "Ingresar identificación.",
|
||||
"my_profile": "Mi perfil",
|
||||
"name": "Nombre",
|
||||
"nickname": "Apodo",
|
||||
"nickname_explanation": "Apodo (opcional)",
|
||||
"not_validated": "No validado",
|
||||
"note": "Nota",
|
||||
"password": "Contraseña",
|
||||
"provide_email": "Por favor ingrese su dirección de correo electrónico válida",
|
||||
"provide_password": "Proporcione una contraseña válida",
|
||||
"save_avatar": "Guardar avatar",
|
||||
"show_hide_password": "Mostrar / Ocultar contraseña",
|
||||
"update_failure": "Error al intentar actualizar: {{error}}",
|
||||
"update_failure_title": "Actualización fallida",
|
||||
"update_success": "Usuario actualizado con éxito",
|
||||
"update_success_title": "Éxito",
|
||||
"user_role": "papel",
|
||||
"users": "Usuarios",
|
||||
"validated": "Validado"
|
||||
},
|
||||
"wifi_analysis": {
|
||||
"association": "Asociación",
|
||||
"associations": "Asociaciones",
|
||||
"mode": "Modo",
|
||||
"network_diagram": "Diagrama de Red",
|
||||
"radios": "Radios",
|
||||
"title": "Análisis de Wi-Fi"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"actions": "actes",
|
||||
"blink": "Cligner",
|
||||
"configure": "Configurer",
|
||||
"connect": "Relier",
|
||||
@@ -7,6 +8,7 @@
|
||||
"factory_reset": "Retour aux paramètres d'usine",
|
||||
"firmware_upgrade": "Mise à jour du firmware",
|
||||
"reboot": "Redémarrer",
|
||||
"telemetry": "Télémétrie",
|
||||
"title": "Les commandes",
|
||||
"trace": "Trace",
|
||||
"wifi_scan": "Balayage Wi-Fi"
|
||||
@@ -21,18 +23,28 @@
|
||||
},
|
||||
"commands": {
|
||||
"error": "Erreur lors de la soumission de la commande !",
|
||||
"error_delete_log": "Erreur lors de la tentative de suppression : {{error}}",
|
||||
"event_queue": "File d'attente d'événements",
|
||||
"success": "Commande soumise avec succès, vous pouvez consulter le journal des commandes pour le résultat",
|
||||
"title": "Historique des commandes"
|
||||
"title": "Historique des commandes",
|
||||
"unable_queue": "Impossible de terminer la demande de file d'attente d'événements: {{error}}"
|
||||
},
|
||||
"common": {
|
||||
"access_policy": "Politique d'accès",
|
||||
"add": "Ajouter",
|
||||
"adding_ellipsis": "Ajouter...",
|
||||
"are_you_sure": "Êtes-vous sûr?",
|
||||
"back_to_login": "Retour connexion",
|
||||
"back_to_start": "Retour au début",
|
||||
"by": "Par",
|
||||
"cancel": "annuler",
|
||||
"certificate": "Certificat",
|
||||
"certificates": "Certificats",
|
||||
"clear": "Clair",
|
||||
"close": "Fermer",
|
||||
"command": "Commander",
|
||||
"commands": "Les commandes",
|
||||
"commands_executed": "commandes exécutées",
|
||||
"compatible": "Compatible",
|
||||
"completed": "Terminé",
|
||||
"config_id": "Config. Identifiant",
|
||||
@@ -40,78 +52,168 @@
|
||||
"connected": "Connecté",
|
||||
"copied": "Copié!",
|
||||
"copy_to_clipboard": "Copier dans le presse-papier",
|
||||
"create": "Créer",
|
||||
"created": "Créé",
|
||||
"created_by": "Créé par",
|
||||
"current": "Actuel",
|
||||
"custom_date": "Date personnalisée",
|
||||
"dashboard": "Tableau de bord",
|
||||
"date": "Rendez-vous amoureux",
|
||||
"day": "journée",
|
||||
"days": "journées",
|
||||
"delete": "Effacer",
|
||||
"delete_device": "Supprimer le périphérique",
|
||||
"details": "Détails",
|
||||
"device": "N° d'appareil{{serialNumber}}",
|
||||
"device_dashboard": "Tableau de bord de l'appareil",
|
||||
"device_delete": "Supprimer l'appareil n°{{serialNumber}}",
|
||||
"device_deleted": "Appareil supprimé avec succès",
|
||||
"device_health": "Santé de l'appareil",
|
||||
"device_list": "Liste des appareils",
|
||||
"device_page": "Page de l'appareil",
|
||||
"device_page": "Vue",
|
||||
"device_status": "Statut du périphérique",
|
||||
"devices": "Dispositifs",
|
||||
"devices_using_latest": "Appareils utilisant le dernier micrologiciel",
|
||||
"devices_using_unknown": "Périphériques utilisant un micrologiciel inconnu",
|
||||
"dismiss": "Rejeter",
|
||||
"do_now": "Faire maintenant!",
|
||||
"download": "Télécharger",
|
||||
"duration": "Durée",
|
||||
"error": "erreur",
|
||||
"edit": "modifier",
|
||||
"edit_user": "Modifier",
|
||||
"email_address": "Adresse électronique",
|
||||
"endpoint": "Point final",
|
||||
"endpoints": "Points de terminaison",
|
||||
"error": "Erreur",
|
||||
"error_adding_note": "Erreur lors de l'ajout de la note",
|
||||
"execute_now": "Souhaitez-vous exécuter cette commande maintenant ?",
|
||||
"executed": "réalisé",
|
||||
"exit": "Sortie",
|
||||
"firmware": "Micrologiciel",
|
||||
"firmware_dashboard": "Tableau de bord du micrologiciel",
|
||||
"firmware_installed": "Micrologiciel installé",
|
||||
"forgot_password": "Mot de passe oublié?",
|
||||
"forgot_password_title": "Mot de passe oublié",
|
||||
"from": "De",
|
||||
"general_error": "Erreur API, veuillez consulter votre administrateur",
|
||||
"hide": "Cacher",
|
||||
"hour": "heure",
|
||||
"hours": "heures",
|
||||
"id": "Id",
|
||||
"ip_address": "Adresse IP",
|
||||
"items_per_page": "Objets par page:",
|
||||
"last_dashboard_refresh": "Dernière actualisation du tableau de bord",
|
||||
"later_tonight": "Plus tard ce soir",
|
||||
"latest": "Dernier",
|
||||
"list": "liste",
|
||||
"loading_ellipsis": "Chargement...",
|
||||
"loading_more_ellipsis": "Chargement plus ...",
|
||||
"logout": "Connectez - Out",
|
||||
"mac": "ADRESSE MAC",
|
||||
"manufacturer": "fabricant",
|
||||
"memory_used": "Mémoire utilisée",
|
||||
"minute": "minute",
|
||||
"minutes": "minutes",
|
||||
"modified": "Modifié",
|
||||
"na": "N / A",
|
||||
"need_date": "Vous avez besoin d'un rendez-vous...",
|
||||
"no": "Non",
|
||||
"no_devices_found": "Aucun périphérique trouvé",
|
||||
"no_items": "Pas d'objet",
|
||||
"none": "Aucun",
|
||||
"not_connected": "Pas connecté",
|
||||
"of_connected": "% d'appareils",
|
||||
"off": "De",
|
||||
"on": "sur",
|
||||
"optional": "Optionnel",
|
||||
"overall_health": "Santé globale",
|
||||
"password_policy": "Politique de mot de passe",
|
||||
"preview": "Aperçu",
|
||||
"recorded": "Enregistré",
|
||||
"refresh": "Rafraîchir",
|
||||
"refresh_device": "Actualiser l'appareil",
|
||||
"required": "Champs obligatoires",
|
||||
"result": "Résultat",
|
||||
"save": "Sauvegarder",
|
||||
"saved": "Enregistré!",
|
||||
"saving": "Économie...",
|
||||
"schedule": "Programme",
|
||||
"search": "Rechercher des appareils",
|
||||
"second": "seconde",
|
||||
"seconds": "secondes",
|
||||
"seconds_elapsed": "Secondes écoulées",
|
||||
"serial_number": "Numéro de série",
|
||||
"show_all": "Montre tout",
|
||||
"socket_connection_closed": "Connexion fermée !",
|
||||
"start": "Début",
|
||||
"stop_editing": "Arrêter la modification",
|
||||
"submit": "Soumettre",
|
||||
"submitted": "Soumis",
|
||||
"success": "Succès",
|
||||
"system": "Système",
|
||||
"table": "Table",
|
||||
"timestamp": "Temps",
|
||||
"to": "à",
|
||||
"type": "Type",
|
||||
"type_for_options": "Tapez la valeur que vous devez créer...",
|
||||
"type_for_options_format": "Saisissez une valeur au format valide ({{format}})...",
|
||||
"unable_to_connect": "Impossible de se connecter à l'appareil",
|
||||
"unable_to_delete": "Impossible de supprimer",
|
||||
"unknown": "Inconnu",
|
||||
"up_to_date": "Appareils à jour",
|
||||
"uptimes": "Disponibilités",
|
||||
"uuid": "UUID",
|
||||
"vendors": "Vendeurs",
|
||||
"view_more": "Afficher plus",
|
||||
"yes": "Oui"
|
||||
},
|
||||
"configuration": {
|
||||
"add_configuration": "Ajouter une configuration",
|
||||
"add_new_block": "Ajouter un nouveau bloc de configuration",
|
||||
"add_or_link": "Lier ou ajouter",
|
||||
"cannot_delete": "Cette configuration ne peut pas être supprimée car elle est utilisée par au moins une entité, un lieu ou un appareil",
|
||||
"choose_section": "Quelle section souhaitez-vous que ce bloc contienne?",
|
||||
"configuration_browser": "Navigateur de configuration",
|
||||
"configurations": "Les configurations",
|
||||
"create": "Créer une configuration",
|
||||
"create_config": "Créer une nouvelle configuration",
|
||||
"create_new_configuration": "Créer un nouvel élément de configuration",
|
||||
"created": "Créé",
|
||||
"creation_success": "Configuration créée avec succès !",
|
||||
"currently_associated": "Configuration associée actuelle : {{config}}",
|
||||
"currently_selected_config": "Configuration actuellement sélectionnée : {{config}}",
|
||||
"delete_config": "Supprimer la configuration",
|
||||
"details": "Détails",
|
||||
"device_password": "Mot de passe",
|
||||
"last_configuration_change": "Dernière modification de configuration",
|
||||
"device_type": "Type d'appareil",
|
||||
"device_types": "Types d'appareils",
|
||||
"edit_configuration": "Modifier la configuration",
|
||||
"error_delete": "Erreur lors de la tentative de suppression : {{error}}",
|
||||
"error_fetching_config": "Erreur lors de la récupération de la configuration",
|
||||
"error_trying_delete": "Erreur lors de la tentative de suppression : {{error}}",
|
||||
"error_update": "Erreur: {{error}}",
|
||||
"explanation": "Explication",
|
||||
"last_configuration_change": "Changement de configuration",
|
||||
"last_configuration_download": "Téléchargement de la dernière configuration",
|
||||
"location": "Emplacement",
|
||||
"need_device_type": "Chaque configuration doit prendre en charge au moins un type d'appareil",
|
||||
"no_associated_config": "Aucune configuration associée",
|
||||
"no_associated_configuration": "Aucune configuration associée",
|
||||
"note": "Remarque",
|
||||
"notes": "Remarques",
|
||||
"owner": "Propriétaire",
|
||||
"select_configuration": "Sélectionnez cette configuration",
|
||||
"success_block_delete": "Bloc de configuration supprimé avec succès",
|
||||
"success_update": "Configuration mise à jour avec succès !",
|
||||
"successful_delete": "Configuration supprimée !",
|
||||
"support_all": "Soutenir tous",
|
||||
"supported_device_types": "Types d'appareils pris en charge",
|
||||
"title": "Configuration",
|
||||
"type": "Type d'appareil",
|
||||
"used_by": "Utilisé par",
|
||||
"used_by_details": "{{entities}} Entités, {{venues}} Lieux et {{devices}} Appareils",
|
||||
"uuid": "Identifiant de configuration",
|
||||
"view_in_use": "Afficher en cours d'utilisation",
|
||||
"view_json": "Afficher le JSON brut"
|
||||
},
|
||||
"configure": {
|
||||
@@ -121,6 +223,9 @@
|
||||
"title": "Configurer",
|
||||
"valid_json": "Vous devez entrer un JSON valide"
|
||||
},
|
||||
"connect": {
|
||||
"error_trying_to_connect": "Erreur lors de la tentative de connexion à l'appareil : {{error}}"
|
||||
},
|
||||
"delete_command": {
|
||||
"explanation": "Êtes-vous sûr de vouloir supprimer cette commande ? Cette action n'est pas réversible.",
|
||||
"title": "Supprimer la commande"
|
||||
@@ -131,11 +236,37 @@
|
||||
"explanation": "Cela supprimera tous les {{object}} avant la date que vous choisissez. Attention, cette action n'est pas réversible.",
|
||||
"healthchecks_title": "Supprimer les vérifications d'état"
|
||||
},
|
||||
"device": {
|
||||
"error_fetching_device": "Erreur lors de la récupération des informations sur l'appareil : {{error}}",
|
||||
"error_fetching_devices": "Erreur lors de la récupération des appareils : {{error}}"
|
||||
},
|
||||
"device_logs": {
|
||||
"log": "Bûche",
|
||||
"severity": "Gravité",
|
||||
"title": "Journaux"
|
||||
},
|
||||
"entity": {
|
||||
"add_child": "Ajouter une entité enfant à {{entityName}}",
|
||||
"add_failure": "Erreur, le serveur a renvoyé : {{error}}",
|
||||
"add_root": "Ajouter une entité racine",
|
||||
"add_success": "Entité créée avec succès !",
|
||||
"assigned_inventory": "Inventaire assigné",
|
||||
"cannot_delete": "Vous ne pouvez pas supprimer des entités qui ont des enfants. Supprimez les enfants de cette entité pour pouvoir la supprimer.",
|
||||
"currently_selected_entity": "Entité actuellement sélectionnée : {{config}}",
|
||||
"currently_selected_venue": "Lieu actuellement sélectionné : {{config}}",
|
||||
"delete_success": "Entité supprimée avec succès",
|
||||
"delete_warning": "Attention : cette opération ne peut pas être annulée",
|
||||
"edit_failure": "Échec de la mise à jour : {{error}}",
|
||||
"entities": "Entités",
|
||||
"entity": "Entité",
|
||||
"error_fetch_entity": "Erreur lors de la récupération des informations sur l'entité",
|
||||
"error_fetching": "Erreur lors de la récupération des entités",
|
||||
"error_saving": "Erreur lors de l'enregistrement de l'entité",
|
||||
"not_assigned": "Non attribué",
|
||||
"only_unassigned": "Uniquement non attribué",
|
||||
"valid_serial": "Doit être un numéro de série valide (12 caractères HEX)",
|
||||
"venues": "Les lieux"
|
||||
},
|
||||
"factory_reset": {
|
||||
"redirector": "Conserver le redirecteur :",
|
||||
"reset": "Réinitialiser",
|
||||
@@ -143,6 +274,35 @@
|
||||
"title": "Retour aux paramètres d'usine",
|
||||
"warning": "Avertissement : Une fois que vous avez soumis, cela ne peut pas être annulé"
|
||||
},
|
||||
"firmware": {
|
||||
"average_age": "Âge moyen du micrologiciel",
|
||||
"choose_custom": "Choisir",
|
||||
"details_title": "Image #{{image}} Détails",
|
||||
"device_type": "Type d'appareil",
|
||||
"device_types": "Types d'appareils",
|
||||
"downloads": "Téléchargements",
|
||||
"error_fetching_latest": "Erreur lors de la récupération du dernier firmware",
|
||||
"from_release": "De",
|
||||
"history_title": "Historique",
|
||||
"image": "Image",
|
||||
"image_date": "Date de l'image",
|
||||
"installed_firmware": "Micrologiciel installé",
|
||||
"latest_version_installed": "Dernière version installée",
|
||||
"newer_firmware_available": "Révisions plus récentes disponibles",
|
||||
"reinstall_latest": "Réinstaller",
|
||||
"revision": "Révision",
|
||||
"show_dev": "Afficher les versions des développeurs",
|
||||
"size": "Taille",
|
||||
"status": "État du micrologiciel",
|
||||
"title": "Micrologiciel",
|
||||
"to_release": "à",
|
||||
"unknown_firmware_status": "État du micrologiciel inconnu",
|
||||
"upgrade": "Améliorer",
|
||||
"upgrade_command_submitted": "Commande de mise à niveau soumise avec succès",
|
||||
"upgrade_to_latest": "Dernier",
|
||||
"upgrade_to_version": "Mettre à niveau vers cette révision",
|
||||
"upgrading": "Mise à niveau..."
|
||||
},
|
||||
"footer": {
|
||||
"coreui_for_react": "CoreUI pour React",
|
||||
"powered_by": "Alimenté par",
|
||||
@@ -152,13 +312,111 @@
|
||||
"sanity": "Santé mentale",
|
||||
"title": "Santé"
|
||||
},
|
||||
"inventory": {
|
||||
"add_child": "Ajouter un lieu enfant",
|
||||
"add_child_venue": "Ajouter un lieu enfant à {{entityName}}",
|
||||
"add_tag": "Créer un tag",
|
||||
"add_tag_to": "Ajouter un nouvel appareil à {{name}}",
|
||||
"add_venue": "Ajouter un lieu",
|
||||
"assign_entity_instructions": "Vous pouvez soit trouver l'entité à laquelle vous souhaitez que cette balise soit attribuée en utilisant le menu ci-dessous, soit coller manuellement l'UUID de l'entité dans le champ ci-dessus.",
|
||||
"assign_error": "Erreur lors de la tentative d'attribution de balise",
|
||||
"assign_to_entity": "Affecter à l'entité",
|
||||
"bulk_delete_assigned": "Souhaitez-vous supprimer en bloc les appareils attribués dans votre fichier ?",
|
||||
"bulk_delete_assigned_warning": "Attention : cette action n'est pas réversible",
|
||||
"bulk_delete_devices": "Supprimer en bloc des appareils",
|
||||
"bulk_delete_devices_not_found": "{{number}} appareils introuvables",
|
||||
"bulk_delete_explanation": "Pour supprimer en bloc des appareils, utilisez un fichier CSV avec une colonne appelée SerialNumber",
|
||||
"bulk_delete_test": "Valider le fichier",
|
||||
"close_entity_menu": "Fermer le menu Modifier l'entité",
|
||||
"delete_devices": "Supprimer des appareils",
|
||||
"delete_errors": "{{number}} Supprimer les erreurs de périphérique",
|
||||
"delete_tag": "Supprimer la balise",
|
||||
"delete_venue": "Supprimer définitivement le lieu",
|
||||
"deleted_devices": "{{number}} appareils supprimés",
|
||||
"deleting": "Suppression ...",
|
||||
"deletion_failure": "Erreur de suppression",
|
||||
"devices_assigned": "{{number}} appareils existants attribués et mis à jour",
|
||||
"devices_created": "{{number}} appareils créés",
|
||||
"devices_deleted": "Appareils supprimés",
|
||||
"devices_errors_while_creating": "{{number}} créations d'appareils ont échoué",
|
||||
"devices_errors_while_updating": "{{number}} mises à jour de l'appareil ont échoué",
|
||||
"devices_found_assigned": "{{number}} appareils trouvés et déjà attribués à une entité ou à un lieu",
|
||||
"devices_found_unassigned": "{{number}} appareils trouvés, mais non attribués",
|
||||
"devices_imported": " Périphériques importés",
|
||||
"devices_not_found": "{{number}} appareils sans conflit",
|
||||
"devices_tested": "Appareils testés",
|
||||
"duplicate_serial": "Numéro de série déjà utilisé dans le fichier (duplicata)",
|
||||
"error_create_venue": "Erreur lors de la création du lieu",
|
||||
"error_delete_tag": "Erreur lors de la suppression de la balise d'inventaire",
|
||||
"error_get_venue": "Erreur lors de la récupération des lieux",
|
||||
"error_retrieving": "Une erreur s'est produite lors de la récupération des balises d'inventaire",
|
||||
"error_unassign": "Erreur lors de l'opération de désaffectation",
|
||||
"error_update_venue": "Erreur lors de la mise à jour du lieu",
|
||||
"error_venue_delete": "Erreur lors de la suppression du lieu",
|
||||
"error_within_file": "{{number}} appareils avec des informations erronées dans le fichier (sera ignoré)",
|
||||
"file_error": "Il semble y avoir une erreur dans votre fichier. Veuillez vous assurer que le fichier est au format CSV et contient les 5 colonnes mentionnées ci-dessus dans la première ligne du fichier",
|
||||
"final_delete_results": "Résultats de la suppression finale",
|
||||
"final_import_results": "Résultats d'importation finaux",
|
||||
"import_assigned_devices": "Voulez-vous les réaffecter avec cette importation ?",
|
||||
"import_assigned_devices_explanation": "Certains appareils sont en conflit avec des appareils déjà attribués. Vous devez résoudre ces problèmes avant d'importer.",
|
||||
"import_devices": "Importer des appareils",
|
||||
"import_devices_explanation": "Pour importer des appareils en bloc, vous devez utiliser un fichier CSV avec les colonnes suivantes : SerialNumber,Name,Description,DeviceType,NoteText",
|
||||
"import_devices_to": "Importer des appareils dans {{name}}",
|
||||
"import_existing_devices": "Mettre à jour et les attribuer ?",
|
||||
"import_existing_devices_explanation": "Certains appareils existent déjà dans l'inventaire et ne sont pas affectés.",
|
||||
"importing": "Importation ...",
|
||||
"last_modification": "dernière modification",
|
||||
"no_devices_to_delete": "Aucun appareil à supprimer",
|
||||
"no_devices_to_import": "Aucun appareil valide à créer ou à mettre à jour !",
|
||||
"note_text": "Texte de note",
|
||||
"passed_tests": "Tous les tests ont été passés, vos appareils sont prêts à être importés !",
|
||||
"serial_number_required": "Erreur : Numéro de série manquant",
|
||||
"showing_top_10": "Voici un aperçu des informations que nous avons récupérées dans votre dossier :",
|
||||
"sub_venues": "Sous-sites",
|
||||
"subscriber": "Abonné",
|
||||
"successful_assign": "Tag attribué avec succès",
|
||||
"successful_tag_delete": "Balise d'inventaire supprimée avec succès",
|
||||
"successful_tag_update": "Balise mise à jour avec succès",
|
||||
"successful_unassign": "L'opération de désaffectation a réussi",
|
||||
"successful_venue_create": "Lieu créé avec succès",
|
||||
"successful_venue_delete": "Lieu supprimé avec succès",
|
||||
"successful_venue_update": "Lieu mis à jour avec succès",
|
||||
"tag_created": "Tag d'inventaire créé avec succès",
|
||||
"tag_creation_error": "Erreur lors de la tentative de création d'une balise d'inventaire",
|
||||
"tag_update_error": "Erreur lors de la mise à jour de la balise",
|
||||
"tags_assigned_to": "Balises d'inventaire attribuées à {{name}}",
|
||||
"test_import": "Valider les données d'importation",
|
||||
"test_results": "Résultats de test",
|
||||
"title": "Inventaire",
|
||||
"type_invalid": "Erreur : Type de périphérique non valide",
|
||||
"unassign": "Annuler l'attribution",
|
||||
"unassign_tag": "Désaffecter la balise de l'entité",
|
||||
"unassigned_deleted_devices": "{{number}} appareils supprimés et non attribués",
|
||||
"unassigned_tags": "Balises non attribuées",
|
||||
"validating_import_file": "Validation du fichier d'importation et des données...",
|
||||
"venue": "Lieu"
|
||||
},
|
||||
"login": {
|
||||
"change_password": "Changer le mot de passe",
|
||||
"change_password_error": "Erreur lors du changement de mot de passe. Assurez-vous que le nouveau mot de passe est valide en visitant la page « Politique de mot de passe »",
|
||||
"change_password_instructions": "Saisissez et confirmez votre nouveau mot de passe",
|
||||
"changing_password": "Modification du mot de passe...",
|
||||
"confirm_new_password": "Confirmer le nouveau mot de passe",
|
||||
"different_passwords": "Vous devez saisir deux fois le même mot de passe",
|
||||
"forgot_password_error": "Erreur lors de la tentative d'envoi de l'e-mail Mot de passe oublié. Veuillez vous assurer que cet identifiant est associé à un compte.",
|
||||
"forgot_password_explanation": "Entrez votre nom d'utilisateur pour recevoir un e-mail contenant les instructions pour réinitialiser votre mot de passe",
|
||||
"forgot_password_success": "Vous devriez bientôt recevoir un e-mail contenant les instructions pour réinitialiser votre mot de passe. S'il vous plaît assurez-vous de vérifier vos spams si vous ne trouvez pas l'e-mail",
|
||||
"logging_in": "Se connecter...",
|
||||
"login": "S'identifier",
|
||||
"login_error": "Erreur de connexion, confirmez que votre nom d'utilisateur, mot de passe et URL de passerelle sont valides",
|
||||
"login_error": "Erreur de connexion, assurez-vous que les informations que vous fournissez sont valides",
|
||||
"new_password": "Nouveau mot de passe",
|
||||
"password": "Mot de passe",
|
||||
"please_enter_gateway": "Veuillez saisir une URL uCentralSec",
|
||||
"please_enter_password": "s'il vous plait entrez votre mot de passe",
|
||||
"please_enter_username": "s'il vous plaît entrez votre nom d'utilisateur",
|
||||
"previously_used": "Le mot de passe a déjà été utilisé",
|
||||
"send_forgot": "Envoyer un email",
|
||||
"sending_ellipsis": "Envoi...",
|
||||
"sign_in_to_account": "Connectez-vous à votre compte",
|
||||
"url": "URL uCentralSec",
|
||||
"username": "Nom d'utilisateur"
|
||||
@@ -179,17 +437,22 @@
|
||||
"scanning": "Balayage... ",
|
||||
"waiting_directions": "Veuillez attendre le résultat de l'analyse. Cela peut prendre jusqu'à 25 secondes. Vous pouvez quitter et consulter les résultats du tableau des commandes plus tard."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Réglages"
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Données (Ko)",
|
||||
"latest_statistics": "Dernières statistiques",
|
||||
"show_latest": "Afficher les dernières statistiques JSON",
|
||||
"lifetime_stats": "Statistiques à vie",
|
||||
"no_interfaces": "Aucune statistique de durée de vie de l'interface disponible",
|
||||
"show_latest": "Dernières statistiques",
|
||||
"title": "statistiques"
|
||||
},
|
||||
"status": {
|
||||
"connection_status": "Statut de connexion",
|
||||
"connection_status": "Statut",
|
||||
"error": "Les données d'état ne sont pas disponibles",
|
||||
"last_contact": "Dernier contact",
|
||||
"load_averages": "Charge (moyenne 1 / 5 / 15 minutes)",
|
||||
"load_averages": "Charge (1/5/15 m.)",
|
||||
"localtime": "heure locale",
|
||||
"memory": "Mémoire utilisée",
|
||||
"percentage_free": "{{percentage}}% de {{total}} gratuit",
|
||||
@@ -198,6 +461,23 @@
|
||||
"uptime": "La disponibilité",
|
||||
"used_total_memory": "{{used}} utilisé / {{total}} total"
|
||||
},
|
||||
"system": {
|
||||
"error_fetching": "Erreur lors de la récupération des informations système",
|
||||
"error_reloading": "Erreur lors du rechargement : {{error}}",
|
||||
"hostname": "nom d'hôte",
|
||||
"os": "le système d'exploitation",
|
||||
"processors": "Processeurs",
|
||||
"reload": "Recharger",
|
||||
"reload_subsystems": "Recharger",
|
||||
"subsystems": "Sous-systèmes",
|
||||
"success_reload": "Recharger la commande soumise avec succès !"
|
||||
},
|
||||
"telemetry": {
|
||||
"connection_failed": "Échec de la création de la connexion. Erreur : {{error}}",
|
||||
"interval": "Intervalle",
|
||||
"last_update": "Dernière mise à jour",
|
||||
"types": "Les types"
|
||||
},
|
||||
"trace": {
|
||||
"choose_network": "Choisir le réseau",
|
||||
"directions": "Lancer une trace à distance de cet appareil pour une durée spécifique ou un nombre de paquets",
|
||||
@@ -205,6 +485,7 @@
|
||||
"packets": "Paquets",
|
||||
"title": "Trace",
|
||||
"trace": "Trace",
|
||||
"trace_not_successful": "Trace non réussie : la passerelle a signalé l'erreur suivante : {{error}}",
|
||||
"wait_for_file": "Souhaitez-vous attendre que le fichier de trace soit prêt ?",
|
||||
"waiting_directions": "Veuillez attendre le fichier de données de trace. Cela peut prendre un certain temps. Vous pouvez quitter l'attente et récupérer le fichier de trace de la table des commandes plus tard.",
|
||||
"waiting_seconds": "Temps écoulé : {{seconds}} secondes"
|
||||
@@ -224,5 +505,53 @@
|
||||
"upgrade": "Améliorer",
|
||||
"wait_for_upgrade": "Souhaitez-vous attendre la fin de la mise à niveau ?",
|
||||
"waiting_for_device": "En attente de la reconnexion de l'appareil"
|
||||
},
|
||||
"user": {
|
||||
"avatar": "Votre avatar",
|
||||
"avatar_file": "Votre Avatar (max. de 2 Mo)",
|
||||
"create": "Créer un utilisateur",
|
||||
"create_failure": "Erreur lors de la création de l'utilisateur. Veuillez vous assurer que cette adresse e-mail n'est pas déjà liée à un compte.",
|
||||
"create_success": "L'utilisateur a été créé avec succès",
|
||||
"creating": "Création de l'utilisateur...",
|
||||
"delete_avatar": "Supprimer l'avatar",
|
||||
"delete_failure": "Erreur lors de la tentative de suppression de l'utilisateur: {{error}}",
|
||||
"delete_success": "Utilisateur supprimé avec succès !",
|
||||
"delete_title": "Supprimer l'utilisateur",
|
||||
"delete_warning": "Avertissement : Une fois que vous avez supprimé un utilisateur, vous ne pouvez plus revenir en arrière",
|
||||
"deleting": "Suppression ...",
|
||||
"description": "La description",
|
||||
"edit": "Modifier l'utilisateur",
|
||||
"email_address": "Adresse électronique",
|
||||
"error_fetching_users": "Erreur lors de la récupération des utilisateurs : {{error}}",
|
||||
"force_password_change": "Forcer le changement de mot de passe lors de la connexion",
|
||||
"id": "Identifiant d'utilisateur.",
|
||||
"last_login": "Dernière connexion",
|
||||
"login_id": "Identifiant de connexion.",
|
||||
"my_profile": "Mon profil",
|
||||
"name": "Prénom",
|
||||
"nickname": "Surnom",
|
||||
"nickname_explanation": "Surnom (optionnel)",
|
||||
"not_validated": "Pas valide",
|
||||
"note": "Remarque",
|
||||
"password": "Mot de passe",
|
||||
"provide_email": "Veuillez fournir une adresse email valide",
|
||||
"provide_password": "Veuillez fournir un mot de passe valide",
|
||||
"save_avatar": "Enregistrer l'avatar",
|
||||
"show_hide_password": "Afficher/Masquer le mot de passe",
|
||||
"update_failure": "Erreur lors de la tentative de mise à jour : {{error}}",
|
||||
"update_failure_title": "mise à jour a échoué",
|
||||
"update_success": "L'utilisateur a bien été mis à jour",
|
||||
"update_success_title": "Succès",
|
||||
"user_role": "Rôle",
|
||||
"users": "Utilisateurs",
|
||||
"validated": "Validé"
|
||||
},
|
||||
"wifi_analysis": {
|
||||
"association": "Association",
|
||||
"associations": "Les associations",
|
||||
"mode": "Mode",
|
||||
"network_diagram": "Diagramme de réseau",
|
||||
"radios": "Radios",
|
||||
"title": "Analyse Wi-Fi"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"actions": "Ações",
|
||||
"blink": "Piscar",
|
||||
"configure": "Configurar",
|
||||
"connect": "Conectar",
|
||||
@@ -7,9 +8,10 @@
|
||||
"factory_reset": "Restauração de fábrica",
|
||||
"firmware_upgrade": "Atualização de firmware",
|
||||
"reboot": "Reiniciar",
|
||||
"telemetry": "Telemetria",
|
||||
"title": "Comandos",
|
||||
"trace": "Vestígio",
|
||||
"wifi_scan": "Wifi Scan"
|
||||
"wifi_scan": "Wi-Fi Scan"
|
||||
},
|
||||
"blink": {
|
||||
"blink": "Piscar",
|
||||
@@ -21,18 +23,28 @@
|
||||
},
|
||||
"commands": {
|
||||
"error": "Erro ao enviar comando!",
|
||||
"error_delete_log": "Erro ao tentar excluir: {{error}}",
|
||||
"event_queue": "Fila de Eventos",
|
||||
"success": "Comando enviado com sucesso, você pode consultar o log de Comandos para ver o resultado",
|
||||
"title": "Histórico de Comandos"
|
||||
"title": "Histórico de Comandos",
|
||||
"unable_queue": "Incapaz de completar o pedido de fila de eventos: {{error}}"
|
||||
},
|
||||
"common": {
|
||||
"access_policy": "Política de Acesso",
|
||||
"add": "Adicionar",
|
||||
"adding_ellipsis": "Adicionando ...",
|
||||
"are_you_sure": "Você tem certeza?",
|
||||
"back_to_login": "Volte ao login",
|
||||
"back_to_start": "Voltar ao Início",
|
||||
"by": "Por",
|
||||
"cancel": "Cancelar",
|
||||
"certificate": "Certificado",
|
||||
"certificates": "Certificados",
|
||||
"clear": "Claro",
|
||||
"close": "Perto",
|
||||
"command": "Comando",
|
||||
"commands": "comandos",
|
||||
"commands_executed": "Comandos Executados",
|
||||
"compatible": "Compatível",
|
||||
"completed": "Concluído",
|
||||
"config_id": "Config. Identidade",
|
||||
@@ -40,78 +52,168 @@
|
||||
"connected": "Conectado",
|
||||
"copied": "Copiado!",
|
||||
"copy_to_clipboard": "Copiar para área de transferência",
|
||||
"create": "Crio",
|
||||
"created": "Criado",
|
||||
"created_by": "Criado Por",
|
||||
"current": "Atual",
|
||||
"custom_date": "Data personalizada",
|
||||
"dashboard": "painel de controle",
|
||||
"date": "Encontro",
|
||||
"day": "dia",
|
||||
"days": "dias",
|
||||
"delete": "Excluir",
|
||||
"delete_device": "Apagar dispositivo",
|
||||
"details": "Detalhes",
|
||||
"device": "Dispositivo nº{{serialNumber}}",
|
||||
"device_dashboard": "Painel do dispositivo",
|
||||
"device_delete": "Excluir dispositivo nº{{serialNumber}}",
|
||||
"device_deleted": "Dispositivo excluído com sucesso",
|
||||
"device_health": "Saúde do Dispositivo",
|
||||
"device_list": "Lista de Dispositivos",
|
||||
"device_page": "Página do dispositivo",
|
||||
"device_page": "Visão",
|
||||
"device_status": "Status do dispositivo",
|
||||
"devices": "Devices",
|
||||
"devices_using_latest": "Dispositivos que usam o firmware mais recente",
|
||||
"devices_using_unknown": "Dispositivos que usam firmware desconhecido",
|
||||
"dismiss": "Dispensar",
|
||||
"do_now": "Faça agora!",
|
||||
"download": "Baixar",
|
||||
"duration": "Duração",
|
||||
"edit": "Editar",
|
||||
"edit_user": "Editar",
|
||||
"email_address": "Endereço de e-mail",
|
||||
"endpoint": "Ponto final",
|
||||
"endpoints": "Pontos finais",
|
||||
"error": "Erro",
|
||||
"error_adding_note": "Erro ao adicionar nota",
|
||||
"execute_now": "Você gostaria de executar este comando agora?",
|
||||
"executed": "Executado",
|
||||
"exit": "Saída",
|
||||
"firmware": "Firmware",
|
||||
"firmware_dashboard": "Painel de Firmware",
|
||||
"firmware_installed": "Firmware Instalado",
|
||||
"forgot_password": "Esqueceu sua senha?",
|
||||
"forgot_password_title": "Esqueceu a senha",
|
||||
"from": "De",
|
||||
"general_error": "Erro de API, consulte o seu administrador",
|
||||
"hide": "Ocultar",
|
||||
"hour": "hora",
|
||||
"hours": "horas",
|
||||
"id": "identidade",
|
||||
"ip_address": "Endereço de IP",
|
||||
"items_per_page": "Itens por página:",
|
||||
"last_dashboard_refresh": "Última atualização do painel",
|
||||
"later_tonight": "Logo à noite",
|
||||
"latest": "Mais recentes",
|
||||
"list": "Lista",
|
||||
"loading_ellipsis": "Carregando...",
|
||||
"loading_more_ellipsis": "Carregando mais ...",
|
||||
"logout": "Sair",
|
||||
"mac": "Endereço MAC",
|
||||
"manufacturer": "Fabricante",
|
||||
"memory_used": "Memória Usada",
|
||||
"minute": "minuto",
|
||||
"minutes": "minutos",
|
||||
"modified": "Modificado",
|
||||
"na": "N / D",
|
||||
"need_date": "Você precisa de um encontro ...",
|
||||
"no": "Não",
|
||||
"no_devices_found": "Nenhum dispositivo encontrado",
|
||||
"no_items": "Nenhum item",
|
||||
"none": "Nenhum",
|
||||
"not_connected": "Não conectado",
|
||||
"of_connected": "% de dispositivos",
|
||||
"off": "Fora",
|
||||
"on": "em",
|
||||
"optional": "Opcional",
|
||||
"overall_health": "Saúde geral",
|
||||
"password_policy": "Política de Senha",
|
||||
"preview": "Visualizar",
|
||||
"recorded": "Gravado",
|
||||
"refresh": "REFRESH",
|
||||
"refresh_device": "Atualizar dispositivo",
|
||||
"required": "Requeridos",
|
||||
"result": "Resultado",
|
||||
"save": "Salve",
|
||||
"saved": "Salvou!",
|
||||
"saving": "Salvando ...",
|
||||
"schedule": "Cronograma",
|
||||
"search": "Dispositivos de pesquisa",
|
||||
"second": "segundo",
|
||||
"seconds": "segundos",
|
||||
"seconds_elapsed": "Segundos decorridos",
|
||||
"serial_number": "Número de série",
|
||||
"show_all": "mostre tudo",
|
||||
"socket_connection_closed": "Conexão fechada!",
|
||||
"start": "Começar",
|
||||
"stop_editing": "Pare de editar",
|
||||
"submit": "Enviar",
|
||||
"submitted": "Submetido",
|
||||
"success": "Sucesso",
|
||||
"system": "Sistema",
|
||||
"table": "Mesa",
|
||||
"timestamp": "tempo",
|
||||
"to": "Para",
|
||||
"type": "Tipo",
|
||||
"type_for_options": "Digite o valor que você precisa criar ...",
|
||||
"type_for_options_format": "Digite um valor no formato válido ({{format}}) ...",
|
||||
"unable_to_connect": "Incapaz de conectar ao dispositivo",
|
||||
"unable_to_delete": "Incapaz de deletar",
|
||||
"unknown": "Desconhecido",
|
||||
"up_to_date": "Dispositivos atualizados",
|
||||
"uptimes": "Uptimes",
|
||||
"uuid": "UUID",
|
||||
"vendors": "Vendedores",
|
||||
"view_more": "Veja mais",
|
||||
"yes": "sim"
|
||||
},
|
||||
"configuration": {
|
||||
"add_configuration": "Adicionar configuração",
|
||||
"add_new_block": "Adicionar novo bloco de configuração",
|
||||
"add_or_link": "Link ou adicionar",
|
||||
"cannot_delete": "Esta configuração não pode ser excluída porque está sendo usada por pelo menos uma entidade, local ou dispositivo",
|
||||
"choose_section": "Qual seção você gostaria que este bloco contivesse?",
|
||||
"configuration_browser": "Navegador de configuração",
|
||||
"configurations": "configurações",
|
||||
"create": "Criar configuração",
|
||||
"create_config": "Criar Nova Configuração",
|
||||
"create_new_configuration": "Criar Novo Elemento de Configuração",
|
||||
"created": "Criado",
|
||||
"creation_success": "Configuração criada com sucesso!",
|
||||
"currently_associated": "Configuração atual associada: {{config}}",
|
||||
"currently_selected_config": "Configuração atualmente selecionada: {{config}}",
|
||||
"delete_config": "Excluir configuração",
|
||||
"details": "Detalhes",
|
||||
"device_password": "Senha",
|
||||
"last_configuration_change": "Última Mudança de Configuração",
|
||||
"device_type": "Tipo de dispositivo",
|
||||
"device_types": "Tipos de dispositivos",
|
||||
"edit_configuration": "Editar configuração",
|
||||
"error_delete": "Erro ao tentar excluir: {{error}}",
|
||||
"error_fetching_config": "Erro ao buscar configuração",
|
||||
"error_trying_delete": "Erro ao tentar excluir: {{error}}",
|
||||
"error_update": "Erro: {{error}}",
|
||||
"explanation": "Explicação",
|
||||
"last_configuration_change": "Mudança de configuração",
|
||||
"last_configuration_download": "Último download da configuração",
|
||||
"location": "Localização",
|
||||
"need_device_type": "Cada configuração deve suportar pelo menos um tipo de dispositivo",
|
||||
"no_associated_config": "Sem configuração associada",
|
||||
"no_associated_configuration": "Sem configurações associadas",
|
||||
"note": "Nota",
|
||||
"notes": "notas",
|
||||
"owner": "Proprietário",
|
||||
"select_configuration": "Selecione esta configuração",
|
||||
"success_block_delete": "Bloco de configuração excluído com sucesso",
|
||||
"success_update": "Configuração atualizada com sucesso!",
|
||||
"successful_delete": "Configuração excluída!",
|
||||
"support_all": "Apoie todos",
|
||||
"supported_device_types": "Tipos de dispositivos suportados",
|
||||
"title": "Configuração",
|
||||
"type": "Tipo de dispositivo",
|
||||
"used_by": "Usado por",
|
||||
"used_by_details": "{{entities}} Entidades, {{venues}} Locais e {{devices}} Dispositivos",
|
||||
"uuid": "ID de configuração",
|
||||
"view_in_use": "Visualização em uso",
|
||||
"view_json": "Exibir JSON bruto"
|
||||
},
|
||||
"configure": {
|
||||
@@ -121,6 +223,9 @@
|
||||
"title": "Configurar",
|
||||
"valid_json": "Você precisa inserir um JSON válido"
|
||||
},
|
||||
"connect": {
|
||||
"error_trying_to_connect": "Erro ao tentar conectar ao dispositivo: {{error}}"
|
||||
},
|
||||
"delete_command": {
|
||||
"explanation": "Tem certeza de que deseja excluir este comando? esta ação não é reversível.",
|
||||
"title": "Apagar Comando"
|
||||
@@ -131,11 +236,37 @@
|
||||
"explanation": "Isso excluirá todos os {{object}} antes da data que você escolheu. Cuidado, esta ação não é reversível.",
|
||||
"healthchecks_title": "Excluir verificações de saúde"
|
||||
},
|
||||
"device": {
|
||||
"error_fetching_device": "Erro ao buscar informações do dispositivo: {{error}}",
|
||||
"error_fetching_devices": "Erro ao buscar dispositivos: {{error}}"
|
||||
},
|
||||
"device_logs": {
|
||||
"log": "Registro",
|
||||
"severity": "Gravidade",
|
||||
"title": "Toras"
|
||||
},
|
||||
"entity": {
|
||||
"add_child": "Adicionar Entidade Filha a {{entityName}}",
|
||||
"add_failure": "Erro, o servidor retornou: {{error}}",
|
||||
"add_root": "Adicionar Entidade Raiz",
|
||||
"add_success": "Entidade criada com sucesso!",
|
||||
"assigned_inventory": "Estoque Atribuído",
|
||||
"cannot_delete": "Você não pode excluir entidades que têm filhos. Exclua os filhos desta entidade para poder excluí-la.",
|
||||
"currently_selected_entity": "Entidade atualmente selecionada: {{config}}",
|
||||
"currently_selected_venue": "Local selecionado atualmente: {{config}}",
|
||||
"delete_success": "Entidade excluída com sucesso",
|
||||
"delete_warning": "Aviso: esta operação não pode ser revertida",
|
||||
"edit_failure": "Atualização malsucedida: {{error}}",
|
||||
"entities": "Entidades",
|
||||
"entity": "Entidade",
|
||||
"error_fetch_entity": "Erro ao buscar informações da entidade",
|
||||
"error_fetching": "Erro ao buscar entidades",
|
||||
"error_saving": "Erro ao salvar entidade",
|
||||
"not_assigned": "Não atribuído",
|
||||
"only_unassigned": "Apenas não atribuídos",
|
||||
"valid_serial": "Precisa ser um número de série válido (12 caracteres HEX)",
|
||||
"venues": "Locais"
|
||||
},
|
||||
"factory_reset": {
|
||||
"redirector": "Manter redirecionador:",
|
||||
"reset": "Restabelecer",
|
||||
@@ -143,6 +274,35 @@
|
||||
"title": "Restauração de fábrica",
|
||||
"warning": "Aviso: depois de enviar, isso não pode ser revertido"
|
||||
},
|
||||
"firmware": {
|
||||
"average_age": "Idade Média do Firmware",
|
||||
"choose_custom": "Escolher",
|
||||
"details_title": "Detalhes da imagem #{{image}} ",
|
||||
"device_type": "Tipo de dispositivo",
|
||||
"device_types": "Tipos de dispositivos",
|
||||
"downloads": "Transferências",
|
||||
"error_fetching_latest": "Erro ao buscar o firmware mais recente",
|
||||
"from_release": "De",
|
||||
"history_title": "História",
|
||||
"image": "Imagem",
|
||||
"image_date": "Data da Imagem",
|
||||
"installed_firmware": "Firmware Instalado",
|
||||
"latest_version_installed": "Última versão instalada",
|
||||
"newer_firmware_available": "Novas revisões disponíveis",
|
||||
"reinstall_latest": "Reinstalar",
|
||||
"revision": "Revisão",
|
||||
"show_dev": "Mostrar lançamentos de desenvolvimento",
|
||||
"size": "Tamanho",
|
||||
"status": "Status do firmware",
|
||||
"title": "Firmware",
|
||||
"to_release": "Para",
|
||||
"unknown_firmware_status": "Status de firmware desconhecido",
|
||||
"upgrade": "Melhorar",
|
||||
"upgrade_command_submitted": "Comando de atualização enviado com sucesso",
|
||||
"upgrade_to_latest": "Mais recentes",
|
||||
"upgrade_to_version": "Atualize para esta revisão",
|
||||
"upgrading": "Atualizando ..."
|
||||
},
|
||||
"footer": {
|
||||
"coreui_for_react": "CoreUI para React",
|
||||
"powered_by": "Distribuído por",
|
||||
@@ -152,13 +312,111 @@
|
||||
"sanity": "Sanidade",
|
||||
"title": "Saúde"
|
||||
},
|
||||
"inventory": {
|
||||
"add_child": "Adicionar local infantil",
|
||||
"add_child_venue": "Adicionar Local Infantil a {{entityName}}",
|
||||
"add_tag": "Criar tag",
|
||||
"add_tag_to": "Adicionar novo dispositivo a {{name}}",
|
||||
"add_venue": "Adicionar Local",
|
||||
"assign_entity_instructions": "Você pode encontrar a entidade à qual deseja que esta tag seja atribuída usando o menu abaixo ou pode colar manualmente o UUID da entidade no campo acima.",
|
||||
"assign_error": "Erro ao tentar atribuir tag",
|
||||
"assign_to_entity": "Atribuir à Entidade",
|
||||
"bulk_delete_assigned": "Gostaria de excluir em massa os dispositivos atribuídos em seu arquivo?",
|
||||
"bulk_delete_assigned_warning": "Aviso: esta ação não é reversível",
|
||||
"bulk_delete_devices": "Dispositivos de exclusão em massa",
|
||||
"bulk_delete_devices_not_found": "{{number}} dispositivos não encontrados",
|
||||
"bulk_delete_explanation": "Para excluir dispositivos em massa, use um arquivo CSV com uma coluna chamada SerialNumber",
|
||||
"bulk_delete_test": "Validar Arquivo",
|
||||
"close_entity_menu": "Fechar Menu Editar Entidade",
|
||||
"delete_devices": "Apagar dispositivos",
|
||||
"delete_errors": "{{number}} Excluir erros do dispositivo",
|
||||
"delete_tag": "Excluir tag",
|
||||
"delete_venue": "Excluir local permanentemente",
|
||||
"deleted_devices": "{{number}} Dispositivos Excluídos",
|
||||
"deleting": "Excluindo ...",
|
||||
"deletion_failure": "Erro de exclusão",
|
||||
"devices_assigned": "{{number}} dispositivos existentes atribuídos e atualizados",
|
||||
"devices_created": "{{number}} dispositivos criados",
|
||||
"devices_deleted": "Dispositivos Excluídos",
|
||||
"devices_errors_while_creating": "{{number}} criações de dispositivos falharam",
|
||||
"devices_errors_while_updating": "{{number}} atualizações do dispositivo falharam",
|
||||
"devices_found_assigned": "{{number}} dispositivos encontrados e já atribuídos a uma entidade ou local",
|
||||
"devices_found_unassigned": "{{number}} dispositivos encontrados, mas não atribuídos",
|
||||
"devices_imported": " Dispositivos importados",
|
||||
"devices_not_found": "{{number}} dispositivos sem conflito",
|
||||
"devices_tested": "Dispositivos Testados",
|
||||
"duplicate_serial": "Número de série já usado no arquivo (duplicado)",
|
||||
"error_create_venue": "Erro ao criar local",
|
||||
"error_delete_tag": "Erro ao excluir tag de inventário",
|
||||
"error_get_venue": "Erro ao recuperar locais",
|
||||
"error_retrieving": "Ocorreu um erro ao recuperar as tags de inventário",
|
||||
"error_unassign": "Erro durante operação de cancelamento de atribuição",
|
||||
"error_update_venue": "Erro ao atualizar o local",
|
||||
"error_venue_delete": "Erro ao excluir local",
|
||||
"error_within_file": "{{number}} dispositivos com informações erradas no arquivo (serão ignorados)",
|
||||
"file_error": "Parece haver um erro em seu arquivo. Certifique-se de que o arquivo esteja no formato CSV e contenha as 5 colunas mencionadas acima na primeira linha do arquivo",
|
||||
"final_delete_results": "Resultados de exclusão final",
|
||||
"final_import_results": "Resultados finais de importação",
|
||||
"import_assigned_devices": "Quer reatribuí-los com esta importação?",
|
||||
"import_assigned_devices_explanation": "Alguns dispositivos entram em conflito com dispositivos já atribuídos. Você deve resolver esses problemas antes de importar.",
|
||||
"import_devices": "Dispositivos de importação",
|
||||
"import_devices_explanation": "Para importar dispositivos em massa, você precisa usar um arquivo CSV com as seguintes colunas: SerialNumber, Name, Description, DeviceType, NoteText",
|
||||
"import_devices_to": "Importar dispositivos para {{name}}",
|
||||
"import_existing_devices": "Atualizar e atribuí-los?",
|
||||
"import_existing_devices_explanation": "Alguns dispositivos já existem no inventário e não foram atribuídos.",
|
||||
"importing": "A importar ...",
|
||||
"last_modification": "Última modificação",
|
||||
"no_devices_to_delete": "Nenhum dispositivo para excluir",
|
||||
"no_devices_to_import": "Nenhum dispositivo válido para criar ou atualizar!",
|
||||
"note_text": "Texto da Nota",
|
||||
"passed_tests": "Todos os testes foram aprovados, seus dispositivos estão prontos para importar!",
|
||||
"serial_number_required": "Erro: Número de série ausente",
|
||||
"showing_top_10": "Aqui está uma prévia das informações que recuperamos de seu arquivo:",
|
||||
"sub_venues": "Subvenues",
|
||||
"subscriber": "Assinante",
|
||||
"successful_assign": "Tag atribuída com sucesso",
|
||||
"successful_tag_delete": "Tag de inventário excluída com sucesso",
|
||||
"successful_tag_update": "Tag atualizada com sucesso",
|
||||
"successful_unassign": "A operação de cancelamento da atribuição foi bem-sucedida",
|
||||
"successful_venue_create": "Local criado com sucesso",
|
||||
"successful_venue_delete": "Local excluído com sucesso",
|
||||
"successful_venue_update": "Local atualizado com sucesso",
|
||||
"tag_created": "Tag de inventário criada com sucesso",
|
||||
"tag_creation_error": "Erro ao tentar criar etiqueta de inventário",
|
||||
"tag_update_error": "Erro ao atualizar tag",
|
||||
"tags_assigned_to": "Tags de inventário atribuídas a {{name}}",
|
||||
"test_import": "Validar dados de importação",
|
||||
"test_results": "Resultado dos testes",
|
||||
"title": "Inventário",
|
||||
"type_invalid": "Erro: Tipo de dispositivo inválido",
|
||||
"unassign": "Cancelar atribuição",
|
||||
"unassign_tag": "Cancelar a atribuição de tag da entidade",
|
||||
"unassigned_deleted_devices": "{{number}} Dispositivos excluídos e não atribuídos",
|
||||
"unassigned_tags": "Tags não atribuídas",
|
||||
"validating_import_file": "Validando arquivo de importação e dados ...",
|
||||
"venue": "Local"
|
||||
},
|
||||
"login": {
|
||||
"change_password": "Mudar senha",
|
||||
"change_password_error": "Erro ao alterar a senha. Certifique-se de que a nova senha é válida visitando a página 'Política de senha'",
|
||||
"change_password_instructions": "Digite e confirme sua nova senha",
|
||||
"changing_password": "Alterando senha ...",
|
||||
"confirm_new_password": "confirme a nova senha",
|
||||
"different_passwords": "Você precisa inserir a mesma senha duas vezes",
|
||||
"forgot_password_error": "Erro ao tentar enviar e-mail Esqueci a senha. Certifique-se de que este userId esteja associado a uma conta.",
|
||||
"forgot_password_explanation": "Digite seu nome de usuário para receber um e-mail contendo as instruções para redefinir sua senha",
|
||||
"forgot_password_success": "Em breve, você receberá um e-mail com as instruções para redefinir sua senha. Certifique-se de verificar o seu spam se você não conseguir encontrar o e-mail",
|
||||
"logging_in": "Fazendo login ...",
|
||||
"login": "Entrar",
|
||||
"login_error": "Erro de login, confirme se seu nome de usuário, senha e url de gateway são válidos",
|
||||
"login_error": "Erro de login, certifique-se de que as informações que você está fornecendo são válidas",
|
||||
"new_password": "Nova senha",
|
||||
"password": "Senha",
|
||||
"please_enter_gateway": "Insira um URL uCentralSec",
|
||||
"please_enter_password": "Por favor, insira sua senha",
|
||||
"please_enter_username": "Por favor insira seu nome de usuário",
|
||||
"previously_used": "A senha foi usada anteriormente",
|
||||
"send_forgot": "ENVIAR EMAIL",
|
||||
"sending_ellipsis": "Enviando ...",
|
||||
"sign_in_to_account": "Faça login em sua conta",
|
||||
"url": "URL uCentralSec",
|
||||
"username": "Nome de usuário"
|
||||
@@ -174,22 +432,27 @@
|
||||
"directions": "Inicie uma verificação de wi-fi deste dispositivo, o que deve levar aproximadamente 25 segundos.",
|
||||
"re_scan": "Verificar novamente",
|
||||
"result_directions": "Clique no botão '$ t (scan.re_scan)' se desejar fazer uma varredura com a mesma configuração da anterior.",
|
||||
"results": "Resultados da verificação de wi-fi",
|
||||
"results": "Resultados da verificação de Wi-Fi",
|
||||
"scan": "Varredura",
|
||||
"scanning": "Scanning... ",
|
||||
"waiting_directions": "Por favor, aguarde o resultado da verificação. Isso pode levar até 25 segundos. Você pode sair e ver os resultados da tabela de comandos mais tarde."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Definições"
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Dados (KB)",
|
||||
"latest_statistics": "Estatísticas mais recentes",
|
||||
"show_latest": "Mostrar estatísticas mais recentes JSON",
|
||||
"lifetime_stats": "Estatísticas de vida",
|
||||
"no_interfaces": "Nenhuma estatística de tempo de vida da interface disponível",
|
||||
"show_latest": "Últimas estatísticas",
|
||||
"title": "Estatisticas"
|
||||
},
|
||||
"status": {
|
||||
"connection_status": "Status da conexão",
|
||||
"connection_status": "Status",
|
||||
"error": "Dados de status indisponíveis",
|
||||
"last_contact": "Último contato",
|
||||
"load_averages": "Carga (1/5/15 minutos em média)",
|
||||
"load_averages": "Carga (1/5/15 m.)",
|
||||
"localtime": "Horário local",
|
||||
"memory": "Memória Usada",
|
||||
"percentage_free": "{{percentage}}% de {{total}} grátis",
|
||||
@@ -198,6 +461,23 @@
|
||||
"uptime": "Tempo de atividade",
|
||||
"used_total_memory": "{{used}} usado / {{total}} total"
|
||||
},
|
||||
"system": {
|
||||
"error_fetching": "Erro ao buscar informações do sistema",
|
||||
"error_reloading": "Erro ao recarregar: {{error}}",
|
||||
"hostname": "Nome de anfitrião",
|
||||
"os": "Sistema Operacional",
|
||||
"processors": "Processadores",
|
||||
"reload": "Recarregar",
|
||||
"reload_subsystems": "Recarregar",
|
||||
"subsystems": "Subsistemas",
|
||||
"success_reload": "Comando de recarregamento enviado com sucesso!"
|
||||
},
|
||||
"telemetry": {
|
||||
"connection_failed": "Falha ao criar conexão. Erro: {{error}}",
|
||||
"interval": "intervalo",
|
||||
"last_update": "Última atualização",
|
||||
"types": "Tipos"
|
||||
},
|
||||
"trace": {
|
||||
"choose_network": "Escolha a rede",
|
||||
"directions": "Lançar um rastreamento remoto deste dispositivo para uma duração específica ou um número de pacotes",
|
||||
@@ -205,6 +485,7 @@
|
||||
"packets": "Pacotes",
|
||||
"title": "Vestígio",
|
||||
"trace": "Vestígio",
|
||||
"trace_not_successful": "O rastreamento não foi bem-sucedido: o gateway relatou o seguinte erro: {{error}}",
|
||||
"wait_for_file": "Você gostaria de esperar até que o arquivo de rastreamento esteja pronto?",
|
||||
"waiting_directions": "Aguarde o arquivo de dados de rastreamento. Isto pode tomar algum tempo. Você pode sair da espera e recuperar o arquivo de rastreamento da tabela de comandos mais tarde.",
|
||||
"waiting_seconds": "Tempo decorrido: {{seconds}} segundos"
|
||||
@@ -224,5 +505,53 @@
|
||||
"upgrade": "Melhorar",
|
||||
"wait_for_upgrade": "Você gostaria de esperar a conclusão da atualização?",
|
||||
"waiting_for_device": "Esperando que o dispositivo se reconecte"
|
||||
},
|
||||
"user": {
|
||||
"avatar": "Seu avatar",
|
||||
"avatar_file": "Seu avatar (máx. De 2 MB)",
|
||||
"create": "Criar usuário",
|
||||
"create_failure": "Erro ao criar usuário. Certifique-se de que este endereço de e-mail ainda não esteja vinculado a uma conta.",
|
||||
"create_success": "Usuário criado com sucesso",
|
||||
"creating": "Criando usuário ...",
|
||||
"delete_avatar": "Apagar Avatar",
|
||||
"delete_failure": "Erro ao tentar excluir usuário: {{error}}",
|
||||
"delete_success": "Usuário excluído com sucesso!",
|
||||
"delete_title": "Deletar usuário",
|
||||
"delete_warning": "Aviso: depois de excluir um usuário, você não pode reverter",
|
||||
"deleting": "Excluindo ...",
|
||||
"description": "Descrição",
|
||||
"edit": "Editar usuário",
|
||||
"email_address": "Endereço de e-mail",
|
||||
"error_fetching_users": "Erro ao buscar usuários: {{error}}",
|
||||
"force_password_change": "Forçar mudança de senha no login",
|
||||
"id": "ID do usuário.",
|
||||
"last_login": "Último login",
|
||||
"login_id": "Identificação de usuário.",
|
||||
"my_profile": "Meu perfil",
|
||||
"name": "Nome",
|
||||
"nickname": "Apelido",
|
||||
"nickname_explanation": "Apelido (opcional)",
|
||||
"not_validated": "Não validado",
|
||||
"note": "Nota",
|
||||
"password": "Senha",
|
||||
"provide_email": "Por favor, forneça um endereço de e-mail válido",
|
||||
"provide_password": "Forneça uma senha válida",
|
||||
"save_avatar": "Salvar Avatar",
|
||||
"show_hide_password": "Mostrar / ocultar senha",
|
||||
"update_failure": "Erro ao tentar atualizar: {{error}}",
|
||||
"update_failure_title": "Atualização falhou",
|
||||
"update_success": "Usuário atualizado com sucesso",
|
||||
"update_success_title": "Sucesso",
|
||||
"user_role": "Função",
|
||||
"users": "Comercial",
|
||||
"validated": "Validado"
|
||||
},
|
||||
"wifi_analysis": {
|
||||
"association": "Associação",
|
||||
"associations": "Associações",
|
||||
"mode": "Modo",
|
||||
"network_diagram": "Diagrama de rede",
|
||||
"radios": "Rádios",
|
||||
"title": "Análise de Wi-Fi"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ import React from 'react';
|
||||
import { HashRouter, Switch } from 'react-router-dom';
|
||||
import 'scss/style.scss';
|
||||
import Router from 'router';
|
||||
import { AuthProvider } from 'contexts/AuthProvider';
|
||||
import { AuthProvider } from 'ucentral-libs';
|
||||
import { checkIfJson } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
|
||||
const loading = (
|
||||
<div className="pt-3 text-center">
|
||||
@@ -18,7 +19,11 @@ const App = () => {
|
||||
: {};
|
||||
|
||||
return (
|
||||
<AuthProvider token={storageToken ?? ''} apiEndpoints={apiEndpoints}>
|
||||
<AuthProvider
|
||||
axiosInstance={axiosInstance}
|
||||
token={storageToken ?? ''}
|
||||
apiEndpoints={apiEndpoints}
|
||||
>
|
||||
<HashRouter>
|
||||
<React.Suspense fallback={loading}>
|
||||
<Switch>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
export const logo = [
|
||||
'608 134',
|
||||
`
|
||||
<title>coreui react pro</title>
|
||||
<g>
|
||||
<g style="fill:#00a1ff">
|
||||
<path d="M362.0177,90.1512,353.25,69.4149a.2507.2507,0,0,0-.2559-.1914H343.01a.2263.2263,0,0,0-.2559.2559V90.0233a.5657.5657,0,0,1-.64.64h-1.2163a.5652.5652,0,0,1-.64-.64V46.5028a.5655.5655,0,0,1,.64-.64H353.442a9.9792,9.9792,0,0,1,7.7437,3.2324A12.2,12.2,0,0,1,364.13,57.64a12.4389,12.4389,0,0,1-2.24,7.584,9.37,9.37,0,0,1-6.08,3.7441c-.1709.086-.2139.1915-.128.3194l8.7041,20.6084.064.2558q0,.5127-.5757.5118h-1.1523A.703.703,0,0,1,362.0177,90.1512ZM342.754,48.3593v18.496a.2259.2259,0,0,0,.2559.2559h10.3037a7.6713,7.6713,0,0,0,6.0166-2.5918,9.8807,9.8807,0,0,0,2.3037-6.8164,10.2875,10.2875,0,0,0-2.272-6.9756,7.6033,7.6033,0,0,0-6.0483-2.624H343.01A.2263.2263,0,0,0,342.754,48.3593Z"/>
|
||||
<path d="M401.3263,48.1034H381.2945a.2262.2262,0,0,0-.2558.2559v18.496a.2259.2259,0,0,0,.2558.2559h13.8238a.5664.5664,0,0,1,.6406.64v.96a.5663.5663,0,0,1-.6406.6406H381.2945a.2263.2263,0,0,0-.2558.2559v18.56a.2258.2258,0,0,0,.2558.2558h20.0318a.5671.5671,0,0,1,.6406.6407v.96a.566.566,0,0,1-.6406.64H379.1827a.5653.5653,0,0,1-.64-.64V46.5028a.5656.5656,0,0,1,.64-.64h22.1436a.5664.5664,0,0,1,.6406.64v.96A.5663.5663,0,0,1,401.3263,48.1034Z"/>
|
||||
<path d="M439.047,90.1512l-2.4317-8.832a.2971.2971,0,0,0-.32-.1924H419.5274a.2957.2957,0,0,0-.32.1924l-2.3681,8.7676a.6577.6577,0,0,1-.7036.5762H414.919a.5385.5385,0,0,1-.5756-.7041l12.0317-43.584a.6436.6436,0,0,1,.7041-.5117h1.6a.6442.6442,0,0,1,.7041.5117l12.16,43.584.0644.1923q0,.5127-.64.5118h-1.2163A.6428.6428,0,0,1,439.047,90.1512ZM419.9435,78.9188a.3031.3031,0,0,0,.2236.0967h15.4883a.3048.3048,0,0,0,.2236-.0967c.0645-.0635.0742-.1162.0322-.1592l-7.872-28.9287c-.043-.0849-.086-.1279-.128-.1279s-.0859.043-.1279.1279L419.9112,78.76C419.8683,78.8026,419.879,78.8553,419.9435,78.9188Z"/>
|
||||
<path d="M456.6017,87.911a11.6372,11.6372,0,0,1-3.3277-8.7041V57.1913a11.4158,11.4158,0,0,1,3.36-8.5762,12.0941,12.0941,0,0,1,8.8-3.2637,12.2566,12.2566,0,0,1,8.8643,3.2315,11.3927,11.3927,0,0,1,3.36,8.6084v.64a.5663.5663,0,0,1-.6406.6407l-1.28.0634q-.6408,0-.64-.5761v-.8321a9.289,9.289,0,0,0-2.6558-6.9121,10.6734,10.6734,0,0,0-14.0161,0,9.2854,9.2854,0,0,0-2.6563,6.9121V79.3993a9.2808,9.2808,0,0,0,2.6563,6.9121,10.67,10.67,0,0,0,14.0161,0,9.2843,9.2843,0,0,0,2.6558-6.9121v-.7686q0-.5757.64-.5752l1.28.0635a.5667.5667,0,0,1,.6406.6406v.5118a11.4952,11.4952,0,0,1-3.36,8.64,13.6227,13.6227,0,0,1-17.6963,0Z"/>
|
||||
<path d="M514.4376,46.5028v.96a.5658.5658,0,0,1-.64.6406H503.046a.2263.2263,0,0,0-.2559.2559v41.664a.566.566,0,0,1-.6406.64h-1.2158a.5652.5652,0,0,1-.64-.64V48.3593a.2266.2266,0,0,0-.2558-.2559H489.8619a.5656.5656,0,0,1-.64-.6406v-.96a.5656.5656,0,0,1,.64-.64H513.798A.5658.5658,0,0,1,514.4376,46.5028Z"/>
|
||||
<path d="M522.0665,89.5116a2.8385,2.8385,0,0,1-.8-2.0488,2.9194,2.9194,0,0,1,.8-2.1114,2.7544,2.7544,0,0,1,2.08-.832,2.8465,2.8465,0,0,1,2.9438,2.9434,2.7541,2.7541,0,0,1-.832,2.08,2.9221,2.9221,0,0,1-2.1118.8008A2.754,2.754,0,0,1,522.0665,89.5116Z"/>
|
||||
<path d="M542.4054,88.0077a11.3123,11.3123,0,0,1-3.2-8.416v-5.44a.5656.5656,0,0,1,.64-.64h1.2158a.5661.5661,0,0,1,.64.64v5.5039a9.1424,9.1424,0,0,0,2.5283,6.72,8.9745,8.9745,0,0,0,6.6875,2.5605,8.7908,8.7908,0,0,0,9.28-9.28V46.5028a.5655.5655,0,0,1,.64-.64h1.2163a.566.566,0,0,1,.64.64V79.5917a11.2545,11.2545,0,0,1-3.2325,8.416,13.0618,13.0618,0,0,1-17.0556,0Z"/>
|
||||
<path d="M580.35,88.1034a10.4859,10.4859,0,0,1-3.36-8.1279v-1.792a.5663.5663,0,0,1,.64-.6407h1.0884a.5668.5668,0,0,1,.64.6407v1.6a8.5459,8.5459,0,0,0,2.752,6.6562,10.5353,10.5353,0,0,0,7.36,2.4961,9.8719,9.8719,0,0,0,6.9761-2.3681,8.2161,8.2161,0,0,0,2.56-6.336,8.4,8.4,0,0,0-1.12-4.416,11.3812,11.3812,0,0,0-3.3281-3.3926,71.6714,71.6714,0,0,0-6.1763-3.7119,71.0479,71.0479,0,0,1-6.24-3.84,12.1711,12.1711,0,0,1-3.4238-3.68,10.2614,10.2614,0,0,1-1.28-5.3438,9.8579,9.8579,0,0,1,3.0718-7.7441,12.0122,12.0122,0,0,1,8.32-2.752q5.6954,0,8.96,3.1036a10.8251,10.8251,0,0,1,3.2642,8.2246v1.6a.5658.5658,0,0,1-.64.64h-1.1519a.5652.5652,0,0,1-.64-.64V56.8075a8.8647,8.8647,0,0,0-2.624-6.6885,9.9933,9.9933,0,0,0-7.232-2.5273,9.37,9.37,0,0,0-6.5278,2.1435,7.8224,7.8224,0,0,0-2.3682,6.1123,7.8006,7.8006,0,0,0,1.0244,4.16,10.387,10.387,0,0,0,3.0078,3.0391,62.8714,62.8714,0,0,0,5.9522,3.4882,71.0575,71.0575,0,0,1,6.72,4.2559,13.4674,13.4674,0,0,1,3.648,3.9365,10.049,10.049,0,0,1,1.28,5.1836,10.7177,10.7177,0,0,1-3.2637,8.1924q-3.2637,3.0717-8.832,3.0723Q583.71,91.1757,580.35,88.1034Z"/>
|
||||
</g>
|
||||
<g style="fill:#3c4b64">
|
||||
<g>
|
||||
<path d="M99.835,36.0577l-39-22.5167a12,12,0,0,0-12,0l-39,22.5166a12.0339,12.0339,0,0,0-6,10.3924V91.4833a12.0333,12.0333,0,0,0,6,10.3923l39,22.5167a12,12,0,0,0,12,0l39-22.5167a12.0331,12.0331,0,0,0,6-10.3923V46.45A12.0334,12.0334,0,0,0,99.835,36.0577Zm-2,55.4256a4,4,0,0,1-2,3.4641l-39,22.5167a4.0006,4.0006,0,0,1-4,0l-39-22.5167a4,4,0,0,1-2-3.4641V46.45a4,4,0,0,1,2-3.4642l39-22.5166a4,4,0,0,1,4,0l39,22.5166a4,4,0,0,1,2,3.4642Z"/>
|
||||
<path d="M77.8567,82.0046h-2.866a4,4,0,0,0-1.9247.4934L55.7852,91.9833,35.835,80.4648V57.4872l19.95-11.5185,17.2893,9.4549a3.9993,3.9993,0,0,0,1.9192.4906h2.8632a2,2,0,0,0,2-2V51.2024a2,2,0,0,0-1.04-1.7547L59.628,38.9521a8.0391,8.0391,0,0,0-7.8428.09L31.8346,50.56a8.0246,8.0246,0,0,0-4,6.9287v22.976a8,8,0,0,0,4,6.9283l19.95,11.5186a8.0429,8.0429,0,0,0,7.8433.0879l19.19-10.5312a2,2,0,0,0,1.0378-1.7533v-2.71A2,2,0,0,0,77.8567,82.0046Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M172.58,45.3618a15.0166,15.0166,0,0,0-15,14.9995V77.6387a15,15,0,0,0,30,0V60.3613A15.0166,15.0166,0,0,0,172.58,45.3618Zm7,32.2769a7,7,0,0,1-14,0V60.3613a7,7,0,0,1,14,0Z"/>
|
||||
<path d="M135.9138,53.4211a7.01,7.01,0,0,1,7.8681,6.0752.9894.9894,0,0,0,.9843.865h6.03a1.0108,1.0108,0,0,0,.9987-1.0971,15.0182,15.0182,0,0,0-15.7162-13.8837,15.2881,15.2881,0,0,0-14.2441,15.4163V77.2037A15.288,15.288,0,0,0,136.0792,92.62a15.0183,15.0183,0,0,0,15.7162-13.8842,1.0107,1.0107,0,0,0-.9987-1.0971h-6.03a.9894.9894,0,0,0-.9843.865,7.01,7.01,0,0,1-7.8679,6.0757,7.1642,7.1642,0,0,1-6.0789-7.1849V60.6057A7.1638,7.1638,0,0,1,135.9138,53.4211Z"/>
|
||||
<path d="M218.7572,72.9277a12.1585,12.1585,0,0,0,7.1843-11.0771V58.1494A12.1494,12.1494,0,0,0,213.7921,46H196.835a1,1,0,0,0-1,1V91a1,1,0,0,0,1,1h6a1,1,0,0,0,1-1V74h6.6216l7.9154,17.4138a1,1,0,0,0,.91.5862h6.5911a1,1,0,0,0,.91-1.4138Zm-.8157-11.0771A4.1538,4.1538,0,0,1,213.7926,66h-9.8511V54h9.8511a4.1538,4.1538,0,0,1,4.1489,4.1494Z"/>
|
||||
<path d="M260.835,46h-26a1,1,0,0,0-1,1V91a1,1,0,0,0,1,1h26a1,1,0,0,0,1-1V85a1,1,0,0,0-1-1h-19V72h13a1,1,0,0,0,1-1V65a1,1,0,0,0-1-1h-13V54h19a1,1,0,0,0,1-1V47A1,1,0,0,0,260.835,46Z"/>
|
||||
<path d="M298.835,46h-6a1,1,0,0,0-1,1V69.6475a7.0066,7.0066,0,1,1-14,0V47a1,1,0,0,0-1-1h-6a1,1,0,0,0-1,1V69.6475a15.0031,15.0031,0,1,0,30,0V47A1,1,0,0,0,298.835,46Z"/>
|
||||
<rect x="307.835" y="46" width="8" height="38" rx="1"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
`,
|
||||
];
|
||||
@@ -1,28 +1,4 @@
|
||||
import {
|
||||
cibSkype,
|
||||
cibFacebook,
|
||||
cibTwitter,
|
||||
cibLinkedin,
|
||||
cibFlickr,
|
||||
cibTumblr,
|
||||
cibXing,
|
||||
cibGithub,
|
||||
cibStackoverflow,
|
||||
cibYoutube,
|
||||
cibDribbble,
|
||||
cibInstagram,
|
||||
cibPinterest,
|
||||
cibVk,
|
||||
cibYahoo,
|
||||
cibBehance,
|
||||
cibReddit,
|
||||
cibVimeo,
|
||||
cibCcMastercard,
|
||||
cibCcVisa,
|
||||
cibStripe,
|
||||
cibPaypal,
|
||||
cibGooglePay,
|
||||
cibCcAmex,
|
||||
cifUs,
|
||||
cifBr,
|
||||
cifIn,
|
||||
@@ -123,10 +99,7 @@ import {
|
||||
cilWarning,
|
||||
} from '@coreui/icons';
|
||||
|
||||
import { logo } from './CoreuiLogo';
|
||||
|
||||
export const icons = {
|
||||
logo,
|
||||
cilAlignCenter,
|
||||
cilAlignLeft,
|
||||
cilAlignRight,
|
||||
@@ -225,28 +198,4 @@ export const icons = {
|
||||
cifFr,
|
||||
cifEs,
|
||||
cifPl,
|
||||
cibSkype,
|
||||
cibFacebook,
|
||||
cibTwitter,
|
||||
cibLinkedin,
|
||||
cibFlickr,
|
||||
cibTumblr,
|
||||
cibXing,
|
||||
cibGithub,
|
||||
cibStackoverflow,
|
||||
cibYoutube,
|
||||
cibDribbble,
|
||||
cibInstagram,
|
||||
cibPinterest,
|
||||
cibVk,
|
||||
cibYahoo,
|
||||
cibBehance,
|
||||
cibReddit,
|
||||
cibVimeo,
|
||||
cibCcMastercard,
|
||||
cibCcVisa,
|
||||
cibStripe,
|
||||
cibPaypal,
|
||||
cibGooglePay,
|
||||
cibCcAmex,
|
||||
};
|
||||
|
||||
@@ -11,21 +11,20 @@ import {
|
||||
CFormGroup,
|
||||
CInputRadio,
|
||||
CLabel,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import PropTypes from 'prop-types';
|
||||
import { dateToUnix } from 'utils/helper';
|
||||
import 'react-widgets/styles.css';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
|
||||
const BlinkModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -74,7 +73,7 @@ const BlinkModal = ({ show, toggleModal }) => {
|
||||
|
||||
axiosInstance
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/leds`,
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/leds`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
@@ -92,8 +91,15 @@ const BlinkModal = ({ show, toggleModal }) => {
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggleModal}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('blink.device_leds')}</CModalTitle>
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="pl-1 pt-1">{t('blink.device_leds')}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
{result === 'success' ? (
|
||||
<SuccessfulActionModalBody toggleModal={toggleModal} />
|
||||
@@ -143,9 +149,9 @@ const BlinkModal = ({ show, toggleModal }) => {
|
||||
</CFormGroup>
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="pt-1">
|
||||
<CCol md="8">
|
||||
<p className={styles.spacedText}>{t('blink.execute_now')}</p>
|
||||
<p>{t('blink.execute_now')}</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CSwitch
|
||||
@@ -158,8 +164,8 @@ const BlinkModal = ({ show, toggleModal }) => {
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow hidden={isNow} className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedDate}>
|
||||
<CRow hidden={isNow} className="pt-3">
|
||||
<CCol md="4" className="pt-2">
|
||||
<p>{t('common.custom_date')}</p>
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedDate {
|
||||
margin-top: 7px;
|
||||
}
|
||||
33
src/components/CommandHistory/DetailsModal.js
Normal file
33
src/components/CommandHistory/DetailsModal.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
|
||||
const DetailsModal = ({ t, show, toggle, details, commandUuid }) => (
|
||||
<CModal size="lg" show={show} onClose={toggle}>
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="text-dark">{commandUuid}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<pre className="ignore">{JSON.stringify(details, null, 4)}</pre>
|
||||
</CModalBody>
|
||||
</CModal>
|
||||
);
|
||||
|
||||
DetailsModal.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
details: PropTypes.instanceOf(Object).isRequired,
|
||||
commandUuid: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DetailsModal;
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CCollapse, CCardBody } from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Translation } from 'react-i18next';
|
||||
|
||||
const DeviceCommandsCollapse = ({ details, responses, index, item, getDetails, getResponse }) => (
|
||||
<Translation>
|
||||
{(t) => (
|
||||
<div>
|
||||
<CCollapse show={details.includes(index)}>
|
||||
<CCardBody>
|
||||
<h5>{t('common.result')}</h5>
|
||||
<div>{getDetails(item, index)}</div>
|
||||
</CCardBody>
|
||||
</CCollapse>
|
||||
<CCollapse show={responses.includes(index)}>
|
||||
<CCardBody>
|
||||
<h5>{t('common.details')}</h5>
|
||||
<div>{getResponse(item, index)}</div>
|
||||
</CCardBody>
|
||||
</CCollapse>
|
||||
</div>
|
||||
)}
|
||||
</Translation>
|
||||
);
|
||||
|
||||
DeviceCommandsCollapse.propTypes = {
|
||||
details: PropTypes.instanceOf(Array).isRequired,
|
||||
responses: PropTypes.instanceOf(Array).isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
getDetails: PropTypes.func.isRequired,
|
||||
getResponse: PropTypes.func.isRequired,
|
||||
item: PropTypes.instanceOf(Object).isRequired,
|
||||
};
|
||||
|
||||
export default DeviceCommandsCollapse;
|
||||
@@ -5,25 +5,22 @@ import {
|
||||
CWidgetDropdown,
|
||||
CRow,
|
||||
CCol,
|
||||
CCollapse,
|
||||
CButton,
|
||||
CDataTable,
|
||||
CCard,
|
||||
CPopover,
|
||||
CButtonToolbar,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import { cilCloudDownload, cilSync, cilCalendarCheck } from '@coreui/icons';
|
||||
import { prettyDate, dateToUnix } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import ConfirmModal from 'components/ConfirmModal';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
import WifiScanResultModalWidget from 'components/WifiScanResultModal';
|
||||
import DeviceCommandsCollapse from './DeviceCommandsCollapse';
|
||||
import styles from './index.module.scss';
|
||||
import DetailsModal from './DetailsModal';
|
||||
|
||||
const DeviceCommands = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -36,11 +33,10 @@ const DeviceCommands = () => {
|
||||
// Delete modal related
|
||||
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||
const [uuidDelete, setUuidDelete] = useState('');
|
||||
// Main collapsible
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
// Two other open collapsible lists
|
||||
const [details, setDetails] = useState([]);
|
||||
const [responses, setResponses] = useState([]);
|
||||
// Details modal related
|
||||
const [showDetailsModal, setShowDetailsModal] = useState(false);
|
||||
const [detailsUuid, setDetailsUuid] = useState('');
|
||||
const [modalDetails, setModalDetails] = useState({});
|
||||
// General states
|
||||
const [commands, setCommands] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -51,11 +47,6 @@ const DeviceCommands = () => {
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
const [showLoadingMore, setShowLoadingMore] = useState(true);
|
||||
|
||||
const toggle = (e) => {
|
||||
setCollapse(!collapse);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const toggleScanModal = () => {
|
||||
setShowScanModal(!showScanModal);
|
||||
};
|
||||
@@ -65,6 +56,10 @@ const DeviceCommands = () => {
|
||||
setShowConfirmModal(!showConfirmModal);
|
||||
};
|
||||
|
||||
const toggleDetailsModal = () => {
|
||||
setShowDetailsModal(!showDetailsModal);
|
||||
};
|
||||
|
||||
const showMoreCommands = () => {
|
||||
setCommandLimit(commandLimit + 50);
|
||||
};
|
||||
@@ -110,7 +105,7 @@ const DeviceCommands = () => {
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/commands?serialNumber=${encodeURIComponent(
|
||||
`${endpoints.owgw}/api/v1/commands?serialNumber=${encodeURIComponent(
|
||||
deviceSerialNumber,
|
||||
)}${extraParams}`,
|
||||
options,
|
||||
@@ -135,10 +130,7 @@ const DeviceCommands = () => {
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/file/${uuid}?serialNumber=${deviceSerialNumber}`,
|
||||
options,
|
||||
)
|
||||
.get(`${endpoints.owgw}/api/v1/file/${uuid}?serialNumber=${deviceSerialNumber}`, options)
|
||||
.then((response) => {
|
||||
const blob = new Blob([response.data], { type: 'application/octet-stream' });
|
||||
const link = document.createElement('a');
|
||||
@@ -159,7 +151,7 @@ const DeviceCommands = () => {
|
||||
},
|
||||
};
|
||||
return axiosInstance
|
||||
.delete(`${endpoints.ucentralgw}/api/v1/command/${uuidDelete}`, options)
|
||||
.delete(`${endpoints.owgw}/api/v1/command/${uuidDelete}`, options)
|
||||
.then(() => {
|
||||
deleteCommandFromList(uuidDelete);
|
||||
setUuidDelete('');
|
||||
@@ -171,7 +163,7 @@ const DeviceCommands = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const toggleDetails = (item, index) => {
|
||||
const toggleDetails = (item) => {
|
||||
if (item.command === 'wifiscan') {
|
||||
setChosenWifiScan(item.results.status.scan);
|
||||
setChosenWifiScanDate(item.completed);
|
||||
@@ -179,64 +171,32 @@ const DeviceCommands = () => {
|
||||
} else if (item.command === 'trace' && item.waitingForFile === 0) {
|
||||
downloadTrace(item.UUID);
|
||||
} else {
|
||||
const position = details.indexOf(index);
|
||||
let newDetails = details.slice();
|
||||
|
||||
if (position !== -1) {
|
||||
newDetails.splice(position, 1);
|
||||
} else {
|
||||
newDetails = [...details, index];
|
||||
}
|
||||
setDetails(newDetails);
|
||||
setModalDetails(item.results ?? item);
|
||||
setDetailsUuid(item.UUID);
|
||||
toggleDetailsModal();
|
||||
}
|
||||
};
|
||||
|
||||
const toggleResponse = (item, index) => {
|
||||
const position = responses.indexOf(index);
|
||||
let newResponses = responses.slice();
|
||||
|
||||
if (position !== -1) {
|
||||
newResponses.splice(position, 1);
|
||||
} else {
|
||||
newResponses = [...newResponses, index];
|
||||
}
|
||||
setResponses(newResponses);
|
||||
const toggleResponse = (item) => {
|
||||
setModalDetails(item);
|
||||
setDetailsUuid(item.UUID);
|
||||
toggleDetailsModal();
|
||||
};
|
||||
|
||||
const refreshCommands = () => {
|
||||
getCommands();
|
||||
};
|
||||
|
||||
const getDetails = (command, index) => {
|
||||
if (!details.includes(index)) {
|
||||
return <pre className="ignore" />;
|
||||
}
|
||||
if (command.results) {
|
||||
const result = command.results;
|
||||
if (result) return <pre className="ignore">{JSON.stringify(result, null, 4)}</pre>;
|
||||
}
|
||||
return <pre className="ignore">{JSON.stringify(command, null, 4)}</pre>;
|
||||
};
|
||||
|
||||
const getResponse = (commandDetails, index) => {
|
||||
if (!responses.includes(index)) {
|
||||
return <pre className="ignore" />;
|
||||
}
|
||||
return <pre className="ignore">{JSON.stringify(commandDetails, null, 4)}</pre>;
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ key: 'UUID', label: t('common.id'), _style: { width: '28%' } },
|
||||
{ key: 'command', label: t('common.command'), _style: { width: '10%' } },
|
||||
{ key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } },
|
||||
{ key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '16%' } },
|
||||
{ key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } },
|
||||
{ key: 'command', label: t('common.command'), _style: { width: '15%' } },
|
||||
{ key: 'completed', label: t('common.completed'), filter: false, _style: { width: '20%' } },
|
||||
{ key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } },
|
||||
{
|
||||
key: 'show_buttons',
|
||||
label: '',
|
||||
sorter: false,
|
||||
filter: false,
|
||||
_style: { width: '14%' },
|
||||
_style: { width: '1%' },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -282,29 +242,14 @@ const DeviceCommands = () => {
|
||||
}, [commands]);
|
||||
|
||||
return (
|
||||
<CWidgetDropdown
|
||||
inverse="true"
|
||||
color="gradient-primary"
|
||||
header={t('commands.title')}
|
||||
footerSlot={
|
||||
<div className={styles.footer}>
|
||||
<CCollapse show={collapse}>
|
||||
<CRow>
|
||||
<CCol />
|
||||
<CCol>
|
||||
<div className={styles.alignRight}>
|
||||
<CButton onClick={refreshCommands} size="sm">
|
||||
<CIcon
|
||||
name="cil-sync"
|
||||
content={cilSync}
|
||||
className={styles.whiteIcon}
|
||||
size="2xl"
|
||||
/>
|
||||
</CButton>
|
||||
</div>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.datepickerRow}>
|
||||
<div>
|
||||
<CWidgetDropdown
|
||||
inverse="true"
|
||||
color="gradient-primary"
|
||||
header={t('commands.title')}
|
||||
footerSlot={
|
||||
<div className="pb-1 px-3">
|
||||
<CRow className="mb-2">
|
||||
<CCol>
|
||||
From:
|
||||
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
|
||||
@@ -315,12 +260,13 @@ const DeviceCommands = () => {
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CCard>
|
||||
<div className={['overflow-auto', styles.scrollableBox].join(' ')}>
|
||||
<div className="overflow-auto" style={{ height: '200px' }}>
|
||||
<CDataTable
|
||||
border
|
||||
loading={loading}
|
||||
items={commands ?? []}
|
||||
fields={columns}
|
||||
className={styles.whiteIcon}
|
||||
className="text-white"
|
||||
sorterValue={{ column: 'created', desc: 'true' }}
|
||||
scopedSlots={{
|
||||
completed: (item) => (
|
||||
@@ -337,112 +283,100 @@ const DeviceCommands = () => {
|
||||
: 'Pending'}
|
||||
</td>
|
||||
),
|
||||
executed: (item) => (
|
||||
<td>
|
||||
{item.executed && item.executed !== ''
|
||||
? prettyDate(item.executed)
|
||||
: 'Pending'}
|
||||
</td>
|
||||
),
|
||||
show_buttons: (item, index) => (
|
||||
<td>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<CPopover
|
||||
content={
|
||||
item.command === 'trace' ? t('common.download') : t('common.result')
|
||||
}
|
||||
<CButtonToolbar
|
||||
role="group"
|
||||
className="justify-content-flex-end"
|
||||
style={{ width: '170px' }}
|
||||
>
|
||||
<CPopover
|
||||
content={
|
||||
item.command === 'trace' ? t('common.download') : t('common.result')
|
||||
}
|
||||
>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
className="mx-2"
|
||||
onClick={() => {
|
||||
toggleDetails(item);
|
||||
}}
|
||||
>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant={details.includes(index) ? '' : 'outline'}
|
||||
disabled={
|
||||
item.completed === 0 ||
|
||||
(item.command === 'trace' && item.waitingForFile !== 0)
|
||||
}
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleDetails(item, index);
|
||||
}}
|
||||
>
|
||||
{item.command === 'trace' ? (
|
||||
<CIcon content={cilCloudDownload} size="lg" />
|
||||
) : (
|
||||
<CIcon content={cilCalendarCheck} size="lg" />
|
||||
)}
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CPopover content={t('common.details')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant={responses.includes(index) ? '' : 'outline'}
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleResponse(item, index);
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilList" size="lg" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CPopover content={t('common.delete')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleConfirmModal(item.UUID, index);
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilTrash" size="lg" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</CCol>
|
||||
</CRow>
|
||||
{item.command === 'trace' ? (
|
||||
<CIcon
|
||||
name="cil-cloud-download"
|
||||
content={cilCloudDownload}
|
||||
size="lg"
|
||||
/>
|
||||
) : (
|
||||
<CIcon
|
||||
name="cil-calendar-check"
|
||||
content={cilCalendarCheck}
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
</CButton>
|
||||
</CPopover>
|
||||
<CPopover content={t('common.details')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
className="mx-2"
|
||||
onClick={() => {
|
||||
toggleResponse(item);
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilList" size="lg" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
<CPopover content={t('common.delete')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
className="mx-2"
|
||||
onClick={() => {
|
||||
toggleConfirmModal(item.UUID, index);
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilTrash" size="lg" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</CButtonToolbar>
|
||||
</td>
|
||||
),
|
||||
details: (item, index) => (
|
||||
<DeviceCommandsCollapse
|
||||
details={details}
|
||||
responses={responses}
|
||||
index={index}
|
||||
getDetails={getDetails}
|
||||
getResponse={getResponse}
|
||||
item={item}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<CRow className={styles.loadMoreSpacing}>
|
||||
{showLoadingMore && (
|
||||
|
||||
{showLoadingMore && (
|
||||
<div className="mb-3">
|
||||
<LoadingButton
|
||||
label="View More"
|
||||
isLoadingLabel="Loading More..."
|
||||
label={t('common.view_more')}
|
||||
isLoadingLabel={t('common.loading_more_ellipsis')}
|
||||
isLoading={loadingMore}
|
||||
action={showMoreCommands}
|
||||
variant="outline"
|
||||
/>
|
||||
)}
|
||||
</CRow>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CCard>
|
||||
</CCollapse>
|
||||
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
|
||||
<CIcon
|
||||
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
||||
className={styles.whiteIcon}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="text-right float-right">
|
||||
<CButton onClick={refreshCommands} size="sm">
|
||||
<CIcon name="cil-sync" content={cilSync} className="text-white" size="2xl" />
|
||||
</CButton>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
</CWidgetDropdown>
|
||||
|
||||
<WifiScanResultModalWidget
|
||||
show={showScanModal}
|
||||
toggle={toggleScanModal}
|
||||
@@ -450,7 +384,14 @@ const DeviceCommands = () => {
|
||||
date={chosenWifiScanDate}
|
||||
/>
|
||||
<ConfirmModal show={showConfirmModal} toggle={toggleConfirmModal} action={deleteCommand} />
|
||||
</CWidgetDropdown>
|
||||
<DetailsModal
|
||||
t={t}
|
||||
show={showDetailsModal}
|
||||
toggle={toggleDetailsModal}
|
||||
details={modalDetails}
|
||||
commandUuid={detailsUuid}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
.footer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.datepickerRow {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.scrollableBox {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.whiteIcon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.alignRight {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.customIconHeight {
|
||||
height: 19px;
|
||||
}
|
||||
|
||||
.loadMoreSpacing {
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
@@ -12,18 +12,19 @@ import {
|
||||
CTextarea,
|
||||
CInvalidFeedback,
|
||||
CInputFile,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import 'react-widgets/styles.css';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import { useAuth, useDevice } from 'ucentral-libs';
|
||||
import { checkIfJson } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const ConfigureModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -85,7 +86,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
|
||||
axiosInstance
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/configure`,
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/configure`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
@@ -126,9 +127,16 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggleModal}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('configure.title')}</CModalTitle>
|
||||
<CModal show={show} onClose={toggleModal} size="lg">
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="pl-1 pt-1">{t('configure.title')}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
{hadSuccess ? (
|
||||
<SuccessfulActionModalBody toggleModal={toggleModal} />
|
||||
@@ -136,7 +144,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
<div>
|
||||
<CModalBody>
|
||||
<CRow>
|
||||
<CCol md="10" className={styles.spacedColumn}>
|
||||
<CCol md="10" className="mt-1">
|
||||
<h6>{t('configure.enter_new')}</h6>
|
||||
</CCol>
|
||||
<CCol>
|
||||
@@ -151,7 +159,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-4">
|
||||
<CCol>
|
||||
<CForm>
|
||||
<CTextarea
|
||||
@@ -169,7 +177,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
</CForm>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-4">
|
||||
<CCol>{t('configure.choose_file')}</CCol>
|
||||
<CCol>
|
||||
<CInputFile
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedColumn {
|
||||
margin-top: 3px;
|
||||
}
|
||||
@@ -9,9 +9,11 @@ import {
|
||||
CModalFooter,
|
||||
CSpinner,
|
||||
CBadge,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const ConfirmModal = ({ show, toggle, action }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -63,9 +65,16 @@ const ConfirmModal = ({ show, toggle, action }) => {
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<CModal className={styles.modal} show={show} onClose={toggle}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('delete_command.title')}</CModalTitle>
|
||||
<CModal className="text-dark" show={show} onClose={toggle}>
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="pl-1 pt-1">{t('delete_command.title')}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<h6>{t('delete_command.explanation')}</h6>
|
||||
@@ -74,9 +83,6 @@ const ConfirmModal = ({ show, toggle, action }) => {
|
||||
<CButton disabled={loading} color="primary" onClick={() => doAction()}>
|
||||
{getButtonContent()}
|
||||
</CButton>
|
||||
<CButton color="secondary" onClick={toggle}>
|
||||
{t('common.cancel')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
);
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.modal {
|
||||
color: #3c4b64;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilClone } from '@coreui/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CButton, CPopover } from '@coreui/react';
|
||||
|
||||
const CopyToClipboardButton = ({ content, size }) => {
|
||||
const { t } = useTranslation();
|
||||
const [result, setResult] = useState('');
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(content);
|
||||
setResult(t('common.copied'));
|
||||
};
|
||||
|
||||
return (
|
||||
<CPopover content={t('common.copy_to_clipboard')}>
|
||||
<CButton onClick={copyToClipboard} size={size}>
|
||||
<CIcon content={cilClone} />
|
||||
{' '}
|
||||
{result || ''}
|
||||
</CButton>
|
||||
</CPopover>
|
||||
);
|
||||
};
|
||||
|
||||
CopyToClipboardButton.propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
size: PropTypes.string,
|
||||
};
|
||||
|
||||
CopyToClipboardButton.defaultProps = {
|
||||
size: 'sm',
|
||||
};
|
||||
|
||||
export default CopyToClipboardButton;
|
||||
182
src/components/CreateUserModal/index.js
Normal file
182
src/components/CreateUserModal/index.js
Normal file
@@ -0,0 +1,182 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CModal, CModalHeader, CModalBody, CModalTitle, CPopover, CButton } from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilSave, cilX } from '@coreui/icons';
|
||||
import { CreateUserForm, useFormFields, useAuth, useToast } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { testRegex, validateEmail } from 'utils/helper';
|
||||
|
||||
const initialState = {
|
||||
name: {
|
||||
value: '',
|
||||
error: false,
|
||||
optional: true,
|
||||
},
|
||||
email: {
|
||||
value: '',
|
||||
error: false,
|
||||
},
|
||||
currentPassword: {
|
||||
value: '',
|
||||
error: false,
|
||||
},
|
||||
changePassword: {
|
||||
value: 'on',
|
||||
error: false,
|
||||
},
|
||||
userRole: {
|
||||
value: 'admin',
|
||||
error: false,
|
||||
},
|
||||
notes: {
|
||||
value: '',
|
||||
error: false,
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
value: '',
|
||||
error: false,
|
||||
optional: true,
|
||||
},
|
||||
};
|
||||
|
||||
const CreateUserModal = ({ show, toggle, getUsers }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { addToast } = useToast();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [policies, setPolicies] = useState({
|
||||
passwordPolicy: '',
|
||||
passwordPattern: '',
|
||||
accessPolicy: '',
|
||||
});
|
||||
const [formFields, updateFieldWithId, updateField, setFormFields] = useFormFields(initialState);
|
||||
|
||||
const toggleChange = () => {
|
||||
updateField('changePassword', { value: !formFields.changePassword.value });
|
||||
};
|
||||
|
||||
const createUser = () => {
|
||||
setLoading(true);
|
||||
|
||||
const parameters = {
|
||||
id: 0,
|
||||
};
|
||||
|
||||
let validationSuccess = true;
|
||||
|
||||
for (const [key, value] of Object.entries(formFields)) {
|
||||
if (!value.optional && value.value === '') {
|
||||
validationSuccess = false;
|
||||
updateField(key, { value: value.value, error: true });
|
||||
} else if (key === 'currentPassword' && !testRegex(value.value, policies.passwordPattern)) {
|
||||
validationSuccess = false;
|
||||
updateField(key, { value: value.value, error: true });
|
||||
} else if (key === 'email' && !validateEmail(value.value)) {
|
||||
validationSuccess = false;
|
||||
updateField(key, { value: value.value, error: true });
|
||||
} else if (key === 'notes') {
|
||||
parameters[key] = [{ note: value.value }];
|
||||
} else if (key === 'changePassword') {
|
||||
parameters[key] = value.value === 'on';
|
||||
} else {
|
||||
parameters[key] = value.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (validationSuccess) {
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(`${endpoints.owsec}/api/v1/user/0`, parameters, {
|
||||
headers,
|
||||
})
|
||||
.then(() => {
|
||||
getUsers();
|
||||
setFormFields(initialState);
|
||||
addToast({
|
||||
title: t('common.success'),
|
||||
body: t('user.create_success'),
|
||||
color: 'success',
|
||||
autohide: true,
|
||||
});
|
||||
toggle();
|
||||
})
|
||||
.catch(() => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('user.create_failure'),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getPasswordPolicy = () => {
|
||||
axiosInstance
|
||||
.post(`${endpoints.owsec}/api/v1/oauth2?requirements=true`, {})
|
||||
.then((response) => {
|
||||
const newPolicies = response.data;
|
||||
newPolicies.accessPolicy = `${endpoints.owsec}${newPolicies.accessPolicy}`;
|
||||
newPolicies.passwordPolicy = `${endpoints.owsec}${newPolicies.passwordPolicy}`;
|
||||
setPolicies(response.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (policies.passwordPattern.length === 0) getPasswordPolicy();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setFormFields(initialState);
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggle} size="xl">
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="pl-1 pt-1">{t('user.create')}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('user.create')}>
|
||||
<CButton color="primary" variant="outline" onClick={createUser} disabled={loading}>
|
||||
<CIcon content={cilSave} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<CreateUserForm
|
||||
t={t}
|
||||
fields={formFields}
|
||||
updateField={updateFieldWithId}
|
||||
policies={policies}
|
||||
toggleChange={toggleChange}
|
||||
/>
|
||||
</CModalBody>
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
CreateUserModal.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
getUsers: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(CreateUserModal);
|
||||
@@ -1,19 +1,28 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CModal, CModalHeader, CModalTitle, CModalBody, CCol, CRow } from '@coreui/react';
|
||||
import {
|
||||
CModal,
|
||||
CModalHeader,
|
||||
CModalTitle,
|
||||
CModalBody,
|
||||
CCol,
|
||||
CRow,
|
||||
CPopover,
|
||||
CButton,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import PropTypes from 'prop-types';
|
||||
import ConfirmFooter from 'components/ConfirmFooter';
|
||||
import { ConfirmFooter, useAuth, useDevice, useToast } from 'ucentral-libs';
|
||||
import { dateToUnix } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeleteLogModal = ({ show, toggle, object }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { addToast } = useToast();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [maxDate, setMaxDate] = useState(new Date().toString());
|
||||
@@ -37,9 +46,16 @@ const DeleteLogModal = ({ show, toggle, object }) => {
|
||||
},
|
||||
};
|
||||
return axiosInstance
|
||||
.delete(`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/${object}`, options)
|
||||
.delete(`${endpoints.owgw}/api/v1/device/${deviceSerialNumber}/${object}`, options)
|
||||
.then(() => {})
|
||||
.catch(() => {})
|
||||
.catch((e) => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('commands.error_delete_log', { error: e.response?.data?.ErrorDescription }),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
if (object === 'healthchecks')
|
||||
eventBus.dispatch('deletedHealth', { message: 'Healthcheck was deleted' });
|
||||
@@ -56,18 +72,25 @@ const DeleteLogModal = ({ show, toggle, object }) => {
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<CModal className={styles.modal} show={show} onClose={toggle}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>
|
||||
<CModal className="text-dark" show={show} onClose={toggle}>
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="pl-1 pt-1">
|
||||
{object === 'healthchecks'
|
||||
? t('delete_logs.healthchecks_title')
|
||||
: t('delete_logs.device_logs_title')}
|
||||
</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<h6>{t('delete_logs.explanation', { object })}</h6>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedDate}>
|
||||
<CRow className="pt-3">
|
||||
<CCol md="4" className="mt-2">
|
||||
<p>{t('common.date')}:</p>
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
@@ -83,6 +106,7 @@ const DeleteLogModal = ({ show, toggle, object }) => {
|
||||
</CRow>
|
||||
</CModalBody>
|
||||
<ConfirmFooter
|
||||
t={t}
|
||||
isShown={show}
|
||||
isLoading={loading}
|
||||
action={deleteLog}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
.modal {
|
||||
color: #3c4b64;
|
||||
}
|
||||
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedColumn {
|
||||
margin-top: 7px;
|
||||
}
|
||||
@@ -1,24 +1,27 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CButton, CCard, CCardHeader, CCardBody, CRow, CCol } from '@coreui/react';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs';
|
||||
import RebootModal from 'components/RebootModal';
|
||||
import FirmwareUpgradeModal from 'components/FirmwareUpgradeModal';
|
||||
import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
|
||||
import ConfigureModal from 'components/ConfigureModal';
|
||||
import TraceModal from 'components/TraceModal';
|
||||
import WifiScanModal from 'components/WifiScanModal';
|
||||
import BlinkModal from 'components/BlinkModal';
|
||||
import FactoryResetModal from 'components/FactoryResetModal';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
import EventQueueModal from 'components/EventQueueModal';
|
||||
import TelemetryModal from 'components/TelemetryModal';
|
||||
|
||||
const DeviceActions = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { addToast } = useToast();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [upgradeStatus, setUpgradeStatus] = useState({
|
||||
loading: false,
|
||||
});
|
||||
const [device, setDevice] = useState({});
|
||||
const [showRebootModal, setShowRebootModal] = useState(false);
|
||||
const [showBlinkModal, setShowBlinkModal] = useState(false);
|
||||
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
|
||||
@@ -27,34 +30,26 @@ const DeviceActions = () => {
|
||||
const [connectLoading, setConnectLoading] = useState(false);
|
||||
const [showConfigModal, setConfigModal] = useState(false);
|
||||
const [showFactoryModal, setShowFactoryModal] = useState(false);
|
||||
const [showQueueModal, setShowQueueModal] = useState(false);
|
||||
const [showTelemetryModal, setShowTelemetryModal] = useState(false);
|
||||
|
||||
const toggleRebootModal = () => {
|
||||
setShowRebootModal(!showRebootModal);
|
||||
};
|
||||
const toggleRebootModal = () => setShowRebootModal(!showRebootModal);
|
||||
|
||||
const toggleBlinkModal = () => {
|
||||
setShowBlinkModal(!showBlinkModal);
|
||||
};
|
||||
const toggleBlinkModal = () => setShowBlinkModal(!showBlinkModal);
|
||||
|
||||
const toggleUpgradeModal = () => {
|
||||
setShowUpgradeModal(!showUpgradeModal);
|
||||
};
|
||||
const toggleUpgradeModal = () => setShowUpgradeModal(!showUpgradeModal);
|
||||
|
||||
const toggleTraceModal = () => {
|
||||
setShowTraceModal(!showTraceModal);
|
||||
};
|
||||
const toggleTraceModal = () => setShowTraceModal(!showTraceModal);
|
||||
|
||||
const toggleScanModal = () => {
|
||||
setShowScanModal(!showScanModal);
|
||||
};
|
||||
const toggleScanModal = () => setShowScanModal(!showScanModal);
|
||||
|
||||
const toggleConfigModal = () => {
|
||||
setConfigModal(!showConfigModal);
|
||||
};
|
||||
const toggleConfigModal = () => setConfigModal(!showConfigModal);
|
||||
|
||||
const toggleFactoryResetModal = () => {
|
||||
setShowFactoryModal(!showFactoryModal);
|
||||
};
|
||||
const toggleFactoryResetModal = () => setShowFactoryModal(!showFactoryModal);
|
||||
|
||||
const toggleQueueModal = () => setShowQueueModal(!showQueueModal);
|
||||
|
||||
const toggleTelemetryModal = () => setShowTelemetryModal(!showTelemetryModal);
|
||||
|
||||
const getRttysInfo = () => {
|
||||
setConnectLoading(true);
|
||||
@@ -67,7 +62,7 @@ const DeviceActions = () => {
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/rtty`,
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/rtty`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
@@ -75,12 +70,56 @@ const DeviceActions = () => {
|
||||
const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
|
||||
if (newWindow) newWindow.opener = null;
|
||||
})
|
||||
.catch(() => {})
|
||||
.catch((e) => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('connect.error_trying_to_connect', { error: e.response?.data?.ErrorDescription }),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setConnectLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const getDeviceInformation = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.owgw}/api/v1/device/${deviceSerialNumber}`, options)
|
||||
.then((response) => {
|
||||
setDevice(response.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (upgradeStatus.result !== undefined) {
|
||||
addToast({
|
||||
title: upgradeStatus.result.success ? t('common.success') : t('common.error'),
|
||||
body: upgradeStatus.result.success
|
||||
? t('firmware.upgrade_command_submitted')
|
||||
: upgradeStatus.result.error,
|
||||
color: upgradeStatus.result.success ? 'success' : 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
});
|
||||
setShowUpgradeModal(false);
|
||||
}
|
||||
}, [upgradeStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
getDeviceInformation();
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
return (
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
@@ -99,7 +138,7 @@ const DeviceActions = () => {
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-4">
|
||||
<CCol>
|
||||
<CButton block color="primary" onClick={toggleUpgradeModal}>
|
||||
{t('actions.firmware_upgrade')}
|
||||
@@ -111,7 +150,7 @@ const DeviceActions = () => {
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-4">
|
||||
<CCol>
|
||||
<CButton block color="primary" onClick={toggleScanModal}>
|
||||
{t('actions.wifi_scan')}
|
||||
@@ -123,7 +162,7 @@ const DeviceActions = () => {
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-4">
|
||||
<CCol>
|
||||
<LoadingButton
|
||||
isLoading={connectLoading}
|
||||
@@ -138,14 +177,37 @@ const DeviceActions = () => {
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-4">
|
||||
<CCol>
|
||||
<CButton block color="primary" onClick={toggleQueueModal}>
|
||||
{t('commands.event_queue')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CButton block color="primary" onClick={toggleTelemetryModal}>
|
||||
{t('actions.telemetry')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardBody>
|
||||
<RebootModal show={showRebootModal} toggleModal={toggleRebootModal} />
|
||||
<BlinkModal show={showBlinkModal} toggleModal={toggleBlinkModal} />
|
||||
<FirmwareUpgradeModal show={showUpgradeModal} toggleModal={toggleUpgradeModal} />
|
||||
<DeviceFirmwareModal
|
||||
t={t}
|
||||
endpoints={endpoints}
|
||||
currentToken={currentToken}
|
||||
device={device}
|
||||
show={showUpgradeModal}
|
||||
toggleFirmwareModal={toggleUpgradeModal}
|
||||
setUpgradeStatus={setUpgradeStatus}
|
||||
upgradeStatus={upgradeStatus}
|
||||
/>
|
||||
<TraceModal show={showTraceModal} toggleModal={toggleTraceModal} />
|
||||
<WifiScanModal show={showScanModal} toggleModal={toggleScanModal} />
|
||||
<ConfigureModal show={showConfigModal} toggleModal={toggleConfigModal} />
|
||||
<FactoryResetModal show={showFactoryModal} toggleModal={toggleFactoryResetModal} />
|
||||
<EventQueueModal show={showQueueModal} toggle={toggleQueueModal} />
|
||||
<TelemetryModal show={showTelemetryModal} toggle={toggleTelemetryModal} />
|
||||
</CCard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 10px;
|
||||
}
|
||||
@@ -10,14 +10,13 @@ import {
|
||||
} from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Translation } from 'react-i18next';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceConfigurationModal = ({ show, toggle, configuration }) => (
|
||||
<Translation>
|
||||
{(t) => (
|
||||
<CModal size="lg" show={show} onClose={toggle}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle className={styles.modalTitle}>{t('configuration.title')}</CModalTitle>
|
||||
<CModalTitle className="text-dark">{t('configuration.title')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<pre className="ignore">{JSON.stringify(configuration, null, 4)}</pre>
|
||||
|
||||
@@ -16,21 +16,31 @@ import CIcon from '@coreui/icons-react';
|
||||
import { cilWindowMaximize } from '@coreui/icons';
|
||||
import { prettyDate } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import CopyToClipboardButton from 'components/CopyToClipboardButton';
|
||||
import DeviceNotes from 'components/DeviceNotes';
|
||||
import {
|
||||
CopyToClipboardButton,
|
||||
HideTextButton,
|
||||
NotesTable,
|
||||
useAuth,
|
||||
useDevice,
|
||||
useToast,
|
||||
} from 'ucentral-libs';
|
||||
import DeviceConfigurationModal from './DeviceConfigurationModal';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceConfiguration = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const { addToast } = useToast();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [device, setDevice] = useState(null);
|
||||
|
||||
const toggleShowPassword = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
|
||||
const toggle = (e) => {
|
||||
setCollapse(!collapse);
|
||||
e.preventDefault();
|
||||
@@ -49,14 +59,51 @@ const DeviceConfiguration = () => {
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`,
|
||||
options,
|
||||
)
|
||||
.get(`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`, options)
|
||||
.then((response) => {
|
||||
setDevice(response.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch((e) => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('device.error_fetching_device', { error: e.response?.data?.ErrorDescription }),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const saveNote = (currentNote) => {
|
||||
setLoading(true);
|
||||
|
||||
const parameters = {
|
||||
serialNumber: deviceSerialNumber,
|
||||
notes: [{ note: currentNote }],
|
||||
};
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.put(
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then(() => {
|
||||
getDevice();
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const getPassword = () => {
|
||||
const password = device.devicePassword === '' ? 'openwifi' : device.devicePassword;
|
||||
return showPassword ? password : '******';
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -72,36 +119,34 @@ const DeviceConfiguration = () => {
|
||||
<CCol>
|
||||
<div className="text-value-lg">{t('configuration.title')}</div>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<div className={styles.alignRight}>
|
||||
<CPopover content={t('configuration.view_json')}>
|
||||
<CButton color="secondary" onClick={toggleModal} size="sm">
|
||||
<CIcon content={cilWindowMaximize} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
<CCol className="text-right">
|
||||
<CPopover content={t('configuration.view_json')}>
|
||||
<CButton color="secondary" onClick={toggleModal} size="sm">
|
||||
<CIcon content={cilWindowMaximize} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.uuid')} : </CLabel>
|
||||
<CLabel>{t('configuration.uuid')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.UUID}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.serial_number')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.serialNumber}
|
||||
<CopyToClipboardButton size="sm" content={device.serialNumber} />
|
||||
<CopyToClipboardButton t={t} size="sm" content={device.serialNumber} />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.type')} : </CLabel>
|
||||
</CCol>
|
||||
@@ -109,49 +154,55 @@ const DeviceConfiguration = () => {
|
||||
{device.deviceType}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.last_configuration_change')} : </CLabel>
|
||||
<CLabel>{t('firmware.revision')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{prettyDate(device.lastConfigurationChange)}
|
||||
{device.firmware}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.mac')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.macAddress}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.created')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{prettyDate(device.createdTimestamp)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3" className={styles.topPadding}>
|
||||
<CLabel>{t('configuration.device_password')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.devicePassword === '' ? 'openwifi' : device.devicePassword}
|
||||
<CopyToClipboardButton
|
||||
size="sm"
|
||||
content={device?.devicePassword === '' ? 'openwifi' : device.devicePassword}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<DeviceNotes
|
||||
notes={device.notes}
|
||||
refreshNotes={getDevice}
|
||||
serialNumber={deviceSerialNumber}
|
||||
/>
|
||||
<CCollapse show={collapse}>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.last_configuration_change')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{prettyDate(device.lastConfigurationChange)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.mac')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.macAddress}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-2 mb-4">
|
||||
<CCol md="3">
|
||||
<CLabel className="align-middle">{t('configuration.device_password')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="2">
|
||||
{getPassword()}
|
||||
</CCol>
|
||||
<CCol md="7">
|
||||
<HideTextButton t={t} toggle={toggleShowPassword} show={showPassword} />
|
||||
<CopyToClipboardButton
|
||||
t={t}
|
||||
size="sm"
|
||||
content={device?.devicePassword === '' ? 'openwifi' : device.devicePassword}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<NotesTable
|
||||
t={t}
|
||||
notes={device.notes}
|
||||
loading={loading}
|
||||
addNote={saveNote}
|
||||
descriptionColumn={false}
|
||||
/>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.last_configuration_download')} : </CLabel>
|
||||
</CCol>
|
||||
@@ -159,7 +210,7 @@ const DeviceConfiguration = () => {
|
||||
{prettyDate(device.lastConfigurationDownload)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.manufacturer')} :</CLabel>
|
||||
</CCol>
|
||||
@@ -167,7 +218,15 @@ const DeviceConfiguration = () => {
|
||||
{device.manufacturer}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.created')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{prettyDate(device.createdTimestamp)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.owner')} :</CLabel>
|
||||
</CCol>
|
||||
@@ -175,7 +234,7 @@ const DeviceConfiguration = () => {
|
||||
{device.owner}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.location')} :</CLabel>
|
||||
</CCol>
|
||||
@@ -187,7 +246,7 @@ const DeviceConfiguration = () => {
|
||||
<CCardFooter>
|
||||
<CButton show={collapse ? 'true' : 'false'} onClick={toggle} block>
|
||||
<CIcon
|
||||
className={styles.blackIcon}
|
||||
className="text-dark"
|
||||
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
||||
size="lg"
|
||||
/>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
.alignRight {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.blackIcon {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.modalTitle {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.topPadding {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.spacedRow {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
135
src/components/DeviceFirmwareModal/index.js
Normal file
135
src/components/DeviceFirmwareModal/index.js
Normal file
@@ -0,0 +1,135 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DeviceFirmwareModal as Modal, useAuth, useToast } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DeviceFirmwareModal = ({
|
||||
device,
|
||||
show,
|
||||
toggleFirmwareModal,
|
||||
setUpgradeStatus,
|
||||
upgradeStatus,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { addToast } = useToast();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [firmwareVersions, setFirmwareVersions] = useState([]);
|
||||
|
||||
const getPartialFirmware = async (offset) => {
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
return axiosInstance
|
||||
.get(
|
||||
`${endpoints.owfms}/api/v1/firmwares?deviceType=${device.compatible}&limit=500&offset=${offset}`,
|
||||
{
|
||||
headers,
|
||||
},
|
||||
)
|
||||
.then((response) => response.data.firmwares)
|
||||
.catch(() => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('common.general_error'),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
return [];
|
||||
});
|
||||
};
|
||||
|
||||
const getFirmwareList = async () => {
|
||||
setLoading(true);
|
||||
|
||||
const allFirmwares = [];
|
||||
let continueFirmware = true;
|
||||
let i = 1;
|
||||
while (continueFirmware) {
|
||||
const newFirmwares = await getPartialFirmware(i);
|
||||
if (newFirmwares === null || newFirmwares.length === 0) continueFirmware = false;
|
||||
allFirmwares.push(...newFirmwares);
|
||||
i += 500;
|
||||
}
|
||||
const sortedFirmware = allFirmwares.sort((a, b) => {
|
||||
const firstDate = a.imageDate;
|
||||
const secondDate = b.imageDate;
|
||||
if (firstDate < secondDate) return 1;
|
||||
return firstDate > secondDate ? -1 : 0;
|
||||
});
|
||||
setFirmwareVersions(sortedFirmware);
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const upgradeToVersion = (uri) => {
|
||||
setUpgradeStatus({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
const parameters = {
|
||||
serialNumber: device.serialNumber,
|
||||
when: 0,
|
||||
uri,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(`${endpoints.owgw}/api/v1/device/${device.serialNumber}/upgrade`, parameters, {
|
||||
headers,
|
||||
})
|
||||
.then((response) => {
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
result: {
|
||||
success: response.data.errorCode === 0,
|
||||
error: response.data.errorCode === 0 ? '' : t('firmware.error_fetching_latest'),
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
result: {
|
||||
success: false,
|
||||
error: t('common.general_error'),
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (show && device.compatible) getFirmwareList();
|
||||
}, [device, show]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
t={t}
|
||||
device={device}
|
||||
show={show}
|
||||
toggle={toggleFirmwareModal}
|
||||
firmwareVersions={firmwareVersions}
|
||||
upgradeToVersion={upgradeToVersion}
|
||||
loading={loading}
|
||||
upgradeStatus={upgradeStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceFirmwareModal.propTypes = {
|
||||
device: PropTypes.instanceOf(Object).isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggleFirmwareModal: PropTypes.func.isRequired,
|
||||
setUpgradeStatus: PropTypes.func.isRequired,
|
||||
upgradeStatus: PropTypes.instanceOf(Object).isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(DeviceFirmwareModal);
|
||||
@@ -13,22 +13,19 @@ import {
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilTrash } from '@coreui/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import { prettyDate, dateToUnix } from 'utils/helper';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
import DeleteLogModal from 'components/DeleteLogModal';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceHealth = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
const [details, setDetails] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [healthChecks, setHealthChecks] = useState([]);
|
||||
@@ -45,11 +42,6 @@ const DeviceHealth = () => {
|
||||
setShowDeleteModal(!showDeleteModal);
|
||||
};
|
||||
|
||||
const toggle = (e) => {
|
||||
setCollapse(!collapse);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const modifyStart = (value) => {
|
||||
setStart(value);
|
||||
};
|
||||
@@ -88,7 +80,7 @@ const DeviceHealth = () => {
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(
|
||||
deviceSerialNumber,
|
||||
)}/healthchecks${extraParams}`,
|
||||
options,
|
||||
@@ -198,97 +190,71 @@ const DeviceHealth = () => {
|
||||
color={barColor}
|
||||
inverse="true"
|
||||
footerSlot={
|
||||
<div className={styles.footer}>
|
||||
<CProgress className={styles.progressBar} color="white" value={sanityLevel ?? 0} />
|
||||
<CCollapse show={collapse}>
|
||||
<div className={styles.alignRight}>
|
||||
<CPopover content={t('common.delete')}>
|
||||
<CButton
|
||||
color="light"
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleDeleteModal();
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilTrash" size="lg" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
<div className="pb-1 px-3">
|
||||
<CProgress className="mb-3" color="white" value={sanityLevel ?? 0} />
|
||||
<CRow className="mb-3">
|
||||
<CCol>
|
||||
{t('common.from')}
|
||||
:
|
||||
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
|
||||
</CCol>
|
||||
<CCol>
|
||||
{t('common.to')}
|
||||
:
|
||||
<DatePicker includeTime onChange={(date) => modifyEnd(date)} />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CCard className="p-0">
|
||||
<div className="overflow-auto" style={{ height: '200px' }}>
|
||||
<CDataTable
|
||||
border
|
||||
items={healthChecks ?? []}
|
||||
fields={columns}
|
||||
className="text-white"
|
||||
loading={loading}
|
||||
sorterValue={{ column: 'recorded', desc: 'true' }}
|
||||
scopedSlots={{
|
||||
UUID: (item) => <td className="align-middle">{item.UUID}</td>,
|
||||
recorded: (item) => <td className="align-middle">{prettyDate(item.recorded)}</td>,
|
||||
sanity: (item) => <td className="align-middle">{`${item.sanity}%`}</td>,
|
||||
show_details: (item, index) => (
|
||||
<td className="align-middle">
|
||||
<CButton
|
||||
color="primary"
|
||||
variant={details.includes(index) ? '' : 'outline'}
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleDetails(index);
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilList" size="lg" />
|
||||
</CButton>
|
||||
</td>
|
||||
),
|
||||
details: (item, index) => (
|
||||
<CCollapse show={details.includes(index)}>
|
||||
<CCardBody>
|
||||
<h5>{t('common.details')}</h5>
|
||||
<div>{getDetails(index, item.values)}</div>
|
||||
</CCardBody>
|
||||
</CCollapse>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{showLoadingMore && (
|
||||
<div className="mb-3">
|
||||
<LoadingButton
|
||||
label={t('common.view_more')}
|
||||
isLoadingLabel={t('common.loading_more_ellipsis')}
|
||||
isLoading={loadingMore}
|
||||
action={showMoreLogs}
|
||||
variant="outline"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol>
|
||||
{t('common.from')}
|
||||
:
|
||||
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
|
||||
</CCol>
|
||||
<CCol>
|
||||
{t('common.to')}
|
||||
:
|
||||
<DatePicker includeTime onChange={(date) => modifyEnd(date)} />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CCard>
|
||||
<div className={[styles.scrollable, 'overflow-auto'].join(' ')}>
|
||||
<CDataTable
|
||||
items={healthChecks ?? []}
|
||||
fields={columns}
|
||||
className={styles.dataTable}
|
||||
loading={loading}
|
||||
sorterValue={{ column: 'recorded', desc: 'true' }}
|
||||
scopedSlots={{
|
||||
recorded: (item) => <td>{prettyDate(item.recorded)}</td>,
|
||||
sanity: (item) => <td>{`${item.sanity}%`}</td>,
|
||||
show_details: (item, index) => {
|
||||
if (item.sanity === 100) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<td className="py-2">
|
||||
<CButton
|
||||
color="primary"
|
||||
variant={details.includes(index) ? '' : 'outline'}
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleDetails(index);
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilList" size="lg" />
|
||||
</CButton>
|
||||
</td>
|
||||
);
|
||||
},
|
||||
details: (item, index) => (
|
||||
<CCollapse show={details.includes(index)}>
|
||||
<CCardBody>
|
||||
<h5>{t('common.details')}</h5>
|
||||
<div>{getDetails(index, item.values)}</div>
|
||||
</CCardBody>
|
||||
</CCollapse>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<CRow className={styles.loadMoreRow}>
|
||||
{showLoadingMore && (
|
||||
<LoadingButton
|
||||
label={t('common.view_more')}
|
||||
isLoadingLabel={t('common.loading_more_ellipsis')}
|
||||
isLoading={loadingMore}
|
||||
action={showMoreLogs}
|
||||
variant="outline"
|
||||
/>
|
||||
)}
|
||||
</CRow>
|
||||
</div>
|
||||
</CCard>
|
||||
</CCollapse>
|
||||
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
|
||||
<CIcon
|
||||
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
||||
className={styles.icon}
|
||||
size="lg"
|
||||
/>
|
||||
</CButton>
|
||||
</CCard>
|
||||
<DeleteLogModal
|
||||
serialNumber={deviceSerialNumber}
|
||||
object="healthchecks"
|
||||
@@ -297,7 +263,15 @@ const DeviceHealth = () => {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
>
|
||||
<div className="text-right float-right">
|
||||
<CPopover content={t('common.delete')}>
|
||||
<CButton onClick={toggleDeleteModal} size="sm">
|
||||
<CIcon name="cil-trash" content={cilTrash} className="text-white" size="2xl" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CWidgetDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
.icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dataTable {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.spacedRow {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.loadMoreRow {
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.alignRight {
|
||||
float: right;
|
||||
}
|
||||
@@ -1,65 +1,121 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
CBadge,
|
||||
CCardBody,
|
||||
CDataTable,
|
||||
CButton,
|
||||
CLink,
|
||||
CCard,
|
||||
CCardHeader,
|
||||
CRow,
|
||||
CCol,
|
||||
CPopover,
|
||||
CSelect,
|
||||
} from '@coreui/react';
|
||||
import ReactPaginate from 'react-paginate';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { cilSync, cilInfo, cilBadge, cilBan } from '@coreui/icons';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { cleanBytesString } from 'utils/helper';
|
||||
import meshIcon from 'assets/icons/Mesh.png';
|
||||
import apIcon from 'assets/icons/AP.png';
|
||||
import internetSwitch from 'assets/icons/Switch.png';
|
||||
import iotIcon from 'assets/icons/IotIcon.png';
|
||||
import { getItem, setItem } from 'utils/localStorageHelper';
|
||||
import styles from './index.module.scss';
|
||||
import DeviceSearchBar from 'components/DeviceSearchBar';
|
||||
import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
|
||||
import FirmwareHistoryModal from 'components/FirmwareHistoryModal';
|
||||
import { DeviceListTable, useAuth, useToast } from 'ucentral-libs';
|
||||
import meshIcon from '../../assets/icons/Mesh.png';
|
||||
import apIcon from '../../assets/icons/AP.png';
|
||||
import internetSwitch from '../../assets/icons/Switch.png';
|
||||
import iotIcon from '../../assets/icons/IotIcon.png';
|
||||
|
||||
const DeviceList = () => {
|
||||
const { t } = useTranslation();
|
||||
const { addToast } = useToast();
|
||||
const history = useHistory();
|
||||
const { search } = useLocation();
|
||||
const page = new URLSearchParams(search).get('page');
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [loadedSerials, setLoadedSerials] = useState(false);
|
||||
const [serialNumbers, setSerialNumbers] = useState([]);
|
||||
const [page, setPage] = useState(0);
|
||||
const [upgradeStatus, setUpgradeStatus] = useState({
|
||||
loading: false,
|
||||
});
|
||||
const [deleteStatus, setDeleteStatus] = useState({
|
||||
loading: false,
|
||||
});
|
||||
const [deviceCount, setDeviceCount] = useState(0);
|
||||
const [pageCount, setPageCount] = useState(0);
|
||||
const [devicesPerPage, setDevicesPerPage] = useState(getItem('devicesPerPage') || 10);
|
||||
const [devicesPerPage, setDevicesPerPage] = useState(getItem('devicesPerPage') || '10');
|
||||
const [devices, setDevices] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showHistoryModal, setHistoryModal] = useState(false);
|
||||
const [showFirmwareModal, setShowFirmwareModal] = useState(false);
|
||||
const [firmwareDevice, setFirmwareDevice] = useState({
|
||||
deviceType: '',
|
||||
serialNumber: '',
|
||||
});
|
||||
|
||||
const getSerialNumbers = () => {
|
||||
const deviceIcons = {
|
||||
meshIcon,
|
||||
apIcon,
|
||||
internetSwitch,
|
||||
iotIcon,
|
||||
};
|
||||
|
||||
const toggleFirmwareModal = (device) => {
|
||||
setShowFirmwareModal(!showFirmwareModal);
|
||||
if (device !== undefined) setFirmwareDevice(device);
|
||||
};
|
||||
|
||||
const toggleHistoryModal = (device) => {
|
||||
setHistoryModal(!showHistoryModal);
|
||||
if (device !== undefined) setFirmwareDevice(device);
|
||||
};
|
||||
|
||||
const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => {
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
let fullDevices;
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.ucentralgw}/api/v1/devices?serialOnly=true`, {
|
||||
headers,
|
||||
.get(
|
||||
`${endpoints.owgw}/api/v1/devices?deviceWithStatus=true&limit=${devicePerPage}&offset=${
|
||||
devicePerPage * selectedPage + 1
|
||||
}`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
fullDevices = response.data.devicesWithStatus;
|
||||
const serialsToGet = fullDevices.map((device) => device.serialNumber);
|
||||
|
||||
if (serialsToGet.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return axiosInstance.get(
|
||||
`${endpoints.owfms}/api/v1/firmwareAge?select=${serialsToGet}`,
|
||||
options,
|
||||
);
|
||||
})
|
||||
.then((response) => {
|
||||
setSerialNumbers(response.data.serialNumbers);
|
||||
setLoadedSerials(true);
|
||||
if (response !== null) {
|
||||
fullDevices = fullDevices.map((device, index) => {
|
||||
const foundAgeDate = response.data.ages[index].age !== undefined;
|
||||
if (foundAgeDate) {
|
||||
return {
|
||||
...device,
|
||||
firmwareInfo: {
|
||||
age: response.data.ages[index].age,
|
||||
latest: response.data.ages[index].latest,
|
||||
},
|
||||
};
|
||||
}
|
||||
return device;
|
||||
});
|
||||
}
|
||||
setDevices(fullDevices);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((e) => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const getDeviceInformation = () => {
|
||||
const getCount = () => {
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
@@ -67,22 +123,31 @@ const DeviceList = () => {
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
const startIndex = page * devicesPerPage;
|
||||
const endIndex = parseInt(startIndex, 10) + parseInt(devicesPerPage, 10);
|
||||
const serialsToGet = serialNumbers
|
||||
.slice(startIndex, endIndex)
|
||||
.map((x) => encodeURIComponent(x))
|
||||
.join(',');
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.ucentralgw}/api/v1/devices?deviceWithStatus=true&select=${serialsToGet}`, {
|
||||
.get(`${endpoints.owgw}/api/v1/devices?countOnly=true`, {
|
||||
headers,
|
||||
})
|
||||
.then((response) => {
|
||||
setDevices(response.data.devicesWithStatus);
|
||||
setLoading(false);
|
||||
const devicesCount = response.data.count;
|
||||
const pagesCount = Math.ceil(devicesCount / devicesPerPage);
|
||||
setPageCount(pagesCount);
|
||||
setDeviceCount(devicesCount);
|
||||
|
||||
let selectedPage = page;
|
||||
|
||||
if (page >= pagesCount) {
|
||||
history.push(`/devices?page=${pagesCount - 1}`);
|
||||
selectedPage = pagesCount - 1;
|
||||
}
|
||||
getDeviceInformation(selectedPage);
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((e) => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
@@ -90,29 +155,54 @@ const DeviceList = () => {
|
||||
const refreshDevice = (serialNumber) => {
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
let newDevice;
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/devices?deviceWithStatus=true&select=${encodeURIComponent(
|
||||
`${endpoints.owgw}/api/v1/devices?deviceWithStatus=true&select=${encodeURIComponent(
|
||||
serialNumber,
|
||||
)}`,
|
||||
{
|
||||
headers,
|
||||
options,
|
||||
)
|
||||
.then(
|
||||
({
|
||||
data: {
|
||||
devicesWithStatus: [device],
|
||||
},
|
||||
}) => {
|
||||
newDevice = device;
|
||||
|
||||
return axiosInstance.get(
|
||||
`${endpoints.owfms}/api/v1/firmwareAge?select=${serialNumber}`,
|
||||
options,
|
||||
);
|
||||
},
|
||||
)
|
||||
.then((response) => {
|
||||
const device = response.data.devicesWithStatus[0];
|
||||
newDevice.firmwareInfo = {
|
||||
age: response.data.ages[0].age,
|
||||
latest: response.data.ages[0].latest,
|
||||
};
|
||||
const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber);
|
||||
const newList = devices;
|
||||
newList[foundIndex] = device;
|
||||
newList[foundIndex] = newDevice;
|
||||
setDevices(newList);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((e) => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
@@ -120,319 +210,212 @@ const DeviceList = () => {
|
||||
const updateDevicesPerPage = (value) => {
|
||||
setItem('devicesPerPage', value);
|
||||
setDevicesPerPage(value);
|
||||
|
||||
const newPageCount = Math.ceil(deviceCount / value);
|
||||
setPageCount(newPageCount);
|
||||
|
||||
let selectedPage = page;
|
||||
|
||||
if (page >= newPageCount) {
|
||||
history.push(`/devices?page=${newPageCount - 1}`);
|
||||
selectedPage = newPageCount - 1;
|
||||
}
|
||||
|
||||
getDeviceInformation(selectedPage, value);
|
||||
};
|
||||
|
||||
const updatePageCount = ({ selected: selectedPage }) => {
|
||||
setPage(selectedPage);
|
||||
history.push(`/devices?page=${selectedPage}`);
|
||||
getDeviceInformation(selectedPage);
|
||||
};
|
||||
|
||||
const upgradeToLatest = (device) => {
|
||||
setUpgradeStatus({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.owfms}/api/v1/firmwares?deviceType=${device.compatible}&latestOnly=true`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data.uri) {
|
||||
const parameters = {
|
||||
serialNumber: device.serialNumber,
|
||||
when: 0,
|
||||
uri: response.data.uri,
|
||||
};
|
||||
return axiosInstance.post(
|
||||
`${endpoints.owgw}/api/v1/device/${device.serialNumber}/upgrade`,
|
||||
parameters,
|
||||
options,
|
||||
);
|
||||
}
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
result: {
|
||||
success: false,
|
||||
error: t('firmware.error_fetching_latest'),
|
||||
},
|
||||
});
|
||||
return null;
|
||||
})
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
result: {
|
||||
success: response.data.errorCode === 0,
|
||||
error: response.data.errorCode === 0 ? '' : t('firmware.error_fetching_latest'),
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
result: {
|
||||
success: false,
|
||||
error: t('common.general_error'),
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const connectRtty = (serialNumber) => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.owgw}/api/v1/device/${encodeURIComponent(serialNumber)}/rtty`, options)
|
||||
.then((response) => {
|
||||
const url = `https://${response.data.server}:${response.data.viewport}/connect/${response.data.connectionId}`;
|
||||
const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
|
||||
if (newWindow) newWindow.opener = null;
|
||||
})
|
||||
.catch((e) => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('connect.error_trying_to_connect', { error: e.response?.data?.ErrorDescription }),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const deleteDevice = (serialNumber) => {
|
||||
setDeleteStatus({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.delete(`${endpoints.owgw}/api/v1/device/${encodeURIComponent(serialNumber)}`, options)
|
||||
.then(() => {
|
||||
addToast({
|
||||
title: t('common.success'),
|
||||
body: t('common.device_deleted'),
|
||||
color: 'success',
|
||||
autohide: true,
|
||||
});
|
||||
getCount();
|
||||
})
|
||||
.catch(() => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('common.unable_to_delete'),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setDeleteStatus({
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getSerialNumbers();
|
||||
if (page === undefined || page === null || Number.isNaN(page)) {
|
||||
history.push(`/devices?page=0`);
|
||||
}
|
||||
getCount();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadedSerials) getDeviceInformation();
|
||||
}, [serialNumbers, page, devicesPerPage, loadedSerials]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadedSerials) {
|
||||
const count = Math.ceil(serialNumbers.length / devicesPerPage);
|
||||
setPageCount(count);
|
||||
if (upgradeStatus.result !== undefined) {
|
||||
addToast({
|
||||
title: upgradeStatus.result.success ? t('common.success') : t('common.error'),
|
||||
body: upgradeStatus.result.success
|
||||
? t('firmware.upgrade_command_submitted')
|
||||
: upgradeStatus.result.error,
|
||||
color: upgradeStatus.result.success ? 'success' : 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
});
|
||||
setShowFirmwareModal(false);
|
||||
}
|
||||
}, [devicesPerPage, loadedSerials]);
|
||||
}, [upgradeStatus]);
|
||||
|
||||
return (
|
||||
<DeviceListDisplay
|
||||
devices={devices}
|
||||
loading={loading}
|
||||
updateDevicesPerPage={updateDevicesPerPage}
|
||||
devicesPerPage={devicesPerPage}
|
||||
pageCount={pageCount}
|
||||
updatePage={updatePageCount}
|
||||
pageRangeDisplayed={5}
|
||||
refreshDevice={refreshDevice}
|
||||
t={t}
|
||||
/>
|
||||
<div>
|
||||
<DeviceListTable
|
||||
currentPage={page}
|
||||
t={t}
|
||||
searchBar={<DeviceSearchBar />}
|
||||
devices={devices}
|
||||
loading={loading}
|
||||
updateDevicesPerPage={updateDevicesPerPage}
|
||||
devicesPerPage={devicesPerPage}
|
||||
pageCount={pageCount}
|
||||
updatePage={updatePageCount}
|
||||
pageRangeDisplayed={5}
|
||||
refreshDevice={refreshDevice}
|
||||
toggleFirmwareModal={toggleFirmwareModal}
|
||||
toggleHistoryModal={toggleHistoryModal}
|
||||
upgradeToLatest={upgradeToLatest}
|
||||
upgradeStatus={upgradeStatus}
|
||||
deviceIcons={deviceIcons}
|
||||
connectRtty={connectRtty}
|
||||
deleteDevice={deleteDevice}
|
||||
deleteStatus={deleteStatus}
|
||||
/>
|
||||
<DeviceFirmwareModal
|
||||
endpoints={endpoints}
|
||||
currentToken={currentToken}
|
||||
device={firmwareDevice}
|
||||
show={showFirmwareModal}
|
||||
toggleFirmwareModal={toggleFirmwareModal}
|
||||
setUpgradeStatus={setUpgradeStatus}
|
||||
upgradeStatus={upgradeStatus}
|
||||
/>
|
||||
<FirmwareHistoryModal
|
||||
serialNumber={firmwareDevice.serialNumber}
|
||||
show={showHistoryModal}
|
||||
toggle={toggleHistoryModal}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DeviceListDisplay = ({
|
||||
devices,
|
||||
devicesPerPage,
|
||||
loading,
|
||||
updateDevicesPerPage,
|
||||
pageCount,
|
||||
updatePage,
|
||||
refreshDevice,
|
||||
t,
|
||||
}) => {
|
||||
const columns = [
|
||||
{ key: 'deviceType', label: '', filter: false, sorter: false, _style: { width: '5%' } },
|
||||
{ key: 'verifiedCertificate', label: t('common.certificate'), _style: { width: '1%' } },
|
||||
{ key: 'serialNumber', label: t('common.serial_number'), _style: { width: '5%' } },
|
||||
{ key: 'UUID', label: t('common.config_id'), _style: { width: '5%' } },
|
||||
{ key: 'firmware', label: t('common.firmware'), filter: false },
|
||||
{ key: 'compatible', label: t('common.compatible'), filter: false, _style: { width: '20%' } },
|
||||
{ key: 'txBytes', label: 'Tx', filter: false, _style: { width: '12%' } },
|
||||
{ key: 'rxBytes', label: 'Rx', filter: false, _style: { width: '12%' } },
|
||||
{ key: 'ipAddress', label: t('common.ip_address'), _style: { width: '16%' } },
|
||||
{
|
||||
key: 'show_details',
|
||||
label: '',
|
||||
_style: { width: '3%' },
|
||||
sorter: false,
|
||||
filter: false,
|
||||
},
|
||||
{
|
||||
key: 'refresh',
|
||||
label: '',
|
||||
_style: { width: '2%' },
|
||||
sorter: false,
|
||||
filter: false,
|
||||
},
|
||||
];
|
||||
|
||||
const getDeviceIcon = (deviceType) => {
|
||||
if (deviceType === 'AP_Default' || deviceType === 'AP') {
|
||||
return <img src={apIcon} className={styles.icon} alt="AP" />;
|
||||
}
|
||||
if (deviceType === 'MESH') {
|
||||
return <img src={meshIcon} className={styles.icon} alt="MESH" />;
|
||||
}
|
||||
if (deviceType === 'SWITCH') {
|
||||
return <img src={internetSwitch} className={styles.icon} alt="SWITCH" />;
|
||||
}
|
||||
if (deviceType === 'IOT') {
|
||||
return <img src={iotIcon} className={styles.icon} alt="SWITCH" />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getCertBadge = (cert) => {
|
||||
if (cert === 'NO_CERTIFICATE') {
|
||||
return (
|
||||
<div className={styles.certificateWrapper}>
|
||||
<CIcon className={styles.badge} name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
|
||||
<CIcon
|
||||
className={styles.badCertificate}
|
||||
name="cil-ban"
|
||||
content={cilBan}
|
||||
size="3xl"
|
||||
alt="AP"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let color = 'transparent';
|
||||
switch (cert) {
|
||||
case 'VALID_CERTIFICATE':
|
||||
color = 'danger';
|
||||
break;
|
||||
case 'MISMATCH_SERIAL':
|
||||
return (
|
||||
<CBadge color={color} className={styles.mismatchBackground}>
|
||||
<CIcon name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
|
||||
</CBadge>
|
||||
);
|
||||
case 'VERIFIED':
|
||||
color = 'success';
|
||||
break;
|
||||
default:
|
||||
return (
|
||||
<div className={styles.certificateWrapper}>
|
||||
<CIcon
|
||||
className={styles.badge}
|
||||
name="cil-badge"
|
||||
content={cilBadge}
|
||||
size="2xl"
|
||||
alt="AP"
|
||||
/>
|
||||
<CIcon
|
||||
className={styles.badCertificate}
|
||||
name="cil-ban"
|
||||
content={cilBan}
|
||||
size="3xl"
|
||||
alt="AP"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<CBadge color={color}>
|
||||
<CIcon name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
|
||||
</CBadge>
|
||||
);
|
||||
};
|
||||
|
||||
const getStatusBadge = (status) => {
|
||||
if (status) {
|
||||
return 'success';
|
||||
}
|
||||
return 'danger';
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol />
|
||||
<CCol xs={1}>
|
||||
<CSelect
|
||||
custom
|
||||
defaultValue={devicesPerPage}
|
||||
onChange={(e) => updateDevicesPerPage(e.target.value)}
|
||||
disabled={loading}
|
||||
>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
</CSelect>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CDataTable
|
||||
items={devices ?? []}
|
||||
fields={columns}
|
||||
hover
|
||||
border
|
||||
loading={loading}
|
||||
scopedSlots={{
|
||||
serialNumber: (item) => (
|
||||
<td className={styles.column}>
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/devices/${item.serialNumber}`}
|
||||
>
|
||||
{item.serialNumber}
|
||||
</CLink>
|
||||
</td>
|
||||
),
|
||||
deviceType: (item) => (
|
||||
<td className={styles.column}>
|
||||
<CPopover
|
||||
content={item.connected ? t('common.connected') : t('common.not_connected')}
|
||||
placement="top"
|
||||
>
|
||||
<CBadge color={getStatusBadge(item.connected)}>
|
||||
{getDeviceIcon(item.deviceType) ?? item.deviceType}
|
||||
</CBadge>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
verifiedCertificate: (item) => (
|
||||
<td className={styles.column}>
|
||||
<CPopover
|
||||
content={item.verifiedCertificate ?? t('common.unknown')}
|
||||
placement="top"
|
||||
>
|
||||
{getCertBadge(item.verifiedCertificate)}
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
firmware: (item) => (
|
||||
<td>
|
||||
<CPopover
|
||||
content={item.firmware ? item.firmware : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p style={{ width: '225px' }} className="text-truncate">
|
||||
{item.firmware}
|
||||
</p>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
compatible: (item) => (
|
||||
<td>
|
||||
<CPopover
|
||||
content={item.compatible ? item.compatible : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p style={{ width: '150px' }} className="text-truncate">
|
||||
{item.compatible}
|
||||
</p>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
txBytes: (item) => <td>{cleanBytesString(item.txBytes)}</td>,
|
||||
rxBytes: (item) => <td>{cleanBytesString(item.rxBytes)}</td>,
|
||||
ipAddress: (item) => (
|
||||
<td>
|
||||
<CPopover
|
||||
content={item.ipAddress ? item.ipAddress : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p style={{ width: '150px' }} className="text-truncate">
|
||||
{item.ipAddress}
|
||||
</p>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
refresh: (item) => (
|
||||
<td className="py-2">
|
||||
<CPopover content={t('common.refresh_device')}>
|
||||
<CButton
|
||||
onClick={() => refreshDevice(item.serialNumber)}
|
||||
color="primary"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
<CIcon name="cil-sync" content={cilSync} size="sm" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
show_details: (item) => (
|
||||
<td className="py-2">
|
||||
<CPopover content={t('configuration.details')}>
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/devices/${item.serialNumber}`}
|
||||
>
|
||||
<CButton color="primary" variant="outline" shape="square" size="sm">
|
||||
<CIcon name="cil-info" content={cilInfo} size="sm" />
|
||||
</CButton>
|
||||
</CLink>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ReactPaginate
|
||||
previousLabel="← Previous"
|
||||
nextLabel="Next →"
|
||||
pageCount={pageCount}
|
||||
onPageChange={updatePage}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName="pagination"
|
||||
pageClassName="page-item"
|
||||
pageLinkClassName="page-link"
|
||||
previousClassName="page-item"
|
||||
previousLinkClassName="page-link"
|
||||
nextClassName="page-item"
|
||||
nextLinkClassName="page-link"
|
||||
activeClassName="active"
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceListDisplay.propTypes = {
|
||||
devices: PropTypes.instanceOf(Array).isRequired,
|
||||
updateDevicesPerPage: PropTypes.func.isRequired,
|
||||
pageCount: PropTypes.number.isRequired,
|
||||
updatePage: PropTypes.func.isRequired,
|
||||
devicesPerPage: PropTypes.string.isRequired,
|
||||
refreshDevice: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceList;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
.icon {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.column {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.certificateWrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
left: 31%;
|
||||
margin-top: 8%;
|
||||
}
|
||||
|
||||
.badCertificate {
|
||||
position: absolute;
|
||||
z-index: 99;
|
||||
left: 22%;
|
||||
color: #e55353;
|
||||
}
|
||||
|
||||
.mismatchBackground {
|
||||
background-color: #ffff5c;
|
||||
}
|
||||
@@ -12,22 +12,19 @@ import {
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilTrash } from '@coreui/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import { prettyDate, dateToUnix } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
import DeleteLogModal from 'components/DeleteLogModal';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceLogs = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
const [details, setDetails] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [logs, setLogs] = useState([]);
|
||||
@@ -42,11 +39,6 @@ const DeviceLogs = () => {
|
||||
setShowDeleteModal(!showDeleteModal);
|
||||
};
|
||||
|
||||
const toggle = (e) => {
|
||||
setCollapse(!collapse);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const modifyStart = (value) => {
|
||||
setStart(value);
|
||||
};
|
||||
@@ -85,7 +77,7 @@ const DeviceLogs = () => {
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(
|
||||
deviceSerialNumber,
|
||||
)}/logs${extraParams}`,
|
||||
options,
|
||||
@@ -179,91 +171,76 @@ const DeviceLogs = () => {
|
||||
color="gradient-info"
|
||||
header={t('device_logs.title')}
|
||||
footerSlot={
|
||||
<div className={styles.footer}>
|
||||
<CCollapse show={collapse}>
|
||||
<div className={styles.alignRight}>
|
||||
<CPopover content={t('common.delete')}>
|
||||
<CButton
|
||||
color="light"
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleDeleteModal();
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilTrash" size="lg" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
<div className="pb-1 px-3">
|
||||
<CRow className="mb-3">
|
||||
<CCol>
|
||||
{t('common.from')}
|
||||
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
|
||||
</CCol>
|
||||
<CCol>
|
||||
{t('common.to')}
|
||||
<DatePicker includeTime onChange={(date) => modifyEnd(date)} />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CCard>
|
||||
<div className="overflow-auto" style={{ height: '250px' }}>
|
||||
<CDataTable
|
||||
items={logs ?? []}
|
||||
fields={columns}
|
||||
loading={loading}
|
||||
className="text-white"
|
||||
sorterValue={{ column: 'recorded', desc: 'true' }}
|
||||
scopedSlots={{
|
||||
recorded: (item) => <td>{prettyDate(item.recorded)}</td>,
|
||||
show_details: (item, index) => (
|
||||
<td className="py-2">
|
||||
<CButton
|
||||
color="primary"
|
||||
variant={details.includes(index) ? '' : 'outline'}
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleDetails(index);
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilList" size="lg" />
|
||||
</CButton>
|
||||
</td>
|
||||
),
|
||||
details: (item, index) => (
|
||||
<CCollapse show={details.includes(index)}>
|
||||
<CCardBody>
|
||||
<h5>{t('common.details')}</h5>
|
||||
<div>{getDetails(index, item)}</div>
|
||||
</CCardBody>
|
||||
</CCollapse>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{showLoadingMore && (
|
||||
<div className="mb-3">
|
||||
<LoadingButton
|
||||
label={t('common.view_more')}
|
||||
isLoadingLabel={t('common.loading_more_ellipsis')}
|
||||
isLoading={loadingMore}
|
||||
action={showMoreLogs}
|
||||
variant="outline"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CRow className={styles.datepickerRow}>
|
||||
<CCol>
|
||||
{t('common.from')}
|
||||
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
|
||||
</CCol>
|
||||
<CCol>
|
||||
{t('common.to')}
|
||||
<DatePicker includeTime onChange={(date) => modifyEnd(date)} />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CCard>
|
||||
<div className={[styles.scrollableCard, 'overflow-auto'].join(' ')}>
|
||||
<CDataTable
|
||||
items={logs ?? []}
|
||||
fields={columns}
|
||||
loading={loading}
|
||||
className={styles.whiteIcon}
|
||||
sorterValue={{ column: 'recorded', desc: 'true' }}
|
||||
scopedSlots={{
|
||||
recorded: (item) => <td>{prettyDate(item.recorded)}</td>,
|
||||
show_details: (item, index) => (
|
||||
<td className="py-2">
|
||||
<CButton
|
||||
color="primary"
|
||||
variant={details.includes(index) ? '' : 'outline'}
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleDetails(index);
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilList" size="lg" />
|
||||
</CButton>
|
||||
</td>
|
||||
),
|
||||
details: (item, index) => (
|
||||
<CCollapse show={details.includes(index)}>
|
||||
<CCardBody>
|
||||
<h5>{t('common.details')}</h5>
|
||||
<div>{getDetails(index, item)}</div>
|
||||
</CCardBody>
|
||||
</CCollapse>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<CRow className={styles.loadMoreRow}>
|
||||
{showLoadingMore && (
|
||||
<LoadingButton
|
||||
label={t('common.view_more')}
|
||||
isLoadingLabel={t('common.loading_more_ellipsis')}
|
||||
isLoading={loadingMore}
|
||||
action={showMoreLogs}
|
||||
variant="outline"
|
||||
/>
|
||||
)}
|
||||
</CRow>
|
||||
</div>
|
||||
</CCard>
|
||||
</CCollapse>
|
||||
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
|
||||
<CIcon
|
||||
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
||||
className={styles.whiteIcon}
|
||||
size="lg"
|
||||
/>
|
||||
</CButton>
|
||||
</CCard>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
>
|
||||
<div className="text-right float-right">
|
||||
<CPopover content={t('common.delete')}>
|
||||
<CButton onClick={toggleDeleteModal} size="sm">
|
||||
<CIcon name="cil-trash" content={cilTrash} className="text-white" size="2xl" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CWidgetDropdown>
|
||||
<DeleteLogModal
|
||||
serialNumber={deviceSerialNumber}
|
||||
object="logs"
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
.whiteIcon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.datepickerRow {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.scrollableCard {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.loadMoreRow {
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
|
||||
.alignRight {
|
||||
float: right;
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CDataTable, CRow, CCol, CLabel, CInput } from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { prettyDate } from 'utils/helper';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceNotes = ({ serialNumber, notes, refreshNotes }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [currentNote, setCurrentNote] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const saveNote = () => {
|
||||
setLoading(true);
|
||||
|
||||
const parameters = {
|
||||
serialNumber,
|
||||
notes: [{ note: currentNote }],
|
||||
};
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.put(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then(() => {
|
||||
setCurrentNote('');
|
||||
refreshNotes();
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
const columns = [
|
||||
{ key: 'created', label: t('common.date'), _style: { width: '30%' } },
|
||||
{ key: 'createdBy', label: t('common.created_by'), _style: { width: '20%' } },
|
||||
{ key: 'note', label: t('configuration.note'), _style: { width: '50%' } },
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.notes')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="9" md="7">
|
||||
<CInput
|
||||
id="notes-input"
|
||||
name="text-input"
|
||||
value={currentNote}
|
||||
onChange={(e) => setCurrentNote(e.target.value)}
|
||||
/>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<LoadingButton
|
||||
label={t('common.add')}
|
||||
isLoadingLabel={t('common.adding_ellipsis')}
|
||||
isLoading={loading}
|
||||
action={saveNote}
|
||||
disabled={loading || currentNote === ''}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol md="3" />
|
||||
<CCol xs="12" md="9">
|
||||
<div className={['overflow-auto', styles.scrollableBox].join(' ')}>
|
||||
<CDataTable
|
||||
striped
|
||||
responsive
|
||||
border
|
||||
loading={loading}
|
||||
fields={columns}
|
||||
className={styles.table}
|
||||
items={notes || []}
|
||||
noItemsView={{ noItems: t('common.no_items') }}
|
||||
sorterValue={{ column: 'created', desc: 'true' }}
|
||||
scopedSlots={{
|
||||
created: (item) => (
|
||||
<td>
|
||||
{item.created && item.created !== 0 ? prettyDate(item.created) : t('common.na')}
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceNotes.propTypes = {
|
||||
serialNumber: PropTypes.string.isRequired,
|
||||
notes: PropTypes.arrayOf(PropTypes.instanceOf(Object)).isRequired,
|
||||
refreshNotes: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceNotes;
|
||||
@@ -1,15 +0,0 @@
|
||||
.scrollableBox {
|
||||
height: 200px;
|
||||
border-style: solid;
|
||||
border-color: #ced2d8;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.table {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.spacedRow {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
71
src/components/DeviceSearchBar/index.js
Normal file
71
src/components/DeviceSearchBar/index.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useAuth, DeviceSearchBar as SearchBar } from 'ucentral-libs';
|
||||
import { checkIfJson } from 'utils/helper';
|
||||
|
||||
const DeviceSearchBar = () => {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [socket, setSocket] = useState(null);
|
||||
const [results, setResults] = useState([]);
|
||||
const [waitingSearch, setWaitingSearch] = useState('');
|
||||
|
||||
const search = (value) => {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
if (value.length > 0 && value.match('^[a-fA-F0-9]+$')) {
|
||||
setWaitingSearch('');
|
||||
socket.send(
|
||||
JSON.stringify({ command: 'serial_number_search', serial_prefix: value.toLowerCase() }),
|
||||
);
|
||||
} else {
|
||||
setResults([]);
|
||||
}
|
||||
} else if (socket.readyState !== WebSocket.CONNECTING) {
|
||||
setWaitingSearch(value);
|
||||
setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`));
|
||||
} else {
|
||||
setWaitingSearch(value);
|
||||
}
|
||||
};
|
||||
|
||||
const closeSocket = () => {
|
||||
if (socket !== null) {
|
||||
socket.close();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (socket !== null) {
|
||||
socket.onopen = () => {
|
||||
socket.send(`token:${currentToken}`);
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
if (checkIfJson(event.data)) {
|
||||
const result = JSON.parse(event.data);
|
||||
if (result.command === 'serial_number_search' && result.serialNumbers) {
|
||||
setResults(result.serialNumbers);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (waitingSearch.length > 0) {
|
||||
search(waitingSearch);
|
||||
}
|
||||
}
|
||||
|
||||
return () => closeSocket();
|
||||
}, [socket]);
|
||||
|
||||
useEffect(() => {
|
||||
if (socket === null && endpoints?.owgw) {
|
||||
setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <SearchBar t={t} search={search} results={results} history={history} />;
|
||||
};
|
||||
|
||||
export default DeviceSearchBar;
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CPopover, CProgress, CProgressBar } from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cleanBytesString } from 'utils/helper';
|
||||
|
||||
const MemoryBar = ({ usedBytes, totalBytes }) => {
|
||||
const { t } = useTranslation();
|
||||
const used = cleanBytesString(usedBytes);
|
||||
const total = cleanBytesString(totalBytes);
|
||||
const percentage = Math.floor((usedBytes / totalBytes) * 100);
|
||||
|
||||
return (
|
||||
<CPopover content={t('status.used_total_memory', { used, total })}>
|
||||
<CProgress>
|
||||
<CProgressBar value={percentage}>
|
||||
{percentage >= 25 ? t('status.percentage_used', { percentage, total }) : ''}
|
||||
</CProgressBar>
|
||||
<CProgressBar value={100 - percentage} color="transparent">
|
||||
<div style={{ color: 'black' }}>
|
||||
{percentage < 25
|
||||
? t('status.percentage_free', { percentage: 100 - percentage, total })
|
||||
: ''}
|
||||
</div>
|
||||
</CProgressBar>
|
||||
</CProgress>
|
||||
</CPopover>
|
||||
);
|
||||
};
|
||||
|
||||
MemoryBar.propTypes = {
|
||||
usedBytes: PropTypes.number.isRequired,
|
||||
totalBytes: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(MemoryBar);
|
||||
@@ -1,40 +1,40 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
CCard,
|
||||
CCardHeader,
|
||||
CRow,
|
||||
CCol,
|
||||
CCardBody,
|
||||
CBadge,
|
||||
CModalBody,
|
||||
CAlert,
|
||||
CPopover,
|
||||
CButton,
|
||||
CSpinner,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import { cilSync } from '@coreui/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { prettyDate, secondsToDetailed } from 'utils/helper';
|
||||
import MemoryBar from './MemoryBar';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
import { DeviceStatusCard as Card, useDevice, useAuth, useToast } from 'ucentral-libs';
|
||||
|
||||
const DeviceStatusCard = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const { addToast } = useToast();
|
||||
const [lastStats, setLastStats] = useState(null);
|
||||
const [status, setStatus] = useState(null);
|
||||
const [deviceConfig, setDeviceConfig] = useState(null);
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const transformLoad = (load) => {
|
||||
if (load === undefined) return t('common.na');
|
||||
return `${((load / 65536) * 100).toFixed(2)}%`;
|
||||
const getDevice = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`, options)
|
||||
.then((response) => {
|
||||
setDeviceConfig(response.data);
|
||||
})
|
||||
.catch((e) => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('device.error_fetching_device', { error: e.response?.data?.ErrorDescription }),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getData = () => {
|
||||
@@ -47,13 +47,13 @@ const DeviceStatusCard = () => {
|
||||
};
|
||||
|
||||
const lastStatsRequest = axiosInstance.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(
|
||||
deviceSerialNumber,
|
||||
)}/statistics?lastOnly=true`,
|
||||
options,
|
||||
);
|
||||
const statusRequest = axiosInstance.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/status`,
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/status`,
|
||||
options,
|
||||
);
|
||||
|
||||
@@ -70,128 +70,31 @@ const DeviceStatusCard = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
getData();
|
||||
getDevice();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setError(false);
|
||||
if (deviceSerialNumber) getData();
|
||||
if (deviceSerialNumber) {
|
||||
getDevice();
|
||||
getData();
|
||||
}
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
if (!error) {
|
||||
return (
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<div className="text-value-lg">
|
||||
{t('status.title', { serialNumber: deviceSerialNumber })}
|
||||
</div>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<div className={styles.alignRight}>
|
||||
<CPopover content={t('common.refresh')}>
|
||||
<CButton color="secondary" onClick={getData} size="sm">
|
||||
<CIcon content={cilSync} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
{(!lastStats || !status) && loading ? (
|
||||
<div className={styles.centerContainer}>
|
||||
<CSpinner className={styles.spinner} />
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div className={styles.overlayContainer} hidden={!loading}>
|
||||
<CSpinner className={styles.spinner} />
|
||||
</div>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.connection_status')} :</CCol>
|
||||
<CCol xs="10" md="7">
|
||||
{status?.connected ? (
|
||||
<CBadge color="success">{t('common.connected')}</CBadge>
|
||||
) : (
|
||||
<CBadge color="danger">{t('common.not_connected')}</CBadge>
|
||||
)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.uptime')} :</CCol>
|
||||
<CCol xs="10" md="7">
|
||||
{secondsToDetailed(
|
||||
lastStats?.unit?.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'),
|
||||
)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.last_contact')} :</CCol>
|
||||
<CCol xs="10" md="7">
|
||||
{prettyDate(status?.lastContact)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.localtime')} :</CCol>
|
||||
<CCol xs="10" md="7">
|
||||
{prettyDate(lastStats?.unit?.localtime)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.load_averages')} :</CCol>
|
||||
<CCol xs="10" md="7">
|
||||
{transformLoad(lastStats?.unit?.load[0])}
|
||||
{' / '}
|
||||
{transformLoad(lastStats?.unit?.load[1])}
|
||||
{' / '}
|
||||
{transformLoad(lastStats?.unit?.load[2])}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.memory')} :</CCol>
|
||||
<CCol xs="9" md="6" style={{ paddingTop: '5px' }}>
|
||||
<MemoryBar
|
||||
usedBytes={
|
||||
lastStats?.unit?.memory?.total && lastStats?.unit?.memory?.free
|
||||
? lastStats?.unit?.memory?.total - lastStats?.unit?.memory?.free
|
||||
: 0
|
||||
}
|
||||
totalBytes={lastStats?.unit?.memory?.total ?? 0}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
)}
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<div className="text-value-lg">
|
||||
{t('status.title', { serialNumber: deviceSerialNumber })}
|
||||
</div>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CModalBody>
|
||||
<CAlert hidden={!error} color="danger" className={styles.centerContainer}>
|
||||
{t('status.error')}
|
||||
</CAlert>
|
||||
</CModalBody>
|
||||
</CCard>
|
||||
<Card
|
||||
t={t}
|
||||
loading={loading}
|
||||
error={error}
|
||||
deviceSerialNumber={deviceSerialNumber}
|
||||
getData={refresh}
|
||||
deviceConfig={deviceConfig}
|
||||
status={status}
|
||||
lastStats={lastStats}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(DeviceStatusCard);
|
||||
export default DeviceStatusCard;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
.centerContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.overlayContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.spacedRow {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.alignRight {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
230
src/components/EditUserModal/index.js
Normal file
230
src/components/EditUserModal/index.js
Normal file
@@ -0,0 +1,230 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useUser, EditUserModal as Modal, useAuth, useToast } from 'ucentral-libs';
|
||||
|
||||
const initialState = {
|
||||
Id: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: false,
|
||||
},
|
||||
changePassword: {
|
||||
value: false,
|
||||
error: false,
|
||||
editable: false,
|
||||
},
|
||||
currentPassword: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: true,
|
||||
},
|
||||
email: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: false,
|
||||
},
|
||||
description: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: true,
|
||||
},
|
||||
name: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: true,
|
||||
},
|
||||
userRole: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: true,
|
||||
},
|
||||
notes: {
|
||||
value: [],
|
||||
editable: false,
|
||||
},
|
||||
};
|
||||
|
||||
const EditUserModal = ({ show, toggle, userId, getUsers }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { addToast } = useToast();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [initialUser, setInitialUser] = useState({});
|
||||
const [user, updateWithId, updateWithKey, setUser] = useUser(initialState);
|
||||
const [policies, setPolicies] = useState({
|
||||
passwordPolicy: '',
|
||||
passwordPattern: '',
|
||||
accessPolicy: '',
|
||||
});
|
||||
|
||||
const getPasswordPolicy = () => {
|
||||
axiosInstance
|
||||
.post(`${endpoints.owsec}/api/v1/oauth2?requirements=true`, {})
|
||||
.then((response) => {
|
||||
const newPolicies = response.data;
|
||||
newPolicies.accessPolicy = `${endpoints.owsec}${newPolicies.accessPolicy}`;
|
||||
newPolicies.passwordPolicy = `${endpoints.owsec}${newPolicies.passwordPolicy}`;
|
||||
setPolicies(response.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const getUser = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.owsec}/api/v1/user/${userId}`, options)
|
||||
.then((response) => {
|
||||
const newUser = {};
|
||||
|
||||
for (const key of Object.keys(response.data)) {
|
||||
if (key in initialState && key !== 'currentPassword') {
|
||||
newUser[key] = {
|
||||
...initialState[key],
|
||||
value: response.data[key],
|
||||
};
|
||||
}
|
||||
}
|
||||
setInitialUser({ ...initialState, ...newUser });
|
||||
setUser({ ...initialState, ...newUser });
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const updateUser = () => {
|
||||
setLoading(true);
|
||||
|
||||
const parameters = {
|
||||
id: userId,
|
||||
};
|
||||
|
||||
let newData = false;
|
||||
|
||||
for (const key of Object.keys(user)) {
|
||||
if (user[key].editable && user[key].value !== initialUser[key].value) {
|
||||
if (key === 'currentPassword' && user[key].length < 8) {
|
||||
updateWithKey('currentPassword', {
|
||||
error: true,
|
||||
});
|
||||
newData = false;
|
||||
break;
|
||||
} else if (key === 'changePassword') {
|
||||
parameters[key] = user[key].value === 'on';
|
||||
newData = true;
|
||||
} else {
|
||||
parameters[key] = user[key].value;
|
||||
newData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newData) {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.put(`${endpoints.owsec}/api/v1/user/${userId}`, parameters, options)
|
||||
.then(() => {
|
||||
addToast({
|
||||
title: t('user.update_success_title'),
|
||||
body: t('user.update_success'),
|
||||
color: 'success',
|
||||
autohide: true,
|
||||
});
|
||||
getUsers();
|
||||
toggle();
|
||||
})
|
||||
.catch((e) => {
|
||||
addToast({
|
||||
title: t('user.update_failure_title'),
|
||||
body: t('user.update_failure', { error: e.response?.data?.ErrorDescription }),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
getUser();
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
addToast({
|
||||
title: t('user.update_success_title'),
|
||||
body: t('user.update_success'),
|
||||
color: 'success',
|
||||
autohide: true,
|
||||
});
|
||||
getUsers();
|
||||
toggle();
|
||||
}
|
||||
};
|
||||
|
||||
const addNote = (currentNote) => {
|
||||
setLoading(true);
|
||||
|
||||
const parameters = {
|
||||
id: userId,
|
||||
notes: [{ note: currentNote }],
|
||||
};
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.put(`${endpoints.owsec}/api/v1/user/${userId}`, parameters, options)
|
||||
.then(() => {
|
||||
getUser();
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (userId) {
|
||||
getUser();
|
||||
}
|
||||
if (policies.passwordPattern.length === 0) {
|
||||
getPasswordPolicy();
|
||||
}
|
||||
}, [userId]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
t={t}
|
||||
user={user}
|
||||
updateUserWithId={updateWithId}
|
||||
saveUser={updateUser}
|
||||
loading={loading}
|
||||
policies={policies}
|
||||
show={show}
|
||||
toggle={toggle}
|
||||
addNote={addNote}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
EditUserModal.propTypes = {
|
||||
userId: PropTypes.string.isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
getUsers: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(EditUserModal);
|
||||
60
src/components/EventQueueModal/index.js
Normal file
60
src/components/EventQueueModal/index.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EventQueueModal as Modal, useAuth, useDevice, useToast } from 'ucentral-libs';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
|
||||
const EventQueueModal = ({ show, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const { addToast } = useToast();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [result, setResult] = useState({});
|
||||
|
||||
const getQueue = () => {
|
||||
setLoading(true);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
const parameters = {
|
||||
serialNumber: deviceSerialNumber,
|
||||
types: ['dhcp', 'wifi'],
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(`${endpoints.owgw}/api/v1/device/${deviceSerialNumber}/eventqueue`, parameters, options)
|
||||
.then((response) => {
|
||||
setResult(response.data);
|
||||
})
|
||||
.catch((e) => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('commands.unable_queue', { error: e.response?.data?.ErrorDescription }),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (show) getQueue();
|
||||
}, [show]);
|
||||
|
||||
return <Modal t={t} show={show} toggle={toggle} loading={loading} result={result} />;
|
||||
};
|
||||
|
||||
EventQueueModal.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default EventQueueModal;
|
||||
@@ -10,16 +10,17 @@ import {
|
||||
CForm,
|
||||
CSwitch,
|
||||
CAlert,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import 'react-widgets/styles.css';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import { useAuth, useDevice } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const ConfigureModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -66,7 +67,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
|
||||
axiosInstance
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/factory`,
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/factory`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
@@ -86,8 +87,15 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggleModal}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('factory_reset.title')}</CModalTitle>
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="pl-1 pt-1">{t('factory_reset.title')}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
{hadSuccess ? (
|
||||
<SuccessfulActionModalBody toggleModal={toggleModal} />
|
||||
@@ -95,9 +103,9 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
<div>
|
||||
<CModalBody>
|
||||
<CAlert color="danger">{t('factory_reset.warning')}</CAlert>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<p className={styles.spacedForm}>{t('factory_reset.redirector')}</p>
|
||||
<CForm className={styles.spacedForm}>
|
||||
<CRow className="mt-3">
|
||||
<p className="pl-4">{t('factory_reset.redirector')}</p>
|
||||
<CForm className="pl-4">
|
||||
<CSwitch
|
||||
color="primary"
|
||||
defaultChecked={keepRedirector}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedForm {
|
||||
padding-left: 5%;
|
||||
}
|
||||
71
src/components/FirmwareHistoryModal/index.js
Normal file
71
src/components/FirmwareHistoryModal/index.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import {
|
||||
CButton,
|
||||
CModal,
|
||||
CModalBody,
|
||||
CModalHeader,
|
||||
CModalFooter,
|
||||
CModalTitle,
|
||||
} from '@coreui/react';
|
||||
import { FirmwareHistoryTable, useAuth } from 'ucentral-libs';
|
||||
|
||||
const FirmwareHistoryModal = ({ serialNumber, show, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState([]);
|
||||
|
||||
const getHistory = () => {
|
||||
setLoading(true);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.owfms}/api/v1/revisionHistory/${serialNumber}`, options)
|
||||
.then((response) => setData(response.data.history ?? []))
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
getHistory();
|
||||
} else {
|
||||
setData([]);
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<CModal size="xl" show={show} onClose={toggle} scrollable>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle className="pl-1 pt-1">
|
||||
#{serialNumber} {t('firmware.history_title')}
|
||||
</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<FirmwareHistoryTable t={t} loading={loading} data={data} />
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" onClick={toggle}>
|
||||
{t('common.close')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
FirmwareHistoryModal.propTypes = {
|
||||
serialNumber: PropTypes.string.isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default FirmwareHistoryModal;
|
||||
@@ -1,74 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CButton, CSpinner, CModalFooter } from '@coreui/react';
|
||||
|
||||
const UpgradeFooter = ({
|
||||
isNow,
|
||||
isShown,
|
||||
isLoading,
|
||||
action,
|
||||
color,
|
||||
variant,
|
||||
block,
|
||||
toggleParent,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [askingIfSure, setAskingIfSure] = useState(false);
|
||||
|
||||
const confirmingIfSure = () => {
|
||||
setAskingIfSure(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setAskingIfSure(false);
|
||||
}, [isShown]);
|
||||
|
||||
return (
|
||||
<CModalFooter>
|
||||
<div hidden={!askingIfSure}>{t('common.are_you_sure')}</div>
|
||||
<CButton
|
||||
disabled={isLoading}
|
||||
hidden={askingIfSure}
|
||||
color={color}
|
||||
variant={variant}
|
||||
onClick={() => confirmingIfSure()}
|
||||
block={block}
|
||||
>
|
||||
{isNow ? t('upgrade.upgrade') : t('common.schedule')}
|
||||
</CButton>
|
||||
<CButton
|
||||
disabled={isLoading}
|
||||
hidden={!askingIfSure}
|
||||
color={color}
|
||||
onClick={() => action()}
|
||||
block={block}
|
||||
>
|
||||
{isLoading ? t('common.loading_ellipsis') : t('common.yes')}
|
||||
<CSpinner color="light" hidden={!isLoading} component="span" size="sm" />
|
||||
</CButton>
|
||||
<CButton color="secondary" onClick={toggleParent}>
|
||||
{t('common.cancel')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
);
|
||||
};
|
||||
|
||||
UpgradeFooter.propTypes = {
|
||||
isNow: PropTypes.bool.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
block: PropTypes.bool,
|
||||
action: PropTypes.func.isRequired,
|
||||
color: PropTypes.string,
|
||||
variant: PropTypes.string,
|
||||
toggleParent: PropTypes.func.isRequired,
|
||||
isShown: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
UpgradeFooter.defaultProps = {
|
||||
color: 'primary',
|
||||
variant: '',
|
||||
block: false,
|
||||
};
|
||||
|
||||
export default UpgradeFooter;
|
||||
@@ -1,102 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CModalBody } from '@coreui/react';
|
||||
import { v4 as createUuid } from 'uuid';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
|
||||
const UpgradeWaitingBody = ({ serialNumber }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [secondsElapsed, setSecondsElapsed] = useState(0);
|
||||
const [labelsToShow, setLabelsToShow] = useState(['upgrade.command_submitted']);
|
||||
|
||||
const getDeviceConnection = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}/status`,
|
||||
options,
|
||||
)
|
||||
.then((response) => response.data.connected)
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const getFirmwareVersion = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}`, options)
|
||||
.then((response) => response.data.firmware)
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const refreshStep = () => {
|
||||
if (currentStep === 0 && !getDeviceConnection) {
|
||||
const labelsToAdd = [
|
||||
t('upgrade.device_disconnected'),
|
||||
t('upgrade.device_upgrading_firmware'),
|
||||
t('upgrade.waiting_for_device'),
|
||||
];
|
||||
setLabelsToShow([...labelsToShow, ...labelsToAdd]);
|
||||
setCurrentStep(1);
|
||||
} else if (currentStep === 1 && getDeviceConnection()) {
|
||||
const newFirmware = `: ${getFirmwareVersion()}`;
|
||||
const labelsToAdd = [
|
||||
t('upgrade.device_reconnected'),
|
||||
`${t('upgrade.new_version')}: ${newFirmware}`,
|
||||
];
|
||||
setLabelsToShow([...labelsToShow, ...labelsToAdd]);
|
||||
setCurrentStep(2);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const refreshIntervalId = setInterval(() => {
|
||||
refreshStep();
|
||||
}, 5000);
|
||||
|
||||
const timerIntervalId = setInterval(() => {
|
||||
setSecondsElapsed(secondsElapsed + 1);
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(refreshIntervalId);
|
||||
clearInterval(timerIntervalId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CModalBody>
|
||||
<div className="consoleBox">
|
||||
{labelsToShow.map((label) => (
|
||||
<p key={createUuid()}>
|
||||
{new Date().toString()}:{label}
|
||||
</p>
|
||||
))}
|
||||
<p>
|
||||
{t('common.seconds_elapsed')}:{secondsElapsed}
|
||||
</p>
|
||||
</div>
|
||||
</CModalBody>
|
||||
);
|
||||
};
|
||||
|
||||
UpgradeWaitingBody.propTypes = {
|
||||
serialNumber: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default UpgradeWaitingBody;
|
||||
@@ -1,247 +0,0 @@
|
||||
import {
|
||||
CButton,
|
||||
CModal,
|
||||
CModalHeader,
|
||||
CModalTitle,
|
||||
CModalBody,
|
||||
CSwitch,
|
||||
CCol,
|
||||
CRow,
|
||||
CInput,
|
||||
CInvalidFeedback,
|
||||
CModalFooter,
|
||||
} from '@coreui/react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import PropTypes from 'prop-types';
|
||||
import { dateToUnix } from 'utils/helper';
|
||||
import 'react-widgets/styles.css';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import getDeviceConnection from 'utils/deviceHelper';
|
||||
import ButtonFooter from './UpgradeFooter';
|
||||
import styles from './index.module.scss';
|
||||
import UpgradeWaitingBody from './UpgradeWaitingBody';
|
||||
|
||||
const FirmwareUpgradeModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [isNow, setIsNow] = useState(true);
|
||||
const [waitForUpgrade, setWaitForUpgrade] = useState(false);
|
||||
const [date, setDate] = useState(new Date().toString());
|
||||
const [firmware, setFirmware] = useState('');
|
||||
const [validFirmware, setValidFirmware] = useState(true);
|
||||
const [validDate, setValidDate] = useState(true);
|
||||
const [blockFields, setBlockFields] = useState(false);
|
||||
const [disabledWaiting, setDisableWaiting] = useState(false);
|
||||
const [waitingForUpgrade, setWaitingForUpgrade] = useState(false);
|
||||
const [showWaitingConsole, setShowWaitingConsole] = useState(false);
|
||||
const [deviceConnected, setDeviceConnected] = useState(true);
|
||||
|
||||
const toggleNow = () => {
|
||||
if (isNow) {
|
||||
setWaitForUpgrade(false);
|
||||
setDisableWaiting(true);
|
||||
} else {
|
||||
setDisableWaiting(false);
|
||||
}
|
||||
|
||||
setIsNow(!isNow);
|
||||
};
|
||||
|
||||
const toggleWaitForUpgrade = () => {
|
||||
setWaitForUpgrade(waitForUpgrade);
|
||||
};
|
||||
|
||||
const formValidation = () => {
|
||||
let valid = true;
|
||||
if (firmware.trim() === '') {
|
||||
setValidFirmware(false);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!isNow && date.trim() === '') {
|
||||
setValidDate(false);
|
||||
valid = false;
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setBlockFields(false);
|
||||
setShowWaitingConsole(false);
|
||||
}, [show]);
|
||||
|
||||
useEffect(() => {
|
||||
setValidFirmware(true);
|
||||
setValidDate(true);
|
||||
}, [firmware, date]);
|
||||
|
||||
useEffect(() => {
|
||||
if (deviceSerialNumber !== null && show) {
|
||||
const asyncGet = async () => {
|
||||
const isConnected = await getDeviceConnection(
|
||||
deviceSerialNumber,
|
||||
currentToken,
|
||||
endpoints.ucentralgw,
|
||||
);
|
||||
setDisableWaiting(!isConnected);
|
||||
setDeviceConnected(isConnected);
|
||||
};
|
||||
asyncGet();
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
const postUpgrade = () => {
|
||||
if (formValidation()) {
|
||||
setWaitingForUpgrade(true);
|
||||
setBlockFields(true);
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
serialNumber: deviceSerialNumber,
|
||||
};
|
||||
|
||||
const parameters = {
|
||||
serialNumber: deviceSerialNumber,
|
||||
when: isNow ? 0 : dateToUnix(date),
|
||||
uri: firmware,
|
||||
};
|
||||
axiosInstance
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/upgrade`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then(() => {
|
||||
if (waitForUpgrade) {
|
||||
setShowWaitingConsole(true);
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setBlockFields(false);
|
||||
setWaitingForUpgrade(false);
|
||||
eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (showWaitingConsole) {
|
||||
return (
|
||||
<CModal show={show} onClose={toggleModal}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('upgrade.title')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<UpgradeWaitingBody serialNumber={deviceSerialNumber} />
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" onClick={toggleModal}>
|
||||
{t('common.close')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggleModal}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('upgrade.title')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<h6>{t('upgrade.directions')}</h6>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedColumn}>
|
||||
<p>{t('upgrade.firmware_uri')}</p>
|
||||
</CCol>
|
||||
<CCol md="8">
|
||||
<CInput
|
||||
disabled={blockFields}
|
||||
className={('form-control', { 'is-invalid': !validFirmware })}
|
||||
type="text"
|
||||
id="uri"
|
||||
name="uri-input"
|
||||
autoComplete="firmware-uri"
|
||||
onChange={(event) => setFirmware(event.target.value)}
|
||||
value={firmware}
|
||||
/>
|
||||
<CInvalidFeedback>{t('upgrade.need_uri')}</CInvalidFeedback>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="8">
|
||||
<p className={styles.spacedText}>{t('common.execute_now')}</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CSwitch
|
||||
disabled={blockFields}
|
||||
color="primary"
|
||||
defaultChecked={isNow}
|
||||
onClick={toggleNow}
|
||||
labelOn={t('common.yes')}
|
||||
labelOff={t('common.no')}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow} hidden={isNow}>
|
||||
<CCol md="4" className={styles.spacedColumn}>
|
||||
<p>{t('upgrade.time')}</p>
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
<DatePicker
|
||||
selected={new Date(date)}
|
||||
value={new Date(date)}
|
||||
className={('form-control', { 'is-invalid': !validDate })}
|
||||
includeTime
|
||||
disabled={blockFields}
|
||||
onChange={(newDate) => setDate(newDate.toString())}
|
||||
/>
|
||||
<CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow
|
||||
className={styles.spacedRow}
|
||||
hidden={true || !isNow || disabledWaiting || !deviceConnected}
|
||||
>
|
||||
<CCol md="8">
|
||||
<p className={styles.spacedText}>
|
||||
{t('upgrade.wait_for_upgrade')}
|
||||
<b hidden={!disabledWaiting}> {t('upgrade.offline_device')}</b>
|
||||
</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CSwitch
|
||||
disabled={blockFields || disabledWaiting}
|
||||
color="primary"
|
||||
defaultChecked={waitForUpgrade}
|
||||
onClick={toggleWaitForUpgrade}
|
||||
labelOn={t('common.yes')}
|
||||
labelOff={t('common.no')}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CModalBody>
|
||||
<ButtonFooter
|
||||
isNow={isNow}
|
||||
isShown={show}
|
||||
isLoading={waitingForUpgrade}
|
||||
action={postUpgrade}
|
||||
color="primary"
|
||||
toggleParent={toggleModal}
|
||||
/>
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
FirmwareUpgradeModal.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggleModal: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default FirmwareUpgradeModal;
|
||||
@@ -1,7 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedColumn {
|
||||
margin-top: 7px;
|
||||
}
|
||||
@@ -1,18 +1,11 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
CButton,
|
||||
CModal,
|
||||
CModalHeader,
|
||||
CModalBody,
|
||||
CModalTitle,
|
||||
CModalFooter,
|
||||
} from '@coreui/react';
|
||||
import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import styles from './index.module.scss';
|
||||
import { useAuth, useDevice } from 'ucentral-libs';
|
||||
|
||||
const LatestStatisticsModal = ({ show, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -30,7 +23,7 @@ const LatestStatisticsModal = ({ show, toggle }) => {
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?lastOnly=true`,
|
||||
`${endpoints.owgw}/api/v1/device/${deviceSerialNumber}/statistics?lastOnly=true`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
@@ -47,17 +40,19 @@ const LatestStatisticsModal = ({ show, toggle }) => {
|
||||
|
||||
return (
|
||||
<CModal size="lg" show={show} onClose={toggle}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle className={styles.modalTitle}>{t('statistics.latest_statistics')}</CModalTitle>
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="text-dark">{t('statistics.latest_statistics')}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<pre className="ignore">{JSON.stringify(latestStats, null, 4)}</pre>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" onClick={toggle}>
|
||||
{t('common.close')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,8 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as createUuid } from 'uuid';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import { useAuth, useDevice } from 'ucentral-libs';
|
||||
import { unixToTime, capitalizeFirstLetter } from 'utils/helper';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import DeviceStatisticsChart from './DeviceStatisticsChart';
|
||||
@@ -129,7 +128,7 @@ const StatisticsChartList = () => {
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?newest=true&limit=50`,
|
||||
`${endpoints.owgw}/api/v1/device/${deviceSerialNumber}/statistics?newest=true&limit=50`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
CDropdown,
|
||||
CDropdownToggle,
|
||||
CDropdownMenu,
|
||||
CDropdownItem,
|
||||
CCard,
|
||||
CCardHeader,
|
||||
CCardBody,
|
||||
CRow,
|
||||
CCol,
|
||||
} from '@coreui/react';
|
||||
import { cilOptions } from '@coreui/icons';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { CCard, CCardHeader, CCardBody, CPopover, CButton } from '@coreui/react';
|
||||
import { cilSync } from '@coreui/icons';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import LifetimeStatsmodal from 'components/LifetimeStatsModal';
|
||||
import StatisticsChartList from './StatisticsChartList';
|
||||
import LatestStatisticsModal from './LatestStatisticsModal';
|
||||
import styles from './index.module.scss';
|
||||
import LatestStatisticsmodal from './LatestStatisticsModal';
|
||||
|
||||
const DeviceStatisticsCard = () => {
|
||||
const history = useHistory();
|
||||
const { deviceId } = useParams();
|
||||
const { t } = useTranslation();
|
||||
const [showLatestModal, setShowLatestModal] = useState(false);
|
||||
const [showLifetimeModal, setShowLifetimeModal] = useState(false);
|
||||
|
||||
const toggleLatestModal = () => {
|
||||
setShowLatestModal(!showLatestModal);
|
||||
};
|
||||
|
||||
const toggleLifetimeModal = () => {
|
||||
setShowLifetimeModal(!showLifetimeModal);
|
||||
};
|
||||
|
||||
const goToAnalysis = () => {
|
||||
history.push(`/devices/${deviceId}/wifianalysis`);
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
eventBus.dispatch('refreshInterfaceStatistics', { message: 'Refresh interface statistics' });
|
||||
};
|
||||
@@ -34,32 +36,38 @@ const DeviceStatisticsCard = () => {
|
||||
<div>
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<div className={['text-value-lg', styles.cardTitle].join(' ')}>
|
||||
{t('statistics.title')}
|
||||
</div>
|
||||
</CCol>
|
||||
<CCol className={styles.cardOptions}>
|
||||
<CDropdown className="m-1 btn-group">
|
||||
<CDropdownToggle>
|
||||
<CIcon name="cil-options" content={cilOptions} size="lg" color="primary" />
|
||||
</CDropdownToggle>
|
||||
<CDropdownMenu>
|
||||
<CDropdownItem onClick={refresh}>{t('common.refresh')}</CDropdownItem>
|
||||
<CDropdownItem onClick={toggleLatestModal}>
|
||||
{t('statistics.show_latest')}
|
||||
</CDropdownItem>
|
||||
</CDropdownMenu>
|
||||
</CDropdown>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<div className="d-flex flex-row-reverse align-items-center">
|
||||
<div className="pl-2">
|
||||
<CPopover content={t('common.refresh')}>
|
||||
<CButton color="primary" variant="outline" onClick={refresh}>
|
||||
<CIcon content={cilSync} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
<div className="pl-2">
|
||||
<CButton color="primary" variant="outline" onClick={toggleLifetimeModal}>
|
||||
Lifetime Statistics
|
||||
</CButton>
|
||||
</div>
|
||||
<div className="pl-2">
|
||||
<CButton color="primary" variant="outline" onClick={toggleLatestModal}>
|
||||
{t('statistics.show_latest')}
|
||||
</CButton>
|
||||
</div>
|
||||
<div>
|
||||
<CButton color="primary" variant="outline" onClick={goToAnalysis}>
|
||||
{t('wifi_analysis.title')}
|
||||
</CButton>
|
||||
</div>
|
||||
<div className="text-value-lg mr-auto">{t('statistics.title')}</div>
|
||||
</div>
|
||||
</CCardHeader>
|
||||
<CCardBody className={styles.statsBody}>
|
||||
<CCardBody className="p-5">
|
||||
<StatisticsChartList />
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
<LatestStatisticsModal show={showLatestModal} toggle={toggleLatestModal} />
|
||||
<LatestStatisticsmodal show={showLatestModal} toggle={toggleLatestModal} />
|
||||
<LifetimeStatsmodal show={showLifetimeModal} toggle={toggleLifetimeModal} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
.cardOptions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.statsBody {
|
||||
padding: 5%;
|
||||
}
|
||||
|
||||
.modalTitle {
|
||||
color: black;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CSelect } from '@coreui/react';
|
||||
|
||||
const LanguageSwitcher = () => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<CSelect
|
||||
custom
|
||||
defaultValue={i18n.language.split('-')[0]}
|
||||
onChange={(e) => i18n.changeLanguage(e.target.value)}
|
||||
>
|
||||
<option value="de">Deutsche</option>
|
||||
<option value="es">Español</option>
|
||||
<option value="en">English</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="pt">Portugues</option>
|
||||
</CSelect>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSwitcher;
|
||||
48
src/components/LifetimeStatsModal/index.js
Normal file
48
src/components/LifetimeStatsModal/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LifetimeStatsModal as Modal, useAuth, useDevice } from 'ucentral-libs';
|
||||
|
||||
const LifetimeStatsModal = ({ show, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState({});
|
||||
|
||||
const getData = () => {
|
||||
setLoading(true);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.owgw}/api/v1/device/${deviceSerialNumber}/statistics?lifetime=true`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
setData(response.data);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (show) getData();
|
||||
}, [show]);
|
||||
|
||||
return <Modal t={t} loading={loading} show={show} toggle={toggle} data={data} />;
|
||||
};
|
||||
|
||||
LifetimeStatsModal.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default LifetimeStatsModal;
|
||||
@@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CButton, CSpinner } from '@coreui/react';
|
||||
|
||||
const LoadingButton = ({
|
||||
isLoading,
|
||||
label,
|
||||
isLoadingLabel,
|
||||
action,
|
||||
color,
|
||||
variant,
|
||||
block,
|
||||
disabled,
|
||||
}) => (
|
||||
<CButton
|
||||
variant={variant}
|
||||
color={color}
|
||||
onClick={action}
|
||||
block={block}
|
||||
disabled={isLoading || disabled}
|
||||
>
|
||||
{isLoading ? isLoadingLabel : label}
|
||||
<CSpinner hidden={!isLoading} color="light" component="span" size="sm" />
|
||||
</CButton>
|
||||
);
|
||||
|
||||
LoadingButton.propTypes = {
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
block: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
label: PropTypes.string.isRequired,
|
||||
isLoadingLabel: PropTypes.string.isRequired,
|
||||
action: PropTypes.func.isRequired,
|
||||
color: PropTypes.string,
|
||||
variant: PropTypes.string,
|
||||
};
|
||||
|
||||
LoadingButton.defaultProps = {
|
||||
color: 'primary',
|
||||
variant: '',
|
||||
block: true,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
export default LoadingButton;
|
||||
34
src/components/NetworkDiagram/dagreAdapter.js
Normal file
34
src/components/NetworkDiagram/dagreAdapter.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import dagre from 'dagre';
|
||||
import { isNode } from 'react-flow-renderer';
|
||||
|
||||
const setupDag = (elements, nodeWidth, nodeHeight) => {
|
||||
const dagreGraph = new dagre.graphlib.Graph();
|
||||
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
||||
dagreGraph.setGraph({ rankdir: 'TB' });
|
||||
|
||||
elements.forEach((el) => {
|
||||
if (isNode(el)) {
|
||||
dagreGraph.setNode(el.id, { width: nodeWidth, height: nodeHeight });
|
||||
} else {
|
||||
dagreGraph.setEdge(el.source, el.target);
|
||||
}
|
||||
});
|
||||
|
||||
dagre.layout(dagreGraph);
|
||||
|
||||
return elements.map((el) => {
|
||||
const newElement = el;
|
||||
if (isNode(newElement)) {
|
||||
const nodeWithPosition = dagreGraph.node(newElement.id);
|
||||
newElement.targetPosition = 'top';
|
||||
newElement.sourcePosition = 'bottom';
|
||||
newElement.position = {
|
||||
x: nodeWithPosition.x - nodeWidth / 2 + Math.random() / 1000,
|
||||
y: nodeWithPosition.y - nodeHeight / 2,
|
||||
};
|
||||
}
|
||||
return newElement;
|
||||
});
|
||||
};
|
||||
|
||||
export default setupDag;
|
||||
162
src/components/NetworkDiagram/index.js
Normal file
162
src/components/NetworkDiagram/index.js
Normal file
@@ -0,0 +1,162 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CRow, CCol } from '@coreui/react';
|
||||
import { NetworkDiagram as Graph } from 'ucentral-libs';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import createLayoutedElements from './dagreAdapter';
|
||||
|
||||
const associationStyle = {
|
||||
background: '#3399ff',
|
||||
color: 'white',
|
||||
border: '1px solid #777',
|
||||
width: 220,
|
||||
padding: 10,
|
||||
};
|
||||
|
||||
const recognizedRadioStyle = {
|
||||
background: '#2eb85c',
|
||||
color: 'white',
|
||||
width: 220,
|
||||
padding: 15,
|
||||
};
|
||||
|
||||
const unrecognizedRadioStyle = {
|
||||
background: '#e55353',
|
||||
color: 'white',
|
||||
width: 220,
|
||||
padding: 15,
|
||||
};
|
||||
|
||||
const recognizedRadioNode = (radio) => (
|
||||
<div className="align-middle">
|
||||
<h6 className="align-middle mb-0">
|
||||
Radio #{radio.radio} ({radio.channel < 16 ? '2G' : '5G'})
|
||||
</h6>
|
||||
</div>
|
||||
);
|
||||
|
||||
const unrecognizedRadioNode = (t, radio) => (
|
||||
<div className="align-middle">
|
||||
<h6 className="align-middle mb-0">
|
||||
Radio #{radio.radioIndex} ({t('common.unrecognized')})
|
||||
</h6>
|
||||
</div>
|
||||
);
|
||||
|
||||
const associationNode = (associationInfo) => (
|
||||
<div>
|
||||
<CRow>
|
||||
<CCol className="text-center">
|
||||
<h6>{associationInfo.bssid}</h6>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol className="text-left pl-4">Rx Rate : {associationInfo.rxRate}</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol className="text-left pl-4">Tx Rate : {associationInfo.txRate}</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
const NetworkDiagram = ({ show, radios, associations }) => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [elements, setElements] = useState([]);
|
||||
|
||||
const getX = (associationsAdded) => {
|
||||
if (associationsAdded === 0) return 0;
|
||||
if ((associationsAdded + 1) % 2 === 0) return -140 * (associationsAdded + 1);
|
||||
return 140 * associationsAdded;
|
||||
};
|
||||
|
||||
const parseData = () => {
|
||||
setLoading(true);
|
||||
const newElements = [];
|
||||
const radiosAdded = {};
|
||||
|
||||
// Creating the radio nodes
|
||||
for (const radio of radios) {
|
||||
if (radiosAdded[radio.radio] === undefined) {
|
||||
newElements.push({
|
||||
id: `r-${radio.radio}`,
|
||||
data: { label: recognizedRadioNode(radio) },
|
||||
position: { x: 0, y: 200 * radio.radio },
|
||||
type: 'input',
|
||||
style: recognizedRadioStyle,
|
||||
});
|
||||
radiosAdded[radio.radio] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Creating the association nodes and their edges
|
||||
for (let i = 0; i < associations.length; i += 1) {
|
||||
const assoc = associations[i];
|
||||
|
||||
// If the radio has not been added, we create a new unknown radio based on its index
|
||||
if (radiosAdded[assoc.radio.radioIndex] === undefined) {
|
||||
newElements.push({
|
||||
id: `r-${assoc.radio.radioIndex}`,
|
||||
data: { label: unrecognizedRadioNode(t, assoc.radio) },
|
||||
position: { x: 0, y: 200 * assoc.radio.radioIndex },
|
||||
type: 'input',
|
||||
style: unrecognizedRadioStyle,
|
||||
});
|
||||
radiosAdded[assoc.radio.radioIndex] = 0;
|
||||
}
|
||||
|
||||
// Adding the association
|
||||
newElements.push({
|
||||
id: `a-${assoc.bssid}`,
|
||||
data: { label: associationNode(assoc) },
|
||||
position: {
|
||||
x: getX(radiosAdded[assoc.radio.radioIndex]),
|
||||
y: 80 + 240 * assoc.radio.radioIndex,
|
||||
},
|
||||
style: associationStyle,
|
||||
type: 'output',
|
||||
});
|
||||
radiosAdded[assoc.radio.radioIndex] += 1;
|
||||
|
||||
// Creating the edge
|
||||
newElements.push({
|
||||
id: `e-${assoc.radio.radioIndex}-${assoc.bssid}`,
|
||||
source: `r-${assoc.radio.radioIndex}`,
|
||||
target: `a-${assoc.bssid}`,
|
||||
arrowHeadType: 'arrowclosed',
|
||||
});
|
||||
}
|
||||
|
||||
setElements(newElements);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (radios !== null && associations !== null) {
|
||||
parseData();
|
||||
}
|
||||
}, [radios, associations]);
|
||||
|
||||
return (
|
||||
<Graph
|
||||
show={show}
|
||||
loading={loading}
|
||||
elements={createLayoutedElements(elements, 220, 80)}
|
||||
setElements={setElements}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
NetworkDiagram.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
radios: PropTypes.instanceOf(Array),
|
||||
associations: PropTypes.instanceOf(Array),
|
||||
};
|
||||
|
||||
NetworkDiagram.defaultProps = {
|
||||
show: true,
|
||||
radios: null,
|
||||
associations: null,
|
||||
};
|
||||
|
||||
export default NetworkDiagram;
|
||||
@@ -8,20 +8,20 @@ import {
|
||||
CSwitch,
|
||||
CCol,
|
||||
CRow,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import PropTypes from 'prop-types';
|
||||
import { dateToUnix } from 'utils/helper';
|
||||
import 'react-widgets/styles.css';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const ActionModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -67,7 +67,7 @@ const ActionModal = ({ show, toggleModal }) => {
|
||||
|
||||
axiosInstance
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/reboot`,
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/reboot`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
@@ -85,8 +85,15 @@ const ActionModal = ({ show, toggleModal }) => {
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggleModal}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('reboot.title')}</CModalTitle>
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="pl-1 pt-1">{t('reboot.title')}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
{result === 'success' ? (
|
||||
<SuccessfulActionModalBody toggleModal={toggleModal} />
|
||||
@@ -108,8 +115,8 @@ const ActionModal = ({ show, toggleModal }) => {
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow hidden={isNow} className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedDate}>
|
||||
<CRow hidden={isNow} className="mt-2">
|
||||
<CCol md="4" className="pt-2">
|
||||
<p>{t('common.custom_date')}:</p>
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedColumn {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.spacedDate {
|
||||
padding-top: 5px;
|
||||
}
|
||||
222
src/components/TelemetryModal/index.js
Normal file
222
src/components/TelemetryModal/index.js
Normal file
@@ -0,0 +1,222 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import Select from 'react-select';
|
||||
import {
|
||||
CModal,
|
||||
CModalHeader,
|
||||
CModalTitle,
|
||||
CModalBody,
|
||||
CButton,
|
||||
CPopover,
|
||||
CRow,
|
||||
CCol,
|
||||
CInput,
|
||||
CSpinner,
|
||||
CAlert,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import { useDevice, useAuth, useToast } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { checkIfJson } from 'utils/helper';
|
||||
|
||||
const typeOptions = [
|
||||
{ value: 'wifi-frames', label: 'wifi-frames' },
|
||||
{ value: 'dhcp-snooping', label: 'dhcp-snooping' },
|
||||
];
|
||||
|
||||
const TelemetryModal = ({ show, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const { addToast } = useToast();
|
||||
const [socket, setSocket] = useState(null);
|
||||
const [lastMessage, setLastMessage] = useState({});
|
||||
const [receivedMessages, setReceivedMessages] = useState(0);
|
||||
const [types, setTypes] = useState([]);
|
||||
const [interval, setInterval] = useState(3);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [lastUpdate, setLastUpdate] = useState('');
|
||||
|
||||
const onIntervalChange = (e) => setInterval(e.target.value);
|
||||
|
||||
const closeSocket = () => {
|
||||
if (socket !== null) {
|
||||
socket.close();
|
||||
setSocket(null);
|
||||
}
|
||||
};
|
||||
|
||||
const getUrl = () => {
|
||||
setLastUpdate('');
|
||||
setLastMessage({});
|
||||
setLoading(true);
|
||||
|
||||
const parameters = {
|
||||
serialNumber: deviceSerialNumber,
|
||||
interval: parseInt(interval, 10),
|
||||
types: types.map((type) => type.value),
|
||||
};
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/telemetry`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data.uri && response.data.uri !== '') {
|
||||
setReceivedMessages(0);
|
||||
setSocket(new WebSocket(response.data.uri));
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('telemetry.connection_failed', { error: e.response?.data?.ErrorDescription }),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (socket !== null) {
|
||||
socket.onopen = () => {
|
||||
socket.send(`token:${currentToken}`);
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
if (checkIfJson(event.data)) {
|
||||
const result = JSON.parse(event.data);
|
||||
setLastMessage(result);
|
||||
setLastUpdate(new Date().toLocaleString());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return () => closeSocket();
|
||||
}, [socket]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!show && socket !== null) {
|
||||
closeSocket();
|
||||
}
|
||||
}, [show, socket]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastMessage !== {}) setReceivedMessages(receivedMessages + 1);
|
||||
}, [lastMessage]);
|
||||
|
||||
return (
|
||||
<CModal className="text-dark" size="lg" show={show} onClose={toggle}>
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="pl-1 pt-1">{t('actions.telemetry')}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
<CModalBody className="px-5">
|
||||
{socket === null ? (
|
||||
<div>
|
||||
<CRow>
|
||||
<CCol>{`${t('telemetry.interval')}: ${interval} ${t('common.seconds')}`}</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<CInput
|
||||
type="range"
|
||||
min="1"
|
||||
max="120"
|
||||
step="1"
|
||||
onChange={onIntervalChange}
|
||||
value={interval}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol sm="2" className="pt-2">
|
||||
{t('telemetry.types')}:
|
||||
</CCol>
|
||||
<CCol sm="6">
|
||||
<Select
|
||||
isMulti
|
||||
closeMenuOnSelect={false}
|
||||
name="Device Types"
|
||||
options={typeOptions}
|
||||
onChange={setTypes}
|
||||
value={types}
|
||||
className="basic-multi-select"
|
||||
classNamePrefix="select"
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol className="text-center p-4">
|
||||
<CButton color="primary" onClick={getUrl} disabled={loading || types.length === 0}>
|
||||
Start Telemetry!
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<CRow>
|
||||
<CCol>
|
||||
{t('telemetry.interval')}: {interval} {t('common.seconds')}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol>
|
||||
{t('telemetry.types')}: {types.map((type) => type.label).join(', ')}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol>
|
||||
{t('telemetry.last_update')}: {lastUpdate}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol className="font-weight-bold">Received Messages: {receivedMessages}</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<pre>{JSON.stringify(lastMessage, null, '\t')}</pre>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
{socket.readyState === WebSocket.OPEN ||
|
||||
socket.readyState === WebSocket.CONNECTING ? (
|
||||
<CCol className="d-flex justify-content-center align-items-center">
|
||||
<CSpinner />
|
||||
</CCol>
|
||||
) : (
|
||||
<CCol>
|
||||
<CAlert color="danger">{t('common.socket_connection_closed')}</CAlert>
|
||||
</CCol>
|
||||
)}
|
||||
</CRow>
|
||||
</div>
|
||||
)}
|
||||
</CModalBody>
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
TelemetryModal.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default TelemetryModal;
|
||||
@@ -1,17 +1,16 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CModalBody, CButton, CSpinner, CModalFooter } from '@coreui/react';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { CAlert, CModalBody, CButton, CSpinner, CModalFooter } from '@coreui/react';
|
||||
import { useAuth } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const WaitingForTraceBody = ({ serialNumber, commandUuid, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [secondsElapsed, setSecondsElapsed] = useState(0);
|
||||
const [waitingForFile, setWaitingForFile] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const getTraceResult = () => {
|
||||
const options = {
|
||||
@@ -22,11 +21,15 @@ const WaitingForTraceBody = ({ serialNumber, commandUuid, toggle }) => {
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.ucentralgw}/api/v1/command/${encodeURIComponent(commandUuid)}`, options)
|
||||
.get(`${endpoints.owgw}/api/v1/command/${encodeURIComponent(commandUuid)}`, options)
|
||||
.then((response) => {
|
||||
if (response.data.waitingForFile === 0) {
|
||||
setWaitingForFile(false);
|
||||
}
|
||||
if (response.data.errorCode !== 0) {
|
||||
setWaitingForFile(false);
|
||||
setError(response.data.errorText);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
@@ -41,10 +44,7 @@ const WaitingForTraceBody = ({ serialNumber, commandUuid, toggle }) => {
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/file/${commandUuid}?serialNumber=${serialNumber}`,
|
||||
options,
|
||||
)
|
||||
.get(`${endpoints.owgw}/api/v1/file/${commandUuid}?serialNumber=${serialNumber}`, options)
|
||||
.then((response) => {
|
||||
const blob = new Blob([response.data], { type: 'application/octet-stream' });
|
||||
const link = document.createElement('a');
|
||||
@@ -83,16 +83,19 @@ const WaitingForTraceBody = ({ serialNumber, commandUuid, toggle }) => {
|
||||
<CModalBody>
|
||||
<h6>{t('trace.waiting_seconds', { seconds: secondsElapsed })}</h6>
|
||||
<p>{t('trace.waiting_directions')}</p>
|
||||
<div className={styles.centerDiv}>
|
||||
<div className="d-flex align-middle justify-content-center">
|
||||
<CSpinner hidden={!waitingForFile} />
|
||||
<CButton
|
||||
hidden={waitingForFile}
|
||||
hidden={waitingForFile || error}
|
||||
onClick={downloadTrace}
|
||||
disabled={waitingForFile}
|
||||
disabled={waitingForFile || error}
|
||||
color="primary"
|
||||
>
|
||||
{t('trace.download_trace')}
|
||||
</CButton>
|
||||
<CAlert hidden={waitingForFile || !error} className="my-3" color="danger">
|
||||
{t('trace.trace_not_successful', { error })}
|
||||
</CAlert>
|
||||
</div>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
|
||||
@@ -13,25 +13,24 @@ import {
|
||||
CInputRadio,
|
||||
CFormGroup,
|
||||
CLabel,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import 'react-widgets/styles.css';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import getDeviceConnection from 'utils/deviceHelper';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||
import WaitingForTraceBody from './WaitingForTraceBody';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const TraceModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const { deviceSerialNumber, getDeviceConnection } = useDevice();
|
||||
const [hadSuccess, setHadSuccess] = useState(false);
|
||||
const [hadFailure, setHadFailure] = useState(false);
|
||||
const [blockFields, setBlockFields] = useState(false);
|
||||
@@ -84,7 +83,7 @@ const TraceModal = ({ show, toggleModal }) => {
|
||||
|
||||
axiosInstance
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/trace`,
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/trace`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
@@ -112,7 +111,7 @@ const TraceModal = ({ show, toggleModal }) => {
|
||||
const isConnected = await getDeviceConnection(
|
||||
deviceSerialNumber,
|
||||
currentToken,
|
||||
endpoints.ucentralgw,
|
||||
endpoints.owgw,
|
||||
);
|
||||
setIsDeviceConnected(isConnected);
|
||||
};
|
||||
@@ -137,7 +136,7 @@ const TraceModal = ({ show, toggleModal }) => {
|
||||
<div>
|
||||
<CModalBody>
|
||||
<h6>{t('trace.directions')}</h6>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-3">
|
||||
<CCol>
|
||||
<CButton
|
||||
disabled={blockFields}
|
||||
@@ -159,8 +158,8 @@ const TraceModal = ({ show, toggleModal }) => {
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedColumn}>
|
||||
<CRow className="mt-3">
|
||||
<CCol md="4" className="pt-2">
|
||||
{usingDuration ? 'Duration: ' : 'Packets: '}
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
@@ -191,7 +190,7 @@ const TraceModal = ({ show, toggleModal }) => {
|
||||
)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-3">
|
||||
<CCol md="7">{t('trace.choose_network')}:</CCol>
|
||||
<CCol>
|
||||
<CForm>
|
||||
@@ -220,9 +219,9 @@ const TraceModal = ({ show, toggleModal }) => {
|
||||
</CForm>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow} hidden={!isDeviceConnected}>
|
||||
<CRow className="mt-3" hidden={!isDeviceConnected}>
|
||||
<CCol md="8">
|
||||
<p className={styles.spacedText}>{t('trace.wait_for_file')}</p>
|
||||
<p>{t('trace.wait_for_file')}</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CSwitch
|
||||
@@ -260,8 +259,15 @@ const TraceModal = ({ show, toggleModal }) => {
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggleModal}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('trace.title')}</CModalTitle>
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="pl-1 pt-1">{t('trace.title')}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
{getBody()}
|
||||
</CModal>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedColumn {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.centerDiv {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
}
|
||||
@@ -10,18 +10,18 @@ import {
|
||||
CSwitch,
|
||||
CCol,
|
||||
CSpinner,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
import WifiChannelTable from 'components/WifiScanResultModal/WifiChannelTable';
|
||||
import 'react-widgets/styles.css';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const WifiScanModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -101,7 +101,7 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
||||
|
||||
axiosInstance
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/wifiscan`,
|
||||
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/wifiscan`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
@@ -127,18 +127,25 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
||||
|
||||
return (
|
||||
<CModal size="lg" show={show} onClose={toggleModal}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('actions.wifi_scan')}</CModalTitle>
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="pl-1 pt-1">{t('actions.wifi_scan')}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<div hidden={hideOptions || waiting}>
|
||||
<h6>{t('scan.directions')}</h6>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-3">
|
||||
<CCol md="3">
|
||||
<p className={styles.spacedText}>Verbose:</p>
|
||||
<p className="pl-2">Verbose:</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CForm className={styles.spacedSwitch}>
|
||||
<CForm className="pl-4">
|
||||
<CSwitch
|
||||
color="primary"
|
||||
defaultChecked={choseVerbose}
|
||||
@@ -149,12 +156,12 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
||||
</CForm>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-3">
|
||||
<CCol md="3">
|
||||
<p className={styles.spacedText}>{t('scan.active')}:</p>
|
||||
<p className="pl-2">{t('scan.active')}:</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CForm className={styles.spacedSwitch}>
|
||||
<CForm className="pl-4">
|
||||
<CSwitch
|
||||
color="primary"
|
||||
defaultChecked={activeScan}
|
||||
@@ -173,13 +180,13 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol className={styles.centerDiv}>
|
||||
<CCol className="d-flex align-middle justify-content-center">
|
||||
<CSpinner />
|
||||
</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
<div hidden={!hadSuccess && !hadFailure}>
|
||||
<CRow className={styles.bottomSpace}>
|
||||
<CRow className="mb-2">
|
||||
<CCol>
|
||||
<h6>{t('scan.result_directions')}</h6>
|
||||
</CCol>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedText {
|
||||
padding-left: 2%;
|
||||
}
|
||||
|
||||
.spacedSwitch {
|
||||
padding-left: 5%;
|
||||
}
|
||||
|
||||
.bottomSpace {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.centerDiv {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import 'react-widgets/styles.css';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const WifiChannelCard = ({ channel }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -12,13 +11,13 @@ const WifiChannelCard = ({ channel }) => {
|
||||
return (
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CCardTitle className={styles.cardTitle}>
|
||||
<CCardTitle className="text-dark">
|
||||
{t('scan.channel')} #{channel.channel}
|
||||
</CCardTitle>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<div className={[styles.scrollable, 'overflow-auto'].join(' ')}>
|
||||
<CDataTable items={channel.devices} fields={columns} className={styles.datatable} />
|
||||
<div className="overflow-auto" style={{ height: '250px' }}>
|
||||
<CDataTable items={channel.devices} fields={columns} className="text-white" />
|
||||
</div>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
/* eslint-disable-rule prefer-destructuring */
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
CButton,
|
||||
CModal,
|
||||
CModalHeader,
|
||||
CModalBody,
|
||||
CModalTitle,
|
||||
CModalFooter,
|
||||
} from '@coreui/react';
|
||||
import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { prettyDate } from 'utils/helper';
|
||||
import WifiChannelTable from './WifiChannelTable';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -49,21 +43,23 @@ const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
|
||||
};
|
||||
return (
|
||||
<CModal size="lg" show={show} onClose={toggle}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle className={styles.modalTitle}>
|
||||
<CModalHeader>
|
||||
<CModalTitle className="text-dark">
|
||||
{date !== '' ? prettyDate(date) : ''} {t('scan.results')}
|
||||
</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
{scanResults === null ? null : (
|
||||
<WifiChannelTable channels={parseThroughList(scanResults)} />
|
||||
)}
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" onClick={toggle}>
|
||||
{t('common.close')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
.modalTitle {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.datatable {
|
||||
color: white;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const AuthContext = React.createContext();
|
||||
|
||||
export const AuthProvider = ({ token, apiEndpoints, children }) => {
|
||||
const [currentToken, setCurrentToken] = useState(token);
|
||||
const [endpoints, setEndpoints] = useState(apiEndpoints);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ currentToken, setCurrentToken, endpoints, setEndpoints }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
AuthProvider.propTypes = {
|
||||
token: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
apiEndpoints: PropTypes.instanceOf(Object),
|
||||
};
|
||||
|
||||
AuthProvider.defaultProps = {
|
||||
apiEndpoints: {},
|
||||
};
|
||||
|
||||
export const useAuth = () => React.useContext(AuthContext);
|
||||
@@ -1,21 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const DeviceContext = React.createContext();
|
||||
|
||||
export const DeviceProvider = ({ serialNumber, children }) => {
|
||||
const [deviceSerialNumber, setDeviceSerialNumber] = useState(serialNumber);
|
||||
|
||||
return (
|
||||
<DeviceContext.Provider value={{ deviceSerialNumber, setDeviceSerialNumber }}>
|
||||
{children}
|
||||
</DeviceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceProvider.propTypes = {
|
||||
serialNumber: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export const useDevice = () => React.useContext(DeviceContext);
|
||||
@@ -8,6 +8,7 @@ i18next
|
||||
.use(HttpApi)
|
||||
.use(LanguageDetector)
|
||||
.init({
|
||||
load: 'languageOnly',
|
||||
supportedLngs: ['de', 'en', 'es', 'fr', 'pt'],
|
||||
fallbackLng: 'en',
|
||||
nonExplicitSupportedLngs: true,
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import React, { Suspense } from 'react';
|
||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { v4 as createUuid } from 'uuid';
|
||||
import { CContainer, CFade } from '@coreui/react';
|
||||
import routes from 'routes';
|
||||
import { Translation } from 'react-i18next';
|
||||
|
||||
const loading = (
|
||||
<div className="pt-3 text-center">
|
||||
<div className="sk-spinner sk-spinner-pulse" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const TheContent = () => (
|
||||
<main className="c-main">
|
||||
<CContainer fluid>
|
||||
<Suspense fallback={loading}>
|
||||
<Translation>
|
||||
{(t) => (
|
||||
<Switch>
|
||||
{routes.map(
|
||||
(route) =>
|
||||
route.component && (
|
||||
<Route
|
||||
key={createUuid()}
|
||||
path={route.path}
|
||||
exact={route.exact}
|
||||
name={t(route.name)}
|
||||
render={(props) => (
|
||||
<CFade>
|
||||
<route.component {...props} />
|
||||
</CFade>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<Redirect from="/" to="/devices" />
|
||||
</Switch>
|
||||
)}
|
||||
</Translation>
|
||||
</Suspense>
|
||||
</CContainer>
|
||||
</main>
|
||||
);
|
||||
|
||||
export default React.memo(TheContent);
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CFooter } from '@coreui/react';
|
||||
import { Translation } from 'react-i18next';
|
||||
|
||||
const TheFooter = () => (
|
||||
<Translation>
|
||||
{(t) => (
|
||||
<CFooter fixed={false}>
|
||||
<div>{t('footer.version')} 0.9.13</div>
|
||||
<div className="mfs-auto">
|
||||
<span className="mr-1">{t('footer.powered_by')}</span>
|
||||
<a href="https://coreui.io/react" target="_blank" rel="noopener noreferrer">
|
||||
{t('footer.coreui_for_react')}
|
||||
</a>
|
||||
</div>
|
||||
</CFooter>
|
||||
)}
|
||||
</Translation>
|
||||
);
|
||||
|
||||
export default React.memo(TheFooter);
|
||||
@@ -1,82 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
CHeader,
|
||||
CToggler,
|
||||
CHeaderBrand,
|
||||
CHeaderNav,
|
||||
CSubheader,
|
||||
CBreadcrumbRouter,
|
||||
CLink,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilAccountLogout } from '@coreui/icons';
|
||||
import { logout } from 'utils/authHelper';
|
||||
import routes from 'routes';
|
||||
import { LanguageSwitcher } from 'ucentral-libs';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
|
||||
const TheHeader = ({ showSidebar, setShowSidebar }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [translatedRoutes, setTranslatedRoutes] = useState(routes);
|
||||
|
||||
const toggleSidebar = () => {
|
||||
const val = [true, 'responsive'].includes(showSidebar) ? false : 'responsive';
|
||||
setShowSidebar(val);
|
||||
};
|
||||
|
||||
const toggleSidebarMobile = () => {
|
||||
const val = [false, 'responsive'].includes(showSidebar) ? true : 'responsive';
|
||||
setShowSidebar(val);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setTranslatedRoutes(routes.map(({ name, ...rest }) => ({ ...rest, name: t(name) })));
|
||||
}, [i18n.language]);
|
||||
|
||||
return (
|
||||
<CHeader withSubheader>
|
||||
<CToggler inHeader className="ml-md-3 d-lg-none" onClick={toggleSidebarMobile} />
|
||||
<CToggler inHeader className="ml-3 d-md-down-none" onClick={toggleSidebar} />
|
||||
<CHeaderBrand className="mx-auto d-lg-none" to="/">
|
||||
<CIcon name="logo" height="48" alt="Logo" />
|
||||
</CHeaderBrand>
|
||||
|
||||
<CHeaderNav className="d-md-down-none mr-auto" />
|
||||
|
||||
<CHeaderNav className="px-3">
|
||||
<LanguageSwitcher i18n={i18n} />
|
||||
</CHeaderNav>
|
||||
|
||||
<CHeaderNav className="px-3">
|
||||
<CPopover content={t('common.logout')}>
|
||||
<CLink className="c-subheader-nav-link">
|
||||
<CIcon
|
||||
name="cilAccountLogout"
|
||||
content={cilAccountLogout}
|
||||
size="2xl"
|
||||
onClick={() => logout(currentToken, endpoints.ucentralsec)}
|
||||
/>
|
||||
</CLink>
|
||||
</CPopover>
|
||||
</CHeaderNav>
|
||||
|
||||
<CSubheader className="px-3 justify-content-between">
|
||||
<CBreadcrumbRouter
|
||||
className="border-0 c-subheader-nav m-0 px-0 px-md-3"
|
||||
routes={translatedRoutes}
|
||||
/>
|
||||
</CSubheader>
|
||||
</CHeader>
|
||||
);
|
||||
};
|
||||
|
||||
TheHeader.propTypes = {
|
||||
showSidebar: PropTypes.string.isRequired,
|
||||
setShowSidebar: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default TheHeader;
|
||||
@@ -1,64 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
CCreateElement,
|
||||
CSidebar,
|
||||
CSidebarBrand,
|
||||
CSidebarNav,
|
||||
CSidebarNavDivider,
|
||||
CSidebarNavTitle,
|
||||
CSidebarMinimizer,
|
||||
CSidebarNavDropdown,
|
||||
CSidebarNavItem,
|
||||
} from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const TheSidebar = ({ showSidebar, setShowSidebar }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const navigation = [
|
||||
{
|
||||
_tag: 'CSidebarNavItem',
|
||||
name: t('common.device_list'),
|
||||
to: '/devices',
|
||||
icon: 'cilNotes',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<CSidebar show={showSidebar} onShowChange={(val) => setShowSidebar(val)}>
|
||||
<CSidebarBrand className="d-md-down-none" to="/devices">
|
||||
<img
|
||||
className={[styles.sidebarImgFull, 'c-sidebar-brand-full'].join(' ')}
|
||||
src="assets/OpenWiFi_LogoLockup_WhiteColour.svg"
|
||||
alt="OpenWifi"
|
||||
/>
|
||||
<img
|
||||
className={[styles.sidebarImgMinimized, 'c-sidebar-brand-minimized'].join(' ')}
|
||||
src="assets/OpenWiFi_LogoLockup_WhiteColour.svg"
|
||||
alt="OpenWifi"
|
||||
/>
|
||||
</CSidebarBrand>
|
||||
<CSidebarNav>
|
||||
<CCreateElement
|
||||
items={navigation}
|
||||
components={{
|
||||
CSidebarNavDivider,
|
||||
CSidebarNavDropdown,
|
||||
CSidebarNavItem,
|
||||
CSidebarNavTitle,
|
||||
}}
|
||||
/>
|
||||
</CSidebarNav>
|
||||
<CSidebarMinimizer className="c-d-md-down-none" />
|
||||
</CSidebar>
|
||||
);
|
||||
};
|
||||
|
||||
TheSidebar.propTypes = {
|
||||
showSidebar: PropTypes.string.isRequired,
|
||||
setShowSidebar: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(TheSidebar);
|
||||
@@ -1,9 +0,0 @@
|
||||
.sidebarImgFull {
|
||||
height: 100px;
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
.sidebarImgMinimized {
|
||||
height: 75px;
|
||||
width: 75px;
|
||||
}
|
||||
@@ -1,25 +1,74 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { logout } from 'utils/authHelper';
|
||||
import routes from 'routes';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { Header } from 'ucentral-libs';
|
||||
import Sidebar from './Sidebar';
|
||||
import TheContent from './Content';
|
||||
import TheFooter from './Footer';
|
||||
import { Header, Sidebar, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs';
|
||||
|
||||
const TheLayout = () => {
|
||||
const [showSidebar, setShowSidebar] = useState('responsive');
|
||||
const { endpoints, currentToken } = useAuth();
|
||||
const { endpoints, currentToken, user, avatar, logout } = useAuth();
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
const navigation = [
|
||||
{
|
||||
_tag: 'CSidebarNavDropdown',
|
||||
name: t('common.devices'),
|
||||
icon: 'cilRouter',
|
||||
_children: [
|
||||
{
|
||||
addLinkClass: 'c-sidebar-nav-link ml-2',
|
||||
_tag: 'CSidebarNavItem',
|
||||
name: t('common.dashboard'),
|
||||
to: '/devicedashboard',
|
||||
},
|
||||
{
|
||||
addLinkClass: 'c-sidebar-nav-link ml-2',
|
||||
_tag: 'CSidebarNavItem',
|
||||
name: t('common.table'),
|
||||
to: '/devices',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
_tag: 'CSidebarNavDropdown',
|
||||
name: t('firmware.title'),
|
||||
icon: 'cilSave',
|
||||
_children: [
|
||||
{
|
||||
addLinkClass: 'c-sidebar-nav-link ml-2',
|
||||
_tag: 'CSidebarNavItem',
|
||||
name: t('common.dashboard'),
|
||||
to: '/firmwaredashboard',
|
||||
},
|
||||
{
|
||||
addLinkClass: 'c-sidebar-nav-link ml-2',
|
||||
_tag: 'CSidebarNavItem',
|
||||
name: t('common.table'),
|
||||
to: '/firmware',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
_tag: 'CSidebarNavItem',
|
||||
name: t('user.users'),
|
||||
to: '/users',
|
||||
icon: 'cilPeople',
|
||||
},
|
||||
{
|
||||
_tag: 'CSidebarNavItem',
|
||||
name: t('common.system'),
|
||||
to: '/system',
|
||||
icon: 'cilSettings',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="c-app c-default-layout">
|
||||
<Sidebar
|
||||
showSidebar={showSidebar}
|
||||
setShowSidebar={setShowSidebar}
|
||||
t={t}
|
||||
logo="assets/OpenWiFi_LogoLockup_DarkGreyColour.svg"
|
||||
logo="assets/OpenWiFi_LogoLockup_WhiteColour.svg"
|
||||
options={navigation}
|
||||
redirectTo="/devices"
|
||||
/>
|
||||
<div className="c-wrapper">
|
||||
<Header
|
||||
@@ -29,13 +78,18 @@ const TheLayout = () => {
|
||||
t={t}
|
||||
i18n={i18n}
|
||||
logout={logout}
|
||||
logo="assets/OpenWiFi_LogoLockup_DarkGreyColour.svg"
|
||||
authToken={currentToken}
|
||||
endpoints={endpoints}
|
||||
user={user}
|
||||
avatar={avatar}
|
||||
/>
|
||||
<div className="c-body">
|
||||
<TheContent />
|
||||
<ToastProvider>
|
||||
<PageContainer t={t} routes={routes} redirectTo="/devices" />
|
||||
</ToastProvider>
|
||||
</div>
|
||||
<TheFooter />
|
||||
<Footer t={t} version={process.env.VERSION} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
360
src/pages/DeviceDashboard/index.js
Normal file
360
src/pages/DeviceDashboard/index.js
Normal file
@@ -0,0 +1,360 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DeviceDashboard as Dashboard, useAuth, COLOR_LIST } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
|
||||
const DeviceDashboard = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState({
|
||||
status: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
healths: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
associations: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
upTimes: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
deviceType: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
vendors: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
certificates: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
commands: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
memoryUsed: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
});
|
||||
|
||||
const parseData = (newData) => {
|
||||
const parsedData = newData;
|
||||
|
||||
// Status pie chart
|
||||
const statusDs = [];
|
||||
const statusColors = [];
|
||||
const statusLabels = [];
|
||||
let totalDevices = parsedData.status.reduce((acc, point) => acc + point.value, 0);
|
||||
for (const point of parsedData.status) {
|
||||
statusDs.push(Math.round((point.value / totalDevices) * 100));
|
||||
statusLabels.push(point.tag);
|
||||
let color = '';
|
||||
switch (point.tag) {
|
||||
case 'connected':
|
||||
color = '#41B883';
|
||||
break;
|
||||
case 'not connected':
|
||||
color = '#39f';
|
||||
break;
|
||||
case 'disconnected':
|
||||
color = '#e55353';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
statusColors.push(color);
|
||||
}
|
||||
parsedData.status = {
|
||||
datasets: [
|
||||
{
|
||||
data: statusDs,
|
||||
backgroundColor: statusColors,
|
||||
},
|
||||
],
|
||||
labels: statusLabels,
|
||||
};
|
||||
|
||||
// General Health
|
||||
let devicesAt100 = 0;
|
||||
let devicesUp90 = 0;
|
||||
let devicesUp60 = 0;
|
||||
let devicesDown60 = 0;
|
||||
|
||||
// Health pie chart
|
||||
const healthDs = [];
|
||||
const healthColors = [];
|
||||
const healthLabels = [];
|
||||
totalDevices = parsedData.healths.reduce((acc, point) => acc + point.value, 0);
|
||||
for (const point of parsedData.healths) {
|
||||
healthDs.push(Math.round((point.value / totalDevices) * 100));
|
||||
healthLabels.push(point.tag);
|
||||
let color = '';
|
||||
switch (point.tag) {
|
||||
case '100%':
|
||||
color = '#41B883';
|
||||
devicesAt100 += point.value;
|
||||
break;
|
||||
case '>90%':
|
||||
color = '#ffff5c';
|
||||
devicesUp90 += point.value;
|
||||
break;
|
||||
case '>60%':
|
||||
color = '#f9b115';
|
||||
devicesUp60 += point.value;
|
||||
break;
|
||||
case '<60%':
|
||||
color = '#e55353';
|
||||
devicesDown60 += point.value;
|
||||
break;
|
||||
default:
|
||||
color = '#39f';
|
||||
break;
|
||||
}
|
||||
healthColors.push(color);
|
||||
}
|
||||
parsedData.healths = {
|
||||
datasets: [
|
||||
{
|
||||
data: healthDs,
|
||||
backgroundColor: healthColors,
|
||||
},
|
||||
],
|
||||
labels: healthLabels,
|
||||
};
|
||||
parsedData.overallHealth =
|
||||
totalDevices === 0
|
||||
? '-'
|
||||
: `${Math.round(
|
||||
(devicesAt100 * 100 + devicesUp90 * 95 + devicesUp60 * 75 + devicesDown60 * 35) /
|
||||
totalDevices,
|
||||
)}%`;
|
||||
|
||||
// Associations pie chart
|
||||
const associationsDs = [];
|
||||
const associationsColors = [];
|
||||
const associationsLabels = [];
|
||||
const totalAssociations = parsedData.associations.reduce((acc, point) => acc + point.value, 0);
|
||||
for (let i = 0; i < parsedData.associations.length; i += 1) {
|
||||
const point = parsedData.associations[i];
|
||||
associationsDs.push(Math.round((point.value / totalAssociations) * 100));
|
||||
associationsLabels.push(point.tag);
|
||||
|
||||
switch (parsedData.associations[i].tag) {
|
||||
case '2G':
|
||||
associationsColors.push('#41B883');
|
||||
break;
|
||||
case '5G':
|
||||
associationsColors.push('#3399ff');
|
||||
break;
|
||||
default:
|
||||
associationsColors.push('#636f83');
|
||||
break;
|
||||
}
|
||||
}
|
||||
parsedData.totalAssociations = totalAssociations;
|
||||
parsedData.associations = {
|
||||
datasets: [
|
||||
{
|
||||
data: associationsDs,
|
||||
backgroundColor: associationsColors,
|
||||
},
|
||||
],
|
||||
labels: associationsLabels,
|
||||
};
|
||||
|
||||
// Uptime bar chart
|
||||
const uptimeDs = [0, 0, 0, 0];
|
||||
const uptimeLabels = ['now', '>day', '>week', '>month'];
|
||||
const uptimeColors = ['#321fdb', '#321fdb', '#321fdb', '#321fdb'];
|
||||
|
||||
for (const point of parsedData.upTimes) {
|
||||
switch (point.tag) {
|
||||
case 'now':
|
||||
uptimeDs[0] = point.value;
|
||||
break;
|
||||
case '>day':
|
||||
uptimeDs[1] = point.value;
|
||||
break;
|
||||
case '>week':
|
||||
uptimeDs[2] = point.value;
|
||||
break;
|
||||
case '>month':
|
||||
uptimeDs[3] = point.value;
|
||||
break;
|
||||
default:
|
||||
uptimeDs.push(point.value);
|
||||
uptimeLabels.push(point.tag);
|
||||
uptimeColors.push('#321fdb');
|
||||
}
|
||||
}
|
||||
parsedData.upTimes = {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Devices',
|
||||
data: uptimeDs,
|
||||
backgroundColor: uptimeColors,
|
||||
},
|
||||
],
|
||||
labels: uptimeLabels,
|
||||
};
|
||||
|
||||
// Vendors bar chart
|
||||
const vendorsTypeDs = [];
|
||||
const vendorsColors = [];
|
||||
const vendorsLabels = [];
|
||||
const sortedVendors = parsedData.vendors.sort((a, b) => (a.value < b.value ? 1 : -1));
|
||||
for (const point of sortedVendors) {
|
||||
vendorsTypeDs.push(point.value);
|
||||
vendorsLabels.push(point.tag === '' ? 'Unknown' : point.tag);
|
||||
vendorsColors.push('#eb7474');
|
||||
}
|
||||
const otherVendors = vendorsTypeDs.slice(5).reduce((acc, vendor) => acc + vendor, 0);
|
||||
parsedData.vendors = {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Devices',
|
||||
data: vendorsTypeDs.slice(0, 5).concat([otherVendors]),
|
||||
backgroundColor: vendorsColors,
|
||||
},
|
||||
],
|
||||
labels: vendorsLabels.slice(0, 5).concat(['Others']),
|
||||
};
|
||||
|
||||
// Device Type pie chart
|
||||
const deviceTypeDs = [];
|
||||
const deviceTypeColors = [];
|
||||
const deviceTypeLabels = [];
|
||||
const sortedTypes = parsedData.deviceType.sort((a, b) => (a.value < b.value ? 1 : -1));
|
||||
for (let i = 0; i < sortedTypes.length; i += 1) {
|
||||
const point = sortedTypes[i];
|
||||
|
||||
deviceTypeDs.push(point.value);
|
||||
deviceTypeLabels.push(point.tag);
|
||||
deviceTypeColors.push(COLOR_LIST[i]);
|
||||
}
|
||||
const otherTypes = deviceTypeDs.slice(5).reduce((acc, type) => acc + type, 0);
|
||||
parsedData.deviceType = {
|
||||
datasets: [
|
||||
{
|
||||
data: deviceTypeDs.slice(0, 5).concat([otherTypes]),
|
||||
backgroundColor: deviceTypeColors,
|
||||
},
|
||||
],
|
||||
labels: deviceTypeLabels.slice(0, 5).concat(['Others']),
|
||||
};
|
||||
|
||||
// Certificates pie chart
|
||||
const certificatesDs = [];
|
||||
const certificatesColors = [];
|
||||
const certificatesLabels = [];
|
||||
const totalCerts = parsedData.certificates.reduce((acc, point) => acc + point.value, 0);
|
||||
for (const point of parsedData.certificates) {
|
||||
certificatesDs.push(Math.round((point.value / totalCerts) * 100));
|
||||
certificatesLabels.push(point.tag);
|
||||
let color = '';
|
||||
switch (point.tag) {
|
||||
case 'verified':
|
||||
color = '#41B883';
|
||||
break;
|
||||
case 'serial mismatch':
|
||||
color = '#f9b115';
|
||||
break;
|
||||
case 'no certificate':
|
||||
color = '#e55353';
|
||||
break;
|
||||
default:
|
||||
color = '#39f';
|
||||
break;
|
||||
}
|
||||
certificatesColors.push(color);
|
||||
}
|
||||
parsedData.certificates = {
|
||||
datasets: [
|
||||
{
|
||||
data: certificatesDs,
|
||||
backgroundColor: certificatesColors,
|
||||
},
|
||||
],
|
||||
labels: certificatesLabels,
|
||||
};
|
||||
|
||||
// Commands bar chart
|
||||
const commandsDs = [];
|
||||
const commandsColors = [];
|
||||
const commandsLabels = [];
|
||||
for (const point of parsedData.commands) {
|
||||
commandsDs.push(point.value);
|
||||
commandsLabels.push(point.tag);
|
||||
commandsColors.push('#39f');
|
||||
}
|
||||
parsedData.commands = {
|
||||
datasets: [
|
||||
{
|
||||
label: t('common.commands_executed'),
|
||||
data: commandsDs,
|
||||
backgroundColor: commandsColors,
|
||||
},
|
||||
],
|
||||
labels: commandsLabels,
|
||||
};
|
||||
|
||||
// Memory Used bar chart
|
||||
const memoryDs = [];
|
||||
const memoryColors = [];
|
||||
const memoryLabels = [];
|
||||
for (const point of parsedData.memoryUsed) {
|
||||
memoryDs.push(point.value);
|
||||
memoryLabels.push(point.tag);
|
||||
memoryColors.push('#636f83');
|
||||
}
|
||||
parsedData.memoryUsed = {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Devices',
|
||||
data: memoryDs,
|
||||
backgroundColor: memoryColors,
|
||||
},
|
||||
],
|
||||
labels: memoryLabels,
|
||||
};
|
||||
|
||||
setData(parsedData);
|
||||
};
|
||||
|
||||
const getDashboard = () => {
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
axiosInstance
|
||||
.get(`${endpoints.owgw}/api/v1/deviceDashboard`, {
|
||||
headers,
|
||||
})
|
||||
.then((response) => {
|
||||
parseData(response.data);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getDashboard();
|
||||
}, []);
|
||||
|
||||
return <Dashboard loading={loading} t={t} data={data} />;
|
||||
};
|
||||
|
||||
export default DeviceDashboard;
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import DeviceList from '../../components/DeviceListTable';
|
||||
import DeviceList from 'components/DeviceListTable';
|
||||
|
||||
const DeviceListPage = () => (
|
||||
<div className="App">
|
||||
|
||||
@@ -2,35 +2,40 @@ import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { CRow, CCol } from '@coreui/react';
|
||||
import DeviceHealth from 'components/DeviceHealth';
|
||||
import DeviceConfiguration from 'components/DeviceConfiguration';
|
||||
import DeviceStatusCard from 'components/DeviceStatusCard';
|
||||
import CommandHistory from 'components/CommandHistory';
|
||||
import DeviceLogs from 'components/DeviceLogs';
|
||||
import DeviceStatisticsCard from 'components/InterfaceStatistics';
|
||||
import DeviceActionCard from 'components/DeviceActionCard';
|
||||
import DeviceStatusCard from 'components/DeviceStatusCard';
|
||||
import { DeviceProvider } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { DeviceProvider } from 'ucentral-libs';
|
||||
|
||||
const DevicePage = () => {
|
||||
const { deviceId } = useParams();
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<DeviceProvider serialNumber={deviceId}>
|
||||
<DeviceProvider axiosInstance={axiosInstance} serialNumber={deviceId}>
|
||||
<CRow>
|
||||
<CCol xs="12" sm="6">
|
||||
<CCol>
|
||||
<DeviceStatusCard />
|
||||
<DeviceConfiguration />
|
||||
</CCol>
|
||||
<CCol xs="12" sm="6">
|
||||
<DeviceLogs />
|
||||
<DeviceHealth />
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol lg="12" xl="6">
|
||||
<CommandHistory />
|
||||
</CCol>
|
||||
<CCol lg="12" xl="6">
|
||||
<DeviceActionCard />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<CCol lg="12" xl="6">
|
||||
<DeviceStatisticsCard />
|
||||
<CommandHistory />
|
||||
</CCol>
|
||||
<CCol lg="12" xl="6">
|
||||
<DeviceHealth />
|
||||
<DeviceLogs />
|
||||
</CCol>
|
||||
</CRow>
|
||||
</DeviceProvider>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 10px;
|
||||
}
|
||||
320
src/pages/FirmwareDashboard/index.js
Normal file
320
src/pages/FirmwareDashboard/index.js
Normal file
@@ -0,0 +1,320 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FirmwareDashboard as Dashboard, useAuth, COLOR_LIST } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
|
||||
const FirmwareDashboard = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState({
|
||||
status: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
deviceType: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
firmwareDistribution: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
latest: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
unknownFirmwares: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
ouis: {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
},
|
||||
endpoints: [],
|
||||
latestSoftwareRate: '-',
|
||||
});
|
||||
|
||||
const getOuiInfo = async (oui) => {
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
return axiosInstance
|
||||
.get(`${endpoints.owgw}/api/v1/ouis?macList=${oui.join(',')}`, {
|
||||
headers,
|
||||
})
|
||||
.then((response) => {
|
||||
const matchedObject = {};
|
||||
for (let i = 0; i < response.data.tagList.length; i += 1) {
|
||||
matchedObject[oui[i]] = response.data.tagList[i];
|
||||
}
|
||||
return matchedObject;
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const parseData = async (newData) => {
|
||||
const parsedData = newData;
|
||||
|
||||
// Status pie chart
|
||||
const statusDs = [];
|
||||
const statusColors = [];
|
||||
const statusLabels = [];
|
||||
const totalDevices = parsedData.status.reduce((acc, point) => acc + point.value, 0);
|
||||
for (const point of parsedData.status) {
|
||||
statusDs.push(Math.round((point.value / totalDevices) * 100));
|
||||
statusLabels.push(point.tag);
|
||||
let color = '';
|
||||
switch (point.tag) {
|
||||
case 'connected':
|
||||
color = '#41B883';
|
||||
break;
|
||||
case 'not connected':
|
||||
color = '#e55353';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
statusColors.push(color);
|
||||
}
|
||||
parsedData.status = {
|
||||
datasets: [
|
||||
{
|
||||
data: statusDs,
|
||||
backgroundColor: statusColors,
|
||||
},
|
||||
],
|
||||
labels: statusLabels,
|
||||
};
|
||||
|
||||
// Device Type pie chart
|
||||
const deviceTypeDs = [];
|
||||
const deviceTypeColors = [];
|
||||
const deviceTypeLabels = [];
|
||||
const sortedTypes = parsedData.deviceTypes.sort((a, b) => (a.value < b.value ? 1 : -1));
|
||||
for (let i = 0; i < sortedTypes.length; i += 1) {
|
||||
const point = sortedTypes[i];
|
||||
|
||||
deviceTypeDs.push(point.value);
|
||||
deviceTypeLabels.push(point.tag);
|
||||
deviceTypeColors.push(COLOR_LIST[i]);
|
||||
}
|
||||
const otherTypes = deviceTypeDs.slice(5).reduce((acc, type) => acc + type, 0);
|
||||
parsedData.deviceType = {
|
||||
datasets: [
|
||||
{
|
||||
data: deviceTypeDs.slice(0, 5).concat([otherTypes]),
|
||||
backgroundColor: deviceTypeColors,
|
||||
},
|
||||
],
|
||||
labels: deviceTypeLabels.slice(0, 5).concat(['Others']),
|
||||
};
|
||||
|
||||
// Latest/unknown distribution
|
||||
const usingLatestFirmware =
|
||||
parsedData.usingLatest.length > 0
|
||||
? parsedData.usingLatest.reduce((acc, firmware) => acc + firmware.value, 0)
|
||||
: 0;
|
||||
const unknownFirmware = parsedData.numberOfDevices - usingLatestFirmware;
|
||||
parsedData.usingLatestFirmware = usingLatestFirmware;
|
||||
parsedData.firmwareDistribution = {
|
||||
datasets: [
|
||||
{
|
||||
label: t('common.devices'),
|
||||
data: [unknownFirmware, usingLatestFirmware],
|
||||
backgroundColor: ['#e55353', '#41B883'],
|
||||
},
|
||||
],
|
||||
labels: [t('common.unknown'), t('common.latest')],
|
||||
};
|
||||
|
||||
if (
|
||||
parsedData.numberOfDevices === undefined ||
|
||||
Number.isNaN(parsedData.numberOfDevices) ||
|
||||
parsedData === 0
|
||||
) {
|
||||
parsedData.latestSoftwareRate = '-';
|
||||
} else {
|
||||
parsedData.latestSoftwareRate = `${(
|
||||
(parsedData.usingLatestFirmware / parsedData.numberOfDevices) *
|
||||
100
|
||||
).toFixed(1)}%`;
|
||||
}
|
||||
|
||||
// Average firmware age calculation
|
||||
const usingUnknownFirmwareFromArray =
|
||||
parsedData.unknownFirmwares.length > 0
|
||||
? parsedData.unknownFirmwares.reduce((acc, firmware) => acc + firmware.value, 0)
|
||||
: 0;
|
||||
const devicesForAverage = parsedData.numberOfDevices - usingUnknownFirmwareFromArray;
|
||||
parsedData.averageFirmwareAge =
|
||||
parsedData.totalSecondsOld[0].value /
|
||||
(devicesForAverage > 0 ? devicesForAverage : 1) /
|
||||
(24 * 60 * 60);
|
||||
|
||||
// Latest firmware distribution
|
||||
const latestDs = [];
|
||||
const latestColors = [];
|
||||
const latestLabels = [];
|
||||
const usingLatest = parsedData.usingLatest.sort((a, b) => (a.value < b.value ? 1 : -1));
|
||||
for (const point of usingLatest) {
|
||||
latestDs.push(point.value);
|
||||
if (point.tag === '') {
|
||||
latestLabels.push('Unknown');
|
||||
} else if (point.tag.split(' / ').length > 1) {
|
||||
latestLabels.push(point.tag.split(' / ')[1]);
|
||||
} else {
|
||||
latestLabels.push(point.tag);
|
||||
}
|
||||
latestColors.push('#39f');
|
||||
}
|
||||
parsedData.latest = {
|
||||
datasets: [
|
||||
{
|
||||
label: t('common.firmware'),
|
||||
data: latestDs.slice(0, 5),
|
||||
backgroundColor: latestColors,
|
||||
},
|
||||
],
|
||||
labels: latestLabels.slice(0, 5),
|
||||
};
|
||||
|
||||
// Unknown firmware distribution
|
||||
const unknownDs = [];
|
||||
const unknownColors = [];
|
||||
const unknownLabels = [];
|
||||
const unknownFirmwares = parsedData.unknownFirmwares.sort((a, b) =>
|
||||
a.value < b.value ? 1 : -1,
|
||||
);
|
||||
for (const point of unknownFirmwares) {
|
||||
unknownDs.push(point.value);
|
||||
if (point.tag === '') {
|
||||
unknownLabels.push('Unknown');
|
||||
} else if (point.tag.split(' / ').length > 1) {
|
||||
unknownLabels.push(point.tag.split(' / ')[1]);
|
||||
} else {
|
||||
unknownLabels.push(point.tag);
|
||||
}
|
||||
|
||||
unknownColors.push('#39f');
|
||||
}
|
||||
parsedData.unknownFirmwares = {
|
||||
datasets: [
|
||||
{
|
||||
label: t('common.firmware'),
|
||||
data: unknownDs.slice(0, 5),
|
||||
backgroundColor: unknownColors,
|
||||
},
|
||||
],
|
||||
labels: unknownLabels.slice(0, 5),
|
||||
};
|
||||
|
||||
// OUIs bar graph
|
||||
const ouiCompleteInfo = [];
|
||||
const ouisLabels = [];
|
||||
const sortedOuis = parsedData.ouis.sort((a, b) => (a.value < b.value ? 1 : -1));
|
||||
for (const point of sortedOuis) {
|
||||
ouiCompleteInfo.push({
|
||||
value: point.value,
|
||||
tag: point.tag,
|
||||
});
|
||||
ouisLabels.push(point.tag === '' ? 'Unknown' : point.tag);
|
||||
}
|
||||
const ouiDetails = await getOuiInfo(ouisLabels);
|
||||
|
||||
// Merging 'Good' labels with ouiCompleteInfo
|
||||
if (ouiDetails !== null) {
|
||||
for (let i = 0; i < ouiCompleteInfo.length; i += 1) {
|
||||
ouiCompleteInfo[i].label =
|
||||
ouiDetails[ouiCompleteInfo[i].tag].value !== undefined &&
|
||||
ouiDetails[ouiCompleteInfo[i].tag].value !== ''
|
||||
? ouiDetails[ouiCompleteInfo[i].tag].value
|
||||
: 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
// Merging OUIs that have the same label that we got from getOuiInfo
|
||||
const finalOuis = {};
|
||||
for (const oui of ouiCompleteInfo) {
|
||||
if (finalOuis[oui.label] === undefined) {
|
||||
finalOuis[oui.label] = {
|
||||
label: oui.label,
|
||||
value: oui.value,
|
||||
};
|
||||
} else {
|
||||
finalOuis[oui.label] = {
|
||||
label: oui.label,
|
||||
value: finalOuis[oui.label].value + oui.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Flattening finalOuis into an array so we can create the arrays necessary for the chart
|
||||
const finalOuisArr = Object.entries(finalOuis);
|
||||
const finalOuiDs = [];
|
||||
const finalOuiLabels = [];
|
||||
const finalOuiColors = [];
|
||||
for (const oui of finalOuisArr) {
|
||||
finalOuiDs.push(oui[1].value);
|
||||
finalOuiLabels.push(oui[1].label);
|
||||
finalOuiColors.push('#39f');
|
||||
}
|
||||
const totalOthers = finalOuiDs.slice(5).reduce((acc, oui) => acc + oui, 0);
|
||||
|
||||
parsedData.ouis = {
|
||||
datasets: [
|
||||
{
|
||||
label: 'OUIs',
|
||||
data: finalOuiDs.slice(0, 5).concat(totalOthers),
|
||||
backgroundColor: finalOuiColors.concat('#39f'),
|
||||
},
|
||||
],
|
||||
labels: finalOuiLabels.slice(0, 5).concat('Others'),
|
||||
};
|
||||
|
||||
// Endpoints table
|
||||
const endpointsDs = [];
|
||||
const endpointsTotal = parsedData.endPoints.reduce((acc, point) => acc + point.value, 0);
|
||||
for (const point of parsedData.endPoints) {
|
||||
endpointsDs.push({
|
||||
endpoint: point.tag,
|
||||
devices: point.value,
|
||||
percent: `${Math.round((point.value / endpointsTotal) * 100)}%`,
|
||||
});
|
||||
}
|
||||
parsedData.endpoints = endpointsDs;
|
||||
|
||||
setData(parsedData);
|
||||
};
|
||||
|
||||
const getDashboard = () => {
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
axiosInstance
|
||||
.get(`${endpoints.owfms}/api/v1/deviceReport`, {
|
||||
headers,
|
||||
})
|
||||
.then((response) => {
|
||||
parseData(response.data);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getDashboard();
|
||||
}, []);
|
||||
|
||||
return <Dashboard loading={loading} t={t} data={data} />;
|
||||
};
|
||||
|
||||
export default FirmwareDashboard;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user