Merge pull request #34 from Telecominfraproject/dev-microservice

Dev microservice
This commit is contained in:
Charles
2021-07-13 10:12:20 -04:00
committed by GitHub
81 changed files with 10879 additions and 22750 deletions

View File

@@ -1 +1,4 @@
/src/assets
/src/assets
/build
/node_modules
.github

View File

@@ -1,16 +1,11 @@
{
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"allowImportExportEverywhere": false,
"codeFrame": false
},
"extends": ["airbnb", "prettier"],
"env": {
"browser": true,
"jest": true
},
"rules": {
"extends": ["airbnb", "prettier"],
"plugins": ["prettier"],
"env": {
"browser": true,
"jest": true
},
"rules": {
"max-len": ["error", {"code": 150}],
"prefer-promise-reject-errors": ["off"],
"react/jsx-filename-extension": ["off"],
@@ -18,13 +13,22 @@
"no-return-assign": ["off"],
"react/jsx-props-no-spreading": ["off"],
"react/destructuring-assignment": ["off"],
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"]
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"],
"react/jsx-one-expression-per-line": "off",
"react/jsx-wrap-multilines": "off",
"react/jsx-curly-newline": "off"
},
"settings": {
"import/resolver": {
"node": {
"paths": ["src"]
}
},
}
},
}
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"allowImportExportEverywhere": false,
"codeFrame": false
}
}

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules

4
.prettierignore Normal file
View File

@@ -0,0 +1,4 @@
/src/assets
build
node_modules
.github

View File

@@ -26,12 +26,12 @@ npm run build
Once the build is done, you can move the `build` folder on your server.
### Configuration
You must change the `config.json` file in `public` directory to point to your gateway URL. You may also limit the ability for users to change the default gateway. If you do not allow a gateway change, the gateway URL will not appear on the login screen.
You must change the `config.json` file in `public` directory to point to your uCentral Security Service URL (uCentralSec). You may also limit the ability for users to change the default uCentralSec. If you do not allow a uCentralSec change, the uCentralSec URL will not appear on the login screen.
Here are the current default values:
```
{
"DEFAULT_GATEWAY_URL": "https://ucentral.dpaas.arilia.com:16001",
"ALLOW_GATEWAY_CHANGE": false
"DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001",
"ALLOW_UCENTRALSEC_CHANGE": false
}
```

7
babel.config.json Normal file
View File

@@ -0,0 +1,7 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": ["@babel/plugin-proposal-class-properties"]
}

12
config/paths.js Normal file
View File

@@ -0,0 +1,12 @@
const path = require('path');
module.exports = {
// Source files
src: path.resolve(__dirname, '../src'),
// Production build files
build: path.resolve(__dirname, '../build'),
// Static files that get copied to build folder
public: path.resolve(__dirname, '../public'),
};

76
config/webpack.common.js Normal file
View File

@@ -0,0 +1,76 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable prefer-template */
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 path = require('path');
const paths = require('./paths');
module.exports = {
entry: [paths.src + '/index.js'],
output: {
path: paths.build,
filename: '[name].bundle.js',
publicPath: '/',
},
resolve: {
modules: [path.resolve('./node_modules'), path.resolve('./src')],
preferRelative: true,
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'styles/[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css',
}),
new CopyWebpackPlugin({
patterns: [
{
from: paths.src + '/assets',
to: 'assets',
globOptions: {
ignore: ['*.DS_Store'],
},
},
{
from: paths.public + '/locales',
to: 'locales',
globOptions: {
ignore: ['*.DS_Store'],
},
},
{
from: paths.public + '/config.json',
to: 'config.json',
},
],
}),
new HtmlWebpackPlugin({
title: 'uCentralGW',
favicon: paths.public + '/favicon.ico',
template: paths.public + '/index.html',
filename: 'index.html',
}),
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
{
test: /\.(css|scss)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
{
test: /\.svg$/,
use: ['@svgr/webpack'],
},
{ test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: 'asset/resource' },
],
},
};

53
config/webpack.dev.js Normal file
View File

@@ -0,0 +1,53 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable prefer-template */
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const { merge } = require('webpack-merge');
const path = require('path');
const paths = require('./paths');
const common = require('./webpack.common');
module.exports = merge(common, {
mode: 'development',
target: 'web',
devtool: 'inline-source-map',
devServer: {
historyApiFallback: true,
contentBase: paths.build,
open: true,
compress: false,
hot: true,
port: 3000,
},
module: {
rules: [
{
test: /\.[js]sx?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('babel-loader'),
options: {
plugins: [require.resolve('react-refresh/babel')],
},
},
],
},
],
},
resolve: {
modules: [
'node_modules',
'src',
path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'),
],
alias: {
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'),
},
},
plugins: [new ReactRefreshWebpackPlugin()],
});

36
config/webpack.prod.js Normal file
View File

@@ -0,0 +1,36 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable prefer-template */
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 paths = require('./paths');
const common = require('./webpack.common');
module.exports = merge(common, {
mode: 'production',
devtool: false,
output: {
path: paths.build,
publicPath: '/',
filename: 'js/[name].[contenthash].bundle.js',
},
plugins: [
new MiniCssExtractPlugin({
filename: 'styles/[name].[contenthash].css',
chunkFilename: '[contenthash].css',
}),
],
module: {
rules: [],
},
optimization: {
minimize: true,
minimizer: [`...`, new TerserPlugin(), new CssMinimizerPlugin()],
},
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000,
},
});

View File

@@ -1,6 +1,6 @@
#!/bin/ash
# Check if variables are set
export DEFAULT_GATEWAY_URL="${DEFAULT_GATEWAY_URL:-https://ucentral.dpaas.arilia.com:16001}"
export ALLOW_GATEWAY_CHANGE="${ALLOW_GATEWAY_CHANGE:-false}"
export DEFAULT_UCENTRALSEC_URL="${DEFAULT_UCENTRALSEC_URL:-https://ucentral.dpaas.arilia.com:16001}"
export ALLOW_UCENTRALSEC_CHANGE="${ALLOW_UCENTRALSEC_CHANGE:-false}"
echo '{"DEFAULT_GATEWAY_URL": "'$DEFAULT_GATEWAY_URL'","ALLOW_GATEWAY_CHANGE": '$ALLOW_GATEWAY_CHANGE'}' > /usr/share/nginx/html/config.json
echo '{"DEFAULT_UCENTRALSEC_URL": "'$DEFAULT_UCENTRALSEC_URL'","ALLOW_UCENTRALSEC_CHANGE": '$ALLOW_UCENTRALSEC_CHANGE'}' > /usr/share/nginx/html/config.json

View File

@@ -71,5 +71,5 @@ affinity: {}
# Application
public_env_variables:
DEFAULT_GATEWAY_URL: https://ucentral.dpaas.arilia.com:16001
ALLOW_GATEWAY_CHANGE: false
DEFAULT_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001
ALLOW_UCENTRALSEC_CHANGE: false

View File

@@ -4,6 +4,6 @@
"paths": {
"*": ["*"]
}
},
},
"include": ["src"]
}

