Compare commits

..

254 Commits

Author SHA1 Message Date
oblom0v
63b1fef875 Change image tag to rc version 2021-08-27 14:54:51 +02:00
Charles
671057e507 Merge pull request #44 from stephb9959/main
Version 2.1.0
2021-08-26 10:52:31 -04:00
Charles
78fe66a155 Merge pull request #20 from stephb9959/dev
New labels added
2021-08-26 10:39:16 -04:00
Charles
1900a2cc1e New labels 2021-08-26 10:38:02 -04:00
Charles
d202938370 Merge pull request #19 from stephb9959/dev
Version 2.1.0
2021-08-26 10:33:01 -04:00
Charles
6f66d7134d Version 2.1.0 2021-08-26 10:08:17 -04:00
Charles
a70d363b62 Version 2.1.0 2021-08-26 09:49:20 -04:00
Charles
3799d6d187 Merge branch 'lindsaybb' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into dev 2021-08-25 18:19:18 -04:00
Charles
1fd56085f2 Update index.js
Hiding searchbar
2021-08-25 18:13:43 -04:00
Charles
61de7f7abf Merge branch 'lindsaybb' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into dev 2021-08-25 18:06:12 -04:00
Charles
bd37b67ab8 New labels 2021-08-25 18:01:25 -04:00
Charles
6c0a5c0806 Update config.json 2021-08-25 11:51:08 -04:00
Charles
5be16c47be Merge branch 'dev' into lindsaybb 2021-08-25 11:24:43 -04:00
Charles
ebe37afa4a Update config.json 2021-08-25 11:10:44 -04:00
BourqueCharles
4ec232f35c Targeting wifi/dchp for event queue 2021-08-19 10:10:27 -04:00
BourqueCharles
53e2909690 Hotfix for eventqueue url 2021-08-19 09:38:58 -04:00
BourqueCharles
092653c838 First version of EventQueueModal 2021-08-19 09:34:35 -04:00
BourqueCharles
d204861c56 Now showing information about all endpoints 2021-08-18 09:45:16 -04:00
BourqueCharles
1494dd5ee9 Command history details now shown with modals 2021-08-17 15:30:43 -04:00
BourqueCharles
4a3e697d78 Ucentral-libs upgrade 2021-08-17 09:30:25 -04:00
BourqueCharles
f234ca2985 Network diagram has a better initial zoom/position 2021-08-17 09:27:14 -04:00
Charles
b64bbb4262 Merge pull request #17 from stephb9959/dev
Version 2.0.28
2021-08-17 08:48:21 -04:00
BourqueCharles
41f139fdf1 Now using dagre for network diagram layout 2021-08-16 16:01:11 -04:00
BourqueCharles
386b94c126 Changed favicon.ico 2021-08-16 15:31:50 -04:00
BourqueCharles
36d2d31878 Upgrade to ucentral-libs 0.8.68 2021-08-16 15:25:46 -04:00
BourqueCharles
3e0dc52376 Version 2.0.26 2021-08-16 15:22:45 -04:00
BourqueCharles
aef48107fe Changed ucentral-libs version 2021-08-16 15:21:51 -04:00
BourqueCharles
9fb2c92a15 Added LindsayBB logo 2021-08-16 15:21:19 -04:00
BourqueCharles
6dbe24e34f Version 2.0.26 2021-08-16 13:00:02 -04:00
BourqueCharles
656d407ade Version 2.0.26 2021-08-16 12:56:04 -04:00
BourqueCharles
133af50067 Version 2.0.26 2021-08-16 10:15:44 -04:00
BourqueCharles
bb03f0d250 CSS cleanup, trace waiting now deals with errors 2021-08-16 10:15:21 -04:00
BourqueCharles
4d373fb1c7 Version 2.0.25 2021-08-13 16:47:35 -04:00
BourqueCharles
c21453861f Good regex for DeviceSearchBar 2021-08-13 16:19:51 -04:00
BourqueCharles
587a0aac68 Tweaks to device search. Using react-select now 2021-08-13 16:09:17 -04:00
BourqueCharles
58a083109e Fixed ucentral-libs version 2021-08-13 14:56:33 -04:00
BourqueCharles
97d326ac88 footer update 2021-08-13 14:48:04 -04:00
BourqueCharles
909616efbe First version of search bar 2021-08-13 14:47:52 -04:00
Charles
fa566c2101 Merge pull request #43 from stephb9959/main
Version 2.0.22
2021-08-12 15:35:41 -04:00
Charles
8a7d740c95 Merge pull request #16 from stephb9959/dev
Version 2.0.22
2021-08-12 15:33:29 -04:00
BourqueCharles
69e5c2ef48 Version 2.0.22 2021-08-12 14:26:13 -04:00
BourqueCharles
f202cd7327 Network diagram edits, lifetime stats 2021-08-12 14:13:55 -04:00
BourqueCharles
f6861ec122 UI adjustments to device list and network diagram 2021-08-12 10:02:18 -04:00
BourqueCharles
76a10f3dd7 ucentral-libs v0.8.59 2021-08-11 16:52:14 -04:00
BourqueCharles
6c16125fa4 First version of radio graph 2021-08-11 16:26:16 -04:00
BourqueCharles
d6b9d445aa Show/hide password added to device config 2021-08-10 15:51:51 -04:00
Charles
5403ce690e Merge pull request #42 from stephb9959/main
Version 2.0.14
2021-08-10 15:21:02 -04:00
Charles
8bb3f64e70 Merge pull request #15 from stephb9959/dev
Version 2.0.14
2021-08-10 15:15:57 -04:00
BourqueCharles
bcbdf3441e UI fixes and ucentral-libs upgrade 2021-08-10 14:52:19 -04:00
BourqueCharles
4c355d8a68 Version 2.0.13 2021-08-10 12:23:13 -04:00
BourqueCharles
44c32f364a Added compression and chunking in webpack 2021-08-10 11:01:06 -04:00
Charles
439bb9667e Merge pull request #41 from stephb9959/main
Version 2.0.12
2021-08-10 08:46:48 -04:00
Charles
d0daf64ae9 Merge pull request #14 from stephb9959/dev
Version 2.0.12
2021-08-10 08:46:07 -04:00
BourqueCharles
6e90ed67ad Device # in analysis page, UI fix for status card 2021-08-09 16:02:05 -04:00
BourqueCharles
ebc44fa1ea Filtering dev firmware in firmware list 2021-08-09 14:59:04 -04:00
BourqueCharles
5ba716a437 Now using ToastProvider for notifications 2021-08-09 11:00:34 -04:00
Charles
b7a427fc6f Merge pull request #40 from stephb9959/main
Version 2.0.9
2021-08-09 09:04:19 -04:00
Charles
6067fcbc9e Merge pull request #13 from stephb9959/dev
Version 2.0.9
2021-08-06 15:35:58 -04:00
BourqueCharles
8b767a8aff Version 2.0.9 2021-08-06 14:29:21 -04:00
BourqueCharles
3c6cf48d9f Code clean bootstrap classes instead of custom css 2021-08-06 11:50:31 -04:00
BourqueCharles
c6b77aecb9 Now using contexts from ucentral-libs 2021-08-06 10:08:13 -04:00
Charles
3119c8f9a6 Merge pull request #39 from stephb9959/main
Version 2.0.6
2021-08-06 08:32:40 -04:00
Charles
ada2dea463 Merge pull request #12 from stephb9959/dev
Version 2.0.6
2021-08-05 16:46:25 -04:00
BourqueCharles
9001214462 Dependency fix 2021-08-05 16:39:20 -04:00
BourqueCharles
77bc5eaabd UI fixes in tables, profile page 2021-08-05 16:36:08 -04:00
BourqueCharles
c4263299b9 New tooltip version for firmware in list 2021-08-05 13:34:04 -04:00
BourqueCharles
633b59f99f Version 2.0.4 2021-08-04 15:46:10 -04:00
BourqueCharles
35f1ddcbae Dependencies fix 2021-08-04 15:17:50 -04:00
BourqueCharles
7279acff7e General UI fixes 2021-08-04 15:14:23 -04:00
BourqueCharles
91dc8f4c92 Merge branch 'dev' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into dev 2021-08-04 13:38:52 -04:00
BourqueCharles
dfba26b76a Label changes and firmware history 2021-08-04 13:38:36 -04:00
Charles
4954f11c93 Merge pull request #38 from stephb9959/main
V 2.0.1
2021-08-04 13:18:17 -04:00
Charles
40f752f48f Merge pull request #11 from stephb9959/dev
V2.0.1
2021-08-04 13:17:08 -04:00
Charles
5ccf299003 Merge branch 'main' into dev 2021-08-04 13:16:58 -04:00
BourqueCharles
b5f8b3c1eb Merge branch 'dev' of https://github.com/stephb9959/wlan-cloud-uce
Test
2021-08-04 09:06:48 -04:00
BourqueCharles
cfe2199354 Fetching device list with pagination 2021-08-04 09:03:43 -04:00
Charles
074c973aeb Update index.js 2021-08-03 13:04:32 -04:00
Charles
2166001174 Update index.js 2021-08-03 13:04:06 -04:00
BourqueCharles
98b82b9fc6 Dashboard and device table fixes 2021-08-02 14:23:18 -04:00
BourqueCharles
a56d92e4eb Added in device dashboard and list 2021-07-31 15:19:40 -04:00
BourqueCharles
283c8361f2 Adjustments to dashboards 2021-07-30 13:33:57 -04:00
BourqueCharles
3743fc5cfa Added functionality to dashboards and device list 2021-07-30 10:40:42 -04:00
BourqueCharles
08d2154489 UI fixes for dashboards 2021-07-29 21:29:01 -04:00
BourqueCharles
449ce0a927 Added chartjs dependency 2021-07-29 18:52:04 -04:00
BourqueCharles
5dff6f76d1 General label fixes + firmware dashboard 2021-07-29 18:47:10 -04:00
BourqueCharles
5ae181cd89 Updated ucentral-libs version 2021-07-29 16:45:09 -04:00
BourqueCharles
09887a439e First version of Device Dashboard 2021-07-29 16:39:07 -04:00
BourqueCharles
b1d999a42e New version of firmware toggle in device list 2021-07-29 14:48:07 -04:00
BourqueCharles
c857796318 UI Fixes and ucentral-libs version 2021-07-29 10:59:28 -04:00
BourqueCharles
68b3693531 Label fixes and device list icon fixes 2021-07-29 10:34:31 -04:00
BourqueCharles
79fb4c550d Merge branch 'dev' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into dev 2021-07-29 10:22:25 -04:00
BourqueCharles
b02e495028 Upgrade device from device list 2021-07-29 10:21:56 -04:00
Charles
acdd51ffdf Update index.js 2021-07-28 15:18:01 -04:00
Charles
69f3d96be2 Update index.js 2021-07-28 14:52:52 -04:00
Charles
c7e3c9d8d0 Merge pull request #37 from stephb9959/main
Version 2.0.0
2021-07-28 14:09:46 -04:00
Charles
d80002afc8 Merge pull request #10 from stephb9959/dev
Merging Dev branch to main
2021-07-28 13:59:17 -04:00
BourqueCharles
5ade42ca7c Version 2.0.0 2021-07-28 13:55:23 -04:00
BourqueCharles
8636215314 Version 2.0.0 2021-07-28 13:47:36 -04:00
BourqueCharles
4f4fe972d3 FirmwareList and WifiAnalysis 2021-07-28 13:47:22 -04:00
bourquecharles
38a39a2aaa Fixes for settings and user list avatars 2021-07-27 18:43:35 -04:00
bourquecharles
1d40cef5d5 Version 0.9.27 2021-07-27 16:02:47 -04:00
bourquecharles
d9160aac2d Added first version of firmware list 2021-07-27 15:49:55 -04:00
bourquecharles
5125be8094 Avatars are now show in the user list table 2021-07-26 15:55:39 -04:00
bourquecharles
cadf862c7b Version 0.9.24 2021-07-26 14:29:32 -04:00
bourquecharles
13639b3a01 UI fixes to users and wifi analysis. Added phy 2021-07-26 14:29:09 -04:00
bourquecharles
f0c8a4053c Updated ucentral-libs version 2021-07-23 17:10:27 -04:00
bourquecharles
e47d8b02d3 First version of settings page and UI fixes 2021-07-23 17:03:27 -04:00
bourquecharles
de7faed4c3 Updated version of ucentral-libs used 2021-07-23 15:26:57 -04:00
bourquecharles
fbd03c1fc5 Fixes for wifi analysis UI 2021-07-23 15:15:45 -04:00
bourquecharles
e23b77c400 Radio analysis table added 2021-07-22 16:03:01 -04:00
Dmitry Dunaev
df6ec36515 Add: KUBERNETES_DEPLOYED env variable temporary added for redeployment forcing 2021-07-22 18:47:57 +03:00
bourquecharles
192836dd2c First vresion of the Wifi analysis page 2021-07-22 11:42:24 -04:00
bourquecharles
3510f6f90b User functions fixes + avatar preview 2021-07-21 14:22:00 -04:00
Dmitry Dunaev
db7394d86c Merge pull request #36 from Telecominfraproject/feature/helm-docs
[WIFI-2592] Add: Helm README
2021-07-21 16:21:27 +03:00
Dmitry Dunaev
772e7cd07d [WIFI-2592] Add: Helm README 2021-07-21 14:59:14 +03:00
bourquecharles
15403befc0 Avatar added to myprofile and navbar 2021-07-20 18:00:22 -04:00
bourquecharles
4d0f7f2de2 Edit user, my profile and create user 2021-07-20 16:54:57 -04:00
bourquecharles
8cdb1865bf Version 0.9.18 2021-07-19 19:30:01 -04:00
bourquecharles
c5843a55c3 Created user page, forgot/change password added 2021-07-19 19:24:57 -04:00
Dmitry Dunaev
54bc4ad403 Merge pull request #35 from Telecominfraproject/feature/git-branching
[WIFI-2622] Add: branching release model in CI
2021-07-19 17:22:31 +03:00
Dmitry Dunaev
8fe26a08b3 [WIFI-2622] Add: branching release model in CI 2021-07-19 17:19:16 +03:00
bourquecharles
75b2dd2e27 Change password form added 2021-07-17 13:11:26 -04:00
bourquecharles
83b7560d4d Fixed toggle for forgot password/login 2021-07-16 11:42:36 -04:00
bourquecharles
946fbbc053 Login with forgot password 2021-07-16 10:45:55 -04:00
bourquecharles
54174dd4f2 User Creation now verifying password/access policy 2021-07-15 12:03:21 -04:00
bourquecharles
017d1719a6 New ucentral-libs version 2021-07-15 10:43:47 -04:00
bourquecharles
efdf5b2b30 Added columns in user list,description in create 2021-07-15 10:43:27 -04:00
bourquecharles
4a58852ebd Naming and icon fixes for navigation 2021-07-15 09:21:38 -04:00
bourquecharles
cf18a96900 Using right version of ucentral-libs 2021-07-15 09:11:20 -04:00
bourquecharles
d1f4e77e6d Added user list page and user creation page 2021-07-15 09:02:46 -04:00
bourquecharles
757b09f031 New label, using ucentral libs for loading button 2021-07-15 08:41:15 -04:00
Charles
6f78768459 Update README.md 2021-07-13 12:09:14 -04:00
Dmitry Dunaev
c3c31ec4ac Fix: downgrading node to 14 to resolve node-sass incompatibility 2021-07-13 18:42:12 +03:00
Charles
3dce199b79 Update README.md 2021-07-13 11:04:02 -04:00
Charles
403e8bc185 Update README.md 2021-07-13 11:02:12 -04:00
Charles
0f15ae7bef Merge pull request #34 from Telecominfraproject/dev-microservice
Dev microservice
2021-07-13 10:12:20 -04:00
bourquecharles
112b01afff Now using LoginPage component from the library 2021-07-12 17:57:54 -04:00
bourquecharles
4a45ad0025 Using library for layout components 2021-07-12 16:24:31 -04:00
Charles
02385c5544 Merge pull request #33 from stephb9959/main
Webpack use
2021-07-12 15:30:20 -04:00
bourquecharles
a422d5d446 Webpack and dependency cleanup 2021-07-12 15:20:55 -04:00
bourquecharles
5fe05e2890 Library update 2021-07-12 12:29:00 -04:00
bourquecharles
d9f3406ae7 Dev webpack working 2021-07-12 12:05:30 -04:00
bourquecharles
28ec0482c8 Eslint fix 2021-07-09 15:28:47 -04:00
bourquecharles
273a91f357 gitignore fixed 2021-07-09 15:08:04 -04:00
bourquecharles
2c7df5abd1 Now building in the /build folder again 2021-07-09 15:07:45 -04:00
bourquecharles
09232c5640 Eslint change 2021-07-09 14:58:31 -04:00
bourquecharles
fe84f6bdb1 Webpack use 2021-07-09 14:56:21 -04:00
Dmitry Dunaev
e1727d2f91 [WIFI-2920] Chg: env variables to match microservice architecture 2021-07-09 16:20:16 +03:00
Charles
74cac90696 Merge pull request #32 from stephb9959/main
Device Notes in config
2021-07-07 13:32:47 -04:00
Charles
4b2bde3c3a Merge pull request #9 from stephb9959/31-device-notes-display
31 device notes display
2021-07-07 09:51:18 -04:00
bourquecharles
c4b19a3b24 New device notes table 2021-07-07 09:49:55 -04:00
bourquecharles
7384558ad0 Users can now add new notes 2021-07-06 14:11:08 -04:00
bourquecharles
cc8be05594 Deleted past file references 2021-07-06 11:46:01 -04:00
bourquecharles
bff117e45a Deleted unused files 2021-07-06 11:44:40 -04:00
Charles
62b1c23b3c Merge pull request #30 from stephb9959/main
Version 0.9.12
2021-07-05 15:28:43 -04:00
bourquecharles
340d21edac Fixed husky running pre-commit 2021-07-05 11:16:34 -04:00
bourquecharles
73a824330a Reinstalled sass dependency 2021-07-05 10:04:27 -04:00
bourquecharles
96cc7dc78b Version 0.9.12 2021-07-05 09:55:58 -04:00
bourquecharles
b71b4cd1a5 Uninstalled sass dependency 2021-07-05 09:40:54 -04:00
bourquecharles
a54acfd347 Simplified Trace select 2021-07-05 09:36:28 -04:00
bourquecharles
4d4af822d9 Uninstalled Select dependency 2021-07-05 09:31:21 -04:00
bourquecharles
3bc028fc3b Uninstalled redux 2021-07-05 09:14:17 -04:00
bourquecharles
e1e74cb29a Uninstalled http/https library 2021-07-05 09:13:31 -04:00
bourquecharles
d9a8a02cd7 Prettier run 2021-07-05 08:53:48 -04:00
bourquecharles
4c96158f11 Merge branch 'main' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into main 2021-07-02 18:16:48 -04:00
bourquecharles
7162303e62 Label changes for UCENTRALSEC change 2021-07-02 18:16:19 -04:00
Charles
26ef27f251 Merge branch 'Telecominfraproject:main' into main 2021-07-02 17:50:11 -04:00
bourquecharles
f2f370602f Merge branch 'main' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into main 2021-07-02 17:42:18 -04:00
bourquecharles
2056482179 Changed variables for UCENTRALSEC 2021-07-02 17:42:09 -04:00
Charles
e8960368a5 Update README.md 2021-07-02 17:38:40 -04:00
bourquecharles
c017fe0774 Now using endpoints returned from the gateway sec 2021-07-02 17:34:13 -04:00
Dmitry Dunaev
3ea22477ae Merge pull request #29 from Telecominfraproject/feature/cicd
[WIFI-2882] Add: image build workflow
2021-07-02 15:21:56 +03:00
Dmitry Dunaev
258ba197bb [WIFI-2882] Add: image build and cleanup workflow 2021-07-02 14:32:12 +03:00
bourquecharles
908a034433 Version 0.9.9 2021-07-01 16:45:32 -04:00
Charles
101509edd6 Merge pull request #8 from stephb9959/28-migrating-to-context
Migrated from redux to context API
2021-07-01 16:44:02 -04:00
bourquecharles
ae7200815d Migrated from redux to context API 2021-07-01 16:42:51 -04:00
bourquecharles
f42b3d48d3 Version 0.9.8 2021-07-01 10:46:54 -04:00
bourquecharles
e60a703f19 Reboot and blink modal with one less line for user 2021-07-01 10:45:54 -04:00
bourquecharles
281a84f19c Simplified RebootModal component 2021-07-01 09:50:02 -04:00
bourquecharles
563b94765e Alignment adjustment for MemoryBar placement 2021-07-01 09:14:50 -04:00
bourquecharles
9695a3ba71 Adjustment to column width 2021-07-01 09:12:27 -04:00
Charles
5b6f0e8f78 Merge branch 'Telecominfraproject:main' into main 2021-07-01 09:08:17 -04:00
Dmitry Dunaev
405e8eb944 Merge pull request #27 from Telecominfraproject/feature/add-helm
[WIFI-2695] Add: helm chart
2021-07-01 14:47:33 +03:00
Dmitry Dunaev
1d771a2bea [WIFI-2695] Add: helm chart 2021-07-01 14:46:59 +03:00
bourquecharles
895301edd3 Dependency cleanup 2021-06-30 15:59:13 -04:00
Charles
568961c4d9 Merge pull request #7 from stephb9959/feature/20-at-a-glance-device-tile
#20 Device Status Card
2021-06-30 15:23:22 -04:00
Charles
ce5c25f169 Merge branch 'Telecominfraproject:main' into main 2021-06-30 15:21:59 -04:00
bourquecharles
0b142b0658 Version 0.9.7 2021-06-30 15:19:47 -04:00
bourquecharles
87b3450221 First version of device status card 2021-06-30 15:17:36 -04:00
bourquecharles
97b3716dc9 Prettier run 2021-06-30 15:17:09 -04:00
bourquecharles
c83b8eb8aa Device configuration CSS cleanup 2021-06-30 15:16:06 -04:00
Stephane Bourque
d5244f8626 Update README.md 2021-06-30 08:32:15 -07:00
Charles
d5156bf000 Update README.md 2021-06-30 11:27:27 -04:00
Dmitry Dunaev
ae36c31dea Merge pull request #26 from Telecominfraproject/feature/configuration-via-envs
[WIFI-2853] Add: configration generation in runtime via env variables
2021-06-30 17:07:03 +03:00
Dmitry Dunaev
77224477b3 [WIFI-2853] Add: configration generation in runtime via env variables 2021-06-30 16:41:15 +03:00
bourquecharles
80fa5e7d43 Version 0.9.6 2021-06-30 08:25:19 -04:00
bourquecharles
84de2595b9 Changed button label in blink modal 2021-06-30 08:24:36 -04:00
bourquecharles
c0023436e8 Trace download now a button instead of a link 2021-06-30 08:24:19 -04:00
bourquecharles
6593be8bdf Health title made biggest on tile 2021-06-30 08:24:01 -04:00
Charles
fb344db120 Merge pull request #25 from stephb9959/main
More accurate translations in German
2021-06-29 16:28:39 -04:00
bourquecharles
6cf081527f More accurate translations in German 2021-06-29 16:27:42 -04:00
Charles
dc4dca417b Merge pull request #24 from stephb9959/main
Using smooth lines for stats line graph
2021-06-29 16:08:53 -04:00
bourquecharles
be7a55fbc0 Using smooth lines for stats line graph 2021-06-29 16:07:17 -04:00
Charles
7b476cf38a Merge pull request #23 from stephb9959/main
General UI fixes
2021-06-29 16:02:31 -04:00
Charles
236e2ced62 Merge pull request #6 from stephb9959/bugfix/21-general-ui-fixes
Bugfix/21 general UI fixes
2021-06-29 15:48:49 -04:00
bourquecharles
e99fd9f6b2 New copy to clipboard component. Alignment fixes 2021-06-29 15:47:55 -04:00
bourquecharles
78eb883fef Aligned Statistics title and options 2021-06-29 15:20:37 -04:00
bourquecharles
16608c5fcf Standardized modal footer buttons 2021-06-29 15:05:20 -04:00
bourquecharles
657f61b43e Wifi scan modal has 'Cancel' instead of Close 2021-06-29 14:41:31 -04:00
bourquecharles
3e279aff58 Cleaned labels of mentioning 'Device' all the time 2021-06-29 14:40:32 -04:00
Charles
6628c69d19 Merge pull request #19 from stephb9959/main
Label changes and Wifi Scan loading
2021-06-29 10:55:56 -04:00
bourquecharles
cdcd29a47c Version 0.9.5 2021-06-29 10:45:20 -04:00
bourquecharles
d8ab92531f Added loading phase, label changes for wifi scan. 2021-06-29 10:44:32 -04:00
bourquecharles
f6876d13fe Label changes for WaitingForTraceBody 2021-06-29 09:23:46 -04:00
bourquecharles
5253009bad Undid adding .babel file to project 2021-06-29 08:53:02 -04:00
bourquecharles
d74e1f4172 Merge branch 'main' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into main 2021-06-29 08:48:37 -04:00
bourquecharles
0ad81c85f2 Added babel translation file 2021-06-29 08:48:28 -04:00
Stephane Bourque
ec8ea71dcc Merge pull request #11 from Telecominfraproject/feature/dockerfile
Add: docker related files
2021-06-28 12:36:30 -07:00
Stephane Bourque
cd4be5c7b1 Merge pull request #14 from stephb9959/main
Version 0.9.4 : wait for Trace, CSS fixes, latest stats
2021-06-28 12:35:38 -07:00
bourquecharles
e2433024d7 Standardized component titles on device page 2021-06-28 15:03:05 -04:00
bourquecharles
ddc877ee3d Using css for cropping strings with ellipsis 2021-06-28 12:19:05 -04:00
bourquecharles
60b03d49c6 Version 0.9.4 2021-06-28 10:24:47 -04:00
Charles
853590376b Merge pull request #5 from stephb9959/bugfix/10-statistics-chart-rendering
Fixed re-renders of statistics chart list
2021-06-28 09:53:47 -04:00
bourquecharles
cccd7b7cec Fixed re-renders of statistics chart list 2021-06-28 09:48:51 -04:00
Dmitry Dunaev
1dfd8d8fb1 Add: docker related files 2021-06-28 16:13:07 +03:00
bourquecharles
68f7570720 Added label for waiting for trace file 2021-06-28 08:26:36 -04:00
Charles
59e1709cff Merge pull request #4 from stephb9959/feature/9-updating-reboot-blink-modal
Feature/9 updating reboot blink modal
2021-06-25 15:26:33 -04:00
bourquecharles
39b05192fe Small refactor of reboot modal 2021-06-25 15:23:33 -04:00
bourquecharles
fa377132a8 Blink refactored 2021-06-25 15:14:25 -04:00
bourquecharles
b3e5cd79ed hotfix: clearer wait for trace behaviour 2021-06-25 14:41:41 -04:00
Charles
6f7b05649b Merge pull request #3 from stephb9959/feature/4-firmware-upgrade-wait
Feature/4 firmware upgrade wait
2021-06-25 14:21:18 -04:00
bourquecharles
4861a746b4 Temporarily deactivating wait for upgrade 2021-06-25 13:57:25 -04:00
bourquecharles
e75c163248 Waiting for trace 2021-06-25 09:35:47 -04:00
bourquecharles
d8b501fff0 Merge branch 'main' into feature/4-firmware-upgrad 2021-06-24 11:53:34 -04:00
bourquecharles
9de6c27c69 Reworked the firmware upgrade modal and added wait option 2021-06-24 11:33:00 -04:00
Charles
80811a6071 Merge branch 'Telecominfraproject:main' into main 2021-06-24 11:26:57 -04:00
Charles
814cb09e44 Merge pull request #2 from stephb9959/feature/7-icon-to-retrieve-last-stats
Feature/7 icon to retrieve last stats
2021-06-24 11:25:20 -04:00
bourquecharles
050a4a3b78 Created LatestStatisticsModal component 2021-06-24 10:48:36 -04:00
bourquecharles
ced06d1391 Cleaned unnecessary nesting in the pages directory 2021-06-24 08:58:08 -04:00
bourquecharles
9876c98628 Cleaned useless nesting in components folder 2021-06-24 08:56:01 -04:00
Charles
93c57ecfb6 Merge pull request #6 from stephb9959/main
Option to delete logs and healthchecks, modified how the default config is evaluated
2021-06-22 15:09:10 -04:00
bourquecharles
666f1a0483 Sidebar img size fix 2021-06-22 14:54:14 -04:00
bourquecharles
1b8abac84b Cleaning the README of env variables 2021-06-22 14:37:48 -04:00
bourquecharles
3de2ffb1a1 Changed defaults for config.json file 2021-06-22 14:34:05 -04:00
bourquecharles
419b8c84cb Now ignoring .env, using config.json in public/ 2021-06-22 14:17:54 -04:00
Charles
c9ef0b9046 Merge pull request #1 from stephb9959/feature/4-delete-logs-healtchecks
Added options to delete logs and healthchecks
2021-06-22 11:21:54 -04:00
bourquecharles
7dc2ba9264 Added options to delete logs and healthchecks 2021-06-22 11:20:54 -04:00
cbuschfb
a2fc111d8c readme update
Signed-off-by: cbuschfb <chrisbusch@fb.com>
2021-06-22 10:04:32 -04:00
Stephane Bourque
1b47daced3 Merge pull request #5 from stephb9959/main
Fixes for Interface Stats Component
2021-06-21 14:02:02 -07:00
bourquecharles
ad2be40718 Deleting console.log 2021-06-21 16:46:52 -04:00
bourquecharles
0f1d251c3a Fixed double rendering of the statistics charts 2021-06-21 16:32:50 -04:00
bourquecharles
1bdb30ef01 The rx_bytes were not converted to KB 2021-06-21 15:47:15 -04:00
bourquecharles
dcc37a113f Added back sass package 2021-06-21 15:34:10 -04:00
bourquecharles
8d4d90197d Changed version number 2021-06-21 15:28:47 -04:00
137 changed files with 18613 additions and 24952 deletions

28
.dockerignore Normal file
View File

@@ -0,0 +1,28 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.git
.github
Dockerfile
helm

2
.env
View File

@@ -1,2 +0,0 @@
REACT_APP_DEFAULT_GATEWAY_URL=https://ucentral.dpaas.arilia.com:16001
REACT_APP_ALLOW_GATEWAY_CHANGE=false

View File

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

View File

@@ -1,16 +1,11 @@
{ {
"parser": "babel-eslint", "extends": ["airbnb", "prettier"],
"parserOptions": { "plugins": ["prettier"],
"sourceType": "module", "env": {
"allowImportExportEverywhere": false, "browser": true,
"codeFrame": false "jest": true
}, },
"extends": ["airbnb", "prettier"], "rules": {
"env": {
"browser": true,
"jest": true
},
"rules": {
"max-len": ["error", {"code": 150}], "max-len": ["error", {"code": 150}],
"prefer-promise-reject-errors": ["off"], "prefer-promise-reject-errors": ["off"],
"react/jsx-filename-extension": ["off"], "react/jsx-filename-extension": ["off"],
@@ -18,13 +13,22 @@
"no-return-assign": ["off"], "no-return-assign": ["off"],
"react/jsx-props-no-spreading": ["off"], "react/jsx-props-no-spreading": ["off"],
"react/destructuring-assignment": ["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": { "settings": {
"import/resolver": { "import/resolver": {
"node": { "node": {
"paths": ["src"] "paths": ["src"]
} }
}, }
}, },
} "parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"allowImportExportEverywhere": false,
"codeFrame": false
}
}

68
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: Build Docker image
on:
push:
paths-ignore:
- '**.md'
branches:
- main
- 'release/*'
tags:
- 'v*'
pull_request:
branches:
- main
defaults:
run:
shell: bash
jobs:
docker:
runs-on: ubuntu-20.04
env:
DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io
DOCKER_REGISTRY_USERNAME: ucentral
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t wlan-cloud-ucentralgw-ui:${{ github.sha }} .
- name: Tag Docker image
run: |
TAGS="${{ github.sha }}"
if [[ ${GITHUB_REF} == "refs/heads/"* ]]
then
CURRENT_TAG=$(echo ${GITHUB_REF#refs/heads/} | tr '/' '-')
TAGS="$TAGS $CURRENT_TAG"
else
if [[ ${GITHUB_REF} == "refs/tags/"* ]]
then
CURRENT_TAG=$(echo ${GITHUB_REF#refs/tags/} | tr '/' '-')
TAGS="$TAGS $CURRENT_TAG"
else # PR build
CURRENT_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-')
TAGS="$TAGS $CURRENT_TAG"
fi
fi
echo "Result tags: $TAGS"
for tag in $TAGS; do
docker tag wlan-cloud-ucentralgw-ui:${{ github.sha }} ${{ env.DOCKER_REGISTRY_URL }}/ucentralgw-ui:$tag
done
- name: Log into Docker registry
if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main'
uses: docker/login-action@v1
with:
registry: ${{ env.DOCKER_REGISTRY_URL }}
username: ${{ env.DOCKER_REGISTRY_USERNAME }}
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
- name: Push Docker images
if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main'
run: |
docker images | grep ${{ env.DOCKER_REGISTRY_URL }}/ucentralgw-ui | awk -F ' ' '{print $1":"$2}' | xargs -I {} docker push {}

19
.github/workflows/cleanup.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Clean up PR Docker images
on:
pull_request:
branches:
- main
types: [ closed ]
defaults:
run:
shell: bash
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- run: |
export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-')
curl -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/ucentralgw-ui/$PR_BRANCH_TAG"

2
.gitignore vendored
View File

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

4
.prettierignore Normal file
View File

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

15
Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM node:14-alpine3.11 AS build
COPY package.json package-lock.json /
RUN npm install
COPY . .
RUN npm run build
FROM nginx:1.20.1-alpine AS runtime
COPY --from=build /build/ /usr/share/nginx/html/
COPY --from=build docker-entrypoint.d/40-generate-config.sh /docker-entrypoint.d/40-generate-config.sh

View File

@@ -4,33 +4,45 @@
The uCentralGW Client is a user interface that lets you monitor and manage devices connected to the [uCentral gateway](https://github.com/Telecominfraproject/wlan-cloud-ucentralgw). To use the interface, The uCentralGW Client is a user interface that lets you monitor and manage devices connected to the [uCentral gateway](https://github.com/Telecominfraproject/wlan-cloud-ucentralgw). To use the interface,
you either need to run it on your machine for [development](#development) or build it for [production](#production). you either need to run it on your machine for [development](#development) or build it for [production](#production).
NOTE: This UI will be evolving as micro services are added to the uCentral program most notably with provisioning, base dashboard, firmware, device management
## Running the solution ## Running the solution
### Development ### Development
Here are the instructions to run the solution on your machine for development purposes. You need to run these in the root folder of the project and also have npm installed on your machine. Please install `npm` for the platform you are using. You need to run these commands in the root folder of the project and also have npm installed on your machine.
``` ```
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
cd wlan-cloud-ucentralgw-ui cd wlan-cloud-ucentralgw-ui
npm install npm install
npm start npm start
``` ```
Run these commands if you want to run the solution on your machine while also doing development on the [uCentral UI Library](https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs).
```
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs
cd wlan-cloud-ucentralgw-ui
npm link ../wlan-cloud-ucentral-ui-libs // Add sudo at the start of this command if it fails because of permissions
npm start
```
### Production ### Production
Here are the instructions to build the production veresion of the application. You need to run this in the root folder of the project and also have npm installed on your machine. You need to run this in the root folder of the project and also have npm installed on your machine.
``` ```
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
cd wlan-cloud-ucentralgw-ui cd wlan-cloud-ucentralgw-ui
npm install
npm run build npm run build
``` ```
Once the build is done, you can move the `build` folder on your server. Once the build is done, you can move the `build` folder on your server.
### Environment variables ### Configuration
There are two environment variables currently used to control the gateway URL and also controlling if the users can modify the gateway URL. You can modify these values in the `.env` file located in the root of the project. 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.
During development, you will need to stop and start the project again to see those changes come into effect. Here are the current default values:
```asm ```
REACT_APP_DEFAULT_GATEWAY_URL=https://ucentral.dpaas.arilia.com:16001 {
REACT_APP_ALLOW_GATEWAY_CHANGE=false "DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001",
"ALLOW_UCENTRALSEC_CHANGE": false
}
``` ```
- `REACT_APP_DEFAULT_GATEWAY_URL` points to the actual uCentral gateway, including the port.
- `REACT_APP_ALLOW_GATEWAY_CHANGE` : when set to `true` will allow a user to change the gateway name she wants to use. When set to `false`, will not show a text field for the gateway and will only allow users to go to the gateway speficied in `REACT_APP_DEFAULT_GATEWAY_URL`.

25
babel.config.json Normal file
View File

@@ -0,0 +1,25 @@
{
"presets": [
[
"@babel/preset-env",
{
"modules": false
}
],
"@babel/preset-react"
],
"env": {
"production": {
"plugins": [
"@babel/plugin-transform-react-inline-elements",
"@babel/plugin-transform-react-constant-elements",
[
"transform-react-remove-prop-types",
{
"removeImport": true
}
]
]
}
}
}

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'),
};

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

@@ -0,0 +1,75 @@
/* 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 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',
}),
new CleanWebpackPlugin(),
],
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' },
],
},
};

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

@@ -0,0 +1,54 @@
/* 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'),
graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'),
},
},
plugins: [new ReactRefreshWebpackPlugin()],
});

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

@@ -0,0 +1,86 @@
/* 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 CompressionPlugin = require('compression-webpack-plugin');
const path = require('path');
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 BundleAnalyzerPlugin(),
new MiniCssExtractPlugin({
filename: 'styles/[name].[contenthash].css',
chunkFilename: '[contenthash].css',
}),
new CompressionPlugin({
filename: '[path]/[name].gz[query]',
algorithm: 'gzip',
test: /\.js$|\.css$|\.html$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/,
threshold: 10240,
minRatio: 0.8,
}),
],
module: {
rules: [],
},
optimization: {
minimize: true,
minimizer: [
'...',
new TerserPlugin({
terserOptions: {
warnings: false,
compress: {
comparisons: false,
},
parse: {},
mangle: true,
output: {
ascii_only: true,
},
},
parallel: true,
}),
new CssMinimizerPlugin(),
],
nodeEnv: 'production',
sideEffects: true,
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: 10,
minSize: 0,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return `npm.${packageName.replace('@', '')}`;
},
},
},
},
},
resolve: {
modules: [],
alias: {
graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'),
},
},
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000,
},
});

View File

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

1
helm/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.swp

22
helm/.helmignore Normal file
View File

@@ -0,0 +1,22 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

5
helm/Chart.yaml Normal file
View File

@@ -0,0 +1,5 @@
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: ucentralgwui
version: 0.1.0

82
helm/README.md Normal file
View File

@@ -0,0 +1,82 @@
# ucentralgwui
This Helm chart helps to deploy uCentralGW-UI to the Kubernetes clusters. It is mainly used in [assembly chart](https://github.com/Telecominfraproject/wlan-cloud-ucentral-deploy/tree/main/chart) as uCentralGW-UI requires other services as dependencies that are considered in that Helm chart. This chart is purposed to define deployment logic close to the application code itself and define default values that could be overriden during deployment.
## TL;DR;
```bash
$ helm install .
```
## Introduction
This chart bootstraps an ucentralgwui on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
## Installing the Chart
Currently this chart is not assembled in charts archives, so [helm-git](https://github.com/aslafy-z/helm-git) is required for remote the installation
To install the chart with the release name `my-release`:
```bash
$ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui@helm?ref=main
```
The command deploys ucentralgwui on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation.
> **Tip**: List all releases using `helm list`
## Uninstalling the Chart
To uninstall/delete the `my-release` deployment:
```bash
$ helm delete my-release
```
The command removes all the Kubernetes components associated with the chart and deletes the release.
## Configuration
The following table lists the configurable parameters of the chart and their default values. If Default value is not listed in the table, please refer to the [Values](values.yaml) files for details.
| Parameter | Type | Description | Default |
|-----------|------|-------------|---------|
| replicaCount | number | Amount of replicas to be deployed | `1` |
| nameOverride | string | Override to be used for application deployment | |
| fullnameOverride | string | Override to be used for application deployment (has priority over nameOverride) | |
| images.ucentralgwui.repository | string | Docker image repository | |
| images.ucentralgwui.tag | string | Docker image tag | `'master'` |
| images.ucentralgwui.pullPolicy | string | Docker image pull policy | `'Always'` |
| services.ucentralgwui.type | string | uCentralGW-UI service type | `'ClusterIP'` |
| services.ucentralgwui.ports.http.servicePort | number | Websocket endpoint port to be exposed on service | `80` |
| services.ucentralgwui.ports.http.targetPort | number | Websocket endpoint port to be targeted by service | `80` |
| services.ucentralgwui.ports.http.protocol | string | Websocket endpoint protocol | `'TCP'` |
| checks.ucentralgwui.liveness.httpGet.path | string | Liveness check path to be used | `'/'` |
| checks.ucentralgwui.liveness.httpGet.port | number | Liveness check port to be used (should be pointint to ALB endpoint) | `http` |
| checks.ucentralgwui.readiness.httpGet.path | string | Readiness check path to be used | `'/'` |
| checks.ucentralgwui.readiness.httpGet.port | number | Readiness check port to be used | `http` |
| ingresses.default.enabled | boolean | Defines if uCentralGW-UI should be exposed via Ingress controller | `False` |
| ingresses.default.hosts | array | List of hosts for exposed uCentralGW-UI | |
| ingresses.default.paths | array | List of paths to be exposed for uCentralGW-UI | |
| public_env_variables | hash | Defines list of environment variables to be passed to uCentralGW-UI (required for application configuration) | |
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
```bash
$ helm install --name my-release \
--set replicaCount=1 \
.
```
The above command sets that only 1 instance of your app should be running
Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example,
```bash
$ helm install --name my-release -f values.yaml .
```
> **Tip**: You can use the default [values.yaml](values.yaml) as a base for customization.

View File

@@ -0,0 +1,32 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "ucentralgwui.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "ucentralgwui.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "ucentralgwui.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

View File

@@ -0,0 +1,86 @@
{{- $root := . -}}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "ucentralgwui.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "ucentralgwui.name" . }}
helm.sh/chart: {{ include "ucentralgwui.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "ucentralgwui.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- with .Values.services.ucentralgwui.labels }}
{{- toYaml . | nindent 6 }}
{{- end }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "ucentralgwui.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- with .Values.services.ucentralgwui.labels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
containers:
- name: ucentralgwui
image: "{{ .Values.images.ucentralgwui.repository }}:{{ .Values.images.ucentralgwui.tag }}"
imagePullPolicy: {{ .Values.images.ucentralgwui.pullPolicy }}
env:
- name: KUBERNETES_DEPLOYED
value: "{{ now }}"
{{- range $key, $value := .Values.public_env_variables }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
ports:
{{- range $key, $value := .Values.services.ucentralgwui.ports }}
- name: {{ $key }}
containerPort: {{ $value.targetPort }}
protocol: {{ $value.protocol }}
{{- end }}
{{- if .Values.checks.ucentralgwui.liveness }}
livenessProbe:
{{- toYaml .Values.checks.ucentralgwui.liveness | nindent 12 }}
{{- end }}
{{- if .Values.checks.ucentralgwui.readiness }}
readinessProbe:
{{- toYaml .Values.checks.ucentralgwui.readiness | nindent 12 }}
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
imagePullSecrets:
{{- range $image, $imageValue := .Values.images }}
{{- if $imageValue.regcred }}
- name: {{ include "ucentralgwui.fullname" $root }}-{{ $image }}-regcred
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,47 @@
{{- $root := . -}}
{{- range $ingress, $ingressValue := .Values.ingresses }}
{{- if $ingressValue.enabled }}
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ include "ucentralgwui.fullname" $root }}-{{ $ingress }}
labels:
app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }}
helm.sh/chart: {{ include "ucentralgwui.chart" $root }}
app.kubernetes.io/instance: {{ $root.Release.Name }}
app.kubernetes.io/managed-by: {{ $root.Release.Service }}
{{- with $ingressValue.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if $ingressValue.tls }}
tls:
{{- range $ingressValue.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ tpl .secretName $root }}
{{- end }}
{{- end }}
rules:
{{- range $ingressValue.hosts }}
- host: {{ . | quote }}
http:
paths:
{{- range $ingressValue.paths }}
- path: {{ .path }}
backend:
serviceName: {{ include "ucentralgwui.fullname" $root }}-{{ .serviceName }}
servicePort: {{ .servicePort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,21 @@
{{- define "imagePullSecret" }}
{{- printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" .registry (printf "%s:%s" .username .password | b64enc) | b64enc }}
{{- end }}
{{- $root := . -}}
{{- range $image, $imageValue := .Values.images }}
{{- if $imageValue.regcred }}
---
apiVersion: v1
kind: Secret
type: kubernetes.io/dockerconfigjson
metadata:
labels:
app.kuberentes.io/name: {{ include "ucentralgwui.name" $root }}
helm.sh/chart: {{ include "ucentralgwui.chart" $root }}
app.kubernetes.io/instance: {{ $root.Release.Name }}
app.kubernetes.io/managed-by: {{ $root.Release.Service }}
name: {{ include "ucentralgwui.fullname" $root }}-{{ $image }}-regcred
data:
.dockerconfigjson: {{ template "imagePullSecret" $imageValue.regcred }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,48 @@
{{- $root := . -}}
{{- range $service, $serviceValue := .Values.services }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "ucentralgwui.fullname" $root }}-{{ $service }}
{{- with $serviceValue.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }}
helm.sh/chart: {{ include "ucentralgwui.chart" $root }}
app.kubernetes.io/instance: {{ $root.Release.Name }}
app.kubernetes.io/managed-by: {{ $root.Release.Service }}
{{- with $serviceValue.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- if $serviceValue.serviceMonitor }}
{{- range $selector, $selectorValue := $serviceValue.serviceMonitor.serviceSelector }}
{{ $selector }}: {{ tpl $selectorValue $root }}
{{- end }}
{{- end }}
spec:
type: {{ $serviceValue.type }}
ports:
{{- range $service_service, $service_value := $serviceValue.ports }}
- name: {{ $service_service }}
targetPort: {{ $service_value.targetPort }}
protocol: {{ $service_value.protocol }}
port: {{ $service_value.servicePort }}
{{- if and (eq "NodePort" $serviceValue.type) $service_value.nodePort }}
nodePort: {{ $service_value.nodePort }}
{{- end }}
{{- end }}
selector:
app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }}
app.kubernetes.io/instance: {{ $root.Release.Name }}
{{- with $serviceValue.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

75
helm/values.yaml Normal file
View File

@@ -0,0 +1,75 @@
# System
replicaCount: 1
nameOverride: ""
fullnameOverride: ""
images:
ucentralgwui:
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/ucentralgw-ui
tag: v2.1.0-RC1
pullPolicy: Always
services:
ucentralgwui:
type: ClusterIP
ports:
http:
servicePort: 80
targetPort: 80
protocol: TCP
checks:
ucentralgwui:
liveness:
httpGet:
path: /
port: http
readiness:
httpGet:
path: /
port: http
ingresses:
default:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
# tls:
# - secretName: '{{ include "ucentralgwui.fullname" . }}-default-tls' # template may be used
# cert: |
# CERT_HERE_IN_PEM
# key: |
# KEY_HERE_IN_PEM
# hosts:
# - chart-example.local
hosts:
- chart-example.local
paths:
- path: /
serviceName: ucentralgwui
servicePort: http
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# requests:
# cpu: 100m
# memory: 128Mi
# limits:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
# Application
public_env_variables:
DEFAULT_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001
ALLOW_UCENTRALSEC_CHANGE: false

View File

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

32882
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,16 @@
{ {
"name": "ucentral-client", "name": "ucentral-client",
"version": "0.9.0", "version": "2.1.0",
"private": true,
"dependencies": { "dependencies": {
"@coreui/coreui": "^3.4.0", "@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1", "@coreui/icons": "^2.0.1",
"@coreui/icons-react": "^1.1.0", "@coreui/icons-react": "^1.1.0",
"@coreui/react": "^3.4.6", "@coreui/react": "^3.4.6",
"@fortawesome/fontawesome-svg-core": "^1.2.35", "@coreui/react-chartjs": "^1.1.0",
"@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", "apexcharts": "^3.27.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"axios-retry": "^3.1.9", "axios-retry": "^3.1.9",
"http": "^0.0.1-security", "dagre": "^0.8.5",
"https": "^1.0.0",
"i18next": "^20.3.1", "i18next": "^20.3.1",
"i18next-browser-languagedetector": "^6.1.2", "i18next-browser-languagedetector": "^6.1.2",
"i18next-http-backend": "^1.2.6", "i18next-http-backend": "^1.2.6",
@@ -25,21 +18,23 @@
"react": "^17.0.2", "react": "^17.0.2",
"react-apexcharts": "^1.3.9", "react-apexcharts": "^1.3.9",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-flow-renderer": "^9.6.6",
"react-i18next": "^11.11.0", "react-i18next": "^11.11.0",
"react-paginate": "^7.1.3", "react-paginate": "^7.1.3",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "^4.0.3",
"react-select": "^4.3.1", "react-select": "^4.3.1",
"react-tooltip": "^4.2.21",
"react-widgets": "^5.1.1", "react-widgets": "^5.1.1",
"redux": "^4.1.0", "sass": "^1.35.1",
"ucentral-libs": "^0.8.82",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
"main": "index.js",
"scripts": { "scripts": {
"start": "react-scripts start", "start": "webpack serve --config config/webpack.dev.js",
"build": "react-scripts build", "build": "webpack --config config/webpack.prod.js",
"test": "react-scripts test", "format": "prettier --write 'src/**/*.js'",
"eject": "react-scripts eject" "eslint-fix": "eslint --fix 'src/**/*.js'"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
@@ -50,12 +45,56 @@
} }
}, },
"lint-staged": { "lint-staged": {
"src/**/*.{js,jsx}": [ "*.{js,jsx}": [
"eslint", "eslint",
"pretty-quick — staged", "prettier --write"
"git add"
] ]
}, },
"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",
"compression-webpack-plugin": "^8.0.1",
"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-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.8.0"
},
"browserslist": { "browserslist": {
"production": [ "production": [
">0.2%", ">0.2%",
@@ -67,17 +106,5 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari 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"
} }
} }

4
public/config.json Normal file
View File

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

View File

@@ -2,42 +2,13 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <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="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <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> <title>uCentralGW</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <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> </body>
</html> </html>

View File

@@ -1,82 +1,157 @@
{ {
"actions": { "actions": {
"blink": "Blinken", "actions": "Aktionen",
"blink": "LEDs Blinken",
"configure": "Konfigurieren", "configure": "Konfigurieren",
"connect": "Verbinden", "connect": "Konsole öffnen",
"connecting": "Verbindung wird hergestellt ...", "connecting": "Die Verbindung wird hergestellt ...",
"factory_reset": "Werkseinstellungen zurückgesetzt", "factory_reset": "Auf Werkseinstellungen zurückgesetzt",
"firmware_upgrade": "Firmware-Aktualisierung", "firmware_upgrade": "Firmware Aktualisierung",
"reboot": "Starten Sie neu", "reboot": "Gerät neustarten",
"title": "Geräteaktionen", "title": "Geräte Administrations",
"trace": "Spur", "trace": "Tcpdump starten",
"wifi_scan": "WLAN-Scan" "wifi_scan": "Wi-Fi Scan"
}, },
"blink": { "blink": {
"blink": "Blinken", "blink": "LEDs Blinken",
"device_leds": "Geräte-LEDs", "device_leds": "LEDs",
"execute_now": "Möchten Sie dieses Muster jetzt einstellen?",
"pattern": "Wählen Sie das Muster, das Sie verwenden möchten:", "pattern": "Wählen Sie das Muster, das Sie verwenden möchten:",
"when_blink_leds": "Wann möchten Sie die Geräte-LEDs blinken lassen?" "set_leds": "LEDs einstellen",
"when_blink_leds": "Wann möchten Sie die LEDs blinken lassen?"
}, },
"commands": { "commands": {
"error": "Fehler beim Senden des Befehls!", "error": "Fehler beim Senden des Befehls!",
"success": "Befehl erfolgreich übermittelt", "event_queue": "Ereigniswarteschlange",
"title": "Gerätebefehle" "success": "Befehl wurde erfolgreich übermittelt",
"title": "Gerätebefehle",
"unable_queue": "Anfrage für Ereigniswarteschlange kann nicht abgeschlossen werden"
}, },
"common": { "common": {
"access_policy": "Zugangsrichtlinien",
"add": "Hinzufügen",
"adding_ellipsis": "Hinzufügen ...",
"are_you_sure": "Bist du sicher?", "are_you_sure": "Bist du sicher?",
"cancel": "Stornieren", "back_to_login": "Zurück zur Anmeldung",
"cancel": "Abbrechen",
"certificate": "Zertifikat", "certificate": "Zertifikat",
"clear": "klar", "certificates": "Zertifikate",
"clear": "Löschen",
"close": "Schließen", "close": "Schließen",
"command": "Befehl", "command": "Befehl",
"commands": "Befehle",
"commands_executed": "Ausgeführte Befehle",
"compatible": "kompatibel", "compatible": "kompatibel",
"completed": "Abgeschlossen", "completed": "Abgeschlossen",
"config_id": "Konfig. Ich würde", "config_id": "Konfigurations ID",
"confirm": "Bestätigen", "confirm": "Bestätigen",
"connected": "In Verbindung gebracht", "connected": "Verbindung wurde hergestellt",
"copied": "kopiert!", "copied": "kopiert!",
"copy_to_clipboard": "In die Zwischenablage kopieren", "copy_to_clipboard": "In die Zwischenablage kopieren",
"create": "Erstellen",
"created": "Erstellt",
"created_by": "Erstellt von",
"current": "Aktuell",
"custom_date": "Benutzerdefiniertes Datum",
"dashboard": "Instrumententafel",
"date": "Datum", "date": "Datum",
"day": "tag",
"days": "tage",
"delete": "Löschen", "delete": "Löschen",
"delete_device": "Gerät löschen",
"details": "Einzelheiten", "details": "Einzelheiten",
"device": "Gerät #{{serialNumber}}",
"device_dashboard": "Geräte-Dashboard",
"device_delete": "Gerät Nr.{{serialNumber}}löschen",
"device_deleted": "Gerät erfolgreich gelöscht",
"device_health": "Gerätezustand",
"device_list": "Liste der Geräte", "device_list": "Liste der Geräte",
"device_page": "Geräteseite", "device_page": "Aussicht",
"device_status": "Gerätestatus",
"devices": "Geräte", "devices": "Geräte",
"devices_using_latest": "Geräte mit der neuesten Firmware",
"devices_using_unknown": "Geräte mit unbekannter Firmware",
"dismiss": "entlassen", "dismiss": "entlassen",
"do_now": "Jetzt tun!", "do_now": "Sofort",
"download": "Herunterladen", "download": "Herunterladen",
"duration": "Dauer", "duration": "Dauer",
"error": "Error", "edit": "Bearbeiten",
"executed": "Hingerichtet", "edit_user": "Bearbeiten",
"email_address": "E-Mail-Addresse",
"endpoint": "Endpunkt",
"endpoints": "Endpunkte",
"error": "Fehler",
"execute_now": "Möchten Sie diesen Befehl jetzt ausführen?",
"executed": "Ausgeführt",
"exit": "Ausgang",
"firmware": "Firmware", "firmware": "Firmware",
"firmware_dashboard": "Firmware-Dashboard",
"firmware_installed": "Firmware installiert",
"forgot_password": "Haben Sie Ihr Passwort vergessen?",
"forgot_password_title": "Passwort vergessen",
"from": "Von", "from": "Von",
"id": "Ich würde", "general_error": "API-Fehler, wenden Sie sich bitte an Ihren Administrator",
"hide": "verbergen",
"hour": "stunde",
"hours": "std",
"id": "ID",
"ip_address": "IP Adresse", "ip_address": "IP Adresse",
"later_tonight": "Heute Abend später", "last_dashboard_refresh": "Letzte Dashboard-Aktualisierung",
"later_tonight": "Später am Abend",
"latest": "Neueste",
"list": "Liste",
"loading_ellipsis": "Wird geladen...", "loading_ellipsis": "Wird geladen...",
"loading_more_ellipsis": "Mehr laden ...", "loading_more_ellipsis": "Mehr laden ...",
"logout": "Ausloggen", "logout": "Ausloggen",
"mac": "MAC-Adresse", "mac": "MAC-Adresse",
"manufacturer": "Hersteller", "manufacturer": "Hersteller",
"na": "N / A", "memory_used": "Verwendeter Speicher",
"minute": "Minute",
"minutes": "protokoll",
"na": "(unbekannt)",
"need_date": "Du brauchst ein Datum...", "need_date": "Du brauchst ein Datum...",
"no": "Nein",
"no_devices_found": "Keine Geräte gefunden",
"no_items": "Keine Gegenstände",
"not_connected": "Nicht verbunden", "not_connected": "Nicht verbunden",
"off": "aus", "of_connected": "% der Geräte",
"on": "auf", "off": "Aus",
"on": "An",
"optional": "Wahlweise",
"overall_health": "Allgemeine Gesundheit",
"password_policy": "Kennwortrichtlinie",
"recorded": "Verzeichnet", "recorded": "Verzeichnet",
"refresh": "Aktualisierung", "refresh": "Aktualisierung",
"refresh_device": "Gerät aktualisieren", "refresh_device": "Gerät aktualisieren",
"required": "Erforderlich",
"result": "Ergebnis", "result": "Ergebnis",
"save": "Sparen",
"saved": "Gerettet!",
"saving": "Speichern ...",
"schedule": "Zeitplan", "schedule": "Zeitplan",
"serial_number": "Ordnungsnummer", "search": "Geräte suchen",
"second": "zweite",
"seconds": "sekunden",
"seconds_elapsed": "Sekunden verstrichen",
"serial_number": "Seriennummer",
"show_all": "Zeige alles",
"start": "Start", "start": "Start",
"submit": "einreichen", "submit": "Absenden",
"submitted": "Eingereicht", "submitted": "Eingereicht",
"success": "Erfolg", "success": "Erfolg",
"system": "System",
"table": "Tabelle",
"timestamp": "Zeit",
"to": "zu", "to": "zu",
"type": "Art",
"unable_to_connect": "Keine Verbindung zum Gerät möglich",
"unable_to_delete": "Löschen nicht möglich",
"unknown": "unbekannte", "unknown": "unbekannte",
"up_to_date": "Aktuelle Geräte",
"uptimes": "Betriebszeiten",
"uuid": "UUID", "uuid": "UUID",
"view_more": "Mehr sehen", "vendors": "Anbieter",
"view_more": "Mehr anzeigen",
"yes": "Ja" "yes": "Ja"
}, },
"configuration": { "configuration": {
@@ -86,77 +161,257 @@
"last_configuration_change": "Letzte Konfigurationsänderung", "last_configuration_change": "Letzte Konfigurationsänderung",
"last_configuration_download": "Letzter Konfigurations-Download", "last_configuration_download": "Letzter Konfigurations-Download",
"location": "Ort", "location": "Ort",
"note": "Hinweis",
"notes": "Anmerkungen", "notes": "Anmerkungen",
"owner": "Inhaber", "owner": "Inhaber",
"title": "Gerätekonfiguration", "title": "Gerätekonfiguration",
"type": "Gerätetyp", "type": "Gerätetyp",
"view_json": "Rohes JSON anzeigen" "uuid": "Konfigurations-ID",
"view_json": "Rohe Konfiguration anzeigen"
}, },
"configure": { "configure": {
"choose_file": "Sie müssen eine gültige .json-Datei auswählen:", "choose_file": "Sie müssen eine gültige .json-Datei auswählen:",
"enter_new": "Geben Sie die JSON für die neue Gerätekonfiguration ein:", "enter_new": "Geben Sie die JSON für die neue Gerätekonfiguration ein:",
"placeholder": "JSON konfigurieren", "placeholder": "Konfiguration",
"title": "Gerät konfigurieren", "title": "Gerät konfigurieren",
"valid_json": "Sie müssen gültiges JSON eingeben" "valid_json": "Sie müssen ein gültiges JSON eingeben"
}, },
"delete_command": { "delete_command": {
"explanation": "Möchten Sie diesen Befehl wirklich löschen? Diese Aktion ist nicht umkehrbar.", "explanation": "Möchten Sie diesen Befehl wirklich löschen? Diese Aktion ist nicht umkehrbar.",
"title": "Befehl löschen" "title": "Befehl löschen"
}, },
"delete_logs": {
"date": "Wählen Sie das Datum des ältesten Protokolls aus, das Sie behalten möchten",
"device_logs_title": "Geräteprotokolle löschen",
"explanation": "Dadurch werden alle {{object}} vor dem von Ihnen gewählten Datum gelöscht. Seien Sie vorsichtig, diese Aktion ist nicht umkehrbar.",
"healthchecks_title": "Healthchecks löschen"
},
"device_logs": { "device_logs": {
"log": "Log", "log": "Protokoll",
"severity": "Schwere", "severity": "Wichtigkeit",
"title": "Geräteprotokolle" "title": "Geräteprotokolle"
}, },
"entity": {
"add_child": "Untergeordnete Entität zu {{entityName}}hinzufügen",
"add_failure": "Fehler, der Server hat zurückgegeben: {{error}}",
"add_root": "Root-Entität hinzufügen",
"add_success": "Entität erfolgreich erstellt!",
"cannot_delete": "Entitäten mit untergeordneten Elementen können nicht gelöscht werden. Löschen Sie die untergeordneten Elemente dieser Entität, um sie löschen zu können.",
"delete_success": "Entität erfolgreich gelöscht",
"delete_warning": "Achtung: Dieser Vorgang kann nicht rückgängig gemacht werden",
"edit_failure": "Aktualisierung fehlgeschlagen : {{error}}",
"entities": "Entitäten",
"entity": "Entität",
"only_unassigned": "Nur nicht zugewiesen",
"valid_serial": "Muss eine gültige Seriennummer sein (12 HEX-Zeichen)"
},
"factory_reset": { "factory_reset": {
"redirector": "Weiterleitung beibehalten:", "redirector": "Gatewaykonfiguration beibehalten:",
"reset": "Zurücksetzen",
"resetting": "Zurücksetzen...",
"title": "Gerät auf Werkseinstellungen zurücksetzen", "title": "Gerät auf Werkseinstellungen zurücksetzen",
"warning": "Achtung: Nach dem Absenden kann dies nicht rückgängig gemacht werden" "warning": "Achtung: Nach dem Absenden kann dies nicht rückgängig gemacht werden"
}, },
"firmware": {
"average_age": "Durchschnittliches Firmware-Alter",
"choose_custom": "Wählen",
"details_title": "Bild #{{image}} Details",
"device_type": "Gerätetyp",
"device_types": "Gerätetypen",
"downloads": "Downloads",
"error_fetching_latest": "Fehler beim Abrufen der neuesten Firmware",
"from_release": "Von",
"history_title": "Geschichte",
"image": "Bild",
"image_date": "Bilddatum",
"installed_firmware": "Installierte Firmware",
"latest_version_installed": "Neueste Version installiert Version",
"newer_firmware_available": "Neuere Versionen verfügbar",
"reinstall_latest": "Neu installieren",
"revision": "Revision",
"show_dev": "Dev-Releases anzeigen",
"size": "Größe",
"status": "Firmware-Status",
"title": "Firmware",
"to_release": "Zu",
"unknown_firmware_status": "Unbekannter Firmware-Status",
"upgrade": "Aktualisierung",
"upgrade_command_submitted": "Upgrade-Befehl erfolgreich gesendet",
"upgrade_to_latest": "Neueste",
"upgrade_to_version": "Upgrade auf diese Revision",
"upgrading": "Upgrade durchführen..."
},
"footer": { "footer": {
"coreui_for_react": "CoreUI für React", "coreui_for_react": "CoreUI für React",
"powered_by": "Unterstützt von", "powered_by": "Unterstützt von",
"version": "Ausführung" "version": "Version"
}, },
"health": { "health": {
"sanity": "Gesundheit", "sanity": "Gesundheitzustand",
"title": "Gerätezustand" "title": "Gesundheitzustand"
},
"inventory": {
"add_child_venue": "Untergeordneten Veranstaltungsort zu {{entityName}}hinzufügen",
"add_tag": "Tag hinzufügen",
"add_tag_to": "Inventar-Tag zu {{name}}hinzufügen",
"assign_error": "Fehler beim Versuch, Tag zuzuweisen",
"assign_to_entity": "Zu Entität zuweisen",
"error_retrieving": "Beim Abrufen von Inventar-Tags ist ein Fehler aufgetreten",
"error_unassign": "Fehler beim Aufheben der Zuweisung",
"subscriber": "Teilnehmer",
"successful_assign": "Tag erfolgreich zugewiesen",
"successful_tag_update": "Tag erfolgreich aktualisiert",
"successful_unassign": "Vorgang zum Aufheben der Zuweisung war erfolgreich",
"tag_created": "Inventar-Tag erfolgreich erstellt",
"tag_creation_error": "Fehler beim Versuch, Inventar-Tag zu erstellen",
"tag_update_error": "Fehler beim Aktualisieren des Tags",
"tags_assigned_to": "Inventar-Tags {{name}}zugewiesen",
"unassign": "Zuordnung aufheben",
"unassign_tag": "Tag von Entität zuweisen",
"unassigned_tags": "Nicht zugewiesene Tags",
"venue": "Tagungsort"
}, },
"login": { "login": {
"change_password": "Ändere das Passwort",
"change_password_error": "Fehler beim Ändern des Passworts. Stellen Sie sicher, dass das neue Passwort gültig ist, indem Sie die Seite \"Passwortrichtlinie\" besuchen",
"change_password_instructions": "Geben Sie Ihr neues Passwort ein und bestätigen Sie es",
"changing_password": "Passwort ändern...",
"confirm_new_password": "Bestätige neues Passwort",
"different_passwords": "Sie müssen das gleiche Passwort zweimal eingeben",
"forgot_password_error": "Fehler beim Versuch, eine E-Mail mit vergessenem Passwort zu senden. Stellen Sie sicher, dass diese Benutzer-ID mit einem Konto verknüpft ist.",
"forgot_password_explanation": "Geben Sie Ihren Benutzernamen ein, um eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts zu erhalten",
"forgot_password_success": "Sie sollten in Kürze eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts erhalten. Bitte überprüfen Sie Ihren Spam, wenn Sie die E-Mail nicht finden können",
"logging_in": "Einloggen...",
"login": "Anmeldung", "login": "Anmeldung",
"login_error": "Anmeldefehler, bestätigen Sie, dass Ihr Benutzername, Ihr Passwort und Ihre Gateway-URL gültig sind", "login_error": "Anmeldefehler, stellen Sie sicher, dass die von Ihnen angegebenen Informationen gültig sind",
"new_password": "Neues Kennwort",
"password": "Passwort", "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_password": "Bitte geben Sie Ihr Passwort ein",
"please_enter_username": "Bitte geben Sie Ihren Benutzernamen ein", "please_enter_username": "Bitte geben Sie Ihren Benutzernamen ein",
"previously_used": "Passwort wurde zuvor verwendet",
"send_forgot": "E-Mail senden",
"sending_ellipsis": "Senden…",
"sign_in_to_account": "Melden Sie sich bei Ihrem Konto an", "sign_in_to_account": "Melden Sie sich bei Ihrem Konto an",
"username": "Nutzername" "url": "uCentralSec-URL",
"username": "Benutzername"
}, },
"reboot": { "reboot": {
"directions": "Wann möchten Sie dieses Gerät neu starten?", "directions": "Wann möchten Sie dieses Gerät neu starten?",
"now": "Möchten Sie dieses Gerät jetzt neu starten?",
"title": "Gerät neustarten" "title": "Gerät neustarten"
}, },
"scan": { "scan": {
"active": "Aktiven Scan aktivieren", "active": "Aktiven Scan aktivieren",
"channel": "Kanal", "channel": "Kanal",
"directions": "Starten Sie einen WLAN-Scan dieses Geräts, der ungefähr 25 Sekunden dauern sollte.", "directions": "Starten Sie einen WiFi-Scan dieses Geräts, der ungefähr 25 Sekunden dauern sollte.",
"results": "Ergebnisse des WLAN-Scans" "re_scan": "Erneut scannen",
"result_directions": "Bitte klicken Sie auf die Schaltfläche '$t(scan.re_scan)', wenn Sie einen Scan mit derselben Konfiguration wie beim letzten Scan durchführen möchten.",
"results": "Ergebnisse des WiFi-Scans",
"scan": "Scan",
"scanning": "Scannen... ",
"waiting_directions": "Bitte warten Sie auf das Scanergebnis. Dies kann bis zu 25 Sekunden dauern. Sie können den Vorgang später beenden und die Ergebnisse aus der Befehlstabelle anzeigen."
},
"settings": {
"title": "die Einstellungen"
}, },
"statistics": { "statistics": {
"data": "Daten (KB)", "data": "Daten (KB)",
"latest_statistics": "Neueste Statistiken",
"lifetime_stats": "Lifetime-Statistik",
"no_interfaces": "Keine Statistiken zur Schnittstellenlebensdauer verfügbar",
"show_latest": "Letzte Statistik",
"title": "Statistiken" "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"
},
"system": {
"error_fetching": "Fehler beim Abrufen von Systeminformationen"
},
"trace": { "trace": {
"choose_network": "Netzwerk auswählen", "choose_network": "Netzwerk auswählen",
"directions": "Starten Sie eine Fernverfolgung dieses Geräts für eine bestimmte Dauer oder eine Anzahl von Paketen", "directions": "Starten Sie eine Tcpdump auf diesem Geräts für eine bestimmte Dauer oder eine Anzahl von Paketen",
"download_trace": "Trace-Datei herunterladen",
"packets": "Pakete", "packets": "Pakete",
"title": "Trace-Gerät" "title": "Tcpdump",
"trace": "Spur",
"trace_not_successful": "Trace nicht erfolgreich: Gateway hat folgenden Fehler gemeldet: {{error}}",
"wait_for_file": "Möchten Sie warten, bis die Trace-Datei fertig ist?",
"waiting_directions": "Bitte warten Sie auf die Trace-Datendatei. Dies könnte eine Weile dauern. Sie können das Warten beenden und die Ablaufverfolgungsdatei später aus der Befehlstabelle abrufen.",
"waiting_seconds": "Verstrichene Zeit: {{seconds}} Sekunden"
}, },
"upgrade": { "upgrade": {
"command_submitted": "Befehl gesendet",
"device_disconnected": "Gerät getrennt",
"device_reconnected": "Gerät wieder verbunden",
"device_upgrading_firmware": "Geräte-Firmware-Upgrade",
"directions": "Wählen Sie eine Uhrzeit und eine Firmware-Version für dieses Gerät", "directions": "Wählen Sie eine Uhrzeit und eine Firmware-Version für dieses Gerät",
"firmware_uri": "Firmware-URI:", "firmware_uri": "Firmware-URL:",
"need_uri": "Du brauchst eine URI...", "need_uri": "Sie brauchen eine URL...",
"time": "Zeitpunkt der Aktualisierung:", "new_version": "Neue Version ist",
"title": "Firmware-Aktualisierung" "offline_device": "Diese Option ist gesperrt, da dieses Gerät nicht verbunden ist",
"time": "Zeitpunkt für Aktualisierung:",
"title": "Firmware Aktualisieren",
"upgrade": "Aktualisierung",
"wait_for_upgrade": "Möchten Sie warten, bis das Upgrade abgeschlossen ist?",
"waiting_for_device": "Warten, bis das Gerät wieder verbunden ist"
},
"user": {
"avatar": "Dein Avatar",
"avatar_file": "Dein Avatar (max. 2 MB)",
"create": "Benutzer erstellen",
"create_failure": "Fehler beim Erstellen des Benutzers. Bitte stellen Sie sicher, dass diese E-Mail-Adresse nicht bereits mit einem Konto verknüpft ist.",
"create_success": "Benutzer erfolgreich erstellt",
"creating": "Benutzer erstellen ...",
"delete_avatar": "Avatar löschen",
"delete_failure": "Fehler beim Versuch, den Benutzer zu löschen",
"delete_success": "Benutzer erfolgreich gelöscht!",
"delete_title": "Benutzer löschen",
"delete_warning": "Warnung: Sobald Sie einen Benutzer gelöscht haben, können Sie ihn nicht wiederherstellen",
"deleting": "Löschen ...",
"description": "Beschreibung",
"edit": "Benutzer bearbeiten",
"email_address": "E-Mail-Addresse",
"force_password_change": "Passwortänderung bei der Anmeldung erzwingen",
"id": "Benutzeridentifikation.",
"last_login": "Letzte Anmeldung",
"login_id": "Anmelde-ID.",
"my_profile": "Mein Profil",
"name": "Name",
"nickname": "Spitzname",
"nickname_explanation": "Spitzname (optional)",
"not_validated": "Nicht validiert",
"note": "Hinweis",
"password": "Passwort",
"provide_email": "Bitte geben Sie eine gültige E-Mail Adresse an",
"provide_password": "Bitte geben Sie ein gültiges Passwort ein",
"save_avatar": "Avatar speichern",
"show_hide_password": "Passwort anzeigen/verbergen",
"update_failure": "Stellen Sie sicher, dass alle Ihre Daten gültig sind. Wenn Sie das Kennwort ändern, stellen Sie sicher, dass es sich nicht um ein altes handelt.",
"update_failure_title": "Update fehlgeschlagen",
"update_success": "Benutzer erfolgreich aktualisiert",
"update_success_title": "Erfolg",
"user_role": "Rolle",
"users": "Benutzer",
"validated": "Bestätigt"
},
"wifi_analysis": {
"association": "Verband",
"associations": "Verbände",
"mode": "Modus",
"network_diagram": "Netzwerkdiagramm",
"radios": "Radios",
"title": "WLAN-Analyse"
} }
} }

View File

@@ -1,5 +1,6 @@
{ {
"actions": { "actions": {
"actions": "Actions",
"blink": "Blink", "blink": "Blink",
"configure": "Configure", "configure": "Configure",
"connect": "Connect", "connect": "Connect",
@@ -7,28 +8,39 @@
"factory_reset": "Factory Reset", "factory_reset": "Factory Reset",
"firmware_upgrade": "Firmware Upgrade", "firmware_upgrade": "Firmware Upgrade",
"reboot": "Reboot", "reboot": "Reboot",
"title": "Device Actions", "title": "Commands",
"trace": "Trace", "trace": "Trace",
"wifi_scan": "Wifi Scan" "wifi_scan": "Wi-Fi Scan"
}, },
"blink": { "blink": {
"blink": "Blink", "blink": "Blink",
"device_leds": "Device LEDs", "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?" "when_blink_leds": "When would you like to make the device LEDs blink?"
}, },
"commands": { "commands": {
"error": "Error while submitting command!", "error": "Error while submitting command!",
"success": "Command submitted successfully", "event_queue": "Event Queue",
"title": "Device Commands" "success": "Command submitted successfully, you can look at the Commands log for the result",
"title": "Command History",
"unable_queue": "Unable to complete event queue request"
}, },
"common": { "common": {
"access_policy": "Access Policy",
"add": "Add",
"adding_ellipsis": "Adding...",
"are_you_sure": "Are you sure?", "are_you_sure": "Are you sure?",
"back_to_login": "Back to Login",
"cancel": "Cancel", "cancel": "Cancel",
"certificate": "Certificate", "certificate": "Certificate",
"certificates": "Certificates",
"clear": "Clear", "clear": "Clear",
"close": "Close", "close": "Close",
"command": "Command", "command": "Command",
"commands": "Commands",
"commands_executed": "Commands Executed",
"compatible": "Compatible", "compatible": "Compatible",
"completed": "Completed", "completed": "Completed",
"config_id": "Config. Id", "config_id": "Config. Id",
@@ -36,83 +48,199 @@
"connected": "Connected", "connected": "Connected",
"copied": "Copied!", "copied": "Copied!",
"copy_to_clipboard": "Copy to clipboard", "copy_to_clipboard": "Copy to clipboard",
"create": "Create",
"created": "Created",
"created_by": "Created By",
"current": "Current ",
"custom_date": "Custom Date",
"dashboard": "Dashboard",
"date": "Date", "date": "Date",
"day": "day",
"days": "days",
"delete": "Delete", "delete": "Delete",
"delete_device": "Delete Device",
"details": "Details", "details": "Details",
"device": "Device #{{serialNumber}}",
"device_dashboard": "Device Dashboard",
"device_delete": "Delete Device #{{serialNumber}}",
"device_deleted": "Device Successfully Deleted",
"device_health": "Device Health",
"device_list": "List of Devices", "device_list": "List of Devices",
"device_page": "Device Page", "device_page": "View",
"device_status": "Device Status",
"devices": "Devices", "devices": "Devices",
"devices_using_latest": "Devices Using Latest Firmware",
"devices_using_unknown": "Devices Using Unknown Firmware",
"dismiss": "Dismiss", "dismiss": "Dismiss",
"do_now": "Do Now!", "do_now": "Do Now!",
"download": "Download", "download": "Download",
"duration": "Duration", "duration": "Duration",
"edit": "Edit",
"edit_user": "Edit",
"email_address": "Email Address",
"endpoint": "Endpoint",
"endpoints": "Endpoints",
"error": "Error", "error": "Error",
"execute_now": "Would you like to execute this command now?",
"executed": "Executed", "executed": "Executed",
"exit": "Exit",
"firmware": "Firmware", "firmware": "Firmware",
"firmware_dashboard": "Firmware Dashboard",
"firmware_installed": "Firmware Installed",
"forgot_password": "Forgot your Password?",
"forgot_password_title": "Forgot Password",
"from": "From", "from": "From",
"general_error": "API Error, please consult your administrator",
"hide": "Hide",
"hour": "hour",
"hours": "hours",
"id": "Id", "id": "Id",
"ip_address": "Ip Address", "ip_address": "IP Address",
"last_dashboard_refresh": "Last Dashboard Refresh",
"later_tonight": "Later tonight", "later_tonight": "Later tonight",
"latest": "Latest",
"list": "List",
"loading_ellipsis": "Loading...", "loading_ellipsis": "Loading...",
"loading_more_ellipsis": "Loading more...", "loading_more_ellipsis": "Loading more...",
"logout": "Logout", "logout": "Logout",
"mac": "MAC Address", "mac": "MAC Address",
"manufacturer": "Manufacturer", "manufacturer": "Manufacturer",
"memory_used": "Memory Used",
"minute": "minute",
"minutes": "minutes",
"na": "N/A", "na": "N/A",
"need_date": "You need a date...", "need_date": "You need a date...",
"no": "No",
"no_devices_found": "No Devices Found",
"no_items": "No Items",
"not_connected": "Not Connected", "not_connected": "Not Connected",
"of_connected": "% of devices",
"off": "Off", "off": "Off",
"on": "On", "on": "On",
"optional": "Optional",
"overall_health": "Overall Health",
"password_policy": "Password Policy",
"recorded": "Recorded", "recorded": "Recorded",
"refresh": "Refresh", "refresh": "Refresh",
"refresh_device": "Refresh Device", "refresh_device": "Refresh Device",
"required": "Required",
"result": "Result", "result": "Result",
"save": "Save",
"saved": "Saved!",
"saving": "Saving... ",
"schedule": "Schedule", "schedule": "Schedule",
"search": "Search Devices",
"second": "second",
"seconds": "seconds",
"seconds_elapsed": "Seconds elapsed",
"serial_number": "Serial Number", "serial_number": "Serial Number",
"show_all": "Show All",
"start": "Start", "start": "Start",
"submit": "Submit", "submit": "Submit",
"submitted": "Submitted", "submitted": "Submitted",
"success": "Success", "success": "Success",
"system": "System",
"table": "Table",
"timestamp": "Time",
"to": "To", "to": "To",
"type": "Type",
"unable_to_connect": "Unable to Connect to Device",
"unable_to_delete": "Unable to Delete",
"unknown": "Unknown", "unknown": "Unknown",
"up_to_date": "Up to Date Devices",
"uptimes": "Uptimes",
"uuid": "UUID", "uuid": "UUID",
"vendors": "Vendors",
"view_more": "View more", "view_more": "View more",
"yes": "Yes" "yes": "Yes"
}, },
"configuration": { "configuration": {
"created": "Created", "created": "Created",
"details": "Device Details", "details": "Details",
"device_password": "Password", "device_password": "Password",
"last_configuration_change": "Last Configuration Change", "last_configuration_change": "Last Configuration Change",
"last_configuration_download": "Last Configuration Download", "last_configuration_download": "Last Configuration Download",
"location": "Location", "location": "Location",
"note": "Note",
"notes": "Notes", "notes": "Notes",
"owner": "Owner", "owner": "Owner",
"title": "Device Configuration", "title": "Configuration",
"type": "Device Type", "type": "Device Type",
"uuid": "Config ID",
"view_json": "View raw JSON" "view_json": "View raw JSON"
}, },
"configure": { "configure": {
"choose_file": "You need to choose a valid .json file: ", "choose_file": "You need to choose a valid .json file: ",
"enter_new": "Enter new device configuration JSON: ", "enter_new": "Enter new device configuration JSON: ",
"placeholder": "Config JSON", "placeholder": "Config JSON",
"title": "Configure Device", "title": "Configure",
"valid_json": "You need to enter valid JSON" "valid_json": "You need to enter valid JSON"
}, },
"delete_command": { "delete_command": {
"explanation": "Are you sure you want to delete this command? This action is not reversible.", "explanation": "Are you sure you want to delete this command? This action is not reversible.",
"title": "Delete Command" "title": "Delete Command"
}, },
"delete_logs": {
"date": "Select the date of the oldest log you would like to keep",
"device_logs_title": "Delete Device Logs",
"explanation": "This will delete all of the {{object}} before the date you choose. Be careful, this action is not reversible.",
"healthchecks_title": "Delete Healthchecks"
},
"device_logs": { "device_logs": {
"log": "Log", "log": "Log",
"severity": "Severity", "severity": "Severity",
"title": "Device Logs" "title": "Logs"
},
"entity": {
"add_child": "Add Child Entity to {{entityName}}",
"add_failure": "Error, the server returned : {{error}}",
"add_root": "Add Root Entity",
"add_success": "Entity Successfully Created!",
"cannot_delete": "You cannot delete entities which have children. Delete this entity's children to be able to delete it.",
"delete_success": "Entity Successfully Deleted",
"delete_warning": "Warning: this operation cannot be reverted",
"edit_failure": "Update unsuccessful : {{error}}",
"entities": "Entities",
"entity": "Entity",
"only_unassigned": "Only Unassigned",
"valid_serial": "Needs to be a valid serial number (12 HEX characters)"
}, },
"factory_reset": { "factory_reset": {
"redirector": "Keep redirector: ", "redirector": "Keep redirector: ",
"title": "Factory Reset Device", "reset": "Reset",
"resetting": "Resetting... ",
"title": "Factory Reset",
"warning": "Warning: Once you submit this cannot be reverted" "warning": "Warning: Once you submit this cannot be reverted"
}, },
"firmware": {
"average_age": "Average Firmware Age",
"choose_custom": "Choose",
"details_title": "Image #{{image}} Details",
"device_type": "Device Type",
"device_types": "Device Types",
"downloads": "Downloads",
"error_fetching_latest": "Error while fetching latest firmware",
"from_release": "From",
"history_title": "History",
"image": "Image",
"image_date": "Image Date",
"installed_firmware": "Installed Firmware",
"latest_version_installed": "Latest Version Installed",
"newer_firmware_available": "Newer Revisions Available",
"reinstall_latest": "Reinstall ",
"revision": "Revision",
"show_dev": "Show Dev Releases",
"size": "Size",
"status": "Firmware Status",
"title": "Firmware",
"to_release": "To",
"unknown_firmware_status": "Unknown Firmware Status",
"upgrade": "Upgrade",
"upgrade_command_submitted": "Upgrade Command Submitted Successfully",
"upgrade_to_latest": "Latest",
"upgrade_to_version": "Upgrade to this Revision",
"upgrading": "Upgrading..."
},
"footer": { "footer": {
"coreui_for_react": "CoreUI for React", "coreui_for_react": "CoreUI for React",
"powered_by": "Powered by", "powered_by": "Powered by",
@@ -120,43 +248,170 @@
}, },
"health": { "health": {
"sanity": "Sanity", "sanity": "Sanity",
"title": "Device Health" "title": "Health"
},
"inventory": {
"add_child_venue": "Add Child Venue to {{entityName}}",
"add_tag": "Add Tag",
"add_tag_to": "Add Inventory Tag to {{name}}",
"assign_error": "Error while trying to assign tag",
"assign_to_entity": "Assign to Entity",
"error_retrieving": "Error occurred while retrieving inventory tags",
"error_unassign": "Error during unassign operation",
"subscriber": "Subscriber",
"successful_assign": "Tag successfully assigned",
"successful_tag_update": "Successfully updated tag",
"successful_unassign": "Unassign operation was successful",
"tag_created": "Inventory tag successfully created",
"tag_creation_error": "Error while trying to create inventory tag",
"tag_update_error": "Error while updating tag",
"tags_assigned_to": "Inventory tags assigned to {{name}}",
"unassign": "Unassign",
"unassign_tag": "Unassign Tag from Entity",
"unassigned_tags": "Unassigned tags",
"venue": "Venue"
}, },
"login": { "login": {
"change_password": "Change Password",
"change_password_error": "Error while changing password. Make sure the new password is valid by visiting the 'Password Policy' page",
"change_password_instructions": "Enter and confirm your new password",
"changing_password": "Changing Password... ",
"confirm_new_password": "Confirm New Password",
"different_passwords": "You need to enter the same password twice",
"forgot_password_error": "Error while trying to send Forgot Password email. Please make sure this userId is associated to an account.",
"forgot_password_explanation": "Enter your username to receive an email containing the instructions to reset your password",
"forgot_password_success": "You should soon receive an email containing the instructions to reset your password. Please make sure to check your spam if you can't find the email",
"logging_in": "Logging In... ",
"login": "Login", "login": "Login",
"login_error": "Login error, confirm that your username, password and gateway url are valid", "login_error": "Login error, make sure the information you are providing is valid",
"new_password": "New Password",
"password": "Password", "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_password": "Please enter your password",
"please_enter_username": "Please enter your username", "please_enter_username": "Please enter your username",
"previously_used": "Password was previously used",
"send_forgot": "Send Email",
"sending_ellipsis": "Sending... ",
"sign_in_to_account": "Sign in to your account", "sign_in_to_account": "Sign in to your account",
"url": "uCentralSec URL",
"username": "Username" "username": "Username"
}, },
"reboot": { "reboot": {
"directions": "When would you like to reboot this device?", "directions": "When would you like to reboot this device?",
"title": "Reboot Device" "now": "Would you like to reboot this device now?",
"title": "Reboot"
}, },
"scan": { "scan": {
"active": "Enable active scan", "active": "Enable active scan",
"channel": "Channel", "channel": "Channel",
"directions": "Launch a wifi scan of this device, which should take approximately 25 seconds.", "directions": "Launch a wifi scan of this device, which should take approximately 25 seconds.",
"results": "Wifi Scan Results" "re_scan": "Re-Scan",
"result_directions": "Please click the '$t(scan.re_scan)' button if you would like to do a scan with the same configuration as the last.",
"results": "Wi-Fi Scan Results",
"scan": "Scan",
"scanning": "Scanning... ",
"waiting_directions": "Please wait for the scan result. This may take up to 25 seconds. You can exit and look at the results from the commands table later."
},
"settings": {
"title": "Settings"
}, },
"statistics": { "statistics": {
"data": "Data (KB)", "data": "Data (KB)",
"latest_statistics": "Latest Statistics",
"lifetime_stats": "Lifetime Statistics",
"no_interfaces": "No interface lifetime statistics available",
"show_latest": "Last Statistics",
"title": "Statistics" "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 "
},
"system": {
"error_fetching": "Error while fetching system information"
},
"trace": { "trace": {
"choose_network": "Choose network", "choose_network": "Choose network",
"directions": "Launch a remote trace of this device for either a specific duration or a number of packets", "directions": "Launch a remote trace of this device for either a specific duration or a number of packets",
"download_trace": "Download Trace File",
"packets": "Packets", "packets": "Packets",
"title": "Trace Device" "title": "Trace",
"trace": "Trace",
"trace_not_successful": "Trace not successful: gateway reported the following error : {{error}}",
"wait_for_file": "Would you like to wait until the trace file is ready?",
"waiting_directions": "Please wait for the trace data file. This may take some time. You can exit the wait and retrieve the trace file from the commands table later.",
"waiting_seconds": "Time Elapsed: {{seconds}} seconds"
}, },
"upgrade": { "upgrade": {
"command_submitted": "Command submitted",
"device_disconnected": "Device disconnected",
"device_reconnected": "Device reconnected",
"device_upgrading_firmware": "Device upgrading firmware",
"directions": "Choose a time and a firmware version for this device", "directions": "Choose a time and a firmware version for this device",
"firmware_uri": "Firmware URI:", "firmware_uri": "Firmware URI:",
"need_uri": "You need a URI...", "need_uri": "You need a URI...",
"new_version": "New version is ",
"offline_device": "This option is blocked because this device is not connected",
"time": "Time of upgrade:", "time": "Time of upgrade:",
"title": "Firmware Upgrade" "title": "Firmware Upgrade",
"upgrade": "Upgrade",
"wait_for_upgrade": "Would you like to wait for the upgrade to finish?",
"waiting_for_device": "Waiting for device to reconnect"
},
"user": {
"avatar": "Your Avatar",
"avatar_file": "Your Avatar (max. of 2 MB)",
"create": "Create User",
"create_failure": "Error while creating user. Please make sure this email address is not already linked to an account.",
"create_success": "User Created Successfully",
"creating": "Creating User...",
"delete_avatar": "Delete Avatar",
"delete_failure": "Error while trying to delete user",
"delete_success": "User successfully deleted!",
"delete_title": "Delete User",
"delete_warning": "Warning: Once you delete a user you cannot revert",
"deleting": "Deleting... ",
"description": "Description",
"edit": "Edit User",
"email_address": "Email Address",
"force_password_change": "Force Password Change on Login",
"id": "User Id.",
"last_login": "Last Login",
"login_id": "Login Id.",
"my_profile": "My Profile",
"name": "Name",
"nickname": "Nickname",
"nickname_explanation": "Nickname (optional)",
"not_validated": "Not Validated",
"note": "Note",
"password": "Password",
"provide_email": "Please provide a valid email address",
"provide_password": "Please provide a valid password",
"save_avatar": "Save Avatar",
"show_hide_password": "Show/Hide Password",
"update_failure": "Make sure all of your data is valid. If you are modifying the password, make sure it is not an old one.",
"update_failure_title": "Update Failed",
"update_success": "User Updated Successfully",
"update_success_title": "Success",
"user_role": "Role",
"users": "Users",
"validated": "Validated"
},
"wifi_analysis": {
"association": "Association",
"associations": "Associations",
"mode": "Mode",
"network_diagram": "Network Diagram",
"radios": "Radios",
"title": "Wi-Fi Analysis"
} }
} }

View File

@@ -1,5 +1,6 @@
{ {
"actions": { "actions": {
"actions": "Comportamiento",
"blink": "Parpadeo", "blink": "Parpadeo",
"configure": "Configurar", "configure": "Configurar",
"connect": "Conectar", "connect": "Conectar",
@@ -7,28 +8,39 @@
"factory_reset": "Restablecimiento De Fábrica", "factory_reset": "Restablecimiento De Fábrica",
"firmware_upgrade": "Actualización de firmware", "firmware_upgrade": "Actualización de firmware",
"reboot": "Reiniciar", "reboot": "Reiniciar",
"title": "Acciones del dispositivo", "title": "Comandos",
"trace": "Rastro", "trace": "Rastro",
"wifi_scan": "Escaneo Wifi" "wifi_scan": "Escaneo Wi-Fi "
}, },
"blink": { "blink": {
"blink": "Parpadeo", "blink": "Parpadeo",
"device_leds": "LED de dispositivo", "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:", "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?" "when_blink_leds": "¿Cuándo desea que los LED del dispositivo parpadeen?"
}, },
"commands": { "commands": {
"error": "¡Error al enviar el comando!", "error": "¡Error al enviar el comando!",
"success": "Comando enviado con éxito", "event_queue": "Cola de eventos",
"title": "Comandos del dispositivo" "success": "Comando enviado con éxito, puede consultar el registro de Comandos para ver el resultado",
"title": "Historial de Comandos",
"unable_queue": "No se pudo completar la solicitud de cola de eventos"
}, },
"common": { "common": {
"access_policy": "Política de acceso",
"add": "Añadir",
"adding_ellipsis": "Añadiendo ...",
"are_you_sure": "¿Estás seguro?", "are_you_sure": "¿Estás seguro?",
"back_to_login": "Atrás para iniciar sesión",
"cancel": "Cancelar", "cancel": "Cancelar",
"certificate": "Certificado", "certificate": "Certificado",
"certificates": "Certificados",
"clear": "Claro", "clear": "Claro",
"close": "Cerrar", "close": "Cerrar",
"command": "Mando", "command": "Mando",
"commands": "comandos",
"commands_executed": "Comandos ejecutados",
"compatible": "Compatible", "compatible": "Compatible",
"completed": "terminado", "completed": "terminado",
"config_id": "Config. Identificación", "config_id": "Config. Identificación",
@@ -36,83 +48,199 @@
"connected": "Conectado", "connected": "Conectado",
"copied": "Copiado!", "copied": "Copiado!",
"copy_to_clipboard": "Copiar al portapapeles", "copy_to_clipboard": "Copiar al portapapeles",
"create": "Crear",
"created": "creado",
"created_by": "Creado por",
"current": "Corriente",
"custom_date": "Fecha personalizada",
"dashboard": "Tablero",
"date": "Fecha", "date": "Fecha",
"day": "día",
"days": "días",
"delete": "Borrar", "delete": "Borrar",
"delete_device": "Eliminar dispositivo",
"details": "Detalles", "details": "Detalles",
"device": "Dispositivo n.º{{serialNumber}}",
"device_dashboard": "Panel de control del dispositivo",
"device_delete": "Eliminar dispositivo n.º{{serialNumber}}",
"device_deleted": "Dispositivo eliminado correctamente",
"device_health": "Salud del dispositivo",
"device_list": "Listado de dispositivos", "device_list": "Listado de dispositivos",
"device_page": "Página del dispositivo", "device_page": "Ver",
"device_status": "Estado del dispositivo",
"devices": "Dispositivos", "devices": "Dispositivos",
"devices_using_latest": "Dispositivos que utilizan el firmware más reciente",
"devices_using_unknown": "Dispositivos que utilizan firmware desconocido",
"dismiss": "Despedir", "dismiss": "Despedir",
"do_now": "¡Hagan ahora!", "do_now": "¡Hagan ahora!",
"download": "Descargar", "download": "Descargar",
"duration": "Duración", "duration": "Duración",
"edit": "Editar",
"edit_user": "Editar",
"email_address": "Dirección de correo electrónico",
"endpoint": "punto final",
"endpoints": "Puntos finales",
"error": "Error", "error": "Error",
"execute_now": "¿Le gustaría ejecutar este comando ahora?",
"executed": "ejecutado", "executed": "ejecutado",
"exit": "salida",
"firmware": "Firmware", "firmware": "Firmware",
"firmware_dashboard": "Panel de firmware",
"firmware_installed": "Firmware instalado",
"forgot_password": "¿Olvidaste tu contraseña?",
"forgot_password_title": "Se te olvidó tu contraseña",
"from": "Desde", "from": "Desde",
"general_error": "Error de API, consulte a su administrador",
"hide": "Esconder",
"hour": "hora",
"hours": "horas",
"id": "Carné de identidad", "id": "Carné de identidad",
"ip_address": "Dirección IP", "ip_address": "Dirección IP",
"last_dashboard_refresh": "Última actualización del panel",
"later_tonight": "Más tarde esta noche", "later_tonight": "Más tarde esta noche",
"latest": "último",
"list": "Lista",
"loading_ellipsis": "Cargando...", "loading_ellipsis": "Cargando...",
"loading_more_ellipsis": "Cargando más ...", "loading_more_ellipsis": "Cargando más ...",
"logout": "Cerrar sesión", "logout": "Cerrar sesión",
"mac": "Dirección MAC", "mac": "Dirección MAC",
"manufacturer": "Fabricante", "manufacturer": "Fabricante",
"memory_used": "Memoria usada",
"minute": "minuto",
"minutes": "minutos",
"na": "N / A", "na": "N / A",
"need_date": "Necesitas una cita ...", "need_date": "Necesitas una cita ...",
"no": "No",
"no_devices_found": "No se encontraron dispositivos",
"no_items": "No hay articulos",
"not_connected": "No conectado", "not_connected": "No conectado",
"of_connected": "% de dispositivos",
"off": "Apagado", "off": "Apagado",
"on": "en", "on": "en",
"optional": "Opcional",
"overall_health": "Salud en general",
"password_policy": "Política de contraseñas",
"recorded": "Grabado", "recorded": "Grabado",
"refresh": "Refrescar", "refresh": "Refrescar",
"refresh_device": "Actualizar dispositivo", "refresh_device": "Actualizar dispositivo",
"required": "Necesario",
"result": "Resultado", "result": "Resultado",
"save": "Salvar",
"saved": "¡Salvado!",
"saving": "Ahorro...",
"schedule": "Programar", "schedule": "Programar",
"search": "Dispositivos de búsqueda",
"second": "segundo",
"seconds": "segundos",
"seconds_elapsed": "Segundos transcurridos",
"serial_number": "Número de serie", "serial_number": "Número de serie",
"show_all": "Mostrar todo",
"start": "comienzo", "start": "comienzo",
"submit": "Enviar", "submit": "Enviar",
"submitted": "Presentado", "submitted": "Presentado",
"success": "Éxito", "success": "Éxito",
"system": "Sistema",
"table": "Mesa",
"timestamp": "hora",
"to": "a", "to": "a",
"type": "Tipo",
"unable_to_connect": "No se puede conectar al dispositivo",
"unable_to_delete": "No se puede eliminar",
"unknown": "Desconocido", "unknown": "Desconocido",
"up_to_date": "Dispositivos actualizados",
"uptimes": "Tiempos de actividad",
"uuid": "UUID", "uuid": "UUID",
"vendors": "Vendedores",
"view_more": "Ver más", "view_more": "Ver más",
"yes": "Sí" "yes": "Sí"
}, },
"configuration": { "configuration": {
"created": "creado", "created": "creado",
"details": "Detalles del dispositivo", "details": "Detalles",
"device_password": "Contraseña", "device_password": "Contraseña",
"last_configuration_change": "Último cambio de configuración", "last_configuration_change": "Último cambio de configuración",
"last_configuration_download": "Descarga de la última configuración", "last_configuration_download": "Descarga de la última configuración",
"location": "Ubicación", "location": "Ubicación",
"note": "Nota",
"notes": "Notas", "notes": "Notas",
"owner": "Propietario", "owner": "Propietario",
"title": "Configuración del dispositivo", "title": "Configuración",
"type": "Tipo de dispositivo", "type": "Tipo de dispositivo",
"uuid": "ID de configuración",
"view_json": "Ver JSON sin procesar" "view_json": "Ver JSON sin procesar"
}, },
"configure": { "configure": {
"choose_file": "Debe elegir un archivo .json válido:", "choose_file": "Debe elegir un archivo .json válido:",
"enter_new": "Ingrese la nueva configuración del dispositivo JSON:", "enter_new": "Ingrese la nueva configuración del dispositivo JSON:",
"placeholder": "Configurar JSON", "placeholder": "Configurar JSON",
"title": "Configurar dispositivo", "title": "Configurar",
"valid_json": "Debes ingresar un JSON válido" "valid_json": "Debes ingresar un JSON válido"
}, },
"delete_command": { "delete_command": {
"explanation": "¿Está seguro de que desea eliminar este comando? Esta acción no es reversible.", "explanation": "¿Está seguro de que desea eliminar este comando? Esta acción no es reversible.",
"title": "Eliminar comando" "title": "Eliminar comando"
}, },
"delete_logs": {
"date": "Seleccione la fecha del registro más antiguo que le gustaría conservar",
"device_logs_title": "Eliminar registros de dispositivos",
"explanation": "Esto eliminará todos los {{object}} antes de la fecha que elija. Tenga cuidado, esta acción no es reversible.",
"healthchecks_title": "Eliminar comprobaciones de estado"
},
"device_logs": { "device_logs": {
"log": "Iniciar sesión", "log": "Iniciar sesión",
"severity": "Gravedad", "severity": "Gravedad",
"title": "Registros de dispositivos" "title": "Registros"
},
"entity": {
"add_child": "Agregar entidad secundaria a {{entityName}}",
"add_failure": "Error, el servidor devolvió: {{error}}",
"add_root": "Agregar entidad raíz",
"add_success": "¡Entidad creada con éxito!",
"cannot_delete": "No puede eliminar entidades que tienen hijos. Elimina los hijos de esta entidad para poder eliminarla.",
"delete_success": "Entidad eliminada correctamente",
"delete_warning": "Advertencia: esta operación no se puede revertir",
"edit_failure": "Actualización fallida: {{error}}",
"entities": "entidades",
"entity": "Entidad",
"only_unassigned": "Solo sin asignar",
"valid_serial": "Debe ser un número de serie válido (12 caracteres HEX)"
}, },
"factory_reset": { "factory_reset": {
"redirector": "Mantener el redirector:", "redirector": "Mantener el redirector:",
"title": "Dispositivo de restablecimiento de fábrica", "reset": "Reiniciar",
"resetting": "Restableciendo…",
"title": "Restablecimiento De Fábrica",
"warning": "Advertencia: una vez que envíe, esto no se podrá revertir" "warning": "Advertencia: una vez que envíe, esto no se podrá revertir"
}, },
"firmware": {
"average_age": "Edad promedio del firmware",
"choose_custom": "Escoger",
"details_title": "Detalles de la imagen n. °{{image}} ",
"device_type": "Tipo de dispositivo",
"device_types": "Tipos de dispositivos",
"downloads": "Descargas",
"error_fetching_latest": "Error al obtener el firmware más reciente",
"from_release": "Desde",
"history_title": "Historia",
"image": "Imagen",
"image_date": "Fecha de la imagen",
"installed_firmware": "Firmware instalado",
"latest_version_installed": "Última versión instalada",
"newer_firmware_available": "Nuevas revisiones disponibles",
"reinstall_latest": "Reinstalar",
"revision": "Revisión",
"show_dev": "Mostrar lanzamientos para desarrolladores",
"size": "Tamaño",
"status": "Estado del firmware",
"title": "Firmware",
"to_release": "A",
"unknown_firmware_status": "Estado de firmware desconocido",
"upgrade": "Mejorar",
"upgrade_command_submitted": "El comando de actualización se envió correctamente",
"upgrade_to_latest": "último",
"upgrade_to_version": "Actualizar a esta revisión",
"upgrading": "Actualizando ..."
},
"footer": { "footer": {
"coreui_for_react": "CoreUI para React", "coreui_for_react": "CoreUI para React",
"powered_by": "energizado por", "powered_by": "energizado por",
@@ -120,43 +248,170 @@
}, },
"health": { "health": {
"sanity": "Cordura", "sanity": "Cordura",
"title": "Salud del dispositivo" "title": "Salud"
},
"inventory": {
"add_child_venue": "Agregar lugar infantil a {{entityName}}",
"add_tag": "Añadir etiqueta",
"add_tag_to": "Agregar etiqueta de inventario a {{name}}",
"assign_error": "Error al intentar asignar la etiqueta",
"assign_to_entity": "Asignar a entidad",
"error_retrieving": "Se produjo un error al recuperar las etiquetas de inventario",
"error_unassign": "Error durante la operación de anulación de asignación",
"subscriber": "Abonado",
"successful_assign": "Etiqueta asignada correctamente",
"successful_tag_update": "Etiqueta actualizada correctamente",
"successful_unassign": "La operación de anulación de asignación se realizó correctamente",
"tag_created": "Etiqueta de inventario creada correctamente",
"tag_creation_error": "Error al intentar crear una etiqueta de inventario",
"tag_update_error": "Error al actualizar la etiqueta",
"tags_assigned_to": "Etiquetas de inventario asignadas a {{name}}",
"unassign": "Anular asignación",
"unassign_tag": "Anular asignación de etiqueta de entidad",
"unassigned_tags": "Etiquetas sin asignar",
"venue": "Lugar de encuentro"
}, },
"login": { "login": {
"change_password": "Cambia la contraseña",
"change_password_error": "Error al cambiar la contraseña. Asegúrese de que la nueva contraseña sea válida visitando la página 'Política de contraseñas'",
"change_password_instructions": "Ingrese y confirme su nueva contraseña",
"changing_password": "Cambio de contraseña ...",
"confirm_new_password": "confirmar nueva contraseña",
"different_passwords": "Debes ingresar la misma contraseña dos veces",
"forgot_password_error": "Error al intentar enviar el correo electrónico de Olvidé mi contraseña. Asegúrese de que este ID de usuario esté asociado a una cuenta.",
"forgot_password_explanation": "Ingrese su nombre de usuario para recibir un correo electrónico con las instrucciones para restablecer su contraseña",
"forgot_password_success": "Pronto debería recibir un correo electrónico con las instrucciones para restablecer su contraseña. Asegúrese de verificar su correo no deseado si no puede encontrar el correo electrónico",
"logging_in": "Iniciar sesión...",
"login": "Iniciar sesión", "login": "Iniciar sesión",
"login_error": "Error de inicio de sesión, confirme que su nombre de usuario, contraseña y URL de puerta de enlace son válidos", "login_error": "Error de inicio de sesión, asegúrese de que la información que proporciona sea válida",
"new_password": "Nueva contraseña",
"password": "Contraseña", "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_password": "Por favor, introduzca su contraseña",
"please_enter_username": "Por favor, ingrese su nombre de usuario", "please_enter_username": "Por favor, ingrese su nombre de usuario",
"previously_used": "La contraseña se usó anteriormente",
"send_forgot": "Enviar correo electrónico",
"sending_ellipsis": "Enviando...",
"sign_in_to_account": "Iniciar sesión en su cuenta", "sign_in_to_account": "Iniciar sesión en su cuenta",
"url": "URL de uCentralSec",
"username": "Nombre de usuario" "username": "Nombre de usuario"
}, },
"reboot": { "reboot": {
"directions": "¿Cuándo le gustaría reiniciar este dispositivo?", "directions": "¿Cuándo le gustaría reiniciar este dispositivo?",
"title": "Reiniciar dispositivo" "now": "¿Le gustaría reiniciar este dispositivo ahora?",
"title": "Reiniciar"
}, },
"scan": { "scan": {
"active": "Habilitar escaneo activo", "active": "Habilitar escaneo activo",
"channel": "Canal", "channel": "Canal",
"directions": "Ejecute un escaneo wifi de este dispositivo, que debería tomar aproximadamente 25 segundos.", "directions": "Ejecute un escaneo wifi de este dispositivo, que debería tomar aproximadamente 25 segundos.",
"results": "Resultados de escaneo Wifi" "re_scan": "Vuelva a escanear",
"result_directions": "Haga clic en el botón '$ t (scan.re_scan)' si desea realizar un escaneo con la misma configuración que el anterior.",
"results": "Resultados de escaneo Wi-Fi",
"scan": "Escanear",
"scanning": "Exploración... ",
"waiting_directions": "Espere el resultado del escaneo. Esto puede tardar hasta 25 segundos. Puede salir y ver los resultados de la tabla de comandos más adelante."
},
"settings": {
"title": "Ajustes"
}, },
"statistics": { "statistics": {
"data": "Datos (KB)", "data": "Datos (KB)",
"latest_statistics": "Últimas estadísticas",
"lifetime_stats": "Estadísticas de por vida",
"no_interfaces": "No hay estadísticas de vida útil de la interfaz disponibles",
"show_latest": "Últimas estadísticas",
"title": "estadística" "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"
},
"system": {
"error_fetching": "Error al obtener información del sistema"
},
"trace": { "trace": {
"choose_network": "Elija la red", "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", "directions": "Lanzar un rastreo remoto de este dispositivo por una duración específica o por una cantidad de paquetes",
"download_trace": "Descargar archivo de seguimiento",
"packets": "Paquetes", "packets": "Paquetes",
"title": "Dispositivo de seguimiento" "title": "Rastro",
"trace": "Rastro",
"trace_not_successful": "Seguimiento fallido: la puerta de enlace informó el siguiente error: {{error}}",
"wait_for_file": "¿Le gustaría esperar hasta que el archivo de seguimiento esté listo?",
"waiting_directions": "Espere el archivo de datos de seguimiento. Esto puede tomar algo de tiempo. Puede salir de la espera y recuperar el archivo de seguimiento de la tabla de comandos más tarde.",
"waiting_seconds": "Tiempo transcurrido: {{seconds}} segundos"
}, },
"upgrade": { "upgrade": {
"command_submitted": "Comando enviado",
"device_disconnected": "Dispositivo desconectado",
"device_reconnected": "Dispositivo reconectado",
"device_upgrading_firmware": "Firmware de actualización del dispositivo",
"directions": "Elija una hora y una versión de firmware para este dispositivo", "directions": "Elija una hora y una versión de firmware para este dispositivo",
"firmware_uri": "URI de firmware:", "firmware_uri": "URI de firmware:",
"need_uri": "Necesitas un URI ...", "need_uri": "Necesitas un URI ...",
"new_version": "La nueva versión es",
"offline_device": "Esta opción está bloqueada porque este dispositivo no está conectado",
"time": "Hora de actualización:", "time": "Hora de actualización:",
"title": "Actualización de firmware" "title": "Actualización de firmware",
"upgrade": "Mejorar",
"wait_for_upgrade": "¿Le gustaría esperar a que finalice la actualización?",
"waiting_for_device": "Esperando que el dispositivo se vuelva a conectar"
},
"user": {
"avatar": "Tu avatar",
"avatar_file": "Tu avatar (máx. De 2 MB)",
"create": "Crear usuario",
"create_failure": "Error al crear usuario. Asegúrese de que esta dirección de correo electrónico no esté vinculada a una cuenta.",
"create_success": "Usuario creado con éxito",
"creating": "Creando usuario ...",
"delete_avatar": "Eliminar avatar",
"delete_failure": "Error al intentar eliminar al usuario",
"delete_success": "¡Usuario eliminado correctamente!",
"delete_title": "Borrar usuario",
"delete_warning": "Advertencia: una vez que elimina un usuario, no puede revertir",
"deleting": "Eliminando ...",
"description": "Descripción",
"edit": "editar usuario",
"email_address": "Dirección de correo electrónico",
"force_password_change": "Forzar cambio de contraseña al iniciar sesión",
"id": "Id. De usuario",
"last_login": "Último acceso",
"login_id": "Ingresar identificación.",
"my_profile": "Mi perfil",
"name": "Nombre",
"nickname": "Apodo",
"nickname_explanation": "Apodo (opcional)",
"not_validated": "No validado",
"note": "Nota",
"password": "Contraseña",
"provide_email": "Por favor ingrese su dirección de correo electrónico válida",
"provide_password": "Proporcione una contraseña válida",
"save_avatar": "Guardar avatar",
"show_hide_password": "Mostrar / Ocultar contraseña",
"update_failure": "Asegúrese de que todos sus datos sean válidos. Si está modificando la contraseña, asegúrese de que no sea antigua.",
"update_failure_title": "Actualización fallida",
"update_success": "Usuario actualizado con éxito",
"update_success_title": "Éxito",
"user_role": "papel",
"users": "Usuarios",
"validated": "Validado"
},
"wifi_analysis": {
"association": "Asociación",
"associations": "Asociaciones",
"mode": "Modo",
"network_diagram": "Diagrama de Red",
"radios": "Radios",
"title": "Análisis de Wi-Fi"
} }
} }

View File

@@ -1,5 +1,6 @@
{ {
"actions": { "actions": {
"actions": "actes",
"blink": "Cligner", "blink": "Cligner",
"configure": "Configurer", "configure": "Configurer",
"connect": "Relier", "connect": "Relier",
@@ -7,28 +8,39 @@
"factory_reset": "Retour aux paramètres d'usine", "factory_reset": "Retour aux paramètres d'usine",
"firmware_upgrade": "Mise à jour du firmware", "firmware_upgrade": "Mise à jour du firmware",
"reboot": "Redémarrer", "reboot": "Redémarrer",
"title": "Actions de l'appareil", "title": "Les commandes",
"trace": "Trace", "trace": "Trace",
"wifi_scan": "Balayage Wi-Fi" "wifi_scan": "Balayage Wi-Fi"
}, },
"blink": { "blink": {
"blink": "Cligner", "blink": "Cligner",
"device_leds": "LED de l'appareil", "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 :", "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 ?" "when_blink_leds": "Quand souhaitez-vous faire clignoter les LED de l'appareil ?"
}, },
"commands": { "commands": {
"error": "Erreur lors de la soumission de la commande !", "error": "Erreur lors de la soumission de la commande !",
"success": "Commande soumise avec succès", "event_queue": "File d'attente d'événements",
"title": "Commandes de l'appareil" "success": "Commande soumise avec succès, vous pouvez consulter le journal des commandes pour le résultat",
"title": "Historique des commandes",
"unable_queue": "Impossible de terminer la demande de file d'attente d'événements"
}, },
"common": { "common": {
"access_policy": "Politique d'accès",
"add": "Ajouter",
"adding_ellipsis": "Ajouter...",
"are_you_sure": "Êtes-vous sûr?", "are_you_sure": "Êtes-vous sûr?",
"back_to_login": "Retour connexion",
"cancel": "annuler", "cancel": "annuler",
"certificate": "Certificat", "certificate": "Certificat",
"certificates": "Certificats",
"clear": "Clair", "clear": "Clair",
"close": "Fermer", "close": "Fermer",
"command": "Commander", "command": "Commander",
"commands": "Les commandes",
"commands_executed": "commandes exécutées",
"compatible": "Compatible", "compatible": "Compatible",
"completed": "Terminé", "completed": "Terminé",
"config_id": "Config. Identifiant", "config_id": "Config. Identifiant",
@@ -36,83 +48,199 @@
"connected": "Connecté", "connected": "Connecté",
"copied": "Copié!", "copied": "Copié!",
"copy_to_clipboard": "Copier dans le presse-papier", "copy_to_clipboard": "Copier dans le presse-papier",
"create": "Créer",
"created": "Créé",
"created_by": "Créé par",
"current": "Actuel",
"custom_date": "Date personnalisée",
"dashboard": "Tableau de bord",
"date": "Rendez-vous amoureux", "date": "Rendez-vous amoureux",
"day": "journée",
"days": "journées",
"delete": "Effacer", "delete": "Effacer",
"delete_device": "Supprimer le périphérique",
"details": "Détails", "details": "Détails",
"device": "N° d'appareil{{serialNumber}}",
"device_dashboard": "Tableau de bord de l'appareil",
"device_delete": "Supprimer l'appareil n°{{serialNumber}}",
"device_deleted": "Appareil supprimé avec succès",
"device_health": "Santé de l'appareil",
"device_list": "Liste des appareils", "device_list": "Liste des appareils",
"device_page": "Page de l'appareil", "device_page": "Vue",
"device_status": "Statut du périphérique",
"devices": "Dispositifs", "devices": "Dispositifs",
"devices_using_latest": "Appareils utilisant le dernier micrologiciel",
"devices_using_unknown": "Périphériques utilisant un micrologiciel inconnu",
"dismiss": "Rejeter", "dismiss": "Rejeter",
"do_now": "Faire maintenant!", "do_now": "Faire maintenant!",
"download": "Télécharger", "download": "Télécharger",
"duration": "Durée", "duration": "Durée",
"error": "erreur", "edit": "modifier",
"edit_user": "Modifier",
"email_address": "Adresse électronique",
"endpoint": "Point final",
"endpoints": "Points de terminaison",
"error": "Erreur",
"execute_now": "Souhaitez-vous exécuter cette commande maintenant ?",
"executed": "réalisé", "executed": "réalisé",
"exit": "Sortie",
"firmware": "Micrologiciel", "firmware": "Micrologiciel",
"firmware_dashboard": "Tableau de bord du micrologiciel",
"firmware_installed": "Micrologiciel installé",
"forgot_password": "Mot de passe oublié?",
"forgot_password_title": "Mot de passe oublié",
"from": "De", "from": "De",
"general_error": "Erreur API, veuillez consulter votre administrateur",
"hide": "Cacher",
"hour": "heure",
"hours": "heures",
"id": "Id", "id": "Id",
"ip_address": "Adresse IP", "ip_address": "Adresse IP",
"last_dashboard_refresh": "Dernière actualisation du tableau de bord",
"later_tonight": "Plus tard ce soir", "later_tonight": "Plus tard ce soir",
"latest": "Dernier",
"list": "liste",
"loading_ellipsis": "Chargement...", "loading_ellipsis": "Chargement...",
"loading_more_ellipsis": "Chargement plus ...", "loading_more_ellipsis": "Chargement plus ...",
"logout": "Connectez - Out", "logout": "Connectez - Out",
"mac": "ADRESSE MAC", "mac": "ADRESSE MAC",
"manufacturer": "fabricant", "manufacturer": "fabricant",
"memory_used": "Mémoire utilisée",
"minute": "minute",
"minutes": "minutes",
"na": "N / A", "na": "N / A",
"need_date": "Vous avez besoin d'un rendez-vous...", "need_date": "Vous avez besoin d'un rendez-vous...",
"no": "Non",
"no_devices_found": "Aucun périphérique trouvé",
"no_items": "Pas d'objet",
"not_connected": "Pas connecté", "not_connected": "Pas connecté",
"of_connected": "% d'appareils",
"off": "De", "off": "De",
"on": "sur", "on": "sur",
"optional": "Optionnel",
"overall_health": "Santé globale",
"password_policy": "Politique de mot de passe",
"recorded": "Enregistré", "recorded": "Enregistré",
"refresh": "Rafraîchir", "refresh": "Rafraîchir",
"refresh_device": "Actualiser l'appareil", "refresh_device": "Actualiser l'appareil",
"required": "Champs obligatoires",
"result": "Résultat", "result": "Résultat",
"save": "Sauvegarder",
"saved": "Enregistré!",
"saving": "Économie...",
"schedule": "Programme", "schedule": "Programme",
"search": "Rechercher des appareils",
"second": "seconde",
"seconds": "secondes",
"seconds_elapsed": "Secondes écoulées",
"serial_number": "Numéro de série", "serial_number": "Numéro de série",
"show_all": "Montre tout",
"start": "Début", "start": "Début",
"submit": "Soumettre", "submit": "Soumettre",
"submitted": "Soumis", "submitted": "Soumis",
"success": "Succès", "success": "Succès",
"system": "Système",
"table": "Table",
"timestamp": "Temps",
"to": "à", "to": "à",
"type": "Type",
"unable_to_connect": "Impossible de se connecter à l'appareil",
"unable_to_delete": "Impossible de supprimer",
"unknown": "Inconnu", "unknown": "Inconnu",
"up_to_date": "Appareils à jour",
"uptimes": "Disponibilités",
"uuid": "UUID", "uuid": "UUID",
"vendors": "Vendeurs",
"view_more": "Afficher plus", "view_more": "Afficher plus",
"yes": "Oui" "yes": "Oui"
}, },
"configuration": { "configuration": {
"created": "Créé", "created": "Créé",
"details": "Détails de l'appareil", "details": "Détails",
"device_password": "Mot de passe", "device_password": "Mot de passe",
"last_configuration_change": "Dernière modification de configuration", "last_configuration_change": "Dernière modification de configuration",
"last_configuration_download": "Téléchargement de la dernière configuration", "last_configuration_download": "Téléchargement de la dernière configuration",
"location": "Emplacement", "location": "Emplacement",
"note": "Remarque",
"notes": "Remarques", "notes": "Remarques",
"owner": "Propriétaire", "owner": "Propriétaire",
"title": "Configuration de l'appareil", "title": "Configuration",
"type": "Type d'appareil", "type": "Type d'appareil",
"uuid": "Identifiant de configuration",
"view_json": "Afficher le JSON brut" "view_json": "Afficher le JSON brut"
}, },
"configure": { "configure": {
"choose_file": "Vous devez choisir un fichier .json valide :", "choose_file": "Vous devez choisir un fichier .json valide :",
"enter_new": "Entrez la nouvelle configuration de l'appareil JSON :", "enter_new": "Entrez la nouvelle configuration de l'appareil JSON :",
"placeholder": "Configurer JSON", "placeholder": "Configurer JSON",
"title": "Configurer l'appareil", "title": "Configurer",
"valid_json": "Vous devez entrer un JSON valide" "valid_json": "Vous devez entrer un JSON valide"
}, },
"delete_command": { "delete_command": {
"explanation": "Êtes-vous sûr de vouloir supprimer cette commande ? Cette action n'est pas réversible.", "explanation": "Êtes-vous sûr de vouloir supprimer cette commande ? Cette action n'est pas réversible.",
"title": "Supprimer la commande" "title": "Supprimer la commande"
}, },
"delete_logs": {
"date": "Sélectionnez la date du plus ancien journal que vous souhaitez conserver",
"device_logs_title": "Supprimer les journaux de l'appareil",
"explanation": "Cela supprimera tous les {{object}} avant la date que vous choisissez. Attention, cette action n'est pas réversible.",
"healthchecks_title": "Supprimer les vérifications d'état"
},
"device_logs": { "device_logs": {
"log": "Bûche", "log": "Bûche",
"severity": "Gravité", "severity": "Gravité",
"title": "Journaux de l'appareil" "title": "Journaux"
},
"entity": {
"add_child": "Ajouter une entité enfant à {{entityName}}",
"add_failure": "Erreur, le serveur a renvoyé : {{error}}",
"add_root": "Ajouter une entité racine",
"add_success": "Entité créée avec succès !",
"cannot_delete": "Vous ne pouvez pas supprimer des entités qui ont des enfants. Supprimez les enfants de cette entité pour pouvoir la supprimer.",
"delete_success": "Entité supprimée avec succès",
"delete_warning": "Attention : cette opération ne peut pas être annulée",
"edit_failure": "Échec de la mise à jour : {{error}}",
"entities": "Entités",
"entity": "Entité",
"only_unassigned": "Uniquement non attribué",
"valid_serial": "Doit être un numéro de série valide (12 caractères HEX)"
}, },
"factory_reset": { "factory_reset": {
"redirector": "Conserver le redirecteur :", "redirector": "Conserver le redirecteur :",
"title": "Dispositif de réinitialisation d'usine", "reset": "Réinitialiser",
"resetting": "Réinitialisation…",
"title": "Retour aux paramètres d'usine",
"warning": "Avertissement : Une fois que vous avez soumis, cela ne peut pas être annulé" "warning": "Avertissement : Une fois que vous avez soumis, cela ne peut pas être annulé"
}, },
"firmware": {
"average_age": "Âge moyen du micrologiciel",
"choose_custom": "Choisir",
"details_title": "Image #{{image}} Détails",
"device_type": "Type d'appareil",
"device_types": "Types d'appareils",
"downloads": "Téléchargements",
"error_fetching_latest": "Erreur lors de la récupération du dernier firmware",
"from_release": "De",
"history_title": "Historique",
"image": "Image",
"image_date": "Date de l'image",
"installed_firmware": "Micrologiciel installé",
"latest_version_installed": "Dernière version installée",
"newer_firmware_available": "Révisions plus récentes disponibles",
"reinstall_latest": "Réinstaller",
"revision": "Révision",
"show_dev": "Afficher les versions des développeurs",
"size": "Taille",
"status": "État du micrologiciel",
"title": "Micrologiciel",
"to_release": "à",
"unknown_firmware_status": "État du micrologiciel inconnu",
"upgrade": "Améliorer",
"upgrade_command_submitted": "Commande de mise à niveau soumise avec succès",
"upgrade_to_latest": "Dernier",
"upgrade_to_version": "Mettre à niveau vers cette révision",
"upgrading": "Mise à niveau..."
},
"footer": { "footer": {
"coreui_for_react": "CoreUI pour React", "coreui_for_react": "CoreUI pour React",
"powered_by": "Alimenté par", "powered_by": "Alimenté par",
@@ -120,43 +248,170 @@
}, },
"health": { "health": {
"sanity": "Santé mentale", "sanity": "Santé mentale",
"title": "Santé de l'appareil" "title": "Santé"
},
"inventory": {
"add_child_venue": "Ajouter un lieu enfant à {{entityName}}",
"add_tag": "Ajouter une étiquette",
"add_tag_to": "Ajouter une balise d'inventaire à {{name}}",
"assign_error": "Erreur lors de la tentative d'attribution de balise",
"assign_to_entity": "Affecter à l'entité",
"error_retrieving": "Une erreur s'est produite lors de la récupération des balises d'inventaire",
"error_unassign": "Erreur lors de l'opération de désaffectation",
"subscriber": "Abonné",
"successful_assign": "Tag attribué avec succès",
"successful_tag_update": "Balise mise à jour avec succès",
"successful_unassign": "L'opération de désaffectation a réussi",
"tag_created": "Tag d'inventaire créé avec succès",
"tag_creation_error": "Erreur lors de la tentative de création d'une balise d'inventaire",
"tag_update_error": "Erreur lors de la mise à jour de la balise",
"tags_assigned_to": "Balises d'inventaire attribuées à {{name}}",
"unassign": "Annuler l'attribution",
"unassign_tag": "Désaffecter la balise de l'entité",
"unassigned_tags": "Balises non attribuées",
"venue": "Lieu"
}, },
"login": { "login": {
"change_password": "Changer le mot de passe",
"change_password_error": "Erreur lors du changement de mot de passe. Assurez-vous que le nouveau mot de passe est valide en visitant la page « Politique de mot de passe »",
"change_password_instructions": "Saisissez et confirmez votre nouveau mot de passe",
"changing_password": "Modification du mot de passe...",
"confirm_new_password": "Confirmer le nouveau mot de passe",
"different_passwords": "Vous devez saisir deux fois le même mot de passe",
"forgot_password_error": "Erreur lors de la tentative d'envoi de l'e-mail Mot de passe oublié. Veuillez vous assurer que cet identifiant est associé à un compte.",
"forgot_password_explanation": "Entrez votre nom d'utilisateur pour recevoir un e-mail contenant les instructions pour réinitialiser votre mot de passe",
"forgot_password_success": "Vous devriez bientôt recevoir un e-mail contenant les instructions pour réinitialiser votre mot de passe. S'il vous plaît assurez-vous de vérifier vos spams si vous ne trouvez pas l'e-mail",
"logging_in": "Se connecter...",
"login": "S'identifier", "login": "S'identifier",
"login_error": "Erreur de connexion, confirmez que votre nom d'utilisateur, mot de passe et URL de passerelle sont valides", "login_error": "Erreur de connexion, assurez-vous que les informations que vous fournissez sont valides",
"new_password": "Nouveau mot de passe",
"password": "Mot de passe", "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_password": "s'il vous plait entrez votre mot de passe",
"please_enter_username": "s'il vous plaît entrez votre nom d'utilisateur", "please_enter_username": "s'il vous plaît entrez votre nom d'utilisateur",
"previously_used": "Le mot de passe a déjà été utilisé",
"send_forgot": "Envoyer un email",
"sending_ellipsis": "Envoi...",
"sign_in_to_account": "Connectez-vous à votre compte", "sign_in_to_account": "Connectez-vous à votre compte",
"url": "URL uCentralSec",
"username": "Nom d'utilisateur" "username": "Nom d'utilisateur"
}, },
"reboot": { "reboot": {
"directions": "Quand souhaitez-vous redémarrer cet appareil ?", "directions": "Quand souhaitez-vous redémarrer cet appareil ?",
"title": "Redémarrez l'appareil" "now": "Souhaitez-vous redémarrer cet appareil maintenant ?",
"title": "Redémarrer"
}, },
"scan": { "scan": {
"active": "Activer l'analyse active", "active": "Activer l'analyse active",
"channel": "Canal", "channel": "Canal",
"directions": "Lancez une analyse wifi de cet appareil, ce qui devrait prendre environ 25 secondes.", "directions": "Lancez une analyse wifi de cet appareil, ce qui devrait prendre environ 25 secondes.",
"results": "Résultats de l'analyse Wi-Fi" "re_scan": "Nouvelle analyse",
"result_directions": "Veuillez cliquer sur le bouton '$t(scan.re_scan)' si vous souhaitez effectuer un scan avec la même configuration que le précédent.",
"results": "Résultats de l'analyse Wi-Fi",
"scan": "Balayage",
"scanning": "Balayage... ",
"waiting_directions": "Veuillez attendre le résultat de l'analyse. Cela peut prendre jusqu'à 25 secondes. Vous pouvez quitter et consulter les résultats du tableau des commandes plus tard."
},
"settings": {
"title": "Réglages"
}, },
"statistics": { "statistics": {
"data": "Données (Ko)", "data": "Données (Ko)",
"latest_statistics": "Dernières statistiques",
"lifetime_stats": "Statistiques à vie",
"no_interfaces": "Aucune statistique de durée de vie de l'interface disponible",
"show_latest": "Dernières statistiques",
"title": "statistiques" "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"
},
"system": {
"error_fetching": "Erreur lors de la récupération des informations système"
},
"trace": { "trace": {
"choose_network": "Choisir le réseau", "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", "directions": "Lancer une trace à distance de cet appareil pour une durée spécifique ou un nombre de paquets",
"download_trace": "Télécharger le fichier de trace",
"packets": "Paquets", "packets": "Paquets",
"title": "Dispositif de traçage" "title": "Trace",
"trace": "Trace",
"trace_not_successful": "Trace non réussie : la passerelle a signalé l'erreur suivante : {{error}}",
"wait_for_file": "Souhaitez-vous attendre que le fichier de trace soit prêt ?",
"waiting_directions": "Veuillez attendre le fichier de données de trace. Cela peut prendre un certain temps. Vous pouvez quitter l'attente et récupérer le fichier de trace de la table des commandes plus tard.",
"waiting_seconds": "Temps écoulé : {{seconds}} secondes"
}, },
"upgrade": { "upgrade": {
"command_submitted": "Commande soumise",
"device_disconnected": "Appareil déconnecté",
"device_reconnected": "Appareil reconnecté",
"device_upgrading_firmware": "Mise à niveau du micrologiciel de l'appareil",
"directions": "Choisissez une heure et une version de firmware pour cet appareil", "directions": "Choisissez une heure et une version de firmware pour cet appareil",
"firmware_uri": "URI du micrologiciel :", "firmware_uri": "URI du micrologiciel :",
"need_uri": "Vous avez besoin d'un URI...", "need_uri": "Vous avez besoin d'un URI...",
"new_version": "La nouvelle version est",
"offline_device": "Cette option est bloquée car cet appareil n'est pas connecté",
"time": "Heure de la mise à niveau :", "time": "Heure de la mise à niveau :",
"title": "Mise à jour du firmware" "title": "Mise à jour du firmware",
"upgrade": "Améliorer",
"wait_for_upgrade": "Souhaitez-vous attendre la fin de la mise à niveau ?",
"waiting_for_device": "En attente de la reconnexion de l'appareil"
},
"user": {
"avatar": "Votre avatar",
"avatar_file": "Votre Avatar (max. de 2 Mo)",
"create": "Créer un utilisateur",
"create_failure": "Erreur lors de la création de l'utilisateur. Veuillez vous assurer que cette adresse e-mail n'est pas déjà liée à un compte.",
"create_success": "L'utilisateur a été créé avec succès",
"creating": "Création de l'utilisateur...",
"delete_avatar": "Supprimer l'avatar",
"delete_failure": "Erreur lors de la tentative de suppression de l'utilisateur",
"delete_success": "Utilisateur supprimé avec succès !",
"delete_title": "Supprimer l'utilisateur",
"delete_warning": "Avertissement : Une fois que vous avez supprimé un utilisateur, vous ne pouvez plus revenir en arrière",
"deleting": "Suppression ...",
"description": "La description",
"edit": "Modifier l'utilisateur",
"email_address": "Adresse électronique",
"force_password_change": "Forcer le changement de mot de passe lors de la connexion",
"id": "Identifiant d'utilisateur.",
"last_login": "Dernière connexion",
"login_id": "Identifiant de connexion.",
"my_profile": "Mon profil",
"name": "Prénom",
"nickname": "Surnom",
"nickname_explanation": "Surnom (optionnel)",
"not_validated": "Pas valide",
"note": "Remarque",
"password": "Mot de passe",
"provide_email": "Veuillez fournir une adresse email valide",
"provide_password": "Veuillez fournir un mot de passe valide",
"save_avatar": "Enregistrer l'avatar",
"show_hide_password": "Afficher/Masquer le mot de passe",
"update_failure": "Assurez-vous que toutes vos données sont valides. Si vous modifiez le mot de passe, assurez-vous qu'il ne s'agit pas d'un ancien.",
"update_failure_title": "mise à jour a échoué",
"update_success": "L'utilisateur a bien été mis à jour",
"update_success_title": "Succès",
"user_role": "Rôle",
"users": "Utilisateurs",
"validated": "Validé"
},
"wifi_analysis": {
"association": "Association",
"associations": "Les associations",
"mode": "Mode",
"network_diagram": "Diagramme de réseau",
"radios": "Radios",
"title": "Analyse Wi-Fi"
} }
} }

View File

@@ -1,5 +1,6 @@
{ {
"actions": { "actions": {
"actions": "Ações",
"blink": "Piscar", "blink": "Piscar",
"configure": "Configurar", "configure": "Configurar",
"connect": "Conectar", "connect": "Conectar",
@@ -7,28 +8,39 @@
"factory_reset": "Restauração de fábrica", "factory_reset": "Restauração de fábrica",
"firmware_upgrade": "Atualização de firmware", "firmware_upgrade": "Atualização de firmware",
"reboot": "Reiniciar", "reboot": "Reiniciar",
"title": "Ações do dispositivo", "title": "Comandos",
"trace": "Vestígio", "trace": "Vestígio",
"wifi_scan": "Wifi Scan" "wifi_scan": "Wi-Fi Scan"
}, },
"blink": { "blink": {
"blink": "Piscar", "blink": "Piscar",
"device_leds": "LEDs do dispositivo", "device_leds": "LEDs do dispositivo",
"execute_now": "Você gostaria de definir este padrão agora?",
"pattern": "Escolha o padrão que deseja usar:", "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?" "when_blink_leds": "Quando você gostaria de fazer os LEDs do dispositivo piscarem?"
}, },
"commands": { "commands": {
"error": "Erro ao enviar comando!", "error": "Erro ao enviar comando!",
"success": "Comando enviado com sucesso", "event_queue": "Fila de Eventos",
"title": "Comandos de dispositivo" "success": "Comando enviado com sucesso, você pode consultar o log de Comandos para ver o resultado",
"title": "Histórico de Comandos",
"unable_queue": "Incapaz de completar o pedido de fila de eventos"
}, },
"common": { "common": {
"access_policy": "Política de Acesso",
"add": "Adicionar",
"adding_ellipsis": "Adicionando ...",
"are_you_sure": "Você tem certeza?", "are_you_sure": "Você tem certeza?",
"back_to_login": "Volte ao login",
"cancel": "Cancelar", "cancel": "Cancelar",
"certificate": "Certificado", "certificate": "Certificado",
"certificates": "Certificados",
"clear": "Claro", "clear": "Claro",
"close": "Perto", "close": "Perto",
"command": "Comando", "command": "Comando",
"commands": "comandos",
"commands_executed": "Comandos Executados",
"compatible": "Compatível", "compatible": "Compatível",
"completed": "Concluído", "completed": "Concluído",
"config_id": "Config. Identidade", "config_id": "Config. Identidade",
@@ -36,83 +48,199 @@
"connected": "Conectado", "connected": "Conectado",
"copied": "Copiado!", "copied": "Copiado!",
"copy_to_clipboard": "Copiar para área de transferência", "copy_to_clipboard": "Copiar para área de transferência",
"create": "Crio",
"created": "Criado",
"created_by": "Criado Por",
"current": "Atual",
"custom_date": "Data personalizada",
"dashboard": "painel de controle",
"date": "Encontro", "date": "Encontro",
"day": "dia",
"days": "dias",
"delete": "Excluir", "delete": "Excluir",
"delete_device": "Apagar dispositivo",
"details": "Detalhes", "details": "Detalhes",
"device": "Dispositivo nº{{serialNumber}}",
"device_dashboard": "Painel do dispositivo",
"device_delete": "Excluir dispositivo nº{{serialNumber}}",
"device_deleted": "Dispositivo excluído com sucesso",
"device_health": "Saúde do Dispositivo",
"device_list": "Lista de Dispositivos", "device_list": "Lista de Dispositivos",
"device_page": "Página do dispositivo", "device_page": "Visão",
"device_status": "Status do dispositivo",
"devices": "Devices", "devices": "Devices",
"devices_using_latest": "Dispositivos que usam o firmware mais recente",
"devices_using_unknown": "Dispositivos que usam firmware desconhecido",
"dismiss": "Dispensar", "dismiss": "Dispensar",
"do_now": "Faça agora!", "do_now": "Faça agora!",
"download": "Baixar", "download": "Baixar",
"duration": "Duração", "duration": "Duração",
"edit": "Editar",
"edit_user": "Editar",
"email_address": "Endereço de e-mail",
"endpoint": "Ponto final",
"endpoints": "Pontos finais",
"error": "Erro", "error": "Erro",
"execute_now": "Você gostaria de executar este comando agora?",
"executed": "Executado", "executed": "Executado",
"exit": "Saída",
"firmware": "Firmware", "firmware": "Firmware",
"firmware_dashboard": "Painel de Firmware",
"firmware_installed": "Firmware Instalado",
"forgot_password": "Esqueceu sua senha?",
"forgot_password_title": "Esqueceu a senha",
"from": "De", "from": "De",
"general_error": "Erro de API, consulte o seu administrador",
"hide": "Ocultar",
"hour": "hora",
"hours": "horas",
"id": "identidade", "id": "identidade",
"ip_address": "Endereço de IP", "ip_address": "Endereço de IP",
"last_dashboard_refresh": "Última atualização do painel",
"later_tonight": "Logo à noite", "later_tonight": "Logo à noite",
"latest": "Mais recentes",
"list": "Lista",
"loading_ellipsis": "Carregando...", "loading_ellipsis": "Carregando...",
"loading_more_ellipsis": "Carregando mais ...", "loading_more_ellipsis": "Carregando mais ...",
"logout": "Sair", "logout": "Sair",
"mac": "Endereço MAC", "mac": "Endereço MAC",
"manufacturer": "Fabricante", "manufacturer": "Fabricante",
"memory_used": "Memória Usada",
"minute": "minuto",
"minutes": "minutos",
"na": "N / D", "na": "N / D",
"need_date": "Você precisa de um encontro ...", "need_date": "Você precisa de um encontro ...",
"no": "Não",
"no_devices_found": "Nenhum dispositivo encontrado",
"no_items": "Nenhum item",
"not_connected": "Não conectado", "not_connected": "Não conectado",
"of_connected": "% de dispositivos",
"off": "Fora", "off": "Fora",
"on": "em", "on": "em",
"optional": "Opcional",
"overall_health": "Saúde geral",
"password_policy": "Política de Senha",
"recorded": "Gravado", "recorded": "Gravado",
"refresh": "REFRESH", "refresh": "REFRESH",
"refresh_device": "Atualizar dispositivo", "refresh_device": "Atualizar dispositivo",
"required": "Requeridos",
"result": "Resultado", "result": "Resultado",
"save": "Salve",
"saved": "Salvou!",
"saving": "Salvando ...",
"schedule": "Cronograma", "schedule": "Cronograma",
"search": "Dispositivos de pesquisa",
"second": "segundo",
"seconds": "segundos",
"seconds_elapsed": "Segundos decorridos",
"serial_number": "Número de série", "serial_number": "Número de série",
"show_all": "mostre tudo",
"start": "Começar", "start": "Começar",
"submit": "Enviar", "submit": "Enviar",
"submitted": "Submetido", "submitted": "Submetido",
"success": "Sucesso", "success": "Sucesso",
"system": "Sistema",
"table": "Mesa",
"timestamp": "tempo",
"to": "Para", "to": "Para",
"type": "Tipo",
"unable_to_connect": "Incapaz de conectar ao dispositivo",
"unable_to_delete": "Incapaz de deletar",
"unknown": "Desconhecido", "unknown": "Desconhecido",
"up_to_date": "Dispositivos atualizados",
"uptimes": "Uptimes",
"uuid": "UUID", "uuid": "UUID",
"vendors": "Vendedores",
"view_more": "Veja mais", "view_more": "Veja mais",
"yes": "sim" "yes": "sim"
}, },
"configuration": { "configuration": {
"created": "Criado", "created": "Criado",
"details": "Detalhes do dispositivo", "details": "Detalhes",
"device_password": "Senha", "device_password": "Senha",
"last_configuration_change": "Última Mudança de Configuração", "last_configuration_change": "Última Mudança de Configuração",
"last_configuration_download": "Último download da configuração", "last_configuration_download": "Último download da configuração",
"location": "Localização", "location": "Localização",
"note": "Nota",
"notes": "notas", "notes": "notas",
"owner": "Proprietário", "owner": "Proprietário",
"title": "Configuração do dispositivo", "title": "Configuração",
"type": "Tipo de dispositivo", "type": "Tipo de dispositivo",
"uuid": "ID de configuração",
"view_json": "Exibir JSON bruto" "view_json": "Exibir JSON bruto"
}, },
"configure": { "configure": {
"choose_file": "Você precisa escolher um arquivo .json válido:", "choose_file": "Você precisa escolher um arquivo .json válido:",
"enter_new": "Insira a nova configuração do dispositivo JSON:", "enter_new": "Insira a nova configuração do dispositivo JSON:",
"placeholder": "Config JSON", "placeholder": "Config JSON",
"title": "Configurar dispositivo", "title": "Configurar",
"valid_json": "Você precisa inserir um JSON válido" "valid_json": "Você precisa inserir um JSON válido"
}, },
"delete_command": { "delete_command": {
"explanation": "Tem certeza de que deseja excluir este comando? esta ação não é reversível.", "explanation": "Tem certeza de que deseja excluir este comando? esta ação não é reversível.",
"title": "Apagar Comando" "title": "Apagar Comando"
}, },
"delete_logs": {
"date": "Selecione a data do registro mais antigo que você gostaria de manter",
"device_logs_title": "Excluir registros do dispositivo",
"explanation": "Isso excluirá todos os {{object}} antes da data que você escolheu. Cuidado, esta ação não é reversível.",
"healthchecks_title": "Excluir verificações de saúde"
},
"device_logs": { "device_logs": {
"log": "Registro", "log": "Registro",
"severity": "Gravidade", "severity": "Gravidade",
"title": "Registros de dispositivos" "title": "Toras"
},
"entity": {
"add_child": "Adicionar Entidade Filha a {{entityName}}",
"add_failure": "Erro, o servidor retornou: {{error}}",
"add_root": "Adicionar Entidade Raiz",
"add_success": "Entidade criada com sucesso!",
"cannot_delete": "Você não pode excluir entidades que têm filhos. Exclua os filhos desta entidade para poder excluí-la.",
"delete_success": "Entidade excluída com sucesso",
"delete_warning": "Aviso: esta operação não pode ser revertida",
"edit_failure": "Atualização malsucedida: {{error}}",
"entities": "Entidades",
"entity": "Entidade",
"only_unassigned": "Apenas não atribuídos",
"valid_serial": "Precisa ser um número de série válido (12 caracteres HEX)"
}, },
"factory_reset": { "factory_reset": {
"redirector": "Manter redirecionador:", "redirector": "Manter redirecionador:",
"title": "Dispositivo de redefinição de fábrica", "reset": "Restabelecer",
"resetting": "Reiniciando ...",
"title": "Restauração de fábrica",
"warning": "Aviso: depois de enviar, isso não pode ser revertido" "warning": "Aviso: depois de enviar, isso não pode ser revertido"
}, },
"firmware": {
"average_age": "Idade Média do Firmware",
"choose_custom": "Escolher",
"details_title": "Detalhes da imagem #{{image}} ",
"device_type": "Tipo de dispositivo",
"device_types": "Tipos de dispositivos",
"downloads": "Transferências",
"error_fetching_latest": "Erro ao buscar o firmware mais recente",
"from_release": "De",
"history_title": "História",
"image": "Imagem",
"image_date": "Data da Imagem",
"installed_firmware": "Firmware Instalado",
"latest_version_installed": "Última versão instalada",
"newer_firmware_available": "Novas revisões disponíveis",
"reinstall_latest": "Reinstalar",
"revision": "Revisão",
"show_dev": "Mostrar lançamentos de desenvolvimento",
"size": "Tamanho",
"status": "Status do firmware",
"title": "Firmware",
"to_release": "Para",
"unknown_firmware_status": "Status de firmware desconhecido",
"upgrade": "Melhorar",
"upgrade_command_submitted": "Comando de atualização enviado com sucesso",
"upgrade_to_latest": "Mais recentes",
"upgrade_to_version": "Atualize para esta revisão",
"upgrading": "Atualizando ..."
},
"footer": { "footer": {
"coreui_for_react": "CoreUI para React", "coreui_for_react": "CoreUI para React",
"powered_by": "Distribuído por", "powered_by": "Distribuído por",
@@ -120,43 +248,170 @@
}, },
"health": { "health": {
"sanity": "Sanidade", "sanity": "Sanidade",
"title": "Saúde do Dispositivo" "title": "Saúde"
},
"inventory": {
"add_child_venue": "Adicionar Local Infantil a {{entityName}}",
"add_tag": "Adicionar etiqueta",
"add_tag_to": "Adicionar tag de estoque a {{name}}",
"assign_error": "Erro ao tentar atribuir tag",
"assign_to_entity": "Atribuir à Entidade",
"error_retrieving": "Ocorreu um erro ao recuperar as tags de inventário",
"error_unassign": "Erro durante operação de cancelamento de atribuição",
"subscriber": "Assinante",
"successful_assign": "Tag atribuída com sucesso",
"successful_tag_update": "Tag atualizada com sucesso",
"successful_unassign": "A operação de cancelamento da atribuição foi bem-sucedida",
"tag_created": "Tag de inventário criada com sucesso",
"tag_creation_error": "Erro ao tentar criar etiqueta de inventário",
"tag_update_error": "Erro ao atualizar tag",
"tags_assigned_to": "Tags de inventário atribuídas a {{name}}",
"unassign": "Cancelar atribuição",
"unassign_tag": "Cancelar a atribuição de tag da entidade",
"unassigned_tags": "Tags não atribuídas",
"venue": "Local"
}, },
"login": { "login": {
"change_password": "Mudar senha",
"change_password_error": "Erro ao alterar a senha. Certifique-se de que a nova senha é válida visitando a página 'Política de senha'",
"change_password_instructions": "Digite e confirme sua nova senha",
"changing_password": "Alterando senha ...",
"confirm_new_password": "confirme a nova senha",
"different_passwords": "Você precisa inserir a mesma senha duas vezes",
"forgot_password_error": "Erro ao tentar enviar e-mail Esqueci a senha. Certifique-se de que este userId esteja associado a uma conta.",
"forgot_password_explanation": "Digite seu nome de usuário para receber um e-mail contendo as instruções para redefinir sua senha",
"forgot_password_success": "Em breve, você receberá um e-mail com as instruções para redefinir sua senha. Certifique-se de verificar o seu spam se você não conseguir encontrar o e-mail",
"logging_in": "Fazendo login ...",
"login": "Entrar", "login": "Entrar",
"login_error": "Erro de login, confirme se seu nome de usuário, senha e url de gateway são válidos", "login_error": "Erro de login, certifique-se de que as informações que você está fornecendo são válidas",
"new_password": "Nova senha",
"password": "Senha", "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_password": "Por favor, insira sua senha",
"please_enter_username": "Por favor insira seu nome de usuário", "please_enter_username": "Por favor insira seu nome de usuário",
"previously_used": "A senha foi usada anteriormente",
"send_forgot": "ENVIAR EMAIL",
"sending_ellipsis": "Enviando ...",
"sign_in_to_account": "Faça login em sua conta", "sign_in_to_account": "Faça login em sua conta",
"url": "URL uCentralSec",
"username": "Nome de usuário" "username": "Nome de usuário"
}, },
"reboot": { "reboot": {
"directions": "Quando você gostaria de reinicializar este dispositivo?", "directions": "Quando você gostaria de reinicializar este dispositivo?",
"title": "Reiniciar dispositivo" "now": "Você gostaria de reiniciar este dispositivo agora?",
"title": "Reiniciar"
}, },
"scan": { "scan": {
"active": "Habilitar varredura ativa", "active": "Habilitar varredura ativa",
"channel": "Canal", "channel": "Canal",
"directions": "Inicie uma verificação de wi-fi deste dispositivo, o que deve levar aproximadamente 25 segundos.", "directions": "Inicie uma verificação de wi-fi deste dispositivo, o que deve levar aproximadamente 25 segundos.",
"results": "Resultados da verificação de wi-fi" "re_scan": "Verificar novamente",
"result_directions": "Clique no botão '$ t (scan.re_scan)' se desejar fazer uma varredura com a mesma configuração da anterior.",
"results": "Resultados da verificação de Wi-Fi",
"scan": "Varredura",
"scanning": "Scanning... ",
"waiting_directions": "Por favor, aguarde o resultado da verificação. Isso pode levar até 25 segundos. Você pode sair e ver os resultados da tabela de comandos mais tarde."
},
"settings": {
"title": "Definições"
}, },
"statistics": { "statistics": {
"data": "Dados (KB)", "data": "Dados (KB)",
"latest_statistics": "Estatísticas mais recentes",
"lifetime_stats": "Estatísticas de vida",
"no_interfaces": "Nenhuma estatística de tempo de vida da interface disponível",
"show_latest": "Últimas estatísticas",
"title": "Estatisticas" "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"
},
"system": {
"error_fetching": "Erro ao buscar informações do sistema"
},
"trace": { "trace": {
"choose_network": "Escolha a rede", "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", "directions": "Lançar um rastreamento remoto deste dispositivo para uma duração específica ou um número de pacotes",
"download_trace": "Baixar arquivo de rastreamento",
"packets": "Pacotes", "packets": "Pacotes",
"title": "Dispositivo de rastreamento" "title": "Vestígio",
"trace": "Vestígio",
"trace_not_successful": "O rastreamento não foi bem-sucedido: o gateway relatou o seguinte erro: {{error}}",
"wait_for_file": "Você gostaria de esperar até que o arquivo de rastreamento esteja pronto?",
"waiting_directions": "Aguarde o arquivo de dados de rastreamento. Isto pode tomar algum tempo. Você pode sair da espera e recuperar o arquivo de rastreamento da tabela de comandos mais tarde.",
"waiting_seconds": "Tempo decorrido: {{seconds}} segundos"
}, },
"upgrade": { "upgrade": {
"command_submitted": "Comando enviado",
"device_disconnected": "Dispositivo desconectado",
"device_reconnected": "Dispositivo reconectado",
"device_upgrading_firmware": "Firmware de atualização de dispositivo",
"directions": "Escolha um horário e uma versão de firmware para este dispositivo", "directions": "Escolha um horário e uma versão de firmware para este dispositivo",
"firmware_uri": "URI de firmware:", "firmware_uri": "URI de firmware:",
"need_uri": "Você precisa de um URI ...", "need_uri": "Você precisa de um URI ...",
"new_version": "Nova versão é",
"offline_device": "Esta opção está bloqueada porque este dispositivo não está conectado",
"time": "Tempo de atualização:", "time": "Tempo de atualização:",
"title": "Atualização de firmware" "title": "Atualização de firmware",
"upgrade": "Melhorar",
"wait_for_upgrade": "Você gostaria de esperar a conclusão da atualização?",
"waiting_for_device": "Esperando que o dispositivo se reconecte"
},
"user": {
"avatar": "Seu avatar",
"avatar_file": "Seu avatar (máx. De 2 MB)",
"create": "Criar usuário",
"create_failure": "Erro ao criar usuário. Certifique-se de que este endereço de e-mail ainda não esteja vinculado a uma conta.",
"create_success": "Usuário criado com sucesso",
"creating": "Criando usuário ...",
"delete_avatar": "Apagar Avatar",
"delete_failure": "Erro ao tentar excluir usuário",
"delete_success": "Usuário excluído com sucesso!",
"delete_title": "Deletar usuário",
"delete_warning": "Aviso: depois de excluir um usuário, você não pode reverter",
"deleting": "Excluindo ...",
"description": "Descrição",
"edit": "Editar usuário",
"email_address": "Endereço de e-mail",
"force_password_change": "Forçar mudança de senha no login",
"id": "ID do usuário.",
"last_login": "Último login",
"login_id": "Identificação de usuário.",
"my_profile": "Meu perfil",
"name": "Nome",
"nickname": "Apelido",
"nickname_explanation": "Apelido (opcional)",
"not_validated": "Não validado",
"note": "Nota",
"password": "Senha",
"provide_email": "Por favor, forneça um endereço de e-mail válido",
"provide_password": "Forneça uma senha válida",
"save_avatar": "Salvar Avatar",
"show_hide_password": "Mostrar / ocultar senha",
"update_failure": "Certifique-se de que todos os seus dados são válidos. Se você estiver modificando a senha, certifique-se de que não seja uma senha antiga.",
"update_failure_title": "Atualização falhou",
"update_success": "Usuário atualizado com sucesso",
"update_success_title": "Sucesso",
"user_role": "Função",
"users": "Comercial",
"validated": "Validado"
},
"wifi_analysis": {
"association": "Associação",
"associations": "Associações",
"mode": "Modo",
"network_diagram": "Diagrama de rede",
"radios": "Rádios",
"title": "Análise de Wi-Fi"
} }
} }

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

@@ -1,28 +1,4 @@
import { import {
cibSkype,
cibFacebook,
cibTwitter,
cibLinkedin,
cibFlickr,
cibTumblr,
cibXing,
cibGithub,
cibStackoverflow,
cibYoutube,
cibDribbble,
cibInstagram,
cibPinterest,
cibVk,
cibYahoo,
cibBehance,
cibReddit,
cibVimeo,
cibCcMastercard,
cibCcVisa,
cibStripe,
cibPaypal,
cibGooglePay,
cibCcAmex,
cifUs, cifUs,
cifBr, cifBr,
cifIn, cifIn,
@@ -122,14 +98,8 @@ import {
cilXCircle, cilXCircle,
cilWarning, cilWarning,
} from '@coreui/icons'; } from '@coreui/icons';
import { sygnet } from './sygnet';
import { logo } from './logo';
import { logoNegative } from './logo-negative';
export const icons = { export const icons = {
sygnet,
logo,
logoNegative,
cilAlignCenter, cilAlignCenter,
cilAlignLeft, cilAlignLeft,
cilAlignRight, cilAlignRight,
@@ -228,28 +198,4 @@ export const icons = {
cifFr, cifFr,
cifEs, cifEs,
cifPl, cifPl,
cibSkype,
cibFacebook,
cibTwitter,
cibLinkedin,
cibFlickr,
cibTumblr,
cibXing,
cibGithub,
cibStackoverflow,
cibYoutube,
cibDribbble,
cibInstagram,
cibPinterest,
cibVk,
cibYahoo,
cibBehance,
cibReddit,
cibVimeo,
cibCcMastercard,
cibCcVisa,
cibStripe,
cibPaypal,
cibGooglePay,
cibCcAmex,
}; };

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>
`,
];

View File

@@ -1,32 +0,0 @@
export const logo = [
'608 134',
`
<title>coreui react pro</title>
<g>
<g style="fill:#00a1ff">
<path d="M362.0177,90.1512,353.25,69.4149a.2507.2507,0,0,0-.2559-.1914H343.01a.2263.2263,0,0,0-.2559.2559V90.0233a.5657.5657,0,0,1-.64.64h-1.2163a.5652.5652,0,0,1-.64-.64V46.5028a.5655.5655,0,0,1,.64-.64H353.442a9.9792,9.9792,0,0,1,7.7437,3.2324A12.2,12.2,0,0,1,364.13,57.64a12.4389,12.4389,0,0,1-2.24,7.584,9.37,9.37,0,0,1-6.08,3.7441c-.1709.086-.2139.1915-.128.3194l8.7041,20.6084.064.2558q0,.5127-.5757.5118h-1.1523A.703.703,0,0,1,362.0177,90.1512ZM342.754,48.3593v18.496a.2259.2259,0,0,0,.2559.2559h10.3037a7.6713,7.6713,0,0,0,6.0166-2.5918,9.8807,9.8807,0,0,0,2.3037-6.8164,10.2875,10.2875,0,0,0-2.272-6.9756,7.6033,7.6033,0,0,0-6.0483-2.624H343.01A.2263.2263,0,0,0,342.754,48.3593Z"/>
<path d="M401.3263,48.1034H381.2945a.2262.2262,0,0,0-.2558.2559v18.496a.2259.2259,0,0,0,.2558.2559h13.8238a.5664.5664,0,0,1,.6406.64v.96a.5663.5663,0,0,1-.6406.6406H381.2945a.2263.2263,0,0,0-.2558.2559v18.56a.2258.2258,0,0,0,.2558.2558h20.0318a.5671.5671,0,0,1,.6406.6407v.96a.566.566,0,0,1-.6406.64H379.1827a.5653.5653,0,0,1-.64-.64V46.5028a.5656.5656,0,0,1,.64-.64h22.1436a.5664.5664,0,0,1,.6406.64v.96A.5663.5663,0,0,1,401.3263,48.1034Z"/>
<path d="M439.047,90.1512l-2.4317-8.832a.2971.2971,0,0,0-.32-.1924H419.5274a.2957.2957,0,0,0-.32.1924l-2.3681,8.7676a.6577.6577,0,0,1-.7036.5762H414.919a.5385.5385,0,0,1-.5756-.7041l12.0317-43.584a.6436.6436,0,0,1,.7041-.5117h1.6a.6442.6442,0,0,1,.7041.5117l12.16,43.584.0644.1923q0,.5127-.64.5118h-1.2163A.6428.6428,0,0,1,439.047,90.1512ZM419.9435,78.9188a.3031.3031,0,0,0,.2236.0967h15.4883a.3048.3048,0,0,0,.2236-.0967c.0645-.0635.0742-.1162.0322-.1592l-7.872-28.9287c-.043-.0849-.086-.1279-.128-.1279s-.0859.043-.1279.1279L419.9112,78.76C419.8683,78.8026,419.879,78.8553,419.9435,78.9188Z"/>
<path d="M456.6017,87.911a11.6372,11.6372,0,0,1-3.3277-8.7041V57.1913a11.4158,11.4158,0,0,1,3.36-8.5762,12.0941,12.0941,0,0,1,8.8-3.2637,12.2566,12.2566,0,0,1,8.8643,3.2315,11.3927,11.3927,0,0,1,3.36,8.6084v.64a.5663.5663,0,0,1-.6406.6407l-1.28.0634q-.6408,0-.64-.5761v-.8321a9.289,9.289,0,0,0-2.6558-6.9121,10.6734,10.6734,0,0,0-14.0161,0,9.2854,9.2854,0,0,0-2.6563,6.9121V79.3993a9.2808,9.2808,0,0,0,2.6563,6.9121,10.67,10.67,0,0,0,14.0161,0,9.2843,9.2843,0,0,0,2.6558-6.9121v-.7686q0-.5757.64-.5752l1.28.0635a.5667.5667,0,0,1,.6406.6406v.5118a11.4952,11.4952,0,0,1-3.36,8.64,13.6227,13.6227,0,0,1-17.6963,0Z"/>
<path d="M514.4376,46.5028v.96a.5658.5658,0,0,1-.64.6406H503.046a.2263.2263,0,0,0-.2559.2559v41.664a.566.566,0,0,1-.6406.64h-1.2158a.5652.5652,0,0,1-.64-.64V48.3593a.2266.2266,0,0,0-.2558-.2559H489.8619a.5656.5656,0,0,1-.64-.6406v-.96a.5656.5656,0,0,1,.64-.64H513.798A.5658.5658,0,0,1,514.4376,46.5028Z"/>
<path d="M522.0665,89.5116a2.8385,2.8385,0,0,1-.8-2.0488,2.9194,2.9194,0,0,1,.8-2.1114,2.7544,2.7544,0,0,1,2.08-.832,2.8465,2.8465,0,0,1,2.9438,2.9434,2.7541,2.7541,0,0,1-.832,2.08,2.9221,2.9221,0,0,1-2.1118.8008A2.754,2.754,0,0,1,522.0665,89.5116Z"/>
<path d="M542.4054,88.0077a11.3123,11.3123,0,0,1-3.2-8.416v-5.44a.5656.5656,0,0,1,.64-.64h1.2158a.5661.5661,0,0,1,.64.64v5.5039a9.1424,9.1424,0,0,0,2.5283,6.72,8.9745,8.9745,0,0,0,6.6875,2.5605,8.7908,8.7908,0,0,0,9.28-9.28V46.5028a.5655.5655,0,0,1,.64-.64h1.2163a.566.566,0,0,1,.64.64V79.5917a11.2545,11.2545,0,0,1-3.2325,8.416,13.0618,13.0618,0,0,1-17.0556,0Z"/>
<path d="M580.35,88.1034a10.4859,10.4859,0,0,1-3.36-8.1279v-1.792a.5663.5663,0,0,1,.64-.6407h1.0884a.5668.5668,0,0,1,.64.6407v1.6a8.5459,8.5459,0,0,0,2.752,6.6562,10.5353,10.5353,0,0,0,7.36,2.4961,9.8719,9.8719,0,0,0,6.9761-2.3681,8.2161,8.2161,0,0,0,2.56-6.336,8.4,8.4,0,0,0-1.12-4.416,11.3812,11.3812,0,0,0-3.3281-3.3926,71.6714,71.6714,0,0,0-6.1763-3.7119,71.0479,71.0479,0,0,1-6.24-3.84,12.1711,12.1711,0,0,1-3.4238-3.68,10.2614,10.2614,0,0,1-1.28-5.3438,9.8579,9.8579,0,0,1,3.0718-7.7441,12.0122,12.0122,0,0,1,8.32-2.752q5.6954,0,8.96,3.1036a10.8251,10.8251,0,0,1,3.2642,8.2246v1.6a.5658.5658,0,0,1-.64.64h-1.1519a.5652.5652,0,0,1-.64-.64V56.8075a8.8647,8.8647,0,0,0-2.624-6.6885,9.9933,9.9933,0,0,0-7.232-2.5273,9.37,9.37,0,0,0-6.5278,2.1435,7.8224,7.8224,0,0,0-2.3682,6.1123,7.8006,7.8006,0,0,0,1.0244,4.16,10.387,10.387,0,0,0,3.0078,3.0391,62.8714,62.8714,0,0,0,5.9522,3.4882,71.0575,71.0575,0,0,1,6.72,4.2559,13.4674,13.4674,0,0,1,3.648,3.9365,10.049,10.049,0,0,1,1.28,5.1836,10.7177,10.7177,0,0,1-3.2637,8.1924q-3.2637,3.0717-8.832,3.0723Q583.71,91.1757,580.35,88.1034Z"/>
</g>
<g style="fill:#3c4b64">
<g>
<path d="M99.835,36.0577l-39-22.5167a12,12,0,0,0-12,0l-39,22.5166a12.0339,12.0339,0,0,0-6,10.3924V91.4833a12.0333,12.0333,0,0,0,6,10.3923l39,22.5167a12,12,0,0,0,12,0l39-22.5167a12.0331,12.0331,0,0,0,6-10.3923V46.45A12.0334,12.0334,0,0,0,99.835,36.0577Zm-2,55.4256a4,4,0,0,1-2,3.4641l-39,22.5167a4.0006,4.0006,0,0,1-4,0l-39-22.5167a4,4,0,0,1-2-3.4641V46.45a4,4,0,0,1,2-3.4642l39-22.5166a4,4,0,0,1,4,0l39,22.5166a4,4,0,0,1,2,3.4642Z"/>
<path d="M77.8567,82.0046h-2.866a4,4,0,0,0-1.9247.4934L55.7852,91.9833,35.835,80.4648V57.4872l19.95-11.5185,17.2893,9.4549a3.9993,3.9993,0,0,0,1.9192.4906h2.8632a2,2,0,0,0,2-2V51.2024a2,2,0,0,0-1.04-1.7547L59.628,38.9521a8.0391,8.0391,0,0,0-7.8428.09L31.8346,50.56a8.0246,8.0246,0,0,0-4,6.9287v22.976a8,8,0,0,0,4,6.9283l19.95,11.5186a8.0429,8.0429,0,0,0,7.8433.0879l19.19-10.5312a2,2,0,0,0,1.0378-1.7533v-2.71A2,2,0,0,0,77.8567,82.0046Z"/>
</g>
<g>
<path d="M172.58,45.3618a15.0166,15.0166,0,0,0-15,14.9995V77.6387a15,15,0,0,0,30,0V60.3613A15.0166,15.0166,0,0,0,172.58,45.3618Zm7,32.2769a7,7,0,0,1-14,0V60.3613a7,7,0,0,1,14,0Z"/>
<path d="M135.9138,53.4211a7.01,7.01,0,0,1,7.8681,6.0752.9894.9894,0,0,0,.9843.865h6.03a1.0108,1.0108,0,0,0,.9987-1.0971,15.0182,15.0182,0,0,0-15.7162-13.8837,15.2881,15.2881,0,0,0-14.2441,15.4163V77.2037A15.288,15.288,0,0,0,136.0792,92.62a15.0183,15.0183,0,0,0,15.7162-13.8842,1.0107,1.0107,0,0,0-.9987-1.0971h-6.03a.9894.9894,0,0,0-.9843.865,7.01,7.01,0,0,1-7.8679,6.0757,7.1642,7.1642,0,0,1-6.0789-7.1849V60.6057A7.1638,7.1638,0,0,1,135.9138,53.4211Z"/>
<path d="M218.7572,72.9277a12.1585,12.1585,0,0,0,7.1843-11.0771V58.1494A12.1494,12.1494,0,0,0,213.7921,46H196.835a1,1,0,0,0-1,1V91a1,1,0,0,0,1,1h6a1,1,0,0,0,1-1V74h6.6216l7.9154,17.4138a1,1,0,0,0,.91.5862h6.5911a1,1,0,0,0,.91-1.4138Zm-.8157-11.0771A4.1538,4.1538,0,0,1,213.7926,66h-9.8511V54h9.8511a4.1538,4.1538,0,0,1,4.1489,4.1494Z"/>
<path d="M260.835,46h-26a1,1,0,0,0-1,1V91a1,1,0,0,0,1,1h26a1,1,0,0,0,1-1V85a1,1,0,0,0-1-1h-19V72h13a1,1,0,0,0,1-1V65a1,1,0,0,0-1-1h-13V54h19a1,1,0,0,0,1-1V47A1,1,0,0,0,260.835,46Z"/>
<path d="M298.835,46h-6a1,1,0,0,0-1,1V69.6475a7.0066,7.0066,0,1,1-14,0V47a1,1,0,0,0-1-1h-6a1,1,0,0,0-1,1V69.6475a15.0031,15.0031,0,1,0,30,0V47A1,1,0,0,0,298.835,46Z"/>
<rect x="307.835" y="46" width="8" height="38" rx="1"/>
</g>
</g>
</g>
`,
];

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

@@ -5,10 +5,9 @@ import {
CModalTitle, CModalTitle,
CModalBody, CModalBody,
CModalFooter, CModalFooter,
CSpinner, CSwitch,
CCol, CCol,
CRow, CRow,
CForm,
CFormGroup, CFormGroup,
CInputRadio, CInputRadio,
CLabel, CLabel,
@@ -17,37 +16,25 @@ import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker'; import DatePicker from 'react-widgets/DatePicker';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useSelector } from 'react-redux'; import { dateToUnix } from 'utils/helper';
import { convertDateFromUtc, convertDateToUtc, dateToUnix } from 'utils/helper';
import 'react-widgets/styles.css'; import 'react-widgets/styles.css';
import { getToken } from 'utils/authHelper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus'; import eventBus from 'utils/eventBus';
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
import LoadingButton from 'components/LoadingButton'; import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
import styles from './index.module.scss';
const BlinkModal = ({ show, toggleModal }) => { const BlinkModal = ({ show, toggleModal }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [hadSuccess, setHadSuccess] = useState(false); const { currentToken, endpoints } = useAuth();
const [hadFailure, setHadFailure] = useState(false); const { deviceSerialNumber } = useDevice();
const [doingNow, setDoingNow] = useState(false); const [isNow, setIsNow] = useState(false);
const [waiting, setWaiting] = useState(false); const [waiting, setWaiting] = useState(false);
const [chosenDate, setChosenDate] = useState(new Date().toString()); const [chosenDate, setChosenDate] = useState(new Date().toString());
const [chosenPattern, setPattern] = useState('on'); const [chosenPattern, setPattern] = useState('on');
const [responseBody, setResponseBody] = useState(''); const [result, setResult] = useState(null);
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const setDateToLate = () => { const toggleNow = () => {
const date = convertDateToUtc(new Date()); setIsNow(!isNow);
if (date.getHours() >= 3) {
date.setDate(date.getDate() + 1);
}
date.setHours(3);
date.setMinutes(0);
setChosenDate(convertDateFromUtc(date).toString());
}; };
const setDate = (date) => { const setDate = (date) => {
@@ -60,47 +47,40 @@ const BlinkModal = ({ show, toggleModal }) => {
if (show) { if (show) {
setWaiting(false); setWaiting(false);
setChosenDate(new Date().toString()); setChosenDate(new Date().toString());
setResponseBody('');
setPattern('on'); setPattern('on');
setDoingNow(false); setResult(null);
setHadSuccess(false);
setHadFailure(false);
} }
}, [show]); }, [show]);
const doAction = (isNow) => { const doAction = () => {
if (isNow !== undefined) setDoingNow(isNow);
setHadFailure(false);
setHadSuccess(false);
setWaiting(true); setWaiting(true);
const token = getToken();
const utcDate = new Date(chosenDate); const utcDate = new Date(chosenDate);
const utcDateString = utcDate.toISOString();
const parameters = { const parameters = {
serialNumber: selectedDeviceId, serialNumber: deviceSerialNumber,
when: isNow ? 0 : dateToUnix(utcDateString), when: isNow ? 0 : dateToUnix(utcDate),
pattern: chosenPattern, pattern: chosenPattern,
duration: 30, duration: 30,
}; };
const headers = { const headers = {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${token}`, Authorization: `Bearer ${currentToken}`,
}; };
axiosInstance axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/leds`, parameters, { headers }) .post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/leds`,
parameters,
{ headers },
)
.then(() => { .then(() => {
setHadSuccess(true); setResult('success');
}) })
.catch(() => { .catch(() => {
setResponseBody('Error while submitting command!'); setResult('error');
setHadFailure(true);
}) })
.finally(() => { .finally(() => {
setDoingNow(false);
setWaiting(false); setWaiting(false);
eventBus.dispatch('actionCompleted', { message: 'An action has been completed' }); eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
}); });
@@ -111,33 +91,72 @@ const BlinkModal = ({ show, toggleModal }) => {
<CModalHeader closeButton> <CModalHeader closeButton>
<CModalTitle>{t('blink.device_leds')}</CModalTitle> <CModalTitle>{t('blink.device_leds')}</CModalTitle>
</CModalHeader> </CModalHeader>
{hadSuccess ? ( {result === 'success' ? (
<SuccessfulActionModalBody toggleModal={toggleModal} /> <SuccessfulActionModalBody toggleModal={toggleModal} />
) : ( ) : (
<div> <div>
<CModalBody> <CModalBody>
<h6>{t('blink.when_blink_leds')}</h6> <CFormGroup row>
<CRow className={styles.spacedRow}> <CCol md="3">
<CCol> <CLabel>{t('blink.pattern')}</CLabel>
<CButton onClick={() => doAction(true)} disabled={waiting} block color="primary">
{waiting && doingNow ? t('common.loading_ellipsis') : t('common.do_now')}
<CSpinner
color="light"
hidden={!waiting || !doingNow}
component="span"
size="sm"
/>
</CButton>
</CCol> </CCol>
<CCol> <CCol>
<CButton disabled={waiting} block color="primary" onClick={setDateToLate}> <CFormGroup variant="custom-radio" onClick={() => setPattern('on')} inline>
{t('common.later_tonight')} <CInputRadio
</CButton> 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="pt-1">
<CCol md="8">
<p>{t('blink.execute_now')}</p>
</CCol>
<CCol>
<CSwitch
disabled={waiting}
color="primary"
defaultChecked={isNow}
onClick={toggleNow}
labelOn={t('common.yes')}
labelOff={t('common.no')}
/>
</CCol> </CCol>
</CRow> </CRow>
<CRow className={styles.spacedRow}> <CRow hidden={isNow} className="pt-3">
<CCol md="4" className={styles.spacedDate}> <CCol md="4" className="pt-2">
<p>{t('common.date')}</p> <p>{t('common.custom_date')}</p>
</CCol> </CCol>
<CCol xs="12" md="8"> <CCol xs="12" md="8">
<DatePicker <DatePicker
@@ -147,63 +166,17 @@ const BlinkModal = ({ show, toggleModal }) => {
placeholder="Select custom date" placeholder="Select custom date"
disabled={waiting} disabled={waiting}
onChange={(date) => setDate(date)} onChange={(date) => setDate(date)}
min={convertDateToUtc(new Date())} min={new Date()}
/> />
</CCol> </CCol>
</CRow> </CRow>
<CRow className={styles.spacedRow}>
<CCol md="7">{t('blink.pattern')}</CCol>
<CCol>
<CForm>
<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>
</CCol>
</CRow>
<div hidden={!hadSuccess && !hadFailure}>
<div>
<pre className="ignore">{responseBody}</pre>
</div>
</div>
</CModalBody> </CModalBody>
<CModalFooter> <CModalFooter>
<LoadingButton <LoadingButton
label={t('common.schedule')} label={isNow ? t('blink.set_leds') : t('common.schedule')}
isLoadingLabel={t('common.loading_ellipsis')} isLoadingLabel={t('common.loading_ellipsis')}
isLoading={waiting && !doingNow} isLoading={waiting}
action={doAction} action={doAction}
variant="outline"
block={false} block={false}
disabled={waiting} disabled={waiting}
/> />

View File

@@ -1,7 +0,0 @@
.spacedRow {
margin-top: 20px;
}
.spacedDate {
margin-top: 7px;
}

View File

@@ -0,0 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
CButton,
CModal,
CModalHeader,
CModalBody,
CModalTitle,
CModalFooter,
} from '@coreui/react';
const DetailsModal = ({ t, show, toggle, details, commandUuid }) => (
<CModal size="lg" show={show} onClose={toggle}>
<CModalHeader closeButton>
<CModalTitle className="text-dark">{commandUuid}</CModalTitle>
</CModalHeader>
<CModalBody>
<pre className="ignore">{JSON.stringify(details, null, 4)}</pre>
</CModalBody>
<CModalFooter>
<CButton color="secondary" onClick={toggle}>
{t('common.close')}
</CButton>
</CModalFooter>
</CModal>
);
DetailsModal.propTypes = {
t: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
details: PropTypes.instanceOf(Object).isRequired,
commandUuid: PropTypes.string.isRequired,
};
export default DetailsModal;

View File

@@ -13,22 +13,20 @@ import {
} from '@coreui/react'; } from '@coreui/react';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import DatePicker from 'react-widgets/DatePicker'; import DatePicker from 'react-widgets/DatePicker';
import { cilCloudDownload, cilSync } from '@coreui/icons'; import { cilCloudDownload, cilSync, cilCalendarCheck } from '@coreui/icons';
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faClipboardCheck } from '@fortawesome/free-solid-svg-icons';
import { prettyDate, dateToUnix } from 'utils/helper'; import { prettyDate, dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper';
import eventBus from 'utils/eventBus'; import eventBus from 'utils/eventBus';
import ConfirmModal from 'components/ConfirmModal'; import ConfirmModal from 'components/ConfirmModal';
import LoadingButton from 'components/LoadingButton'; import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
import WifiScanResultModalWidget from 'components/WifiScanResultModal'; import WifiScanResultModalWidget from 'components/WifiScanResultModal';
import DeviceCommandsCollapse from './DeviceCommandsCollapse'; import DetailsModal from './DetailsModal';
import styles from './index.module.scss'; import styles from './index.module.scss';
const DeviceCommands = ({ selectedDeviceId }) => { const DeviceCommands = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
// Wifiscan result related // Wifiscan result related
const [chosenWifiScan, setChosenWifiScan] = useState(null); const [chosenWifiScan, setChosenWifiScan] = useState(null);
const [showScanModal, setShowScanModal] = useState(false); const [showScanModal, setShowScanModal] = useState(false);
@@ -36,11 +34,12 @@ const DeviceCommands = ({ selectedDeviceId }) => {
// Delete modal related // Delete modal related
const [showConfirmModal, setShowConfirmModal] = useState(false); const [showConfirmModal, setShowConfirmModal] = useState(false);
const [uuidDelete, setUuidDelete] = useState(''); const [uuidDelete, setUuidDelete] = useState('');
// Details modal related
const [showDetailsModal, setShowDetailsModal] = useState(false);
const [detailsUuid, setDetailsUuid] = useState('');
const [modalDetails, setModalDetails] = useState({});
// Main collapsible // Main collapsible
const [collapse, setCollapse] = useState(false); const [collapse, setCollapse] = useState(false);
// Two other open collapsible lists
const [details, setDetails] = useState([]);
const [responses, setResponses] = useState([]);
// General states // General states
const [commands, setCommands] = useState([]); const [commands, setCommands] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -65,6 +64,10 @@ const DeviceCommands = ({ selectedDeviceId }) => {
setShowConfirmModal(!showConfirmModal); setShowConfirmModal(!showConfirmModal);
}; };
const toggleDetailsModal = () => {
setShowDetailsModal(!showDetailsModal);
};
const showMoreCommands = () => { const showMoreCommands = () => {
setCommandLimit(commandLimit + 50); setCommandLimit(commandLimit + 50);
}; };
@@ -92,7 +95,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
const options = { const options = {
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${currentToken}`,
}, },
params: { params: {
limit: commandLimit, limit: commandLimit,
@@ -109,7 +112,12 @@ const DeviceCommands = ({ selectedDeviceId }) => {
} }
axiosInstance axiosInstance
.get(`/commands?serialNumber=${encodeURIComponent(selectedDeviceId)}${extraParams}`, options) .get(
`${endpoints.ucentralgw}/api/v1/commands?serialNumber=${encodeURIComponent(
deviceSerialNumber,
)}${extraParams}`,
options,
)
.then((response) => { .then((response) => {
setCommands(response.data.commands); setCommands(response.data.commands);
}) })
@@ -124,13 +132,16 @@ const DeviceCommands = ({ selectedDeviceId }) => {
const options = { const options = {
headers: { headers: {
Accept: 'application/octet-stream', Accept: 'application/octet-stream',
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${currentToken}`,
}, },
responseType: 'arraybuffer', responseType: 'arraybuffer',
}; };
axiosInstance axiosInstance
.get(`/file/${uuid}?serialNumber=${selectedDeviceId}`, options) .get(
`${endpoints.ucentralgw}/api/v1/file/${uuid}?serialNumber=${deviceSerialNumber}`,
options,
)
.then((response) => { .then((response) => {
const blob = new Blob([response.data], { type: 'application/octet-stream' }); const blob = new Blob([response.data], { type: 'application/octet-stream' });
const link = document.createElement('a'); const link = document.createElement('a');
@@ -147,11 +158,11 @@ const DeviceCommands = ({ selectedDeviceId }) => {
const options = { const options = {
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${currentToken}`,
}, },
}; };
return axiosInstance return axiosInstance
.delete(`/command/${uuidDelete}`, options) .delete(`${endpoints.ucentralgw}/api/v1/command/${uuidDelete}`, options)
.then(() => { .then(() => {
deleteCommandFromList(uuidDelete); deleteCommandFromList(uuidDelete);
setUuidDelete(''); setUuidDelete('');
@@ -163,7 +174,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
}); });
}; };
const toggleDetails = (item, index) => { const toggleDetails = (item) => {
if (item.command === 'wifiscan') { if (item.command === 'wifiscan') {
setChosenWifiScan(item.results.status.scan); setChosenWifiScan(item.results.status.scan);
setChosenWifiScanDate(item.completed); setChosenWifiScanDate(item.completed);
@@ -171,52 +182,22 @@ const DeviceCommands = ({ selectedDeviceId }) => {
} else if (item.command === 'trace' && item.waitingForFile === 0) { } else if (item.command === 'trace' && item.waitingForFile === 0) {
downloadTrace(item.UUID); downloadTrace(item.UUID);
} else { } else {
const position = details.indexOf(index); setModalDetails(item.results ?? item);
let newDetails = details.slice(); setDetailsUuid(item.UUID);
toggleDetailsModal();
if (position !== -1) {
newDetails.splice(position, 1);
} else {
newDetails = [...details, index];
}
setDetails(newDetails);
} }
}; };
const toggleResponse = (item, index) => { const toggleResponse = (item) => {
const position = responses.indexOf(index); setModalDetails(item);
let newResponses = responses.slice(); setDetailsUuid(item.UUID);
toggleDetailsModal();
if (position !== -1) {
newResponses.splice(position, 1);
} else {
newResponses = [...newResponses, index];
}
setResponses(newResponses);
}; };
const refreshCommands = () => { const refreshCommands = () => {
getCommands(); getCommands();
}; };
const getDetails = (command, index) => {
if (!details.includes(index)) {
return <pre className="ignore" />;
}
if (command.results) {
const result = command.results;
if (result) return <pre className="ignore">{JSON.stringify(result, null, 4)}</pre>;
}
return <pre className="ignore">{JSON.stringify(command, null, 4)}</pre>;
};
const getResponse = (commandDetails, index) => {
if (!responses.includes(index)) {
return <pre className="ignore" />;
}
return <pre className="ignore">{JSON.stringify(commandDetails, null, 4)}</pre>;
};
const columns = [ const columns = [
{ key: 'UUID', label: t('common.id'), _style: { width: '28%' } }, { key: 'UUID', label: t('common.id'), _style: { width: '28%' } },
{ key: 'command', label: t('common.command'), _style: { width: '10%' } }, { key: 'command', label: t('common.command'), _style: { width: '10%' } },
@@ -233,12 +214,12 @@ const DeviceCommands = ({ selectedDeviceId }) => {
]; ];
useEffect(() => { useEffect(() => {
if (selectedDeviceId && start !== '' && end !== '') { if (deviceSerialNumber && start !== '' && end !== '') {
getCommands(); getCommands();
} else if (selectedDeviceId && start === '' && end === '') { } else if (deviceSerialNumber && start === '' && end === '') {
getCommands(); getCommands();
} }
}, [selectedDeviceId, start, end]); }, [deviceSerialNumber, start, end]);
useEffect(() => { useEffect(() => {
eventBus.on('actionCompleted', () => refreshCommands()); eventBus.on('actionCompleted', () => refreshCommands());
@@ -249,7 +230,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
}, []); }, []);
useEffect(() => { useEffect(() => {
if (selectedDeviceId) { if (deviceSerialNumber) {
setCommandLimit(25); setCommandLimit(25);
setLoadingMore(false); setLoadingMore(false);
setShowLoadingMore(true); setShowLoadingMore(true);
@@ -257,7 +238,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
setEnd(''); setEnd('');
getCommands(); getCommands();
} }
}, [selectedDeviceId]); }, [deviceSerialNumber]);
useEffect(() => { useEffect(() => {
if (commandLimit !== 25) { if (commandLimit !== 25) {
@@ -283,20 +264,15 @@ const DeviceCommands = ({ selectedDeviceId }) => {
<CCollapse show={collapse}> <CCollapse show={collapse}>
<CRow> <CRow>
<CCol /> <CCol />
<CCol> <CCol className="text-right">
<div className={styles.alignRight}> <div>
<CButton onClick={refreshCommands} size="sm"> <CButton onClick={refreshCommands} size="sm">
<CIcon <CIcon name="cil-sync" content={cilSync} className="text-white" size="2xl" />
name="cil-sync"
content={cilSync}
className={styles.whiteIcon}
size="2xl"
/>
</CButton> </CButton>
</div> </div>
</CCol> </CCol>
</CRow> </CRow>
<CRow className={styles.datepickerRow}> <CRow className="mb-2">
<CCol> <CCol>
From: From:
<DatePicker includeTime onChange={(date) => modifyStart(date)} /> <DatePicker includeTime onChange={(date) => modifyStart(date)} />
@@ -312,10 +288,8 @@ const DeviceCommands = ({ selectedDeviceId }) => {
loading={loading} loading={loading}
items={commands ?? []} items={commands ?? []}
fields={columns} fields={columns}
className={styles.whiteIcon} className="text-white"
columnFilter sorterValue={{ column: 'created', desc: 'true' }}
sorter
sorterValue={{ column: 'submitted', desc: 'true' }}
scopedSlots={{ scopedSlots={{
completed: (item) => ( completed: (item) => (
<td> <td>
@@ -349,26 +323,17 @@ const DeviceCommands = ({ selectedDeviceId }) => {
> >
<CButton <CButton
color="primary" color="primary"
variant={details.includes(index) ? '' : 'outline'} variant="outline"
disabled={
item.completed === 0 ||
(item.command === 'trace' && item.waitingForFile !== 0)
}
shape="square" shape="square"
size="sm" size="sm"
onClick={() => { onClick={() => {
toggleDetails(item, index); toggleDetails(item);
}} }}
> >
{item.command === 'trace' ? ( {item.command === 'trace' ? (
<CIcon content={cilCloudDownload} size="lg" /> <CIcon content={cilCloudDownload} size="lg" />
) : ( ) : (
<FontAwesomeIcon <CIcon content={cilCalendarCheck} size="lg" />
icon={faClipboardCheck}
className={[styles.customIconHeight, 'c-icon c-icon-lg'].join(
' ',
)}
/>
)} )}
</CButton> </CButton>
</CPopover> </CPopover>
@@ -377,11 +342,11 @@ const DeviceCommands = ({ selectedDeviceId }) => {
<CPopover content={t('common.details')}> <CPopover content={t('common.details')}>
<CButton <CButton
color="primary" color="primary"
variant={responses.includes(index) ? '' : 'outline'} variant="outline"
shape="square" shape="square"
size="sm" size="sm"
onClick={() => { onClick={() => {
toggleResponse(item, index); toggleResponse(item);
}} }}
> >
<CIcon name="cilList" size="lg" /> <CIcon name="cilList" size="lg" />
@@ -406,16 +371,6 @@ const DeviceCommands = ({ selectedDeviceId }) => {
</CRow> </CRow>
</td> </td>
), ),
details: (item, index) => (
<DeviceCommandsCollapse
details={details}
responses={responses}
index={index}
getDetails={getDetails}
getResponse={getResponse}
item={item}
/>
),
}} }}
/> />
<CRow className={styles.loadMoreSpacing}> <CRow className={styles.loadMoreSpacing}>
@@ -435,7 +390,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block> <CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
<CIcon <CIcon
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'} name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
className={styles.whiteIcon} className="text-white"
size="lg" size="lg"
/> />
</CButton> </CButton>
@@ -449,13 +404,15 @@ const DeviceCommands = ({ selectedDeviceId }) => {
date={chosenWifiScanDate} date={chosenWifiScanDate}
/> />
<ConfirmModal show={showConfirmModal} toggle={toggleConfirmModal} action={deleteCommand} /> <ConfirmModal show={showConfirmModal} toggle={toggleConfirmModal} action={deleteCommand} />
<CIcon name="cilNotes" className={styles.whiteIcon} size="lg" /> <DetailsModal
t={t}
show={showDetailsModal}
toggle={toggleDetailsModal}
details={modalDetails}
commandUuid={detailsUuid}
/>
</CWidgetDropdown> </CWidgetDropdown>
); );
}; };
DeviceCommands.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceCommands; export default DeviceCommands;

View File

@@ -2,20 +2,8 @@
padding: 20px; padding: 20px;
} }
.datepickerRow {
margin-bottom: 10px;
}
.scrollableBox { .scrollableBox {
height: 400px; height: 200px;
}
.whiteIcon {
color: white;
}
.alignRight {
float: right;
} }
.customIconHeight { .customIconHeight {

View File

@@ -16,17 +16,17 @@ import {
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import 'react-widgets/styles.css'; import 'react-widgets/styles.css';
import { getToken } from 'utils/authHelper'; import { useAuth, useDevice } from 'ucentral-libs';
import { checkIfJson } from 'utils/helper'; import { checkIfJson } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus'; import eventBus from 'utils/eventBus';
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
import styles from './index.module.scss';
const ConfigureModal = ({ show, toggleModal }) => { const ConfigureModal = ({ show, toggleModal }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [hadSuccess, setHadSuccess] = useState(false); const [hadSuccess, setHadSuccess] = useState(false);
const [hadFailure, setHadFailure] = useState(false); const [hadFailure, setHadFailure] = useState(false);
const [doingNow, setDoingNow] = useState(false); const [doingNow, setDoingNow] = useState(false);
@@ -36,7 +36,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
const [checkingIfSure, setCheckingIfSure] = useState(false); const [checkingIfSure, setCheckingIfSure] = useState(false);
const [errorJson, setErrorJson] = useState(false); const [errorJson, setErrorJson] = useState(false);
const [inputKey, setInputKey] = useState(0); const [inputKey, setInputKey] = useState(0);
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
let fileReader; let fileReader;
const confirmingIfSure = () => { const confirmingIfSure = () => {
@@ -69,10 +69,8 @@ const ConfigureModal = ({ show, toggleModal }) => {
setHadSuccess(false); setHadSuccess(false);
setWaiting(true); setWaiting(true);
const token = getToken();
const parameters = { const parameters = {
serialNumber: selectedDeviceId, serialNumber: deviceSerialNumber,
when: 0, when: 0,
UUID: 1, UUID: 1,
configuration: JSON.parse(newConfig), configuration: JSON.parse(newConfig),
@@ -80,11 +78,15 @@ const ConfigureModal = ({ show, toggleModal }) => {
const headers = { const headers = {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${token}`, Authorization: `Bearer ${currentToken}`,
}; };
axiosInstance axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/configure`, parameters, { headers }) .post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/configure`,
parameters,
{ headers },
)
.then(() => { .then(() => {
setHadSuccess(true); setHadSuccess(true);
}) })
@@ -122,7 +124,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
}; };
return ( return (
<CModal show={show} onClose={toggleModal}> <CModal show={show} onClose={toggleModal} size="lg">
<CModalHeader closeButton> <CModalHeader closeButton>
<CModalTitle>{t('configure.title')}</CModalTitle> <CModalTitle>{t('configure.title')}</CModalTitle>
</CModalHeader> </CModalHeader>
@@ -132,7 +134,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
<div> <div>
<CModalBody> <CModalBody>
<CRow> <CRow>
<CCol md="10" className={styles.spacedColumn}> <CCol md="10" className="mt-1">
<h6>{t('configure.enter_new')}</h6> <h6>{t('configure.enter_new')}</h6>
</CCol> </CCol>
<CCol> <CCol>
@@ -147,7 +149,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
</CButton> </CButton>
</CCol> </CCol>
</CRow> </CRow>
<CRow className={styles.spacedRow}> <CRow className="mt-4">
<CCol> <CCol>
<CForm> <CForm>
<CTextarea <CTextarea
@@ -165,7 +167,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
</CForm> </CForm>
</CCol> </CCol>
</CRow> </CRow>
<CRow className={styles.spacedRow}> <CRow className="mt-4">
<CCol>{t('configure.choose_file')}</CCol> <CCol>{t('configure.choose_file')}</CCol>
<CCol> <CCol>
<CInputFile <CInputFile
@@ -191,7 +193,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
color="primary" color="primary"
onClick={confirmingIfSure} onClick={confirmingIfSure}
> >
{t('common.submit')} {t('common.save')}
</CButton> </CButton>
<CButton <CButton
hidden={!checkingIfSure} hidden={!checkingIfSure}
@@ -199,7 +201,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
color="primary" color="primary"
onClick={() => doAction(false)} onClick={() => doAction(false)}
> >
{waiting && !doingNow ? 'Loading...' : 'Yes'} {' '} {waiting && !doingNow ? t('common.saving') : t('common.yes')} {' '}
<CSpinner color="light" hidden={!waiting || doingNow} component="span" size="sm" /> <CSpinner color="light" hidden={!waiting || doingNow} component="span" size="sm" />
</CButton> </CButton>
<CButton color="secondary" onClick={toggleModal}> <CButton color="secondary" onClick={toggleModal}>

View File

@@ -1,7 +0,0 @@
.spacedRow {
margin-top: 20px;
}
.spacedColumn {
margin-top: 3px;
}

View File

@@ -0,0 +1,64 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { CButton, CSpinner, CModalFooter } from '@coreui/react';
const ConfirmFooter = ({ isShown, isLoading, action, color, variant, block, toggleParent }) => {
const { t } = useTranslation();
const [askingIfSure, setAskingIfSure] = useState(false);
const confirmingIfSure = () => {
setAskingIfSure(true);
};
useEffect(() => {
setAskingIfSure(false);
}, [isShown]);
return (
<CModalFooter>
<div hidden={!askingIfSure}>{t('common.are_you_sure')}</div>
<CButton
disabled={isLoading}
hidden={askingIfSure}
color={color}
variant={variant}
onClick={() => confirmingIfSure()}
block={block}
>
{t('common.submit')}
</CButton>
<CButton
disabled={isLoading}
hidden={!askingIfSure}
color={color}
onClick={() => action()}
block={block}
>
{isLoading ? t('common.loading_ellipsis') : t('common.yes')}
<CSpinner color="light" hidden={!isLoading} component="span" size="sm" />
</CButton>
<CButton color="secondary" onClick={toggleParent}>
{t('common.cancel')}
</CButton>
</CModalFooter>
);
};
ConfirmFooter.propTypes = {
isLoading: PropTypes.bool.isRequired,
block: PropTypes.bool,
action: PropTypes.func.isRequired,
color: PropTypes.string,
variant: PropTypes.string,
toggleParent: PropTypes.func.isRequired,
isShown: PropTypes.bool.isRequired,
};
ConfirmFooter.defaultProps = {
color: 'primary',
variant: '',
block: false,
};
export default ConfirmFooter;

View File

@@ -11,7 +11,6 @@ import {
CBadge, CBadge,
} from '@coreui/react'; } from '@coreui/react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styles from './index.module.scss';
const ConfirmModal = ({ show, toggle, action }) => { const ConfirmModal = ({ show, toggle, action }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -63,7 +62,7 @@ const ConfirmModal = ({ show, toggle, action }) => {
}, [show]); }, [show]);
return ( return (
<CModal className={styles.modal} show={show} onClose={toggle}> <CModal className="text-dark" show={show} onClose={toggle}>
<CModalHeader closeButton> <CModalHeader closeButton>
<CModalTitle>{t('delete_command.title')}</CModalTitle> <CModalTitle>{t('delete_command.title')}</CModalTitle>
</CModalHeader> </CModalHeader>

View File

@@ -1,3 +0,0 @@
.modal {
color: #3c4b64;
}

View File

@@ -0,0 +1,168 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { CModal, CModalHeader, CModalBody } from '@coreui/react';
import { CreateUserForm, useFormFields, useAuth, useToast } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance';
import { testRegex, validateEmail } from 'utils/helper';
const initialState = {
name: {
value: '',
error: false,
optional: true,
},
email: {
value: '',
error: false,
},
currentPassword: {
value: '',
error: false,
},
changePassword: {
value: 'on',
error: false,
},
userRole: {
value: 'admin',
error: false,
},
notes: {
value: '',
error: false,
optional: true,
},
description: {
value: '',
error: false,
optional: true,
},
};
const CreateUserModal = ({ show, toggle, getUsers }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { addToast } = useToast();
const [loading, setLoading] = useState(false);
const [policies, setPolicies] = useState({
passwordPolicy: '',
passwordPattern: '',
accessPolicy: '',
});
const [formFields, updateFieldWithId, updateField, setFormFields] = useFormFields(initialState);
const toggleChange = () => {
updateField('changePassword', { value: !formFields.changePassword.value });
};
const createUser = () => {
setLoading(true);
const parameters = {
id: 0,
};
let validationSuccess = true;
for (const [key, value] of Object.entries(formFields)) {
if (!value.optional && value.value === '') {
validationSuccess = false;
updateField(key, { value: value.value, error: true });
} else if (key === 'currentPassword' && !testRegex(value.value, policies.passwordPattern)) {
validationSuccess = false;
updateField(key, { value: value.value, error: true });
} else if (key === 'email' && !validateEmail(value.value)) {
validationSuccess = false;
updateField(key, { value: value.value, error: true });
} else if (key === 'notes') {
parameters[key] = [{ note: value.value }];
} else if (key === 'changePassword') {
parameters[key] = value.value === 'on';
} else {
parameters[key] = value.value;
}
}
if (validationSuccess) {
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.post(`${endpoints.ucentralsec}/api/v1/user/0`, parameters, {
headers,
})
.then(() => {
getUsers();
setFormFields(initialState);
addToast({
title: t('common.success'),
body: t('user.create_success'),
color: 'success',
autohide: true,
});
toggle();
})
.catch(() => {
addToast({
title: t('common.error'),
body: t('user.create_failure'),
color: 'danger',
autohide: true,
});
})
.finally(() => {
setLoading(false);
});
} else {
setLoading(false);
}
};
const getPasswordPolicy = () => {
axiosInstance
.post(`${endpoints.ucentralsec}/api/v1/oauth2?requirements=true`, {})
.then((response) => {
const newPolicies = response.data;
newPolicies.accessPolicy = `${endpoints.ucentralsec}${newPolicies.accessPolicy}`;
newPolicies.passwordPolicy = `${endpoints.ucentralsec}${newPolicies.passwordPolicy}`;
setPolicies(response.data);
})
.catch(() => {});
};
useEffect(() => {
if (policies.passwordPattern.length === 0) getPasswordPolicy();
}, []);
useEffect(() => {
setFormFields(initialState);
}, [show]);
return (
<CModal show={show} onClose={toggle} size="xl">
<CModalHeader>{t('user.create')}</CModalHeader>
<CModalBody>
<CreateUserForm
t={t}
fields={formFields}
updateField={updateFieldWithId}
createUser={createUser}
loading={loading}
policies={policies}
toggleChange={toggleChange}
/>
</CModalBody>
</CModal>
);
};
CreateUserModal.propTypes = {
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
getUsers: PropTypes.func.isRequired,
};
export default React.memo(CreateUserModal);

View File

@@ -0,0 +1,100 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { CModal, CModalHeader, CModalTitle, CModalBody, CCol, CRow } from '@coreui/react';
import DatePicker from 'react-widgets/DatePicker';
import PropTypes from 'prop-types';
import { ConfirmFooter, useAuth, useDevice } from 'ucentral-libs';
import { dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus';
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());
const setDate = (date) => {
if (date) {
setMaxDate(date.toString());
}
};
const deleteLog = async () => {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
params: {
endDate: dateToUnix(maxDate),
},
};
return axiosInstance
.delete(`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/${object}`, options)
.then(() => {})
.catch(() => {})
.finally(() => {
if (object === 'healthchecks')
eventBus.dispatch('deletedHealth', { message: 'Healthcheck was deleted' });
else if (object === 'logs')
eventBus.dispatch('deletedLogs', { message: 'Deleted device logs' });
setLoading(false);
toggle();
});
};
useEffect(() => {
setLoading(false);
setMaxDate(new Date().toString());
}, [show]);
return (
<CModal className="text-dark" show={show} onClose={toggle}>
<CModalHeader closeButton>
<CModalTitle>
{object === 'healthchecks'
? t('delete_logs.healthchecks_title')
: t('delete_logs.device_logs_title')}
</CModalTitle>
</CModalHeader>
<CModalBody>
<h6>{t('delete_logs.explanation', { object })}</h6>
<CRow className="pt-3">
<CCol md="4" className="mt-2">
<p>{t('common.date')}:</p>
</CCol>
<CCol xs="12" md="8">
<DatePicker
selected={new Date(maxDate)}
includeTime
value={new Date(maxDate)}
placeholder="Select custom date"
disabled={loading}
onChange={(date) => setDate(date)}
/>
</CCol>
</CRow>
</CModalBody>
<ConfirmFooter
t={t}
isShown={show}
isLoading={loading}
action={deleteLog}
color="primary"
toggleParent={toggle}
/>
</CModal>
);
};
DeleteLogModal.propTypes = {
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
object: PropTypes.string.isRequired,
};
export default DeleteLogModal;

View File

@@ -1,21 +1,26 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { CButton, CCard, CCardHeader, CCardBody, CRow, CCol } from '@coreui/react'; import { CButton, CCard, CCardHeader, CCardBody, CRow, CCol } from '@coreui/react';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper'; import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs';
import LoadingButton from 'components/LoadingButton';
import RebootModal from 'components/RebootModal'; import RebootModal from 'components/RebootModal';
import FirmwareUpgradeModal from 'components/FirmwareUpgradeModal'; import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
import ConfigureModal from 'components/ConfigureModal'; import ConfigureModal from 'components/ConfigureModal';
import TraceModal from 'components/TraceModal'; import TraceModal from 'components/TraceModal';
import WifiScanModal from 'components/WifiScanModal'; import WifiScanModal from 'components/WifiScanModal';
import BlinkModal from 'components/BlinkModal'; import BlinkModal from 'components/BlinkModal';
import FactoryResetModal from 'components/FactoryResetModal'; import FactoryResetModal from 'components/FactoryResetModal';
import styles from './index.module.scss'; import EventQueueModal from 'components/EventQueueModal';
const DeviceActions = ({ selectedDeviceId }) => { const DeviceActions = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { addToast } = useToast();
const { deviceSerialNumber } = useDevice();
const [upgradeStatus, setUpgradeStatus] = useState({
loading: false,
});
const [device, setDevice] = useState({});
const [showRebootModal, setShowRebootModal] = useState(false); const [showRebootModal, setShowRebootModal] = useState(false);
const [showBlinkModal, setShowBlinkModal] = useState(false); const [showBlinkModal, setShowBlinkModal] = useState(false);
const [showUpgradeModal, setShowUpgradeModal] = useState(false); const [showUpgradeModal, setShowUpgradeModal] = useState(false);
@@ -24,6 +29,7 @@ const DeviceActions = ({ selectedDeviceId }) => {
const [connectLoading, setConnectLoading] = useState(false); const [connectLoading, setConnectLoading] = useState(false);
const [showConfigModal, setConfigModal] = useState(false); const [showConfigModal, setConfigModal] = useState(false);
const [showFactoryModal, setShowFactoryModal] = useState(false); const [showFactoryModal, setShowFactoryModal] = useState(false);
const [showQueueModal, setShowQueueModal] = useState(false);
const toggleRebootModal = () => { const toggleRebootModal = () => {
setShowRebootModal(!showRebootModal); setShowRebootModal(!showRebootModal);
@@ -53,17 +59,24 @@ const DeviceActions = ({ selectedDeviceId }) => {
setShowFactoryModal(!showFactoryModal); setShowFactoryModal(!showFactoryModal);
}; };
const toggleQueueModal = () => {
setShowQueueModal(!showQueueModal);
};
const getRttysInfo = () => { const getRttysInfo = () => {
setConnectLoading(true); setConnectLoading(true);
const options = { const options = {
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${currentToken}`,
}, },
}; };
axiosInstance axiosInstance
.get(`/device/${encodeURIComponent(selectedDeviceId)}/rtty`, options) .get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/rtty`,
options,
)
.then((response) => { .then((response) => {
const url = `https://${response.data.server}:${response.data.viewport}/connect/${response.data.connectionId}`; const url = `https://${response.data.server}:${response.data.viewport}/connect/${response.data.connectionId}`;
const newWindow = window.open(url, '_blank', 'noopener,noreferrer'); const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
@@ -75,9 +88,48 @@ const DeviceActions = ({ selectedDeviceId }) => {
}); });
}; };
const getDeviceInformation = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}`, options)
.then((response) => {
setDevice(response.data);
})
.catch(() => {});
};
useEffect(() => {
if (upgradeStatus.result !== undefined) {
addToast({
title: upgradeStatus.result.success ? t('common.success') : t('common.error'),
body: upgradeStatus.result.success
? t('firmware.upgrade_command_submitted')
: upgradeStatus.result.error,
color: upgradeStatus.result.success ? 'success' : 'danger',
autohide: true,
});
setUpgradeStatus({
loading: false,
});
setShowUpgradeModal(false);
}
}, [upgradeStatus]);
useEffect(() => {
getDeviceInformation();
}, [deviceSerialNumber]);
return ( return (
<CCard> <CCard>
<CCardHeader>{t('actions.title')}</CCardHeader> <CCardHeader>
<div className="text-value-lg">{t('actions.title')}</div>
</CCardHeader>
<CCardBody> <CCardBody>
<CRow> <CRow>
<CCol> <CCol>
@@ -91,7 +143,7 @@ const DeviceActions = ({ selectedDeviceId }) => {
</CButton> </CButton>
</CCol> </CCol>
</CRow> </CRow>
<CRow className={styles.spacedRow}> <CRow className="mt-3">
<CCol> <CCol>
<CButton block color="primary" onClick={toggleUpgradeModal}> <CButton block color="primary" onClick={toggleUpgradeModal}>
{t('actions.firmware_upgrade')} {t('actions.firmware_upgrade')}
@@ -103,7 +155,7 @@ const DeviceActions = ({ selectedDeviceId }) => {
</CButton> </CButton>
</CCol> </CCol>
</CRow> </CRow>
<CRow className={styles.spacedRow}> <CRow className="mt-3">
<CCol> <CCol>
<CButton block color="primary" onClick={toggleScanModal}> <CButton block color="primary" onClick={toggleScanModal}>
{t('actions.wifi_scan')} {t('actions.wifi_scan')}
@@ -115,7 +167,7 @@ const DeviceActions = ({ selectedDeviceId }) => {
</CButton> </CButton>
</CCol> </CCol>
</CRow> </CRow>
<CRow className={styles.spacedRow}> <CRow className="mt-3">
<CCol> <CCol>
<LoadingButton <LoadingButton
isLoading={connectLoading} isLoading={connectLoading}
@@ -130,20 +182,34 @@ const DeviceActions = ({ selectedDeviceId }) => {
</CButton> </CButton>
</CCol> </CCol>
</CRow> </CRow>
<CRow className="mt-3">
<CCol>
<CButton block color="primary" onClick={toggleQueueModal}>
{t('commands.event_queue')}
</CButton>
</CCol>
<CCol />
</CRow>
</CCardBody> </CCardBody>
<RebootModal show={showRebootModal} toggleModal={toggleRebootModal} /> <RebootModal show={showRebootModal} toggleModal={toggleRebootModal} />
<BlinkModal show={showBlinkModal} toggleModal={toggleBlinkModal} /> <BlinkModal show={showBlinkModal} toggleModal={toggleBlinkModal} />
<FirmwareUpgradeModal show={showUpgradeModal} toggleModal={toggleUpgradeModal} /> <DeviceFirmwareModal
t={t}
endpoints={endpoints}
currentToken={currentToken}
device={device}
show={showUpgradeModal}
toggleFirmwareModal={toggleUpgradeModal}
setUpgradeStatus={setUpgradeStatus}
upgradeStatus={upgradeStatus}
/>
<TraceModal show={showTraceModal} toggleModal={toggleTraceModal} /> <TraceModal show={showTraceModal} toggleModal={toggleTraceModal} />
<WifiScanModal show={showScanModal} toggleModal={toggleScanModal} /> <WifiScanModal show={showScanModal} toggleModal={toggleScanModal} />
<ConfigureModal show={showConfigModal} toggleModal={toggleConfigModal} /> <ConfigureModal show={showConfigModal} toggleModal={toggleConfigModal} />
<FactoryResetModal show={showFactoryModal} toggleModal={toggleFactoryResetModal} /> <FactoryResetModal show={showFactoryModal} toggleModal={toggleFactoryResetModal} />
<EventQueueModal show={showQueueModal} toggle={toggleQueueModal} />
</CCard> </CCard>
); );
}; };
DeviceActions.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceActions; export default DeviceActions;

View File

@@ -1,36 +0,0 @@
import React from 'react';
import { CCollapse, CCardBody } from '@coreui/react';
import PropTypes from 'prop-types';
import { Translation } from 'react-i18next';
const DeviceCommandsCollapse = ({ details, responses, index, item, getDetails, getResponse }) => (
<Translation>
{(t) => (
<div>
<CCollapse show={details.includes(index)}>
<CCardBody>
<h5>{t('common.result')}</h5>
<div>{getDetails(item, index)}</div>
</CCardBody>
</CCollapse>
<CCollapse show={responses.includes(index)}>
<CCardBody>
<h5>{t('common.details')}</h5>
<div>{getResponse(item, index)}</div>
</CCardBody>
</CCollapse>
</div>
)}
</Translation>
);
DeviceCommandsCollapse.propTypes = {
details: PropTypes.instanceOf(Array).isRequired,
responses: PropTypes.instanceOf(Array).isRequired,
index: PropTypes.number.isRequired,
getDetails: PropTypes.func.isRequired,
getResponse: PropTypes.func.isRequired,
item: PropTypes.instanceOf(Object).isRequired,
};
export default DeviceCommandsCollapse;

View File

@@ -10,14 +10,13 @@ import {
} from '@coreui/react'; } from '@coreui/react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Translation } from 'react-i18next'; import { Translation } from 'react-i18next';
import styles from './index.module.scss';
const DeviceConfigurationModal = ({ show, toggle, configuration }) => ( const DeviceConfigurationModal = ({ show, toggle, configuration }) => (
<Translation> <Translation>
{(t) => ( {(t) => (
<CModal size="lg" show={show} onClose={toggle}> <CModal size="lg" show={show} onClose={toggle}>
<CModalHeader closeButton> <CModalHeader closeButton>
<CModalTitle className={styles.modalTitle}>{t('configuration.title')}</CModalTitle> <CModalTitle className="text-dark">{t('configuration.title')}</CModalTitle>
</CModalHeader> </CModalHeader>
<CModalBody> <CModalBody>
<pre className="ignore">{JSON.stringify(configuration, null, 4)}</pre> <pre className="ignore">{JSON.stringify(configuration, null, 4)}</pre>

View File

@@ -1,3 +0,0 @@
.modalTitle {
color: black;
}

View File

@@ -4,11 +4,8 @@ import {
CCard, CCard,
CCardHeader, CCardHeader,
CCardBody, CCardBody,
CFormGroup,
CCol, CCol,
CLabel, CLabel,
CForm,
CInput,
CCollapse, CCollapse,
CCardFooter, CCardFooter,
CButton, CButton,
@@ -16,20 +13,31 @@ import {
CPopover, CPopover,
} from '@coreui/react'; } from '@coreui/react';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import PropTypes from 'prop-types'; import { cilWindowMaximize } from '@coreui/icons';
import { cilWindowMaximize, cilClone } from '@coreui/icons';
import { prettyDate } from 'utils/helper'; import { prettyDate } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper'; import {
import DeviceConfigurationModal from './containers/DeviceConfigurationModal/index'; CopyToClipboardButton,
import styles from './index.module.scss'; HideTextButton,
NotesTable,
useAuth,
useDevice,
} from 'ucentral-libs';
import DeviceConfigurationModal from './DeviceConfigurationModal';
const DeviceConfiguration = ({ selectedDeviceId }) => { const DeviceConfiguration = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [collapse, setCollapse] = useState(false); const [collapse, setCollapse] = useState(false);
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const [device, setDevice] = useState(null); const [device, setDevice] = useState(null);
const [copyPasswordSuccess, setCopyPasswordSuccess] = useState('');
const toggleShowPassword = () => {
setShowPassword(!showPassword);
};
const toggle = (e) => { const toggle = (e) => {
setCollapse(!collapse); setCollapse(!collapse);
@@ -40,32 +48,61 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
setShowModal(!showModal); setShowModal(!showModal);
}; };
const copyPasswordToClipboard = () => {
const password = device.devicePassword === '' ? 'openwifi' : device.devicePassword;
navigator.clipboard.writeText(password);
setCopyPasswordSuccess(t('common.copied'));
};
const getDevice = () => { const getDevice = () => {
const options = { const options = {
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${currentToken}`,
}, },
}; };
axiosInstance axiosInstance
.get(`/device/${encodeURIComponent(selectedDeviceId)}`, options) .get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`,
options,
)
.then((response) => { .then((response) => {
setDevice(response.data); setDevice(response.data);
}) })
.catch(() => {}); .catch(() => {});
}; };
const saveNote = (currentNote) => {
setLoading(true);
const parameters = {
serialNumber: deviceSerialNumber,
notes: [{ note: currentNote }],
};
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.put(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`,
parameters,
{ headers },
)
.then(() => {
getDevice();
})
.catch(() => {})
.finally(() => {
setLoading(false);
});
};
const getPassword = () => {
const password = device.devicePassword === '' ? 'openwifi' : device.devicePassword;
return showPassword ? password : '******';
};
useEffect(() => { useEffect(() => {
if (selectedDeviceId) getDevice(); if (deviceSerialNumber) getDevice();
setCopyPasswordSuccess(null); }, [deviceSerialNumber]);
}, [selectedDeviceId]);
if (device) { if (device) {
return ( return (
@@ -73,139 +110,142 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
<CCard> <CCard>
<CCardHeader> <CCardHeader>
<CRow> <CRow>
<CCol>{t('configuration.details')}</CCol>
<CCol> <CCol>
<div className={styles.alignRight}> <div className="text-value-lg">{t('configuration.title')}</div>
<CPopover content={t('configuration.view_json')}> </CCol>
<CButton color="secondary" onClick={toggleModal} size="sm"> <CCol className="text-right">
<CIcon content={cilWindowMaximize} /> <CPopover content={t('configuration.view_json')}>
</CButton> <CButton color="secondary" onClick={toggleModal} size="sm">
</CPopover> <CIcon content={cilWindowMaximize} />
</div> </CButton>
</CPopover>
</CCol> </CCol>
</CRow> </CRow>
</CCardHeader> </CCardHeader>
<CCardBody> <CCardBody>
<CForm <CRow className="mt-2">
action="" <CCol md="3">
method="post" <CLabel>{t('configuration.uuid')} : </CLabel>
encType="multipart/form-data" </CCol>
className="form-horizontal" <CCol xs="12" md="9">
> {device.UUID}
<CFormGroup row> </CCol>
<CCol md="3"> </CRow>
<CLabel>{t('common.uuid')} : </CLabel> <CRow className="mt-2">
</CCol> <CCol md="3">
<CCol xs="12" md="9"> <CLabel>{t('common.serial_number')} : </CLabel>
{device.UUID} </CCol>
</CCol> <CCol xs="12" md="9">
</CFormGroup> {device.serialNumber}
<CFormGroup row> <CopyToClipboardButton t={t} size="sm" content={device.serialNumber} />
<CCol md="3"> </CCol>
<CLabel>{t('common.serial_number')} : </CLabel> </CRow>
</CCol> <CRow className="mt-2">
<CCol xs="12" md="9"> <CCol md="3">
{device.serialNumber} <CLabel>{t('configuration.type')} : </CLabel>
</CCol> </CCol>
</CFormGroup> <CCol xs="12" md="9">
<CFormGroup row> {device.deviceType}
<CCol md="3"> </CCol>
<CLabel>{t('configuration.type')} : </CLabel> </CRow>
</CCol> <CRow className="mt-2">
<CCol xs="12" md="9"> <CCol md="3">
{device.deviceType} <CLabel>{t('firmware.revision')} : </CLabel>
</CCol> </CCol>
</CFormGroup> <CCol xs="12" md="9">
<CFormGroup row> {device.firmware}
<CCol md="3"> </CCol>
<CLabel>{t('configuration.last_configuration_change')} : </CLabel> </CRow>
</CCol> <CRow className="mt-2">
<CCol xs="12" md="9"> <CCol md="3">
{prettyDate(device.lastConfigurationChange)} <CLabel>{t('configuration.last_configuration_change')} : </CLabel>
</CCol> </CCol>
</CFormGroup> <CCol xs="12" md="9">
<CFormGroup row> {prettyDate(device.lastConfigurationChange)}
<CCol md="3"> </CCol>
<CLabel>{t('common.mac')} :</CLabel> </CRow>
</CCol> <CRow className="mt-2">
<CCol xs="12" md="9"> <CCol md="3">
{device.macAddress} <CLabel>{t('common.mac')} :</CLabel>
</CCol> </CCol>
</CFormGroup> <CCol xs="12" md="9">
<CFormGroup row> {device.macAddress}
<CCol md="3"> </CCol>
<CLabel>{t('configuration.created')} : </CLabel> </CRow>
</CCol> <CRow className="mt-2 mb-4">
<CCol xs="12" md="9"> <CCol md="3">
{prettyDate(device.createdTimestamp)} <CLabel className="align-middle">{t('configuration.device_password')} : </CLabel>
</CCol> </CCol>
</CFormGroup> <CCol xs="12" md="2">
<CFormGroup row> {getPassword()}
</CCol>
<CCol md="7">
<HideTextButton t={t} toggle={toggleShowPassword} show={showPassword} />
<CopyToClipboardButton
t={t}
size="sm"
content={device?.devicePassword === '' ? 'openwifi' : device.devicePassword}
/>
</CCol>
</CRow>
<NotesTable
t={t}
notes={device.notes}
loading={loading}
addNote={saveNote}
descriptionColumn={false}
/>
<CCollapse show={collapse}>
<CRow className="mt-2">
<CCol md="3"> <CCol md="3">
<CLabel>{t('configuration.last_configuration_download')} : </CLabel> <CLabel>{t('configuration.last_configuration_download')} : </CLabel>
</CCol> </CCol>
<CCol xs="12" md="9"> <CCol xs="12" md="9">
{prettyDate(device.lastConfigurationDownload)} {prettyDate(device.lastConfigurationDownload)}
</CCol> </CCol>
</CFormGroup> </CRow>
<CFormGroup row> <CRow className="mt-2">
<CCol md="3"> <CCol md="3">
<CLabel>{t('configuration.device_password')} : </CLabel> <CLabel>{t('common.manufacturer')} :</CLabel>
</CCol> </CCol>
<CCol xs="12" md="9"> <CCol xs="12" md="9">
{device.devicePassword === '' ? 'openwifi' : device.devicePassword} {device.manufacturer}
<CPopover content={t('common.copy_to_clipboard')}>
<CButton onClick={copyPasswordToClipboard} size="sm">
<CIcon content={cilClone} />
</CButton>
</CPopover>
{copyPasswordSuccess}
</CCol> </CCol>
</CFormGroup> </CRow>
<CCollapse show={collapse}> <CRow className="mt-2">
<CFormGroup row> <CCol md="3">
<CCol md="3"> <CLabel>{t('configuration.created')} : </CLabel>
<CLabel>{t('common.manufacturer')} :</CLabel> </CCol>
</CCol> <CCol xs="12" md="9">
<CCol xs="12" md="9"> {prettyDate(device.createdTimestamp)}
{device.manufacturer} </CCol>
</CCol> </CRow>
</CFormGroup> <CRow className="mt-2">
<CFormGroup row> <CCol md="3">
<CCol md="3"> <CLabel>{t('configuration.owner')} :</CLabel>
<CLabel htmlFor="text-input">{t('configuration.notes')} :</CLabel> </CCol>
</CCol> <CCol xs="12" md="9">
<CCol xs="12" md="9"> {device.owner}
<CInput id="text-input" name="text-input" placeholder={device.notes} /> </CCol>
</CCol> </CRow>
</CFormGroup> <CRow className="mt-2">
<CFormGroup row> <CCol md="3">
<CCol md="3"> <CLabel>{t('configuration.location')} :</CLabel>
<CLabel>{t('configuration.owner')} :</CLabel> </CCol>
</CCol> <CCol xs="12" md="9">
<CCol xs="12" md="9"> {device.location}
{device.owner} </CCol>
</CCol> </CRow>
</CFormGroup> </CCollapse>
<CFormGroup row> <CCardFooter>
<CCol md="3"> <CButton show={collapse ? 'true' : 'false'} onClick={toggle} block>
<CLabel>{t('configuration.location')} :</CLabel> <CIcon
</CCol> className="text-dark"
<CCol xs="12" md="9"> name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
{device.location} size="lg"
</CCol> />
</CFormGroup> </CButton>
</CCollapse> </CCardFooter>
<CCardFooter>
<CButton show={collapse ? 'true' : 'false'} onClick={toggle} block>
<CIcon
className={styles.blackIcon}
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
size="lg"
/>
</CButton>
</CCardFooter>
</CForm>
</CCardBody> </CCardBody>
</CCard> </CCard>
<DeviceConfigurationModal show={showModal} toggle={toggleModal} configuration={device} /> <DeviceConfigurationModal show={showModal} toggle={toggleModal} configuration={device} />
@@ -221,8 +261,4 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
); );
}; };
DeviceConfiguration.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceConfiguration; export default DeviceConfiguration;

View File

@@ -1,7 +0,0 @@
.alignRight {
float: right;
}
.blackIcon {
color: black;
}

View File

@@ -0,0 +1,112 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { DeviceFirmwareModal as Modal, useAuth } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance';
import { useTranslation } from 'react-i18next';
const DeviceFirmwareModal = ({
device,
show,
toggleFirmwareModal,
setUpgradeStatus,
upgradeStatus,
}) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const [loading, setLoading] = useState(false);
const [firmwareVersions, setFirmwareVersions] = useState([]);
const getFirmwareList = () => {
setLoading(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.get(`${endpoints.ucentralfms}/api/v1/firmwares?deviceType=${device.compatible}`, {
headers,
})
.then((response) => {
const sortedFirmware = response.data.firmwares.sort((a, b) => {
const firstDate = a.imageDate;
const secondDate = b.imageDate;
if (firstDate < secondDate) return 1;
return firstDate > secondDate ? -1 : 0;
});
setFirmwareVersions(sortedFirmware);
setLoading(false);
})
.catch(() => {
setLoading(false);
});
};
const upgradeToVersion = (uri) => {
setUpgradeStatus({
loading: true,
});
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
const parameters = {
serialNumber: device.serialNumber,
when: 0,
uri,
};
axiosInstance
.post(`${endpoints.ucentralgw}/api/v1/device/${device.serialNumber}/upgrade`, parameters, {
headers,
})
.then((response) => {
setUpgradeStatus({
loading: false,
result: {
success: response.data.errorCode === 0,
error: response.data.errorCode === 0 ? '' : t('firmware.error_fetching_latest'),
},
});
})
.catch(() => {
setUpgradeStatus({
loading: false,
result: {
success: false,
error: t('common.general_error'),
},
});
});
};
useEffect(() => {
if (show && device.compatible) getFirmwareList();
}, [device, show]);
return (
<Modal
t={t}
device={device}
show={show}
toggle={toggleFirmwareModal}
firmwareVersions={firmwareVersions}
upgradeToVersion={upgradeToVersion}
loading={loading}
upgradeStatus={upgradeStatus}
/>
);
};
DeviceFirmwareModal.propTypes = {
device: PropTypes.instanceOf(Object).isRequired,
show: PropTypes.bool.isRequired,
toggleFirmwareModal: PropTypes.func.isRequired,
setUpgradeStatus: PropTypes.func.isRequired,
upgradeStatus: PropTypes.instanceOf(Object).isRequired,
};
export default React.memo(DeviceFirmwareModal);

View File

@@ -10,19 +10,21 @@ import {
CRow, CRow,
CCol, CCol,
CProgress, CProgress,
CPopover,
} from '@coreui/react'; } from '@coreui/react';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker'; import DatePicker from 'react-widgets/DatePicker';
import PropTypes from 'prop-types';
import { prettyDate, dateToUnix } from 'utils/helper'; import { prettyDate, dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper'; import eventBus from 'utils/eventBus';
import LoadingButton from 'components/LoadingButton'; import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
import styles from './index.module.scss'; import DeleteLogModal from 'components/DeleteLogModal';
const DeviceHealth = ({ selectedDeviceId }) => { const DeviceHealth = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [collapse, setCollapse] = useState(false); const [collapse, setCollapse] = useState(false);
const [details, setDetails] = useState([]); const [details, setDetails] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -34,6 +36,11 @@ const DeviceHealth = ({ selectedDeviceId }) => {
const [showLoadingMore, setShowLoadingMore] = useState(true); const [showLoadingMore, setShowLoadingMore] = useState(true);
const [sanityLevel, setSanityLevel] = useState(null); const [sanityLevel, setSanityLevel] = useState(null);
const [barColor, setBarColor] = useState('gradient-dark'); const [barColor, setBarColor] = useState('gradient-dark');
const [showDeleteModal, setShowDeleteModal] = useState(false);
const toggleDeleteModal = () => {
setShowDeleteModal(!showDeleteModal);
};
const toggle = (e) => { const toggle = (e) => {
setCollapse(!collapse); setCollapse(!collapse);
@@ -60,7 +67,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
const options = { const options = {
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${currentToken}`,
}, },
params: { params: {
limit: logLimit, limit: logLimit,
@@ -77,7 +84,12 @@ const DeviceHealth = ({ selectedDeviceId }) => {
} }
axiosInstance axiosInstance
.get(`/device/${encodeURIComponent(selectedDeviceId)}/healthchecks${extraParams}`, options) .get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(
deviceSerialNumber,
)}/healthchecks${extraParams}`,
options,
)
.then((response) => { .then((response) => {
setHealthChecks(response.data.values); setHealthChecks(response.data.values);
}) })
@@ -120,7 +132,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
]; ];
useEffect(() => { useEffect(() => {
if (selectedDeviceId) { if (deviceSerialNumber) {
setLogLimit(25); setLogLimit(25);
setLoadingMore(false); setLoadingMore(false);
setShowLoadingMore(true); setShowLoadingMore(true);
@@ -128,7 +140,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
setEnd(''); setEnd('');
getDeviceHealth(); getDeviceHealth();
} }
}, [selectedDeviceId]); }, [deviceSerialNumber]);
useEffect(() => { useEffect(() => {
if (logLimit !== 25) { if (logLimit !== 25) {
@@ -160,65 +172,88 @@ const DeviceHealth = ({ selectedDeviceId }) => {
}, [healthChecks]); }, [healthChecks]);
useEffect(() => { useEffect(() => {
if (selectedDeviceId && start !== '' && end !== '') { if (deviceSerialNumber && start !== '' && end !== '') {
getDeviceHealth(); getDeviceHealth();
} else if (selectedDeviceId && start === '' && end === '') { } else if (deviceSerialNumber && start === '' && end === '') {
getDeviceHealth(); getDeviceHealth();
} }
}, [start, end, selectedDeviceId]); }, [start, end, deviceSerialNumber]);
useEffect(() => {
eventBus.on('deletedHealth', () => getDeviceHealth());
return () => {
eventBus.remove('deletedHealth');
};
}, []);
return ( return (
<CWidgetDropdown <CWidgetDropdown
header={sanityLevel ? `${sanityLevel}%` : t('common.unknown')} header={t('health.title')}
text={t('health.title')} text={sanityLevel ? `${sanityLevel}%` : t('common.unknown')}
value={sanityLevel ?? 100} value={sanityLevel ?? 100}
color={barColor} color={barColor}
inverse="true" inverse="true"
footerSlot={ footerSlot={
<div className={styles.footer}> <div className="p-4">
<CProgress className={styles.progressBar} color="white" value={sanityLevel ?? 0} /> <CProgress className="mb-3" color="white" value={sanityLevel ?? 0} />
<CCollapse show={collapse}> <CCollapse show={collapse}>
<CRow className={styles.spacedRow}> <div className="text-right">
<CPopover content={t('common.delete')}>
<CButton
color="light"
shape="square"
size="sm"
onClick={() => {
toggleDeleteModal();
}}
>
<CIcon name="cilTrash" size="lg" />
</CButton>
</CPopover>
</div>
<CRow className="mb-3">
<CCol> <CCol>
{t('common.from')}: {t('common.from')}
:
<DatePicker includeTime onChange={(date) => modifyStart(date)} /> <DatePicker includeTime onChange={(date) => modifyStart(date)} />
</CCol> </CCol>
<CCol> <CCol>
{t('common.to')}: {t('common.to')}
:
<DatePicker includeTime onChange={(date) => modifyEnd(date)} /> <DatePicker includeTime onChange={(date) => modifyEnd(date)} />
</CCol> </CCol>
</CRow> </CRow>
<CCard> <CCard className="p-0">
<div className={[styles.scrollable, 'overflow-auto'].join(' ')}> <div className="overflow-auto" style={{ height: '250px' }}>
<CDataTable <CDataTable
border
items={healthChecks ?? []} items={healthChecks ?? []}
fields={columns} fields={columns}
className={styles.dataTable} className="text-white"
loading={loading} loading={loading}
sorterValue={{ column: 'recorded', desc: 'true' }} sorterValue={{ column: 'recorded', desc: 'true' }}
scopedSlots={{ scopedSlots={{
recorded: (item) => <td>{prettyDate(item.recorded)}</td>, UUID: (item) => <td className="align-middle">{item.UUID}</td>,
sanity: (item) => <td>{`${item.sanity}%`}</td>, recorded: (item) => (
show_details: (item, index) => { <td className="align-middle">{prettyDate(item.recorded)}</td>
if (item.sanity === 100) { ),
return <></>; sanity: (item) => <td className="align-middle">{`${item.sanity}%`}</td>,
} show_details: (item, index) => (
return ( <td className="align-middle">
<td className="py-2"> <CButton
<CButton color="primary"
color="primary" variant={details.includes(index) ? '' : 'outline'}
variant={details.includes(index) ? '' : 'outline'} shape="square"
shape="square" size="sm"
size="sm" onClick={() => {
onClick={() => { toggleDetails(index);
toggleDetails(index); }}
}} >
> <CIcon name="cilList" size="lg" />
<CIcon name="cilList" size="lg" /> </CButton>
</CButton> </td>
</td> ),
);
},
details: (item, index) => ( details: (item, index) => (
<CCollapse show={details.includes(index)}> <CCollapse show={details.includes(index)}>
<CCardBody> <CCardBody>
@@ -229,8 +264,8 @@ const DeviceHealth = ({ selectedDeviceId }) => {
), ),
}} }}
/> />
<CRow className={styles.loadMoreRow}> {showLoadingMore && (
{showLoadingMore && ( <div className="mb-3">
<LoadingButton <LoadingButton
label={t('common.view_more')} label={t('common.view_more')}
isLoadingLabel={t('common.loading_more_ellipsis')} isLoadingLabel={t('common.loading_more_ellipsis')}
@@ -238,26 +273,28 @@ const DeviceHealth = ({ selectedDeviceId }) => {
action={showMoreLogs} action={showMoreLogs}
variant="outline" variant="outline"
/> />
)} </div>
</CRow> )}
</div> </div>
</CCard> </CCard>
</CCollapse> </CCollapse>
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block> <CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
<CIcon <CIcon
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'} name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
className={styles.icon} className="text-white"
size="lg" size="lg"
/> />
</CButton> </CButton>
<DeleteLogModal
serialNumber={deviceSerialNumber}
object="healthchecks"
show={showDeleteModal}
toggle={toggleDeleteModal}
/>
</div> </div>
} }
/> />
); );
}; };
DeviceHealth.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceHealth; export default DeviceHealth;

View File

@@ -1,27 +0,0 @@
.icon {
color: white;
}
.dataTable {
color: white;
}
.footer {
padding: 20px;
}
.progressBar {
margin-bottom: 20px;
}
.spacedRow {
margin-bottom: 10px;
}
.loadMoreRow {
margin-bottom: 1%;
}
.scrollable {
height: 250px;
}

View File

@@ -1,87 +1,136 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import {
CBadge,
CCardBody,
CDataTable,
CButton,
CLink,
CCard,
CCardHeader,
CRow,
CCol,
CPopover,
} from '@coreui/react';
import ReactPaginate from 'react-paginate';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Select from 'react-select'; import { useHistory, useLocation } from 'react-router-dom';
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 axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import { cleanBytesString, cropStringWithEllipsis } from 'utils/helper';
import meshIcon from 'assets/icons/Mesh.png';
import apIcon from 'assets/icons/AP.png';
import internetSwitch from 'assets/icons/Switch.png';
import iotIcon from 'assets/icons/IotIcon.png';
import { getItem, setItem } from 'utils/localStorageHelper'; import { getItem, setItem } from 'utils/localStorageHelper';
import styles from './index.module.scss'; import DeviceSearchBar from 'components/DeviceSearchBar';
import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
import FirmwareHistoryModal from 'components/FirmwareHistoryModal';
import { DeviceListTable, useAuth, useToast } from 'ucentral-libs';
import meshIcon from '../../assets/icons/Mesh.png';
import apIcon from '../../assets/icons/AP.png';
import internetSwitch from '../../assets/icons/Switch.png';
import iotIcon from '../../assets/icons/IotIcon.png';
const DeviceList = () => { const DeviceList = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [loadedSerials, setLoadedSerials] = useState(false); const { addToast } = useToast();
const [serialNumbers, setSerialNumbers] = useState([]); const history = useHistory();
const [page, setPage] = useState(0); const { search } = useLocation();
const page = new URLSearchParams(search).get('page');
const { currentToken, endpoints } = useAuth();
const [upgradeStatus, setUpgradeStatus] = useState({
loading: false,
});
const [deleteStatus, setDeleteStatus] = useState({
loading: false,
});
const [deviceCount, setDeviceCount] = useState(0);
const [pageCount, setPageCount] = useState(0); const [pageCount, setPageCount] = useState(0);
const [devicesPerPage, setDevicesPerPage] = useState(getItem('devicesPerPage') || 10); const [devicesPerPage, setDevicesPerPage] = useState(getItem('devicesPerPage') || '10');
const [devices, setDevices] = useState([]); const [devices, setDevices] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [showHistoryModal, setHistoryModal] = useState(false);
const [showFirmwareModal, setShowFirmwareModal] = useState(false);
const [firmwareDevice, setFirmwareDevice] = useState({
deviceType: '',
serialNumber: '',
});
const getSerialNumbers = () => { const deviceIcons = {
const token = getToken(); meshIcon,
apIcon,
internetSwitch,
iotIcon,
};
const toggleFirmwareModal = (device) => {
setShowFirmwareModal(!showFirmwareModal);
if (device !== undefined) setFirmwareDevice(device);
};
const toggleHistoryModal = (device) => {
setHistoryModal(!showHistoryModal);
if (device !== undefined) setFirmwareDevice(device);
};
const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => {
setLoading(true); setLoading(true);
const headers = { const options = {
Accept: 'application/json', headers: {
Authorization: `Bearer ${token}`, Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
}; };
let fullDevices;
axiosInstance axiosInstance
.get('/devices?serialOnly=true', { .get(
headers, `${
endpoints.ucentralgw
}/api/v1/devices?deviceWithStatus=true&limit=${devicePerPage}&offset=${
devicePerPage * selectedPage + 1
}`,
options,
)
.then((response) => {
fullDevices = response.data.devicesWithStatus;
const serialsToGet = fullDevices.map((device) => device.serialNumber);
return axiosInstance.get(
`${endpoints.ucentralfms}/api/v1/firmwareAge?select=${serialsToGet}`,
options,
);
}) })
.then((response) => { .then((response) => {
setSerialNumbers(response.data.serialNumbers); fullDevices = fullDevices.map((device, index) => {
setLoadedSerials(true); const foundAgeDate = response.data.ages[index].age !== undefined;
if (foundAgeDate) {
return {
...device,
firmwareInfo: {
age: response.data.ages[index].age,
latest: response.data.ages[index].latest,
},
};
}
return device;
});
setDevices(fullDevices);
setLoading(false);
}) })
.catch(() => { .catch(() => {
setLoading(false); setLoading(false);
}); });
}; };
const getDeviceInformation = () => { const getCount = () => {
const token = getToken();
setLoading(true); setLoading(true);
const headers = { const headers = {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${token}`, Authorization: `Bearer ${currentToken}`,
}; };
const startIndex = page * devicesPerPage;
const endIndex = parseInt(startIndex, 10) + parseInt(devicesPerPage, 10);
const serialsToGet = serialNumbers
.slice(startIndex, endIndex)
.map((x) => encodeURIComponent(x))
.join(',');
axiosInstance axiosInstance
.get(`/devices?deviceWithStatus=true&select=${serialsToGet}`, { .get(`${endpoints.ucentralgw}/api/v1/devices?countOnly=true`, {
headers, headers,
}) })
.then((response) => { .then((response) => {
setDevices(response.data.devicesWithStatus); const devicesCount = response.data.count;
setLoading(false); const pagesCount = Math.ceil(devicesCount / devicesPerPage);
setPageCount(pagesCount);
setDeviceCount(devicesCount);
let selectedPage = page;
if (page >= pagesCount) {
history.push(`/devices?page=${pagesCount - 1}`);
selectedPage = pagesCount - 1;
}
getDeviceInformation(selectedPage);
}) })
.catch(() => { .catch(() => {
setLoading(false); setLoading(false);
@@ -89,18 +138,22 @@ const DeviceList = () => {
}; };
const refreshDevice = (serialNumber) => { const refreshDevice = (serialNumber) => {
const token = getToken();
setLoading(true); setLoading(true);
const headers = { const options = {
Accept: 'application/json', headers: {
Authorization: `Bearer ${token}`, Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
}; };
axiosInstance axiosInstance
.get(`/devices?deviceWithStatus=true&select=${encodeURIComponent(serialNumber)}`, { .get(
headers, `${endpoints.ucentralgw}/api/v1/devices?deviceWithStatus=true&select=${encodeURIComponent(
}) serialNumber,
)}`,
options,
)
.then((response) => { .then((response) => {
const device = response.data.devicesWithStatus[0]; const device = response.data.devicesWithStatus[0];
const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber); const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber);
@@ -117,314 +170,215 @@ const DeviceList = () => {
const updateDevicesPerPage = (value) => { const updateDevicesPerPage = (value) => {
setItem('devicesPerPage', value); setItem('devicesPerPage', value);
setDevicesPerPage(value); setDevicesPerPage(value);
const newPageCount = Math.ceil(deviceCount / value);
setPageCount(newPageCount);
let selectedPage = page;
if (page >= newPageCount) {
history.push(`/devices?page=${newPageCount - 1}`);
selectedPage = newPageCount - 1;
}
getDeviceInformation(selectedPage, value);
}; };
const updatePageCount = ({ selected: selectedPage }) => { const updatePageCount = ({ selected: selectedPage }) => {
setPage(selectedPage); history.push(`/devices?page=${selectedPage}`);
getDeviceInformation(selectedPage);
};
const upgradeToLatest = (device) => {
setUpgradeStatus({
loading: true,
});
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(
`${endpoints.ucentralfms}/api/v1/firmwares?deviceType=${device.compatible}&latestOnly=true`,
options,
)
.then((response) => {
if (response.data.uri) {
const parameters = {
serialNumber: device.serialNumber,
when: 0,
uri: response.data.uri,
};
return axiosInstance.post(
`${endpoints.ucentralgw}/api/v1/device/${device.serialNumber}/upgrade`,
parameters,
options,
);
}
setUpgradeStatus({
loading: false,
result: {
success: false,
error: t('firmware.error_fetching_latest'),
},
});
return null;
})
.then((response) => {
if (response) {
setUpgradeStatus({
loading: false,
result: {
success: response.data.errorCode === 0,
error: response.data.errorCode === 0 ? '' : t('firmware.error_fetching_latest'),
},
});
}
})
.catch(() => {
setUpgradeStatus({
loading: false,
result: {
success: false,
error: t('common.general_error'),
},
});
});
};
const connectRtty = (serialNumber) => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}/rtty`,
options,
)
.then((response) => {
const url = `https://${response.data.server}:${response.data.viewport}/connect/${response.data.connectionId}`;
const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
if (newWindow) newWindow.opener = null;
})
.catch(() => {
addToast({
title: t('common.error'),
body: t('common.unable_to_connect'),
color: 'danger',
autohide: true,
});
});
};
const deleteDevice = (serialNumber) => {
setDeleteStatus({
loading: true,
});
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.delete(`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}`, options)
.then(() => {
addToast({
title: t('common.success'),
body: t('common.device_deleted'),
color: 'success',
autohide: true,
});
getCount();
})
.catch(() => {
addToast({
title: t('common.error'),
body: t('common.unable_to_delete'),
color: 'danger',
autohide: true,
});
})
.finally(() => {
setDeleteStatus({
loading: false,
});
});
}; };
useEffect(() => { useEffect(() => {
getSerialNumbers(); if (page === undefined || page === null || Number.isNaN(page)) {
history.push(`/devices?page=0`);
}
getCount();
}, []); }, []);
useEffect(() => { useEffect(() => {
if (loadedSerials) getDeviceInformation(); if (upgradeStatus.result !== undefined) {
}, [serialNumbers, page, devicesPerPage, loadedSerials]); addToast({
title: upgradeStatus.result.success ? t('common.success') : t('common.error'),
useEffect(() => { body: upgradeStatus.result.success
if (loadedSerials) { ? t('firmware.upgrade_command_submitted')
const count = Math.ceil(serialNumbers.length / devicesPerPage); : upgradeStatus.result.error,
setPageCount(count); color: upgradeStatus.result.success ? 'success' : 'danger',
autohide: true,
});
setUpgradeStatus({
loading: false,
});
setShowFirmwareModal(false);
} }
}, [devicesPerPage, loadedSerials]); }, [upgradeStatus]);
return ( return (
<DeviceListDisplay <div>
devices={devices} <DeviceListTable
loading={loading} currentPage={page}
updateDevicesPerPage={updateDevicesPerPage} t={t}
devicesPerPage={devicesPerPage} searchBar={<DeviceSearchBar />}
pageCount={pageCount} devices={devices}
updatePage={updatePageCount} loading={loading}
pageRangeDisplayed={5} updateDevicesPerPage={updateDevicesPerPage}
refreshDevice={refreshDevice} devicesPerPage={devicesPerPage}
t={t} pageCount={pageCount}
/> updatePage={updatePageCount}
pageRangeDisplayed={5}
refreshDevice={refreshDevice}
toggleFirmwareModal={toggleFirmwareModal}
toggleHistoryModal={toggleHistoryModal}
upgradeToLatest={upgradeToLatest}
upgradeStatus={upgradeStatus}
deviceIcons={deviceIcons}
connectRtty={connectRtty}
deleteDevice={deleteDevice}
deleteStatus={deleteStatus}
/>
<DeviceFirmwareModal
endpoints={endpoints}
currentToken={currentToken}
device={firmwareDevice}
show={showFirmwareModal}
toggleFirmwareModal={toggleFirmwareModal}
setUpgradeStatus={setUpgradeStatus}
upgradeStatus={upgradeStatus}
/>
<FirmwareHistoryModal
serialNumber={firmwareDevice.serialNumber}
show={showHistoryModal}
toggle={toggleHistoryModal}
/>
</div>
); );
}; };
const DeviceListDisplay = ({
devices,
devicesPerPage,
loading,
updateDevicesPerPage,
pageCount,
updatePage,
refreshDevice,
t,
}) => {
const columns = [
{ key: 'deviceType', label: '', filter: false, sorter: false, _style: { width: '5%' } },
{ key: 'verifiedCertificate', label: t('common.certificate'), _style: { width: '1%' } },
{ key: 'serialNumber', label: t('common.serial_number'), _style: { width: '5%' } },
{ key: 'UUID', label: t('common.config_id'), _style: { width: '5%' } },
{ key: 'firmware', label: t('common.firmware'), filter: false, _style: { width: '20%' } },
{ key: 'compatible', label: t('common.compatible'), filter: false, _style: { width: '20%' } },
{ key: 'txBytes', label: 'Tx', filter: false, _style: { width: '12%' } },
{ key: 'rxBytes', label: 'Rx', filter: false, _style: { width: '12%' } },
{ key: 'ipAddress', label: t('common.ip_address'), _style: { width: '16%' } },
{
key: 'show_details',
label: '',
_style: { width: '3%' },
sorter: false,
filter: false,
},
{
key: 'refresh',
label: '',
_style: { width: '2%' },
sorter: false,
filter: false,
},
];
const 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" />;
}
if (deviceType === 'MESH') {
return <img src={meshIcon} className={styles.icon} alt="MESH" />;
}
if (deviceType === 'SWITCH') {
return <img src={internetSwitch} className={styles.icon} alt="SWITCH" />;
}
if (deviceType === 'IOT') {
return <img src={iotIcon} className={styles.icon} alt="SWITCH" />;
}
return null;
};
const getCertBadge = (cert) => {
if (cert === 'NO_CERTIFICATE') {
return (
<div className={styles.certificateWrapper}>
<CIcon className={styles.badge} name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
<CIcon
className={styles.badCertificate}
name="cil-ban"
content={cilBan}
size="3xl"
alt="AP"
/>
</div>
);
}
let color = 'transparent';
switch (cert) {
case 'VALID_CERTIFICATE':
color = 'danger';
break;
case 'MISMATCH_SERIAL':
return (
<CBadge color={color} className={styles.mismatchBackground}>
<CIcon name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
</CBadge>
);
case 'VERIFIED':
color = 'success';
break;
default:
return (
<div className={styles.certificateWrapper}>
<CIcon
className={styles.badge}
name="cil-badge"
content={cilBadge}
size="2xl"
alt="AP"
/>
<CIcon
className={styles.badCertificate}
name="cil-ban"
content={cilBan}
size="3xl"
alt="AP"
/>
</div>
);
}
return (
<CBadge color={color}>
<CIcon name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
</CBadge>
);
};
const getStatusBadge = (status) => {
if (status) {
return 'success';
}
return 'danger';
};
return (
<>
<CCard>
<CCardHeader>
<CRow>
<CCol />
<CCol xs={2}>
<Select
isClearable={false}
options={selectOptions}
defaultValue={{ value: devicesPerPage, label: devicesPerPage }}
onChange={(value) => updateDevicesPerPage(value.value)}
/>
</CCol>
</CRow>
</CCardHeader>
<CCardBody>
<CDataTable
items={devices ?? []}
fields={columns}
hover
loading={loading}
scopedSlots={{
serialNumber: (item) => (
<td className={styles.column}>
<CLink
className="c-subheader-nav-link"
aria-current="page"
to={() => `/devices/${item.serialNumber}`}
>
{item.serialNumber}
</CLink>
</td>
),
deviceType: (item) => (
<td className={styles.column}>
<CPopover
content={item.connected ? t('common.connected') : t('common.not_connected')}
placement="top"
>
<CBadge color={getStatusBadge(item.connected)}>
{getDeviceIcon(item.deviceType) ?? item.deviceType}
</CBadge>
</CPopover>
</td>
),
verifiedCertificate: (item) => (
<td className={styles.column}>
<CPopover
content={item.verifiedCertificate ?? t('common.unknown')}
placement="top"
>
{getCertBadge(item.verifiedCertificate)}
</CPopover>
</td>
),
firmware: (item) => (
<td>
<CPopover
content={item.firmware ? item.firmware : t('common.na')}
placement="top"
>
<p>{cropStringWithEllipsis(item.firmware, 16)}</p>
</CPopover>
</td>
),
compatible: (item) => (
<td>
<CPopover
content={item.compatible ? item.compatible : t('common.na')}
placement="top"
>
<p>{cropStringWithEllipsis(item.compatible, 16)}</p>
</CPopover>
</td>
),
txBytes: (item) => <td>{cleanBytesString(item.txBytes)}</td>,
rxBytes: (item) => <td>{cleanBytesString(item.rxBytes)}</td>,
ipAddress: (item) => (
<td>
<CPopover
content={item.ipAddress ? item.ipAddress : t('common.na')}
placement="top"
>
<p>{cropStringWithEllipsis(item.ipAddress, 20)}</p>
</CPopover>
</td>
),
refresh: (item) => (
<td className="py-2">
<CPopover content={t('common.refresh_device')}>
<CButton
onClick={() => refreshDevice(item.serialNumber)}
color="primary"
variant="outline"
size="sm"
>
<CIcon name="cil-sync" content={cilSync} size="sm" />
</CButton>
</CPopover>
</td>
),
show_details: (item) => (
<td className="py-2">
<CPopover content={t('configuration.details')}>
<CLink
className="c-subheader-nav-link"
aria-current="page"
to={() => `/devices/${item.serialNumber}`}
>
<CButton color="primary" variant="outline" shape="square" size="sm">
<CIcon name="cil-info" content={cilInfo} size="sm" />
</CButton>
</CLink>
</CPopover>
</td>
),
}}
/>
<ReactPaginate
previousLabel="← Previous"
nextLabel="Next →"
pageCount={pageCount}
onPageChange={updatePage}
breakClassName="page-item"
breakLinkClassName="page-link"
containerClassName="pagination"
pageClassName="page-item"
pageLinkClassName="page-link"
previousClassName="page-item"
previousLinkClassName="page-link"
nextClassName="page-item"
nextLinkClassName="page-link"
activeClassName="active"
/>
</CCardBody>
</CCard>
</>
);
};
DeviceListDisplay.propTypes = {
devices: PropTypes.instanceOf(Array).isRequired,
updateDevicesPerPage: PropTypes.func.isRequired,
pageCount: PropTypes.number.isRequired,
updatePage: PropTypes.func.isRequired,
devicesPerPage: PropTypes.string.isRequired,
refreshDevice: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
};
export default DeviceList; export default DeviceList;

View File

@@ -1,29 +0,0 @@
.icon {
height: 32px;
width: 32px;
}
.column {
text-align: center;
}
.certificateWrapper {
position: relative;
}
.badge {
position: absolute;
left: 31%;
margin-top: 8%;
}
.badCertificate {
position: absolute;
z-index: 99;
left: 22%;
color: #e55353;
}
.mismatchBackground {
background-color: #ffff5c;
}

View File

@@ -9,19 +9,21 @@ import {
CDataTable, CDataTable,
CCard, CCard,
CCardBody, CCardBody,
CPopover,
} from '@coreui/react'; } from '@coreui/react';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker'; import DatePicker from 'react-widgets/DatePicker';
import PropTypes from 'prop-types';
import { prettyDate, dateToUnix } from 'utils/helper'; import { prettyDate, dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper'; import eventBus from 'utils/eventBus';
import LoadingButton from 'components/LoadingButton'; import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
import styles from './index.module.scss'; import DeleteLogModal from 'components/DeleteLogModal';
const DeviceLogs = ({ selectedDeviceId }) => { const DeviceLogs = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [collapse, setCollapse] = useState(false); const [collapse, setCollapse] = useState(false);
const [details, setDetails] = useState([]); const [details, setDetails] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -31,6 +33,11 @@ const DeviceLogs = ({ selectedDeviceId }) => {
const [logLimit, setLogLimit] = useState(25); const [logLimit, setLogLimit] = useState(25);
const [loadingMore, setLoadingMore] = useState(false); const [loadingMore, setLoadingMore] = useState(false);
const [showLoadingMore, setShowLoadingMore] = useState(true); const [showLoadingMore, setShowLoadingMore] = useState(true);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const toggleDeleteModal = () => {
setShowDeleteModal(!showDeleteModal);
};
const toggle = (e) => { const toggle = (e) => {
setCollapse(!collapse); setCollapse(!collapse);
@@ -57,7 +64,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
const options = { const options = {
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${currentToken}`,
}, },
params: { params: {
limit: logLimit, limit: logLimit,
@@ -74,7 +81,12 @@ const DeviceLogs = ({ selectedDeviceId }) => {
} }
axiosInstance axiosInstance
.get(`/device/${encodeURIComponent(selectedDeviceId)}/logs${extraParams}`, options) .get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(
deviceSerialNumber,
)}/logs${extraParams}`,
options,
)
.then((response) => { .then((response) => {
setLogs(response.data.values); setLogs(response.data.values);
}) })
@@ -117,7 +129,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
]; ];
useEffect(() => { useEffect(() => {
if (selectedDeviceId) { if (deviceSerialNumber) {
setLogLimit(25); setLogLimit(25);
setLoadingMore(false); setLoadingMore(false);
setShowLoadingMore(true); setShowLoadingMore(true);
@@ -125,7 +137,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
setEnd(''); setEnd('');
getLogs(); getLogs();
} }
}, [selectedDeviceId]); }, [deviceSerialNumber]);
useEffect(() => { useEffect(() => {
if (logLimit !== 25) { if (logLimit !== 25) {
@@ -142,97 +154,121 @@ const DeviceLogs = ({ selectedDeviceId }) => {
}, [logs]); }, [logs]);
useEffect(() => { useEffect(() => {
if (selectedDeviceId && start !== '' && end !== '') { if (deviceSerialNumber && start !== '' && end !== '') {
getLogs(); getLogs();
} else if (selectedDeviceId && start === '' && end === '') { } else if (deviceSerialNumber && start === '' && end === '') {
getLogs(); getLogs();
} }
}, [start, end, selectedDeviceId]); }, [start, end, deviceSerialNumber]);
useEffect(() => {
eventBus.on('deletedLogs', () => getLogs());
return () => {
eventBus.remove('deletedLogs');
};
}, []);
return ( return (
<CWidgetDropdown <div>
inverse="true" <CWidgetDropdown
color="gradient-info" inverse="true"
header={t('device_logs.title')} color="gradient-info"
footerSlot={ header={t('device_logs.title')}
<div className={styles.footer}> footerSlot={
<CCollapse show={collapse}> <div className="p-4">
<CRow className={styles.datepickerRow}> <CCollapse show={collapse}>
<CCol> <div className="text-right">
{t('common.from')} <CPopover content={t('common.delete')}>
<DatePicker includeTime onChange={(date) => modifyStart(date)} /> <CButton
</CCol> color="light"
<CCol> shape="square"
{t('common.to')} size="sm"
<DatePicker includeTime onChange={(date) => modifyEnd(date)} /> onClick={() => {
</CCol> toggleDeleteModal();
</CRow> }}
<CCard> >
<div className={[styles.scrollableCard, 'overflow-auto'].join(' ')}> <CIcon name="cilTrash" size="lg" />
<CDataTable </CButton>
items={logs ?? []} </CPopover>
fields={columns}
loading={loading}
className={styles.whiteIcon}
sorterValue={{ column: 'recorded', desc: 'true' }}
scopedSlots={{
recorded: (item) => <td>{prettyDate(item.recorded)}</td>,
show_details: (item, index) => (
<td className="py-2">
<CButton
color="primary"
variant={details.includes(index) ? '' : 'outline'}
shape="square"
size="sm"
onClick={() => {
toggleDetails(index);
}}
>
<CIcon name="cilList" size="lg" />
</CButton>
</td>
),
details: (item, index) => (
<CCollapse show={details.includes(index)}>
<CCardBody>
<h5>{t('common.details')}</h5>
<div>{getDetails(index, item)}</div>
</CCardBody>
</CCollapse>
),
}}
/>
<CRow className={styles.loadMoreRow}>
{showLoadingMore && (
<LoadingButton
label={t('common.view_more')}
isLoadingLabel={t('common.loading_more_ellipsis')}
isLoading={loadingMore}
action={showMoreLogs}
variant="outline"
/>
)}
</CRow>
</div> </div>
</CCard> <CRow className="mb-3">
</CCollapse> <CCol>
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block> {t('common.from')}
<CIcon <DatePicker includeTime onChange={(date) => modifyStart(date)} />
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'} </CCol>
className={styles.whiteIcon} <CCol>
size="lg" {t('common.to')}
/> <DatePicker includeTime onChange={(date) => modifyEnd(date)} />
</CButton> </CCol>
</div> </CRow>
} <CCard>
> <div className="overflow-auto" style={{ height: '250px' }}>
<CIcon name="cilList" className={styles.whiteIcon} size="lg" /> <CDataTable
</CWidgetDropdown> items={logs ?? []}
fields={columns}
loading={loading}
className="text-white"
sorterValue={{ column: 'recorded', desc: 'true' }}
scopedSlots={{
recorded: (item) => <td>{prettyDate(item.recorded)}</td>,
show_details: (item, index) => (
<td className="py-2">
<CButton
color="primary"
variant={details.includes(index) ? '' : 'outline'}
shape="square"
size="sm"
onClick={() => {
toggleDetails(index);
}}
>
<CIcon name="cilList" size="lg" />
</CButton>
</td>
),
details: (item, index) => (
<CCollapse show={details.includes(index)}>
<CCardBody>
<h5>{t('common.details')}</h5>
<div>{getDetails(index, item)}</div>
</CCardBody>
</CCollapse>
),
}}
/>
{showLoadingMore && (
<div className="mb-3">
<LoadingButton
label={t('common.view_more')}
isLoadingLabel={t('common.loading_more_ellipsis')}
isLoading={loadingMore}
action={showMoreLogs}
variant="outline"
/>
</div>
)}
</div>
</CCard>
</CCollapse>
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
<CIcon
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
className="text-white"
size="lg"
/>
</CButton>
</div>
}
/>
<DeleteLogModal
serialNumber={deviceSerialNumber}
object="logs"
show={showDeleteModal}
toggle={toggleDeleteModal}
/>
</div>
); );
}; };
DeviceLogs.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceLogs; export default DeviceLogs;

View File

@@ -1,19 +0,0 @@
.whiteIcon {
color: white;
}
.footer {
padding: 20px;
}
.datepickerRow {
margin-bottom: 10px;
}
.scrollableCard {
height: 250px;
}
.loadMoreRow {
margin-bottom: 1%;
}

View File

@@ -0,0 +1,71 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { useAuth, DeviceSearchBar as SearchBar } from 'ucentral-libs';
import { checkIfJson } from 'utils/helper';
const DeviceSearchBar = () => {
const { t } = useTranslation();
const history = useHistory();
const { currentToken, endpoints } = useAuth();
const [socket, setSocket] = useState(null);
const [results, setResults] = useState([]);
const [waitingSearch, setWaitingSearch] = useState('');
const search = (value) => {
if (socket.readyState === WebSocket.OPEN) {
if (value.length > 0 && value.match('^[a-fA-F0-9]+$')) {
setWaitingSearch('');
socket.send(
JSON.stringify({ command: 'serial_number_search', serial_prefix: value.toLowerCase() }),
);
} else {
setResults([]);
}
} else if (socket.readyState !== WebSocket.CONNECTING) {
setWaitingSearch(value);
setSocket(new WebSocket(`${endpoints.ucentralgw.replace('https', 'wss')}/api/v1/ws`));
} else {
setWaitingSearch(value);
}
};
const closeSocket = () => {
if (socket !== null) {
socket.close();
}
};
useEffect(() => {
if (socket !== null) {
socket.onopen = () => {
socket.send(`token:${currentToken}`);
};
socket.onmessage = (event) => {
if (checkIfJson(event.data)) {
const result = JSON.parse(event.data);
if (result.command === 'serial_number_search' && result.serialNumbers) {
setResults(result.serialNumbers);
}
}
};
if (waitingSearch.length > 0) {
search(waitingSearch);
}
}
return () => closeSocket();
}, [socket]);
useEffect(() => {
if (socket === null) {
setSocket(new WebSocket(`${endpoints.ucentralgw.replace('https', 'wss')}/api/v1/ws`));
}
}, []);
return <SearchBar t={t} search={search} results={results} history={history} />;
};
export default DeviceSearchBar;

View File

@@ -0,0 +1,66 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import axiosInstance from 'utils/axiosInstance';
import { DeviceStatusCard as Card, useDevice, useAuth } from 'ucentral-libs';
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 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]);
return (
<Card
t={t}
loading={loading}
error={error}
deviceSerialNumber={deviceSerialNumber}
getData={getData}
status={status}
lastStats={lastStats}
/>
);
};
export default DeviceStatusCard;

View File

@@ -0,0 +1,230 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import axiosInstance from 'utils/axiosInstance';
import { useUser, EditUserModal as Modal, useAuth, useToast } from 'ucentral-libs';
const initialState = {
Id: {
value: '',
error: false,
editable: false,
},
changePassword: {
value: false,
error: false,
editable: false,
},
currentPassword: {
value: '',
error: false,
editable: true,
},
email: {
value: '',
error: false,
editable: false,
},
description: {
value: '',
error: false,
editable: true,
},
name: {
value: '',
error: false,
editable: true,
},
userRole: {
value: '',
error: false,
editable: true,
},
notes: {
value: [],
editable: false,
},
};
const EditUserModal = ({ show, toggle, userId, getUsers }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { addToast } = useToast();
const [loading, setLoading] = useState(false);
const [initialUser, setInitialUser] = useState({});
const [user, updateWithId, updateWithKey, setUser] = useUser(initialState);
const [policies, setPolicies] = useState({
passwordPolicy: '',
passwordPattern: '',
accessPolicy: '',
});
const getPasswordPolicy = () => {
axiosInstance
.post(`${endpoints.ucentralsec}/api/v1/oauth2?requirements=true`, {})
.then((response) => {
const newPolicies = response.data;
newPolicies.accessPolicy = `${endpoints.ucentralsec}${newPolicies.accessPolicy}`;
newPolicies.passwordPolicy = `${endpoints.ucentralsec}${newPolicies.passwordPolicy}`;
setPolicies(response.data);
})
.catch(() => {});
};
const getUser = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`${endpoints.ucentralsec}/api/v1/user/${userId}`, options)
.then((response) => {
const newUser = {};
for (const key of Object.keys(response.data)) {
if (key in initialState && key !== 'currentPassword') {
newUser[key] = {
...initialState[key],
value: response.data[key],
};
}
}
setInitialUser({ ...initialState, ...newUser });
setUser({ ...initialState, ...newUser });
})
.catch(() => {});
};
const updateUser = () => {
setLoading(true);
const parameters = {
id: userId,
};
let newData = false;
for (const key of Object.keys(user)) {
if (user[key].editable && user[key].value !== initialUser[key].value) {
if (key === 'currentPassword' && user[key].length < 8) {
updateWithKey('currentPassword', {
error: true,
});
newData = false;
break;
} else if (key === 'changePassword') {
parameters[key] = user[key].value === 'on';
newData = true;
} else {
parameters[key] = user[key].value;
newData = true;
}
}
}
if (newData) {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.put(`${endpoints.ucentralsec}/api/v1/user/${userId}`, parameters, options)
.then(() => {
addToast({
title: t('user.update_success_title'),
body: t('user.update_success'),
color: 'success',
autohide: true,
});
getUsers();
toggle();
})
.catch(() => {
addToast({
title: t('user.update_failure_title'),
body: t('user.update_failure'),
color: 'danger',
autohide: true,
});
getUser();
})
.finally(() => {
setLoading(false);
});
} else {
setLoading(false);
addToast({
title: t('user.update_success_title'),
body: t('user.update_success'),
color: 'success',
autohide: true,
});
getUsers();
toggle();
}
};
const addNote = (currentNote) => {
setLoading(true);
const parameters = {
id: userId,
notes: [{ note: currentNote }],
};
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.put(`${endpoints.ucentralsec}/api/v1/user/${userId}`, parameters, options)
.then(() => {
getUser();
})
.catch(() => {})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
if (userId) {
getUser();
}
if (policies.passwordPattern.length === 0) {
getPasswordPolicy();
}
}, [userId]);
return (
<Modal
t={t}
user={user}
updateUserWithId={updateWithId}
saveUser={updateUser}
loading={loading}
policies={policies}
show={show}
toggle={toggle}
addNote={addNote}
/>
);
};
EditUserModal.propTypes = {
userId: PropTypes.string.isRequired,
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
getUsers: PropTypes.func.isRequired,
};
export default React.memo(EditUserModal);

View File

@@ -0,0 +1,64 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { EventQueueModal as Modal, useAuth, useDevice, useToast } from 'ucentral-libs';
import { useTranslation } from 'react-i18next';
import axiosInstance from 'utils/axiosInstance';
const EventQueueModal = ({ show, toggle }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const { addToast } = useToast();
const [loading, setLoading] = useState(false);
const [result, setResult] = useState({});
const getQueue = () => {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
const parameters = {
serialNumber: deviceSerialNumber,
types: ['dhcp', 'wifi'],
};
axiosInstance
.post(
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/eventqueue`,
parameters,
options,
)
.then((response) => {
setResult(response.data);
})
.catch(() => {
addToast({
title: t('common.error'),
body: t('commands.unable_queue'),
color: 'danger',
autohide: true,
});
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
if (show) getQueue();
}, [show]);
return <Modal t={t} show={show} toggle={toggle} loading={loading} result={result} />;
};
EventQueueModal.propTypes = {
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
};
export default EventQueueModal;

View File

@@ -14,15 +14,15 @@ import {
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import 'react-widgets/styles.css'; import 'react-widgets/styles.css';
import { getToken } from 'utils/authHelper'; import { useAuth, useDevice } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
import styles from './index.module.scss';
const ConfigureModal = ({ show, toggleModal }) => { const ConfigureModal = ({ show, toggleModal }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [hadSuccess, setHadSuccess] = useState(false); const [hadSuccess, setHadSuccess] = useState(false);
const [hadFailure, setHadFailure] = useState(false); const [hadFailure, setHadFailure] = useState(false);
const [doingNow, setDoingNow] = useState(false); const [doingNow, setDoingNow] = useState(false);
@@ -30,7 +30,6 @@ const ConfigureModal = ({ show, toggleModal }) => {
const [keepRedirector, setKeepRedirector] = useState(true); const [keepRedirector, setKeepRedirector] = useState(true);
const [responseBody, setResponseBody] = useState(''); const [responseBody, setResponseBody] = useState('');
const [checkingIfSure, setCheckingIfSure] = useState(false); const [checkingIfSure, setCheckingIfSure] = useState(false);
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const toggleRedirector = () => { const toggleRedirector = () => {
setKeepRedirector(!keepRedirector); setKeepRedirector(!keepRedirector);
@@ -54,17 +53,21 @@ const ConfigureModal = ({ show, toggleModal }) => {
setWaiting(true); setWaiting(true);
const parameters = { const parameters = {
serialNumber: selectedDeviceId, serialNumber: deviceSerialNumber,
keepRedirector, keepRedirector,
}; };
const headers = { const headers = {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${currentToken}`,
}; };
axiosInstance axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/factory`, parameters, { headers }) .post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/factory`,
parameters,
{ headers },
)
.then(() => { .then(() => {
setHadSuccess(true); setHadSuccess(true);
}) })
@@ -90,9 +93,9 @@ const ConfigureModal = ({ show, toggleModal }) => {
<div> <div>
<CModalBody> <CModalBody>
<CAlert color="danger">{t('factory_reset.warning')}</CAlert> <CAlert color="danger">{t('factory_reset.warning')}</CAlert>
<CRow className={styles.spacedRow}> <CRow className="mt-3">
<p className={styles.spacedForm}>{t('factory_reset.redirector')}</p> <p className="pl-4">{t('factory_reset.redirector')}</p>
<CForm className={styles.spacedForm}> <CForm className="pl-4">
<CSwitch <CSwitch
color="primary" color="primary"
defaultChecked={keepRedirector} defaultChecked={keepRedirector}
@@ -116,7 +119,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
color="primary" color="primary"
onClick={() => confirmingIfSure()} onClick={() => confirmingIfSure()}
> >
{t('common.submit')} {t('factory_reset.reset')}
</CButton> </CButton>
<CButton <CButton
hidden={!checkingIfSure} hidden={!checkingIfSure}
@@ -124,7 +127,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
color="primary" color="primary"
onClick={() => doAction(false)} onClick={() => doAction(false)}
> >
{waiting && !doingNow ? 'Loading...' : 'Yes'} {' '} {waiting && !doingNow ? t('factory_reset.resetting') : t('common.yes')} {' '}
<CSpinner color="light" hidden={!waiting || doingNow} component="span" size="sm" /> <CSpinner color="light" hidden={!waiting || doingNow} component="span" size="sm" />
</CButton> </CButton>
<CButton color="secondary" onClick={toggleModal}> <CButton color="secondary" onClick={toggleModal}>

View File

@@ -1,7 +0,0 @@
.spacedRow {
margin-top: 20px;
}
.spacedForm {
padding-left: 5%;
}

View File

@@ -0,0 +1,71 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import axiosInstance from 'utils/axiosInstance';
import {
CButton,
CModal,
CModalBody,
CModalHeader,
CModalFooter,
CModalTitle,
} from '@coreui/react';
import { FirmwareHistoryTable, useAuth } from 'ucentral-libs';
const FirmwareHistoryModal = ({ serialNumber, show, toggle }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
const getHistory = () => {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`${endpoints.ucentralfms}/api/v1/revisionHistory/${serialNumber}`, options)
.then((response) => setData(response.data.history ?? []))
.catch(() => {})
.finally(() => setLoading(false));
};
useEffect(() => {
if (show) {
getHistory();
} else {
setData([]);
}
}, [show]);
return (
<CModal size="xl" show={show} onClose={toggle} scrollable>
<CModalHeader closeButton>
<CModalTitle>
#{serialNumber} {t('firmware.history_title')}
</CModalTitle>
</CModalHeader>
<CModalBody>
<FirmwareHistoryTable t={t} loading={loading} data={data} />
</CModalBody>
<CModalFooter>
<CButton color="secondary" onClick={toggle}>
{t('common.close')}
</CButton>
</CModalFooter>
</CModal>
);
};
FirmwareHistoryModal.propTypes = {
serialNumber: PropTypes.string.isRequired,
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
};
export default FirmwareHistoryModal;

View File

@@ -0,0 +1,74 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { CButton, CSpinner, CModalFooter } from '@coreui/react';
const UpgradeFooter = ({
isNow,
isShown,
isLoading,
action,
color,
variant,
block,
toggleParent,
}) => {
const { t } = useTranslation();
const [askingIfSure, setAskingIfSure] = useState(false);
const confirmingIfSure = () => {
setAskingIfSure(true);
};
useEffect(() => {
setAskingIfSure(false);
}, [isShown]);
return (
<CModalFooter>
<div hidden={!askingIfSure}>{t('common.are_you_sure')}</div>
<CButton
disabled={isLoading}
hidden={askingIfSure}
color={color}
variant={variant}
onClick={() => confirmingIfSure()}
block={block}
>
{isNow ? t('upgrade.upgrade') : t('common.schedule')}
</CButton>
<CButton
disabled={isLoading}
hidden={!askingIfSure}
color={color}
onClick={() => action()}
block={block}
>
{isLoading ? t('common.loading_ellipsis') : t('common.yes')}
<CSpinner color="light" hidden={!isLoading} component="span" size="sm" />
</CButton>
<CButton color="secondary" onClick={toggleParent}>
{t('common.cancel')}
</CButton>
</CModalFooter>
);
};
UpgradeFooter.propTypes = {
isNow: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired,
block: PropTypes.bool,
action: PropTypes.func.isRequired,
color: PropTypes.string,
variant: PropTypes.string,
toggleParent: PropTypes.func.isRequired,
isShown: PropTypes.bool.isRequired,
};
UpgradeFooter.defaultProps = {
color: 'primary',
variant: '',
block: false,
};
export default UpgradeFooter;

View File

@@ -0,0 +1,102 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { CModalBody } from '@coreui/react';
import { v4 as createUuid } from 'uuid';
import { useAuth } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance';
const UpgradeWaitingBody = ({ serialNumber }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const [currentStep, setCurrentStep] = useState(0);
const [secondsElapsed, setSecondsElapsed] = useState(0);
const [labelsToShow, setLabelsToShow] = useState(['upgrade.command_submitted']);
const getDeviceConnection = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}/status`,
options,
)
.then((response) => response.data.connected)
.catch(() => {});
};
const getFirmwareVersion = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}`, options)
.then((response) => response.data.firmware)
.catch(() => {});
};
const refreshStep = () => {
if (currentStep === 0 && !getDeviceConnection) {
const labelsToAdd = [
t('upgrade.device_disconnected'),
t('upgrade.device_upgrading_firmware'),
t('upgrade.waiting_for_device'),
];
setLabelsToShow([...labelsToShow, ...labelsToAdd]);
setCurrentStep(1);
} else if (currentStep === 1 && getDeviceConnection()) {
const newFirmware = `: ${getFirmwareVersion()}`;
const labelsToAdd = [
t('upgrade.device_reconnected'),
`${t('upgrade.new_version')}: ${newFirmware}`,
];
setLabelsToShow([...labelsToShow, ...labelsToAdd]);
setCurrentStep(2);
}
};
useEffect(() => {
const refreshIntervalId = setInterval(() => {
refreshStep();
}, 5000);
const timerIntervalId = setInterval(() => {
setSecondsElapsed(secondsElapsed + 1);
}, 1000);
return () => {
clearInterval(refreshIntervalId);
clearInterval(timerIntervalId);
};
}, []);
return (
<CModalBody>
<div className="consoleBox">
{labelsToShow.map((label) => (
<p key={createUuid()}>
{new Date().toString()}:{label}
</p>
))}
<p>
{t('common.seconds_elapsed')}:{secondsElapsed}
</p>
</div>
</CModalBody>
);
};
UpgradeWaitingBody.propTypes = {
serialNumber: PropTypes.string.isRequired,
};
export default UpgradeWaitingBody;

View File

@@ -4,39 +4,55 @@ import {
CModalHeader, CModalHeader,
CModalTitle, CModalTitle,
CModalBody, CModalBody,
CModalFooter, CSwitch,
CSpinner,
CCol, CCol,
CRow, CRow,
CInput, CInput,
CInvalidFeedback, CInvalidFeedback,
CModalFooter,
} from '@coreui/react'; } from '@coreui/react';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker'; import DatePicker from 'react-widgets/DatePicker';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useSelector } from 'react-redux'; import { dateToUnix } from 'utils/helper';
import { convertDateToUtc, convertDateFromUtc, dateToUnix } from 'utils/helper';
import 'react-widgets/styles.css'; import 'react-widgets/styles.css';
import { getToken } from 'utils/authHelper'; import { useDevice, useAuth } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus'; import eventBus from 'utils/eventBus';
import styles from './index.module.scss'; import ButtonFooter from './UpgradeFooter';
import UpgradeWaitingBody from './UpgradeWaitingBody';
const FirmwareUpgradeModal = ({ show, toggleModal }) => { const FirmwareUpgradeModal = ({ show, toggleModal }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [hadSuccess, setHadSuccess] = useState(false); const { currentToken, endpoints } = useAuth();
const [hadFailure, setHadFailure] = useState(false); const { deviceSerialNumber, getDeviceConnection } = useDevice();
const [waiting, setWaiting] = useState(false); const [isNow, setIsNow] = useState(true);
const [chosenDate, setChosenDate] = useState(new Date().toString()); const [waitForUpgrade, setWaitForUpgrade] = useState(false);
const [date, setDate] = useState(new Date().toString());
const [firmware, setFirmware] = useState(''); const [firmware, setFirmware] = useState('');
const [doingNow, setDoingNow] = useState(false);
const [validFirmware, setValidFirmware] = useState(true); const [validFirmware, setValidFirmware] = useState(true);
const [validDate, setValidDate] = useState(true); const [validDate, setValidDate] = useState(true);
const [responseBody, setResponseBody] = useState(''); const [blockFields, setBlockFields] = useState(false);
const [checkingIfSure, setCheckingIfSure] = useState(false); const [disabledWaiting, setDisableWaiting] = useState(false);
const [checkingIfNow, setCheckingIfNow] = useState(false); const [waitingForUpgrade, setWaitingForUpgrade] = useState(false);
const selectedDeviceId = useSelector((state) => state.selectedDeviceId); const [showWaitingConsole, setShowWaitingConsole] = useState(false);
const [deviceConnected, setDeviceConnected] = useState(true);
const toggleNow = () => {
if (isNow) {
setWaitForUpgrade(false);
setDisableWaiting(true);
} else {
setDisableWaiting(false);
}
setIsNow(!isNow);
};
const toggleWaitForUpgrade = () => {
setWaitForUpgrade(waitForUpgrade);
};
const formValidation = () => { const formValidation = () => {
let valid = true; let valid = true;
@@ -45,95 +61,91 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
valid = false; valid = false;
} }
if (chosenDate.trim() === '') { if (!isNow && date.trim() === '') {
setValidDate(false); setValidDate(false);
valid = false; valid = false;
} }
return valid; return valid;
}; };
const setDateToLate = () => {
const date = convertDateToUtc(new Date());
if (date.getHours() >= 3) {
date.setDate(date.getDate() + 1);
}
date.setHours(3);
date.setMinutes(0);
setChosenDate(convertDateFromUtc(date).toString());
};
const setDate = (date) => {
if (date) {
setChosenDate(date.toString());
}
};
const confirmingIfSure = () => {
setCheckingIfSure(true);
};
const confirmingIfNow = () => {
setCheckingIfNow(true);
};
useEffect(() => { useEffect(() => {
setHadSuccess(false); setBlockFields(false);
setHadFailure(false); setShowWaitingConsole(false);
setWaiting(false);
setChosenDate(new Date().toString());
setFirmware('');
setValidFirmware(true);
setResponseBody('');
setCheckingIfSure(false);
setDoingNow(false);
setCheckingIfNow(false);
}, [show]); }, [show]);
useEffect(() => { useEffect(() => {
setValidFirmware(true); setValidFirmware(true);
setValidDate(true); setValidDate(true);
}, [firmware, chosenDate]); }, [firmware, date]);
const postUpgrade = (isNow) => { useEffect(() => {
setDoingNow(isNow); if (deviceSerialNumber !== null && show) {
setHadFailure(false); const asyncGet = async () => {
setHadSuccess(false); const isConnected = await getDeviceConnection(
setWaiting(true); deviceSerialNumber,
currentToken,
endpoints.ucentralgw,
);
setDisableWaiting(!isConnected);
setDeviceConnected(isConnected);
};
asyncGet();
}
}, [show]);
const token = getToken(); const postUpgrade = () => {
const utcDate = new Date(chosenDate); if (formValidation()) {
const utcDateString = utcDate.toISOString(); setWaitingForUpgrade(true);
setBlockFields(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
serialNumber: deviceSerialNumber,
};
const headers = { const parameters = {
Accept: 'application/json', serialNumber: deviceSerialNumber,
Authorization: `Bearer ${token}`, when: isNow ? 0 : dateToUnix(date),
serialNumber: selectedDeviceId, uri: firmware,
}; };
axiosInstance
const parameters = { .post(
serialNumber: selectedDeviceId, `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/upgrade`,
when: isNow ? 0 : dateToUnix(utcDateString), parameters,
uri: firmware, { headers },
}; )
axiosInstance .then(() => {
.post(`/device/${encodeURIComponent(selectedDeviceId)}/upgrade`, parameters, { headers }) if (waitForUpgrade) {
.then(() => { setShowWaitingConsole(true);
setResponseBody('Command submitted successfully'); }
setHadSuccess(true); })
}) .catch(() => {})
.catch(() => { .finally(() => {
setResponseBody(t('commands.error')); setBlockFields(false);
setHadFailure(true); setWaitingForUpgrade(false);
}) eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
.finally(() => { });
setCheckingIfNow(false); }
setDoingNow(false);
setCheckingIfSure(false);
setWaiting(false);
eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
});
}; };
if (showWaitingConsole) {
return (
<CModal show={show} onClose={toggleModal}>
<CModalHeader closeButton>
<CModalTitle>{t('upgrade.title')}</CModalTitle>
</CModalHeader>
<CModalBody>
<UpgradeWaitingBody serialNumber={deviceSerialNumber} />
</CModalBody>
<CModalFooter>
<CButton color="secondary" onClick={toggleModal}>
{t('common.close')}
</CButton>
</CModalFooter>
</CModal>
);
}
return ( return (
<CModal show={show} onClose={toggleModal}> <CModal show={show} onClose={toggleModal}>
<CModalHeader closeButton> <CModalHeader closeButton>
@@ -141,94 +153,82 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
</CModalHeader> </CModalHeader>
<CModalBody> <CModalBody>
<h6>{t('upgrade.directions')}</h6> <h6>{t('upgrade.directions')}</h6>
<CRow className={styles.spacedRow}> <CRow className="mt-3">
<CCol> <CCol md="4" className="mt-2">
<CButton <p>{t('upgrade.firmware_uri')}</p>
color="primary"
onClick={() => (formValidation() ? confirmingIfNow() : null)}
disabled={waiting}
hidden={checkingIfNow}
block
>
{t('common.do_now')}
</CButton>
<CButton
color="primary"
onClick={() => (formValidation() ? postUpgrade(true) : null)}
disabled={waiting}
hidden={!checkingIfNow}
block
>
{waiting && doingNow ? t('common.loading_ellipsis') : t('common.confirm')}
<CSpinner hidden={!waiting || doingNow} component="span" size="sm" />
</CButton>
</CCol> </CCol>
<CCol> <CCol md="8">
<CButton disabled={waiting} block color="primary" onClick={setDateToLate}> <CInput
{t('common.later_tonight')} disabled={blockFields}
</CButton> className={('form-control', { 'is-invalid': !validFirmware })}
type="text"
id="uri"
name="uri-input"
autoComplete="firmware-uri"
onChange={(event) => setFirmware(event.target.value)}
value={firmware}
/>
<CInvalidFeedback>{t('upgrade.need_uri')}</CInvalidFeedback>
</CCol> </CCol>
</CRow> </CRow>
<CRow className={styles.spacedRow}> <CRow className="mt-3">
<CCol md="4" className={styles.spacedColumn}> <CCol md="8">
<p>{t('common.execute_now')}</p>
</CCol>
<CCol>
<CSwitch
disabled={blockFields}
color="primary"
defaultChecked={isNow}
onClick={toggleNow}
labelOn={t('common.yes')}
labelOff={t('common.no')}
/>
</CCol>
</CRow>
<CRow className="mt-3" hidden={isNow}>
<CCol md="4" className="mt-2">
<p>{t('upgrade.time')}</p> <p>{t('upgrade.time')}</p>
</CCol> </CCol>
<CCol xs="12" md="8"> <CCol xs="12" md="8">
<DatePicker <DatePicker
selected={chosenDate === '' ? new Date() : new Date(chosenDate)} selected={new Date(date)}
value={chosenDate === '' ? new Date() : new Date(chosenDate)} value={new Date(date)}
className={('form-control', { 'is-invalid': !validDate })} className={('form-control', { 'is-invalid': !validDate })}
includeTime includeTime
placeholder="Select custom date in UTC" disabled={blockFields}
disabled={waiting} onChange={(newDate) => setDate(newDate.toString())}
onChange={(date) => setDate(date)}
min={new Date()}
/> />
<CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback> <CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback>
</CCol> </CCol>
</CRow> </CRow>
<div>{t('upgrade.firmware_uri')}</div> <CRow className="mt-3" hidden={true || !isNow || disabledWaiting || !deviceConnected}>
<CInput <CCol md="8">
disabled={waiting} <p>
className={('form-control', { 'is-invalid': !validFirmware })} {t('upgrade.wait_for_upgrade')}
type="text" <b hidden={!disabledWaiting}> {t('upgrade.offline_device')}</b>
id="uri" </p>
name="uri-input" </CCol>
placeholder="https://s3-us-west-2.amazonaws.com/ucentral.arilia.com/20210508-linksys_ea8300-uCentral-trunk-43e1a2d-upgrade.bin" <CCol>
autoComplete="firmware-uri" <CSwitch
onChange={(event) => setFirmware(event.target.value)} disabled={blockFields || disabledWaiting}
value={firmware} color="primary"
/> defaultChecked={waitForUpgrade}
<CInvalidFeedback>{t('upgrade.need_uri')}</CInvalidFeedback> onClick={toggleWaitForUpgrade}
<div hidden={!hadSuccess && !hadFailure}> labelOn={t('common.yes')}
<div> labelOff={t('common.no')}
<pre className="ignore">{responseBody}</pre> />
</div> </CCol>
</div> </CRow>
</CModalBody> </CModalBody>
<CModalFooter> <ButtonFooter
<div hidden={!checkingIfSure}>{t('common.are_you_sure')}</div> isNow={isNow}
<CButton isShown={show}
hidden={checkingIfSure} isLoading={waitingForUpgrade}
disabled={waiting} action={postUpgrade}
color="primary" color="primary"
onClick={() => (formValidation() ? confirmingIfSure() : null)} toggleParent={toggleModal}
> />
{t('common.schedule')}
</CButton>
<CButton
hidden={!checkingIfSure}
disabled={waiting}
color="primary"
onClick={() => (formValidation() ? postUpgrade() : null)}
>
{waiting && !doingNow ? 'Loading...' : 'Yes'} {' '}
<CSpinner color="light" hidden={!waiting || doingNow} component="span" size="sm" />
</CButton>
<CButton color="secondary" onClick={toggleModal}>
{t('common.cancel')}
</CButton>
</CModalFooter>
</CModal> </CModal>
); );
}; };

View File

@@ -1,7 +0,0 @@
.spacedRow {
margin-top: 20px;
}
.spacedColumn {
margin-top: 7px;
}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import Chart from 'react-apexcharts';
const DeviceStatisticsChart = ({ chart }) => (
<div style={{ height: '360px' }}>
<Chart series={chart.data} options={chart.options} type="line" height="100%" />
</div>
);
DeviceStatisticsChart.propTypes = {
chart: PropTypes.instanceOf(Object).isRequired,
};
export default DeviceStatisticsChart;

View File

@@ -0,0 +1,68 @@
import React, { useState, useEffect } from 'react';
import {
CButton,
CModal,
CModalHeader,
CModalBody,
CModalTitle,
CModalFooter,
} from '@coreui/react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import axiosInstance from 'utils/axiosInstance';
import { useAuth, useDevice } from 'ucentral-libs';
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 ${currentToken}`,
},
};
axiosInstance
.get(
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?lastOnly=true`,
options,
)
.then((response) => {
setLatestStats(response.data);
})
.catch(() => {});
};
useEffect(() => {
if (show) {
getLatestStats();
}
}, [show]);
return (
<CModal size="lg" show={show} onClose={toggle}>
<CModalHeader closeButton>
<CModalTitle className="text-dark">{t('statistics.latest_statistics')}</CModalTitle>
</CModalHeader>
<CModalBody>
<pre className="ignore">{JSON.stringify(latestStats, null, 4)}</pre>
</CModalBody>
<CModalFooter>
<CButton color="secondary" onClick={toggle}>
{t('common.close')}
</CButton>
</CModalFooter>
</CModal>
);
};
LatestStatisticsModal.propTypes = {
toggle: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
};
export default LatestStatisticsModal;