30991
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,14 @@
{
"name": "ucentral-client",
"version": "0.9.5",
"private": true,
"version": "0.9.14",
"dependencies": {
"@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1",
"@coreui/icons-react": "^1.1.0",
"@coreui/react": "^3.4.6",
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.14",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^13.1.9",
"apexcharts": "^3.27.1",
"axios": "^0.21.1",
"axios-retry": "^3.1.9",
"http": "^0.0.1-security",
"https": "^1.0.0",
"i18next": "^20.3.1",
"i18next-browser-languagedetector": "^6.1.2",
"i18next-http-backend": "^1.2.6",
@@ -27,20 +18,18 @@
"react-dom": "^17.0.2",
"react-i18next": "^11.11.0",
"react-paginate": "^7.1.3",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.3",
"react-select": "^4.3.1",
"react-widgets": "^5.1.1",
"redux": "^4.1.0",
"sass": "^1.35.1",
"ucentral-libs": "^0.8.7",
"uuid": "^8.3.2"
},
"main": "index.js",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"start": "webpack serve --config config/webpack.dev.js",
"build": "webpack --config config/webpack.prod.js",
"format": "prettier --write 'src/**/*.js'",
"eslint-fix": "eslint --fix 'src/**/*.js'"
},
"eslintConfig": {
"extends": "react-app"
@@ -51,12 +40,54 @@
}
},
"lint-staged": {
"src/**/*.{js,jsx}": [
"*.{js,jsx}": [
"eslint",
"pretty-quick — staged",
"git add"
"prettier --write"
]
},
"devDependencies": {
"@babel/core": "^7.14.6",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-transform-runtime": "^7.14.5",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.14.7",
"@babel/preset-react": "^7.14.5",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@svgr/webpack": "^5.5.0",
"autoprefixer": "^10.2.6",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^7.0.0",
"css-loader": "^5.2.6",
"css-minimizer-webpack-plugin": "^2.0.0",
"dotenv-webpack": "^6.0.4",
"eslint": "^7.29.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^7.2.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-loader": "^4.0.2",
"eslint-plugin-babel": "^5.3.1",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.2.0",
"html-webpack-plugin": "^5.3.2",
"husky": "^4.3.8",
"lint-staged": "^11.0.0",
"mini-css-extract-plugin": "^1.6.1",
"node-sass": "^5.0.0",
"path": "^0.12.7",
"prettier": "^2.3.2",
"react-refresh": "^0.9.0",
"sass-loader": "^11.1.1",
"style-loader": "^2.0.0",
"terser-webpack-plugin": "^5.1.4",
"webpack": "^5.40.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.8.0"
},
"browserslist": {
"production": [
">0.2%",
@@ -68,17 +99,5 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"eslint": "^7.28.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-react": "^7.24.0",
"husky": "^6.0.0",
"lint-staged": "^11.0.0",
"prettier": "^2.3.1",
"pretty-quick": "^3.1.0"
}
}

View File

@@ -1,4 +1,4 @@
{
"DEFAULT_GATEWAY_URL": "https://ucentral.dpaas.arilia.com:16001",
"ALLOW_GATEWAY_CHANGE": false
"DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001",
"ALLOW_UCENTRALSEC_CHANGE": false
}

View File

@@ -2,42 +2,13 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="icon" href="favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>uCentralGW</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@@ -14,7 +14,9 @@
"blink": {
"blink": "LEDs Blinken",
"device_leds": "LEDs",
"execute_now": "Möchten Sie dieses Muster jetzt einstellen?",
"pattern": "Wählen Sie das Muster, das Sie verwenden möchten:",
"set_leds": "LEDs einstellen",
"when_blink_leds": "Wann möchten Sie die LEDs blinken lassen?"
},
"commands": {
@@ -23,6 +25,8 @@
"title": "Gerätebefehle"
},
"common": {
"add": "Hinzufügen",
"adding_ellipsis": "Hinzufügen ...",
"are_you_sure": "Bist du sicher?",
"cancel": "Abbrechen",
"certificate": "Zertifikat",
@@ -36,7 +40,11 @@
"connected": "Verbindung wurde hergestellt",
"copied": "kopiert!",
"copy_to_clipboard": "In die Zwischenablage kopieren",
"created_by": "Erstellt von",
"custom_date": "Benutzerdefiniertes Datum",
"date": "Datum",
"day": "tag",
"days": "tage",
"delete": "Löschen",
"details": "Einzelheiten",
"device_list": "Liste der Geräte",
@@ -52,6 +60,8 @@
"exit": "Ausgang",
"firmware": "Firmware",
"from": "Von",
"hour": "stunde",
"hours": "std",
"id": "ID",
"ip_address": "IP Adresse",
"later_tonight": "Später am Abend",
@@ -60,9 +70,12 @@
"logout": "Ausloggen",
"mac": "MAC-Adresse",
"manufacturer": "Hersteller",
"minute": "Minute",
"minutes": "protokoll",
"na": "(unbekannt)",
"need_date": "Du brauchst ein Datum...",
"no": "Nein",
"no_items": "Keine Gegenstände",
"not_connected": "Nicht verbunden",
"off": "Aus",
"on": "An",
@@ -73,6 +86,8 @@
"save": "Sparen",
"saving": "Speichern ...",
"schedule": "Zeitplan",
"second": "zweite",
"seconds": "sekunden",
"seconds_elapsed": "Sekunden verstrichen",
"serial_number": "Seriennummer",
"start": "Start",
@@ -92,6 +107,7 @@
"last_configuration_change": "Letzte Konfigurationsänderung",
"last_configuration_download": "Letzter Konfigurations-Download",
"location": "Ort",
"note": "Hinweis",
"notes": "Anmerkungen",
"owner": "Inhaber",
"title": "Gerätekonfiguration",
@@ -140,14 +156,16 @@
"login": "Anmeldung",
"login_error": "Anmeldefehler, bestätigen Sie, dass Ihr Benutzername, Ihr Passwort und Ihre Gateway-URL gültig sind",
"password": "Passwort",
"please_enter_gateway": "Bitte geben Sie eine Gateway-URL ein",
"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",
"sign_in_to_account": "Melden Sie sich bei Ihrem Konto an",
"url": "uCentralSec-URL",
"username": "Benutzername"
},
"reboot": {
"directions": "Wann möchten Sie dieses Gerät neu starten?",
"now": "Möchten Sie dieses Gerät jetzt neu starten?",
"title": "Gerät neustarten"
},
"scan": {
@@ -167,10 +185,23 @@
"show_latest": "Neueste Statistiken anzeigen JSON",
"title": "Statistiken"
},
"status": {
"connection_status": "Verbindungsstatus",
"error": "Statusdaten sind nicht verfügbar",
"last_contact": "Letzter Kontakt",
"load_averages": "Belastung (Durchschnitt 1 / 5 / 15 Minuten)",
"localtime": "Ortszeit",
"memory": "Verwendeter Speicher",
"percentage_free": "{{percentage}}% von {{total}} kostenlos",
"percentage_used": "{{percentage}}% von {{total}} verwendet",
"title": "#{{serialNumber}} Status",
"uptime": "Betriebszeit",
"used_total_memory": "{{used}} verwendet / {{total}} insgesamt"
},
"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",
"download_trace": "Klicke hier zum herunterladen",
"download_trace": "Trace-Datei herunterladen",
"packets": "Pakete",
"title": "Tcpdump",
"trace": "Spur",

View File

@@ -14,7 +14,9 @@
"blink": {
"blink": "Blink",
"device_leds": "Device LEDs",
"pattern": "Choose the pattern you would like to use: ",
"execute_now": "Would you like to set this pattern now?",
"pattern": "LEDs pattern: ",
"set_leds": "Set LEDs",
"when_blink_leds": "When would you like to make the device LEDs blink?"
},
"commands": {
@@ -23,6 +25,8 @@
"title": "Command History"
},
"common": {
"add": "Add",
"adding_ellipsis": "Adding...",
"are_you_sure": "Are you sure?",
"cancel": "Cancel",
"certificate": "Certificate",
@@ -36,7 +40,11 @@
"connected": "Connected",
"copied": "Copied!",
"copy_to_clipboard": "Copy to clipboard",
"created_by": "Created By",
"custom_date": "Custom Date",
"date": "Date",
"day": "day",
"days": "days",
"delete": "Delete",
"details": "Details",
"device_list": "List of Devices",
@@ -52,6 +60,8 @@
"exit": "Exit",
"firmware": "Firmware",
"from": "From",
"hour": "hour",
"hours": "hours",
"id": "Id",
"ip_address": "Ip Address",
"later_tonight": "Later tonight",
@@ -60,9 +70,12 @@
"logout": "Logout",
"mac": "MAC Address",
"manufacturer": "Manufacturer",
"minute": "minute",
"minutes": "minutes",
"na": "N/A",
"need_date": "You need a date...",
"no": "No",
"no_items": "No Items",
"not_connected": "Not Connected",
"off": "Off",
"on": "On",
@@ -73,6 +86,8 @@
"save": "Save",
"saving": "Saving... ",
"schedule": "Schedule",
"second": "second",
"seconds": "seconds",
"seconds_elapsed": "Seconds elapsed",
"serial_number": "Serial Number",
"start": "Start",
@@ -92,6 +107,7 @@
"last_configuration_change": "Last Configuration Change",
"last_configuration_download": "Last Configuration Download",
"location": "Location",
"note": "Note",
"notes": "Notes",
"owner": "Owner",
"title": "Configuration",
@@ -140,14 +156,16 @@
"login": "Login",
"login_error": "Login error, confirm that your username, password and gateway url are valid",
"password": "Password",
"please_enter_gateway": "Please enter a gateway URL",
"please_enter_gateway": "Please enter a uCentralSec URL",
"please_enter_password": "Please enter your password",
"please_enter_username": "Please enter your username",
"sign_in_to_account": "Sign in to your account",
"url": "uCentralSec URL",
"username": "Username"
},
"reboot": {
"directions": "When would you like to reboot this device?",
"now": "Would you like to reboot this device now?",
"title": "Reboot"
},
"scan": {
@@ -167,10 +185,23 @@
"show_latest": "Show latest statistics JSON",
"title": "Statistics"
},
"status": {
"connection_status": "Connection Status",
"error": "Status data is unavailable",
"last_contact": "Last Contact",
"load_averages": "Load ( 1 / 5 / 15 minute average)",
"localtime": "Localtime",
"memory": "Memory Used",
"percentage_free": "{{percentage}}% of {{total}} free",
"percentage_used": "{{percentage}}% of {{total}} used",
"title": "#{{serialNumber}} Status",
"uptime": "Uptime",
"used_total_memory": "{{used}} used / {{total}} total "
},
"trace": {
"choose_network": "Choose network",
"directions": "Launch a remote trace of this device for either a specific duration or a number of packets",
"download_trace": "Click here to download",
"download_trace": "Download Trace File",
"packets": "Packets",
"title": "Trace",
"trace": "Trace",

View File

@@ -14,7 +14,9 @@
"blink": {
"blink": "Parpadeo",
"device_leds": "LED de dispositivo",
"execute_now": "¿Le gustaría establecer este patrón ahora?",
"pattern": "Elija el patrón que le gustaría usar:",
"set_leds": "Establecer LED",
"when_blink_leds": "¿Cuándo desea que los LED del dispositivo parpadeen?"
},
"commands": {
@@ -23,6 +25,8 @@
"title": "Historial de Comandos"
},
"common": {
"add": "Añadir",
"adding_ellipsis": "Añadiendo ...",
"are_you_sure": "¿Estás seguro?",
"cancel": "Cancelar",
"certificate": "Certificado",
@@ -36,7 +40,11 @@
"connected": "Conectado",
"copied": "Copiado!",
"copy_to_clipboard": "Copiar al portapapeles",
"created_by": "Creado por",
"custom_date": "Fecha personalizada",
"date": "Fecha",
"day": "día",
"days": "días",
"delete": "Borrar",
"details": "Detalles",
"device_list": "Listado de dispositivos",
@@ -52,6 +60,8 @@
"exit": "salida",
"firmware": "Firmware",
"from": "Desde",
"hour": "hora",
"hours": "horas",
"id": "Carné de identidad",
"ip_address": "Dirección IP",
"later_tonight": "Más tarde esta noche",
@@ -60,9 +70,12 @@
"logout": "Cerrar sesión",
"mac": "Dirección MAC",
"manufacturer": "Fabricante",
"minute": "minuto",
"minutes": "minutos",
"na": "N / A",
"need_date": "Necesitas una cita ...",
"no": "No",
"no_items": "No hay articulos",
"not_connected": "No conectado",
"off": "Apagado",
"on": "en",
@@ -73,6 +86,8 @@
"save": "Salvar",
"saving": "Ahorro...",
"schedule": "Programar",
"second": "segundo",
"seconds": "segundos",
"seconds_elapsed": "Segundos transcurridos",
"serial_number": "Número de serie",
"start": "comienzo",
@@ -92,6 +107,7 @@
"last_configuration_change": "Último cambio de configuración",
"last_configuration_download": "Descarga de la última configuración",
"location": "Ubicación",
"note": "Nota",
"notes": "Notas",
"owner": "Propietario",
"title": "Configuración",
@@ -140,14 +156,16 @@
"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",
"password": "Contraseña",
"please_enter_gateway": "Ingrese una URL de puerta de enlace",
"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",
"sign_in_to_account": "Iniciar sesión en su cuenta",
"url": "URL de uCentralSec",
"username": "Nombre de usuario"
},
"reboot": {
"directions": "¿Cuándo le gustaría reiniciar este dispositivo?",
"now": "¿Le gustaría reiniciar este dispositivo ahora?",
"title": "Reiniciar"
},
"scan": {
@@ -167,10 +185,23 @@
"show_latest": "Mostrar las últimas estadísticas JSON",
"title": "estadística"
},
"status": {
"connection_status": "Estado de conexión",
"error": "Los datos de estado no están disponibles",
"last_contact": "Último contacto",
"load_averages": "Carga (promedio de 1/5/15 minutos)",
"localtime": "Hora local",
"memory": "Memoria usada",
"percentage_free": "{{percentage}}% de {{total}} gratis",
"percentage_used": "{{percentage}}% de {{total}} utilizado",
"title": "#{{serialNumber}} Estado",
"uptime": "Tiempo de actividad",
"used_total_memory": "{{used}} usado / {{total}} total"
},
"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",
"download_trace": "Haga click aquí para descargar",
"download_trace": "Descargar archivo de seguimiento",
"packets": "Paquetes",
"title": "Rastro",
"trace": "Rastro",

View File

@@ -14,7 +14,9 @@
"blink": {
"blink": "Cligner",
"device_leds": "LED de l'appareil",
"execute_now": "Souhaitez-vous définir ce modèle maintenant ?",
"pattern": "Choisissez le modèle que vous souhaitez utiliser :",
"set_leds": "Définir les LED",
"when_blink_leds": "Quand souhaitez-vous faire clignoter les LED de l'appareil ?"
},
"commands": {
@@ -23,6 +25,8 @@
"title": "Historique des commandes"
},
"common": {
"add": "Ajouter",
"adding_ellipsis": "Ajouter...",
"are_you_sure": "Êtes-vous sûr?",
"cancel": "annuler",
"certificate": "Certificat",
@@ -36,7 +40,11 @@
"connected": "Connecté",
"copied": "Copié!",
"copy_to_clipboard": "Copier dans le presse-papier",
"created_by": "Créé par",
"custom_date": "Date personnalisée",
"date": "Rendez-vous amoureux",
"day": "journée",
"days": "journées",
"delete": "Effacer",
"details": "Détails",
"device_list": "Liste des appareils",
@@ -52,6 +60,8 @@
"exit": "Sortie",
"firmware": "Micrologiciel",
"from": "De",
"hour": "heure",
"hours": "heures",
"id": "Id",
"ip_address": "Adresse IP",
"later_tonight": "Plus tard ce soir",
@@ -60,9 +70,12 @@
"logout": "Connectez - Out",
"mac": "ADRESSE MAC",
"manufacturer": "fabricant",
"minute": "minute",
"minutes": "minutes",
"na": "N / A",
"need_date": "Vous avez besoin d'un rendez-vous...",
"no": "Non",
"no_items": "Pas d'objet",
"not_connected": "Pas connecté",
"off": "De",
"on": "sur",
@@ -73,6 +86,8 @@
"save": "Sauvegarder",
"saving": "Économie...",
"schedule": "Programme",
"second": "seconde",
"seconds": "secondes",
"seconds_elapsed": "Secondes écoulées",
"serial_number": "Numéro de série",
"start": "Début",
@@ -92,6 +107,7 @@
"last_configuration_change": "Dernière modification de configuration",
"last_configuration_download": "Téléchargement de la dernière configuration",
"location": "Emplacement",
"note": "Remarque",
"notes": "Remarques",
"owner": "Propriétaire",
"title": "Configuration",
@@ -140,14 +156,16 @@
"login": "S'identifier",
"login_error": "Erreur de connexion, confirmez que votre nom d'utilisateur, mot de passe et URL de passerelle sont valides",
"password": "Mot de passe",
"please_enter_gateway": "Veuillez saisir une URL de passerelle",
"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",
"sign_in_to_account": "Connectez-vous à votre compte",
"url": "URL uCentralSec",
"username": "Nom d'utilisateur"
},
"reboot": {
"directions": "Quand souhaitez-vous redémarrer cet appareil ?",
"now": "Souhaitez-vous redémarrer cet appareil maintenant ?",
"title": "Redémarrer"
},
"scan": {
@@ -167,10 +185,23 @@
"show_latest": "Afficher les dernières statistiques JSON",
"title": "statistiques"
},
"status": {
"connection_status": "Statut de connexion",
"error": "Les données d'état ne sont pas disponibles",
"last_contact": "Dernier contact",
"load_averages": "Charge (moyenne 1 / 5 / 15 minutes)",
"localtime": "heure locale",
"memory": "Mémoire utilisée",
"percentage_free": "{{percentage}}% de {{total}} gratuit",
"percentage_used": "{{percentage}}% de {{total}} utilisé",
"title": "#{{serialNumber}} état",
"uptime": "La disponibilité",
"used_total_memory": "{{used}} utilisé / {{total}} total"
},
"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",
"download_trace": "Cliquez ici pour télécharger",
"download_trace": "Télécharger le fichier de trace",
"packets": "Paquets",
"title": "Trace",
"trace": "Trace",

View File

@@ -14,7 +14,9 @@
"blink": {
"blink": "Piscar",
"device_leds": "LEDs do dispositivo",
"execute_now": "Você gostaria de definir este padrão agora?",
"pattern": "Escolha o padrão que deseja usar:",
"set_leds": "Definir LEDs",
"when_blink_leds": "Quando você gostaria de fazer os LEDs do dispositivo piscarem?"
},
"commands": {
@@ -23,6 +25,8 @@
"title": "Histórico de Comandos"
},
"common": {
"add": "Adicionar",
"adding_ellipsis": "Adicionando ...",
"are_you_sure": "Você tem certeza?",
"cancel": "Cancelar",
"certificate": "Certificado",
@@ -36,7 +40,11 @@
"connected": "Conectado",
"copied": "Copiado!",
"copy_to_clipboard": "Copiar para área de transferência",
"created_by": "Criado Por",
"custom_date": "Data personalizada",
"date": "Encontro",
"day": "dia",
"days": "dias",
"delete": "Excluir",
"details": "Detalhes",
"device_list": "Lista de Dispositivos",
@@ -52,6 +60,8 @@
"exit": "Saída",
"firmware": "Firmware",
"from": "De",
"hour": "hora",
"hours": "horas",
"id": "identidade",
"ip_address": "Endereço de IP",
"later_tonight": "Logo à noite",
@@ -60,9 +70,12 @@
"logout": "Sair",
"mac": "Endereço MAC",
"manufacturer": "Fabricante",
"minute": "minuto",
"minutes": "minutos",
"na": "N / D",
"need_date": "Você precisa de um encontro ...",
"no": "Não",
"no_items": "Nenhum item",
"not_connected": "Não conectado",
"off": "Fora",
"on": "em",
@@ -73,6 +86,8 @@
"save": "Salve",
"saving": "Salvando ...",
"schedule": "Cronograma",
"second": "segundo",
"seconds": "segundos",
"seconds_elapsed": "Segundos decorridos",
"serial_number": "Número de série",
"start": "Começar",
@@ -92,6 +107,7 @@
"last_configuration_change": "Última Mudança de Configuração",
"last_configuration_download": "Último download da configuração",
"location": "Localização",
"note": "Nota",
"notes": "notas",
"owner": "Proprietário",
"title": "Configuração",
@@ -140,14 +156,16 @@
"login": "Entrar",
"login_error": "Erro de login, confirme se seu nome de usuário, senha e url de gateway são válidos",
"password": "Senha",
"please_enter_gateway": "Insira um URL de gateway",
"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",
"sign_in_to_account": "Faça login em sua conta",
"url": "URL uCentralSec",
"username": "Nome de usuário"
},
"reboot": {
"directions": "Quando você gostaria de reinicializar este dispositivo?",
"now": "Você gostaria de reiniciar este dispositivo agora?",
"title": "Reiniciar"
},
"scan": {
@@ -167,10 +185,23 @@
"show_latest": "Mostrar estatísticas mais recentes JSON",
"title": "Estatisticas"
},
"status": {
"connection_status": "Status da conexão",
"error": "Dados de status indisponíveis",
"last_contact": "Último contato",
"load_averages": "Carga (1/5/15 minutos em média)",
"localtime": "Horário local",
"memory": "Memória Usada",
"percentage_free": "{{percentage}}% de {{total}} grátis",
"percentage_used": "{{percentage}}% de {{total}} usado",
"title": "#{{serialNumber}} status",
"uptime": "Tempo de atividade",
"used_total_memory": "{{used}} usado / {{total}} total"
},
"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",
"download_trace": "Clique aqui para baixar",
"download_trace": "Baixar arquivo de rastreamento",
"packets": "Pacotes",
"title": "Vestígio",
"trace": "Vestígio",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "favicon.svg",
"type": "image/svg",
"sizes": "192x192"
},
{
"src": "favicon.svg",
"type": "image/svg",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -1,165 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 141.5 185.6" style="enable-background:new 0 0 141.5 185.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414141;}
.st1{fill:#FFFFFF;}
.st2{fill:#FED206;}
.st3{fill:#EB6F53;}
.st4{fill:#3BA9B6;}
</style>
<g>
<g>
<path class="st0" d="M120.7,183.9H21.5c-10.8,0-19.5-8.7-19.5-19.5V20.5c0-10.8,8.7-19.5,19.5-19.5h99.2
c10.8,0,19.5,8.7,19.5,19.5v143.9C140.2,175.2,131.5,183.9,120.7,183.9z"/>
<g>
<g>
<g>
<path class="st1" d="M46.3,166.2v-3.4h-1.2v-0.6h3.1v0.6H47v3.4H46.3z"/>
</g>
<g>
<path class="st1" d="M49,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H49z"/>
</g>
<g>
<path class="st1" d="M52.6,166.2v-4h0.7v3.4h1.8v0.6H52.6z"/>
</g>
<g>
<path class="st1" d="M55.7,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H55.7z"/>
</g>
<g>
<path class="st1" d="M59.1,164.2c0-1.2,0.9-2.1,2.1-2.1c0.8,0,1.3,0.4,1.6,0.9l-0.6,0.3c-0.2-0.3-0.6-0.6-1-0.6
c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4c0.4,0,0.8-0.3,1-0.6l0.6,0.3c-0.3,0.5-0.8,0.9-1.6,0.9
C60,166.3,59.1,165.5,59.1,164.2z"/>
</g>
<g>
<path class="st1" d="M63.2,164.2c0-1.2,0.8-2.1,2-2.1c1.2,0,2,0.9,2,2.1c0,1.2-0.8,2.1-2,2.1C64,166.3,63.2,165.4,63.2,164.2z
M66.5,164.2c0-0.8-0.5-1.4-1.3-1.4c-0.8,0-1.3,0.6-1.3,1.4c0,0.8,0.5,1.4,1.3,1.4C66,165.7,66.5,165,66.5,164.2z"/>
</g>
<g>
<path class="st1" d="M71.3,166.2v-3.1l-1.2,3.1h-0.3l-1.2-3.1v3.1h-0.7v-4h1l1.1,2.7l1.1-2.7h1v4H71.3z"/>
</g>
<g>
<path class="st1" d="M75.7,166.2v-4h0.7v4H75.7z"/>
</g>
<g>
<path class="st1" d="M80.4,166.2l-2.1-2.8v2.8h-0.7v-4h0.7l2,2.8v-2.8h0.7v4H80.4z"/>
</g>
<g>
<path class="st1" d="M82.3,166.2v-4H85v0.6h-2v1h2v0.6h-2v1.7H82.3z"/>
</g>
<g>
<path class="st1" d="M87.9,166.2l-0.9-1.5h-0.7v1.5h-0.7v-4h1.7c0.8,0,1.3,0.5,1.3,1.2c0,0.7-0.5,1.1-0.9,1.2l1,1.6H87.9z
M88,163.5c0-0.4-0.3-0.6-0.7-0.6h-1v1.3h1C87.7,164.1,88,163.9,88,163.5z"/>
</g>
<g>
<path class="st1" d="M92.4,166.2l-0.3-0.8h-1.8l-0.3,0.8h-0.8l1.6-4h0.9l1.6,4H92.4z M91.2,162.9l-0.7,1.9h1.4L91.2,162.9z"/>
</g>
<g>
<path class="st1" d="M95.8,166.2v-4h1.5c0.8,0,1.2,0.5,1.2,1.2c0,0.6-0.4,1.2-1.2,1.2h-1.2v1.7H95.8z M98.2,163.4
c0-0.5-0.3-0.9-0.9-0.9h-1.1v1.7h1.1C97.8,164.3,98.2,163.9,98.2,163.4z"/>
</g>
<g>
<path class="st1" d="M101.5,166.2l-1.1-1.6h-0.9v1.6h-0.3v-4h1.5c0.7,0,1.2,0.4,1.2,1.2c0,0.7-0.5,1.1-1.1,1.1l1.2,1.7H101.5z
M101.6,163.4c0-0.5-0.4-0.9-0.9-0.9h-1.1v1.7h1.1C101.2,164.3,101.6,163.9,101.6,163.4z"/>
</g>
<g>
<path class="st1" d="M102.8,164.2c0-1.2,0.8-2.1,1.9-2.1c1.2,0,1.9,0.9,1.9,2.1c0,1.2-0.8,2.1-1.9,2.1
C103.6,166.3,102.8,165.4,102.8,164.2z M106.3,164.2c0-1-0.6-1.7-1.6-1.7c-1,0-1.6,0.7-1.6,1.7c0,1,0.6,1.7,1.6,1.7
C105.7,166,106.3,165.2,106.3,164.2z"/>
</g>
<g>
<path class="st1" d="M106.9,165.8l0.2-0.3c0.2,0.2,0.4,0.4,0.8,0.4c0.5,0,0.9-0.4,0.9-0.9v-2.8h0.3v2.8c0,0.8-0.5,1.2-1.2,1.2
C107.5,166.3,107.2,166.1,106.9,165.8z"/>
</g>
<g>
<path class="st1" d="M110.4,166.2v-4h2.5v0.3h-2.2v1.5h2.1v0.3h-2.1v1.6h2.2v0.3H110.4z"/>
</g>
<g>
<path class="st1" d="M113.5,164.2c0-1.2,0.9-2.1,2-2.1c0.6,0,1.1,0.3,1.5,0.7l-0.3,0.2c-0.3-0.3-0.7-0.6-1.2-0.6
c-0.9,0-1.7,0.7-1.7,1.7c0,1,0.7,1.7,1.7,1.7c0.5,0,0.9-0.2,1.2-0.6l0.3,0.2c-0.4,0.4-0.8,0.7-1.5,0.7
C114.4,166.3,113.5,165.5,113.5,164.2z"/>
</g>
<g>
<path class="st1" d="M118.7,166.2v-3.7h-1.3v-0.3h2.9v0.3H119v3.7H118.7z"/>
</g>
</g>
<g>
<polygon class="st1" points="26.3,163.8 31.6,158.5 36.9,163.8 37.7,163.8 31.6,157.6 25.5,163.8 "/>
<polygon class="st1" points="36.9,164.7 31.6,170 26.3,164.7 25.5,164.7 31.6,170.8 37.7,164.7 "/>
<polygon class="st1" points="31,163.8 36.3,158.5 41.6,163.8 42.5,163.8 36.3,157.6 30.2,163.8 "/>
<polygon class="st1" points="41.6,164.7 36.3,170 31,164.7 30.2,164.7 36.3,170.8 42.5,164.7 "/>
</g>
</g>
<g>
<path class="st1" d="M33.2,100.7c-4.6,0-8.3,3.7-8.3,8.3s3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3S37.8,100.7,33.2,100.7z"/>
</g>
<g>
<g>
<g>
<path class="st2" d="M33.2,35.2c40.7,0,73.8,33.1,73.8,73.8c0,0.7,0,1.4,0,2.1c0,1.7,0.6,3.3,1.7,4.6c1.2,1.2,2.8,1.9,4.5,2
l0.2,0c3.5,0,6.3-2.7,6.4-6.2c0-0.8,0-1.7,0-2.5c0-47.7-38.8-86.6-86.6-86.6c-0.8,0-1.7,0-2.5,0c-1.7,0-3.3,0.8-4.5,2
c-1.2,1.2-1.8,2.9-1.7,4.6c0.1,3.5,3,6.3,6.6,6.2C31.8,35.2,32.5,35.2,33.2,35.2z"/>
</g>
</g>
</g>
<g>
<g>
<g>
<path class="st3" d="M33.2,60.5c26.7,0,48.5,21.7,48.5,48.5c0,0.6,0,1.3,0,2c-0.1,1.7,0.5,3.3,1.7,4.6c1.2,1.3,2.7,2,4.4,2.1
c1.7,0.1,3.3-0.5,4.6-1.7c1.2-1.2,2-2.7,2-4.4c0-0.9,0.1-1.8,0.1-2.6c0-33.8-27.5-61.2-61.2-61.2c-0.8,0-1.6,0-2.6,0.1
c-1.7,0.1-3.3,0.8-4.4,2.1c-1.2,1.3-1.8,2.9-1.7,4.6s0.8,3.3,2.1,4.4c1.3,1.2,2.9,1.8,4.6,1.7C31.9,60.5,32.6,60.5,33.2,60.5z"
/>
</g>
</g>
</g>
<g>
<g>
<g>
<path class="st4" d="M33.2,86.7c12.3,0,22.3,10,22.3,22.3c0,0.5,0,1.1-0.1,1.8c-0.3,3.5,2.3,6.6,5.8,6.9
c3.5,0.3,6.6-2.3,6.9-5.8c0.1-1,0.1-1.9,0.1-2.8c0-19.3-15.7-35.1-35.1-35.1c-0.9,0-1.8,0-2.8,0.1c-1.7,0.1-3.2,0.9-4.3,2.2
c-1.1,1.3-1.6,2.9-1.5,4.6c0.1,1.7,0.9,3.2,2.2,4.3c1.3,1.1,2.9,1.6,4.6,1.5C32.1,86.7,32.7,86.7,33.2,86.7z"/>
</g>
</g>
</g>
</g>
<g>
<path class="st1" d="M35.8,130.4c1.1,0.6,2.1,1.5,2.7,2.6c0.7,1.1,1,2.3,1,3.7s-0.3,2.6-1,3.7c-0.7,1.1-1.6,2-2.7,2.6
c-1.1,0.6-2.4,1-3.8,1s-2.7-0.3-3.8-1c-1.1-0.6-2.1-1.5-2.7-2.6c-0.7-1.1-1-2.3-1-3.7c0-1.3,0.3-2.6,1-3.7c0.7-1.1,1.6-2,2.7-2.6
c1.1-0.6,2.4-0.9,3.8-0.9C33.4,129.5,34.7,129.8,35.8,130.4z M29.9,132.9c-0.7,0.4-1.2,0.9-1.6,1.6s-0.6,1.4-0.6,2.2
c0,0.8,0.2,1.6,0.6,2.3c0.4,0.7,0.9,1.2,1.6,1.6c0.7,0.4,1.4,0.6,2.1,0.6c0.8,0,1.5-0.2,2.1-0.6c0.6-0.4,1.2-0.9,1.5-1.6
c0.4-0.7,0.6-1.4,0.6-2.3c0-0.8-0.2-1.6-0.6-2.2s-0.9-1.2-1.5-1.6c-0.6-0.4-1.4-0.6-2.1-0.6C31.3,132.3,30.6,132.5,29.9,132.9z"/>
<path class="st1" d="M50.6,133.6c0.8,0.5,1.4,1.1,1.8,2c0.4,0.8,0.6,1.8,0.6,2.9c0,1.1-0.2,2-0.6,2.8c-0.4,0.8-1,1.5-1.8,1.9
c-0.8,0.5-1.6,0.7-2.6,0.7c-0.7,0-1.4-0.1-2-0.4s-1.1-0.7-1.5-1.2v5.4h-3.1V133h3.1v1.6c0.4-0.5,0.9-1,1.4-1.2s1.2-0.4,2-0.4
C48.9,132.9,49.8,133.1,50.6,133.6z M49.1,140.5c0.5-0.6,0.7-1.3,0.7-2.2c0-0.9-0.2-1.6-0.7-2.1c-0.5-0.6-1.1-0.8-1.9-0.8
s-1.4,0.3-1.9,0.8c-0.5,0.6-0.8,1.3-0.8,2.1c0,0.9,0.2,1.6,0.8,2.2s1.1,0.8,1.9,0.8S48.6,141,49.1,140.5z"/>
<path class="st1" d="M63.4,134.4c0.9,1,1.4,2.4,1.4,4.2c0,0.3,0,0.6,0,0.7H57c0.2,0.7,0.5,1.2,1,1.6c0.5,0.4,1.1,0.6,1.8,0.6
c0.5,0,1-0.1,1.5-0.3s0.9-0.5,1.3-0.9l1.6,1.6c-0.5,0.6-1.2,1.1-2,1.4c-0.8,0.3-1.6,0.5-2.6,0.5c-1.1,0-2.1-0.2-3-0.7
s-1.5-1.1-2-1.9c-0.5-0.8-0.7-1.8-0.7-2.9c0-1.1,0.2-2.1,0.7-2.9s1.1-1.5,2-1.9c0.8-0.5,1.8-0.7,2.9-0.7
C61.2,132.9,62.5,133.4,63.4,134.4z M61.8,137.5c0-0.7-0.3-1.3-0.7-1.7s-1-0.6-1.7-0.6c-0.7,0-1.2,0.2-1.7,0.6
c-0.4,0.4-0.7,1-0.9,1.7H61.8z"/>
<path class="st1" d="M76.2,134c0.7,0.7,1.1,1.7,1.1,3v6.8h-3.1v-5.9c0-0.7-0.2-1.2-0.6-1.6s-0.9-0.6-1.5-0.6
c-0.8,0-1.4,0.3-1.8,0.8c-0.4,0.5-0.7,1.2-0.7,2v5.3h-3.1V133h3.1v1.9c0.7-1.3,2-2,3.7-2C74.6,132.8,75.5,133.2,76.2,134z"/>
<path class="st1" d="M96,129.7h3.3l-4.7,14h-3.3l-2.9-10.1l-3,10.1h-3.2l-4.7-14h3.4l3,10.7l3-10.7H90l3.1,10.7L96,129.7z"/>
<path class="st1" d="M103.3,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C102.6,128.2,103,128.3,103.3,128.7z M100.6,133h3.1
v10.8h-3.1V133z"/>
<path class="st1" d="M106.5,129.7h10.1l0,2.6h-6.9v3.4h6.3v2.6h-6.3v5.3h-3.2V129.7z"/>
<path class="st1" d="M120.9,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C120.1,128.2,120.5,128.3,120.9,128.7z M118.1,133h3.1
v10.8h-3.1V133z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -1,2 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

View File

@@ -1,7 +1,9 @@
import React, { useEffect } from 'react';
import { HashRouter, Route, Switch } from 'react-router-dom';
import React from 'react';
import { HashRouter, Switch } from 'react-router-dom';
import 'scss/style.scss';
import { useSelector, useDispatch } from 'react-redux';
import Router from 'router';
import { AuthProvider } from 'contexts/AuthProvider';
import { checkIfJson } from 'utils/helper';
const loading = (
<div className="pt-3 text-center">
@@ -9,32 +11,22 @@ const loading = (
</div>
);
const TheLayout = React.lazy(() => import('layout'));
const Login = React.lazy(() => import('pages/LoginPage'));
const App = () => {
const isLoggedIn = useSelector((state) => state.connected);
const dispatch = useDispatch();
useEffect(() => {
const token = sessionStorage.getItem('access_token');
if (token !== undefined && token !== null) {
dispatch({ type: 'set', connected: true });
}
}, [dispatch]);
const storageToken = sessionStorage.getItem('access_token');
const apiEndpoints = checkIfJson(sessionStorage.getItem('gateway_endpoints'))
? JSON.parse(sessionStorage.getItem('gateway_endpoints'))
: {};
return (
<HashRouter>
<React.Suspense fallback={loading}>
<Switch>
<Route
path="/"
name="Devices"
render={(props) => (isLoggedIn ? <TheLayout {...props} /> : <Login {...props} />)}
/>
</Switch>
</React.Suspense>
</HashRouter>
<AuthProvider token={storageToken ?? ''} apiEndpoints={apiEndpoints}>
<HashRouter>
<React.Suspense fallback={loading}>
<Switch>
<Router />
</Switch>
</React.Suspense>
</HashRouter>
</AuthProvider>
);
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -122,14 +122,11 @@ import {
cilXCircle,
cilWarning,
} from '@coreui/icons';
import { sygnet } from './sygnet';
import { logo } from './logo';
import { logoNegative } from './logo-negative';
import { logo } from './CoreuiLogo';
export const icons = {
sygnet,
logo,
logoNegative,
cilAlignCenter,
cilAlignLeft,
cilAlignRight,

View File

@@ -1,33 +0,0 @@
export const logoNegative = [
'608 134',
`
<title>coreui react pro logo</title>
<g>
<g style="fill:#80d0ff;">
<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:#fff;">
<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>
`,
];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,12 +0,0 @@
export const sygnet = [
'160 160',
`
<title>coreui logo</title>
<g>
<g style="fill:#fff;">
<path d="M125,47.091,86,24.5743a12,12,0,0,0-12,0L35,47.091a12.0336,12.0336,0,0,0-6,10.3923v45.0334a12.0335,12.0335,0,0,0,6,10.3923l39,22.5166a11.9993,11.9993,0,0,0,12,0l39-22.5166a12.0335,12.0335,0,0,0,6-10.3923V57.4833A12.0336,12.0336,0,0,0,125,47.091Zm-2,55.4257a4,4,0,0,1-2,3.464L82,128.4974a4,4,0,0,1-4,0L39,105.9807a4,4,0,0,1-2-3.464V57.4833a4,4,0,0,1,2-3.4641L78,31.5025a4,4,0,0,1,4,0l39,22.5167a4,4,0,0,1,2,3.4641Z"/>
<path d="M103.0216,93.0379h-2.866a4,4,0,0,0-1.9246.4935L80.95,103.0167,61,91.4981V68.5206L80.95,57.002l17.2894,9.455a4,4,0,0,0,1.9192.4905h2.8632a2,2,0,0,0,2-2V62.2357a2,2,0,0,0-1.04-1.7547L84.793,49.9854a8.0391,8.0391,0,0,0-7.8428.09L57,61.5929A8.0243,8.0243,0,0,0,53,68.5216v22.976a8,8,0,0,0,4,6.9283l19.95,11.5185a8.0422,8.0422,0,0,0,7.8433.0879l19.19-10.5311a2,2,0,0,0,1.0378-1.7534v-2.71A2,2,0,0,0,103.0216,93.0379Z"/>
</g>
</g>
`,
];

View File

@@ -8,7 +8,6 @@ import {
CSwitch,
CCol,
CRow,
CForm,
CFormGroup,
CInputRadio,
CLabel,
@@ -17,10 +16,10 @@ import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { dateToUnix } from 'utils/helper';
import 'react-widgets/styles.css';
import { getToken } from 'utils/authHelper';
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';
@@ -30,16 +29,17 @@ import styles from './index.module.scss';
const BlinkModal = ({ show, toggleModal }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [isNow, setIsNow] = useState(false);
const [waiting, setWaiting] = useState(false);
const [chosenDate, setChosenDate] = useState(new Date().toString());
const [chosenPattern, setPattern] = useState('on');
const [result, setResult] = useState(null);
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const toggleNow = () => {
setIsNow(!isNow);
}
};
const setDate = (date) => {
if (date) {
@@ -58,12 +58,10 @@ const BlinkModal = ({ show, toggleModal }) => {
const doAction = () => {
setWaiting(true);
const token = getToken();
const utcDate = new Date(chosenDate);
const parameters = {
serialNumber: selectedDeviceId,
serialNumber: deviceSerialNumber,
when: isNow ? 0 : dateToUnix(utcDate),
pattern: chosenPattern,
duration: 30,
@@ -71,11 +69,15 @@ const BlinkModal = ({ show, toggleModal }) => {
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/leds`, parameters, { headers })
.post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/leds`,
parameters,
{ headers },
)
.then(() => {
setResult('success');
})
@@ -98,50 +100,52 @@ const BlinkModal = ({ show, toggleModal }) => {
) : (
<div>
<CModalBody>
<h6>{t('blink.when_blink_leds')}</h6>
<CRow className={styles.spacedRow}>
<CCol md="7">{t('blink.pattern')}</CCol>
<CCol>
<CForm disabled={waiting}>
<CFormGroup variant="checkbox" onClick={() => setPattern('on')}>
<CInputRadio
defaultChecked={chosenPattern === 'on'}
id="radio1"
name="radios"
value="option1"
/>
<CLabel variant="checkbox" htmlFor="radio1">
{t('common.on')}
</CLabel>
</CFormGroup>
<CFormGroup variant="checkbox" onClick={() => setPattern('off')}>
<CInputRadio
defaultChecked={chosenPattern === 'off'}
id="radio2"
name="radios"
value="option2"
/>
<CLabel variant="checkbox" htmlFor="radio2">
{t('common.off')}
</CLabel>
</CFormGroup>
<CFormGroup variant="checkbox" onClick={() => setPattern('blink')}>
<CInputRadio
defaultChecked={chosenPattern === 'blink'}
id="radio3"
name="radios"
value="option3"
/>
<CLabel variant="checkbox" htmlFor="radio3">
{t('blink.blink')}
</CLabel>
</CFormGroup>
</CForm>
<CFormGroup row>
<CCol md="3">
<CLabel>{t('blink.pattern')}</CLabel>
</CCol>
</CRow>
<CCol>
<CFormGroup variant="custom-radio" onClick={() => setPattern('on')} inline>
<CInputRadio
custom
defaultChecked={chosenPattern === 'on'}
id="radio1"
name="radios"
value="option1"
/>
<CLabel variant="custom-checkbox" htmlFor="radio1">
{t('common.on')}
</CLabel>
</CFormGroup>
<CFormGroup variant="custom-radio" onClick={() => setPattern('off')} inline>
<CInputRadio
custom
defaultChecked={chosenPattern === 'off'}
id="radio2"
name="radios"
value="option2"
/>
<CLabel variant="custom-checkbox" htmlFor="radio2">
{t('common.off')}
</CLabel>
</CFormGroup>
<CFormGroup variant="custom-radio" onClick={() => setPattern('blink')} inline>
<CInputRadio
custom
defaultChecked={chosenPattern === 'blink'}
id="radio3"
name="radios"
value="option3"
/>
<CLabel variant="custom-checkbox" htmlFor="radio3">
{t('blink.blink')}
</CLabel>
</CFormGroup>
</CCol>
</CFormGroup>
<CRow className={styles.spacedRow}>
<CCol md="8">
<p className={styles.spacedText}>{t('common.execute_now')}</p>
<p className={styles.spacedText}>{t('blink.execute_now')}</p>
</CCol>
<CCol>
<CSwitch
@@ -156,7 +160,7 @@ const BlinkModal = ({ show, toggleModal }) => {
</CRow>
<CRow hidden={isNow} className={styles.spacedRow}>
<CCol md="4" className={styles.spacedDate}>
<p>{t('common.date')}</p>
<p>{t('common.custom_date')}</p>
</CCol>
<CCol xs="12" md="8">
<DatePicker
@@ -173,7 +177,7 @@ const BlinkModal = ({ show, toggleModal }) => {
</CModalBody>
<CModalFooter>
<LoadingButton
label={isNow ? t('blink.blink') : t('common.schedule')}
label={isNow ? t('blink.set_leds') : t('common.schedule')}
isLoadingLabel={t('common.loading_ellipsis')}
isLoading={waiting}
action={doAction}

View File

@@ -13,13 +13,11 @@ import {
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import DatePicker from 'react-widgets/DatePicker';
import { cilCloudDownload, cilSync } from '@coreui/icons';
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faClipboardCheck } from '@fortawesome/free-solid-svg-icons';
import { cilCloudDownload, cilSync, cilCalendarCheck } from '@coreui/icons';
import { prettyDate, dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper';
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';
@@ -27,8 +25,10 @@ import WifiScanResultModalWidget from 'components/WifiScanResultModal';
import DeviceCommandsCollapse from './DeviceCommandsCollapse';
import styles from './index.module.scss';
const DeviceCommands = ({ selectedDeviceId }) => {
const DeviceCommands = () => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
// Wifiscan result related
const [chosenWifiScan, setChosenWifiScan] = useState(null);
const [showScanModal, setShowScanModal] = useState(false);
@@ -92,7 +92,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
params: {
limit: commandLimit,
@@ -109,7 +109,12 @@ const DeviceCommands = ({ selectedDeviceId }) => {
}
axiosInstance
.get(`/commands?serialNumber=${encodeURIComponent(selectedDeviceId)}${extraParams}`, options)
.get(
`${endpoints.ucentralgw}/api/v1/commands?serialNumber=${encodeURIComponent(
deviceSerialNumber,
)}${extraParams}`,
options,
)
.then((response) => {
setCommands(response.data.commands);
})
@@ -124,13 +129,16 @@ const DeviceCommands = ({ selectedDeviceId }) => {
const options = {
headers: {
Accept: 'application/octet-stream',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
responseType: 'arraybuffer',
};
axiosInstance
.get(`/file/${uuid}?serialNumber=${selectedDeviceId}`, options)
.get(
`${endpoints.ucentralgw}/api/v1/file/${uuid}?serialNumber=${deviceSerialNumber}`,
options,
)
.then((response) => {
const blob = new Blob([response.data], { type: 'application/octet-stream' });
const link = document.createElement('a');
@@ -147,11 +155,11 @@ const DeviceCommands = ({ selectedDeviceId }) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
};
return axiosInstance
.delete(`/command/${uuidDelete}`, options)
.delete(`${endpoints.ucentralgw}/api/v1/command/${uuidDelete}`, options)
.then(() => {
deleteCommandFromList(uuidDelete);
setUuidDelete('');
@@ -233,12 +241,12 @@ const DeviceCommands = ({ selectedDeviceId }) => {
];
useEffect(() => {
if (selectedDeviceId && start !== '' && end !== '') {
if (deviceSerialNumber && start !== '' && end !== '') {
getCommands();
} else if (selectedDeviceId && start === '' && end === '') {
} else if (deviceSerialNumber && start === '' && end === '') {
getCommands();
}
}, [selectedDeviceId, start, end]);
}, [deviceSerialNumber, start, end]);
useEffect(() => {
eventBus.on('actionCompleted', () => refreshCommands());
@@ -249,7 +257,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
}, []);
useEffect(() => {
if (selectedDeviceId) {
if (deviceSerialNumber) {
setCommandLimit(25);
setLoadingMore(false);
setShowLoadingMore(true);
@@ -257,7 +265,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
setEnd('');
getCommands();
}
}, [selectedDeviceId]);
}, [deviceSerialNumber]);
useEffect(() => {
if (commandLimit !== 25) {
@@ -313,9 +321,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
items={commands ?? []}
fields={columns}
className={styles.whiteIcon}
columnFilter
sorter
sorterValue={{ column: 'submitted', desc: 'true' }}
sorterValue={{ column: 'created', desc: 'true' }}
scopedSlots={{
completed: (item) => (
<td>
@@ -363,12 +369,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
{item.command === 'trace' ? (
<CIcon content={cilCloudDownload} size="lg" />
) : (
<FontAwesomeIcon
icon={faClipboardCheck}
className={[styles.customIconHeight, 'c-icon c-icon-lg'].join(
' ',
)}
/>
<CIcon content={cilCalendarCheck} size="lg" />
)}
</CButton>
</CPopover>
@@ -453,8 +454,4 @@ const DeviceCommands = ({ selectedDeviceId }) => {
);
};
DeviceCommands.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceCommands;

View File

@@ -7,7 +7,7 @@
}
.scrollableBox {
height: 400px;
height: 200px;
}
.whiteIcon {

View File

@@ -16,9 +16,9 @@ import {
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import 'react-widgets/styles.css';
import { getToken } from 'utils/authHelper';
import { useAuth } from 'contexts/AuthProvider';
import { useDevice } from 'contexts/DeviceProvider';
import { checkIfJson } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus';
@@ -27,6 +27,8 @@ import styles from './index.module.scss';
const ConfigureModal = ({ show, toggleModal }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [hadSuccess, setHadSuccess] = useState(false);
const [hadFailure, setHadFailure] = useState(false);
const [doingNow, setDoingNow] = useState(false);
@@ -36,7 +38,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
const [checkingIfSure, setCheckingIfSure] = useState(false);
const [errorJson, setErrorJson] = useState(false);
const [inputKey, setInputKey] = useState(0);
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
let fileReader;
const confirmingIfSure = () => {
@@ -69,10 +71,8 @@ const ConfigureModal = ({ show, toggleModal }) => {
setHadSuccess(false);
setWaiting(true);
const token = getToken();
const parameters = {
serialNumber: selectedDeviceId,
serialNumber: deviceSerialNumber,
when: 0,
UUID: 1,
configuration: JSON.parse(newConfig),
@@ -80,11 +80,15 @@ const ConfigureModal = ({ show, toggleModal }) => {
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/configure`, parameters, { headers })
.post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/configure`,
parameters,
{ headers },
)
.then(() => {
setHadSuccess(true);
})

View File

@@ -5,28 +5,29 @@ import { cilClone } from '@coreui/icons';
import PropTypes from 'prop-types';
import { CButton, CPopover } from '@coreui/react';
const CopyToClipboardButton = ({content, size}) => {
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>
<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
size: PropTypes.string,
};
CopyToClipboardButton.defaultProps = {

View File

@@ -6,12 +6,15 @@ import PropTypes from 'prop-types';
import ConfirmFooter from 'components/ConfirmFooter';
import { dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper';
import { useDevice } from 'contexts/DeviceProvider';
import { useAuth } from 'contexts/AuthProvider';
import eventBus from 'utils/eventBus';
import styles from './index.module.scss';
const DeleteLogModal = ({ serialNumber, show, toggle, object }) => {
const DeleteLogModal = ({ show, toggle, object }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [loading, setLoading] = useState(false);
const [maxDate, setMaxDate] = useState(new Date().toString());
@@ -27,14 +30,14 @@ const DeleteLogModal = ({ serialNumber, show, toggle, object }) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
params: {
endDate: dateToUnix(maxDate),
},
};
return axiosInstance
.delete(`/device/${serialNumber}/${object}`, options)
.delete(`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/${object}`, options)
.then(() => {})
.catch(() => {})
.finally(() => {
@@ -94,7 +97,6 @@ DeleteLogModal.propTypes = {
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
object: PropTypes.string.isRequired,
serialNumber: PropTypes.string.isRequired,
};
export default DeleteLogModal;

View File

@@ -1,9 +1,9 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { CButton, CCard, CCardHeader, CCardBody, CRow, CCol } from '@coreui/react';
import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper';
import { useAuth } from 'contexts/AuthProvider';
import { useDevice } from 'contexts/DeviceProvider';
import LoadingButton from 'components/LoadingButton';
import RebootModal from 'components/RebootModal';
import FirmwareUpgradeModal from 'components/FirmwareUpgradeModal';
@@ -15,8 +15,10 @@ import FactoryResetModal from 'components/FactoryResetModal';
import styles from './index.module.scss';
const DeviceActions = ({ selectedDeviceId }) => {
const DeviceActions = () => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [showRebootModal, setShowRebootModal] = useState(false);
const [showBlinkModal, setShowBlinkModal] = useState(false);
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
@@ -59,12 +61,15 @@ const DeviceActions = ({ selectedDeviceId }) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`/device/${encodeURIComponent(selectedDeviceId)}/rtty`, options)
.get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/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');
@@ -78,7 +83,9 @@ const DeviceActions = ({ selectedDeviceId }) => {
return (
<CCard>
<CCardHeader><div className="text-value-lg">{t('actions.title')}</div></CCardHeader>
<CCardHeader>
<div className="text-value-lg">{t('actions.title')}</div>
</CCardHeader>
<CCardBody>
<CRow>
<CCol>
@@ -143,8 +150,4 @@ const DeviceActions = ({ selectedDeviceId }) => {
);
};
DeviceActions.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceActions;

View File

@@ -4,11 +4,8 @@ import {
CCard,
CCardHeader,
CCardBody,
CFormGroup,
CCol,
CLabel,
CForm,
CInput,
CCollapse,
CCardFooter,
CButton,
@@ -16,17 +13,20 @@ import {
CPopover,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import PropTypes from 'prop-types';
import { cilWindowMaximize } from '@coreui/icons';
import { prettyDate } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper';
import { useAuth } from 'contexts/AuthProvider';
import { useDevice } from 'contexts/DeviceProvider';
import CopyToClipboardButton from 'components/CopyToClipboardButton';
import DeviceNotes from 'components/DeviceNotes';
import DeviceConfigurationModal from './DeviceConfigurationModal';
import styles from './index.module.scss';
const DeviceConfiguration = ({ selectedDeviceId }) => {
const DeviceConfiguration = () => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [collapse, setCollapse] = useState(false);
const [showModal, setShowModal] = useState(false);
const [device, setDevice] = useState(null);
@@ -44,12 +44,15 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`/device/${encodeURIComponent(selectedDeviceId)}`, options)
.get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`,
options,
)
.then((response) => {
setDevice(response.data);
})
@@ -57,8 +60,8 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
};
useEffect(() => {
if (selectedDeviceId) getDevice();
}, [selectedDeviceId]);
if (deviceSerialNumber) getDevice();
}, [deviceSerialNumber]);
if (device) {
return (
@@ -66,7 +69,9 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
<CCard>
<CCardHeader>
<CRow>
<CCol><div className="text-value-lg">{t('configuration.title')}</div></CCol>
<CCol>
<div className="text-value-lg">{t('configuration.title')}</div>
</CCol>
<CCol>
<div className={styles.alignRight}>
<CPopover content={t('configuration.view_json')}>
@@ -79,122 +84,115 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
</CRow>
</CCardHeader>
<CCardBody>
<CForm
action=""
method="post"
encType="multipart/form-data"
className="form-horizontal"
>
<CFormGroup row>
<CCol md="3">
<CLabel>{t('common.uuid')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{device.UUID}
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol md="3">
<CLabel>{t('common.serial_number')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{device.serialNumber}
<CopyToClipboardButton size="sm" content={device.serialNumber}/>
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol md="3">
<CLabel>{t('configuration.type')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{device.deviceType}
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol md="3">
<CLabel>{t('configuration.last_configuration_change')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{prettyDate(device.lastConfigurationChange)}
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol md="3">
<CLabel>{t('common.mac')} :</CLabel>
</CCol>
<CCol xs="12" md="9">
{device.macAddress}
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol md="3">
<CLabel>{t('configuration.created')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{prettyDate(device.createdTimestamp)}
</CCol>
</CFormGroup>
<CFormGroup row>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('common.uuid')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{device.UUID}
</CCol>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('common.serial_number')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{device.serialNumber}
<CopyToClipboardButton size="sm" content={device.serialNumber} />
</CCol>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('configuration.type')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{device.deviceType}
</CCol>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('configuration.last_configuration_change')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{prettyDate(device.lastConfigurationChange)}
</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}>
<CCol md="3">
<CLabel>{t('configuration.last_configuration_download')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{prettyDate(device.lastConfigurationDownload)}
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol md="3" className={styles.topPadding}>
<CLabel>{t('configuration.device_password')} : </CLabel>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('common.manufacturer')} :</CLabel>
</CCol>
<CCol xs="12" md="9">
{device.devicePassword === '' ? 'openwifi' : device.devicePassword}
<CopyToClipboardButton size="sm" content={device?.devicePassword === '' ? 'openwifi' : device.devicePassword}/>
{device.manufacturer}
</CCol>
</CFormGroup>
<CCollapse show={collapse}>
<CFormGroup row>
<CCol md="3">
<CLabel>{t('common.manufacturer')} :</CLabel>
</CCol>
<CCol xs="12" md="9">
{device.manufacturer}
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol md="3">
<CLabel htmlFor="text-input">{t('configuration.notes')} :</CLabel>
</CCol>
<CCol xs="12" md="9">
<CInput id="text-input" name="text-input" placeholder={device.notes} />
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol md="3">
<CLabel>{t('configuration.owner')} :</CLabel>
</CCol>
<CCol xs="12" md="9">
{device.owner}
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol md="3">
<CLabel>{t('configuration.location')} :</CLabel>
</CCol>
<CCol xs="12" md="9">
{device.location}
</CCol>
</CFormGroup>
</CCollapse>
<CCardFooter>
<CButton show={collapse ? 'true' : 'false'} onClick={toggle} block>
<CIcon
className={styles.blackIcon}
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
size="lg"
/>
</CButton>
</CCardFooter>
</CForm>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('configuration.owner')} :</CLabel>
</CCol>
<CCol xs="12" md="9">
{device.owner}
</CCol>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('configuration.location')} :</CLabel>
</CCol>
<CCol xs="12" md="9">
{device.location}
</CCol>
</CRow>
</CCollapse>
<CCardFooter>
<CButton show={collapse ? 'true' : 'false'} onClick={toggle} block>
<CIcon
className={styles.blackIcon}
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
size="lg"
/>
</CButton>
</CCardFooter>
</CCardBody>
</CCard>
<DeviceConfigurationModal show={showModal} toggle={toggleModal} configuration={device} />
@@ -210,8 +208,4 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
);
};
DeviceConfiguration.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceConfiguration;

View File

@@ -13,3 +13,8 @@
.topPadding {
padding-top: 5px;
}
.spacedRow {
margin-top: 5px;
margin-bottom: 5px;
}

View File

@@ -15,17 +15,19 @@ import {
import CIcon from '@coreui/icons-react';
import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker';
import PropTypes from 'prop-types';
import { prettyDate, dateToUnix } from 'utils/helper';
import { useAuth } from 'contexts/AuthProvider';
import { useDevice } from 'contexts/DeviceProvider';
import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper';
import eventBus from 'utils/eventBus';
import LoadingButton from 'components/LoadingButton';
import DeleteLogModal from 'components/DeleteLogModal';
import styles from './index.module.scss';
const DeviceHealth = ({ selectedDeviceId }) => {
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);
@@ -68,7 +70,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
params: {
limit: logLimit,
@@ -85,7 +87,12 @@ const DeviceHealth = ({ selectedDeviceId }) => {
}
axiosInstance
.get(`/device/${encodeURIComponent(selectedDeviceId)}/healthchecks${extraParams}`, options)
.get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(
deviceSerialNumber,
)}/healthchecks${extraParams}`,
options,
)
.then((response) => {
setHealthChecks(response.data.values);
})
@@ -128,7 +135,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
];
useEffect(() => {
if (selectedDeviceId) {
if (deviceSerialNumber) {
setLogLimit(25);
setLoadingMore(false);
setShowLoadingMore(true);
@@ -136,7 +143,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
setEnd('');
getDeviceHealth();
}
}, [selectedDeviceId]);
}, [deviceSerialNumber]);
useEffect(() => {
if (logLimit !== 25) {
@@ -168,12 +175,12 @@ const DeviceHealth = ({ selectedDeviceId }) => {
}, [healthChecks]);
useEffect(() => {
if (selectedDeviceId && start !== '' && end !== '') {
if (deviceSerialNumber && start !== '' && end !== '') {
getDeviceHealth();
} else if (selectedDeviceId && start === '' && end === '') {
} else if (deviceSerialNumber && start === '' && end === '') {
getDeviceHealth();
}
}, [start, end, selectedDeviceId]);
}, [start, end, deviceSerialNumber]);
useEffect(() => {
eventBus.on('deletedHealth', () => getDeviceHealth());
@@ -185,8 +192,8 @@ const DeviceHealth = ({ selectedDeviceId }) => {
return (
<CWidgetDropdown
header={sanityLevel ? `${sanityLevel}%` : t('common.unknown')}
text={t('health.title')}
header={t('health.title')}
text={sanityLevel ? `${sanityLevel}%` : t('common.unknown')}
value={sanityLevel ?? 100}
color={barColor}
inverse="true"
@@ -210,11 +217,13 @@ const DeviceHealth = ({ selectedDeviceId }) => {
</div>
<CRow className={styles.spacedRow}>
<CCol>
{t('common.from')}:
{t('common.from')}
:
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
</CCol>
<CCol>
{t('common.to')}:
{t('common.to')}
:
<DatePicker includeTime onChange={(date) => modifyEnd(date)} />
</CCol>
</CRow>
@@ -281,7 +290,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
/>
</CButton>
<DeleteLogModal
serialNumber={selectedDeviceId}
serialNumber={deviceSerialNumber}
object="healthchecks"
show={showDeleteModal}
toggle={toggleDeleteModal}
@@ -292,8 +301,4 @@ const DeviceHealth = ({ selectedDeviceId }) => {
);
};
DeviceHealth.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceHealth;

View File

@@ -10,14 +10,14 @@ import {
CRow,
CCol,
CPopover,
CSelect,
} from '@coreui/react';
import ReactPaginate from 'react-paginate';
import { useTranslation } from 'react-i18next';
import Select from 'react-select';
import PropTypes from 'prop-types';
import { cilSync, cilInfo, cilBadge, cilBan } from '@coreui/icons';
import CIcon from '@coreui/icons-react';
import { getToken } from 'utils/authHelper';
import { useAuth } from 'contexts/AuthProvider';
import axiosInstance from 'utils/axiosInstance';
import { cleanBytesString } from 'utils/helper';
import meshIcon from 'assets/icons/Mesh.png';
@@ -29,6 +29,7 @@ import styles from './index.module.scss';
const DeviceList = () => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const [loadedSerials, setLoadedSerials] = useState(false);
const [serialNumbers, setSerialNumbers] = useState([]);
const [page, setPage] = useState(0);
@@ -38,16 +39,15 @@ const DeviceList = () => {
const [loading, setLoading] = useState(true);
const getSerialNumbers = () => {
const token = getToken();
setLoading(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.get('/devices?serialOnly=true', {
.get(`${endpoints.ucentralgw}/api/v1/devices?serialOnly=true`, {
headers,
})
.then((response) => {
@@ -60,12 +60,11 @@ const DeviceList = () => {
};
const getDeviceInformation = () => {
const token = getToken();
setLoading(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${currentToken}`,
};
const startIndex = page * devicesPerPage;
@@ -76,7 +75,7 @@ const DeviceList = () => {
.join(',');
axiosInstance
.get(`/devices?deviceWithStatus=true&select=${serialsToGet}`, {
.get(`${endpoints.ucentralgw}/api/v1/devices?deviceWithStatus=true&select=${serialsToGet}`, {
headers,
})
.then((response) => {
@@ -89,18 +88,22 @@ const DeviceList = () => {
};
const refreshDevice = (serialNumber) => {
const token = getToken();
setLoading(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.get(`/devices?deviceWithStatus=true&select=${encodeURIComponent(serialNumber)}`, {
headers,
})
.get(
`${endpoints.ucentralgw}/api/v1/devices?deviceWithStatus=true&select=${encodeURIComponent(
serialNumber,
)}`,
{
headers,
},
)
.then((response) => {
const device = response.data.devicesWithStatus[0];
const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber);
@@ -189,12 +192,6 @@ const DeviceListDisplay = ({
},
];
const selectOptions = [
{ value: '10', label: '10' },
{ value: '25', label: '25' },
{ value: '50', label: '50' },
];
const getDeviceIcon = (deviceType) => {
if (deviceType === 'AP_Default' || deviceType === 'AP') {
return <img src={apIcon} className={styles.icon} alt="AP" />;
@@ -281,13 +278,17 @@ const DeviceListDisplay = ({
<CCardHeader>
<CRow>
<CCol />
<CCol xs={2}>
<Select
isClearable={false}
options={selectOptions}
defaultValue={{ value: devicesPerPage, label: devicesPerPage }}
onChange={(value) => updateDevicesPerPage(value.value)}
/>
<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>
@@ -296,6 +297,7 @@ const DeviceListDisplay = ({
items={devices ?? []}
fields={columns}
hover
border
loading={loading}
scopedSlots={{
serialNumber: (item) => (
@@ -337,7 +339,9 @@ const DeviceListDisplay = ({
content={item.firmware ? item.firmware : t('common.na')}
placement="top"
>
<p style={{width: '225px'}} className="text-truncate">{item.firmware}</p>
<p style={{ width: '225px' }} className="text-truncate">
{item.firmware}
</p>
</CPopover>
</td>
),
@@ -347,7 +351,9 @@ const DeviceListDisplay = ({
content={item.compatible ? item.compatible : t('common.na')}
placement="top"
>
<p style={{width: '150px'}} className="text-truncate">{item.compatible}</p>
<p style={{ width: '150px' }} className="text-truncate">
{item.compatible}
</p>
</CPopover>
</td>
),
@@ -359,7 +365,9 @@ const DeviceListDisplay = ({
content={item.ipAddress ? item.ipAddress : t('common.na')}
placement="top"
>
<p style={{width: '150px'}} className="text-truncate">{item.ipAddress}</p>
<p style={{ width: '150px' }} className="text-truncate">
{item.ipAddress}
</p>
</CPopover>
</td>
),

View File

@@ -14,17 +14,19 @@ import {
import CIcon from '@coreui/icons-react';
import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker';
import PropTypes from 'prop-types';
import { prettyDate, dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper';
import { useAuth } from 'contexts/AuthProvider';
import { useDevice } from 'contexts/DeviceProvider';
import eventBus from 'utils/eventBus';
import LoadingButton from 'components/LoadingButton';
import DeleteLogModal from 'components/DeleteLogModal';
import styles from './index.module.scss';
const DeviceLogs = ({ selectedDeviceId }) => {
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);
@@ -65,7 +67,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
params: {
limit: logLimit,
@@ -82,7 +84,12 @@ const DeviceLogs = ({ selectedDeviceId }) => {
}
axiosInstance
.get(`/device/${encodeURIComponent(selectedDeviceId)}/logs${extraParams}`, options)
.get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(
deviceSerialNumber,
)}/logs${extraParams}`,
options,
)
.then((response) => {
setLogs(response.data.values);
})
@@ -125,7 +132,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
];
useEffect(() => {
if (selectedDeviceId) {
if (deviceSerialNumber) {
setLogLimit(25);
setLoadingMore(false);
setShowLoadingMore(true);
@@ -133,7 +140,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
setEnd('');
getLogs();
}
}, [selectedDeviceId]);
}, [deviceSerialNumber]);
useEffect(() => {
if (logLimit !== 25) {
@@ -150,12 +157,12 @@ const DeviceLogs = ({ selectedDeviceId }) => {
}, [logs]);
useEffect(() => {
if (selectedDeviceId && start !== '' && end !== '') {
if (deviceSerialNumber && start !== '' && end !== '') {
getLogs();
} else if (selectedDeviceId && start === '' && end === '') {
} else if (deviceSerialNumber && start === '' && end === '') {
getLogs();
}
}, [start, end, selectedDeviceId]);
}, [start, end, deviceSerialNumber]);
useEffect(() => {
eventBus.on('deletedLogs', () => getLogs());
@@ -258,7 +265,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
}
/>
<DeleteLogModal
serialNumber={selectedDeviceId}
serialNumber={deviceSerialNumber}
object="logs"
show={showDeleteModal}
toggle={toggleDeleteModal}
@@ -267,8 +274,4 @@ const DeviceLogs = ({ selectedDeviceId }) => {
);
};
DeviceLogs.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceLogs;

View File

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

View File

@@ -0,0 +1,15 @@
.scrollableBox {
height: 200px;
border-style: solid;
border-color: #ced2d8;
margin-bottom: 25px;
}
.table {
color: white;
}
.spacedRow {
margin-top: 5px;
margin-bottom: 20px;
}

View File

@@ -0,0 +1,36 @@
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);

View File

@@ -0,0 +1,197 @@
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';
const DeviceStatusCard = () => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [lastStats, setLastStats] = useState(null);
const [status, setStatus] = 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 getData = () => {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
const lastStatsRequest = axiosInstance.get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(
deviceSerialNumber,
)}/statistics?lastOnly=true`,
options,
);
const statusRequest = axiosInstance.get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/status`,
options,
);
Promise.all([lastStatsRequest, statusRequest])
.then(([newStats, newStatus]) => {
setLastStats(newStats.data);
setStatus(newStatus.data);
})
.catch(() => {
setError(true);
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
setError(false);
if (deviceSerialNumber) 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>
);
};
export default React.memo(DeviceStatusCard);

View File

@@ -0,0 +1,29 @@
.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;
}

View File

@@ -14,15 +14,17 @@ import {
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import 'react-widgets/styles.css';
import { getToken } from 'utils/authHelper';
import { useAuth } from 'contexts/AuthProvider';
import { useDevice } from 'contexts/DeviceProvider';
import axiosInstance from 'utils/axiosInstance';
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
import styles from './index.module.scss';
const ConfigureModal = ({ show, toggleModal }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [hadSuccess, setHadSuccess] = useState(false);
const [hadFailure, setHadFailure] = useState(false);
const [doingNow, setDoingNow] = useState(false);
@@ -30,7 +32,6 @@ const ConfigureModal = ({ show, toggleModal }) => {
const [keepRedirector, setKeepRedirector] = useState(true);
const [responseBody, setResponseBody] = useState('');
const [checkingIfSure, setCheckingIfSure] = useState(false);
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const toggleRedirector = () => {
setKeepRedirector(!keepRedirector);
@@ -54,17 +55,21 @@ const ConfigureModal = ({ show, toggleModal }) => {
setWaiting(true);
const parameters = {
serialNumber: selectedDeviceId,
serialNumber: deviceSerialNumber,
keepRedirector,
};
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/factory`, parameters, { headers })
.post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/factory`,
parameters,
{ headers },
)
.then(() => {
setHadSuccess(true);
})

View File

@@ -3,7 +3,16 @@ 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 UpgradeFooter = ({
isNow,
isShown,
isLoading,
action,
color,
variant,
block,
toggleParent,
}) => {
const { t } = useTranslation();
const [askingIfSure, setAskingIfSure] = useState(false);

View File

@@ -3,11 +3,12 @@ import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { CModalBody } from '@coreui/react';
import { v4 as createUuid } from 'uuid';
import { getToken } from 'utils/authHelper';
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']);
@@ -16,12 +17,15 @@ const UpgradeWaitingBody = ({ serialNumber }) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`/device/${encodeURIComponent(serialNumber)}/status`, options)
.get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}/status`,
options,
)
.then((response) => response.data.connected)
.catch(() => {});
};
@@ -30,12 +34,12 @@ const UpgradeWaitingBody = ({ serialNumber }) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`/device/${encodeURIComponent(serialNumber)}`, options)
.get(`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}`, options)
.then((response) => response.data.firmware)
.catch(() => {});
};
@@ -80,11 +84,11 @@ const UpgradeWaitingBody = ({ serialNumber }) => {
<div className="consoleBox">
{labelsToShow.map((label) => (
<p key={createUuid()}>
{new Date().toString()}: {label}
{new Date().toString()}:{label}
</p>
))}
<p>
{t('common.seconds_elapsed')}: {secondsElapsed}
{t('common.seconds_elapsed')}:{secondsElapsed}
</p>
</div>
</CModalBody>

View File

@@ -15,10 +15,10 @@ import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { dateToUnix } from 'utils/helper';
import 'react-widgets/styles.css';
import { getToken } from 'utils/authHelper';
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';
@@ -28,6 +28,8 @@ 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());
@@ -39,14 +41,12 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
const [waitingForUpgrade, setWaitingForUpgrade] = useState(false);
const [showWaitingConsole, setShowWaitingConsole] = useState(false);
const [deviceConnected, setDeviceConnected] = useState(true);
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const toggleNow = () => {
if(isNow){
if (isNow) {
setWaitForUpgrade(false);
setDisableWaiting(true);
}
else{
} else {
setDisableWaiting(false);
}
@@ -82,9 +82,13 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
}, [firmware, date]);
useEffect(() => {
if (selectedDeviceId !== null && show) {
if (deviceSerialNumber !== null && show) {
const asyncGet = async () => {
const isConnected = await getDeviceConnection(selectedDeviceId);
const isConnected = await getDeviceConnection(
deviceSerialNumber,
currentToken,
endpoints.ucentralgw,
);
setDisableWaiting(!isConnected);
setDeviceConnected(isConnected);
};
@@ -98,24 +102,27 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
setBlockFields(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
serialNumber: selectedDeviceId,
Authorization: `Bearer ${currentToken}`,
serialNumber: deviceSerialNumber,
};
const parameters = {
serialNumber: selectedDeviceId,
serialNumber: deviceSerialNumber,
when: isNow ? 0 : dateToUnix(date),
uri: firmware,
};
axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/upgrade`, parameters, { headers })
.post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/upgrade`,
parameters,
{ headers },
)
.then(() => {
if (waitForUpgrade) {
setShowWaitingConsole(true);
}
})
.catch(() => {
})
.catch(() => {})
.finally(() => {
setBlockFields(false);
setWaitingForUpgrade(false);
@@ -131,7 +138,7 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
<CModalTitle>{t('upgrade.title')}</CModalTitle>
</CModalHeader>
<CModalBody>
<UpgradeWaitingBody serialNumber={selectedDeviceId} />
<UpgradeWaitingBody serialNumber={deviceSerialNumber} />
</CModalBody>
<CModalFooter>
<CButton color="secondary" onClick={toggleModal}>
@@ -198,7 +205,10 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
<CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback>
</CCol>
</CRow>
<CRow className={styles.spacedRow} hidden={true || !isNow || disabledWaiting || !deviceConnected}>
<CRow
className={styles.spacedRow}
hidden={true || !isNow || disabledWaiting || !deviceConnected}
>
<CCol md="8">
<p className={styles.spacedText}>
{t('upgrade.wait_for_upgrade')}

View File

@@ -10,23 +10,29 @@ import {
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper';
import { useAuth } from 'contexts/AuthProvider';
import { useDevice } from 'contexts/DeviceProvider';
import styles from './index.module.scss';
const LatestStatisticsModal = ({ show, toggle, serialNumber }) => {
const LatestStatisticsModal = ({ show, toggle }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [latestStats, setLatestStats] = useState('');
const getLatestStats = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`/device/${serialNumber}/statistics?lastOnly=true`, options)
.get(
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?lastOnly=true`,
options,
)
.then((response) => {
setLatestStats(response.data);
})
@@ -57,7 +63,6 @@ const LatestStatisticsModal = ({ show, toggle, serialNumber }) => {
};
LatestStatisticsModal.propTypes = {
serialNumber: PropTypes.string.isRequired,
toggle: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
};

View File

@@ -1,15 +1,17 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { v4 as createUuid } from 'uuid';
import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper';
import { useAuth } from 'contexts/AuthProvider';
import { useDevice } from 'contexts/DeviceProvider';
import { unixToTime, capitalizeFirstLetter } from 'utils/helper';
import eventBus from 'utils/eventBus';
import DeviceStatisticsChart from './DeviceStatisticsChart';
const StatisticsChartList = ({ selectedDeviceId }) => {
const StatisticsChartList = () => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [statOptions, setStatOptions] = useState({
interfaceList: [],
settings: {},
@@ -60,10 +62,10 @@ const StatisticsChartList = ({ selectedDeviceId }) => {
// Looping through the interfaces of the log
for (const inter of log.data.interfaces) {
interfaceList[interfaceTypes[inter.name]][0].data.push(
inter.counters?.tx_bytes? Math.floor(inter.counters.tx_bytes / 1024) : 0
inter.counters?.tx_bytes ? Math.floor(inter.counters.tx_bytes / 1024) : 0,
);
interfaceList[interfaceTypes[inter.name]][1].data.push(
inter.counters?.rx_bytes? Math.floor(inter.counters.rx_bytes / 1024) : 0
inter.counters?.rx_bytes ? Math.floor(inter.counters.rx_bytes / 1024) : 0,
);
}
}
@@ -74,7 +76,7 @@ const StatisticsChartList = ({ selectedDeviceId }) => {
group: 'txrx',
},
stroke: {
curve: 'smooth'
curve: 'smooth',
},
xaxis: {
title: {
@@ -115,29 +117,32 @@ const StatisticsChartList = ({ selectedDeviceId }) => {
};
const getStatistics = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
},
params: {
serialNumber: '24f5a207a130',
},
};
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
params: {
serialNumber: '24f5a207a130',
},
};
axiosInstance
.get(`/device/${selectedDeviceId}/statistics?newest=true&limit=50`, options)
.then((response) => {
transformIntoDataset(response.data.data);
})
.catch(() => {});
axiosInstance
.get(
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?newest=true&limit=50`,
options,
)
.then((response) => {
transformIntoDataset(response.data.data);
})
.catch(() => {});
};
useEffect(() => {
if (selectedDeviceId) {
if (deviceSerialNumber) {
getStatistics();
}
}, [selectedDeviceId]);
}, [deviceSerialNumber]);
useEffect(() => {
eventBus.on('refreshInterfaceStatistics', () => getStatistics());
@@ -161,11 +166,11 @@ const StatisticsChartList = ({ selectedDeviceId }) => {
fontSize: '25px',
},
},
}
}
},
};
return (
<div key={createUuid()}>
<DeviceStatisticsChart chart={ options } />
<DeviceStatisticsChart chart={options} />
</div>
);
})}
@@ -173,8 +178,4 @@ const StatisticsChartList = ({ selectedDeviceId }) => {
);
};
StatisticsChartList.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default React.memo(StatisticsChartList);

View File

@@ -1,6 +1,5 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import {
CDropdown,
CDropdownToggle,
@@ -19,7 +18,7 @@ import StatisticsChartList from './StatisticsChartList';
import LatestStatisticsModal from './LatestStatisticsModal';
import styles from './index.module.scss';
const DeviceStatisticsCard = ({ selectedDeviceId }) => {
const DeviceStatisticsCard = () => {
const { t } = useTranslation();
const [showLatestModal, setShowLatestModal] = useState(false);
@@ -37,7 +36,9 @@ const DeviceStatisticsCard = ({ selectedDeviceId }) => {
<CCardHeader>
<CRow>
<CCol>
<div className={["text-value-lg", styles.cardTitle].join(" ")}>{t('statistics.title')}</div>
<div className={['text-value-lg', styles.cardTitle].join(' ')}>
{t('statistics.title')}
</div>
</CCol>
<CCol className={styles.cardOptions}>
<CDropdown className="m-1 btn-group">
@@ -55,20 +56,12 @@ const DeviceStatisticsCard = ({ selectedDeviceId }) => {
</CRow>
</CCardHeader>
<CCardBody className={styles.statsBody}>
<StatisticsChartList selectedDeviceId={selectedDeviceId} />
<StatisticsChartList />
</CCardBody>
</CCard>
<LatestStatisticsModal
show={showLatestModal}
toggle={toggleLatestModal}
serialNumber={selectedDeviceId}
/>
<LatestStatisticsModal show={showLatestModal} toggle={toggleLatestModal} />
</div>
);
};
DeviceStatisticsCard.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceStatisticsCard;

View File

@@ -13,10 +13,10 @@ import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { dateToUnix } from 'utils/helper';
import 'react-widgets/styles.css';
import { getToken } from 'utils/authHelper';
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';
@@ -25,15 +25,16 @@ import styles from './index.module.scss';
const ActionModal = ({ show, toggleModal }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [waiting, setWaiting] = useState(false);
const [result, setResult] = useState(null);
const [chosenDate, setChosenDate] = useState(new Date().toString());
const [isNow, setIsNow] = useState(false);
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const toggleNow = () => {
setIsNow(!isNow);
}
};
const setDate = (date) => {
if (date) {
@@ -52,21 +53,24 @@ const ActionModal = ({ show, toggleModal }) => {
const doAction = () => {
setWaiting(true);
const token = getToken();
const utcDate = new Date(chosenDate);
const parameters = {
serialNumber: selectedDeviceId,
serialNumber: deviceSerialNumber,
when: isNow ? 0 : dateToUnix(utcDate),
};
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/reboot`, parameters, { headers })
.post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/reboot`,
parameters,
{ headers },
)
.then(() => {
setResult('success');
})
@@ -89,10 +93,9 @@ const ActionModal = ({ show, toggleModal }) => {
) : (
<div>
<CModalBody>
<h6>{t('reboot.directions')}</h6>
<CRow className={styles.spacedRow}>
<CRow>
<CCol md="8">
<p className={styles.spacedText}>{t('common.execute_now')}</p>
<p>{t('reboot.now')}</p>
</CCol>
<CCol>
<CSwitch
@@ -107,7 +110,7 @@ const ActionModal = ({ show, toggleModal }) => {
</CRow>
<CRow hidden={isNow} className={styles.spacedRow}>
<CCol md="4" className={styles.spacedDate}>
<p>{t('common.date')}</p>
<p>{t('common.custom_date')}:</p>
</CCol>
<CCol xs="12" md="8">
<DatePicker

View File

@@ -7,5 +7,5 @@
}
.spacedDate {
margin-top: 7px;
padding-top: 5px;
}

View File

@@ -2,13 +2,14 @@ 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 { getToken } from 'utils/authHelper';
import { useAuth } from 'contexts/AuthProvider';
import axiosInstance from 'utils/axiosInstance';
import styles from './index.module.scss';
const WaitingForTraceBody = ({serialNumber, commandUuid, toggle}) => {
const WaitingForTraceBody = ({ serialNumber, commandUuid, toggle }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const [secondsElapsed, setSecondsElapsed] = useState(0);
const [waitingForFile, setWaitingForFile] = useState(true);
@@ -16,31 +17,34 @@ const WaitingForTraceBody = ({serialNumber, commandUuid, toggle}) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`/command/${encodeURIComponent(commandUuid)}`, options)
.get(`${endpoints.ucentralgw}/api/v1/command/${encodeURIComponent(commandUuid)}`, options)
.then((response) => {
if(response.data.waitingForFile === 0){
if (response.data.waitingForFile === 0) {
setWaitingForFile(false);
}
})
.catch(() => {});
}
};
const downloadTrace = () => {
const options = {
headers: {
Accept: 'application/octet-stream',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
responseType: 'arraybuffer',
};
axiosInstance
.get(`/file/${commandUuid}?serialNumber=${serialNumber}`, options)
.get(
`${endpoints.ucentralgw}/api/v1/file/${commandUuid}?serialNumber=${serialNumber}`,
options,
)
.then((response) => {
const blob = new Blob([response.data], { type: 'application/octet-stream' });
const link = document.createElement('a');
@@ -48,49 +52,48 @@ const WaitingForTraceBody = ({serialNumber, commandUuid, toggle}) => {
link.download = `Trace_${commandUuid}.pcap`;
link.click();
});
}
};
useEffect(() => {
const timer = setInterval(() => {
setSecondsElapsed(secondsElapsed + 1);
}, 1000);
if(!waitingForFile){
if (!waitingForFile) {
clearInterval(timer);
}
return () => {
clearInterval(timer);
}
};
}, [waitingForFile, secondsElapsed]);
useEffect(() => {
const refreshStatus = setInterval(() => {
getTraceResult();
}, 5000);
if(!waitingForFile){
if (!waitingForFile) {
clearInterval(refreshStatus);
}
return () => {
clearInterval(refreshStatus);
}
};
}, [waitingForFile]);
return (
<div>
<CModalBody>
<h6>{t('trace.waiting_seconds', {seconds: secondsElapsed})}</h6>
<h6>{t('trace.waiting_seconds', { seconds: secondsElapsed })}</h6>
<p>{t('trace.waiting_directions')}</p>
<div className={styles.centerDiv}>
<CSpinner hidden={!waitingForFile} />
<CButton
hidden={waitingForFile}
onClick={downloadTrace}
disabled={waitingForFile}
color="primary"
>
{t('trace.download_trace')}
</CButton>
</div>
<CButton
hidden={waitingForFile}
onClick={downloadTrace}
disabled={waitingForFile}
color="link"
block
>
{t('trace.download_trace')}
</CButton>
</CModalBody>
<CModalFooter>
<CButton color="secondary" block onClick={toggle}>
@@ -98,14 +101,13 @@ const WaitingForTraceBody = ({serialNumber, commandUuid, toggle}) => {
</CButton>
</CModalFooter>
</div>
);
}
};
WaitingForTraceBody.propTypes = {
serialNumber: PropTypes.string.isRequired,
commandUuid: PropTypes.string.isRequired,
toggle: PropTypes.func.isRequired
toggle: PropTypes.func.isRequired,
};
export default WaitingForTraceBody;

View File

@@ -17,9 +17,9 @@ import {
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import 'react-widgets/styles.css';
import { getToken } from 'utils/authHelper';
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';
@@ -30,6 +30,8 @@ import styles from './index.module.scss';
const TraceModal = ({ show, toggleModal }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [hadSuccess, setHadSuccess] = useState(false);
const [hadFailure, setHadFailure] = useState(false);
const [blockFields, setBlockFields] = useState(false);
@@ -43,11 +45,9 @@ const TraceModal = ({ show, toggleModal }) => {
const [waitingForTrace, setWaitingForTrace] = useState(false);
const [commandUuid, setCommandUuid] = useState(null);
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const toggleWaitForTrace = () => {
setWaitForTrace(!waitForTrace);
}
};
useEffect(() => {
setWaitForTrace(false);
@@ -65,10 +65,8 @@ const TraceModal = ({ show, toggleModal }) => {
setHadFailure(false);
setHadSuccess(false);
const token = getToken();
const parameters = {
serialNumber: selectedDeviceId,
serialNumber: deviceSerialNumber,
when: 0,
network: chosenInterface,
};
@@ -81,14 +79,18 @@ const TraceModal = ({ show, toggleModal }) => {
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/trace`, parameters, { headers })
.post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/trace`,
parameters,
{ headers },
)
.then((response) => {
setHadSuccess(true);
if(waitForTrace) {
if (waitForTrace) {
setCommandUuid(response.data.UUID);
setWaitingForTrace(true);
}
@@ -105,26 +107,31 @@ const TraceModal = ({ show, toggleModal }) => {
};
useEffect(() => {
if (selectedDeviceId !== null && show) {
if (deviceSerialNumber !== null && show) {
const asyncGet = async () => {
const isConnected = await getDeviceConnection(selectedDeviceId);
const isConnected = await getDeviceConnection(
deviceSerialNumber,
currentToken,
endpoints.ucentralgw,
);
setIsDeviceConnected(isConnected);
};
asyncGet();
}
}, [show]);
const getBody = () => {
if(waitingForTrace){
if (waitingForTrace) {
return (
<WaitingForTraceBody toggle={toggleModal} serialNumber={selectedDeviceId} commandUuid={commandUuid}/>
<WaitingForTraceBody
toggle={toggleModal}
serialNumber={deviceSerialNumber}
commandUuid={commandUuid}
/>
);
}
if(hadSuccess){
return(
<SuccessfulActionModalBody toggleModal={toggleModal} />
);
if (hadSuccess) {
return <SuccessfulActionModalBody toggleModal={toggleModal} />;
}
return (
<div>
@@ -158,34 +165,28 @@ const TraceModal = ({ show, toggleModal }) => {
</CCol>
<CCol xs="12" md="8">
{usingDuration ? (
<CSelect defaultValue="duration" disabled={blockFields}>
<option value="20" onClick={() => setDuration(20)}>
20s
</option>
<option value="40" onClick={() => setDuration(40)}>
40s
</option>
<option value="60" onClick={() => setDuration(60)}>
60s
</option>
<option value="120" onClick={() => setDuration(120)}>
120s
</option>
<CSelect
custom
defaultValue={duration}
disabled={blockFields}
onChange={(e) => setDuration(e.target.value)}
>
<option value="20">20s</option>
<option value="40">40s</option>
<option value="60">60s</option>
<option value="120">120s</option>
</CSelect>
) : (
<CSelect defaultValue={packets} disabled={blockFields}>
<option value="100" onClick={() => setPackets(100)}>
100
</option>
<option value="250" onClick={() => setPackets(250)}>
250
</option>
<option value="500" onClick={() => setPackets(500)}>
500
</option>
<option value="1000" onClick={() => setPackets(1000)}>
1000
</option>
<CSelect
custom
defaultValue={packets}
disabled={blockFields}
onChange={(e) => setPackets(e.target.value)}
>
<option value="100">100</option>
<option value="250">250</option>
<option value="500">500</option>
<option value="1000">1000</option>
</CSelect>
)}
</CCol>
@@ -221,9 +222,7 @@ const TraceModal = ({ show, toggleModal }) => {
</CRow>
<CRow className={styles.spacedRow} hidden={!isDeviceConnected}>
<CCol md="8">
<p className={styles.spacedText}>
{t('trace.wait_for_file')}
</p>
<p className={styles.spacedText}>{t('trace.wait_for_file')}</p>
</CCol>
<CCol>
<CSwitch
@@ -257,7 +256,7 @@ const TraceModal = ({ show, toggleModal }) => {
</CModalFooter>
</div>
);
}
};
return (
<CModal show={show} onClose={toggleModal}>

View File

@@ -9,13 +9,13 @@ import {
CForm,
CSwitch,
CCol,
CSpinner
CSpinner,
} from '@coreui/react';
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { getToken } from 'utils/authHelper';
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';
@@ -25,6 +25,8 @@ import styles from './index.module.scss';
const WifiScanModal = ({ show, toggleModal }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [hadSuccess, setHadSuccess] = useState(false);
const [hadFailure, setHadFailure] = useState(false);
const [waiting, setWaiting] = useState(false);
@@ -32,7 +34,6 @@ const WifiScanModal = ({ show, toggleModal }) => {
const [activeScan, setActiveScan] = useState(false);
const [hideOptions, setHideOptions] = useState(false);
const [channelList, setChannelList] = useState([]);
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const toggleVerbose = () => {
setVerbose(!choseVerbose);
@@ -88,20 +89,22 @@ const WifiScanModal = ({ show, toggleModal }) => {
setHadSuccess(false);
setWaiting(true);
const token = getToken();
const parameters = {
serialNumber: selectedDeviceId,
serialNumber: deviceSerialNumber,
verbose: choseVerbose,
activeScan,
};
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/wifiscan`, parameters, { headers })
.post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/wifiscan`,
parameters,
{ headers },
)
.then((response) => {
const scanList = response?.data?.results?.status?.scan;
@@ -186,7 +189,7 @@ const WifiScanModal = ({ show, toggleModal }) => {
</CModalBody>
<CModalFooter>
<LoadingButton
label={(!hadSuccess && !hadFailure) ? t('scan.scan') : t('scan.re_scan')}
label={!hadSuccess && !hadFailure ? t('scan.scan') : t('scan.re_scan')}
isLoadingLabel={t('scan.scanning')}
isLoading={waiting}
action={doAction}
@@ -195,7 +198,7 @@ const WifiScanModal = ({ show, toggleModal }) => {
disabled={waiting}
/>
<CButton color="secondary" onClick={toggleModal}>
{(!hadSuccess && !hadFailure) ? t('common.cancel') : t('common.exit')}
{!hadSuccess && !hadFailure ? t('common.cancel') : t('common.exit')}
</CButton>
</CModalFooter>
</CModal>

View File

@@ -0,0 +1,27 @@
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);

View File

@@ -0,0 +1,21 @@
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);

View File

@@ -1,19 +1,17 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import ReactDOM from 'react-dom';
import 'index.css';
import { Provider } from 'react-redux';
import App from 'App';
import store from 'store';
import { icons } from 'assets/icons';
import '@babel/polyfill';
import 'i18n';
React.icons = icons;
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
<App />
</React.StrictMode>,
document.getElementById('root'),
);

View File

@@ -6,7 +6,7 @@ const TheFooter = () => (
<Translation>
{(t) => (
<CFooter fixed={false}>
<div>{t('footer.version')} 0.9.5</div>
<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">

View File

@@ -1,5 +1,4 @@
import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useTranslation } from 'react-i18next';
import {
CHeader,
@@ -11,26 +10,27 @@ import {
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 'components/LanguageSwitcher';
import { LanguageSwitcher } from 'ucentral-libs';
import { useAuth } from 'contexts/AuthProvider';
const TheHeader = () => {
const TheHeader = ({ showSidebar, setShowSidebar }) => {
const { t, i18n } = useTranslation();
const dispatch = useDispatch();
const { currentToken, endpoints } = useAuth();
const [translatedRoutes, setTranslatedRoutes] = useState(routes);
const sidebarShow = useSelector((state) => state.sidebarShow);
const toggleSidebar = () => {
const val = [true, 'responsive'].includes(sidebarShow) ? false : 'responsive';
dispatch({ type: 'set', sidebarShow: val });
const val = [true, 'responsive'].includes(showSidebar) ? false : 'responsive';
setShowSidebar(val);
};
const toggleSidebarMobile = () => {
const val = [false, 'responsive'].includes(sidebarShow) ? true : 'responsive';
dispatch({ type: 'set', sidebarShow: val });
const val = [false, 'responsive'].includes(showSidebar) ? true : 'responsive';
setShowSidebar(val);
};
useEffect(() => {
@@ -48,13 +48,18 @@ const TheHeader = () => {
<CHeaderNav className="d-md-down-none mr-auto" />
<CHeaderNav className="px-3">
<LanguageSwitcher />
<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} />
<CIcon
name="cilAccountLogout"
content={cilAccountLogout}
size="2xl"
onClick={() => logout(currentToken, endpoints.ucentralsec)}
/>
</CLink>
</CPopover>
</CHeaderNav>
@@ -69,4 +74,9 @@ const TheHeader = () => {
);
};
TheHeader.propTypes = {
showSidebar: PropTypes.string.isRequired,
setShowSidebar: PropTypes.func.isRequired,
};
export default TheHeader;

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
CCreateElement,
CSidebar,
@@ -11,14 +10,12 @@ import {
CSidebarNavDropdown,
CSidebarNavItem,
} from '@coreui/react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import logoBar from 'assets/OpenWiFi_LogoLockup_WhiteColour.svg';
import styles from './index.module.scss';
const TheSidebar = () => {
const TheSidebar = ({ showSidebar, setShowSidebar }) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const show = useSelector((state) => state.sidebarShow);
const navigation = [
{
@@ -30,16 +27,16 @@ const TheSidebar = () => {
];
return (
<CSidebar show={show} onShowChange={(val) => dispatch({ type: 'set', sidebarShow: val })}>
<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={logoBar}
src="assets/OpenWiFi_LogoLockup_WhiteColour.svg"
alt="OpenWifi"
/>
<img
className={[styles.sidebarImgMinimized, 'c-sidebar-brand-minimized'].join(' ')}
src={logoBar}
src="assets/OpenWiFi_LogoLockup_WhiteColour.svg"
alt="OpenWifi"
/>
</CSidebarBrand>
@@ -59,4 +56,9 @@ const TheSidebar = () => {
);
};
TheSidebar.propTypes = {
showSidebar: PropTypes.string.isRequired,
setShowSidebar: PropTypes.func.isRequired,
};
export default React.memo(TheSidebar);

View File

@@ -1,21 +1,37 @@
import React from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
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 TheSidebar from './Sidebar';
import TheFooter from './Footer';
import TheHeader from './Header';
const TheLayout = (props) => {
const { isLoggedIn } = useSelector((state) => state.connected);
if (isLoggedIn) {
return <div>{props.children}</div>;
}
const TheLayout = () => {
const [showSidebar, setShowSidebar] = useState('responsive');
const { endpoints, currentToken } = useAuth();
const { t, i18n } = useTranslation();
return (
<div className="c-app c-default-layout">
<TheSidebar />
<Sidebar
showSidebar={showSidebar}
setShowSidebar={setShowSidebar}
t={t}
logo="assets/OpenWiFi_LogoLockup_DarkGreyColour.svg"
/>
<div className="c-wrapper">
<TheHeader />
<Header
showSidebar={showSidebar}
setShowSidebar={setShowSidebar}
routes={routes}
t={t}
i18n={i18n}
logout={logout}
authToken={currentToken}
endpoints={endpoints}
/>
<div className="c-body">
<TheContent />
</div>
@@ -25,12 +41,4 @@ const TheLayout = (props) => {
);
};
TheLayout.propTypes = {
children: PropTypes.instanceOf(Object),
};
TheLayout.defaultProps = {
children: {},
};
export default TheLayout;

View File

@@ -1,5 +1,4 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import React from 'react';
import { useParams } from 'react-router-dom';
import { CRow, CCol } from '@coreui/react';
import DeviceHealth from 'components/DeviceHealth';
@@ -7,40 +6,35 @@ import DeviceConfiguration from 'components/DeviceConfiguration';
import CommandHistory from 'components/CommandHistory';
import DeviceLogs from 'components/DeviceLogs';
import DeviceStatisticsCard from 'components/InterfaceStatistics';
import DeviceActionCard from '../../components/DeviceActionCard';
import DeviceActionCard from 'components/DeviceActionCard';
import DeviceStatusCard from 'components/DeviceStatusCard';
import { DeviceProvider } from 'contexts/DeviceProvider';
const DevicePage = () => {
const dispatch = useDispatch();
const { deviceId } = useParams();
const previouslySelectedDeviceId = useSelector((state) => state.selectedDeviceId);
useEffect(() => {
if (deviceId && deviceId !== previouslySelectedDeviceId)
dispatch({ type: 'set', selectedDeviceId: deviceId });
}, [deviceId]);
return (
<>
<div className="App">
<div className="App">
<DeviceProvider serialNumber={deviceId}>
<CRow>
<CCol xs="12" sm="6">
<DeviceConfiguration selectedDeviceId={deviceId} />
<DeviceStatusCard />
<DeviceConfiguration />
</CCol>
<CCol xs="12" sm="6">
<DeviceLogs selectedDeviceId={deviceId} />
<DeviceHealth selectedDeviceId={deviceId} />
<DeviceActionCard selectedDeviceId={deviceId} />
<DeviceLogs />
<DeviceHealth />
<DeviceActionCard />
</CCol>
</CRow>
<CRow>
<CCol>
<DeviceStatisticsCard selectedDeviceId={deviceId} />
<CommandHistory selectedDeviceId={deviceId} />
<DeviceStatisticsCard />
<CommandHistory />
</CCol>
</CRow>
</div>
</>
</DeviceProvider>
</div>
);
};

View File

@@ -18,26 +18,25 @@ import {
CInvalidFeedback,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { useAuth } from 'contexts/AuthProvider';
import { cilUser, cilLockLocked, cilLink } from '@coreui/icons';
import { useDispatch } from 'react-redux';
import axiosInstance from 'utils/axiosInstance';
import logo from 'assets/OpenWiFi_LogoLockup_DarkGreyColour.svg';
import LanguageSwitcher from 'components/LanguageSwitcher';
import { LanguageSwitcher } from 'ucentral-libs';
import styles from './index.module.scss';
const Login = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const { t, i18n } = useTranslation();
const { setCurrentToken, setEndpoints } = useAuth();
const [userId, setUsername] = useState('');
const [password, setPassword] = useState('');
const [gatewayUrl, setGatewayUrl] = useState('');
const [uCentralSecUrl, setUCentralSecUrl] = useState('');
const [hadError, setHadError] = useState(false);
const [emptyUsername, setEmptyUsername] = useState(false);
const [emptyPassword, setEmptyPassword] = useState(false);
const [emptyGateway, setEmptyGateway] = useState(false);
const [defaultConfig, setDefaultConfig] = useState({
DEFAULT_GATEWAY_URL: '',
ALLOW_GATEWAY_CHANGE: true,
DEFAULT_UCENTRALSEC_URL: '',
ALLOW_UCENTRALSEC_CHANGE: true,
});
const placeholderUrl = 'Gateway URL (ex: https://your-url:port)';
@@ -70,7 +69,7 @@ const Login = () => {
isSuccessful = false;
}
if (gatewayUrl.trim() === '') {
if (uCentralSecUrl.trim() === '') {
setEmptyGateway(true);
isSuccessful = false;
}
@@ -78,16 +77,33 @@ const Login = () => {
};
const SignIn = (credentials) => {
const gatewayUrlToUse = defaultConfig.ALLOW_GATEWAY_CHANGE
? gatewayUrl
: defaultConfig.DEFAULT_GATEWAY_URL;
let token = '';
const finalUCentralSecUrl = defaultConfig.ALLOW_UCENTRALSEC_CHANGE
? uCentralSecUrl
: defaultConfig.DEFAULT_UCENTRALSEC_URL;
axiosInstance
.post(`${gatewayUrlToUse}/api/v1/oauth2`, credentials)
.post(`${finalUCentralSecUrl}/api/v1/oauth2`, credentials)
.then((response) => {
sessionStorage.setItem('gw_url', `${gatewayUrlToUse}/api/v1`);
sessionStorage.setItem('access_token', response.data.access_token);
dispatch({ type: 'set', connected: true });
token = response.data.access_token;
return axiosInstance.get(`${finalUCentralSecUrl}/api/v1/systemEndpoints`, {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${response.data.access_token}`,
},
});
})
.then((response) => {
const endpoints = {
ucentralsec: finalUCentralSecUrl,
};
for (const endpoint of response.data.endpoints) {
endpoints[endpoint.type] = endpoint.uri;
}
sessionStorage.setItem('gateway_endpoints', JSON.stringify(endpoints));
setEndpoints(endpoints);
setCurrentToken(token);
})
.catch(() => {
setHadError(true);
@@ -108,12 +124,12 @@ const Login = () => {
}, [password]);
useEffect(() => {
if (emptyGateway) setEmptyGateway(false);
}, [gatewayUrl]);
}, [uCentralSecUrl]);
useEffect(() => {
getDefaultConfig();
}, []);
useEffect(() => {
setGatewayUrl(defaultConfig.DEFAULT_GATEWAY_URL);
setUCentralSecUrl(defaultConfig.DEFAULT_UCENTRALSEC_URL);
}, [defaultConfig]);
return (
@@ -123,7 +139,7 @@ const Login = () => {
<CCol md="8">
<img
className={[styles.logo, 'c-sidebar-brand-full'].join(' ')}
src={logo}
src="assets/OpenWiFi_LogoLockup_DarkGreyColour.svg"
alt="OpenWifi"
/>
<CCardGroup>
@@ -133,7 +149,7 @@ const Login = () => {
<h1>{t('login.login')}</h1>
<p className="text-muted">{t('login.sign_in_to_account')}</p>
<CInputGroup className="mb-3">
<CPopover content="Username">
<CPopover content={t('login.username')}>
<CInputGroupPrepend>
<CInputGroupText>
<CIcon name="cilUser" content={cilUser} />
@@ -154,7 +170,7 @@ const Login = () => {
</CInvalidFeedback>
</CInputGroup>
<CInputGroup className="mb-4">
<CPopover content="Password">
<CPopover content={t('login.password')}>
<CInputGroupPrepend>
<CInputGroupText>
<CIcon content={cilLockLocked} />
@@ -173,8 +189,8 @@ const Login = () => {
{t('login.please_enter_password')}
</CInvalidFeedback>
</CInputGroup>
<CInputGroup className="mb-4" hidden={!defaultConfig.ALLOW_GATEWAY_CHANGE}>
<CPopover content="Gateway URL">
<CInputGroup className="mb-4" hidden={!defaultConfig.ALLOW_UCENTRALSEC_CHANGE}>
<CPopover content={t('login.url')}>
<CInputGroupPrepend>
<CInputGroupText>
<CIcon name="cilLink" content={cilLink} />
@@ -186,9 +202,9 @@ const Login = () => {
type="text"
required
placeholder={placeholderUrl}
value={gatewayUrl}
value={uCentralSecUrl}
autoComplete="gateway-url"
onChange={(event) => setGatewayUrl(event.target.value)}
onChange={(event) => setUCentralSecUrl(event.target.value)}
/>
<CInvalidFeedback className="help-block">
{t('login.please_enter_gateway')}
@@ -213,7 +229,7 @@ const Login = () => {
</CCol>
<CCol xs="6">
<div className={styles.languageSwitcher}>
<LanguageSwitcher />
<LanguageSwitcher i18n={i18n} />
</div>
</CCol>
</CRow>

26
src/router/index.js Normal file
View File

@@ -0,0 +1,26 @@
import { useAuth } from 'contexts/AuthProvider';
import { Route } from 'react-router-dom';
import React from 'react';
const TheLayout = React.lazy(() => import('layout'));
const Login = React.lazy(() => import('pages/LoginPage'));
const Routes = () => {
const { currentToken, endpoints } = useAuth();
return (
<Route
path="/"
name="Devices"
render={(props) =>
currentToken !== '' && Object.keys(endpoints).length !== 0 ? (
<TheLayout {...props} />
) : (
<Login {...props} />
)
}
/>
);
};
export default Routes;

View File

@@ -3,9 +3,7 @@ import React from 'react';
const DevicePage = React.lazy(() => import('pages/DevicePage'));
const DeviceListPage = React.lazy(() => import('pages/DeviceListPage'));
const routes = [
export default [
{ path: '/devices', exact: true, name: 'common.devices', component: DeviceListPage },
{ path: '/devices/:deviceId', name: 'common.device_page', component: DevicePage },
];
export default routes;

View File

@@ -1,3 +1,9 @@
pre.ignore {
font-family: inherit;
}
.custom-select {
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
}

View File

@@ -1,19 +0,0 @@
import { createStore } from 'redux';
const initialState = {
sidebarShow: 'responsive',
connected: false,
selectedDeviceId: null,
};
const changeState = (state = initialState, { type, ...rest }) => {
switch (type) {
case 'set':
return { ...state, ...rest };
default:
return state;
}
};
const store = createStore(changeState);
export default store;

View File

@@ -1,6 +1,19 @@
export const logout = () => {
sessionStorage.clear();
window.location.replace('/');
import axiosInstance from './axiosInstance';
export const logout = (token, endpoint) => {
axiosInstance
.delete(`${endpoint}/api/v1/oauth2/${token}`, {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
},
})
.then(() => {})
.catch(() => {})
.finally(() => {
sessionStorage.clear();
window.location.replace('/');
});
};
export const getToken = () => {

View File

@@ -1,11 +1,7 @@
import * as axios from 'axios';
import axiosRetry from 'axios-retry';
import http from 'http';
import https from 'https';
const httpAgent = new http.Agent({ keepAlive: true });
const httpsAgent = new https.Agent({ keepAlive: true });
const axiosInstance = axios.create(httpAgent, httpsAgent);
const axiosInstance = axios.create();
axiosRetry(axiosInstance, {
retries: 3,
@@ -16,15 +12,6 @@ axiosInstance.defaults.timeout = 30000;
axiosInstance.defaults.headers.get.Accept = 'application/json';
axiosInstance.defaults.headers.post.Accept = 'application/json';
axiosInstance.interceptors.request.use((config) => {
const newConfig = config;
const url = sessionStorage.getItem('gw_url');
if (url !== undefined && url !== null && !newConfig.url.includes(url)) {
newConfig.url = url + newConfig.url;
}
return newConfig;
});
axiosInstance.interceptors.response.use(
// Success actions
undefined,

View File

@@ -1,16 +1,15 @@
import { getToken } from 'utils/authHelper';
import axiosInstance from 'utils/axiosInstance';
export default async (deviceId) => {
export default async (deviceId, token, endpoint) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${token}`,
},
};
return axiosInstance
.get(`/device/${encodeURIComponent(deviceId)}/status`, options)
.get(`${endpoint}/api/v1/device/${encodeURIComponent(deviceId)}/status`, options)
.then((response) => response.data.connected)
.catch(() => false);
};

View File

@@ -66,3 +66,47 @@ export const checkIfJson = (string) => {
}
return true;
};
export const secondsToDetailed = (
seconds,
dayLabel,
daysLabel,
hourLabel,
hoursLabel,
minuteLabel,
minutesLabel,
secondLabel,
secondsLabel,
) => {
if (!seconds || seconds === 0) return `0 ${secondsLabel}`;
let secondsLeft = seconds;
const days = Math.floor(secondsLeft / (3600 * 24));
secondsLeft -= days * (3600 * 24);
const hours = Math.floor(secondsLeft / 3600);
secondsLeft -= hours * 3600;
const minutes = Math.floor(secondsLeft / 60);
secondsLeft -= minutes * 60;
let finalString = '';
if (days > 0)
finalString =
days === 1 ? `${finalString}${days} ${dayLabel}, ` : `${finalString}${days} ${daysLabel}, `;
if (hours > 0)
finalString =
hours === 1
? `${finalString}${hours} ${hourLabel}, `
: `${finalString}${hours} ${hoursLabel}, `;
if (minutes > 0)
finalString =
minutes === 1
? `${finalString}${minutes} ${minuteLabel}, `
: `${finalString}${minutes} ${minutesLabel}, `;
if (secondsLeft > 0)
finalString =
secondsLeft === 1
? `${finalString}${secondsLeft} ${secondLabel}`
: `${finalString}${secondsLeft} ${secondsLabel}`;
return finalString;
};