View File

@@ -1,17 +1,20 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { v4 as createUuid } from 'uuid'; import { v4 as createUuid } from 'uuid';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import { getToken } from 'utils/authHelper'; import { useAuth, useDevice } from 'ucentral-libs';
import { unixToTime, capitalizeFirstLetter } from 'utils/helper'; import { unixToTime, capitalizeFirstLetter } from 'utils/helper';
import DeviceStatisticsChart from '../DeviceStatisticsChart'; import eventBus from 'utils/eventBus';
import DeviceStatisticsChart from './DeviceStatisticsChart';
const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => { const StatisticsChartList = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const { currentToken, endpoints } = useAuth();
const [deviceStats, setStats] = useState([]); const { deviceSerialNumber } = useDevice();
const [statOptions, setStatOptions] = useState({}); const [statOptions, setStatOptions] = useState({
interfaceList: [],
settings: {},
});
const transformIntoDataset = (data) => { const transformIntoDataset = (data) => {
const sortedData = data.sort((a, b) => { const sortedData = data.sort((a, b) => {
@@ -58,9 +61,11 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
// Looping through the interfaces of the log // Looping through the interfaces of the log
for (const inter of log.data.interfaces) { for (const inter of log.data.interfaces) {
interfaceList[interfaceTypes[inter.name]][0].data.push( interfaceList[interfaceTypes[inter.name]][0].data.push(
Math.floor(inter.counters.tx_bytes / 1024), 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,
); );
interfaceList[interfaceTypes[inter.name]][1].data.push(Math.floor(inter.counters.rx_bytes));
} }
} }
@@ -69,6 +74,9 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
id: 'chart', id: 'chart',
group: 'txrx', group: 'txrx',
}, },
stroke: {
curve: 'smooth',
},
xaxis: { xaxis: {
title: { title: {
text: 'Time', text: 'Time',
@@ -97,79 +105,76 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
}, },
}; };
setStatOptions(options); const newOptions = {
setStats(interfaceList); interfaceList,
settings: options,
};
if (statOptions !== newOptions) {
setStatOptions(newOptions);
}
}; };
const getStatistics = () => { const getStatistics = () => {
if (!loading) { const options = {
setLoading(true); headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
params: {
serialNumber: '24f5a207a130',
},
};
const options = { axiosInstance
headers: { .get(
Accept: 'application/json', `${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?newest=true&limit=50`,
Authorization: `Bearer ${getToken()}`, options,
}, )
params: { .then((response) => {
serialNumber: '24f5a207a130', transformIntoDataset(response.data.data);
}, })
}; .catch(() => {});
axiosInstance
.get(`/device/${selectedDeviceId}/statistics?newest=true&limit=50`, options)
.then((response) => {
transformIntoDataset(response.data.data);
})
.catch(() => {})
.finally(() => {
setLoading(false);
});
}
}; };
useEffect(() => { useEffect(() => {
if (selectedDeviceId) { if (deviceSerialNumber) {
getStatistics(); getStatistics();
} }
}, [selectedDeviceId]); }, [deviceSerialNumber]);
useEffect(() => { useEffect(() => {
if (lastRefresh !== '' && selectedDeviceId) { eventBus.on('refreshInterfaceStatistics', () => getStatistics());
getStatistics();
} return () => {
}, [lastRefresh]); eventBus.remove('refreshInterfaceStatistics');
};
}, []);
return ( return (
<div> <div>
{deviceStats.map((data) => ( {statOptions.interfaceList.map((data) => {
<div key={createUuid()}> const options = {
<DeviceStatisticsChart data,
key={createUuid()} options: {
data={data} ...statOptions.settings,
options={{ title: {
...statOptions, text: capitalizeFirstLetter(data[0].titleName),
title: { align: 'left',
text: capitalizeFirstLetter(data[0].titleName), style: {
align: 'left', fontSize: '25px',
style: {
fontSize: '25px',
},
}, },
}} },
/> },
</div> };
))} return (
<div key={createUuid()}>
<DeviceStatisticsChart chart={options} />
</div>
);
})}
</div> </div>
); );
}; };
StatisticsChartList.propTypes = { export default React.memo(StatisticsChartList);
lastRefresh: PropTypes.string,
selectedDeviceId: PropTypes.string.isRequired,
};
StatisticsChartList.defaultProps = {
lastRefresh: '',
};
export default StatisticsChartList;

View File

@@ -1,22 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import Chart from 'react-apexcharts';
import styles from './index.module.scss';
const DeviceStatisticsChart = ({ data, options }) => (
<div className={styles.chart}>
<Chart series={data} options={options} type="line" height="100%" />
</div>
);
DeviceStatisticsChart.propTypes = {
data: PropTypes.instanceOf(Array),
options: PropTypes.instanceOf(Object),
};
DeviceStatisticsChart.defaultProps = {
data: [],
options: {},
};
export default DeviceStatisticsChart;

View File

@@ -1,3 +0,0 @@
.chart {
height: 360px;
}

View File

@@ -1,47 +1,82 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types'; import { useHistory, useParams } from 'react-router-dom';
import { CCard, CCardHeader, CCardBody, CPopover, CRow, CCol } from '@coreui/react'; import { CCard, CCardHeader, CCardBody, CRow, CCol, CPopover, CButton } from '@coreui/react';
import { cilSync } from '@coreui/icons'; import { cilSync } from '@coreui/icons';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import StatisticsChartList from './containers/StatisticsChartList'; import eventBus from 'utils/eventBus';
import styles from './index.module.scss'; import LifetimeStatsmodal from 'components/LifetimeStatsModal';
import StatisticsChartList from './StatisticsChartList';
import LatestStatisticsmodal from './LatestStatisticsModal';
const DeviceStatisticsCard = ({ selectedDeviceId }) => { const DeviceStatisticsCard = () => {
const history = useHistory();
const { deviceId } = useParams();
const { t } = useTranslation(); const { t } = useTranslation();
const [lastRefresh, setLastRefresh] = useState(''); const [showLatestModal, setShowLatestModal] = useState(false);
const [showLifetimeModal, setShowLifetimeModal] = useState(false);
const toggleLatestModal = () => {
setShowLatestModal(!showLatestModal);
};
const toggleLifetimeModal = () => {
setShowLifetimeModal(!showLifetimeModal);
};
const goToAnalysis = () => {
history.push(`/devices/${deviceId}/wifianalysis`);
};
const refresh = () => { const refresh = () => {
setLastRefresh(new Date().toString()); eventBus.dispatch('refreshInterfaceStatistics', { message: 'Refresh interface statistics' });
}; };
return ( return (
<CCard> <div>
<CCardHeader> <CCard>
<CRow> <CCardHeader>
<CCol>{t('statistics.title')}</CCol> <CRow>
<CCol className={styles.alignRight}> <CCol>
<CPopover content={t('common.refresh')}> <div className="text-value-xxl pt-2">{t('statistics.title')}</div>
<CIcon </CCol>
onClick={refresh} <CCol sm="6" xxl="6">
name="cil-sync" <CRow>
content={cilSync} <CCol sm="1" xxl="5" />
size="lg" <CCol sm="4" xxl="2" className="text-right">
color="primary" <CButton color="secondary" onClick={goToAnalysis}>
/> {t('wifi_analysis.title')}
</CPopover> </CButton>
</CCol> </CCol>
</CRow> <CCol sm="3" xxl="2" className="text-right">
</CCardHeader> <CButton color="secondary" onClick={toggleLatestModal}>
<CCardBody className={styles.statsBody}> {t('statistics.show_latest')}
<StatisticsChartList selectedDeviceId={selectedDeviceId} lastRefresh={lastRefresh} /> </CButton>
</CCardBody> </CCol>
</CCard> <CCol sm="3" xxl="2" className="text-right">
<CButton color="secondary" onClick={toggleLifetimeModal}>
Lifetime Statistics
</CButton>
</CCol>
<CCol sm="1" xxl="1" className="text-center">
<CPopover content={t('common.refresh')}>
<CButton color="secondary" onClick={refresh} size="sm">
<CIcon content={cilSync} />
</CButton>
</CPopover>
</CCol>
</CRow>
</CCol>
</CRow>
</CCardHeader>
<CCardBody className="p-5">
<StatisticsChartList />
</CCardBody>
</CCard>
<LatestStatisticsmodal show={showLatestModal} toggle={toggleLatestModal} />
<LifetimeStatsmodal show={showLifetimeModal} toggle={toggleLifetimeModal} />
</div>
); );
}; };
DeviceStatisticsCard.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceStatisticsCard; export default DeviceStatisticsCard;

View File

@@ -1,7 +0,0 @@
.alignRight {
text-align: right;
}
.statsBody {
padding: 5%;
}

View File

@@ -1,23 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { CSelect } from '@coreui/react';
const LanguageSwitcher = () => {
const { i18n } = useTranslation();
return (
<CSelect
custom
defaultValue={i18n.language.split('-')[0]}
onChange={(e) => i18n.changeLanguage(e.target.value)}
>
<option value="de">Deutsche</option>
<option value="es">Español</option>
<option value="en">English</option>
<option value="fr">Français</option>
<option value="pt">Portugues</option>
</CSelect>
);
};
export default LanguageSwitcher;

View File

@@ -0,0 +1,48 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import axiosInstance from 'utils/axiosInstance';
import { useTranslation } from 'react-i18next';
import { LifetimeStatsModal as Modal, useAuth, useDevice } from 'ucentral-libs';
const LifetimeStatsModal = ({ show, toggle }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [loading, setLoading] = useState(false);
const [data, setData] = useState({});
const getData = () => {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?lifetime=true`,
options,
)
.then((response) => {
setData(response.data);
})
.catch(() => {})
.finally(() => setLoading(false));
};
useEffect(() => {
if (show) getData();
}, [show]);
return <Modal t={t} loading={loading} show={show} toggle={toggle} data={data} />;
};
LifetimeStatsModal.propTypes = {
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
};
export default LifetimeStatsModal;

View File

@@ -1,45 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CButton, CSpinner } from '@coreui/react';
const LoadingButton = ({
isLoading,
label,
isLoadingLabel,
action,
color,
variant,
block,
disabled,
}) => (
<CButton
variant={variant}
color={color}
onClick={action}
block={block}
disabled={isLoading || disabled}
>
{isLoading ? isLoadingLabel : label}
<CSpinner hidden={!isLoading} color="light" component="span" size="sm" />
</CButton>
);
LoadingButton.propTypes = {
isLoading: PropTypes.bool.isRequired,
block: PropTypes.bool,
disabled: PropTypes.bool,
label: PropTypes.string.isRequired,
isLoadingLabel: PropTypes.string.isRequired,
action: PropTypes.func.isRequired,
color: PropTypes.string,
variant: PropTypes.string,
};
LoadingButton.defaultProps = {
color: 'primary',
variant: '',
block: true,
disabled: false,
};
export default LoadingButton;

View File

@@ -0,0 +1,34 @@
import dagre from 'dagre';
import { isNode } from 'react-flow-renderer';
const setupDag = (elements, nodeWidth, nodeHeight) => {
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));
dagreGraph.setGraph({ rankdir: 'TB' });
elements.forEach((el) => {
if (isNode(el)) {
dagreGraph.setNode(el.id, { width: nodeWidth, height: nodeHeight });
} else {
dagreGraph.setEdge(el.source, el.target);
}
});
dagre.layout(dagreGraph);
return elements.map((el) => {
const newElement = el;
if (isNode(newElement)) {
const nodeWithPosition = dagreGraph.node(newElement.id);
newElement.targetPosition = 'top';
newElement.sourcePosition = 'bottom';
newElement.position = {
x: nodeWithPosition.x - nodeWidth / 2 + Math.random() / 1000,
y: nodeWithPosition.y - nodeHeight / 2,
};
}
return newElement;
});
};
export default setupDag;

View File

@@ -0,0 +1,162 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { CRow, CCol } from '@coreui/react';
import { NetworkDiagram as Graph } from 'ucentral-libs';
import { useTranslation } from 'react-i18next';
import createLayoutedElements from './dagreAdapter';
const associationStyle = {
background: '#3399ff',
color: 'white',
border: '1px solid #777',
width: 220,
padding: 10,
};
const recognizedRadioStyle = {
background: '#2eb85c',
color: 'white',
width: 220,
padding: 15,
};
const unrecognizedRadioStyle = {
background: '#e55353',
color: 'white',
width: 220,
padding: 15,
};
const recognizedRadioNode = (radio) => (
<div className="align-middle">
<h6 className="align-middle mb-0">
Radio #{radio.radio} ({radio.channel < 16 ? '2G' : '5G'})
</h6>
</div>
);
const unrecognizedRadioNode = (t, radio) => (
<div className="align-middle">
<h6 className="align-middle mb-0">
Radio #{radio.radioIndex} ({t('common.unrecognized')})
</h6>
</div>
);
const associationNode = (associationInfo) => (
<div>
<CRow>
<CCol className="text-center">
<h6>{associationInfo.bssid}</h6>
</CCol>
</CRow>
<CRow>
<CCol className="text-left pl-4">Rx Rate : {associationInfo.rxRate}</CCol>
</CRow>
<CRow>
<CCol className="text-left pl-4">Tx Rate : {associationInfo.txRate}</CCol>
</CRow>
</div>
);
const NetworkDiagram = ({ show, radios, associations }) => {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [elements, setElements] = useState([]);
const getX = (associationsAdded) => {
if (associationsAdded === 0) return 0;
if ((associationsAdded + 1) % 2 === 0) return -140 * (associationsAdded + 1);
return 140 * associationsAdded;
};
const parseData = () => {
setLoading(true);
const newElements = [];
const radiosAdded = {};
// Creating the radio nodes
for (const radio of radios) {
if (radiosAdded[radio.radio] === undefined) {
newElements.push({
id: `r-${radio.radio}`,
data: { label: recognizedRadioNode(radio) },
position: { x: 0, y: 200 * radio.radio },
type: 'input',
style: recognizedRadioStyle,
});
radiosAdded[radio.radio] = 0;
}
}
// Creating the association nodes and their edges
for (let i = 0; i < associations.length; i += 1) {
const assoc = associations[i];
// If the radio has not been added, we create a new unknown radio based on its index
if (radiosAdded[assoc.radio.radioIndex] === undefined) {
newElements.push({
id: `r-${assoc.radio.radioIndex}`,
data: { label: unrecognizedRadioNode(t, assoc.radio) },
position: { x: 0, y: 200 * assoc.radio.radioIndex },
type: 'input',
style: unrecognizedRadioStyle,
});
radiosAdded[assoc.radio.radioIndex] = 0;
}
// Adding the association
newElements.push({
id: `a-${assoc.bssid}`,
data: { label: associationNode(assoc) },
position: {
x: getX(radiosAdded[assoc.radio.radioIndex]),
y: 80 + 240 * assoc.radio.radioIndex,
},
style: associationStyle,
type: 'output',
});
radiosAdded[assoc.radio.radioIndex] += 1;
// Creating the edge
newElements.push({
id: `e-${assoc.radio.radioIndex}-${assoc.bssid}`,
source: `r-${assoc.radio.radioIndex}`,
target: `a-${assoc.bssid}`,
arrowHeadType: 'arrowclosed',
});
}
setElements(newElements);
setLoading(false);
};
useEffect(() => {
if (radios !== null && associations !== null) {
parseData();
}
}, [radios, associations]);
return (
<Graph
show={show}
loading={loading}
elements={createLayoutedElements(elements, 220, 80)}
setElements={setElements}
/>
);
};
NetworkDiagram.propTypes = {
show: PropTypes.bool,
radios: PropTypes.instanceOf(Array),
associations: PropTypes.instanceOf(Array),
};
NetworkDiagram.defaultProps = {
show: true,
radios: null,
associations: null,
};
export default NetworkDiagram;

View File

@@ -5,45 +5,32 @@ import {
CModalTitle, CModalTitle,
CModalBody, CModalBody,
CModalFooter, CModalFooter,
CSpinner, CSwitch,
CCol, CCol,
CRow, CRow,
CInvalidFeedback,
} from '@coreui/react'; } from '@coreui/react';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker'; import DatePicker from 'react-widgets/DatePicker';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useSelector } from 'react-redux'; import { dateToUnix } from 'utils/helper';
import { convertDateToUtc, convertDateFromUtc, dateToUnix } from 'utils/helper';
import 'react-widgets/styles.css'; import 'react-widgets/styles.css';
import { getToken } from 'utils/authHelper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus'; import eventBus from 'utils/eventBus';
import LoadingButton from 'components/LoadingButton'; import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
import styles from './index.module.scss';
const ActionModal = ({ show, toggleModal }) => { const ActionModal = ({ show, toggleModal }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [hadSuccess, setHadSuccess] = useState(false); const { currentToken, endpoints } = useAuth();
const [hadFailure, setHadFailure] = useState(false); const { deviceSerialNumber } = useDevice();
const [waiting, setWaiting] = useState(false); const [waiting, setWaiting] = useState(false);
const [validDate, setValidDate] = useState(true); const [result, setResult] = useState(null);
const [chosenDate, setChosenDate] = useState(new Date().toString()); const [chosenDate, setChosenDate] = useState(new Date().toString());
const [doingNow, setDoingNow] = useState(false); const [isNow, setIsNow] = useState(false);
const [responseBody, setResponseBody] = useState('');
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const setDateToLate = () => { const toggleNow = () => {
const date = convertDateToUtc(new Date()); setIsNow(!isNow);
if (date.getHours() >= 3) {
date.setDate(date.getDate() + 1);
}
date.setHours(3);
date.setMinutes(0);
setChosenDate(convertDateFromUtc(date).toString());
}; };
const setDate = (date) => { const setDate = (date) => {
@@ -54,47 +41,40 @@ const ActionModal = ({ show, toggleModal }) => {
useEffect(() => { useEffect(() => {
if (show) { if (show) {
setHadSuccess(false); setResult(null);
setHadFailure(false);
setWaiting(false); setWaiting(false);
setDoingNow(false);
setChosenDate(new Date().toString()); setChosenDate(new Date().toString());
setResponseBody('');
setValidDate(true);
} }
}, [show]); }, [show]);
const doAction = (isNow) => { const doAction = () => {
if (isNow !== undefined) setDoingNow(isNow);
setHadFailure(false);
setHadSuccess(false);
setWaiting(true); setWaiting(true);
const token = getToken();
const utcDate = new Date(chosenDate); const utcDate = new Date(chosenDate);
const utcDateString = utcDate.toISOString();
const parameters = { const parameters = {
serialNumber: selectedDeviceId, serialNumber: deviceSerialNumber,
when: isNow ? 0 : dateToUnix(utcDateString), when: isNow ? 0 : dateToUnix(utcDate),
}; };
const headers = { const headers = {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${token}`, Authorization: `Bearer ${currentToken}`,
}; };
axiosInstance axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/reboot`, parameters, { headers }) .post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/reboot`,
parameters,
{ headers },
)
.then(() => { .then(() => {
setHadSuccess(true); setResult('success');
}) })
.catch(() => { .catch(() => {
setResponseBody(t('commands.error')); setResult('error');
setHadFailure(true);
}) })
.finally(() => { .finally(() => {
setDoingNow(false);
setWaiting(false); setWaiting(false);
eventBus.dispatch('actionCompleted', { message: 'An action has been completed' }); eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
}); });
@@ -105,62 +85,49 @@ const ActionModal = ({ show, toggleModal }) => {
<CModalHeader closeButton> <CModalHeader closeButton>
<CModalTitle>{t('reboot.title')}</CModalTitle> <CModalTitle>{t('reboot.title')}</CModalTitle>
</CModalHeader> </CModalHeader>
{hadSuccess ? ( {result === 'success' ? (
<SuccessfulActionModalBody toggleModal={toggleModal} /> <SuccessfulActionModalBody toggleModal={toggleModal} />
) : ( ) : (
<div> <div>
<CModalBody> <CModalBody>
<h6>{t('reboot.directions')}</h6> <CRow>
<CRow className={styles.spacedRow}> <CCol md="8">
<CCol> <p>{t('reboot.now')}</p>
<CButton onClick={() => doAction(true)} disabled={waiting} block color="primary">
{waiting && doingNow ? t('common.loading_ellipsis') : t('common.do_now')}
<CSpinner
color="light"
hidden={!waiting || !doingNow}
component="span"
size="sm"
/>
</CButton>
</CCol> </CCol>
<CCol> <CCol>
<CButton disabled={waiting} block color="primary" onClick={() => setDateToLate()}> <CSwitch
{t('common.later_tonight')} disabled={waiting}
</CButton> color="primary"
defaultChecked={isNow}
onClick={toggleNow}
labelOn={t('common.yes')}
labelOff={t('common.no')}
/>
</CCol> </CCol>
</CRow> </CRow>
<CRow className={styles.spacedRow}> <CRow hidden={isNow} className="mt-2">
<CCol md="4" className={styles.spacedColumn}> <CCol md="4" className="pt-2">
<p>{t('common.date')}:</p> <p>{t('common.custom_date')}:</p>
</CCol> </CCol>
<CCol xs="12" md="8"> <CCol xs="12" md="8">
<DatePicker <DatePicker
selected={new Date(chosenDate)} selected={new Date(chosenDate)}
includeTime includeTime
className={('form-control', { 'is-invalid': !validDate })}
value={new Date(chosenDate)} value={new Date(chosenDate)}
placeholder="Select custom date" placeholder="Select custom date"
disabled={waiting} disabled={waiting}
onChange={(date) => setDate(date)} onChange={(date) => setDate(date)}
min={convertDateToUtc(new Date())} min={new Date()}
/> />
</CCol> </CCol>
</CRow> </CRow>
<CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback>
<div hidden={!hadSuccess && !hadFailure}>
<div>
<pre className="ignore">{responseBody}</pre>
</div>
</div>
</CModalBody> </CModalBody>
<CModalFooter> <CModalFooter>
<LoadingButton <LoadingButton
label={t('common.schedule')} label={isNow ? t('reboot.title') : t('common.schedule')}
isLoadingLabel={t('common.loading_ellipsis')} isLoadingLabel={t('common.loading_ellipsis')}
isLoading={waiting} isLoading={waiting}
action={doAction} action={doAction}
variant="outline"
block={false} block={false}
disabled={waiting} disabled={waiting}
/> />

View File

@@ -1,7 +0,0 @@
.spacedRow {
margin-top: 20px;
}
.spacedColumn {
margin-top: 7px;
}

View File

@@ -0,0 +1,119 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { CAlert, CModalBody, CButton, CSpinner, CModalFooter } from '@coreui/react';
import { useAuth } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance';
const WaitingForTraceBody = ({ serialNumber, commandUuid, toggle }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const [secondsElapsed, setSecondsElapsed] = useState(0);
const [waitingForFile, setWaitingForFile] = useState(true);
const [error, setError] = useState(null);
const getTraceResult = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`${endpoints.ucentralgw}/api/v1/command/${encodeURIComponent(commandUuid)}`, options)
.then((response) => {
if (response.data.waitingForFile === 0) {
setWaitingForFile(false);
}
if (response.data.errorCode !== 0) {
setWaitingForFile(false);
setError(response.data.errorText);
}
})
.catch(() => {});
};
const downloadTrace = () => {
const options = {
headers: {
Accept: 'application/octet-stream',
Authorization: `Bearer ${currentToken}`,
},
responseType: 'arraybuffer',
};
axiosInstance
.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');
link.href = window.URL.createObjectURL(blob);
link.download = `Trace_${commandUuid}.pcap`;
link.click();
});
};
useEffect(() => {
const timer = setInterval(() => {
setSecondsElapsed(secondsElapsed + 1);
}, 1000);
if (!waitingForFile) {
clearInterval(timer);
}
return () => {
clearInterval(timer);
};
}, [waitingForFile, secondsElapsed]);
useEffect(() => {
const refreshStatus = setInterval(() => {
getTraceResult();
}, 5000);
if (!waitingForFile) {
clearInterval(refreshStatus);
}
return () => {
clearInterval(refreshStatus);
};
}, [waitingForFile]);
return (
<div>
<CModalBody>
<h6>{t('trace.waiting_seconds', { seconds: secondsElapsed })}</h6>
<p>{t('trace.waiting_directions')}</p>
<div className="d-flex align-middle justify-content-center">
<CSpinner hidden={!waitingForFile} />
<CButton
hidden={waitingForFile || error}
onClick={downloadTrace}
disabled={waitingForFile || error}
color="primary"
>
{t('trace.download_trace')}
</CButton>
<CAlert hidden={waitingForFile || !error} className="my-3" color="danger">
{t('trace.trace_not_successful', { error })}
</CAlert>
</div>
</CModalBody>
<CModalFooter>
<CButton color="secondary" block onClick={toggle}>
{t('common.exit')}
</CButton>
</CModalFooter>
</div>
);
};
WaitingForTraceBody.propTypes = {
serialNumber: PropTypes.string.isRequired,
commandUuid: PropTypes.string.isRequired,
toggle: PropTypes.func.isRequired,
};
export default WaitingForTraceBody;

Some files were not shown because too many files have changed in this diff Show More